]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch 'testing' of gitorious.org:statusnet/mainline
authorBrion Vibber <brion@pobox.com>
Thu, 28 Jan 2010 01:39:03 +0000 (17:39 -0800)
committerBrion Vibber <brion@pobox.com>
Thu, 28 Jan 2010 01:39:03 +0000 (17:39 -0800)
101 files changed:
EVENTS.txt
README
actions/accessadminpanel.php [new file with mode: 0644]
actions/apiaccountratelimitstatus.php
actions/apiaccountverifycredentials.php
actions/apifriendshipsexists.php
actions/apifriendshipsshow.php
actions/apigroupismember.php
actions/apigroupshow.php
actions/apihelptest.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/apistatusnetconfig.php
actions/apistatusnetversion.php
actions/apiusershow.php
actions/avatarsettings.php
actions/designadminpanel.php
actions/doc.php
actions/editapplication.php [new file with mode: 0644]
actions/grouplogo.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/pathsadminpanel.php
actions/replies.php
actions/showapplication.php [new file with mode: 0644]
actions/showfavorites.php
actions/showgroup.php
actions/showstream.php
actions/siteadminpanel.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/Status_network.php
classes/Token.php
classes/statusnet.ini
db/rc3to09.sql [deleted file]
db/rc3torc4.sql [new file with mode: 0644]
db/site.sql
db/statusnet.sql
js/geometa.js
lib/action.php
lib/adminpanelaction.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/htmloutputter.php
lib/iomaster.php
lib/personalgroupnav.php
lib/queuemanager.php
lib/router.php
lib/spawningdaemon.php
lib/stompqueuemanager.php
lib/uapplugin.php [new file with mode: 0644]
plugins/Adsense/AdsensePlugin.php [new file with mode: 0644]
plugins/BlankAd/BlankAdPlugin.php [new file with mode: 0644]
plugins/BlankAd/redpixel.png [new file with mode: 0644]
plugins/Facebook/facebookaction.php
plugins/OpenX/OpenXPlugin.php [new file with mode: 0644]
plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php
plugins/PoweredByStatusNet/locale/PoweredByStatusNet.po [new file with mode: 0644]
plugins/PubSubHubBub/PubSubHubBubPlugin.php
plugins/Realtime/RealtimePlugin.php
plugins/TwitterBridge/TwitterBridgePlugin.php
plugins/TwitterBridge/twitterauthorization.php
plugins/TwitterBridge/twitterlogin.php [new file with mode: 0644]
plugins/TwitterBridge/twitteroauthclient.php
plugins/TwitterBridge/twittersettings.php
scripts/console.php
scripts/queuectl.php [new file with mode: 0755]
scripts/queuedaemon.php
scripts/sendemail.php [new file with mode: 0755]
scripts/setup.cfg.sample
scripts/setup_status_network.sh
scripts/xmppdaemon.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/statusupdate.php [new file with mode: 0644]
tests/oauth/verifycreds.php [new file with mode: 0755]
theme/base/css/display.css
theme/base/css/uap.css [new file with mode: 0644]
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
 
diff --git a/README b/README
index 6022887899c27db6e644b7226778c56d117d5e57..f83873ca84487803a276de5fbc71cc6b4b9270df 100644 (file)
--- a/README
+++ b/README
@@ -1492,6 +1492,15 @@ disabled: whether to enable this command. If enabled, users who send
          should enable it only after you've convinced yourself that
          it is safe. Default is 'false'.
 
+singleuser
+----------
+
+If an installation has only one user, this can simplify a lot of the
+interface. It also makes the user's profile the root URL.
+
+enabled: Whether to run in "single user mode". Default false.
+nickname: nickname of the single user.
+
 Plugins
 =======
 
diff --git a/actions/accessadminpanel.php b/actions/accessadminpanel.php
new file mode 100644 (file)
index 0000000..4768e2f
--- /dev/null
@@ -0,0 +1,192 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Site access administration panel
+ *
+ * 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 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);
+}
+
+/**
+ * Administer site access settings
+ *
+ * @category Admin
+ * @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 AccessadminpanelAction extends AdminPanelAction
+{
+    /**
+     * Returns the page title
+     *
+     * @return string page title
+     */
+
+    function title()
+    {
+        return _('Access');
+    }
+
+    /**
+     * Instructions for using this form.
+     *
+     * @return string instructions
+     */
+
+    function getInstructions()
+    {
+        return _('Site access settings');
+    }
+
+    /**
+     * Show the site admin panel form
+     *
+     * @return void
+     */
+
+    function showForm()
+    {
+        $form = new AccessAdminPanelForm($this);
+        $form->show();
+        return;
+    }
+
+    /**
+     * Save settings from the form
+     *
+     * @return void
+     */
+
+    function saveSettings()
+    {
+        static $booleans = array('site' => array('private', 'inviteonly', 'closed'));
+
+        foreach ($booleans as $section => $parts) {
+            foreach ($parts as $setting) {
+                $values[$section][$setting] = ($this->boolean($setting)) ? 1 : 0;
+            }
+        }
+
+        $config = new Config();
+
+        $config->query('BEGIN');
+
+        foreach ($booleans as $section => $parts) {
+            foreach ($parts as $setting) {
+                Config::save($section, $setting, $values[$section][$setting]);
+            }
+        }
+
+        $config->query('COMMIT');
+
+        return;
+    }
+
+}
+
+class AccessAdminPanelForm extends AdminForm
+{
+    /**
+     * ID of the form
+     *
+     * @return int ID of the form
+     */
+
+    function id()
+    {
+        return 'form_site_admin_panel';
+    }
+
+    /**
+     * class of the form
+     *
+     * @return string class of the form
+     */
+
+    function formClass()
+    {
+        return 'form_settings';
+    }
+
+    /**
+     * Action of the form
+     *
+     * @return string URL of the action
+     */
+
+    function action()
+    {
+        return common_local_url('accessadminpanel');
+    }
+
+    /**
+     * Data elements of the form
+     *
+     * @return void
+     */
+
+    function formData()
+    {
+       $this->out->elementStart('fieldset', array('id' => 'settings_admin_access'));
+        $this->out->element('legend', null, _('Registration'));
+        $this->out->elementStart('ul', 'form_data');
+        $this->li();
+        $this->out->checkbox('private', _('Private'),
+                             (bool) $this->value('private'),
+                             _('Prohibit anonymous users (not logged in) from viewing site?'));
+        $this->unli();
+
+        $this->li();
+        $this->out->checkbox('inviteonly', _('Invite only'),
+                             (bool) $this->value('inviteonly'),
+                             _('Make registration invitation only.'));
+        $this->unli();
+
+        $this->li();
+        $this->out->checkbox('closed', _('Closed'),
+                             (bool) $this->value('closed'),
+                             _('Disable new registrations.'));
+        $this->unli();
+        $this->out->elementEnd('ul');
+        $this->out->elementEnd('fieldset');
+    }
+
+    /**
+     * Action elements
+     *
+     * @return void
+     */
+
+    function formActions()
+    {
+        $this->out->submit('submit', _('Save'), 'submit', null, _('Save access settings'));
+    }
+
+}
index 1a5afd552c90d8c1d43b6ecdb0cd257dfd40d356..f19e315bf88e5d5b0dc2b5f4b355bc5de4cbf62f 100644 (file)
@@ -105,7 +105,22 @@ class ApiAccountRateLimitStatusAction extends ApiBareAuthAction
              print json_encode($out);
          }
 
-         $this->endDocument($this->format);
+        $this->endDocument($this->format);
+    }
+
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
     }
 
 }
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;
+    }
+    
 }
index c040b9f6ad87edc455137432ca0d90d6774440fc..ca62b5f51420b81009d766bdebb30e6fd17d359d 100644 (file)
@@ -116,4 +116,19 @@ class ApiFriendshipsExistsAction extends ApiPrivateAuthAction
         }
     }
 
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
 }
index 73ecc9249a1d437c43aa5f74cefe6ed46f865294..f29e63713759ac007f81afb7b63823fb9b144276 100644 (file)
@@ -87,7 +87,6 @@ class ApiFriendshipsShowAction extends ApiBareAuthAction
         return true;
     }
 
-
     /**
      * Determines whether this API resource requires auth.  Overloaded to look
      * return true in case source_id and source_screen_name are both empty
@@ -165,4 +164,19 @@ class ApiFriendshipsShowAction extends ApiBareAuthAction
 
     }
 
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
 }
index 69ead0b531af95c53f9cf82148eb5a3c19ddd30e..97f8435614e9da39662c67155f0fd1fba1daf3d6 100644 (file)
@@ -119,4 +119,19 @@ class ApiGroupIsMemberAction extends ApiBareAuthAction
         }
     }
 
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
 }
index 7aa49b1bf37c71c77763ea781b199814eca6ed76..95d6f95afa8a18be770d5d8a0d00225563638d16 100644 (file)
@@ -149,4 +149,19 @@ class ApiGroupShowAction extends ApiPrivateAuthAction
         return null;
     }
 
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
 }
index 7b4017531cc73de9e5de3a516f8fa1ef67f9564b..d0e9e4926f55ca85adfce968ef50a9feb9cb83f3 100644 (file)
@@ -92,5 +92,20 @@ class ApiHelpTestAction extends ApiPrivateAuthAction
         }
     }
 
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
 }
 
diff --git a/actions/apioauthaccesstoken.php b/actions/apioauthaccesstoken.php
new file mode 100644 (file)
index 0000000..887df4c
--- /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_WARNING, '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..4fa626d
--- /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_WARNING, 'API OAuthException - ' . $e->getMessage());
+            header('HTTP/1.1 401 Unauthorized');
+            header('Content-Type: text/html; charset=utf-8');
+            print $e->getMessage() . "\n";
+        }
+    }
+
+}
index 9d831b9dbbc42c855dec90813c04861b0acd9029..bf367e1e181741e4d5626bd6332f0ae5432cc713 100644 (file)
@@ -28,7 +28,7 @@
  * @author    Mike Cochrane <mikec@mikenz.geek.nz>
  * @author    Robin Millette <robin@millette.info>
  * @author    Zach Copley <zach@status.net>
- * @copyright 2009 StatusNet, Inc.
+ * @copyright 2009-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/
  */
@@ -79,12 +79,16 @@ class ApiStatusesUpdateAction extends ApiAuthAction
     {
         parent::prepare($args);
 
-        $this->user   = $this->auth_user;
         $this->status = $this->trimmed('status');
         $this->source = $this->trimmed('source');
         $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';
         }
@@ -140,7 +144,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction
             return;
         }
 
-        if (empty($this->user)) {
+        if (empty($this->auth_user)) {
             $this->clientError(_('No such user.'), 404, $this->format);
             return;
         }
@@ -167,7 +171,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction
         // Check for commands
 
         $inter = new CommandInterpreter();
-        $cmd = $inter->handle_command($this->user, $status_shortened);
+        $cmd = $inter->handle_command($this->auth_user, $status_shortened);
 
         if ($cmd) {
 
@@ -179,7 +183,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction
             // And, it returns your last status whether the cmd was successful
             // or not!
 
-            $this->notice = $this->user->getCurrentNotice();
+            $this->notice = $this->auth_user->getCurrentNotice();
 
         } else {
 
@@ -206,7 +210,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction
             $upload = null;
 
             try {
-                $upload = MediaFile::fromUpload('media', $this->user);
+                $upload = MediaFile::fromUpload('media', $this->auth_user);
             } catch (ClientException $ce) {
                 $this->clientError($ce->getMessage());
                 return;
@@ -229,19 +233,19 @@ class ApiStatusesUpdateAction extends ApiAuthAction
 
             $options = array('reply_to' => $reply_to);
 
-            if ($this->user->shareLocation()) {
+            if ($this->auth_user->shareLocation()) {
 
                 $locOptions = Notice::locationOptions($this->lat,
                                                       $this->lon,
                                                       null,
                                                       null,
-                                                      $this->user->getProfile());
+                                                      $this->auth_user->getProfile());
 
                 $options = array_merge($options, $locOptions);
             }
 
             $this->notice =
-              Notice::saveNew($this->user->id,
+              Notice::saveNew($this->auth_user->id,
                               $content,
                               $this->source,
                               $options);
@@ -250,7 +254,6 @@ class ApiStatusesUpdateAction extends ApiAuthAction
                 $upload->attachToNotice($this->notice);
             }
 
-
         }
 
         $this->showNotice();
index ab96f2e5f9f27e1466ba66dc091a2fe8d62c850f..dc1ab8685b8a05dee7db140b0b0e3920b0c8e26a 100644 (file)
@@ -138,5 +138,20 @@ class ApiStatusnetConfigAction extends ApiAction
         }
     }
 
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
 }
 
index 5109cd8062090a677069a58c136cc001e461c9a1..d0948075978210c76a8816fa7b68af9b45090e1a 100644 (file)
@@ -98,5 +98,20 @@ class ApiStatusnetVersionAction extends ApiPrivateAuthAction
         }
     }
 
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
 }
 
index a7fe0dcc1e2cddfa68a6f23038e53e7c7bcc4bc2..6c8fad49ba9f5caac1ea15fb73867606a0f4d056 100644 (file)
@@ -123,4 +123,19 @@ class ApiUserShowAction extends ApiPrivateAuthAction
 
     }
 
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
 }
index cf4525552029f1fde641d6311235991cca3732a5..6a7398746ae8f8ec2a5916812f588202c2487f8d 100644 (file)
@@ -416,8 +416,8 @@ class AvatarsettingsAction extends AccountSettingsAction
         parent::showScripts();
 
         if ($this->mode == 'crop') {
-            $this->script('js/jcrop/jquery.Jcrop.min.js');
-            $this->script('js/jcrop/jquery.Jcrop.go.js');
+            $this->script('jcrop/jquery.Jcrop.min.js');
+            $this->script('jcrop/jquery.Jcrop.go.js');
         }
 
         $this->autofocus('avatarfile');
