From 7cd666ae928b003a8096289009bfd7290f8f6c73 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 18:19:13 -0500 Subject: defaults for nickname and wildcard --- lib/default.php | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib/default.php') diff --git a/lib/default.php b/lib/default.php index 0cb70de2d..e3a043de1 100644 --- a/lib/default.php +++ b/lib/default.php @@ -30,6 +30,8 @@ $default = array('site' => array('name' => 'Just another StatusNet microblog', + 'nickname' => 'statusnet', + 'wildcard' => null, 'server' => $_server, 'theme' => 'default', 'path' => $_path, -- cgit v1.2.3-54-g00ecf From 3c2b05d222a55cd1e148f3f887bf55e924898f1b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 16 Nov 2009 16:58:49 -0800 Subject: Workflow for registering new OAuth apps pretty much done. --- actions/apps.php | 63 +++++++- actions/editapplication.php | 246 ++++++++++++++++++++++++++++ actions/newapplication.php | 133 ++++++++++----- actions/oauthconnectionssettings.php | 13 ++ actions/showapplication.php | 306 +++++++++++++++++++++++++++++++++++ classes/Consumer.php | 16 +- classes/Oauth_application.php | 44 +++++ db/statusnet.sql | 2 +- lib/applicationeditform.php | 135 +++++++++++++--- lib/applicationlist.php | 46 ++++-- lib/default.php | 2 + lib/router.php | 25 ++- 12 files changed, 949 insertions(+), 82 deletions(-) create mode 100644 actions/editapplication.php create mode 100644 actions/showapplication.php (limited to 'lib/default.php') diff --git a/actions/apps.php b/actions/apps.php index d4cea1e3e..e6500599f 100644 --- a/actions/apps.php +++ b/actions/apps.php @@ -31,7 +31,8 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR . '/lib/connectsettingsaction.php'; +require_once INSTALLDIR . '/lib/settingsaction.php'; +require_once INSTALLDIR . '/lib/applicationlist.php'; /** * Show a user's registered OAuth applications @@ -45,8 +46,23 @@ require_once INSTALLDIR . '/lib/connectsettingsaction.php'; * @see SettingsAction */ -class AppsAction extends ConnectSettingsAction +class AppsAction 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 * @@ -79,6 +95,49 @@ class AppsAction extends ConnectSettingsAction { $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->element('a', + array('href' => common_local_url( + 'newapplication', + array('nickname' => $user->nickname) + ) + ), + 'Register a new application ยป'); + + $this->pagination( + $this->page > 1, + $cnt > APPS_PER_PAGE, + $this->page, + 'apps', + array('nickname' => $user->nickname) + ); + } + + 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'); } /** diff --git a/actions/editapplication.php b/actions/editapplication.php new file mode 100644 index 000000000..3af482844 --- /dev/null +++ b/actions/editapplication.php @@ -0,0 +1,246 @@ +. + * + * @category Applications + * @package StatusNet + * @author Zach Copley + * @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 + * @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 $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); + + 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 group. + * + * @param array $args unused + * + * @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; + } + + $cur = common_current_user(); + + if ($this->arg('cancel')) { + common_redirect(common_local_url('showapplication', + array( + 'nickname' => $cur->nickname, + 'id' => $this->app->id) + ), 303); + } elseif ($this->arg('save')) { + $this->trySave(); + } else { + $this->clientError(_('Unexpected form submission.')); + } + } else { + $this->showForm(); + } + } + + 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('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 (empty($callback_url)) { + $this->showForm(_('Callback is required.')); + 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)); + + $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; + + if ($access_type == 'r') { + $this->app->setAccessFlags(true, false); + } else { + $this->app->setAccessFlags(true, true); + } + + $result = $this->app->update($orig); + + if (!$result) { + common_log_db_error($app, 'UPDATE', __FILE__); + $this->serverError(_('Could not update application.')); + } + + common_redirect(common_local_url('apps', + array('nickname' => $cur->nickname)), 303); + } + +} + diff --git a/actions/newapplication.php b/actions/newapplication.php index a78a856b1..9d8635270 100644 --- a/actions/newapplication.php +++ b/actions/newapplication.php @@ -43,7 +43,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { * @link http://status.net/ */ -class NewApplicationAction extends Action +class NewApplicationAction extends OwnerDesignAction { var $msg; @@ -61,7 +61,7 @@ class NewApplicationAction extends Action parent::prepare($args); if (!common_logged_in()) { - $this->clientError(_('You must be logged in to create a group.')); + $this->clientError(_('You must be logged in to register an application.')); return false; } @@ -81,8 +81,19 @@ class NewApplicationAction extends Action function handle($args) { parent::handle($args); + if ($_SERVER['REQUEST_METHOD'] == 'POST') { - $this->trySave(); + + $cur = common_current_user(); + + if ($this->arg('cancel')) { + common_redirect(common_local_url('apps', + array('nickname' => $cur->nickname)), 303); + } elseif ($this->arg('save')) { + $this->trySave(); + } else { + $this->clientError(_('Unexpected form submission.')); + } } else { $this->showForm(); } @@ -112,55 +123,73 @@ class NewApplicationAction extends Action function trySave() { - $name = $this->trimmed('name'); - $description = $this->trimmed('description'); - $source_url = $this->trimmed('source_url'); - $organization = $this->trimmed('organization'); - $homepage = $this->trimmed('application'); - $callback_url = $this->trimmed('callback_url'); - $this->type = $this->trimmed('type'); - $this->access_type = $this->trimmed('access_type'); - - if (!is_null($name) && mb_strlen($name) > 255) { + $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('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; - } else if (User_group::descriptionTooLong($description)) { + } elseif (empty($description)) { + $this->showForm(_('Description is required.')); + return; + } elseif (Oauth_application::descriptionTooLong($description)) { $this->showForm(sprintf( - _('description is too long (max %d chars).'), + _('Description is too long (max %d chars).'), Oauth_application::maxDescription())); return; - } elseif (!is_null($source_url) - && (strlen($source_url) > 0) + } 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 (!is_null($homepage) - && (strlen($homepage) > 0) + } 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 (!is_null($callback_url) - && (strlen($callback_url) > 0) + return; + } elseif (empty($callback_url)) { + $this->showForm(_('Callback is required.')); + 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 @@ -171,31 +200,53 @@ class NewApplicationAction extends Action $app->query('BEGIN'); - $app->name = $name; - $app->owner = $cur->id; - $app->description = $description; - $app->source_url = $souce_url; + $app->name = $name; + $app->owner = $cur->id; + $app->description = $description; + $app->source_url = $source_url; $app->organization = $organization; - $app->homepage = $homepage; + $app->homepage = $homepage; $app->callback_url = $callback_url; - $app->type = $type; - $app->access_type = $access_type; - + $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 - - $app->created = common_sql_now(); + + $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; $result = $app->insert(); if (!$result) { - common_log_db_error($group, 'INSERT', __FILE__); + common_log_db_error($app, 'INSERT', __FILE__); $this->serverError(_('Could not create application.')); + $app->query('ROLLBACK'); } - - $group->query('COMMIT'); - common_redirect($group->homeUrl(), 303); - + $app->query('COMMIT'); + + common_redirect(common_local_url('apps', + array('nickname' => $cur->nickname)), 303); + } } diff --git a/actions/oauthconnectionssettings.php b/actions/oauthconnectionssettings.php index 6ec9f7027..e4b5af158 100644 --- a/actions/oauthconnectionssettings.php +++ b/actions/oauthconnectionssettings.php @@ -132,4 +132,17 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction $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('apps', array('nickname' => $cur->nickname))), + 'here.'); + $this->elementEnd('p'); + } + } diff --git a/actions/showapplication.php b/actions/showapplication.php new file mode 100644 index 000000000..6b8eff4a6 --- /dev/null +++ b/actions/showapplication.php @@ -0,0 +1,306 @@ +. + * + * @category Application + * @package StatusNet + * @author Zach Copley + * @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 + * @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 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(); + + $this->elementStart('div', 'entity_actions'); + + $this->element('a', + array('href' => + common_local_url( + 'editapplication', + array( + 'nickname' => $this->owner->nickname, + 'id' => $this->application->id + ) + ) + ), 'Edit application'); + + $this->elementStart('form', array( + 'id' => 'forma_reset_key', + 'class' => 'form_reset_key', + 'method' => 'POST', + 'action' => common_local_url('showapplication', + array('nickname' => $cur->nickname, + 'id' => $this->application->id)))); + + $this->elementStart('fieldset'); + $this->hidden('token', common_session_token()); + $this->submit('reset', _('Reset Consumer key/secret')); + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + + $this->elementEnd('div'); + + $consumer = $this->application->getConsumer(); + + $this->elementStart('div', 'entity-application'); + + $this->elementStart('ul', 'entity_application_details'); + + $this->elementStart('li', 'entity_application_name'); + $this->element('span', array('class' => 'big'), $this->application->name); + $this->raw(sprintf(_(' by %1$s'), $this->application->organization)); + $this->elementEnd('li'); + + $this->element('li', 'entity_application_description', $this->application->description); + + $this->elementStart('li', 'entity_application_statistics'); + + $defaultAccess = ($this->application->access_type & Oauth_application::$writeAccess) + ? 'read-write' : 'read-only'; + $profile = Profile::staticGet($this->application->owner); + $userCnt = 0; // XXX: count how many users use the app + + $this->raw(sprintf( + _('Created by %1$s - %2$s access by default - %3$d users.'), + $profile->getBestName(), + $defaultAccess, + $userCnt + )); + + $this->elementEnd('li'); + + $this->elementEnd('ul'); + + $this->elementStart('dl', 'entity_consumer_key'); + $this->element('dt', null, _('Consumer key')); + $this->element('dd', 'label', $consumer->consumer_key); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_consumer_secret'); + $this->element('dt', null, _('Consumer secret')); + $this->element('dd', 'label', $consumer->consumer_secret); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_request_token_url'); + $this->element('dt', null, _('Request token URL')); + $this->element('dd', 'label', common_local_url('oauthrequesttoken')); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_access_token_url'); + $this->element('dt', null, _('Access token URL')); + $this->element('dd', 'label', common_local_url('oauthaccesstoken')); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_authorize_url'); + $this->element('dt', null, _('Authorize URL')); + $this->element('dd', 'label', common_local_url('oauthauthorize')); + $this->elementEnd('dl'); + + $this->element('p', 'oauth-signature-note', + '*We support hmac-sha1 signatures. We do not support the plaintext signature method.'); + + $this->elementEnd('div'); + + $this->elementStart('div', 'entity-list-apps'); + $this->element('a', + array( + 'href' => common_local_url( + 'apps', + array('nickname' => $this->owner->nickname) + ) + ), + 'View your applications'); + $this->elementEnd('div'); + } + + 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(); + } + +} + diff --git a/classes/Consumer.php b/classes/Consumer.php index d17f183a8..ad64a8491 100644 --- a/classes/Consumer.php +++ b/classes/Consumer.php @@ -4,7 +4,7 @@ */ 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 */ @@ -22,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; + } + } diff --git a/classes/Oauth_application.php b/classes/Oauth_application.php index e2862bf97..ef1bbf6d9 100644 --- a/classes/Oauth_application.php +++ b/classes/Oauth_application.php @@ -31,4 +31,48 @@ class Oauth_application extends Memcached_DataObject } /* 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; + } + } + } diff --git a/db/statusnet.sql b/db/statusnet.sql index 03e6115e5..a2740b60c 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -219,7 +219,7 @@ create table oauth_application ( organization varchar(255) comment 'name of the organization running the application', homepage varchar(255) comment 'homepage for the organization', callback_url varchar(255) not null comment 'url to redirect to after authentication', - type tinyint default 0 comment 'type of app, 0 = browser, 1 = desktop', + 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' diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php index 3fd45876a..ed187ba0b 100644 --- a/lib/applicationeditform.php +++ b/lib/applicationeditform.php @@ -100,11 +100,16 @@ class ApplicationEditForm extends Form function action() { - if ($this->application) { + $cur = common_current_user(); + + if (!empty($this->application)) { return common_local_url('editapplication', - array('id' => $this->application->id)); + array('id' => $this->application->id, + 'nickname' => $cur->nickname) + ); } else { - return common_local_url('newapplication'); + return common_local_url('newapplication', + array('nickname' => $cur->nickname)); } } @@ -116,7 +121,7 @@ class ApplicationEditForm extends Form function formLegend() { - $this->out->element('legend', null, _('Register a new application')); + $this->out->element('legend', null, _('Edit application')); } /** @@ -130,7 +135,7 @@ class ApplicationEditForm extends Form if ($this->application) { $id = $this->application->id; $name = $this->application->name; - $description = $this->application->description; + $description = $this->application->description; $source_url = $this->application->source_url; $organization = $this->application->organization; $homepage = $this->application->homepage; @@ -151,34 +156,46 @@ class ApplicationEditForm extends Form $this->out->elementStart('ul', 'form_data'); $this->out->elementStart('li'); - + $this->out->hidden('application_id', $id); + $this->out->hidden('token', common_session_token()); + $this->out->input('name', _('Name'), ($this->out->arg('name')) ? $this->out->arg('name') : $name); - + $this->out->elementEnd('li'); - + $this->out->elementStart('li'); - $this->out->input('description', _('Description'), - ($this->out->arg('Description')) ? $this->out->arg('discription') : $description); + + $maxDesc = Oauth_application::maxDesc(); + if ($maxDesc > 0) { + $descInstr = sprintf(_('Describe your application in %d chars'), + $maxDesc); + } else { + $descInstr = _('Describe your application'); + } + $this->out->textarea('description', _('Description'), + ($this->out->arg('description')) ? $this->out->arg('description') : $description, + $descInstr); + $this->out->elementEnd('li'); - + $this->out->elementStart('li'); $this->out->input('source_url', _('Source URL'), ($this->out->arg('source_url')) ? $this->out->arg('source_url') : $source_url, _('URL of the homepage of this application')); - $this->out->elementEnd('li'); + $this->out->elementEnd('li'); $this->out->elementStart('li'); - $this->out->input('Organization', _('Organization'), - ($this->out->arg('organization')) ? $this->out->arg('organization') : $orgranization, + $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 of the homepage of the organization')); + _('URL for the homepage of the organization')); $this->out->elementEnd('li'); $this->out->elementStart('li'); @@ -188,17 +205,86 @@ class ApplicationEditForm extends Form $this->out->elementEnd('li'); $this->out->elementStart('li'); - $this->out->input('type', _('Application type'), - ($this->out->arg('type')) ? $this->out->arg('type') : $type, - _('Type of application, browser or desktop')); + + $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->applicaiton->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'); - $this->out->input('access_type', _('Default access'), - ($this->out->arg('access_type')) ? $this->out->arg('access_type') : $access_type, - _('Default access for this application: read-write, or read-only')); + + $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'); } @@ -210,6 +296,7 @@ class ApplicationEditForm extends Form function formActions() { - $this->out->submit('submit', _('Save')); + $this->out->submit('save', _('Save')); + $this->out->submit('cancel', _('Cancel')); } } diff --git a/lib/applicationlist.php b/lib/applicationlist.php index fed784bb6..3141ea974 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -20,7 +20,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * - * @category Public + * @category Application * @package StatusNet * @author Zach Copley * @copyright 2008-2009 StatusNet, Inc. @@ -39,7 +39,7 @@ define('APPS_PER_PAGE', 20); /** * Widget to show a list of OAuth applications * - * @category Public + * @category Application * @package StatusNet * @author Zach Copley * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 @@ -50,10 +50,10 @@ 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; @@ -87,14 +87,42 @@ class ApplicationList extends Widget function showApplication() { - $this->out->elementStart('li', array('class' => 'application', - 'id' => 'oauthclient-' . $this->application->id)); $user = common_current_user(); - $this->out->raw($this->application->name); - - $this->out->elementEnd('li'); + $this->out->elementStart('li', array('class' => 'application', + 'id' => 'oauthclient-' . $this->application->id)); + + $this->out->elementStart('a', + array('href' => common_local_url( + 'showapplication', + array( + 'nickname' => $user->nickname, + 'id' => $this->application->id + ) + ), + 'class' => 'url') + ); + + $this->out->raw($this->application->name); + $this->out->elementEnd('a'); + + $this->out->raw(' by '); + + $this->out->elementStart('a', + array( + 'href' => $this->application->homepage, + 'class' => 'url' + ) + ); + $this->out->raw($this->application->organization); + $this->out->elementEnd('a'); + + $this->out->elementStart('p', 'note'); + $this->out->raw($this->application->description); + $this->out->elementEnd('p'); + + $this->out->elementEnd('li'); } /* Override this in subclasses. */ diff --git a/lib/default.php b/lib/default.php index e3a043de1..b6ee72279 100644 --- a/lib/default.php +++ b/lib/default.php @@ -211,6 +211,8 @@ $default = 'uploads' => true, 'filecommand' => '/usr/bin/file', ), + 'application' => + array('desclimit' => null), 'group' => array('maxaliases' => 3, 'desclimit' => null), diff --git a/lib/router.php b/lib/router.php index 7b65ae215..a8dbbf6d0 100644 --- a/lib/router.php +++ b/lib/router.php @@ -641,13 +641,30 @@ class Router array('nickname' => '[a-zA-Z0-9]{1,64}')); } - $m->connect('apps/new', array('action' => 'newapplication')); - - $m->connect(':nickname/apps/edit', + $m->connect(':nickname/apps', + array('action' => 'apps'), + array('nickname' => '['.NICKNAME_FMT.']{1,64}')); + $m->connect(':nickname/apps/show/:id', + array('action' => 'showapplication'), + array('nickname' => '['.NICKNAME_FMT.']{1,64}', + 'id' => '[0-9]+') + ); + $m->connect(':nickname/apps/new', + array('action' => 'newapplication'), + array('nickname' => '['.NICKNAME_FMT.']{1,64}')); + $m->connect(':nickname/apps/edit/:id', array('action' => 'editapplication'), - array('nickname' => '['.NICKNAME_FMT.']{1,64}') + array('nickname' => '['.NICKNAME_FMT.']{1,64}', + 'id' => '[0-9]+') ); + $m->connect('oauth/request_token', + array('action' => 'oauthrequesttoken')); + $m->connect('oauth/access_token', + array('action' => 'oauthaccesstoken')); + $m->connect('oauth/authorize', + array('action' => 'oauthauthorize')); + foreach (array('subscriptions', 'subscribers') as $a) { $m->connect(':nickname/'.$a.'/:tag', array('action' => $a), -- cgit v1.2.3-54-g00ecf From 655573c213113861cfd82a42ef999dde1aeea149 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 26 Jan 2010 00:21:05 -0500 Subject: Single-user mode New configuration options to define a single-user mode. This hides most of the "community" pages, like the public timeline and groups. The main user's timeline becomes the main page, and most other URLs are changed. Switching back and forth between 1-user and multi-user mode is probably hazardous. Squashed commit of the following: commit d814aa5c92d14a27a12baba7893f3f8bf63f1d08 Author: Evan Prodromou Date: Tue Jan 26 00:17:27 2010 -0500 don't show inbox and outbox in single-user mode commit 47f19b9523a7015d4c6e460b73ea32c839e00aa1 Author: Evan Prodromou Date: Tue Jan 26 00:15:22 2010 -0500 show correct URL for logo in single-user mode commit 552010cffc33eadbc512ec5a67619dbc2015239a Author: Evan Prodromou Date: Tue Jan 26 00:15:06 2010 -0500 make singleuser its own config section commit 786ab260a3ca172e57b555c75ca10946d8f258a1 Author: Evan Prodromou Date: Tue Jan 26 00:05:19 2010 -0500 make single-user mode work commit 5b21d7309b3a8dd5a4e0f29aea76f7897f1818b1 Author: Evan Prodromou Date: Mon Jan 25 23:45:55 2010 -0500 add single-user mode --- README | 9 +++ lib/action.php | 8 +- lib/default.php | 5 +- lib/personalgroupnav.php | 7 +- lib/router.php | 207 ++++++++++++++++++++++++++++++----------------- 5 files changed, 157 insertions(+), 79 deletions(-) (limited to 'lib/default.php') diff --git a/README b/README index 602288789..f83873ca8 100644 --- 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/lib/action.php b/lib/action.php index e24277558..3ffc452de 100644 --- a/lib/action.php +++ b/lib/action.php @@ -392,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'), diff --git a/lib/default.php b/lib/default.php index b6ee72279..35115542f 100644 --- a/lib/default.php +++ b/lib/default.php @@ -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', @@ -260,4 +260,7 @@ $default = ), 'admin' => array('panels' => array('design', 'site', 'user', 'paths')), + 'singleuser' => + array('enabled' => false, + 'nickname' => null), ); diff --git a/lib/personalgroupnav.php b/lib/personalgroupnav.php index cdde1feca..25db5baa9 100644 --- a/lib/personalgroupnav.php +++ b/lib/personalgroupnav.php @@ -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)), diff --git a/lib/router.php b/lib/router.php index 42bff2778..c0ddc8db3 100644 --- a/lib/router.php +++ b/lib/router.php @@ -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', @@ -145,6 +139,18 @@ class Router $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,6 +623,17 @@ 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')); @@ -631,78 +643,125 @@ class Router 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}')); - } - - $m->connect('settings/oauthapps/show/:id', - array('action' => 'showapplication'), - array('id' => '[0-9]+') - ); - $m->connect('settings/oauthapps/new', - array('action' => 'newapplication') - ); - $m->connect('settings/oauthapps/edit/:id', - array('action' => 'editapplication'), - array('id' => '[0-9]+') - ); - - $m->connect('api/oauth/request_token', - array('action' => 'apioauthrequesttoken')); - - $m->connect('api/oauth/access_token', - array('action' => 'apioauthaccesstoken')); - - $m->connect('api/oauth/authorize', - array('action' => 'apioauthauthorize')); - foreach (array('subscriptions', 'subscribers') as $a) { - $m->connect(':nickname/'.$a.'/:tag', - array('action' => $a), - 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]+')); + + $m->connect(':nickname/tag/:tag', + array('action' => 'showstream'), + 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', + 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)); } -- cgit v1.2.3-54-g00ecf From 58be61b6417119de1b03ef50e166369c4005e4d1 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 26 Jan 2010 11:49:49 -0800 Subject: Control channel for queue daemons to request graceful shutdown, restart, or update to listen to a newly added or reconfigured site. queuectl.php --update -s queuectl.php --stop queuectl.php --restart Default control channel is /topic/statusnet-control. For external utilities to send a site update ping direct to the queue server, connect via Stomp and send a message formatted thus: update: (Nickname here, *not* server hostname! The rest of the queues will be updated to use nicknames later.) Note that all currently-connected queue daemons will get these notifications, including both queuedaemon.php and xmppdaemon.php. (XMPP will ignore site update requests for sites that it's not handling.) Limitations: * only implemented for stomp queue manager so far * --update may not yet handle a changed server name properly * --restart won't reload PHP code files that were already loaded at startup. Still need to stop and restart the daemons from 'outside' when updating code base. --- classes/Status_network.php | 11 +++- lib/default.php | 1 + lib/iomaster.php | 53 +++++++++++---- lib/queuemanager.php | 17 +++++ lib/spawningdaemon.php | 58 +++++++++++++---- lib/stompqueuemanager.php | 159 +++++++++++++++++++++++++++++++++++++++++---- scripts/queuectl.php | 85 ++++++++++++++++++++++++ scripts/queuedaemon.php | 2 +- scripts/xmppdaemon.php | 2 +- 9 files changed, 347 insertions(+), 41 deletions(-) create mode 100755 scripts/queuectl.php (limited to 'lib/default.php') diff --git a/classes/Status_network.php b/classes/Status_network.php index f1314d615..4bda24b6a 100644 --- a/classes/Status_network.php +++ b/classes/Status_network.php @@ -42,7 +42,16 @@ class Status_network extends DB_DataObject 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 diff --git a/lib/default.php b/lib/default.php index 35115542f..d2dd8ab33 100644 --- a/lib/default.php +++ b/lib/default.php @@ -81,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) diff --git a/lib/iomaster.php b/lib/iomaster.php index 3bf82bc6b..bcab3542b 100644 --- a/lib/iomaster.php +++ b/lib/iomaster.php @@ -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; + } + } diff --git a/lib/queuemanager.php b/lib/queuemanager.php index 1bc8de1f7..afe710e88 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -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 diff --git a/lib/spawningdaemon.php b/lib/spawningdaemon.php index 8baefe88e..b1961d688 100644 --- a/lib/spawningdaemon.php +++ b/lib/spawningdaemon.php @@ -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); } /** diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index 8f0091a13..89f3d74cc 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -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; } /** @@ -180,7 +213,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 +239,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 +266,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 +277,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 +310,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 +348,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 +379,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 +410,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 +493,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/scripts/queuectl.php b/scripts/queuectl.php new file mode 100755 index 000000000..1c9ea3353 --- /dev/null +++ b/scripts/queuectl.php @@ -0,0 +1,85 @@ +#!/usr/bin/env php +. + */ + +/** + * Sends control signals to running queue daemons. + * + * @author Brion Vibber + * @package QueueHandler + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'ur'; +$longoptions = array('update', 'restart', 'stop'); + +$helptext = <<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(); +} + diff --git a/scripts/queuedaemon.php b/scripts/queuedaemon.php index bedd14b1a..c2e2351c3 100755 --- a/scripts/queuedaemon.php +++ b/scripts/queuedaemon.php @@ -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; } } diff --git a/scripts/xmppdaemon.php b/scripts/xmppdaemon.php index fd7cf055b..46dd9b90c 100755 --- a/scripts/xmppdaemon.php +++ b/scripts/xmppdaemon.php @@ -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; } } -- cgit v1.2.3-54-g00ecf From aad42427ccd4d3824efcbce43d8e9dba6d5b475d Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 Jan 2010 15:56:19 -0800 Subject: New access admin panel for site registration settings --- actions/accessadminpanel.php | 192 +++++++++++++++++++++++++++++++++++++++++++ actions/siteadminpanel.php | 37 --------- lib/adminpanelaction.php | 13 ++- lib/default.php | 2 +- lib/router.php | 1 + 5 files changed, 203 insertions(+), 42 deletions(-) create mode 100644 actions/accessadminpanel.php (limited to 'lib/default.php') diff --git a/actions/accessadminpanel.php b/actions/accessadminpanel.php new file mode 100644 index 000000000..4768e2faf --- /dev/null +++ b/actions/accessadminpanel.php @@ -0,0 +1,192 @@ +. + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @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 + * @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')); + } + +} diff --git a/actions/siteadminpanel.php b/actions/siteadminpanel.php index 1cb57ec09..8c8f8b374 100644 --- a/actions/siteadminpanel.php +++ b/actions/siteadminpanel.php @@ -95,8 +95,6 @@ class SiteadminpanelAction extends AdminPanelAction 'site', 'textlimit', 'dupelimit'), 'snapshot' => array('run', 'reporturl', 'frequency')); - static $booleans = array('site' => array('private', 'inviteonly', 'closed')); - $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,29 +285,6 @@ class SiteAdminPanelForm extends AdminForm $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'); diff --git a/lib/adminpanelaction.php b/lib/adminpanelaction.php index a6981ac61..f62bfa458 100644 --- a/lib/adminpanelaction.php +++ b/lib/adminpanelaction.php @@ -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)); diff --git a/lib/default.php b/lib/default.php index d2dd8ab33..37fb65b53 100644 --- a/lib/default.php +++ b/lib/default.php @@ -260,7 +260,7 @@ $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), diff --git a/lib/router.php b/lib/router.php index c0ddc8db3..03765b39d 100644 --- a/lib/router.php +++ b/lib/router.php @@ -637,6 +637,7 @@ class Router $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', -- cgit v1.2.3-54-g00ecf From c52951cef5508452733f84dec7815daf4aca1016 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 11:37:22 -0500 Subject: Optionally set a separate Javascript server and path We have about 10-12 JavaScript pages per Web page. They usually are based on the same server as the Web pages, but since they're static files, it makes sense to offload them to a lite server that handles static files well. This commit lets you set a separate Javascript server and path for the default Javascript code in StatusNet. Squashed commit of the following: commit 139d1622fdafe5ad00c820224416d9021efc3234 Author: Evan Prodromou Date: Wed Jan 27 11:30:24 2010 -0500 modules that call htmloutputter::script() don't prescribe js/ path commit c6ca3174af73efed55eaed5ff1e2a3bdc77d2d87 Author: Evan Prodromou Date: Wed Jan 27 11:28:07 2010 -0500 configurable server and path for javascript files --- actions/avatarsettings.php | 4 ++-- actions/designadminpanel.php | 4 ++-- actions/grouplogo.php | 4 ++-- lib/action.php | 16 ++++++++-------- lib/default.php | 3 +++ lib/designsettings.php | 4 ++-- lib/htmloutputter.php | 28 +++++++++++++++++++++++++++- plugins/Facebook/facebookaction.php | 2 +- 8 files changed, 47 insertions(+), 18 deletions(-) (limited to 'lib/default.php') diff --git a/actions/avatarsettings.php b/actions/avatarsettings.php index cf4525552..6a7398746 100644 --- a/actions/avatarsettings.php +++ b/actions/avatarsettings.php @@ -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'); diff --git a/actions/designadminpanel.php b/actions/designadminpanel.php index 72ad6ade2..30e8bde1a 100644 --- a/actions/designadminpanel.php +++ b/actions/designadminpanel.php @@ -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'); } diff --git a/actions/grouplogo.php b/actions/grouplogo.php index f197aef33..3c9b56296 100644 --- a/actions/grouplogo.php +++ b/actions/grouplogo.php @@ -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'); diff --git a/lib/action.php b/lib/action.php index 3ffc452de..cc4f4aad0 100644 --- a/lib/action.php +++ b/lib/action.php @@ -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; }'); diff --git a/lib/default.php b/lib/default.php index 37fb65b53..10ea34864 100644 --- a/lib/default.php +++ b/lib/default.php @@ -120,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 diff --git a/lib/designsettings.php b/lib/designsettings.php index 8e44c03a9..4955e9219 100644 --- a/lib/designsettings.php +++ b/lib/designsettings.php @@ -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'); } diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 31660ce95..317f5ea61 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -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)); } } diff --git a/plugins/Facebook/facebookaction.php b/plugins/Facebook/facebookaction.php index 815fee094..389e1ea81 100644 --- a/plugins/Facebook/facebookaction.php +++ b/plugins/Facebook/facebookaction.php @@ -89,7 +89,7 @@ class FacebookAction extends Action function showScripts() { - $this->script('js/facebookapp.js'); + $this->script('facebookapp.js'); } /** -- cgit v1.2.3-54-g00ecf