index 72ad6ade2a12ced8c9dadba73b51069aec2e3119..30e8bde1a4e5d344ffa221a770183720cbfda204 100644 (file)
@@ -302,8 +302,8 @@ class DesignadminpanelAction extends AdminPanelAction
     {
         parent::showScripts();
 
-        $this->script('js/farbtastic/farbtastic.js');
-        $this->script('js/userdesign.go.js');
+        $this->script('farbtastic/farbtastic.js');
+        $this->script('userdesign.go.js');
 
         $this->autofocus('design_background-image_file');
     }
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 f197aef33ec489baf748ab2b17fbb5f1e90f1227..3c9b562962e34944ab7864b8f96157de90d114b7 100644 (file)
@@ -437,8 +437,8 @@ class GrouplogoAction extends GroupDesignAction
         parent::showScripts();
 
         if ($this->mode == 'crop') {
-            $this->script('js/jcrop/jquery.Jcrop.min.js');
-            $this->script('js/jcrop/jquery.Jcrop.go.js');
+            $this->script('jcrop/jquery.Jcrop.min.js');
+            $this->script('jcrop/jquery.Jcrop.go.js');
         }
 
         $this->autofocus('avatarfile');
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..c2e8d44
--- /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 3779fcfaaaeb94c90bd4dc47c4d47be23386524f..9155a7e42856ae19b61b405973c5de83aa3b3d5e 100644 (file)
@@ -24,7 +24,7 @@
  * @author    Evan Prodromou <evan@status.net>
  * @author    Zach Copley <zach@status.net>
  * @author    Sarven Capadisli <csarven@status.net>
- * @copyright 2008-2009 StatusNet, Inc.
+ * @copyright 2008-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/
  */
@@ -98,6 +98,11 @@ class PathsadminpanelAction extends AdminPanelAction
             'background' => array('server', 'dir', 'path')
         );
 
+       // XXX: If we're only going to have one boolean on thi page we
+       // can remove some of the boolean processing code --Z
+
+       static $booleans = array('site' => array('fancy'));
+
         $values = array();
 
         foreach ($settings as $section => $parts) {
@@ -106,6 +111,12 @@ class PathsadminpanelAction extends AdminPanelAction
             }
         }
 
+        foreach ($booleans as $section => $parts) {
+            foreach ($parts as $setting) {
+                $values[$section][$setting] = ($this->boolean($setting)) ? 1 : 0;
+            }
+        }
+
         $this->validate($values);
 
         // assert(all values are valid);
@@ -120,7 +131,13 @@ class PathsadminpanelAction extends AdminPanelAction
             }
         }
 
-        $config->query('COMMIT');
+       foreach ($booleans as $section => $parts) {
+           foreach ($parts as $setting) {
+                Config::save($section, $setting, $values[$section][$setting]);
+            }
+       }
+
+       $config->query('COMMIT');
 
         return;
     }
@@ -213,10 +230,14 @@ class PathsAdminPanelForm extends AdminForm
 
     function formData()
     {
-        $this->out->elementStart('fieldset', array('id' => 'settings_paths_locale'));
+       $this->out->elementStart('fieldset', array('id' => 'settings_paths_locale'));
         $this->out->element('legend', null, _('Site'), 'site');
         $this->out->elementStart('ul', 'form_data');
 
+       $this->li();
+        $this->input('server', _('Server'), _('Site\'s server hostname.'));
+        $this->unli();
+
         $this->li();
         $this->input('path', _('Path'), _('Site path'));
         $this->unli();
@@ -225,6 +246,12 @@ class PathsAdminPanelForm extends AdminForm
         $this->input('locale_path', _('Path to locales'), _('Directory path to locales'), 'site');
         $this->unli();
 
+       $this->li();
+        $this->out->checkbox('fancy', _('Fancy URLs'),
+                             (bool) $this->value('fancy'),
+                             _('Use fancy (more readable and memorable) URLs?'));
+       $this->unli();
+
         $this->out->elementEnd('ul');
         $this->out->elementEnd('fieldset');
 
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..a6ff425
--- /dev/null
@@ -0,0 +1,327 @@
+<?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 dd388a18a2f1c6ac757b9196fcb59b85329d95bd..8c8f8b3742bf5c28590281c59dc14504c828b521 100644 (file)
@@ -24,7 +24,7 @@
  * @author    Evan Prodromou <evan@status.net>
  * @author    Zach Copley <zach@status.net>
  * @author    Sarven Capadisli <csarven@status.net>
- * @copyright 2008-2009 StatusNet, Inc.
+ * @copyright 2008-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/
  */
@@ -95,8 +95,6 @@ class SiteadminpanelAction extends AdminPanelAction
                                                  'site', 'textlimit', 'dupelimit'),
                                  'snapshot' => array('run', 'reporturl', 'frequency'));
 
-        static $booleans = array('site' => array('private', 'inviteonly', 'closed', 'fancy'));
-
         $values = array();
 
         foreach ($settings as $section => $parts) {
@@ -105,12 +103,6 @@ class SiteadminpanelAction extends AdminPanelAction
             }
         }
 
-        foreach ($booleans as $section => $parts) {
-            foreach ($parts as $setting) {
-                $values[$section][$setting] = ($this->boolean($setting)) ? 1 : 0;
-            }
-        }
-
         // This throws an exception on validation errors
 
         $this->validate($values);
@@ -127,12 +119,6 @@ class SiteadminpanelAction extends AdminPanelAction
             }
         }
 
-        foreach ($booleans as $section => $parts) {
-            foreach ($parts as $setting) {
-                Config::save($section, $setting, $values[$section][$setting]);
-            }
-        }
-
         $config->query('COMMIT');
 
         return;
@@ -299,44 +285,6 @@ class SiteAdminPanelForm extends AdminForm
         $this->out->elementEnd('ul');
         $this->out->elementEnd('fieldset');
 
-        $this->out->elementStart('fieldset', array('id' => 'settings_admin_urls'));
-        $this->out->element('legend', null, _('URLs'));
-        $this->out->elementStart('ul', 'form_data');
-        $this->li();
-        $this->input('server', _('Server'), _('Site\'s server hostname.'));
-        $this->unli();
-
-        $this->li();
-        $this->out->checkbox('fancy', _('Fancy URLs'),
-                             (bool) $this->value('fancy'),
-                             _('Use fancy (more readable and memorable) URLs?'));
-        $this->unli();
-        $this->out->elementEnd('ul');
-        $this->out->elementEnd('fieldset');
-
-        $this->out->elementStart('fieldset', array('id' => 'settings_admin_access'));
-        $this->out->element('legend', null, _('Access'));
-        $this->out->elementStart('ul', 'form_data');
-        $this->li();
-        $this->out->checkbox('private', _('Private'),
-                             (bool) $this->value('private'),
-                             _('Prohibit anonymous users (not logged in) from viewing site?'));
-        $this->unli();
-
-        $this->li();
-        $this->out->checkbox('inviteonly', _('Invite only'),
-                             (bool) $this->value('inviteonly'),
-                             _('Make registration invitation only.'));
-        $this->unli();
-
-        $this->li();
-        $this->out->checkbox('closed', _('Closed'),
-                             (bool) $this->value('closed'),
-                             _('Disable new registrations.'));
-        $this->unli();
-        $this->out->elementEnd('ul');
-        $this->out->elementEnd('fieldset');
-
         $this->out->elementStart('fieldset', array('id' => 'settings_admin_snapshots'));
         $this->out->element('legend', null, _('Snapshots'));
         $this->out->elementStart('ul', 'form_data');
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 445f8a5a3c5759e0c5a4b00ca1861ce85a9af5e3..4bda24b6a02d253aeb03ad2efdf65605da0ee65d 100644 (file)
@@ -39,9 +39,19 @@ class Status_network extends DB_DataObject
     public $logo;                            // varchar(255)
     public $created;                         // datetime()   not_null
     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
+    public $tags;                            // text
 
     /* Static get */
-    function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('Status_network',$k,$v); }
+    function staticGet($k,$v=NULL) {
+        $i = DB_DataObject::staticGet('Status_network',$k,$v);
+
+        // Don't use local process cache; if we're fetching multiple
+        // times it's because we're reloading it in a long-running
+        // process; we need a fresh copy!
+        global $_DB_DATAOBJECT;
+        unset($_DB_DATAOBJECT['CACHE']['status_network']);
+        return $i;
+    }
 
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
@@ -245,4 +255,23 @@ class Status_network extends DB_DataObject
             return $this->nickname . '.' . self::$wildcard;
         }
     }
+
+    /**
+     * Return site meta-info tags as an array
+     * @return array of strings
+     */
+    function getTags()
+    {
+        return array_filter(explode("|", strval($this->tags)));
+    }
+
+    /**
+     * Check if this site record has a particular meta-info tag attached.
+     * @param string $tag
+     * @return bool
+     */
+    function hasTag($tag)
+    {
+        return in_array($tag, $this->getTags());
+    }
 }
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
 
diff --git a/db/rc3to09.sql b/db/rc3to09.sql
deleted file mode 100644 (file)
index 02dc7a6..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-create table queue_item_new (
-    id integer auto_increment primary key comment 'unique identifier',
-    frame blob not null comment 'data: object reference or opaque string',
-    transport varchar(8) not null comment 'queue for what? "email", "jabber", "sms", "irc", ...',
-    created datetime not null comment 'date this record was created',
-    claimed datetime comment 'date this item was claimed',
-
-    index queue_item_created_idx (created)
-
-) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
-
-insert into queue_item_new (frame,transport,created,claimed)
-    select notice_id,transport,created,claimed from queue_item;
-alter table queue_item rename to queue_item_old;
-alter table queue_item_new rename to queue_item;
-
diff --git a/db/rc3torc4.sql b/db/rc3torc4.sql
new file mode 100644 (file)
index 0000000..917c1f1
--- /dev/null
@@ -0,0 +1,50 @@
+create table queue_item_new (
+    id integer auto_increment primary key comment 'unique identifier',
+    frame blob not null comment 'data: object reference or opaque string',
+    transport varchar(8) not null comment 'queue for what? "email", "jabber", "sms", "irc", ...',
+    created datetime not null comment 'date this record was created',
+    claimed datetime comment 'date this item was claimed',
+
+    index queue_item_created_idx (created)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+insert into queue_item_new (frame,transport,created,claimed)
+    select notice_id,transport,created,claimed from queue_item;
+alter table queue_item rename to queue_item_old;
+alter table queue_item_new rename to queue_item;
+
+alter table consumer
+    add consumer_secret varchar(255) not null comment 'secret value';
+
+alter table token
+    add verifier varchar(255) comment 'verifier string for OAuth 1.0a',
+    add verified_callback varchar(255) comment 'verified callback URL for OAuth 1.0a';
+
+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;
+
index a9f64e5a5d0c0f9eac3a30a1255db0b03eb451d1..791303bd540885857f0f2baef805994684b8261a 100644 (file)
@@ -14,6 +14,8 @@ create table status_network (
     sitename varchar(255) comment 'display name',
     theme varchar(255) comment 'theme name',
     logo varchar(255) comment 'site logo',
+    
+    tags text comment 'site meta-info tags (pipe-separated)',
 
     created datetime not null comment 'date this record was created',
     modified timestamp comment 'date this record was modified'
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 (
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 e9207a66a62712f854c298e70414c2a939fe4865..cc4f4aad074b910399ac84133cc993621dcb5e5a 100644 (file)
@@ -246,18 +246,18 @@ class Action extends HTMLOutputter // lawsuit
     {
         if (Event::handle('StartShowScripts', array($this))) {
             if (Event::handle('StartShowJQueryScripts', array($this))) {
-                $this->script('js/jquery.min.js');
-                $this->script('js/jquery.form.js');
-                $this->script('js/jquery.cookie.js');
-                $this->script('js/json2.js');
-                $this->script('js/jquery.joverlay.min.js');
+                $this->script('jquery.min.js');
+                $this->script('jquery.form.js');
+                $this->script('jquery.cookie.js');
+                $this->script('json2.js');
+                $this->script('jquery.joverlay.min.js');
                 Event::handle('EndShowJQueryScripts', array($this));
             }
             if (Event::handle('StartShowStatusNetScripts', array($this)) &&
                 Event::handle('StartShowLaconicaScripts', array($this))) {
-                $this->script('js/xbImportNode.js');
-                $this->script('js/util.js');
-                $this->script('js/geometa.js');
+                $this->script('xbImportNode.js');
+                $this->script('util.js');
+                $this->script('geometa.js');
                 // Frame-busting code to avoid clickjacking attacks.
                 $this->element('script', array('type' => 'text/javascript'),
                                'if (window.top !== window.self) { window.top.location.href = window.self.location.href; }');
@@ -369,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 {
@@ -388,8 +392,14 @@ class Action extends HTMLOutputter // lawsuit
         $this->elementStart('address', array('id' => 'site_contact',
                                              'class' => 'vcard'));
         if (Event::handle('StartAddressData', array($this))) {
+            if (common_config('singleuser', 'enabled')) {
+                $url = common_local_url('showstream',
+                                        array('nickname' => common_config('singleuser', 'nickname')));
+            } else {
+                $url = common_local_url('public');
+            }
             $this->elementStart('a', array('class' => 'url home bookmark',
-                                           'href' => common_local_url('public')));
+                                           'href' => $url));
             if (common_config('site', 'logo') || file_exists(Theme::file('logo.png'))) {
                 $this->element('img', array('class' => 'logo photo',
                                             'src' => (common_config('site', 'logo')) ? common_config('site', 'logo') : Theme::path('logo.png'),
index a6981ac6117d703128a9f9a03ff06dfe36e55499..f62bfa458ac89cc14ea05e9fed7d8ead6706084a 100644 (file)
@@ -319,12 +319,17 @@ class AdminPanelNav extends Widget
 
             if ($this->canAdmin('user')) {
                 $this->out->menuItem(common_local_url('useradminpanel'), _('User'),
-                                     _('Paths configuration'), $action_name == 'useradminpanel', 'nav_design_admin_panel');
+                                     _('User configuration'), $action_name == 'useradminpanel', 'nav_design_admin_panel');
             }
 
-            if ($this->canAdmin('paths')) {
-                $this->out->menuItem(common_local_url('pathsadminpanel'), _('Paths'),
-                                     _('Paths configuration'), $action_name == 'pathsadminpanel', 'nav_design_admin_panel');
+            if ($this->canAdmin('access')) {
+                $this->out->menuItem(common_local_url('accessadminpanel'), _('Access'),
+                                     _('Access configuration'), $action_name == 'accessadminpanel', 'nav_design_admin_panel');
+            }
+
+           if ($this->canAdmin('paths')) {
+               $this->out->menuItem(common_local_url('pathsadminpanel'), _('Paths'),
+                                    _('Paths configuration'), $action_name == 'pathsadminpanel', 'nav_design_admin_panel');
             }
 
             Event::handle('EndAdminPanelNav', array($this));
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..c684a6caee9d90e4bb7fbb7cd614b9696d85c23b 100644 (file)
@@ -28,8 +28,8 @@
  * @author    Evan Prodromou <evan@status.net>
  * @author    mEDI <medi@milaro.net>
  * @author    Sarven Capadisli <csarven@status.net>
- * @author    Zach Copley <zach@status.net> 
- * @copyright 2009 StatusNet, Inc.
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009-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/
  */
@@ -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,10 @@ require_once INSTALLDIR . '/lib/api.php';
 
 class ApiAuthAction extends ApiAction
 {
+    var $auth_user_nickname = null;
+    var $auth_user_password = null;
+    var $access_token       = null;
+    var $oauth_source       = null;
 
     /**
      * Take arguments for running, and output basic auth header if needed
@@ -66,13 +71,130 @@ class ApiAuthAction extends ApiAction
     {
         parent::prepare($args);
 
+        $this->consumer_key = $this->arg('oauth_consumer_key');
+        $this->access_token = $this->arg('oauth_token');
+
+        // NOTE: $this->auth_user has to get set in prepare(), not handle(),
+        // because subclasses do stuff with it in their prepares.
+
         if ($this->requiresAuth()) {
-            $this->checkBasicAuthUser();
+            if (!empty($this->access_token)) {
+                $this->checkOAuthRequest();
+            } else {
+                $this->checkBasicAuthUser(true);
+            }
+        } else {
+
+            // Check to see if a basic auth user is there even
+            // if one's not required
+
+            if (empty($this->access_token)) {
+                $this->checkBasicAuthUser(false);
+            }
+        }
+
+        // Reject API calls with the wrong access level
+
+        if ($this->isReadOnly($args) == false) {
+            if ($this->access != self::READ_WRITE) {
+                $msg = _('API resource requires read-write access, ' .
+                         'but you only have read access.');
+                $this->clientError($msg, 401, $this->format);
+                exit;
+            }
         }
 
         return true;
     }
 
+    function handle($args)
+    {
+        parent::handle($args);
+    }
+
+    function checkOAuthRequest()
+    {
+        $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 probably not happen
+                common_log(LOG_WARNING,
+                           '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)) {
+
+                // If access_type == 0 we have either a request token
+                // or a bad / revoked access token
+
+                if ($appUser->access_type != 0) {
+
+                    // Set the access level 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) with %s access.";
+
+                    common_log(LOG_INFO, sprintf($msg,
+                                                 $this->auth_user->nickname,
+                                                 $this->auth_user->id,
+                                                 $app->name,
+                                                 $app->id,
+                                                 ($this->access = self::READ_WRITE) ?
+                                                 'read-write' : 'read-only'
+                                                 ));
+                    return;
+                } 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_WARNING, 'API OAuthException - ' . $e->getMessage());
+            $this->showAuthError();
+            exit;
+        }
+    }
+
     /**
      * Does this API resource require authentication?
      *
@@ -91,44 +213,54 @@ class ApiAuthAction extends ApiAction
      * @return boolean true or false
      */
 
-    function checkBasicAuthUser()
+    function checkBasicAuthUser($required = true)
     {
         $this->basicAuthProcessHeader();
 
         $realm = common_config('site', 'name') . ' API';
 
-        if (!isset($this->auth_user)) {
+        if (!isset($this->auth_user_nickname) && $required) {
             header('WWW-Authenticate: Basic realm="' . $realm . '"');
 
             // show error if the user clicks 'cancel'
 
-            $this->showBasicAuthError();
+            $this->showAuthError();
             exit;
 
         } else {
-            $nickname = $this->auth_user;
-            $password = $this->auth_pw;
-            $user = common_check_user($nickname, $password);
+
+            $user = common_check_user($this->auth_user_nickname,
+                                      $this->auth_user_password);
+
             if (Event::handle('StartSetApiUser', array(&$user))) {
-                $this->auth_user = $user;
+
+                if (!empty($user)) {
+                    $this->auth_user = $user;
+                }
+
                 Event::handle('EndSetApiUser', array($user));
             }
 
-            if (empty($this->auth_user)) {
+            // By default, basic auth users have rw access
+
+            $this->access = self::READ_WRITE;
+
+            if (empty($this->auth_user) && $required) {
 
                 // basic authentication failed
 
                 list($proxy, $ip) = common_client_ip();
-                common_log(
-                    LOG_WARNING,
-                    'Failed API auth attempt, nickname = ' .
-                    "$nickname, proxy = $proxy, ip = $ip."
-                );
-                $this->showBasicAuthError();
+
+                $msg = sprintf(_('Failed API auth attempt, nickname = %1$s, ' .
+                         'proxy = %2$s, ip = %3$s'),
+                               $this->auth_user_nickname,
+                               $proxy,
+                               $ip);
+                common_log(LOG_WARNING, $msg);
+                $this->showAuthError();
                 exit;
             }
         }
-        return true;
     }
 
     /**
@@ -142,32 +274,30 @@ class ApiAuthAction extends ApiAction
     {
         if (isset($_SERVER['AUTHORIZATION'])
             || isset($_SERVER['HTTP_AUTHORIZATION'])
-        ) {
-                $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION'])
-                ? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION'];
+            ) {
+            $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION'])
+              ? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION'];
         }
 
         if (isset($_SERVER['PHP_AUTH_USER'])) {
-            $this->auth_user = $_SERVER['PHP_AUTH_USER'];
-            $this->auth_pw = $_SERVER['PHP_AUTH_PW'];
+            $this->auth_user_nickname = $_SERVER['PHP_AUTH_USER'];
+            $this->auth_user_password = $_SERVER['PHP_AUTH_PW'];
         } elseif (isset($authorization_header)
             && strstr(substr($authorization_header, 0, 5), 'Basic')) {
 
-            // decode the HTTP_AUTHORIZATION header on php-cgi server self
+            // Decode the HTTP_AUTHORIZATION header on php-cgi server self
             // on fcgid server the header name is AUTHORIZATION
 
             $auth_hash = base64_decode(substr($authorization_header, 6));
-            list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash);
+            list($this->auth_user_nickname,
+                 $this->auth_user_password) = explode(':', $auth_hash);
 
-            // set all to null on a empty basic auth request
+            // Set all to null on a empty basic auth request
 
-            if ($this->auth_user == "") {
-                $this->auth_user = null;
-                $this->auth_pw = null;
+            if (empty($this->auth_user_nickname)) {
+                $this->auth_user_nickname = null;
+                $this->auth_password = null;
             }
-        } else {
-            $this->auth_user = null;
-            $this->auth_pw = null;
         }
     }
 
@@ -178,7 +308,7 @@ class ApiAuthAction extends ApiAction
      * @return void
      */
 
-    function showBasicAuthError()
+    function showAuthError()
     {
         header('HTTP/1.1 401 Unauthorized');
         $msg = 'Could not authenticate you.';
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..6f03a9b
--- /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 characters'),
+                                 $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..10ea34864066b03a6ab5af2586be832ed4dbe11e 100644 (file)
@@ -56,7 +56,7 @@ $default =
               'dupelimit' => 60, # default for same person saying the same thing
               'textlimit' => 140,
               'indent' => true,
-              'use_x_sendfile' => false,
+              'use_x_sendfile' => false
               ),
         'db' =>
         array('database' => 'YOU HAVE TO SET THIS IN config.php',
@@ -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
@@ -79,6 +81,7 @@ $default =
               'subsystem' => 'db', # default to database, or 'stomp'
               'stomp_server' => null,
               'queue_basename' => '/queue/statusnet/',
+              'control_channel' => '/topic/statusnet-control', // broadcasts to all queue daemons
               'stomp_username' => null,
               'stomp_password' => null,
               'monitor' => null, // URL to monitor ping endpoint (work in progress)
@@ -117,6 +120,9 @@ $default =
         array('server' => null,
               'dir' => null,
               'path'=> null),
+        'javascript' =>
+        array('server' => null,
+              'path'=> null),
         'throttle' =>
         array('enabled' => false, // whether to throttle edits; false by default
               'count' => 20, // number of allowed messages in timespan
@@ -209,6 +215,8 @@ $default =
               'uploads' => true,
               'filecommand' => '/usr/bin/file',
               ),
+        'application' =>
+        array('desclimit' => null),
         'group' =>
         array('maxaliases' => 3,
               'desclimit' => null),
@@ -255,5 +263,8 @@ $default =
                                  'OpenID' => null),
               ),
         'admin' =>
-        array('panels' => array('design', 'site', 'user', 'paths')),
+        array('panels' => array('design', 'site', 'user', 'paths', 'access')),
+        'singleuser' =>
+        array('enabled' => false,
+              'nickname' => null),
         );
index 8e44c03a9272942d16285d99d27e426894dd0401..4955e9219954c2bf000124ae8f0f8911c512bca7 100644 (file)
@@ -327,8 +327,8 @@ class DesignSettingsAction extends AccountSettingsAction
     {
         parent::showScripts();
 
-        $this->script('js/farbtastic/farbtastic.js');
-        $this->script('js/userdesign.go.js');
+        $this->script('farbtastic/farbtastic.js');
+        $this->script('userdesign.go.js');
 
         $this->autofocus('design_background-image_file');
     }
index 31660ce954982e365be8e68e05d179bda72cadbd..317f5ea612a79b486488875120188583a10d1e01 100644 (file)
@@ -351,14 +351,40 @@ class HTMLOutputter extends XMLOutputter
     function script($src, $type='text/javascript')
     {
         if(Event::handle('StartScriptElement', array($this,&$src,&$type))) {
+
             $url = parse_url($src);
+
             if( empty($url['scheme']) && empty($url['host']) && empty($url['query']) && empty($url['fragment']))
             {
-                $src = common_path($src) . '?version=' . STATUSNET_VERSION;
+                $path = common_config('javascript', 'path');
+
+                if (empty($path)) {
+                    $path = common_config('site', 'path') . '/js/';
+                }
+
+                if ($path[strlen($path)-1] != '/') {
+                    $path .= '/';
+                }
+
+                if ($path[0] != '/') {
+                    $path = '/'.$path;
+                }
+
+                $server = common_config('javascript', 'server');
+
+                if (empty($server)) {
+                    $server = common_config('site', 'server');
+                }
+
+                // XXX: protocol
+
+                $src = 'http://'.$server.$path.$src . '?version=' . STATUSNET_VERSION;
             }
+
             $this->element('script', array('type' => $type,
                                                    'src' => $src),
                                    ' ');
+
             Event::handle('EndScriptElement', array($this,$src,$type));
         }
     }
index 3bf82bc6b47903b76fc42631ddb1fdbc4014a0aa..bcab3542be5af791892d949c2ec8f80074a183c3 100644 (file)
@@ -38,6 +38,9 @@ abstract class IoMaster
     protected $pollTimeouts = array();
     protected $lastPoll = array();
 
+    public $shutdown = false; // Did we do a graceful shutdown?
+    public $respawn = true; // Should we respawn after shutdown?
+
     /**
      * @param string $id process ID to use in logging/monitoring
      */
@@ -144,7 +147,7 @@ abstract class IoMaster
         $this->logState('init');
         $this->start();
 
-        while (true) {
+        while (!$this->shutdown) {
             $timeouts = array_values($this->pollTimeouts);
             $timeouts[] = 60; // default max timeout
 
@@ -196,22 +199,31 @@ abstract class IoMaster
             $this->logState('idle');
             $this->idle();
 
-            $memoryLimit = $this->softMemoryLimit();
-            if ($memoryLimit > 0) {
-                $usage = memory_get_usage();
-                if ($usage > $memoryLimit) {
-                    common_log(LOG_INFO, "Queue thread hit soft memory limit ($usage > $memoryLimit); gracefully restarting.");
-                    break;
-                } else if (common_config('queue', 'debug_memory')) {
-                    common_log(LOG_DEBUG, "Memory usage $usage");
-                }
-            }
+            $this->checkMemory();
         }
 
         $this->logState('shutdown');
         $this->finish();
     }
 
+    /**
+     * Check runtime memory usage, possibly triggering a graceful shutdown
+     * and thread respawn if we've crossed the soft limit.
+     */
+    protected function checkMemory()
+    {
+        $memoryLimit = $this->softMemoryLimit();
+        if ($memoryLimit > 0) {
+            $usage = memory_get_usage();
+            if ($usage > $memoryLimit) {
+                common_log(LOG_INFO, "Queue thread hit soft memory limit ($usage > $memoryLimit); gracefully restarting.");
+                $this->requestRestart();
+            } else if (common_config('queue', 'debug_memory')) {
+                common_log(LOG_DEBUG, "Memory usage $usage");
+            }
+        }
+    }
+
     /**
      * Return fully-parsed soft memory limit in bytes.
      * @return intval 0 or -1 if not set
@@ -354,5 +366,24 @@ abstract class IoMaster
         $owners[] = "thread:" . $this->id;
         $this->monitor->stats($key, $owners);
     }
+
+    /**
+     * For IoManagers to request a graceful shutdown at end of event loop.
+     */
+    public function requestShutdown()
+    {
+        $this->shutdown = true;
+        $this->respawn = false;
+    }
+
+    /**
+     * For IoManagers to request a graceful restart at end of event loop.
+     */
+    public function requestRestart()
+    {
+        $this->shutdown = true;
+        $this->respawn = true;
+    }
+
 }
 
index cdde1feca081fdc922cb032c8522158ae31bfd76..25db5baa92c59ceef572ff67f2536c78425c33c7 100644 (file)
@@ -78,9 +78,9 @@ class PersonalGroupNav extends Widget
     function show()
     {
         $user = null;
-       
+
        // FIXME: we should probably pass this in
-       
+
         $action = $this->action->trimmed('action');
         $nickname = $this->action->trimmed('nickname');
 
@@ -117,7 +117,8 @@ class PersonalGroupNav extends Widget
 
             $cur = common_current_user();
 
-            if ($cur && $cur->id == $user->id) {
+            if ($cur && $cur->id == $user->id &&
+                !common_config('singleuser', 'enabled')) {
 
                 $this->out->menuItem(common_local_url('inbox', array('nickname' =>
                                                                          $nickname)),
index e5cf8239e9cb1ee49dd5681032995b20a8eaa412..afe710e884dbc9d4aea2168a4f9548c4fc053b9c 100644 (file)
@@ -100,6 +100,23 @@ abstract class QueueManager extends IoManager
         $this->initialize();
     }
 
+    /**
+     * Optional; ping any running queue handler daemons with a notification
+     * such as announcing a new site to handle or requesting clean shutdown.
+     * This avoids having to restart all the daemons manually to update configs
+     * and such.
+     *
+     * Called from scripts/queuectl.php controller utility.
+     *
+     * @param string $event event key
+     * @param string $param optional parameter to append to key
+     * @return boolean success
+     */
+    public function sendControlSignal($event, $param='')
+    {
+        throw new Exception(get_class($this) . " does not support control signals.");
+    }
+
     /**
      * Store an object (usually/always a Notice) into the given queue
      * for later processing. No guarantee is made on when it will be
@@ -225,7 +242,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..03765b39ddae8b8050d9c961fbf70d51a8085627 100644 (file)
@@ -73,12 +73,6 @@ class Router
 
         if (Event::handle('StartInitializeRouter', array(&$m))) {
 
-            // In the "root"
-
-            $m->connect('', array('action' => 'public'));
-            $m->connect('rss', array('action' => 'publicrss'));
-            $m->connect('featuredrss', array('action' => 'featuredrss'));
-            $m->connect('favoritedrss', array('action' => 'favoritedrss'));
             $m->connect('opensearch/people', array('action' => 'opensearch',
                                                    'type' => 'people'));
             $m->connect('opensearch/notice', array('action' => 'opensearch',
@@ -140,11 +134,23 @@ 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'));
             }
 
+            $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]+')
+            );
+
             // search
 
             foreach (array('group', 'people', 'notice') as $s) {
@@ -227,11 +233,6 @@ class Router
                         array('action' => 'peopletag'),
                         array('tag' => '[a-zA-Z0-9]+'));
 
-            $m->connect('featured/', array('action' => 'featured'));
-            $m->connect('featured', array('action' => 'featured'));
-            $m->connect('favorited/', array('action' => 'favorited'));
-            $m->connect('favorited', array('action' => 'favorited'));
-
             // groups
 
             $m->connect('group/new', array('action' => 'newgroup'));
@@ -622,66 +623,146 @@ class Router
             $m->connect('api/search.json', array('action' => 'twitapisearchjson'));
             $m->connect('api/trends.json', array('action' => 'twitapitrends'));
 
+            $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'));
+
+            // Admin
+
             $m->connect('admin/site', array('action' => 'siteadminpanel'));
             $m->connect('admin/design', array('action' => 'designadminpanel'));
             $m->connect('admin/user', array('action' => 'useradminpanel'));
+           $m->connect('admin/access', array('action' => 'accessadminpanel'));
             $m->connect('admin/paths', array('action' => 'pathsadminpanel'));
 
             $m->connect('getfile/:filename',
                         array('action' => 'getfile'),
                         array('filename' => '[A-Za-z0-9._-]+'));
 
-            // user stuff
+            // In the "root"
 
-            foreach (array('subscriptions', 'subscribers',
-                           'nudge', 'all', 'foaf', 'xrds',
-                           'replies', 'inbox', 'outbox', 'microsummary') as $a) {
-                $m->connect(':nickname/'.$a,
-                            array('action' => $a),
+            if (common_config('singleuser', 'enabled')) {
+
+                $nickname = common_config('singleuser', 'nickname');
+
+                foreach (array('subscriptions', 'subscribers',
+                               'all', 'foaf', 'xrds',
+                               'replies', 'microsummary') as $a) {
+                    $m->connect($a,
+                                array('action' => $a,
+                                      'nickname' => $nickname));
+                }
+
+                foreach (array('subscriptions', 'subscribers') as $a) {
+                    $m->connect($a.'/:tag',
+                                array('action' => $a,
+                                      'nickname' => $nickname),
+                                array('tag' => '[a-zA-Z0-9]+'));
+                }
+
+                foreach (array('rss', 'groups') as $a) {
+                    $m->connect($a,
+                                array('action' => 'user'.$a,
+                                      'nickname' => $nickname));
+                }
+
+                foreach (array('all', 'replies', 'favorites') as $a) {
+                    $m->connect($a.'/rss',
+                                array('action' => $a.'rss',
+                                      'nickname' => $nickname));
+                }
+
+                $m->connect('favorites',
+                            array('action' => 'showfavorites',
+                                  'nickname' => $nickname));
+
+                $m->connect('avatar/:size',
+                            array('action' => 'avatarbynickname',
+                                  'nickname' => $nickname),
+                            array('size' => '(original|96|48|24)'));
+
+                $m->connect('tag/:tag/rss',
+                            array('action' => 'userrss',
+                                  'nickname' => $nickname),
+                            array('tag' => '[a-zA-Z0-9]+'));
+
+                $m->connect('tag/:tag',
+                            array('action' => 'showstream',
+                                  'nickname' => $nickname),
+                            array('tag' => '[a-zA-Z0-9]+'));
+
+                $m->connect('',
+                            array('action' => 'showstream',
+                                  'nickname' => $nickname));
+
+            } else {
+
+                $m->connect('', array('action' => 'public'));
+                $m->connect('rss', array('action' => 'publicrss'));
+                $m->connect('featuredrss', array('action' => 'featuredrss'));
+                $m->connect('favoritedrss', array('action' => 'favoritedrss'));
+                $m->connect('featured/', array('action' => 'featured'));
+                $m->connect('featured', array('action' => 'featured'));
+                $m->connect('favorited/', array('action' => 'favorited'));
+                $m->connect('favorited', array('action' => 'favorited'));
+
+                foreach (array('subscriptions', 'subscribers',
+                               'nudge', 'all', 'foaf', 'xrds',
+                               'replies', 'inbox', 'outbox', 'microsummary') as $a) {
+                    $m->connect(':nickname/'.$a,
+                                array('action' => $a),
+                                array('nickname' => '[a-zA-Z0-9]{1,64}'));
+                }
+
+                foreach (array('subscriptions', 'subscribers') as $a) {
+                    $m->connect(':nickname/'.$a.'/:tag',
+                                array('action' => $a),
+                                array('tag' => '[a-zA-Z0-9]+',
+                                      'nickname' => '[a-zA-Z0-9]{1,64}'));
+                }
+
+                foreach (array('rss', 'groups') as $a) {
+                    $m->connect(':nickname/'.$a,
+                                array('action' => 'user'.$a),
+                                array('nickname' => '[a-zA-Z0-9]{1,64}'));
+                }
+
+                foreach (array('all', 'replies', 'favorites') as $a) {
+                    $m->connect(':nickname/'.$a.'/rss',
+                                array('action' => $a.'rss'),
+                                array('nickname' => '[a-zA-Z0-9]{1,64}'));
+                }
+
+                $m->connect(':nickname/favorites',
+                            array('action' => 'showfavorites'),
                             array('nickname' => '[a-zA-Z0-9]{1,64}'));
-            }
 
-            foreach (array('subscriptions', 'subscribers') as $a) {
-                $m->connect(':nickname/'.$a.'/:tag',
-                            array('action' => $a),
-                            array('tag' => '[a-zA-Z0-9]+',
+                $m->connect(':nickname/avatar/:size',
+                            array('action' => 'avatarbynickname'),
+                            array('size' => '(original|96|48|24)',
                                   'nickname' => '[a-zA-Z0-9]{1,64}'));
-            }
 
-            foreach (array('rss', 'groups') as $a) {
-                $m->connect(':nickname/'.$a,
-                            array('action' => 'user'.$a),
-                            array('nickname' => '[a-zA-Z0-9]{1,64}'));
-            }
+                $m->connect(':nickname/tag/:tag/rss',
+                            array('action' => 'userrss'),
+                            array('nickname' => '[a-zA-Z0-9]{1,64}'),
+                            array('tag' => '[a-zA-Z0-9]+'));
 
-            foreach (array('all', 'replies', 'favorites') as $a) {
-                $m->connect(':nickname/'.$a.'/rss',
-                            array('action' => $a.'rss'),
+                $m->connect(':nickname/tag/:tag',
+                            array('action' => 'showstream'),
+                            array('nickname' => '[a-zA-Z0-9]{1,64}'),
+                            array('tag' => '[a-zA-Z0-9]+'));
+
+                $m->connect(':nickname',
+                            array('action' => 'showstream'),
                             array('nickname' => '[a-zA-Z0-9]{1,64}'));
             }
 
-            $m->connect(':nickname/favorites',
-                        array('action' => 'showfavorites'),
-                        array('nickname' => '[a-zA-Z0-9]{1,64}'));
-
-            $m->connect(':nickname/avatar/:size',
-                        array('action' => 'avatarbynickname'),
-                        array('size' => '(original|96|48|24)',
-                              'nickname' => '[a-zA-Z0-9]{1,64}'));
-
-            $m->connect(':nickname/tag/:tag/rss',
-                        array('action' => 'userrss'),
-                        array('nickname' => '[a-zA-Z0-9]{1,64}'),
-                        array('tag' => '[a-zA-Z0-9]+'));
-
-            $m->connect(':nickname/tag/:tag',
-                        array('action' => 'showstream'),
-                        array('nickname' => '[a-zA-Z0-9]{1,64}'),
-                        array('tag' => '[a-zA-Z0-9]+'));
-
-            $m->connect(':nickname',
-                        array('action' => 'showstream'),
-                        array('nickname' => '[a-zA-Z0-9]{1,64}'));
+            // user stuff
 
             Event::handle('RouterInitialized', array($m));
         }
index 8baefe88e8184db42b216f589f19e79f9207ea5f..b1961d68801c308fc25429a4cee80b028439f72d 100644 (file)
@@ -36,6 +36,11 @@ abstract class SpawningDaemon extends Daemon
 {
     protected $threads=1;
 
+    const EXIT_OK = 0;
+    const EXIT_ERR = 1;
+    const EXIT_SHUTDOWN = 100;
+    const EXIT_RESTART = 101;
+
     function __construct($id=null, $daemonize=true, $threads=1)
     {
         parent::__construct($daemonize);
@@ -49,7 +54,7 @@ abstract class SpawningDaemon extends Daemon
     /**
      * Perform some actual work!
      *
-     * @return boolean true on success, false on failure
+     * @return int exit code; use self::EXIT_SHUTDOWN to request not to respawn.
      */
     public abstract function runThread();
 
@@ -84,23 +89,30 @@ abstract class SpawningDaemon extends Daemon
         while (count($children) > 0) {
             $status = null;
             $pid = pcntl_wait($status);
-            if ($pid > 0) {
+            if ($pid > 0 && pcntl_wifexited($status)) {
+                $exitCode = pcntl_wexitstatus($status);
+
                 $i = array_search($pid, $children);
                 if ($i === false) {
-                    $this->log(LOG_ERR, "Unrecognized child pid $pid exited!");
+                    $this->log(LOG_ERR, "Unrecognized child pid $pid exited with status $exitCode");
                     continue;
                 }
                 unset($children[$i]);
-                $this->log(LOG_INFO, "Thread $i pid $pid exited.");
-                
-                $pid = pcntl_fork();
-                if ($pid < 0) {
-                    $this->log(LOG_ERROR, "Couldn't fork to respawn thread $i; aborting thread.\n");
-                } else if ($pid == 0) {
-                    $this->initAndRunChild($i);
+
+                if ($this->shouldRespawn($exitCode)) {
+                    $this->log(LOG_INFO, "Thread $i pid $pid exited with status $exitCode; respawing.");
+
+                    $pid = pcntl_fork();
+                    if ($pid < 0) {
+                        $this->log(LOG_ERROR, "Couldn't fork to respawn thread $i; aborting thread.\n");
+                    } else if ($pid == 0) {
+                        $this->initAndRunChild($i);
+                    } else {
+                        $this->log(LOG_INFO, "Respawned thread $i as pid $pid");
+                        $children[$i] = $pid;
+                    }
                 } else {
-                    $this->log(LOG_INFO, "Respawned thread $i as pid $pid");
-                    $children[$i] = $pid;
+                    $this->log(LOG_INFO, "Thread $i pid $pid exited with status $exitCode; closing out thread.");
                 }
             }
         }
@@ -108,6 +120,24 @@ abstract class SpawningDaemon extends Daemon
         return true;
     }
 
+    /**
+     * Determine whether to respawn an exited subprocess based on its exit code.
+     * Otherwise we'll respawn all exits by default.
+     *
+     * @param int $exitCode
+     * @return boolean true to respawn
+     */
+    protected function shouldRespawn($exitCode)
+    {
+        if ($exitCode == self::EXIT_SHUTDOWN) {
+            // Thread requested a clean shutdown.
+            return false;
+        } else {
+            // Otherwise we should always respawn!
+            return true;
+        }
+    }
+
     /**
      * Initialize things for a fresh thread, call runThread(), and
      * exit at completion with appropriate return value.
@@ -116,8 +146,8 @@ abstract class SpawningDaemon extends Daemon
     {
         $this->set_id($this->get_id() . "." . $thread);
         $this->resetDb();
-        $ok = $this->runThread();
-        exit($ok ? 0 : 1);
+        $exitCode = $this->runThread();
+        exit($exitCode);
     }
 
     /**
index 8f0091a1384f51b1cf07ad6c4bddf7dfbd920f03..19e8c49b5ce9c9e7a996ef3f8d948a9360b8f952 100644 (file)
@@ -38,8 +38,10 @@ class StompQueueManager extends QueueManager
     var $password = null;
     var $base = null;
     var $con = null;
+    protected $control;
     
     protected $sites = array();
+    protected $subscriptions = array();
 
     protected $useTransactions = true;
     protected $transaction = null;
@@ -52,6 +54,7 @@ class StompQueueManager extends QueueManager
         $this->username = common_config('queue', 'stomp_username');
         $this->password = common_config('queue', 'stomp_password');
         $this->base     = common_config('queue', 'queue_basename');
+        $this->control  = common_config('queue', 'control_channel');
     }
 
     /**
@@ -77,6 +80,36 @@ class StompQueueManager extends QueueManager
         $this->initialize();
     }
 
+    /**
+     * Optional; ping any running queue handler daemons with a notification
+     * such as announcing a new site to handle or requesting clean shutdown.
+     * This avoids having to restart all the daemons manually to update configs
+     * and such.
+     *
+     * Currently only relevant for multi-site queue managers such as Stomp.
+     *
+     * @param string $event event key
+     * @param string $param optional parameter to append to key
+     * @return boolean success
+     */
+    public function sendControlSignal($event, $param='')
+    {
+        $message = $event;
+        if ($param != '') {
+            $message .= ':' . $param;
+        }
+        $this->_connect();
+        $result = $this->con->send($this->control,
+                                   $message,
+                                   array ('created' => common_sql_now()));
+        if ($result) {
+            $this->_log(LOG_INFO, "Sent control ping to queue daemons: $message");
+            return true;
+        } else {
+            $this->_log(LOG_ERR, "Failed sending control ping to queue daemons: $message");
+            return false;
+        }
+    }
 
     /**
      * Instantiate the appropriate QueueHandler class for the given queue.
@@ -86,7 +119,7 @@ class StompQueueManager extends QueueManager
      */
     function getHandler($queue)
     {
-        $handlers = $this->handlers[common_config('site', 'server')];
+        $handlers = $this->handlers[$this->currentSite()];
         if (isset($handlers[$queue])) {
             $class = $handlers[$queue];
             if (class_exists($class)) {
@@ -108,7 +141,7 @@ class StompQueueManager extends QueueManager
     function getQueues()
     {
         $group = $this->activeGroup();
-        $site = common_config('site', 'server');
+        $site = $this->currentSite();
         if (empty($this->groups[$site][$group])) {
             return array();
         } else {
@@ -126,8 +159,8 @@ class StompQueueManager extends QueueManager
      */
     public function connect($transport, $class, $group='queuedaemon')
     {
-        $this->handlers[common_config('site', 'server')][$transport] = $class;
-        $this->groups[common_config('site', 'server')][$group][$transport] = $class;
+        $this->handlers[$this->currentSite()][$transport] = $class;
+        $this->groups[$this->currentSite()][$group][$transport] = $class;
     }
 
     /**
@@ -145,7 +178,8 @@ class StompQueueManager extends QueueManager
 
         $result = $this->con->send($this->queueName($queue),
                                    $msg,               // BODY of the message
-                                   array ('created' => common_sql_now()));
+                                   array ('created' => common_sql_now(),
+                                          'persistent' => 'true'));
 
         if (!$result) {
             common_log(LOG_ERR, "Error sending $rep to $queue queue");
@@ -180,7 +214,16 @@ class StompQueueManager extends QueueManager
         $ok = true;
         $frames = $this->con->readFrames();
         foreach ($frames as $frame) {
-            $ok = $ok && $this->_handleItem($frame);
+            $dest = $frame->headers['destination'];
+            if ($dest == $this->control) {
+                if (!$this->handleControlSignal($frame)) {
+                    // We got a control event that requests a shutdown;
+                    // close out and stop handling anything else!
+                    break;
+                }
+            } else {
+                $ok = $ok && $this->handleItem($frame);
+            }
         }
         return $ok;
     }
@@ -197,6 +240,9 @@ class StompQueueManager extends QueueManager
     public function start($master)
     {
         parent::start($master);
+        $this->_connect();
+
+        $this->con->subscribe($this->control);
         if ($this->sites) {
             foreach ($this->sites as $server) {
                 StatusNet::init($server);
@@ -221,6 +267,7 @@ class StompQueueManager extends QueueManager
         // If there are any outstanding delivered messages we haven't processed,
         // free them for another thread to take.
         $this->rollback();
+        $this->con->unsubscribe($this->control);
         if ($this->sites) {
             foreach ($this->sites as $server) {
                 StatusNet::init($server);
@@ -231,7 +278,16 @@ class StompQueueManager extends QueueManager
         }
         return true;
     }
-    
+
+    /**
+     * Get identifier of the currently active site configuration
+     * @return string
+     */
+    protected function currentSite()
+    {
+        return common_config('site', 'server'); // @fixme switch to nickname
+    }
+
     /**
      * Lazy open connection to Stomp queue server.
      */
@@ -255,22 +311,29 @@ class StompQueueManager extends QueueManager
      */
     protected function doSubscribe()
     {
+        $site = $this->currentSite();
         $this->_connect();
         foreach ($this->getQueues() as $queue) {
             $rawqueue = $this->queueName($queue);
+            $this->subscriptions[$site][$queue] = $rawqueue;
             $this->_log(LOG_INFO, "Subscribing to $rawqueue");
             $this->con->subscribe($rawqueue);
         }
     }
-    
+
     /**
      * Subscribe from all enabled notice queues for the current site.
      */
     protected function doUnsubscribe()
     {
+        $site = $this->currentSite();
         $this->_connect();
-        foreach ($this->getQueues() as $queue) {
-            $this->con->unsubscribe($this->queueName($queue));
+        if (!empty($this->subscriptions[$site])) {
+            foreach ($this->subscriptions[$site] as $queue => $rawqueue) {
+                $this->_log(LOG_INFO, "Unsubscribing from $rawqueue");
+                $this->con->unsubscribe($rawqueue);
+                unset($this->subscriptions[$site][$queue]);
+            }
         }
     }
 
@@ -286,10 +349,10 @@ class StompQueueManager extends QueueManager
      * @param StompFrame $frame
      * @return bool
      */
-    protected function _handleItem($frame)
+    protected function handleItem($frame)
     {
         list($site, $queue) = $this->parseDestination($frame->headers['destination']);
-        if ($site != common_config('site', 'server')) {
+        if ($site != $this->currentSite()) {
             $this->stats('switch');
             StatusNet::init($site);
         }
@@ -317,7 +380,7 @@ class StompQueueManager extends QueueManager
 
         $handler = $this->getHandler($queue);
         if (!$handler) {
-            $this->_log(LOG_ERROR, "Missing handler class; skipping $info");
+            $this->_log(LOG_ERR, "Missing handler class; skipping $info");
             $this->ack($frame);
             $this->commit();
             $this->begin();
@@ -348,6 +411,77 @@ class StompQueueManager extends QueueManager
         return true;
     }
 
+    /**
+     * Process a control signal broadcast.
+     *
+     * @param array $frame Stomp frame
+     * @return bool true to continue; false to stop further processing.
+     */
+    protected function handleControlSignal($frame)
+    {
+        $message = trim($frame->body);
+        if (strpos($message, ':') !== false) {
+            list($event, $param) = explode(':', $message, 2);
+        } else {
+            $event = $message;
+            $param = '';
+        }
+
+        $shutdown = false;
+
+        if ($event == 'shutdown') {
+            $this->master->requestShutdown();
+            $shutdown = true;
+        } else if ($event == 'restart') {
+            $this->master->requestRestart();
+            $shutdown = true;
+        } else if ($event == 'update') {
+            $this->updateSiteConfig($param);
+        } else {
+            $this->_log(LOG_ERR, "Ignoring unrecognized control message: $message");
+        }
+
+        $this->ack($frame);
+        $this->commit();
+        $this->begin();
+        return $shutdown;
+    }
+    
+    /**
+     * Set us up with queue subscriptions for a new site added at runtime,
+     * triggered by a broadcast to the 'statusnet-control' topic.
+     *
+     * @param array $frame Stomp frame
+     * @return bool true to continue; false to stop further processing.
+     */
+    protected function updateSiteConfig($nickname)
+    {
+        if (empty($this->sites)) {
+            if ($nickname == common_config('site', 'nickname')) {
+                StatusNet::init(common_config('site', 'server'));
+                $this->doUnsubscribe();
+                $this->doSubscribe();
+            } else {
+                $this->_log(LOG_INFO, "Ignoring update ping for other site $nickname");
+            }
+        } else {
+            $sn = Status_network::staticGet($nickname);
+            if ($sn) {
+                $server = $sn->getServerName(); // @fixme do config-by-nick
+                StatusNet::init($server);
+                if (empty($this->sites[$server])) {
+                    $this->addSite($server);
+                }
+                $this->_log(LOG_INFO, "(Re)subscribing to queues for site $nickname / $server");
+                $this->doUnsubscribe();
+                $this->doSubscribe();
+                $this->stats('siteupdate');
+            } else {
+                $this->_log(LOG_ERR, "Ignoring ping for unrecognized new site $nickname");
+            }
+        }
+    }
+
     /**
      * Combines the queue_basename from configuration with the
      * site server name and queue name to give eg:
@@ -360,7 +494,7 @@ class StompQueueManager extends QueueManager
     protected function queueName($queue)
     {
         return common_config('queue', 'queue_basename') .
-            common_config('site', 'server') . '/' . $queue;
+            $this->currentSite() . '/' . $queue;
     }
 
     /**
diff --git a/lib/uapplugin.php b/lib/uapplugin.php
new file mode 100644 (file)
index 0000000..ef35baf
--- /dev/null
@@ -0,0 +1,204 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * UAP (Universal Ad Package) plugin
+ *
+ * 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  Action
+ * @package   StatusNet
+ * @author    Sarven Capadisli <csarven@status.net>
+ * @author    Evan Prodromou <evan@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') && !defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * Abstract superclass for advertising plugins
+ *
+ * Plugins for showing ads should derive from this plugin.
+ *
+ * Outputs the following ad types (based on UAP):
+ *
+ * Medium Rectangle 300x250
+ * Rectangle        180x150
+ * Leaderboard      728x90
+ * Wide Skyscraper  160x600
+ *
+ * @category Plugin
+ * @package  StatusNet
+ * @author   Sarven Capadisli <csarven@status.net>
+ * @author   Evan Prodromou <evan@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/
+ */
+
+abstract class UAPPlugin extends Plugin
+{
+    public $mediumRectangle = null;
+    public $rectangle       = null;
+    public $leaderboard     = null;
+    public $wideSkyscraper  = null;
+
+    /**
+     * Output our dedicated stylesheet
+     *
+     * @param Action $action Action being shown
+     *
+     * @return boolean hook flag
+     */
+
+    function onEndShowStatusNetStyles($action)
+    {
+        // XXX: allow override by theme
+        $action->cssLink('css/uap.css', 'base', 'screen, projection, tv');
+        return true;
+    }
+
+    /**
+     * Add a medium rectangle ad at the beginning of sidebar
+     *
+     * @param Action $action Action being shown
+     *
+     * @return boolean hook flag
+     */
+
+    function onStartShowAside($action)
+    {
+        if (!is_null($this->mediumRectangle)) {
+
+            $action->elementStart('div',
+                                  array('id' => 'ad_medium-rectangle',
+                                        'class' => 'ad'));
+
+            $this->showMediumRectangle($action);
+
+            $action->elementEnd('div');
+        }
+
+        return true;
+    }
+
+    /**
+     * Add a leaderboard in the header
+     *
+     * @param Action $action Action being shown
+     *
+     * @return boolean hook flag
+     */
+
+    function onEndShowHeader($action)
+    {
+        if (!is_null($this->leaderboard)) {
+            $action->elementStart('div',
+                                  array('id' => 'ad_leaderboard',
+                                        'class' => 'ad'));
+            $this->showLeaderboard($action);
+            $action->elementEnd('div');
+        }
+
+        return true;
+    }
+
+    /**
+     * Add a rectangle before aside sections
+     *
+     * @param Action $action Action being shown
+     *
+     * @return boolean hook flag
+     */
+
+    function onStartShowSections($action)
+    {
+        if (!is_null($this->rectangle)) {
+            $action->elementStart('div',
+                                  array('id' => 'ad_rectangle',
+                                        'class' => 'ad'));
+            $this->showRectangle($action);
+            $action->elementEnd('div');
+        }
+
+        return true;
+    }
+
+    /**
+     * Add a wide skyscraper after the aside
+     *
+     * @param Action $action Action being shown
+     *
+     * @return boolean hook flag
+     */
+
+    function onEndShowAside($action)
+    {
+        if (!is_null($this->wideSkyscraper)) {
+            $action->elementStart('div',
+                                  array('id' => 'ad_wide-skyscraper',
+                                        'class' => 'ad'));
+
+            $this->showWideSkyscraper($action);
+
+            $action->elementEnd('div');
+        }
+        return true;
+    }
+
+    /**
+     * Show a medium rectangle ad
+     *
+     * @param Action $action Action being shown
+     *
+     * @return void
+     */
+
+    abstract protected function showMediumRectangle($action);
+
+    /**
+     * Show a rectangle ad
+     *
+     * @param Action $action Action being shown
+     *
+     * @return void
+     */
+
+    abstract protected function showRectangle($action);
+
+    /**
+     * Show a wide skyscraper ad
+     *
+     * @param Action $action Action being shown
+     *
+     * @return void
+     */
+
+    abstract protected function showWideSkyscraper($action);
+
+    /**
+     * Show a leaderboard ad
+     *
+     * @param Action $action Action being shown
+     *
+     * @return void
+     */
+
+    abstract protected function showLeaderboard($action);
+}
diff --git a/plugins/Adsense/AdsensePlugin.php b/plugins/Adsense/AdsensePlugin.php
new file mode 100644 (file)
index 0000000..ab2b9a6
--- /dev/null
@@ -0,0 +1,160 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin for Google Adsense
+ *
+ * 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  Ads
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@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);
+}
+
+/**
+ * Plugin to add Google Adsense to StatusNet sites
+ *
+ * This plugin lets you add Adsense ad units to your StatusNet site.
+ *
+ * We support the 4 ad sizes for the Universal Ad Platform (UAP):
+ *
+ *     Medium Rectangle
+ *     (Small) Rectangle
+ *     Leaderboard
+ *     Wide Skyscraper
+ *
+ * They fit in different places on the default theme. Some themes
+ * might interact quite poorly with this plugin.
+ *
+ * To enable advertising, you must sign up with Google Adsense and
+ * get a client ID.
+ *
+ *     https://www.google.com/adsense/
+ *
+ * You'll also need to create an Adsense for Content unit in one
+ * of the four sizes described above. At the end of the process,
+ * note the "google_ad_client" and "google_ad_slot" values in the
+ * resultant Javascript.
+ *
+ * Add the plugin to config.php like so:
+ *
+ *     addPlugin('Adsense', array('client' => 'Your client ID',
+ *                                'rectangle' => 'slot'));
+ *
+ * Here, your client ID is the value of google_ad_client and the
+ * slot is the value of google_ad_slot. Note that if you create
+ * a different size, you'll need to provide different arguments:
+ * 'mediumRectangle', 'leaderboard', or 'wideSkyscraper'.
+ *
+ * If for some reason your ad server is different from the default,
+ * use the 'adScript' parameter to set the full path to the ad script.
+ *
+ * @category Plugin
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@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/
+ *
+ * @seeAlso  UAPPlugin
+ */
+
+class AdsensePlugin extends UAPPlugin
+{
+    public $adScript = 'http://pagead2.googlesyndication.com/pagead/show_ads.js';
+    public $client   = null;
+
+    /**
+     * Show a medium rectangle 'ad'
+     *
+     * @param Action $action Action being shown
+     *
+     * @return void
+     */
+
+    protected function showMediumRectangle($action)
+    {
+        $this->showAdsenseCode($action, 300, 250, $this->mediumRectangle);
+    }
+
+    /**
+     * Show a rectangle 'ad'
+     *
+     * @param Action $action Action being shown
+     *
+     * @return void
+     */
+
+    protected function showRectangle($action)
+    {
+        $this->showAdsenseCode($action, 180, 150, $this->rectangle);
+    }
+
+    /**
+     * Show a wide skyscraper ad
+     *
+     * @param Action $action Action being shown
+     *
+     * @return void
+     */
+
+    protected function showWideSkyscraper($action)
+    {
+        $this->showAdsenseCode($action, 160, 600, $this->wideSkyscraper);
+    }
+
+    /**
+     * Show a leaderboard ad
+     *
+     * @param Action $action Action being shown
+     *
+     * @return void
+     */
+
+    protected function showLeaderboard($action)
+    {
+        $this->showAdsenseCode($action, 728, 90, $this->leaderboard);
+    }
+
+    /**
+     * Output the bits of JavaScript code to show Adsense
+     *
+     * @param Action  $action Action being shown
+     * @param integer $width  Width of the block
+     * @param integer $height Height of the block
+     * @param string  $slot   Slot identifier
+     *
+     * @return void
+     */
+
+    protected function showAdsenseCode($action, $width, $height, $slot)
+    {
+        $code  = 'google_ad_client = "'.$this->client.'"; ';
+        $code .= 'google_ad_slot = "'.$slot.'"; ';
+        $code .= 'google_ad_width = '.$width.'; ';
+        $code .= 'google_ad_height = '.$height.'; ';
+
+        $action->inlineScript($code);
+
+        $action->script($this->adScript);
+    }
+}
\ No newline at end of file
diff --git a/plugins/BlankAd/BlankAdPlugin.php b/plugins/BlankAd/BlankAdPlugin.php
new file mode 100644 (file)
index 0000000..0e2719a
--- /dev/null
@@ -0,0 +1,124 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin for testing ad layout
+ *
+ * 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  Ads
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@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);
+}
+
+/**
+ * Plugin for testing ad layout
+ *
+ * This plugin uses the UAPPlugin framework to output ad content. However,
+ * its ad content is just images with one red pixel stretched to the
+ * right size. It's mostly useful for debugging theme layout.
+ *
+ * To use this plugin, set the parameter for the ad size you want to use
+ * to true (or anything non-null). For example, to make a leaderboard:
+ *
+ *     addPlugin('BlankAd', array('leaderboard' => true));
+ *
+ * @category Plugin
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@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/
+ *
+ * @seeAlso  Location
+ */
+
+class BlankAdPlugin extends UAPPlugin
+{
+    /**
+     * Show a medium rectangle 'ad'
+     *
+     * @param Action $action Action being shown
+     *
+     * @return void
+     */
+
+    protected function showMediumRectangle($action)
+    {
+        $action->element('img',
+                         array('width' => 300,
+                               'height' => 250,
+                               'src' => common_path('plugins/BlankAd/redpixel.png')),
+                         '');
+    }
+
+    /**
+     * Show a rectangle 'ad'
+     *
+     * @param Action $action Action being shown
+     *
+     * @return void
+     */
+
+    protected function showRectangle($action)
+    {
+        $action->element('img',
+                         array('width' => 180,
+                               'height' => 150,
+                               'src' => common_path('plugins/BlankAd/redpixel.png')),
+                         '');
+    }
+
+    /**
+     * Show a wide skyscraper ad
+     *
+     * @param Action $action Action being shown
+     *
+     * @return void
+     */
+
+    protected function showWideSkyscraper($action)
+    {
+        $action->element('img',
+                         array('width' => 160,
+                               'height' => 600,
+                               'src' => common_path('plugins/BlankAd/redpixel.png')),
+                         '');
+    }
+
+    /**
+     * Show a leaderboard ad
+     *
+     * @param Action $action Action being shown
+     *
+     * @return void
+     */
+
+    protected function showLeaderboard($action)
+    {
+        $action->element('img',
+                         array('width' => 728,
+                               'height' => 90,
+                               'src' => common_path('plugins/BlankAd/redpixel.png')),
+                         '');
+    }
+}
\ No newline at end of file
diff --git a/plugins/BlankAd/redpixel.png b/plugins/BlankAd/redpixel.png
new file mode 100644 (file)
index 0000000..26299a5
Binary files /dev/null and b/plugins/BlankAd/redpixel.png differ
index 815fee094ca69510036994c9a628583cc0eb799d..389e1ea81fd0bf437e535ebc3e22057e7adbdb9c 100644 (file)
@@ -89,7 +89,7 @@ class FacebookAction extends Action
 
     function showScripts()
     {
-        $this->script('js/facebookapp.js');
+        $this->script('facebookapp.js');
     }
 
     /**
diff --git a/plugins/OpenX/OpenXPlugin.php b/plugins/OpenX/OpenXPlugin.php
new file mode 100644 (file)
index 0000000..59485f2
--- /dev/null
@@ -0,0 +1,165 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin for OpenX ad server
+ *
+ * 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  Ads
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@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);
+}
+
+/**
+ * Plugin for OpenX Ad Server
+ *
+ * This plugin supports the OpenX ad server, http://www.openx.org/
+ *
+ * We support the 4 ad sizes for the Universal Ad Platform (UAP):
+ *
+ *     Medium Rectangle
+ *     (Small) Rectangle
+ *     Leaderboard
+ *     Wide Skyscraper
+ *
+ * They fit in different places on the default theme. Some themes
+ * might interact quite poorly with this plugin.
+ *
+ * To enable advertising, you will need an OpenX server. You'll need
+ * to set up a "zone" for your StatusNet site that identifies a
+ * kind of ad you want to place (of the above 4 sizes).
+ *
+ * Add the plugin to config.php like so:
+ *
+ *     addPlugin('OpenX', array('adScript' => 'full path to script',
+ *                              'rectangle' => 1));
+ *
+ * Here, the 'adScript' parameter is the full path to the OpenX
+ * ad script, like 'http://example.com/www/delivery/ajs.php'. Note
+ * that we don't do any magic to swap between HTTP and HTTPS, so
+ * if you want HTTPS, say so.
+ *
+ * The 'rectangle' parameter is the zone ID for that ad space on
+ * your site. If you've configured another size, try 'mediumRectangle',
+ * 'leaderboard', or 'wideSkyscraper'.
+ *
+ * If for some reason your ad server is different from the default,
+ * use the 'adScript' parameter to set the full path to the ad script.
+ *
+ * @category Ads
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@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/
+ *
+ * @seeAlso  UAPPlugin
+ */
+
+class OpenXPlugin extends UAPPlugin
+{
+    public $adScript = null;
+
+    /**
+     * Show a medium rectangle 'ad'
+     *
+     * @param Action $action Action being shown
+     *
+     * @return void
+     */
+
+    protected function showMediumRectangle($action)
+    {
+        $this->showAd($action, $this->mediumRectangle);
+    }
+
+    /**
+     * Show a rectangle 'ad'
+     *
+     * @param Action $action Action being shown
+     *
+     * @return void
+     */
+
+    protected function showRectangle($action)
+    {
+        $this->showAd($action, $this->rectangle);
+    }
+
+    /**
+     * Show a wide skyscraper ad
+     *
+     * @param Action $action Action being shown
+     *
+     * @return void
+     */
+
+    protected function showWideSkyscraper($action)
+    {
+        $this->showAd($action, $this->wideSkyscraper);
+    }
+
+    /**
+     * Show a leaderboard ad
+     *
+     * @param Action $action Action being shown
+     *
+     * @return void
+     */
+
+    protected function showLeaderboard($action)
+    {
+        $this->showAd($action, $this->leaderboard);
+    }
+
+    /**
+     * Show an ad using OpenX
+     *
+     * @param Action  $action Action being shown
+     * @param integer $zone   Zone to show
+     *
+     * @return void
+     */
+
+    protected function showAd($action, $zone)
+    {
+$scr = <<<ENDOFSCRIPT
+var m3_u = '%s';
+var m3_r = Math.floor(Math.random()*99999999999);
+if (!document.MAX_used) document.MAX_used = ',';
+document.write ("<scr"+"ipt type='text/javascript' src='"+m3_u);
+document.write ("?zoneid=%d");
+document.write ('&amp;cb=' + m3_r);
+if (document.MAX_used != ',') document.write ("&amp;exclude=" + document.MAX_used);
+document.write (document.charset ? '&amp;charset='+document.charset : (document.characterSet ? '&amp;charset='+document.characterSet : ''));
+document.write ("&amp;loc=" + escape(window.location));
+if (document.referrer) document.write ("&amp;referer=" + escape(document.referrer));
+if (document.context) document.write ("&context=" + escape(document.context));
+if (document.mmm_fo) document.write ("&amp;mmm_fo=1");
+document.write ("'><\/scr"+"ipt>");
+ENDOFSCRIPT;
+
+        $action->inlineScript(sprintf($scr, $this->adScript, $zone));
+        return true;
+    }
+}
\ No newline at end of file
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 89640f5beb25c7e41db6c4a3dceeb2569fa326cb..16e28e94d3eed57718c63e9efdd6de916ec0845f 100644 (file)
@@ -87,7 +87,7 @@ class RealtimePlugin extends Plugin
         $scripts = $this->_getScripts();
 
         foreach ($scripts as $script) {
-            $action->script($script);
+            $action->script(common_path($script));
         }
 
         $user = common_current_user();
index 57b3c1c9953d55a98e87b5d5b7af619636248492..c7f57ffc7753066f29b86d4020b072fe41ddd928 100644 (file)
@@ -20,7 +20,8 @@
  * @category  Plugin
  * @package   StatusNet
  * @author    Zach Copley <zach@status.net>
- * @copyright 2009 Control Yourself, Inc.
+ * @author    Julien C <chaumond@gmail.com>
+ * @copyright 2009-2010 Control Yourself, Inc.
  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link      http://laconi.ca/
  */
@@ -41,6 +42,7 @@ define('TWITTERBRIDGEPLUGIN_VERSION', '0.9');
  * @category Plugin
  * @package  StatusNet
  * @author   Zach Copley <zach@status.net>
+ * @author   Julien C <chaumond@gmail.com>
  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link     http://laconi.ca/
  * @link     http://twitter.com/
@@ -72,6 +74,27 @@ class TwitterBridgePlugin extends Plugin
         $m->connect('twitter/authorization',
                     array('action' => 'twitterauthorization'));
         $m->connect('settings/twitter', array('action' => 'twittersettings'));
+        $m->connect('main/twitterlogin', array('action' => 'twitterlogin'));
+
+        return true;
+    }
+
+    /*
+     * Add a login tab for 'Sign in with Twitter'
+     *
+     * @param Action &action the current action
+     *
+     * @return void
+     */
+    function onEndLoginGroupNav(&$action)
+    {
+
+        $action_name = $action->trimmed('action');
+
+        $action->menuItem(common_local_url('twitterlogin'),
+                                           _('Twitter'),
+                                           _('Login or register using Twitter'),
+                                             'twitterlogin' === $action_name);
 
         return true;
     }
@@ -108,6 +131,7 @@ class TwitterBridgePlugin extends Plugin
         switch ($cls) {
         case 'TwittersettingsAction':
         case 'TwitterauthorizationAction':
+        case 'TwitterloginAction':
             include_once INSTALLDIR . '/plugins/TwitterBridge/' .
               strtolower(mb_substr($cls, 0, -6)) . '.php';
             return false;
index 4af2f0394165d6a6986dca87ee5a46d60d55c270..b2657ff61fb0bbd220e71105139b8cc1c51fd96d 100644 (file)
  * 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  TwitterauthorizationAction
+ * @category  Plugin
  * @package   StatusNet
- * @author    Zach Copely <zach@status.net>
- * @copyright 2009 StatusNet, Inc.
+ * @author    Zach Copley <zach@status.net>
+ * @author    Julien C <chaumond@gmail.com>
+ * @copyright 2009-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/
  */
@@ -41,15 +42,21 @@ require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
  * (Foreign_link) between the StatusNet user and Twitter user and stores the
  * access token and secret in the link.
  *
- * @category Twitter
+ * @category Plugin
  * @package  StatusNet
  * @author   Zach Copley <zach@status.net>
+ * @author   Julien C <chaumond@gmail.com>
  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link     http://laconi.ca/
  *
  */
 class TwitterauthorizationAction extends Action
 {
+    var $twuid        = null;
+    var $tw_fields    = null;
+    var $access_token = null;
+    var $signin       = null;
+
     /**
      * Initialize class members. Looks for 'oauth_token' parameter.
      *
@@ -61,6 +68,7 @@ class TwitterauthorizationAction extends Action
     {
         parent::prepare($args);
 
+        $this->signin      = $this->boolean('signin');
         $this->oauth_token = $this->arg('oauth_token');
 
         return true;
@@ -77,28 +85,61 @@ class TwitterauthorizationAction extends Action
     {
         parent::handle($args);
 
-        if (!common_logged_in()) {
-            $this->clientError(_m('Not logged in.'), 403);
+        if (common_logged_in()) {
+            $user  = common_current_user();
+            $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
+
+            // If there's already a foreign link record, it means we already
+            // have an access token, and this is unecessary. So go back.
+
+            if (isset($flink)) {
+                common_redirect(common_local_url('twittersettings'));
+            }
         }
 
-        $user  = common_current_user();
-        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
+        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
 
-        // If there's already a foreign link record, it means we already
-        // have an access token, and this is unecessary. So go back.
+            // User was not logged in to StatusNet before
 
-        if (isset($flink)) {
-            common_redirect(common_local_url('twittersettings'));
-        }
+            $this->twuid = $this->trimmed('twuid');
+
+            $this->tw_fields = array('screen_name' => $this->trimmed('tw_fields_screen_name'),
+                                     'fullname' => $this->trimmed('tw_fields_fullname'));
+
+            $this->access_token = new OAuthToken($this->trimmed('access_token_key'), $this->trimmed('access_token_secret'));
+
+            $token = $this->trimmed('token');
 
-        // $this->oauth_token is only populated once Twitter authorizes our
-        // request token. If it's empty we're at the beginning of the auth
-        // process
+            if (!$token || $token != common_session_token()) {
+                $this->showForm(_('There was a problem with your session token. Try again, please.'));
+                return;
+            }
 
-        if (empty($this->oauth_token)) {
-            $this->authorizeRequestToken();
+            if ($this->arg('create')) {
+                if (!$this->boolean('license')) {
+                    $this->showForm(_('You can\'t register if you don\'t agree to the license.'),
+                                    $this->trimmed('newname'));
+                    return;
+                }
+                $this->createNewUser();
+            } else if ($this->arg('connect')) {
+                $this->connectNewUser();
+            } else {
+                common_debug('Twitter Connect Plugin - ' .
+                             print_r($this->args, true));
+                $this->showForm(_('Something weird happened.'),
+                                $this->trimmed('newname'));
+            }
         } else {
-            $this->saveAccessToken();
+            // $this->oauth_token is only populated once Twitter authorizes our
+            // request token. If it's empty we're at the beginning of the auth
+            // process
+
+            if (empty($this->oauth_token)) {
+                $this->authorizeRequestToken();
+            } else {
+                $this->saveAccessToken();
+            }
         }
     }
 
@@ -123,7 +164,7 @@ class TwitterauthorizationAction extends Action
             $_SESSION['twitter_request_token']        = $req_tok->key;
             $_SESSION['twitter_request_token_secret'] = $req_tok->secret;
 
-            $auth_link = $client->getAuthorizeLink($req_tok);
+            $auth_link = $client->getAuthorizeLink($req_tok, $this->signin);
 
         } catch (OAuthClientException $e) {
             $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s',
@@ -150,6 +191,8 @@ class TwitterauthorizationAction extends Action
             $this->serverError(_m('Couldn\'t link your Twitter account.'));
         }
 
+        $twitter_user = null;
+
         try {
 
             $client = new TwitterOAuthClient($_SESSION['twitter_request_token'],
@@ -165,40 +208,54 @@ class TwitterauthorizationAction extends Action
             $twitter_user = $client->verifyCredentials();
 
         } catch (OAuthClientException $e) {
-            $msg = sprintf('OAuth client cURL error - code: %1$s, msg: %2$s',
+            $msg = sprintf('OAuth client error - code: %1$s, msg: %2$s',
                            $e->getCode(), $e->getMessage());
             $this->serverError(_m('Couldn\'t link your Twitter account.'));
         }
 
-        // Save the access token and Twitter user info
+        if (common_logged_in()) {
+
+            // Save the access token and Twitter user info
+
+            $user = common_current_user();
+            $this->saveForeignLink($user->id, $twitter_user->id, $atok);
+            save_twitter_user($twitter_user->id, $twitter_user->name);
+
+        } else {
 
-        $this->saveForeignLink($atok, $twitter_user);
+            $this->twuid = $twitter_user->id;
+            $this->tw_fields = array("screen_name" => $twitter_user->screen_name,
+                                     "name" => $twitter_user->name);
+            $this->access_token = $atok;
+            $this->tryLogin();
+        }
 
         // Clean up the the mess we made in the session
 
         unset($_SESSION['twitter_request_token']);
         unset($_SESSION['twitter_request_token_secret']);
 
-        common_redirect(common_local_url('twittersettings'));
+        if (common_logged_in()) {
+            common_redirect(common_local_url('twittersettings'));
+        }
     }
 
     /**
      * Saves a Foreign_link between Twitter user and local user,
      * which includes the access token and secret.
      *
-     * @param OAuthToken $access_token the access token to save
-     * @param mixed      $twitter_user twitter API user object
+     * @param int        $user_id StatusNet user ID
+     * @param int        $twuid   Twitter user ID
+     * @param OAuthToken $token   the access token to save
      *
      * @return nothing
      */
-    function saveForeignLink($access_token, $twitter_user)
+    function saveForeignLink($user_id, $twuid, $access_token)
     {
-        $user = common_current_user();
-
         $flink = new Foreign_link();
 
-        $flink->user_id     = $user->id;
-        $flink->foreign_id  = $twitter_user->id;
+        $flink->user_id     = $user_id;
+        $flink->foreign_id  = $twuid;
         $flink->service     = TWITTER_SERVICE;
 
         $creds = TwitterOAuthClient::packToken($access_token);
@@ -214,10 +271,325 @@ class TwitterauthorizationAction extends Action
 
         if (empty($flink_id)) {
             common_log_db_error($flink, 'INSERT', __FILE__);
-                $this->serverError(_m('Couldn\'t link your Twitter account.'));
+                $this->serverError(_('Couldn\'t link your Twitter account.'));
+        }
+
+        return $flink_id;
+    }
+
+    function showPageNotice()
+    {
+        if ($this->error) {
+            $this->element('div', array('class' => 'error'), $this->error);
+        } else {
+            $this->element('div', 'instructions',
+                           sprintf(_('This is the first time you\'ve logged into %s so we must connect your Twitter account to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name')));
+        }
+    }
+
+    function title()
+    {
+        return _('Twitter Account Setup');
+    }
+
+    function showForm($error=null, $username=null)
+    {
+        $this->error = $error;
+        $this->username = $username;
+
+        $this->showPage();
+    }
+
+    function showPage()
+    {
+        parent::showPage();
+    }
+
+    function showContent()
+    {
+        if (!empty($this->message_text)) {
+            $this->element('p', null, $this->message);
+            return;
+        }
+
+        $this->elementStart('form', array('method' => 'post',
+                                          'id' => 'form_settings_twitter_connect',
+                                          'class' => 'form_settings',
+                                          'action' => common_local_url('twitterauthorization')));
+        $this->elementStart('fieldset', array('id' => 'settings_twitter_connect_options'));
+        $this->element('legend', null, _('Connection options'));
+        $this->elementStart('ul', 'form_data');
+        $this->elementStart('li');
+        $this->element('input', array('type' => 'checkbox',
+                                      'id' => 'license',
+                                      'class' => 'checkbox',
+                                      'name' => 'license',
+                                      'value' => 'true'));
+        $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license'));
+        $this->text(_('My text and files are available under '));
+        $this->element('a', array('href' => common_config('license', 'url')),
+                       common_config('license', 'title'));
+        $this->text(_(' except this private data: password, email address, IM address, phone number.'));
+        $this->elementEnd('label');
+        $this->elementEnd('li');
+        $this->elementEnd('ul');
+        $this->hidden('access_token_key', $this->access_token->key);
+        $this->hidden('access_token_secret', $this->access_token->secret);
+        $this->hidden('twuid', $this->twuid);
+        $this->hidden('tw_fields_screen_name', $this->tw_fields['screen_name']);
+        $this->hidden('tw_fields_name', $this->tw_fields['name']);
+
+        $this->elementStart('fieldset');
+        $this->hidden('token', common_session_token());
+        $this->element('legend', null,
+                       _('Create new account'));
+        $this->element('p', null,
+                       _('Create a new user with this nickname.'));
+        $this->elementStart('ul', 'form_data');
+        $this->elementStart('li');
+        $this->input('newname', _('New nickname'),
+                     ($this->username) ? $this->username : '',
+                     _('1-64 lowercase letters or numbers, no punctuation or spaces'));
+        $this->elementEnd('li');
+        $this->elementEnd('ul');
+        $this->submit('create', _('Create'));
+        $this->elementEnd('fieldset');
+
+        $this->elementStart('fieldset');
+        $this->element('legend', null,
+                       _('Connect existing account'));
+        $this->element('p', null,
+                       _('If you already have an account, login with your username and password to connect it to your Twitter account.'));
+        $this->elementStart('ul', 'form_data');
+        $this->elementStart('li');
+        $this->input('nickname', _('Existing nickname'));
+        $this->elementEnd('li');
+        $this->elementStart('li');
+        $this->password('password', _('Password'));
+        $this->elementEnd('li');
+        $this->elementEnd('ul');
+        $this->submit('connect', _('Connect'));
+        $this->elementEnd('fieldset');
+
+        $this->elementEnd('fieldset');
+        $this->elementEnd('form');
+    }
+
+    function message($msg)
+    {
+        $this->message_text = $msg;
+        $this->showPage();
+    }
+
+    function createNewUser()
+    {
+        if (common_config('site', 'closed')) {
+            $this->clientError(_('Registration not allowed.'));
+            return;
+        }
+
+        $invite = null;
+
+        if (common_config('site', 'inviteonly')) {
+            $code = $_SESSION['invitecode'];
+            if (empty($code)) {
+                $this->clientError(_('Registration not allowed.'));
+                return;
+            }
+
+            $invite = Invitation::staticGet($code);
+
+            if (empty($invite)) {
+                $this->clientError(_('Not a valid invitation code.'));
+                return;
+            }
+        }
+
+        $nickname = $this->trimmed('newname');
+
+        if (!Validate::string($nickname, array('min_length' => 1,
+                                               'max_length' => 64,
+                                               'format' => NICKNAME_FMT))) {
+            $this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
+            return;
+        }
+
+        if (!User::allowed_nickname($nickname)) {
+            $this->showForm(_('Nickname not allowed.'));
+            return;
+        }
+
+        if (User::staticGet('nickname', $nickname)) {
+            $this->showForm(_('Nickname already in use. Try another one.'));
+            return;
+        }
+
+        $fullname = trim($this->tw_fields['name']);
+
+        $args = array('nickname' => $nickname, 'fullname' => $fullname);
+
+        if (!empty($invite)) {
+            $args['code'] = $invite->code;
+        }
+
+        $user = User::register($args);
+
+        $result = $this->saveForeignLink($user->id,
+                                         $this->twuid,
+                                         $this->access_token);
+
+        save_twitter_user($this->twuid, $this->tw_fields['screen_name']);
+
+        if (!$result) {
+            $this->serverError(_('Error connecting user to Twitter.'));
+            return;
+        }
+
+        common_set_user($user);
+        common_real_login(true);
+
+        common_debug('TwitterBridge Plugin - ' .
+                     "Registered new user $user->id from Twitter user $this->twuid");
+
+        common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)),
+                        303);
+    }
+
+    function connectNewUser()
+    {
+        $nickname = $this->trimmed('nickname');
+        $password = $this->trimmed('password');
+
+        if (!common_check_user($nickname, $password)) {
+            $this->showForm(_('Invalid username or password.'));
+            return;
+        }
+
+        $user = User::staticGet('nickname', $nickname);
+
+        if (!empty($user)) {
+            common_debug('TwitterBridge Plugin - ' .
+                         "Legit user to connect to Twitter: $nickname");
+        }
+
+        $result = $this->saveForeignLink($user->id,
+                                         $this->twuid,
+                                         $this->access_token);
+
+        save_twitter_user($this->twuid, $this->tw_fields['screen_name']);
+
+        if (!$result) {
+            $this->serverError(_('Error connecting user to Twitter.'));
+            return;
+        }
+
+        common_debug('TwitterBridge Plugin - ' .
+                     "Connected Twitter user $this->twuid to local user $user->id");
+
+        common_set_user($user);
+        common_real_login(true);
+
+        $this->goHome($user->nickname);
+    }
+
+    function connectUser()
+    {
+        $user = common_current_user();
+
+        $result = $this->flinkUser($user->id, $this->twuid);
+
+        if (empty($result)) {
+            $this->serverError(_('Error connecting user to Twitter.'));
+            return;
         }
 
-        save_twitter_user($twitter_user->id, $twitter_user->screen_name);
+        common_debug('TwitterBridge Plugin - ' .
+                     "Connected Twitter user $this->twuid to local user $user->id");
+
+        // Return to Twitter connection settings tab
+        common_redirect(common_local_url('twittersettings'), 303);
+    }
+
+    function tryLogin()
+    {
+        common_debug('TwitterBridge Plugin - ' .
+                     "Trying login for Twitter user $this->twuid.");
+
+        $flink = Foreign_link::getByForeignID($this->twuid,
+                                              TWITTER_SERVICE);
+
+        if (!empty($flink)) {
+            $user = $flink->getUser();
+
+            if (!empty($user)) {
+
+                common_debug('TwitterBridge Plugin - ' .
+                             "Logged in Twitter user $flink->foreign_id as user $user->id ($user->nickname)");
+
+                common_set_user($user);
+                common_real_login(true);
+                $this->goHome($user->nickname);
+            }
+
+        } else {
+
+            common_debug('TwitterBridge Plugin - ' .
+                         "No flink found for twuid: $this->twuid - new user");
+
+            $this->showForm(null, $this->bestNewNickname());
+        }
+    }
+
+    function goHome($nickname)
+    {
+        $url = common_get_returnto();
+        if ($url) {
+            // We don't have to return to it again
+            common_set_returnto(null);
+        } else {
+            $url = common_local_url('all',
+                                    array('nickname' =>
+                                          $nickname));
+        }
+
+        common_redirect($url, 303);
+    }
+
+    function bestNewNickname()
+    {
+        if (!empty($this->tw_fields['name'])) {
+            $nickname = $this->nicknamize($this->tw_fields['name']);
+            if ($this->isNewNickname($nickname)) {
+                return $nickname;
+            }
+        }
+
+        return null;
+    }
+
+     // Given a string, try to make it work as a nickname
+
+     function nicknamize($str)
+     {
+         $str = preg_replace('/\W/', '', $str);
+         $str = str_replace(array('-', '_'), '', $str);
+         return strtolower($str);
+     }
+
+    function isNewNickname($str)
+    {
+        if (!Validate::string($str, array('min_length' => 1,
+                                          'max_length' => 64,
+                                          'format' => NICKNAME_FMT))) {
+            return false;
+        }
+        if (!User::allowed_nickname($str)) {
+            return false;
+        }
+        if (User::staticGet('nickname', $str)) {
+            return false;
+        }
+        return true;
     }
 
 }
diff --git a/plugins/TwitterBridge/twitterlogin.php b/plugins/TwitterBridge/twitterlogin.php
new file mode 100644 (file)
index 0000000..79421fb
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * 'Sign in with Twitter' login page
+ *
+ * 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  Login
+ * @package   StatusNet
+ * @author    Julien Chaumond <chaumond@gmail.com>
+ * @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') && !defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+
+/**
+ * Page for logging in with Twitter
+ *
+ * @category Login
+ * @package  StatusNet
+ * @author   Julien Chaumond <chaumond@gmail.com>
+ * @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 TwitterloginAction extends Action
+{
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (common_is_real_login()) {
+            $this->clientError(_('Already logged in.'));
+        }
+
+        $this->showPage();
+    }
+
+    function title()
+    {
+        return _('Twitter Login');
+    }
+
+    function getInstructions()
+    {
+        return _('Login with your Twitter account');
+    }
+
+    function showPageNotice()
+    {
+        $instr = $this->getInstructions();
+        $output = common_markup_to_html($instr);
+        $this->elementStart('div', 'instructions');
+        $this->raw($output);
+        $this->elementEnd('div');
+    }
+
+    function showContent()
+    {
+        $this->elementStart('a', array('href' => common_local_url('twitterauthorization',
+                                                                  null,
+                                                                  array('signin' => true))));
+        $this->element('img', array('src' => common_path('plugins/TwitterBridge/Sign-in-with-Twitter-lighter.png'),
+                                    'alt' => 'Sign in with Twitter'));
+        $this->elementEnd('a');
+    }
+
+    function showLocalNav()
+    {
+        $nav = new LoginGroupNav($this);
+        $nav->show();
+    }
+}
index bad2b74ca324101388eaa6feae1e787e65c72892..277e7ab409c69b76598e5a72a093274f263c3d49 100644 (file)
@@ -45,6 +45,7 @@ class TwitterOAuthClient extends OAuthClient
 {
     public static $requestTokenURL = 'https://twitter.com/oauth/request_token';
     public static $authorizeURL    = 'https://twitter.com/oauth/authorize';
+    public static $signinUrl       = 'https://twitter.com/oauth/authenticate';
     public static $accessTokenURL  = 'https://twitter.com/oauth/access_token';
 
     /**
@@ -97,9 +98,11 @@ class TwitterOAuthClient extends OAuthClient
      *
      * @return the link
      */
-    function getAuthorizeLink($request_token)
+    function getAuthorizeLink($request_token, $signin = false)
     {
-        return parent::getAuthorizeLink(self::$authorizeURL,
+        $url = ($signin) ? self::$signinUrl : self::$authorizeURL;
+
+        return parent::getAuthorizeLink($url,
                                         $request_token,
                                         common_local_url('twitterauthorization'));
     }
index bc9a636a1586d4bc289d768371a2fb78bea53b42..0137060e9c04f300ba5726ad8ae8880059f80e2a 100644 (file)
@@ -121,8 +121,35 @@ class TwittersettingsAction extends ConnectSettingsAction
             $this->elementEnd('p');
             $this->element('p', 'form_note',
                            _m('Connected Twitter account'));
+            $this->elementEnd('fieldset');
+
+            $this->elementStart('fieldset');
+
+            $this->element('legend', null, _m('Disconnect my account from Twitter'));
 
-            $this->submit('remove', _m('Remove'));
+            if (!$user->password) {
+
+                $this->elementStart('p', array('class' => 'form_guide'));
+                $this->text(_m('Disconnecting your Twitter ' .
+                               'could make it impossible to log in! Please '));
+                $this->element('a',
+                    array('href' => common_local_url('passwordsettings')),
+                        _m('set a password'));
+
+                $this->text(_m(' first.'));
+                $this->elementEnd('p');
+            } else {
+
+                $note = _m('Keep your %1$s account but disconnect from Twitter. ' .
+                    'You can use your %1$s password to log in.');
+
+                $site = common_config('site', 'name');
+
+                $this->element('p', 'instructions',
+                    sprintf($note, $site));
+
+                $this->submit('disconnect', _m('Disconnect'));
+            }
 
             $this->elementEnd('fieldset');
 
@@ -205,7 +232,7 @@ class TwittersettingsAction extends ConnectSettingsAction
 
         if ($this->arg('save')) {
             $this->savePreferences();
-        } else if ($this->arg('remove')) {
+        } else if ($this->arg('disconnect')) {
             $this->removeTwitterAccount();
         } else {
             $this->showForm(_m('Unexpected form submission.'));
@@ -231,7 +258,7 @@ class TwittersettingsAction extends ConnectSettingsAction
             return;
         }
 
-        $this->showForm(_m('Twitter account removed.'), true);
+        $this->showForm(_m('Twitter account disconnected.'), 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 {
diff --git a/scripts/queuectl.php b/scripts/queuectl.php
new file mode 100755 (executable)
index 0000000..1c9ea33
--- /dev/null
@@ -0,0 +1,85 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, 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/>.
+ */
+
+/**
+ * Sends control signals to running queue daemons.
+ *
+ * @author Brion Vibber <brion@status.net>
+ * @package QueueHandler
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+
+$shortoptions = 'ur';
+$longoptions = array('update', 'restart', 'stop');
+
+$helptext = <<<END_OF_QUEUECTL_HELP
+Send broadcast events to control any running queue handlers.
+(Currently for Stomp queues only.)
+
+Events relating to current site (as selected with -s etc)
+    -u --update       Announce new site or updated configuration. Running
+                      daemons will start subscribing to any new queues needed
+                      for this site.
+
+Global events:
+    -r --restart      Graceful restart of all threads
+       --stop         Graceful shutdown of all threads
+
+END_OF_QUEUECTL_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+function doSendControl($message, $event, $param='')
+{
+    print $message;
+    $qm = QueueManager::get();
+    if ($qm->sendControlSignal($event, $param)) {
+        print " sent.\n";
+    } else {
+        print " FAILED.\n";
+    }
+}
+
+$actions = 0;
+
+if (have_option('u') || have_option('--update')) {
+    $nickname = common_config('site', 'nickname');
+    doSendControl("Sending site update signal to queue daemons for $nickname",
+                  "update", $nickname);
+    $actions++;
+}
+
+if (have_option('r') || have_option('--restart')) {
+    doSendControl("Sending graceful restart signal to queue daemons...",
+                  "restart");
+    $actions++;
+}
+
+if (have_option('--stop')) {
+    doSendControl("Sending graceful shutdown signal to queue daemons...",
+                  "shutdown");
+    $actions++;
+}
+
+if (!$actions) {
+    show_help();
+}
+
index a9cfda6d72e8f927ee513fe8572a26ccde00c96c..c2e2351c3910e307331303c0509e30692f89b012 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
@@ -115,7 +115,7 @@ class QueueDaemon extends SpawningDaemon
 
         $this->log(LOG_INFO, 'terminating normally');
 
-        return true;
+        return $master->respawn ? self::EXIT_RESTART : self::EXIT_SHUTDOWN;
     }
 }
 
@@ -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/scripts/sendemail.php b/scripts/sendemail.php
new file mode 100755 (executable)
index 0000000..436e085
--- /dev/null
@@ -0,0 +1,82 @@
+#!/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__) . '/..'));
+
+$shortoptions = 'i:n:';
+$longoptions = array('id=', 'nickname=', 'subject=');
+
+$helptext = <<<END_OF_USEREMAIL_HELP
+sendemail.php [options] < <message body>
+Sends given email text to user.
+
+  -i --id       id of the user to query
+  -n --nickname nickname of the user to query
+     --subject  mail subject line (required)
+
+END_OF_USEREMAIL_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+if (have_option('i', 'id')) {
+    $id = get_option_value('i', 'id');
+    $user = User::staticGet('id', $id);
+    if (empty($user)) {
+        print "Can't find user with ID $id\n";
+        exit(1);
+    }
+} else if (have_option('n', 'nickname')) {
+    $nickname = get_option_value('n', 'nickname');
+    $user = User::staticGet('nickname', $nickname);
+    if (empty($user)) {
+        print "Can't find user with nickname '$nickname'\n";
+        exit(1);
+    }
+} else {
+    print "You must provide a user by --id or --nickname\n";
+    exit(1);
+}
+
+if (empty($user->email)) {
+    // @fixme unconfirmed address?
+    print "No email registered for user '$user->nickname'\n";
+    exit(1);
+}
+
+if (!have_option('subject')) {
+    echo "You must provide a subject line for the mail in --subject='...' param.\n";
+    exit(1);
+}
+$subject = get_option_value('subject');
+
+if (posix_isatty(STDIN)) {
+    print "You must provide message input on stdin!\n";
+    exit(1);
+}
+$body = file_get_contents('php://stdin');
+
+print "Sending to $user->email...";
+if (mail_to_user($user, $subject, $body)) {
+    print " done\n";
+} else {
+    print " failed.\n";
+    exit(1);
+}
+
index 8d03b06f5e8db8c13e00f8ee888195cc32fe3fe6..f247a3bcae0e6910eb709bfddee23fd61682014a 100644 (file)
@@ -11,4 +11,8 @@ export AVATARBASE=/var/www/avatar.example.net
 export BACKGROUNDBASE=/var/www/background.example.net
 export FILEBASE=/var/www/file.example.net
 export PWDGEN="pwgen 20"
-
+export PHPBASE=/var/www/statusnet
+export WILDCARD=example.net
+export MAILTEMPLATE=/etc/statusnet/newsite-mail.txt
+export MAILSUBJECT="Your new StatusNet site"
+export POSTINSTALL=/etc/statusnet/morestuff.sh
index 777711fb55a29d24f836e1b3bca8e2a602b3415c..f502a169a2bd609a3afd726cb7d72f26d2dcc0ad 100755 (executable)
@@ -2,9 +2,23 @@
 
 source /etc/statusnet/setup.cfg
 
-export nickname=$1
-export sitename=$2
+# setup_status_net.sh mysite 'My Site' '1user' 'owner@example.com' 'Firsty McLastname'
 
+export nickname="$1"
+export sitename="$2"
+export tags="$3"
+export email="$4"
+export fullname="$5"
+
+# Fixme: if this is changed later we need to update profile URLs
+# for the created user.
+export server="$nickname.$WILDCARD"
+
+# End-user info
+export userpass=`$PWDGEN`
+export roles="administrator moderator owner"
+
+# DB info
 export password=`$PWDGEN`
 export database=$nickname$DBBASE
 export username=$nickname$USERBASE
@@ -21,8 +35,8 @@ mysql -h $DBHOST -u $ADMIN --password=$ADMINPASS $SITEDB << ENDOFCOMMANDS
 
 GRANT ALL ON $database.* TO '$username'@'localhost' IDENTIFIED BY '$password';
 GRANT ALL ON $database.* TO '$username'@'%' IDENTIFIED BY '$password';
-INSERT INTO status_network (nickname, dbhost, dbuser, dbpass, dbname, sitename, created)
-VALUES ('$nickname', '$DBHOSTNAME', '$username', '$password', '$database', '$sitename', now());
+INSERT INTO status_network (nickname, dbhost, dbuser, dbpass, dbname, sitename, created, tags)
+VALUES ('$nickname', '$DBHOSTNAME', '$username', '$password', '$database', '$sitename', now(), '$tags');
 
 ENDOFCOMMANDS
 
@@ -30,3 +44,39 @@ for top in $AVATARBASE $FILEBASE $BACKGROUNDBASE; do
     mkdir $top/$nickname
     chmod a+w $top/$nickname
 done
+
+php $PHPBASE/scripts/registeruser.php \
+  -s"$server" \
+  -n"$nickname" \
+  -f"$fullname" \
+  -w"$userpass" \
+  -e"$email"
+
+for role in $roles
+do
+  php $PHPBASE/scripts/userrole.php \
+    -s"$server" \
+    -n"$nickname" \
+    -r"$role"
+done
+
+if [ -f "$MAILTEMPLATE" ]
+then
+    # fixme how safe is this? are sitenames sanitized?
+    cat $MAILTEMPLATE | \
+      sed "s/\$nickname/$nickname/" | \
+      sed "s/\$sitename/$sitename/" | \
+      sed "s/\$userpass/$userpass/" | \
+      php $PHPBASE/scripts/sendemail.php \
+        -s"$server" \
+        -n"$nickname" \
+        --subject="$MAILSUBJECT"
+else
+    echo "No mail template, not sending email."
+fi
+
+if [ -f "$POSTINSTALL" ]
+then
+    echo "Running $POSTINSTALL ..."
+    source "$POSTINSTALL"
+fi
index fd7cf055b485cedaae3f327952c16baf4f634456..46dd9b90cc636be75028f7ddbfac0ff563143879 100755 (executable)
@@ -56,7 +56,7 @@ class XMPPDaemon extends SpawningDaemon
 
         common_log(LOG_INFO, 'terminating normally');
 
-        return true;
+        return $master->respawn ? self::EXIT_RESTART : self::EXIT_SHUTDOWN;
     }
 
 }
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/statusupdate.php b/tests/oauth/statusupdate.php
new file mode 100644 (file)
index 0000000..4aa230e
--- /dev/null
@@ -0,0 +1,115 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a distributed open-source microblogging tool
+ * Copyright (C) 2010, 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:u:';
+$longoptions = array('oauth_token=', 'token_secret=', 'update=');
+
+$helptext = <<<END_OF_VERIFY_HELP
+    statusupdate.php [options]
+    Update your status using OAuth
+
+    -o --oauth_token       access token
+    -s --token_secret      access token secret
+    -u --update            status update
+
+
+END_OF_VERIFY_HELP;
+
+$token        = null;
+$token_secret = null;
+$update       = 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 (have_option('u', 'update')) {
+    $update = get_option_value('u', 'update');
+}
+
+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);
+}
+
+if (empty($update)) {
+    print "You forgot to update your status!\n";
+    exit(1);
+}
+
+$ini = parse_ini_file("oauth.ini");
+
+$test_consumer = new OAuthConsumer($ini['consumer_key'], $ini['consumer_secret']);
+
+$endpoint = $ini['apiroot'] . '/statuses/update.xml';
+
+print "$endpoint\n";
+
+$at = new OAuthToken($token, $token_secret);
+
+$parsed = parse_url($endpoint);
+$params = array();
+parse_str($parsed['query'], $params);
+
+$params['status'] = $update;
+
+$hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
+
+$req_req = OAuthRequest::from_consumer_and_token($test_consumer, $at, 'POST', $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->post($url);
+}
+
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 d4c471674215caad8861dc1850456ac294fe7171..65dd159900e224426d6338916e615457eaa36105 100644 (file)
@@ -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;
 }
 
@@ -895,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;
diff --git a/theme/base/css/uap.css b/theme/base/css/uap.css
new file mode 100644 (file)
index 0000000..73be5f0
--- /dev/null
@@ -0,0 +1,54 @@
+/** Universal Ad Package styles:
+ * Medium Rectangle 300x250
+ * Rectangle        180x150
+ * Leaderboard      728x90
+ * Wide Skyscraper  160x600
+ *
+ * @package   StatusNet
+ * @author    Sarven Capadisli <csarven@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/
+ */
+
+
+.ad {
+border:1px solid #CCC;
+float:left;
+}
+
+#ad_medium-rectangle {
+width:300px;
+height:250px;
+
+margin-left:1.35%;
+margin-bottom:18px;
+}
+
+#ad_rectangle {
+width:180px;
+height:150px;
+
+float:none;
+clear:both;
+margin:0 auto;
+margin-bottom:29px;
+}
+
+#ad_leaderboard {
+width:728px;
+height:90px;
+
+margin:0 auto 18px;
+float:none;
+clear:both;
+}
+
+#ad_wide-skyscraper {
+width:160px;
+height:600px;
+
+float:right;
+margin-top:18px;
+margin-right:8.25%;
+}
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);
 }