Merge branch '0.9.x' into 1.0.x

Conflicts:
	EVENTS.txt
	lib/imqueuehandler.php
	lib/jabber.php
	lib/util.php
	plugins/Xmpp/Sharing_XMPP.php
This commit is contained in:
Craig Andrews 2010-02-02 17:00:10 -05:00
commit 057ec1fcea
118 changed files with 19107 additions and 11205 deletions

View File

@ -717,3 +717,17 @@ SendImConfirmationCode: Send a confirmation code to confirm a user owns an IM sc
- $screenname: screenname being confirmed
- $code: confirmation code for confirmation URL
- $user: user requesting the confirmation
StartUserRegister: When a new user is being registered
- &$profile: new profile data (no ID)
- &$user: new user account (no ID or URI)
EndUserRegister: When a new user has been registered
- &$profile: new profile data
- &$user: new user account
StartRobotsTxt: Before outputting the robots.txt page
- &$action: RobotstxtAction being shown
EndRobotsTxt: After the default robots.txt page (good place for customization)
- &$action: RobotstxtAction being shown

127
README
View File

@ -2,8 +2,8 @@
README
------
StatusNet 0.9.0 ("Stand") Beta 3
20 Jan 2010
StatusNet 0.9.0 ("Stand") Beta 5
1 Feb 2010
This is the README file for StatusNet (formerly Laconica), the Open
Source microblogging platform. It includes installation instructions,
@ -78,6 +78,11 @@ New this version
================
This is a major feature release since version 0.8.2, released Nov 1 2009.
It is also a security release since 0.9.0beta4 January 27 2010. Beta
users are strongly encouraged to upgrade to deal with a security alert.
http://status.net/wiki/Security_alert_0000002
Notable changes this version:
- Records of deleted notices are stored without the notice content.
@ -198,6 +203,77 @@ Notable changes this version:
- Major refactoring of queue handlers to manage very
large hosting site (like status.net)
- SubscriptionThrottle plugin to prevent subscription spamming
- Don't enqueue into plugin or SMS queues when disabled (breaks unqueuehandler if SMS queue isn't attached)
- Improve name validation checks on local File references
- fix local file include vulnerability in doc.php
- Reusing fixed selector name for 'processing' in util.js
- Removed hAtom pattern from registration page.
- restructuring of User::registerNew() lost password munging
- Add a script to clear the cache for a given key
- buggy fetch for site owner
- Added missing concat of </li> in Realtime response
- Updated XHR binded events to work better in jQuery 1.4.1. Using .live() for event delegation instead of jQuery.data() and checking to see if an element was previously binded.
- Updated jQuery Form Plugin from v2.17 to v2.36
- Updated jQuery JavaScript Library from v1.3.2 to v1.4.1
- move schema.type.php to typeschema.php like other files
- Add Really Simple Discovery (RSD) support
- Add a robots.txt URL to the site root
- error clearing tags for profiles from memcached
- on exceptions, stomp logs the error and reenqueues
- add lat, lon, location and remove closing tag from geocode.php
- Use passed-in lat long in geocode.php
- better handling of null responses from geonames.org
- Globalized form notice data geo values
- Using jQuery chaining in FormNoticeXHR
- Using form object instead of form_id and find(). Slightly faster and easier to read.
- removed describeTable from base class, and fixed it up in pgsql
- getTableDef() mostly working in postgres
- move the schema DDL sql off into seperate files for each db we support
- plugin to limit number of registered users
- add hooks for user registration
- live fast, die young in bash scripts
- for single-user mode, retrieve either site owner or defined nickname
- method to get the site owner
- define a constant for the 'owner' role of a site
- add simple cache getter/setter static functions to Memcached_DataObject
- Adds notice author's name to @title in Realtime response
- Hides .author from XHR response in showstream
- Hides .author from XHR response in showstream
- Fix more fatal errors in queue edge cases
- Don't attempt to resend XMPP messages that can't be broadcast due to the profile being deleted.
- Wrap each bit of distrib queue handler's saving operation in a try/catch; log exceptions but let everything else continue.
- Log exceptions from queuedaemon.php if they're not already caught
- Move sessions settings to its own panel
- Fixes for status_network db object .ini and tag setter script
- Add a script to set tags for sites
- Adjust API authentication to also check for OAuth protocol params in the HTTP Authorization header, as defined in OAuth HTTP Authorization Scheme.
- Last-chance distribution if enqueueing fails
- Manual failover for stomp queues.
- lost config in index.php made all traffic go to master
- "Revert "move RW setup above user get in index.php so remember_me works""
- Revert "move RW setup above user get in index.php so remember_me works"
- move RW setup above user get in index.php so remember_me works
- hide most DB_DataObject errors
- always set up database_rw, regardless, so cached sessions work
- update mysqltimestamps on insert and update
- additional debugging data for Sessions
- 'Sign in with Twitter' button img
- Update to biz theme
- Remove redundant session token field from form (was already being added by base class).
- 'Sign in with Twitter' button img
- Can now set $config['queue']['stomp_persistent'] = false; to explicitly disable persistence when we queue items
- Showing processing indicator for form_repeat on submit instead of form
- Removed avatar from repeat of username (matches noticelist)
- Removed unused variable assignment for avatar URL and added missing fn
- Don't preemptively close existing DB connections for web views (needed to keep # of conns from going insane on multi-site queue daemons, so just doing for CLI) May, or may not, help with mystery session problems
- dropping the setcookie() call from common_ensure_session() since we're pretty sure it's unnecessary
- append '/' on cookie path for now (may still need some refactoring)
- set session cookie correctly
- Fix for Mapstraction plugin's zoomed map links
- debug log line for control channel sub
- Move faceboookapp.js to the Facebook plugin
- fix for fix for bad realtime JS load
- default 24-hour expiry on Memcached objects where not specified.
Prerequisites
=============
@ -597,26 +673,19 @@ server is probably a good idea for high-volume sites.
needs as a parameter the install path; if you run it from the
StatusNet dir, "." should suffice.
This will run eight (for now) queue handlers:
This will run the queue handlers:
* queuedaemon.php - polls for queued items for inbox processing and
pushing out to OMB, SMS, XMPP, etc.
* xmppdaemon.php - listens for new XMPP messages from users and stores
them as notices in the database.
* jabberqueuehandler.php - sends queued notices in the database to
registered users who should receive them.
* publicqueuehandler.php - sends queued notices in the database to
public feed listeners.
* ombqueuehandler.php - sends queued notices to OpenMicroBlogging
recipients on foreign servers.
* smsqueuehandler.php - sends queued notices to SMS-over-email addresses
of registered users.
* xmppconfirmhandler.php - sends confirmation messages to registered
users.
them as notices in the database; also pulls queued XMPP output from
queuedaemon.php to push out to clients.
Note that these queue daemons are pretty raw, and need your care. In
particular, they leak memory, and you may want to restart them on a
regular (daily or so) basis with a cron job. Also, if they lose
the connection to the XMPP server for too long, they'll simply die. It
may be a good idea to use a daemon-monitoring service, like 'monit',
These two daemons will automatically restart in most cases of failure
including memory leaks (if a memory_limit is set), but may still die
or behave oddly if they lose connections to the XMPP or queue servers.
It may be a good idea to use a daemon-monitoring service, like 'monit',
to check their status and keep them running.
All the daemons write their process IDs (pids) to /var/run/ by
@ -626,7 +695,7 @@ daemons.
Since version 0.8.0, it's now possible to use a STOMP server instead of
our kind of hacky home-grown DB-based queue solution. See the "queues"
config section below for how to configure to use STOMP. As of this
writing, the software has been tested with ActiveMQ (
writing, the software has been tested with ActiveMQ.
Sitemaps
--------
@ -712,10 +781,12 @@ subdirectory to add a new language to your system. You'll need to
compile the ".po" files into ".mo" files, however.
Contributions of translation information to StatusNet are very easy:
you can use the Web interface at http://status.net/pootle/ to add one
you can use the Web interface at TranslateWiki.net to add one
or a few or lots of new translations -- or even new languages. You can
also download more up-to-date .po files there, if you so desire.
For info on helping with translations, see http://status.net/wiki/Translations
Backups
-------
@ -1501,6 +1572,20 @@ 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.
robotstxt
---------
We put out a default robots.txt file to guide the processing of
Web crawlers. See http://www.robotstxt.org/ for more information
on the format of this file.
crawldelay: if non-empty, this value is provided as the Crawl-Delay:
for the robots.txt file. see http://ur1.ca/l5a0
for more information. Default is zero, no explicit delay.
disallow: Array of (virtual) directories to disallow. Default is 'main',
'search', 'message', 'settings', 'admin'. Ignored when site
is private, in which case the entire site ('/') is disallowed.
Plugins
=======

View File

@ -67,8 +67,6 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
{
parent::prepare($args);
common_debug("apioauthauthorize");
$this->nickname = $this->trimmed('nickname');
$this->password = $this->arg('password');
$this->oauth_token = $this->arg('oauth_token');
@ -99,24 +97,17 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
} else {
// XXX: make better error messages
if (empty($this->oauth_token)) {
common_debug("No request token found.");
$this->clientError(_('Bad request.'));
$this->clientError(_('No oauth_token parameter provided.'));
return;
}
if (empty($this->app)) {
common_debug('No app for that token.');
$this->clientError(_('Bad request.'));
$this->clientError(_('Invalid token.'));
return;
}
$name = $this->app->name;
common_debug("Requesting auth for app: " . $name);
$this->showForm();
}
@ -124,8 +115,6 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
function handlePost()
{
common_debug("handlePost()");
// check session token for CSRF protection.
$token = $this->trimmed('token');
@ -202,21 +191,15 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
// 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!");
@ -236,9 +219,12 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
} else if ($this->arg('deny')) {
$datastore = new ApiStatusNetOAuthDataStore();
$datastore->revoke_token($this->oauth_token, 0);
$this->elementStart('p');
$this->raw(sprintf(_("The request token %s has been denied."),
$this->raw(sprintf(_("The request token %s has been denied and revoked."),
$this->oauth_token));
$this->elementEnd('p');
@ -303,13 +289,17 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
$access = ($this->app->access_type & Oauth_application::$writeAccess) ?
'access and update' : 'access';
$msg = _("The application <strong>%1$s</strong> by <strong>%2$s</strong> would like " .
"the ability to <strong>%3$s</strong> your account data.");
$msg = _('The application <strong>%1$s</strong> by ' .
'<strong>%2$s</strong> would like the ability ' .
'to <strong>%3$s</strong> your %4$s account data. ' .
'You should only give access to your %4$s account ' .
'to third parties you trust.');
$this->raw(sprintf($msg,
$this->app->name,
$this->app->organization,
$access));
$access,
common_config('site', 'name')));
$this->elementEnd('p');
$this->elementEnd('li');
$this->elementEnd('ul');
@ -371,6 +361,31 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
function showLocalNav()
{
// NOP
}
/**
* Show site notice.
*
* @return nothing
*/
function showSiteNotice()
{
// NOP
}
/**
* Show notice form.
*
* Show the form for posting a new notice
*
* @return nothing
*/
function showNoticeForm()
{
// NOP
}
}

View File

@ -0,0 +1,176 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Action class to delete 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 Action
* @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') && !defined('LACONICA')) {
exit(1);
}
/**
* Delete an OAuth appliction
*
* @category Action
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*/
class DeleteapplicationAction extends Action
{
var $app = null;
/**
* Take arguments for running
*
* @param array $args $_REQUEST args
*
* @return boolean success flag
*/
function prepare($args)
{
if (!parent::prepare($args)) {
return false;
}
if (!common_logged_in()) {
$this->clientError(_('You must be logged in to delete an application.'));
return false;
}
$id = (int)$this->arg('id');
$this->app = Oauth_application::staticGet('id', $id);
if (empty($this->app)) {
$this->clientError(_('Application not found.'));
return false;
}
$cur = common_current_user();
if ($cur->id != $this->app->owner) {
$this->clientError(_('You are not the owner of this application.'), 401);
return false;
}
return true;
}
/**
* Handle request
*
* Shows a page with list of favorite notices
*
* @param array $args $_REQUEST args; handled in prepare()
*
* @return void
*/
function 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('no')) {
common_redirect(common_local_url('showapplication',
array('id' => $this->app->id)), 303);
} elseif ($this->arg('yes')) {
$this->handlePost();
common_redirect(common_local_url('oauthappssettings'), 303);
} else {
$this->showPage();
}
}
}
function showContent() {
$this->areYouSureForm();
}
function title() {
return _('Delete application');
}
function showNoticeForm() {
// nop
}
/**
* Confirm with user.
*
* Shows a confirmation form.
*
* @return void
*/
function areYouSureForm()
{
$id = $this->app->id;
$this->elementStart('form', array('id' => 'deleteapplication-' . $id,
'method' => 'post',
'class' => 'form_settings form_entity_block',
'action' => common_local_url('deleteapplication',
array('id' => $this->app->id))));
$this->elementStart('fieldset');
$this->hidden('token', common_session_token());
$this->element('legend', _('Delete application'));
$this->element('p', null,
_('Are you sure you want to delete this application? '.
'This will clear all data about the application from the '.
'database, including all existing user connections.'));
$this->submit('form_action-no',
_('No'),
'submit form_action-primary',
'no',
_("Do not delete this application"));
$this->submit('form_action-yes',
_('Yes'),
'submit form_action-secondary',
'yes', _('Delete this application'));
$this->elementEnd('fieldset');
$this->elementEnd('form');
}
/**
* Actually delete the app
*
* @return void
*/
function handlePost()
{
$this->app->delete();
}
}

View File

@ -54,6 +54,9 @@ class DocAction extends Action
parent::prepare($args);
$this->title = $this->trimmed('title');
if (!preg_match('/^[a-zA-Z0-9_-]*$/', $this->title)) {
$this->title = 'help';
}
$this->output = null;
$this->loadDoc();

View File

@ -179,6 +179,9 @@ class EditApplicationAction extends OwnerDesignAction
} elseif (mb_strlen($name) > 255) {
$this->showForm(_('Name is too long (max 255 chars).'));
return;
} else if ($this->nameExists($name)) {
$this->showForm(_('Name already in use. Try another one.'));
return;
} elseif (empty($description)) {
$this->showForm(_('Description is required.'));
return;
@ -260,5 +263,26 @@ class EditApplicationAction extends OwnerDesignAction
common_redirect(common_local_url('oauthappssettings'), 303);
}
/**
* Does the app name already exist?
*
* Checks the DB to see someone has already registered and app
* with the same name.
*
* @param string $name app name to check
*
* @return boolean true if the name already exists
*/
function nameExists($name)
{
$newapp = Oauth_application::staticGet('name', $name);
if (!$newapp) {
return false;
} else {
return $newapp->id != $this->app->id;
}
}
}

View File

@ -42,6 +42,10 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
*/
class GeocodeAction extends Action
{
var $lat = null;
var $lon = null;
var $location = null;
function prepare($args)
{
parent::prepare($args);
@ -52,12 +56,7 @@ class GeocodeAction extends Action
}
$this->lat = $this->trimmed('lat');
$this->lon = $this->trimmed('lon');
$location = Location::fromLatLon($this->lat, $this->lon);
if ($location) {
$this->location = Location::fromId($location->location_id, $location->location_ns);
$this->lat = $this->location->lat;
$this->lon = $this->location->lon;
}
$this->location = Location::fromLatLon($this->lat, $this->lon);
return true;
}
@ -95,4 +94,3 @@ class GeocodeAction extends Action
return true;
}
}
?>

View File

@ -71,7 +71,7 @@ class GetfileAction extends Action
$filename = $this->trimmed('filename');
$path = null;
if ($filename) {
if ($filename && File::validFilename($filename)) {
$path = File::path($filename);
}

View File

@ -158,6 +158,9 @@ class NewApplicationAction extends OwnerDesignAction
if (empty($name)) {
$this->showForm(_('Name is required.'));
return;
} else if ($this->nameExists($name)) {
$this->showForm(_('Name already in use. Try another one.'));
return;
} elseif (mb_strlen($name) > 255) {
$this->showForm(_('Name is too long (max 255 chars).'));
return;
@ -273,5 +276,22 @@ class NewApplicationAction extends OwnerDesignAction
}
/**
* Does the app name already exist?
*
* Checks the DB to see someone has already registered and app
* with the same name.
*
* @param string $name app name to check
*
* @return boolean true if the name already exists
*/
function nameExists($name)
{
$app = Oauth_application::staticGet('name', $name);
return ($app !== false);
}
}

View File

@ -33,6 +33,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
require_once INSTALLDIR . '/lib/connectsettingsaction.php';
require_once INSTALLDIR . '/lib/applicationlist.php';
require_once INSTALLDIR . '/lib/apioauthstore.php';
/**
* Show connected OAuth applications
@ -71,11 +72,6 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction
return _('Connected applications');
}
function isReadOnly($args)
{
return true;
}
/**
* Instructions for use
*
@ -153,6 +149,13 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction
}
}
/**
* Revoke access to an authorized OAuth application
*
* @param int $appId the ID of the application
*
*/
function revokeAccess($appId)
{
$cur = common_current_user();
@ -164,6 +167,8 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction
return false;
}
// XXX: Transaction here?
$appUser = Oauth_application_user::getByKeys($cur, $app);
if (empty($appUser)) {
@ -171,12 +176,13 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction
return false;
}
$orig = clone($appUser);
$appUser->access_type = 0; // No access
$result = $appUser->update();
$datastore = new ApiStatusNetOAuthDataStore();
$datastore->revoke_token($appUser->token, 1);
$result = $appUser->delete();
if (!$result) {
common_log_db_error($orig, 'UPDATE', __FILE__);
common_log_db_error($orig, 'DELETE', __FILE__);
$this->clientError(_('Unable to revoke access for app: ' . $app->id));
return false;
}

View File

@ -131,12 +131,20 @@ class PublicAction extends Action
return _('Public timeline');
}
}
function extraHead()
{
parent::extraHead();
$this->element('meta', array('http-equiv' => 'X-XRDS-Location',
'content' => common_local_url('publicxrds')));
$rsd = common_local_url('rsd');
// RSD, http://tales.phrasewise.com/rfc/rsd
$this->element('link', array('rel' => 'EditURI',
'type' => 'application/rsd+xml',
'href' => $rsd));
}
/**

100
actions/robotstxt.php Normal file
View File

@ -0,0 +1,100 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* robots.txt generator
*
* PHP version 5
*
* @category Action
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*
* 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')) {
exit(1);
}
/**
* Prints out a static robots.txt
*
* @category Action
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*/
class RobotstxtAction extends Action
{
/**
* Handles requests
*
* Since this is a relatively static document, we
* don't do a prepare()
*
* @param array $args GET, POST, and URL params; unused.
*
* @return void
*/
function handle($args)
{
if (Event::handle('StartRobotsTxt', array($this))) {
header('Content-Type: text/plain');
print "User-Agent: *\n";
if (common_config('site', 'private')) {
print "Disallow: /\n";
} else {
$disallow = common_config('robotstxt', 'disallow');
foreach ($disallow as $dir) {
print "Disallow: /$dir/\n";
}
$crawldelay = common_config('robotstxt', 'crawldelay');
if (!empty($crawldelay)) {
print "Crawl-delay: " . $crawldelay . "\n";
}
}
Event::handle('EndRobotsTxt', array($this));
}
}
/**
* Return true; this page doesn't touch the DB.
*
* @param array $args other arguments
*
* @return boolean is read only action?
*/
function isReadOnly($args)
{
return true;
}
}

226
actions/rsd.php Normal file
View File

@ -0,0 +1,226 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2008-2010, StatusNet, Inc.
*
* Really Simple Discovery (RSD) for API access
*
* PHP version 5
*
* 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 Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*
*/
if (!defined('STATUSNET')) {
exit(1);
}
/**
* RSD action class
*
* Really Simple Discovery (RSD) is a simple (to a fault, maybe)
* discovery tool for blog APIs.
*
* http://tales.phrasewise.com/rfc/rsd
*
* Anil Dash suggested that RSD be used for services that implement
* the Twitter API:
*
* http://dashes.com/anil/2009/12/the-twitter-api-is-finished.html
*
* It's in use now for WordPress.com blogs:
*
* http://matt.wordpress.com/xmlrpc.php?rsd
*
* I (evan@status.net) have tried to stay faithful to the premise of
* RSD, while adding information useful to StatusNet client developers.
* In particular:
*
* - There is a link from each user's profile page to their personal
* RSD feed. A personal rsd.xml includes a 'blogID' element that is
* their username.
* - There is a link from the public root to '/rsd.xml', a public RSD
* feed. It's identical to the personal rsd except it doesn't include
* a blogId.
* - I've added a setting to the API to indicate that OAuth support is
* available.
*
* @category API
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*/
class RsdAction extends Action
{
/**
* Optional attribute for the personal rsd.xml file.
*/
var $user = null;
/**
* Prepare the action for use.
*
* Check for a nickname; redirect if non-canonical; if
* not provided, assume public rsd.xml.
*
* @param array $args GET, POST, and URI arguments.
*
* @return boolean success flag
*/
function prepare($args)
{
parent::prepare($args);
// optional argument
$nickname_arg = $this->arg('nickname');
if (empty($nickname_arg)) {
$this->user = null;
} else {
$nickname = common_canonical_nickname($nickname_arg);
// Permanent redirect on non-canonical nickname
if ($nickname_arg != $nickname) {
common_redirect(common_local_url('rsd',
array('nickname' => $nickname)),
301);
return false;
}
$this->user = User::staticGet('nickname', $nickname);
if (empty($this->user)) {
$this->clientError(_('No such user.'), 404);
return false;
}
}
return true;
}
/**
* Action handler.
*
* Outputs the XML format for an RSD file. May include
* personal information if this is a personal file
* (based on whether $user attribute is set).
*
* @param array $args array of arguments
*
* @return nothing
*/
function handle($args)
{
header('Content-Type: application/rsd+xml');
$this->startXML();
$rsdNS = 'http://archipelago.phrasewise.com/rsd';
$this->elementStart('rsd', array('version' => '1.0',
'xmlns' => $rsdNS));
$this->elementStart('service');
$this->element('engineName', null, _('StatusNet'));
$this->element('engineLink', null, 'http://status.net/');
$this->elementStart('apis');
if (Event::handle('StartRsdListApis', array($this, $this->user))) {
$blogID = (empty($this->user)) ? '' : $this->user->nickname;
$apiAttrs = array('name' => 'Twitter',
'preferred' => 'true',
'apiLink' => $this->_apiRoot(),
'blogID' => $blogID);
$this->elementStart('api', $apiAttrs);
$this->elementStart('settings');
$this->element('docs', null,
'http://status.net/wiki/TwitterCompatibleAPI');
$this->element('setting', array('name' => 'OAuth'),
'true');
$this->elementEnd('settings');
$this->elementEnd('api');
Event::handle('EndRsdListApis', array($this, $this->user));
}
$this->elementEnd('apis');
$this->elementEnd('service');
$this->elementEnd('rsd');
$this->endXML();
return true;
}
/**
* Returns last-modified date for use in caching
*
* Per-user rsd.xml is dated to last change of user
* (in case of nickname change); public has no date.
*
* @return string date of last change of this page
*/
function lastModified()
{
if (!empty($this->user)) {
return $this->user->modified;
} else {
return null;
}
}
/**
* Flag to indicate if this action is read-only
*
* It is; it doesn't change the DB.
*
* @param array $args ignored
*
* @return boolean true
*/
function isReadOnly($args)
{
return true;
}
/**
* Return current site's API root
*
* Varies based on URL parameters, like if fancy URLs are
* turned on.
*
* @return string API root URI for this site
*/
private function _apiRoot()
{
if (common_config('site', 'fancy')) {
return common_path('api/', true);
} else {
return common_path('index.php/api/', true);
}
}
}

View File

@ -0,0 +1,201 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Sessions 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);
}
/**
* Admin site sessions
*
* @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 SessionsadminpanelAction extends AdminPanelAction
{
/**
* Returns the page title
*
* @return string page title
*/
function title()
{
return _('Sessions');
}
/**
* Instructions for using this form.
*
* @return string instructions
*/
function getInstructions()
{
return _('Session settings for this StatusNet site.');
}
/**
* Show the site admin panel form
*
* @return void
*/
function showForm()
{
$form = new SessionsAdminPanelForm($this);
$form->show();
return;
}
/**
* Save settings from the form
*
* @return void
*/
function saveSettings()
{
static $booleans = array('sessions' => array('handle', 'debug'));
$values = array();
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);
// assert(all values are valid);
$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;
}
function validate(&$values)
{
// stub
}
}
class SessionsAdminPanelForm extends AdminForm
{
/**
* ID of the form
*
* @return int ID of the form
*/
function id()
{
return 'sessionsadminpanel';
}
/**
* 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('sessionsadminpanel');
}
/**
* Data elements of the form
*
* @return void
*/
function formData()
{
$this->out->elementStart('fieldset', array('id' => 'settings_user_sessions'));
$this->out->element('legend', null, _('Sessions'));
$this->out->elementStart('ul', 'form_data');
$this->li();
$this->out->checkbox('handle', _('Handle sessions'),
(bool) $this->value('handle', 'sessions'),
_('Whether to handle sessions ourselves.'));
$this->unli();
$this->li();
$this->out->checkbox('debug', _('Session debugging'),
(bool) $this->value('debug', 'sessions'),
_('Turn on debugging output for sessions.'));
$this->unli();
$this->out->elementEnd('ul');
$this->out->elementEnd('fieldset');
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
$this->out->submit('submit', _('Save'), 'submit', null, _('Save site settings'));
}
}

View File

@ -201,7 +201,7 @@ class ShowApplicationAction extends OwnerDesignAction
$userCnt = $appUsers->count();
$this->raw(sprintf(
_('created by %1$s - %2$s access by default - %3$d users'),
_('Created by %1$s - %2$s access by default - %3$d users'),
$profile->getBestName(),
$defaultAccess,
$userCnt
@ -222,18 +222,33 @@ class ShowApplicationAction extends OwnerDesignAction
$this->elementStart('li', 'entity_reset_keysecret');
$this->elementStart('form', array(
'id' => 'forma_reset_key',
'id' => 'form_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->elementStart('li', 'entity_delete');
$this->elementStart('form', array(
'id' => 'form_delete_application',
'class' => 'form_delete_application',
'method' => 'POST',
'action' => common_local_url('deleteapplication',
array('id' => $this->application->id))));
$this->elementStart('fieldset');
$this->hidden('token', common_session_token());
$this->submit('delete', _('Delete'));
$this->elementEnd('fieldset');
$this->elementEnd('form');
$this->elementEnd('li');
$this->elementEnd('ul');
$this->elementEnd('div');

View File

@ -172,6 +172,15 @@ class ShowstreamAction extends ProfileAction
$this->element('link', array('rel' => 'microsummary',
'href' => common_local_url('microsummary',
array('nickname' => $this->profile->nickname))));
$rsd = common_local_url('rsd',
array('nickname' => $this->profile->nickname));
// RSD, http://tales.phrasewise.com/rfc/rsd
$this->element('link', array('rel' => 'EditURI',
'type' => 'application/rsd+xml',
'href' => $rsd));
}
function showProfile()

View File

@ -96,7 +96,6 @@ class UseradminpanelAction extends AdminPanelAction
);
static $booleans = array(
'sessions' => array('handle', 'debug'),
'invite' => array('enabled')
);
@ -261,26 +260,7 @@ class UserAdminPanelForm extends AdminForm
$this->out->elementEnd('ul');
$this->out->elementEnd('fieldset');
$this->out->elementStart('fieldset', array('id' => 'settings_user_sessions'));
$this->out->element('legend', null, _('Sessions'));
$this->out->elementStart('ul', 'form_data');
$this->li();
$this->out->checkbox('sessions-handle', _('Handle sessions'),
(bool) $this->value('handle', 'sessions'),
_('Whether to handle sessions ourselves.'));
$this->unli();
$this->li();
$this->out->checkbox('sessions-debug', _('Session debugging'),
(bool) $this->value('debug', 'sessions'),
_('Turn on debugging output for sessions.'));
$this->unli();
$this->out->elementEnd('ul');
$this->out->elementEnd('fieldset');
}

View File

@ -36,4 +36,34 @@ class Consumer extends Memcached_DataObject
return $cons;
}
/**
* Delete a Consumer and related tokens and nonces
*
* XXX: Should this happen in an OAuthDataStore instead?
*
*/
function delete()
{
// XXX: Is there any reason NOT to do this kind of cleanup?
$this->_deleteTokens();
$this->_deleteNonces();
parent::delete();
}
function _deleteTokens()
{
$token = new Token();
$token->consumer_key = $this->consumer_key;
$token->delete();
}
function _deleteNonces()
{
$nonce = new Nonce();
$nonce->consumer_key = $this->consumer_key;
$nonce->delete();
}
}

View File

@ -176,8 +176,22 @@ class File extends Memcached_DataObject
return "$nickname-$datestamp-$random.$ext";
}
/**
* Validation for as-saved base filenames
*/
static function validFilename($filename)
{
return preg_match('/^[A-Za-z0-9._-]+$/', $filename);
}
/**
* @throws ClientException on invalid filename
*/
static function path($filename)
{
if (!self::validFilename($filename)) {
throw new ClientException("Invalid filename");
}
$dir = common_config('attachments', 'dir');
if ($dir[strlen($dir)-1] != '/') {
@ -189,6 +203,9 @@ class File extends Memcached_DataObject
static function url($filename)
{
if (!self::validFilename($filename)) {
throw new ClientException("Invalid filename");
}
if(common_config('site','private')) {
return common_local_url('getfile',

View File

@ -147,6 +147,7 @@ class Memcached_DataObject extends DB_DataObject
{
$result = parent::insert();
if ($result) {
$this->fixupTimestamps();
$this->encache(); // in case of cached negative lookups
}
return $result;
@ -159,6 +160,7 @@ class Memcached_DataObject extends DB_DataObject
}
$result = parent::update($orig);
if ($result) {
$this->fixupTimestamps();
$this->encache();
}
return $result;
@ -366,7 +368,7 @@ class Memcached_DataObject extends DB_DataObject
}
/**
* sends query to database - this is the private one that must work
* sends query to database - this is the private one that must work
* - internal functions use this rather than $this->query()
*
* Overridden to do logging.
@ -428,7 +430,7 @@ class Memcached_DataObject extends DB_DataObject
//
// WARNING WARNING if we end up actually using multiple DBs at a time
// we'll need some fancier logic here.
if (!$exists && !empty($_DB_DATAOBJECT['CONNECTIONS'])) {
if (!$exists && !empty($_DB_DATAOBJECT['CONNECTIONS']) && php_sapi_name() == 'cli') {
foreach ($_DB_DATAOBJECT['CONNECTIONS'] as $index => $conn) {
if (!empty($conn)) {
$conn->disconnect();
@ -529,4 +531,51 @@ class Memcached_DataObject extends DB_DataObject
return $c->delete($cacheKey);
}
function fixupTimestamps()
{
// Fake up timestamp columns
$columns = $this->table();
foreach ($columns as $name => $type) {
if ($type & DB_DATAOBJECT_MYSQLTIMESTAMP) {
$this->$name = common_sql_now();
}
}
}
function debugDump()
{
common_debug("debugDump: " . common_log_objstring($this));
}
function raiseError($message, $type = null, $behaviour = null)
{
throw new ServerException("DB_DataObject error [$type]: $message");
}
static function cacheGet($keyPart)
{
$c = self::memcache();
if (empty($c)) {
return false;
}
$cacheKey = common_cache_key($keyPart);
return $c->get($cacheKey);
}
static function cacheSet($keyPart, $value)
{
$c = self::memcache();
if (empty($c)) {
return false;
}
$cacheKey = common_cache_key($keyPart);
return $c->set($cacheKey, $value);
}
}

View File

@ -140,7 +140,7 @@ class Notice extends Memcached_DataObject
foreach(array_unique($hashtags) as $hashtag) {
/* elide characters we don't want in the tag */
$this->saveTag($hashtag);
self::blow('profile:notice_ids_tagged:%d:%s', $this->profile_id, $tag->tag);
self::blow('profile:notice_ids_tagged:%d:%s', $this->profile_id, $hashtag);
}
return true;
}
@ -326,9 +326,7 @@ class Notice extends Memcached_DataObject
# XXX: someone clever could prepend instead of clearing the cache
$notice->blowOnInsert();
$qm = QueueManager::get();
$qm->enqueue($notice, 'distrib');
$notice->distribute();
return $notice;
}
@ -1374,8 +1372,6 @@ class Notice extends Memcached_DataObject
}
$reply->free();
return $ids;
}
function clearRepeats()
@ -1445,4 +1441,31 @@ class Notice extends Memcached_DataObject
$gi->free();
}
function distribute()
{
if (common_config('queue', 'inboxes')) {
// If there's a failure, we want to _force_
// distribution at this point.
try {
$qm = QueueManager::get();
$qm->enqueue($this, 'distrib');
} catch (Exception $e) {
// If the exception isn't transient, this
// may throw more exceptions as DQH does
// its own enqueueing. So, we ignore them!
try {
$handler = new DistribQueueHandler();
$handler->handle($this);
} catch (Exception $e) {
common_log(LOG_ERR, "emergency redistribution resulted in " . $e->getMessage());
}
// Re-throw so somebody smarter can handle it.
throw $e;
}
} else {
$handler = new DistribQueueHandler();
$handler->handle($this);
}
}
}

View File

@ -137,4 +137,21 @@ class Oauth_application extends Memcached_DataObject
}
}
function delete()
{
$this->_deleteAppUsers();
$consumer = $this->getConsumer();
$consumer->delete();
parent::delete();
}
function _deleteAppUsers()
{
$oauser = new Oauth_application_user();
$oauser->application_id = $this->id;
$oauser->delete();
}
}

View File

@ -48,6 +48,7 @@ class Profile_role extends Memcached_DataObject
return Memcached_DataObject::pkeyGet('Profile_role', $kv);
}
const OWNER = 'owner';
const MODERATOR = 'moderator';
const ADMINISTRATOR = 'administrator';
const SANDBOXED = 'sandboxed';

View File

@ -64,8 +64,12 @@ class Session extends Memcached_DataObject
$session = Session::staticGet('id', $id);
if (empty($session)) {
self::logdeb("Couldn't find '$id'");
return '';
} else {
self::logdeb("Found '$id', returning " .
strlen($session->session_data) .
" chars of data");
return (string)$session->session_data;
}
}
@ -77,14 +81,24 @@ class Session extends Memcached_DataObject
$session = Session::staticGet('id', $id);
if (empty($session)) {
self::logdeb("'$id' doesn't yet exist; inserting.");
$session = new Session();
$session->id = $id;
$session->session_data = $session_data;
$session->created = common_sql_now();
return $session->insert();
$result = $session->insert();
if (!$result) {
common_log_db_error($session, 'INSERT', __FILE__);
self::logdeb("Failed to insert '$id'.");
} else {
self::logdeb("Successfully inserted '$id' (result = $result).");
}
return $result;
} else {
self::logdeb("'$id' already exists; updating.");
if (strcmp($session->session_data, $session_data) == 0) {
self::logdeb("Not writing session '$id'; unchanged");
return true;
@ -95,7 +109,16 @@ class Session extends Memcached_DataObject
$session->session_data = $session_data;
return $session->update($orig);
$result = $session->update($orig);
if (!$result) {
common_log_db_error($session, 'UPDATE', __FILE__);
self::logdeb("Failed to update '$id'.");
} else {
self::logdeb("Successfully updated '$id' (result = $result).");
}
return $result;
}
}
}
@ -106,8 +129,17 @@ class Session extends Memcached_DataObject
$session = Session::staticGet('id', $id);
if (!empty($session)) {
return $session->delete();
if (empty($session)) {
self::logdeb("Can't find '$id' to delete.");
} else {
$result = $session->delete();
if (!$result) {
common_log_db_error($session, 'DELETE', __FILE__);
self::logdeb("Failed to delete '$id'.");
} else {
self::logdeb("Successfully deleted '$id' (result = $result).");
}
return $result;
}
}
@ -132,7 +164,10 @@ class Session extends Memcached_DataObject
$session->free();
self::logdeb("Found " . count($ids) . " ids to delete.");
foreach ($ids as $id) {
self::logdeb("Destroying session '$id'.");
self::destroy($id);
}
}

View File

@ -204,8 +204,6 @@ class User extends Memcached_DataObject
$profile = new Profile();
$profile->query('BEGIN');
if(!empty($email))
{
$email = common_canonical_email($email);
@ -215,7 +213,7 @@ class User extends Memcached_DataObject
$profile->nickname = $nickname;
if(! User::allowed_nickname($nickname)){
common_log(LOG_WARNING, sprintf("Attempted to register a nickname that is not allowed: %s", $profile->nickname),
__FILE__);
__FILE__);
}
$profile->profileurl = common_profile_url($nickname);
@ -243,22 +241,10 @@ class User extends Memcached_DataObject
$profile->created = common_sql_now();
$id = $profile->insert();
if (empty($id)) {
common_log_db_error($profile, 'INSERT', __FILE__);
return false;
}
$user = new User();
$user->id = $id;
$user->nickname = $nickname;
if (!empty($password)) { // may not have a password for OpenID users
$user->password = common_munge_password($password, $id);
}
// Users who respond to invite email have proven their ownership of that address
if (!empty($code)) {
@ -277,109 +263,129 @@ class User extends Memcached_DataObject
$user->inboxed = 1;
$user->created = common_sql_now();
$user->uri = common_user_uri($user);
$result = $user->insert();
if (Event::handle('StartUserRegister', array(&$user, &$profile))) {
if (!$result) {
common_log_db_error($user, 'INSERT', __FILE__);
return false;
}
$profile->query('BEGIN');
// Everyone gets an inbox
$id = $profile->insert();
$inbox = new Inbox();
$inbox->user_id = $user->id;
$inbox->notice_ids = '';
$result = $inbox->insert();
if (!$result) {
common_log_db_error($inbox, 'INSERT', __FILE__);
return false;
}
// Everyone is subscribed to themself
$subscription = new Subscription();
$subscription->subscriber = $user->id;
$subscription->subscribed = $user->id;
$subscription->created = $user->created;
$result = $subscription->insert();
if (!$result) {
common_log_db_error($subscription, 'INSERT', __FILE__);
return false;
}
if (!empty($email) && !$user->email) {
$confirm = new Confirm_address();
$confirm->code = common_confirmation_code(128);
$confirm->user_id = $user->id;
$confirm->address = $email;
$confirm->address_type = 'email';
$result = $confirm->insert();
if (!$result) {
common_log_db_error($confirm, 'INSERT', __FILE__);
if (empty($id)) {
common_log_db_error($profile, 'INSERT', __FILE__);
return false;
}
}
if (!empty($code) && $user->email) {
$user->emailChanged();
}
$user->id = $id;
$user->uri = common_user_uri($user);
if (!empty($password)) { // may not have a password for OpenID users
$user->password = common_munge_password($password, $id);
}
// Default system subscription
$result = $user->insert();
$defnick = common_config('newuser', 'default');
if (!$result) {
common_log_db_error($user, 'INSERT', __FILE__);
return false;
}
if (!empty($defnick)) {
$defuser = User::staticGet('nickname', $defnick);
if (empty($defuser)) {
common_log(LOG_WARNING, sprintf("Default user %s does not exist.", $defnick),
__FILE__);
} else {
$defsub = new Subscription();
$defsub->subscriber = $user->id;
$defsub->subscribed = $defuser->id;
$defsub->created = $user->created;
// Everyone gets an inbox
$result = $defsub->insert();
$inbox = new Inbox();
$inbox->user_id = $user->id;
$inbox->notice_ids = '';
$result = $inbox->insert();
if (!$result) {
common_log_db_error($inbox, 'INSERT', __FILE__);
return false;
}
// Everyone is subscribed to themself
$subscription = new Subscription();
$subscription->subscriber = $user->id;
$subscription->subscribed = $user->id;
$subscription->created = $user->created;
$result = $subscription->insert();
if (!$result) {
common_log_db_error($subscription, 'INSERT', __FILE__);
return false;
}
if (!empty($email) && !$user->email) {
$confirm = new Confirm_address();
$confirm->code = common_confirmation_code(128);
$confirm->user_id = $user->id;
$confirm->address = $email;
$confirm->address_type = 'email';
$result = $confirm->insert();
if (!$result) {
common_log_db_error($defsub, 'INSERT', __FILE__);
common_log_db_error($confirm, 'INSERT', __FILE__);
return false;
}
}
}
$profile->query('COMMIT');
if (!empty($email) && !$user->email) {
mail_confirm_address($user, $confirm->code, $profile->nickname, $email);
}
// Welcome message
$welcome = common_config('newuser', 'welcome');
if (!empty($welcome)) {
$welcomeuser = User::staticGet('nickname', $welcome);
if (empty($welcomeuser)) {
common_log(LOG_WARNING, sprintf("Welcome user %s does not exist.", $defnick),
__FILE__);
} else {
$notice = Notice::saveNew($welcomeuser->id,
sprintf(_('Welcome to %1$s, @%2$s!'),
common_config('site', 'name'),
$user->nickname),
'system');
if (!empty($code) && $user->email) {
$user->emailChanged();
}
// Default system subscription
$defnick = common_config('newuser', 'default');
if (!empty($defnick)) {
$defuser = User::staticGet('nickname', $defnick);
if (empty($defuser)) {
common_log(LOG_WARNING, sprintf("Default user %s does not exist.", $defnick),
__FILE__);
} else {
$defsub = new Subscription();
$defsub->subscriber = $user->id;
$defsub->subscribed = $defuser->id;
$defsub->created = $user->created;
$result = $defsub->insert();
if (!$result) {
common_log_db_error($defsub, 'INSERT', __FILE__);
return false;
}
}
}
$profile->query('COMMIT');
if (!empty($email) && !$user->email) {
mail_confirm_address($user, $confirm->code, $profile->nickname, $email);
}
// Welcome message
$welcome = common_config('newuser', 'welcome');
if (!empty($welcome)) {
$welcomeuser = User::staticGet('nickname', $welcome);
if (empty($welcomeuser)) {
common_log(LOG_WARNING, sprintf("Welcome user %s does not exist.", $defnick),
__FILE__);
} else {
$notice = Notice::saveNew($welcomeuser->id,
sprintf(_('Welcome to %1$s, @%2$s!'),
common_config('site', 'name'),
$user->nickname),
'system');
}
}
Event::handle('EndUserRegister', array(&$profile, &$user));
}
return $user;
@ -920,4 +926,30 @@ class User extends Memcached_DataObject
return $share;
}
}
static function siteOwner()
{
$owner = self::cacheGet('user:site_owner');
if ($owner === false) { // cache miss
$pr = new Profile_role();
$pr->role = Profile_role::OWNER;
$pr->orderBy('created');
$pr->limit(1);
if ($pr->find(true)) {
$owner = User::staticGet('id', $pr->profile_id);
} else {
$owner = null;
}
self::cacheSet('user:site_owner', $owner);
}
return $owner;
}
}

View File

@ -11,6 +11,7 @@ theme = 2
logo = 2
created = 142
modified = 384
tags = 34
[status_network__keys]
nickname = K

View File

@ -353,7 +353,7 @@ notice_id = K
id = 129
owner = 129
consumer_key = 130
name = 130
name = 2
description = 2
icon = 130
source_url = 2
@ -367,6 +367,7 @@ modified = 384
[oauth_application__keys]
id = N
name = U
[oauth_application_user]
profile_id = 129

View File

@ -110,3 +110,34 @@ insert into queue_item_new (frame,transport,created,claimed)
alter table queue_item rename to queue_item_old;
alter table queue_item_new rename to queue_item;
alter table consumer
add column consumer_secret varchar(255) not null comment 'secret value';
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;

View File

@ -15,7 +15,9 @@ 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',
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';

View File

@ -209,7 +209,7 @@ 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',
name varchar(255) unique key 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',
@ -225,7 +225,7 @@ create table oauth_application (
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',
access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write',
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',

View File

@ -146,12 +146,27 @@ function formatBacktraceLine($n, $line)
return $out;
}
function checkMirror($action_obj, $args)
function setupRW()
{
global $config;
static $alwaysRW = array('session', 'remember_me');
// We ensure that these tables always are used
// on the master DB
$config['db']['database_rw'] = $config['db']['database'];
$config['db']['ini_rw'] = INSTALLDIR.'/classes/statusnet.ini';
foreach ($alwaysRW as $table) {
$config['db']['table_'.$table] = 'rw';
}
}
function checkMirror($action_obj, $args)
{
global $config;
if (common_config('db', 'mirror') && $action_obj->isReadOnly($args)) {
if (is_array(common_config('db', 'mirror'))) {
// "load balancing", ha ha
@ -162,16 +177,6 @@ function checkMirror($action_obj, $args)
$mirror = common_config('db', 'mirror');
}
// We ensure that these tables always are used
// on the master DB
$config['db']['database_rw'] = $config['db']['database'];
$config['db']['ini_rw'] = INSTALLDIR.'/classes/statusnet.ini';
foreach ($alwaysRW as $table) {
$config['db']['table_'.$table] = 'rw';
}
// everyone else uses the mirror
$config['db']['database'] = $mirror;
@ -237,9 +242,13 @@ function main()
PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError');
// Make sure RW database is setup
setupRW();
// XXX: we need a little more structure in this script
// get and cache current user
// get and cache current user (may hit RW!)
$user = common_current_user();
@ -276,8 +285,9 @@ function main()
if (!$user && common_config('site', 'private')
&& !isLoginAction($action)
&& !preg_match('/rss$/', $action)
&& !preg_match('/^Api/', $action)
) {
&& $action != 'robotstxt'
&& !preg_match('/^Api/', $action)) {
// set returnto
$rargs =& common_copy_args($args);
unset($rargs['action']);

File diff suppressed because it is too large Load Diff

6711
js/jquery.js vendored

File diff suppressed because it is too large Load Diff

164
js/jquery.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -143,87 +143,85 @@ var SN = { // StatusNet
SN.U.Counter(form);
},
FormXHR: function(f) {
if (jQuery.data(f[0], "ElementData") === undefined) {
jQuery.data(f[0], "ElementData", {Bind:'submit'});
f.bind('submit', function(e) {
form_id = $(this)[0].id;
$.ajax({
type: 'POST',
dataType: 'xml',
url: $(this)[0].action,
data: $(this).serialize() + '&ajax=1',
beforeSend: function(xhr) {
$('#'+form_id).addClass(SN.C.S.Processing);
$('#'+form_id+' .submit').addClass(SN.C.S.Disabled);
$('#'+form_id+' .submit').attr(SN.C.S.Disabled, SN.C.S.Disabled);
},
error: function (xhr, textStatus, errorThrown) {
alert(errorThrown || textStatus);
},
success: function(data, textStatus) {
if (typeof($('form', data)[0]) != 'undefined') {
form_new = document._importNode($('form', data)[0], true);
$('#'+form_id).replaceWith(form_new);
$('#'+form_new.id).each(function() { SN.U.FormXHR($(this)); });
}
else {
$('#'+form_id).replaceWith(document._importNode($('p', data)[0], true));
}
}
});
return false;
});
}
FormXHR: function(form) {
$.ajax({
type: 'POST',
dataType: 'xml',
url: form.attr('action'),
data: form.serialize() + '&ajax=1',
beforeSend: function(xhr) {
form
.addClass(SN.C.S.Processing)
.find('.submit')
.addClass(SN.C.S.Disabled)
.attr(SN.C.S.Disabled, SN.C.S.Disabled);
},
error: function (xhr, textStatus, errorThrown) {
alert(errorThrown || textStatus);
},
success: function(data, textStatus) {
if (typeof($('form', data)[0]) != 'undefined') {
form_new = document._importNode($('form', data)[0], true);
form.replaceWith(form_new);
}
else {
form.replaceWith(document._importNode($('p', data)[0], true));
}
}
});
},
FormNoticeXHR: function(form) {
var NDG, NLat, NLon, NLNS, NLID;
SN.C.I.NoticeDataGeo = {};
form_id = form.attr('id');
form.append('<input type="hidden" name="ajax" value="1"/>');
form.ajaxForm({
dataType: 'xml',
timeout: '60000',
beforeSend: function(formData) {
if ($('#'+form_id+' #'+SN.C.S.NoticeDataText)[0].value.length === 0) {
if (form.find('#'+SN.C.S.NoticeDataText)[0].value.length === 0) {
form.addClass(SN.C.S.Warning);
return false;
}
form.addClass(SN.C.S.Processing);
$('#'+form_id+' #'+SN.C.S.NoticeActionSubmit).addClass(SN.C.S.Disabled);
$('#'+form_id+' #'+SN.C.S.NoticeActionSubmit).attr(SN.C.S.Disabled, SN.C.S.Disabled);
form
.addClass(SN.C.S.Processing)
.find('#'+SN.C.S.NoticeActionSubmit)
.addClass(SN.C.S.Disabled)
.attr(SN.C.S.Disabled, SN.C.S.Disabled);
NLat = $('#'+SN.C.S.NoticeLat).val();
NLon = $('#'+SN.C.S.NoticeLon).val();
NLNS = $('#'+SN.C.S.NoticeLocationNs).val();
NLID = $('#'+SN.C.S.NoticeLocationId).val();
NDG = $('#'+SN.C.S.NoticeDataGeo).attr('checked');
SN.C.I.NoticeDataGeo.NLat = $('#'+SN.C.S.NoticeLat).val();
SN.C.I.NoticeDataGeo.NLon = $('#'+SN.C.S.NoticeLon).val();
SN.C.I.NoticeDataGeo.NLNS = $('#'+SN.C.S.NoticeLocationNs).val();
SN.C.I.NoticeDataGeo.NLID = $('#'+SN.C.S.NoticeLocationId).val();
SN.C.I.NoticeDataGeo.NDG = $('#'+SN.C.S.NoticeDataGeo).attr('checked');
cookieValue = $.cookie(SN.C.S.NoticeDataGeoCookie);
if (cookieValue !== null && cookieValue != 'disabled') {
cookieValue = JSON.parse(cookieValue);
NLat = $('#'+SN.C.S.NoticeLat).val(cookieValue.NLat).val();
NLon = $('#'+SN.C.S.NoticeLon).val(cookieValue.NLon).val();
SN.C.I.NoticeDataGeo.NLat = $('#'+SN.C.S.NoticeLat).val(cookieValue.NLat).val();
SN.C.I.NoticeDataGeo.NLon = $('#'+SN.C.S.NoticeLon).val(cookieValue.NLon).val();
if ($('#'+SN.C.S.NoticeLocationNs).val(cookieValue.NLNS)) {
NLNS = $('#'+SN.C.S.NoticeLocationNs).val(cookieValue.NLNS).val();
NLID = $('#'+SN.C.S.NoticeLocationId).val(cookieValue.NLID).val();
SN.C.I.NoticeDataGeo.NLNS = $('#'+SN.C.S.NoticeLocationNs).val(cookieValue.NLNS).val();
SN.C.I.NoticeDataGeo.NLID = $('#'+SN.C.S.NoticeLocationId).val(cookieValue.NLID).val();
}
}
if (cookieValue == 'disabled') {
NDG = $('#'+SN.C.S.NoticeDataGeo).attr('checked', false).attr('checked');
SN.C.I.NoticeDataGeo.NDG = $('#'+SN.C.S.NoticeDataGeo).attr('checked', false).attr('checked');
}
else {
NDG = $('#'+SN.C.S.NoticeDataGeo).attr('checked', true).attr('checked');
SN.C.I.NoticeDataGeo.NDG = $('#'+SN.C.S.NoticeDataGeo).attr('checked', true).attr('checked');
}
return true;
},
error: function (xhr, textStatus, errorThrown) {
form.removeClass(SN.C.S.Processing);
$('#'+form_id+' #'+SN.C.S.NoticeActionSubmit).removeClass(SN.C.S.Disabled);
$('#'+form_id+' #'+SN.C.S.NoticeActionSubmit).removeAttr(SN.C.S.Disabled, SN.C.S.Disabled);
$('#'+form_id+' .form_response').remove();
form
.removeClass(SN.C.S.Processing)
.find('#'+SN.C.S.NoticeActionSubmit)
.removeClass(SN.C.S.Disabled)
.removeAttr(SN.C.S.Disabled, SN.C.S.Disabled);
form.find('.form_response').remove();
if (textStatus == 'timeout') {
form.append('<p class="form_response error">Sorry! We had trouble sending your notice. The servers are overloaded. Please try again, and contact the site administrator if this problem persists.</p>');
}
@ -233,9 +231,10 @@ var SN = { // StatusNet
}
else {
if (parseInt(xhr.status) === 0 || jQuery.inArray(parseInt(xhr.status), SN.C.I.HTTP20x30x) >= 0) {
$('#'+form_id).resetForm();
$('#'+form_id+' #'+SN.C.S.NoticeDataAttachSelected).remove();
SN.U.FormNoticeEnhancements($('#'+form_id));
form
.resetForm()
.find('#'+SN.C.S.NoticeDataAttachSelected).remove();
SN.U.FormNoticeEnhancements(form);
}
else {
form.append('<p class="form_response error">(Sorry! We had trouble sending your notice ('+xhr.status+' '+xhr.statusText+'). Please report the problem to the site administrator if this happens again.</p>');
@ -244,7 +243,7 @@ var SN = { // StatusNet
}
},
success: function(data, textStatus) {
$('#'+form_id+' .form_response').remove();
form.find('.form_response').remove();
var result;
if ($('#'+SN.C.S.Error, data).length > 0) {
result = document._importNode($('p', data)[0], true);
@ -277,11 +276,11 @@ var SN = { // StatusNet
else {
notices.prepend(notice);
}
$('#'+notice.id).css({display:'none'});
$('#'+notice.id).fadeIn(2500);
$('#'+notice.id)
.css({display:'none'})
.fadeIn(2500);
SN.U.NoticeWithAttachment($('#'+notice.id));
SN.U.NoticeReplyTo($('#'+notice.id));
SN.U.FormXHR($('#'+notice.id+' .form_favor'));
}
}
else {
@ -290,24 +289,26 @@ var SN = { // StatusNet
form.append('<p class="form_response success">'+result_title+'</p>');
}
}
$('#'+form_id).resetForm();
$('#'+form_id+' #'+SN.C.S.NoticeInReplyTo).val('');
$('#'+form_id+' #'+SN.C.S.NoticeDataAttachSelected).remove();
SN.U.FormNoticeEnhancements($('#'+form_id));
form.resetForm();
form.find('#'+SN.C.S.NoticeInReplyTo).val('');
form.find('#'+SN.C.S.NoticeDataAttachSelected).remove();
SN.U.FormNoticeEnhancements(form);
}
},
complete: function(xhr, textStatus) {
form.removeClass(SN.C.S.Processing);
$('#'+form_id+' #'+SN.C.S.NoticeActionSubmit).removeAttr(SN.C.S.Disabled);
$('#'+form_id+' #'+SN.C.S.NoticeActionSubmit).removeClass(SN.C.S.Disabled);
form
.removeClass(SN.C.S.Processing)
.find('#'+SN.C.S.NoticeActionSubmit)
.removeAttr(SN.C.S.Disabled)
.removeClass(SN.C.S.Disabled);
$('#'+SN.C.S.NoticeLat).val(NLat);
$('#'+SN.C.S.NoticeLon).val(NLon);
$('#'+SN.C.S.NoticeLat).val(SN.C.I.NoticeDataGeo.NLat);
$('#'+SN.C.S.NoticeLon).val(SN.C.I.NoticeDataGeo.NLon);
if ($('#'+SN.C.S.NoticeLocationNs)) {
$('#'+SN.C.S.NoticeLocationNs).val(NLNS);
$('#'+SN.C.S.NoticeLocationId).val(NLID);
$('#'+SN.C.S.NoticeLocationNs).val(SN.C.I.NoticeDataGeo.NLNS);
$('#'+SN.C.S.NoticeLocationId).val(SN.C.I.NoticeDataGeo.NLID);
}
$('#'+SN.C.S.NoticeDataGeo).attr('checked', NDG);
$('#'+SN.C.S.NoticeDataGeo).attr('checked', SN.C.I.NoticeDataGeo.NDG);
}
});
},
@ -350,14 +351,15 @@ var SN = { // StatusNet
},
NoticeFavor: function() {
$('.form_favor').each(function() { SN.U.FormXHR($(this)); });
$('.form_disfavor').each(function() { SN.U.FormXHR($(this)); });
$('.form_favor').live('click', function() { SN.U.FormXHR($(this)); return false; });
$('.form_disfavor').live('click', function() { SN.U.FormXHR($(this)); return false; });
},
NoticeRepeat: function() {
$('.form_repeat').each(function() {
$('.form_repeat').live('click', function() {
SN.U.FormXHR($(this));
SN.U.NoticeRepeatConfirmation($(this));
return false;
});
},
@ -639,7 +641,7 @@ var SN = { // StatusNet
NDM.bind('click', function() {
var NDMF = $('.entity_send-a-message form');
if (NDMF.length === 0) {
$(this).addClass('processing');
$(this).addClass(SN.C.S.Processing);
$.get(NDM.attr('href'), null, function(data) {
$('.entity_send-a-message').append(document._importNode($('form', data)[0], true));
NDMF = $('.entity_send-a-message .form_notice');
@ -650,7 +652,7 @@ var SN = { // StatusNet
NDMF.hide();
return false;
});
NDM.removeClass('processing');
NDM.removeClass(SN.C.S.Processing);
});
}
else {
@ -695,11 +697,11 @@ var SN = { // StatusNet
EntityActions: function() {
if ($('body.user_in').length > 0) {
$('.form_user_subscribe').each(function() { SN.U.FormXHR($(this)); });
$('.form_user_unsubscribe').each(function() { SN.U.FormXHR($(this)); });
$('.form_group_join').each(function() { SN.U.FormXHR($(this)); });
$('.form_group_leave').each(function() { SN.U.FormXHR($(this)); });
$('.form_user_nudge').each(function() { SN.U.FormXHR($(this)); });
$('.form_user_subscribe').live('click', function() { SN.U.FormXHR($(this)); return false; });
$('.form_user_unsubscribe').live('click', function() { SN.U.FormXHR($(this)); return false; });
$('.form_group_join').live('click', function() { SN.U.FormXHR($(this)); return false; });
$('.form_group_leave').live('click', function() { SN.U.FormXHR($(this)); return false; });
$('.form_user_nudge').live('click', function() { SN.U.FormXHR($(this)); return false; });
SN.U.NewDirectMessage();
}

View File

@ -327,9 +327,14 @@ class AdminPanelNav extends Widget
_('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');
if ($this->canAdmin('paths')) {
$this->out->menuItem(common_local_url('pathsadminpanel'), _('Paths'),
_('Paths configuration'), $action_name == 'pathsadminpanel', 'nav_design_admin_panel');
}
if ($this->canAdmin('sessions')) {
$this->out->menuItem(common_local_url('sessionsadminpanel'), _('Sessions'),
_('Sessions configuration'), $action_name == 'sessionsadminpanel', 'nav_design_admin_panel');
}
Event::handle('EndAdminPanelNav', array($this));

View File

@ -299,7 +299,7 @@ class ApiAction extends Action
}
}
if ($include_user) {
if ($include_user && $profile) {
# Don't get notice (recursive!)
$twitter_user = $this->twitterUserArray($profile, false);
$twitter_status['user'] = $twitter_user;
@ -1250,10 +1250,27 @@ class ApiAction extends Action
case 'api':
break;
default:
$name = null;
$url = null;
$ns = Notice_source::staticGet($source);
if ($ns) {
$source_name = '<a href="' . $ns->url . '">' . $ns->name . '</a>';
$name = $ns->name;
$url = $ns->url;
} else {
$app = Oauth_application::staticGet('name', $source);
if ($app) {
$name = $app->name;
$url = $app->source_url;
}
}
if (!empty($name) && !empty($url)) {
$source_name = '<a href="' . $url . '">' . $name . '</a>';
}
break;
}
return $source_name;

View File

@ -55,11 +55,10 @@ 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
* Take arguments for running, looks for an OAuth request,
* and outputs basic auth header if needed
*
* @param array $args $_REQUEST args
*
@ -71,26 +70,23 @@ 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()) {
if (!empty($this->access_token)) {
$this->checkOAuthRequest();
} else {
$oauthReq = $this->getOAuthRequest();
if (!$oauthReq) {
$this->checkBasicAuthUser(true);
} else {
$this->checkOAuthRequest($oauthReq);
}
} 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);
}
$this->checkBasicAuthUser(false);
}
// Reject API calls with the wrong access level
@ -110,12 +106,44 @@ class ApiAuthAction extends ApiAction
return true;
}
function handle($args)
/**
* Determine whether the request is an OAuth request.
* This is to avoid doign any unnecessary DB lookups.
*
* @return mixed the OAuthRequest or false
*
*/
function getOAuthRequest()
{
parent::handle($args);
ApiOauthAction::cleanRequest();
$req = OAuthRequest::from_request();
$consumer = $req->get_parameter('oauth_consumer_key');
$accessToken = $req->get_parameter('oauth_token');
// XXX: Is it good enough to assume it's not meant to be an
// OAuth request if there is no consumer or token? --Z
if (empty($consumer) || empty($accessToken)) {
return false;
}
return $req;
}
function checkOAuthRequest()
/**
* Verifies the OAuth request signature, sets the auth user
* and access type (read-only or read-write)
*
* @param OAuthRequest $request the OAuth Request
*
* @return nothing
*
*/
function checkOAuthRequest($request)
{
$datastore = new ApiStatusNetOAuthDataStore();
$server = new OAuthServer($datastore);
@ -123,22 +151,19 @@ class ApiAuthAction extends ApiAction
$server->add_signature_method($hmac_method);
ApiOauthAction::cleanRequest();
try {
$req = OAuthRequest::from_request();
$server->verify_request($req);
$server->verify_request($request);
$app = Oauth_application::getByConsumerKey($this->consumer_key);
$consumer = $request->get_parameter('oauth_consumer_key');
$access_token = $request->get_parameter('oauth_token');
$app = Oauth_application::getByConsumerKey($consumer);
if (empty($app)) {
// this should probably not happen
common_log(LOG_WARNING,
'Couldn\'t find the OAuth app for consumer key: ' .
$this->consumer_key);
$consumer);
throw new OAuthException('No application for that consumer key.');
}
@ -146,11 +171,7 @@ class ApiAuthAction extends ApiAction
$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?
$appUser = Oauth_application_user::staticGet('token', $access_token);
if (!empty($appUser)) {
@ -164,6 +185,8 @@ class ApiAuthAction extends ApiAction
$this->access = ($appUser->access_type & Oauth_application::$writeAccess)
? self::READ_WRITE : self::READ_ONLY;
// Set the auth user
if (Event::handle('StartSetApiUser', array(&$user))) {
$this->auth_user = User::staticGet('id', $appUser->profile_id);
Event::handle('EndSetApiUser', array($user));
@ -180,7 +203,6 @@ class ApiAuthAction extends ApiAction
($this->access = self::READ_WRITE) ?
'read-write' : 'read-only'
));
return;
} else {
throw new OAuthException('Bad access token.');
}

View File

@ -159,5 +159,32 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore
}
}
/**
* Revoke specified access token
*
* Revokes the token specified by $token_key.
* Throws exceptions in case of error.
*
* @param string $token_key the token to be revoked
* @param int $type type of token (0 = req, 1 = access)
*
* @access public
*
* @return void
*/
public function revoke_token($token_key, $type = 0) {
$rt = new Token();
$rt->tok = $token_key;
$rt->type = $type;
$rt->state = 0;
if (!$rt->find(true)) {
throw new Exception('Tried to revoke unknown token');
}
if (!$rt->delete()) {
throw new Exception('Failed to delete revoked token');
}
}
}

View File

@ -69,13 +69,17 @@ abstract class AuthenticationPlugin extends Plugin
/**
* Automatically register a user when they attempt to login with valid credentials.
* User::register($data) is a very useful method for this implementation
* @param username
* @param username username (that is used to login and find the user in the authentication provider) of the user to be registered
* @param nickname nickname of the user in the SN system. If nickname is null, then set nickname = username
* @return mixed instance of User, or false (if user couldn't be created)
*/
function autoRegister($username)
function autoRegister($username, $nickname = null)
{
if(is_null($nickname)){
$nickname = $username;
}
$registration_data = array();
$registration_data['nickname'] = $username ;
$registration_data['nickname'] = $nickname ;
return User::register($registration_data);
}
@ -132,7 +136,7 @@ abstract class AuthenticationPlugin extends Plugin
//someone already exists with the suggested nickname
//not much else we can do
}else{
$user = $this->autoregister($suggested_nickname);
$user = $this->autoRegister($nickname, $suggested_nickname);
if($user){
User_username::register($user,$nickname,$this->provider_name);
return false;

View File

@ -22,7 +22,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
//exit with 200 response, if this is checking fancy from the installer
if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; }
define('STATUSNET_VERSION', '0.9.0beta3');
define('STATUSNET_VERSION', '0.9.0beta5');
define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility
define('STATUSNET_CODENAME', 'Stand');
@ -115,6 +115,10 @@ function __autoload($cls)
require_once 'Validate.php';
require_once 'markdown.php';
// XXX: other formats here
define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER);
require_once INSTALLDIR.'/lib/util.php';
require_once INSTALLDIR.'/lib/action.php';
require_once INSTALLDIR.'/lib/mail.php';
@ -136,6 +140,3 @@ try {
exit;
}
// XXX: other formats here
define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER);

View File

@ -84,9 +84,12 @@ $default =
'control_channel' => '/topic/statusnet-control', // broadcasts to all queue daemons
'stomp_username' => null,
'stomp_password' => null,
'stomp_persistent' => true, // keep items across queue server restart, if persistence is enabled
'stomp_manual_failover' => true, // if multiple servers are listed, treat them as separate (enqueue on one randomly, listen on all)
'monitor' => null, // URL to monitor ping endpoint (work in progress)
'softlimit' => '90%', // total size or % of memory_limit at which to restart queue threads gracefully
'debug_memory' => false, // true to spit memory usage to log
'inboxes' => true, // true to do inbox distribution & output queueing from in background via 'distrib' queue
),
'license' =>
array('type' => 'cc', # can be 'cc', 'allrightsreserved', 'private'
@ -265,8 +268,12 @@ $default =
'OpenID' => null),
),
'admin' =>
array('panels' => array('design', 'site', 'user', 'paths', 'access')),
array('panels' => array('design', 'site', 'user', 'paths', 'access', 'sessions')),
'singleuser' =>
array('enabled' => false,
'nickname' => null),
'robotstxt' =>
array('crawldelay' => 0,
'disallow' => array('main', 'settings', 'admin', 'search', 'message')
),
);

View File

@ -62,23 +62,60 @@ class DistribQueueHandler
{
// XXX: do we need to change this for remote users?
$notice->saveTags();
try {
$notice->saveTags();
} catch (Exception $e) {
$this->logit($notice, $e);
}
$groups = $notice->saveGroups();
try {
$groups = $notice->saveGroups();
} catch (Exception $e) {
$this->logit($notice, $e);
}
$recipients = $notice->saveReplies();
try {
$recipients = $notice->saveReplies();
} catch (Exception $e) {
$this->logit($notice, $e);
}
$notice->addToInboxes($groups, $recipients);
try {
$notice->addToInboxes($groups, $recipients);
} catch (Exception $e) {
$this->logit($notice, $e);
}
$notice->saveUrls();
try {
$notice->saveUrls();
} catch (Exception $e) {
$this->logit($notice, $e);
}
Event::handle('EndNoticeSave', array($notice));
try {
Event::handle('EndNoticeSave', array($notice));
// Enqueue for other handlers
} catch (Exception $e) {
$this->logit($notice, $e);
}
// Enqueue for other handlers
common_enqueue_notice($notice);
try {
common_enqueue_notice($notice);
} catch (Exception $e) {
$this->logit($notice, $e);
}
return true;
}
protected function logit($notice, $e)
{
common_log(LOG_ERR, "Distrib queue exception saving notice $notice->id: " .
$e->getMessage() . ' ' .
str_replace("\n", " ", $e->getTraceAsString()));
// We'll still return true so we don't get stuck in a loop
// trying to run a bad insert over and over...
}
}

View File

@ -33,6 +33,22 @@ class LiberalStomp extends Stomp
return $this->_socket;
}
/**
* Return the host we're currently connected to.
*
* @return string
*/
function getServer()
{
$idx = $this->_currentHost;
if ($idx >= 0) {
$host = $this->_hosts[$idx];
return "$host[0]:$host[1]";
} else {
return '[unconnected]';
}
}
/**
* Make socket connection to the server
* We also set the stream to non-blocking mode, since we'll be
@ -71,10 +87,12 @@ class LiberalStomp extends Stomp
// @fixme this sometimes hangs in blocking mode...
// shouldn't we have been idle until we found there's more data?
$read = fread($this->_socket, $rb);
if ($read === false) {
$this->_reconnect();
if ($read === false || ($read === '' && feof($this->_socket))) {
// @fixme possibly attempt an auto reconnect as old code?
throw new StompException("Error reading");
//$this->_reconnect();
// @fixme this will lose prior items
return $this->readFrames();
//return $this->readFrames();
}
$data .= $read;
if (strpos($data, "\x00") !== false) {

537
lib/mysqlschema.php Normal file
View File

@ -0,0 +1,537 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Database schema utilities
*
* 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 Database
* @package StatusNet
* @author Evan Prodromou <evan@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')) {
exit(1);
}
/**
* Class representing the database schema
*
* A class representing the database schema. Can be used to
* manipulate the schema -- especially for plugins and upgrade
* utilities.
*
* @category Database
* @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/
*/
class MysqlSchema extends Schema
{
static $_single = null;
protected $conn = null;
/**
* Constructor. Only run once for singleton object.
*/
protected function __construct()
{
// XXX: there should be an easier way to do this.
$user = new User();
$this->conn = $user->getDatabaseConnection();
$user->free();
unset($user);
}
/**
* Main public entry point. Use this to get
* the singleton object.
*
* @return Schema the (single) Schema object
*/
static function get()
{
if (empty(self::$_single)) {
self::$_single = new Schema();
}
return self::$_single;
}
/**
* Returns a TableDef object for the table
* in the schema with the given name.
*
* Throws an exception if the table is not found.
*
* @param string $name Name of the table to get
*
* @return TableDef tabledef for that table.
*/
public function getTableDef($name)
{
$res = $this->conn->query('DESCRIBE ' . $name);
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
$td = new TableDef();
$td->name = $name;
$td->columns = array();
$row = array();
while ($res->fetchInto($row, DB_FETCHMODE_ASSOC)) {
$cd = new ColumnDef();
$cd->name = $row['Field'];
$packed = $row['Type'];
if (preg_match('/^(\w+)\((\d+)\)$/', $packed, $match)) {
$cd->type = $match[1];
$cd->size = $match[2];
} else {
$cd->type = $packed;
}
$cd->nullable = ($row['Null'] == 'YES') ? true : false;
$cd->key = $row['Key'];
$cd->default = $row['Default'];
$cd->extra = $row['Extra'];
$td->columns[] = $cd;
}
return $td;
}
/**
* Gets a ColumnDef object for a single column.
*
* Throws an exception if the table is not found.
*
* @param string $table name of the table
* @param string $column name of the column
*
* @return ColumnDef definition of the column or null
* if not found.
*/
public function getColumnDef($table, $column)
{
$td = $this->getTableDef($table);
foreach ($td->columns as $cd) {
if ($cd->name == $column) {
return $cd;
}
}
return null;
}
/**
* Creates a table with the given names and columns.
*
* @param string $name Name of the table
* @param array $columns Array of ColumnDef objects
* for new table.
*
* @return boolean success flag
*/
public function createTable($name, $columns)
{
$uniques = array();
$primary = array();
$indices = array();
$sql = "CREATE TABLE $name (\n";
for ($i = 0; $i < count($columns); $i++) {
$cd =& $columns[$i];
if ($i > 0) {
$sql .= ",\n";
}
$sql .= $this->_columnSql($cd);
switch ($cd->key) {
case 'UNI':
$uniques[] = $cd->name;
break;
case 'PRI':
$primary[] = $cd->name;
break;
case 'MUL':
$indices[] = $cd->name;
break;
}
}
if (count($primary) > 0) { // it really should be...
$sql .= ",\nconstraint primary key (" . implode(',', $primary) . ")";
}
foreach ($uniques as $u) {
$sql .= ",\nunique index {$name}_{$u}_idx ($u)";
}
foreach ($indices as $i) {
$sql .= ",\nindex {$name}_{$i}_idx ($i)";
}
$sql .= "); ";
$res = $this->conn->query($sql);
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
return true;
}
/**
* Drops a table from the schema
*
* Throws an exception if the table is not found.
*
* @param string $name Name of the table to drop
*
* @return boolean success flag
*/
public function dropTable($name)
{
$res = $this->conn->query("DROP TABLE $name");
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
return true;
}
/**
* Adds an index to a table.
*
* If no name is provided, a name will be made up based
* on the table name and column names.
*
* Throws an exception on database error, esp. if the table
* does not exist.
*
* @param string $table Name of the table
* @param array $columnNames Name of columns to index
* @param string $name (Optional) name of the index
*
* @return boolean success flag
*/
public function createIndex($table, $columnNames, $name=null)
{
if (!is_array($columnNames)) {
$columnNames = array($columnNames);
}
if (empty($name)) {
$name = "$table_".implode("_", $columnNames)."_idx";
}
$res = $this->conn->query("ALTER TABLE $table ".
"ADD INDEX $name (".
implode(",", $columnNames).")");
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
return true;
}
/**
* Drops a named index from a table.
*
* @param string $table name of the table the index is on.
* @param string $name name of the index
*
* @return boolean success flag
*/
public function dropIndex($table, $name)
{
$res = $this->conn->query("ALTER TABLE $table DROP INDEX $name");
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
return true;
}
/**
* Adds a column to a table
*
* @param string $table name of the table
* @param ColumnDef $columndef Definition of the new
* column.
*
* @return boolean success flag
*/
public function addColumn($table, $columndef)
{
$sql = "ALTER TABLE $table ADD COLUMN " . $this->_columnSql($columndef);
$res = $this->conn->query($sql);
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
return true;
}
/**
* Modifies a column in the schema.
*
* The name must match an existing column and table.
*
* @param string $table name of the table
* @param ColumnDef $columndef new definition of the column.
*
* @return boolean success flag
*/
public function modifyColumn($table, $columndef)
{
$sql = "ALTER TABLE $table MODIFY COLUMN " .
$this->_columnSql($columndef);
$res = $this->conn->query($sql);
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
return true;
}
/**
* Drops a column from a table
*
* The name must match an existing column.
*
* @param string $table name of the table
* @param string $columnName name of the column to drop
*
* @return boolean success flag
*/
public function dropColumn($table, $columnName)
{
$sql = "ALTER TABLE $table DROP COLUMN $columnName";
$res = $this->conn->query($sql);
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
return true;
}
/**
* Ensures that a table exists with the given
* name and the given column definitions.
*
* If the table does not yet exist, it will
* create the table. If it does exist, it will
* alter the table to match the column definitions.
*
* @param string $tableName name of the table
* @param array $columns array of ColumnDef
* objects for the table
*
* @return boolean success flag
*/
public function ensureTable($tableName, $columns)
{
// XXX: DB engine portability -> toilet
try {
$td = $this->getTableDef($tableName);
} catch (Exception $e) {
if (preg_match('/no such table/', $e->getMessage())) {
return $this->createTable($tableName, $columns);
} else {
throw $e;
}
}
$cur = $this->_names($td->columns);
$new = $this->_names($columns);
$toadd = array_diff($new, $cur);
$todrop = array_diff($cur, $new);
$same = array_intersect($new, $cur);
$tomod = array();
foreach ($same as $m) {
$curCol = $this->_byName($td->columns, $m);
$newCol = $this->_byName($columns, $m);
if (!$newCol->equals($curCol)) {
$tomod[] = $newCol->name;
}
}
if (count($toadd) + count($todrop) + count($tomod) == 0) {
// nothing to do
return true;
}
// For efficiency, we want this all in one
// query, instead of using our methods.
$phrase = array();
foreach ($toadd as $columnName) {
$cd = $this->_byName($columns, $columnName);
$phrase[] = 'ADD COLUMN ' . $this->_columnSql($cd);
}
foreach ($todrop as $columnName) {
$phrase[] = 'DROP COLUMN ' . $columnName;
}
foreach ($tomod as $columnName) {
$cd = $this->_byName($columns, $columnName);
$phrase[] = 'MODIFY COLUMN ' . $this->_columnSql($cd);
}
$sql = 'ALTER TABLE ' . $tableName . ' ' . implode(', ', $phrase);
$res = $this->conn->query($sql);
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
return true;
}
/**
* Returns the array of names from an array of
* ColumnDef objects.
*
* @param array $cds array of ColumnDef objects
*
* @return array strings for name values
*/
private function _names($cds)
{
$names = array();
foreach ($cds as $cd) {
$names[] = $cd->name;
}
return $names;
}
/**
* Get a ColumnDef from an array matching
* name.
*
* @param array $cds Array of ColumnDef objects
* @param string $name Name of the column
*
* @return ColumnDef matching item or null if no match.
*/
private function _byName($cds, $name)
{
foreach ($cds as $cd) {
if ($cd->name == $name) {
return $cd;
}
}
return null;
}
/**
* Return the proper SQL for creating or
* altering a column.
*
* Appropriate for use in CREATE TABLE or
* ALTER TABLE statements.
*
* @param ColumnDef $cd column to create
*
* @return string correct SQL for that column
*/
private function _columnSql($cd)
{
$sql = "{$cd->name} ";
if (!empty($cd->size)) {
$sql .= "{$cd->type}({$cd->size}) ";
} else {
$sql .= "{$cd->type} ";
}
if (!empty($cd->default)) {
$sql .= "default {$cd->default} ";
} else {
$sql .= ($cd->nullable) ? "null " : "not null ";
}
if (!empty($cd->auto_increment)) {
$sql .= " auto_increment ";
}
if (!empty($cd->extra)) {
$sql .= "{$cd->extra} ";
}
return $sql;
}
}

View File

@ -486,12 +486,28 @@ class NoticeListItem extends Widget
$this->out->element('span', 'device', $source_name);
break;
default:
$name = null;
$url = null;
$ns = Notice_source::staticGet($this->notice->source);
if ($ns) {
$name = $ns->name;
$url = $ns->url;
} else {
$app = Oauth_application::staticGet('name', $this->notice->source);
if ($app) {
$name = $app->name;
$url = $app->source_url;
}
}
if (!empty($name) && !empty($url)) {
$this->out->elementStart('span', 'device');
$this->out->element('a', array('href' => $ns->url,
$this->out->element('a', array('href' => $url,
'rel' => 'external'),
$ns->name);
$name);
$this->out->elementEnd('span');
} else {
$this->out->element('span', 'device', $source_name);

View File

@ -39,7 +39,7 @@ class OmbQueueHandler extends QueueHandler
function handle($notice)
{
if ($this->is_remote($notice)) {
$this->log(LOG_DEBUG, 'Ignoring remote notice ' . $notice->id);
common_log(LOG_DEBUG, 'Ignoring remote notice ' . $notice->id);
return true;
} else {
require_once(INSTALLDIR.'/lib/omb.php');

503
lib/pgsqlschema.php Normal file
View File

@ -0,0 +1,503 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Database schema utilities
*
* 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 Database
* @package StatusNet
* @author Evan Prodromou <evan@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')) {
exit(1);
}
/**
* Class representing the database schema
*
* A class representing the database schema. Can be used to
* manipulate the schema -- especially for plugins and upgrade
* utilities.
*
* @category Database
* @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/
*/
class PgsqlSchema extends Schema
{
/**
* Returns a TableDef object for the table
* in the schema with the given name.
*
* Throws an exception if the table is not found.
*
* @param string $name Name of the table to get
*
* @return TableDef tabledef for that table.
*/
public function getTableDef($name)
{
$res = $this->conn->query("select *, column_default as default, is_nullable as Null, udt_name as Type, column_name AS Field from INFORMATION_SCHEMA.COLUMNS where table_name = '$name'");
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
$td = new TableDef();
$td->name = $name;
$td->columns = array();
$row = array();
while ($res->fetchInto($row, DB_FETCHMODE_ASSOC)) {
// var_dump($row);
$cd = new ColumnDef();
$cd->name = $row['field'];
$packed = $row['type'];
if (preg_match('/^(\w+)\((\d+)\)$/', $packed, $match)) {
$cd->type = $match[1];
$cd->size = $match[2];
} else {
$cd->type = $packed;
}
$cd->nullable = ($row['null'] == 'YES') ? true : false;
$cd->key = $row['Key'];
$cd->default = $row['default'];
$cd->extra = $row['Extra'];
$td->columns[] = $cd;
}
return $td;
}
/**
* Gets a ColumnDef object for a single column.
*
* Throws an exception if the table is not found.
*
* @param string $table name of the table
* @param string $column name of the column
*
* @return ColumnDef definition of the column or null
* if not found.
*/
public function getColumnDef($table, $column)
{
$td = $this->getTableDef($table);
foreach ($td->columns as $cd) {
if ($cd->name == $column) {
return $cd;
}
}
return null;
}
/**
* Creates a table with the given names and columns.
*
* @param string $name Name of the table
* @param array $columns Array of ColumnDef objects
* for new table.
*
* @return boolean success flag
*/
public function createTable($name, $columns)
{
$uniques = array();
$primary = array();
$indices = array();
$sql = "CREATE TABLE $name (\n";
for ($i = 0; $i < count($columns); $i++) {
$cd =& $columns[$i];
if ($i > 0) {
$sql .= ",\n";
}
$sql .= $this->_columnSql($cd);
switch ($cd->key) {
case 'UNI':
$uniques[] = $cd->name;
break;
case 'PRI':
$primary[] = $cd->name;
break;
case 'MUL':
$indices[] = $cd->name;
break;
}
}
if (count($primary) > 0) { // it really should be...
$sql .= ",\nconstraint primary key (" . implode(',', $primary) . ")";
}
foreach ($uniques as $u) {
$sql .= ",\nunique index {$name}_{$u}_idx ($u)";
}
foreach ($indices as $i) {
$sql .= ",\nindex {$name}_{$i}_idx ($i)";
}
$sql .= "); ";
$res = $this->conn->query($sql);
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
return true;
}
/**
* Drops a table from the schema
*
* Throws an exception if the table is not found.
*
* @param string $name Name of the table to drop
*
* @return boolean success flag
*/
public function dropTable($name)
{
$res = $this->conn->query("DROP TABLE $name");
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
return true;
}
/**
* Adds an index to a table.
*
* If no name is provided, a name will be made up based
* on the table name and column names.
*
* Throws an exception on database error, esp. if the table
* does not exist.
*
* @param string $table Name of the table
* @param array $columnNames Name of columns to index
* @param string $name (Optional) name of the index
*
* @return boolean success flag
*/
public function createIndex($table, $columnNames, $name=null)
{
if (!is_array($columnNames)) {
$columnNames = array($columnNames);
}
if (empty($name)) {
$name = "$table_".implode("_", $columnNames)."_idx";
}
$res = $this->conn->query("ALTER TABLE $table ".
"ADD INDEX $name (".
implode(",", $columnNames).")");
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
return true;
}
/**
* Drops a named index from a table.
*
* @param string $table name of the table the index is on.
* @param string $name name of the index
*
* @return boolean success flag
*/
public function dropIndex($table, $name)
{
$res = $this->conn->query("ALTER TABLE $table DROP INDEX $name");
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
return true;
}
/**
* Adds a column to a table
*
* @param string $table name of the table
* @param ColumnDef $columndef Definition of the new
* column.
*
* @return boolean success flag
*/
public function addColumn($table, $columndef)
{
$sql = "ALTER TABLE $table ADD COLUMN " . $this->_columnSql($columndef);
$res = $this->conn->query($sql);
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
return true;
}
/**
* Modifies a column in the schema.
*
* The name must match an existing column and table.
*
* @param string $table name of the table
* @param ColumnDef $columndef new definition of the column.
*
* @return boolean success flag
*/
public function modifyColumn($table, $columndef)
{
$sql = "ALTER TABLE $table MODIFY COLUMN " .
$this->_columnSql($columndef);
$res = $this->conn->query($sql);
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
return true;
}
/**
* Drops a column from a table
*
* The name must match an existing column.
*
* @param string $table name of the table
* @param string $columnName name of the column to drop
*
* @return boolean success flag
*/
public function dropColumn($table, $columnName)
{
$sql = "ALTER TABLE $table DROP COLUMN $columnName";
$res = $this->conn->query($sql);
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
return true;
}
/**
* Ensures that a table exists with the given
* name and the given column definitions.
*
* If the table does not yet exist, it will
* create the table. If it does exist, it will
* alter the table to match the column definitions.
*
* @param string $tableName name of the table
* @param array $columns array of ColumnDef
* objects for the table
*
* @return boolean success flag
*/
public function ensureTable($tableName, $columns)
{
// XXX: DB engine portability -> toilet
try {
$td = $this->getTableDef($tableName);
} catch (Exception $e) {
if (preg_match('/no such table/', $e->getMessage())) {
return $this->createTable($tableName, $columns);
} else {
throw $e;
}
}
$cur = $this->_names($td->columns);
$new = $this->_names($columns);
$toadd = array_diff($new, $cur);
$todrop = array_diff($cur, $new);
$same = array_intersect($new, $cur);
$tomod = array();
foreach ($same as $m) {
$curCol = $this->_byName($td->columns, $m);
$newCol = $this->_byName($columns, $m);
if (!$newCol->equals($curCol)) {
$tomod[] = $newCol->name;
}
}
if (count($toadd) + count($todrop) + count($tomod) == 0) {
// nothing to do
return true;
}
// For efficiency, we want this all in one
// query, instead of using our methods.
$phrase = array();
foreach ($toadd as $columnName) {
$cd = $this->_byName($columns, $columnName);
$phrase[] = 'ADD COLUMN ' . $this->_columnSql($cd);
}
foreach ($todrop as $columnName) {
$phrase[] = 'DROP COLUMN ' . $columnName;
}
foreach ($tomod as $columnName) {
$cd = $this->_byName($columns, $columnName);
$phrase[] = 'MODIFY COLUMN ' . $this->_columnSql($cd);
}
$sql = 'ALTER TABLE ' . $tableName . ' ' . implode(', ', $phrase);
$res = $this->conn->query($sql);
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
return true;
}
/**
* Returns the array of names from an array of
* ColumnDef objects.
*
* @param array $cds array of ColumnDef objects
*
* @return array strings for name values
*/
private function _names($cds)
{
$names = array();
foreach ($cds as $cd) {
$names[] = $cd->name;
}
return $names;
}
/**
* Get a ColumnDef from an array matching
* name.
*
* @param array $cds Array of ColumnDef objects
* @param string $name Name of the column
*
* @return ColumnDef matching item or null if no match.
*/
private function _byName($cds, $name)
{
foreach ($cds as $cd) {
if ($cd->name == $name) {
return $cd;
}
}
return null;
}
/**
* Return the proper SQL for creating or
* altering a column.
*
* Appropriate for use in CREATE TABLE or
* ALTER TABLE statements.
*
* @param ColumnDef $cd column to create
*
* @return string correct SQL for that column
*/
private function _columnSql($cd)
{
$sql = "{$cd->name} ";
if (!empty($cd->size)) {
$sql .= "{$cd->type}({$cd->size}) ";
} else {
$sql .= "{$cd->type} ";
}
if (!empty($cd->default)) {
$sql .= "default {$cd->default} ";
} else {
$sql .= ($cd->nullable) ? "null " : "not null ";
}
if (!empty($cd->auto_increment)) {
$sql .= " auto_increment ";
}
if (!empty($cd->extra)) {
$sql .= "{$cd->extra} ";
}
return $sql;
}
}

View File

@ -73,6 +73,8 @@ class Router
if (Event::handle('StartInitializeRouter', array(&$m))) {
$m->connect('robots.txt', array('action' => 'robotstxt'));
$m->connect('opensearch/people', array('action' => 'opensearch',
'type' => 'people'));
$m->connect('opensearch/notice', array('action' => 'opensearch',
@ -150,6 +152,10 @@ class Router
array('action' => 'editapplication'),
array('id' => '[0-9]+')
);
$m->connect('settings/oauthapps/delete/:id',
array('action' => 'deleteapplication'),
array('id' => '[0-9]+')
);
// search
@ -637,8 +643,9 @@ 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/access', array('action' => 'accessadminpanel'));
$m->connect('admin/paths', array('action' => 'pathsadminpanel'));
$m->connect('admin/sessions', array('action' => 'sessionsadminpanel'));
$m->connect('getfile/:filename',
array('action' => 'getfile'),
@ -648,7 +655,16 @@ class Router
if (common_config('singleuser', 'enabled')) {
$nickname = common_config('singleuser', 'nickname');
$user = User::siteOwner();
if (!empty($user)) {
$nickname = $user->nickname;
} else {
$nickname = common_config('singleuser', 'nickname');
if (empty($nickname)) {
throw new ServerException(_("No single user defined for single-user mode."));
}
}
foreach (array('subscriptions', 'subscribers',
'all', 'foaf', 'xrds',
@ -696,6 +712,10 @@ class Router
'nickname' => $nickname),
array('tag' => '[a-zA-Z0-9]+'));
$m->connect('rsd.xml',
array('action' => 'rsd',
'nickname' => $nickname));
$m->connect('',
array('action' => 'showstream',
'nickname' => $nickname));
@ -710,6 +730,7 @@ class Router
$m->connect('featured', array('action' => 'featured'));
$m->connect('favorited/', array('action' => 'favorited'));
$m->connect('favorited', array('action' => 'favorited'));
$m->connect('rsd.xml', array('action' => 'rsd'));
foreach (array('subscriptions', 'subscribers',
'nudge', 'all', 'foaf', 'xrds',
@ -757,6 +778,10 @@ class Router
array('nickname' => '[a-zA-Z0-9]{1,64}'),
array('tag' => '[a-zA-Z0-9]+'));
$m->connect(':nickname/rsd.xml',
array('action' => 'rsd'),
array('nickname' => '[a-zA-Z0-9]{1,64}'));
$m->connect(':nickname',
array('action' => 'showstream'),
array('nickname' => '[a-zA-Z0-9]{1,64}'));

View File

@ -75,64 +75,14 @@ class Schema
static function get()
{
$type = common_config('db', 'type');
if (empty(self::$_single)) {
self::$_single = new Schema();
$schemaClass = ucfirst($type).'Schema';
self::$_single = new $schemaClass();
}
return self::$_single;
}
/**
* Returns a TableDef object for the table
* in the schema with the given name.
*
* Throws an exception if the table is not found.
*
* @param string $name Name of the table to get
*
* @return TableDef tabledef for that table.
*/
public function getTableDef($name)
{
$res = $this->conn->query('DESCRIBE ' . $name);
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
$td = new TableDef();
$td->name = $name;
$td->columns = array();
$row = array();
while ($res->fetchInto($row, DB_FETCHMODE_ASSOC)) {
$cd = new ColumnDef();
$cd->name = $row['Field'];
$packed = $row['Type'];
if (preg_match('/^(\w+)\((\d+)\)$/', $packed, $match)) {
$cd->type = $match[1];
$cd->size = $match[2];
} else {
$cd->type = $packed;
}
$cd->nullable = ($row['Null'] == 'YES') ? true : false;
$cd->key = $row['Key'];
$cd->default = $row['Default'];
$cd->extra = $row['Extra'];
$td->columns[] = $cd;
}
return $td;
}
/**
* Gets a ColumnDef object for a single column.
*
@ -523,7 +473,7 @@ class Schema
} else {
$sql .= ($cd->nullable) ? "null " : "not null ";
}
if (!empty($cd->auto_increment)) {
$sql .= " auto_increment ";
}

View File

@ -29,28 +29,36 @@
*/
require_once 'Stomp.php';
require_once 'Stomp/Exception.php';
class StompQueueManager extends QueueManager
{
var $server = null;
var $username = null;
var $password = null;
var $base = null;
var $con = null;
protected $servers;
protected $username;
protected $password;
protected $base;
protected $control;
protected $useTransactions = true;
protected $sites = array();
protected $subscriptions = array();
protected $useTransactions = true;
protected $transaction = null;
protected $transactionCount = 0;
protected $cons = array(); // all open connections
protected $disconnect = array();
protected $transaction = array();
protected $transactionCount = array();
protected $defaultIdx = 0;
function __construct()
{
parent::__construct();
$this->server = common_config('queue', 'stomp_server');
$server = common_config('queue', 'stomp_server');
if (is_array($server)) {
$this->servers = $server;
} else {
$this->servers = array($server);
}
$this->username = common_config('queue', 'stomp_username');
$this->password = common_config('queue', 'stomp_password');
$this->base = common_config('queue', 'queue_basename');
@ -99,9 +107,9 @@ class StompQueueManager extends QueueManager
$message .= ':' . $param;
}
$this->_connect();
$result = $this->con->send($this->control,
$message,
array ('created' => common_sql_now()));
$result = $this->_send($this->control,
$message,
array ('created' => common_sql_now()));
if ($result) {
$this->_log(LOG_INFO, "Sent control ping to queue daemons: $message");
return true;
@ -166,28 +174,59 @@ class StompQueueManager extends QueueManager
/**
* Saves a notice object reference into the queue item table.
* @return boolean true on success
* @throws StompException on connection or send error
*/
public function enqueue($object, $queue)
{
$this->_connect();
return $this->_doEnqueue($object, $queue, $this->defaultIdx);
}
/**
* Saves a notice object reference into the queue item table
* on the given connection.
*
* @return boolean true on success
* @throws StompException on connection or send error
*/
protected function _doEnqueue($object, $queue, $idx)
{
$msg = $this->encode($object);
$rep = $this->logrep($object);
$this->_connect();
$props = array('created' => common_sql_now());
if ($this->isPersistent($queue)) {
$props['persistent'] = 'true';
}
// XXX: serialize and send entire notice
$result = $this->con->send($this->queueName($queue),
$msg, // BODY of the message
array ('created' => common_sql_now(),
'persistent' => 'true'));
$con = $this->cons[$idx];
$host = $con->getServer();
$result = $con->send($this->queueName($queue), $msg, $props);
if (!$result) {
common_log(LOG_ERR, "Error sending $rep to $queue queue");
common_log(LOG_ERR, "Error sending $rep to $queue queue on $host");
return false;
}
common_log(LOG_DEBUG, "complete remote queueing $rep for $queue");
common_log(LOG_DEBUG, "complete remote queueing $rep for $queue on $host");
$this->stats('enqueued', $queue);
return true;
}
/**
* Determine whether messages to this queue should be marked as persistent.
* Actual persistent storage depends on the queue server's configuration.
* @param string $queue
* @return bool
*/
protected function isPersistent($queue)
{
$mode = common_config('queue', 'stomp_persistent');
if (is_array($mode)) {
return in_array($queue, $mode);
} else {
return (bool)$mode;
}
}
/**
@ -198,7 +237,29 @@ class StompQueueManager extends QueueManager
*/
public function getSockets()
{
return array($this->con->getSocket());
$sockets = array();
foreach ($this->cons as $con) {
if ($con) {
$sockets[] = $con->getSocket();
}
}
return $sockets;
}
/**
* Get the Stomp connection object associated with the given socket.
* @param resource $socket
* @return int index into connections list
* @throws Exception
*/
protected function connectionFromSocket($socket)
{
foreach ($this->cons as $i => $con) {
if ($con && $con->getSocket() === $socket) {
return $i;
}
}
throw new Exception(__CLASS__ . " asked to read from unrecognized socket");
}
/**
@ -210,27 +271,56 @@ class StompQueueManager extends QueueManager
*/
public function handleInput($socket)
{
assert($socket === $this->con->getSocket());
$idx = $this->connectionFromSocket($socket);
$con = $this->cons[$idx];
$host = $con->getServer();
$ok = true;
$frames = $this->con->readFrames();
try {
$frames = $con->readFrames();
} catch (StompException $e) {
common_log(LOG_ERR, "Lost connection to $host: " . $e->getMessage());
$this->cons[$idx] = null;
$this->transaction[$idx] = null;
$this->disconnect[$idx] = time();
return false;
}
foreach ($frames as $frame) {
$dest = $frame->headers['destination'];
if ($dest == $this->control) {
if (!$this->handleControlSignal($frame)) {
if (!$this->handleControlSignal($idx, $frame)) {
// We got a control event that requests a shutdown;
// close out and stop handling anything else!
break;
}
} else {
$ok = $ok && $this->handleItem($frame);
$ok = $ok && $this->handleItem($idx, $frame);
}
}
return $ok;
}
/**
* Attempt to reconnect in background if we lost a connection.
*/
function idle()
{
$now = time();
foreach ($this->cons as $idx => $con) {
if (empty($con)) {
$age = $now - $this->disconnect[$idx];
if ($age >= 60) {
$this->_reconnect($idx);
}
}
}
return true;
}
/**
* Initialize our connection and subscribe to all the queues
* we're going to need to handle...
* we're going to need to handle... If multiple queue servers
* are configured for failover, we'll listen to all of them.
*
* Side effects: in multi-site mode, may reset site configuration.
*
@ -240,9 +330,14 @@ class StompQueueManager extends QueueManager
public function start($master)
{
parent::start($master);
$this->_connect();
$this->_connectAll();
$this->con->subscribe($this->control);
common_log(LOG_INFO, "Subscribing to $this->control");
foreach ($this->cons as $con) {
if ($con) {
$con->subscribe($this->control);
}
}
if ($this->sites) {
foreach ($this->sites as $server) {
StatusNet::init($server);
@ -251,10 +346,14 @@ class StompQueueManager extends QueueManager
} else {
$this->doSubscribe();
}
$this->begin();
foreach ($this->cons as $i => $con) {
if ($con) {
$this->begin($i);
}
}
return true;
}
/**
* Subscribe to all the queues we're going to need to handle...
*
@ -266,8 +365,12 @@ 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);
foreach ($this->cons as $i => $con) {
if ($con) {
$this->rollback($i);
$con->unsubscribe($this->control);
}
}
if ($this->sites) {
foreach ($this->sites as $server) {
StatusNet::init($server);
@ -289,23 +392,106 @@ class StompQueueManager extends QueueManager
}
/**
* Lazy open connection to Stomp queue server.
* Lazy open a single connection to Stomp queue server.
* If multiple servers are configured, we let the Stomp client library
* worry about finding a working connection among them.
*/
protected function _connect()
{
if (empty($this->con)) {
$this->_log(LOG_INFO, "Connecting to '$this->server' as '$this->username'...");
$this->con = new LiberalStomp($this->server);
if ($this->con->connect($this->username, $this->password)) {
$this->_log(LOG_INFO, "Connected.");
if (empty($this->cons)) {
$list = $this->servers;
if (count($list) > 1) {
shuffle($list); // Randomize to spread load
$url = 'failover://(' . implode(',', $list) . ')';
} else {
$this->_log(LOG_ERR, 'Failed to connect to queue server');
throw new ServerException('Failed to connect to queue server');
$url = $list[0];
}
$con = $this->_doConnect($url);
$this->cons = array($con);
$this->transactionCount = array(0);
$this->transaction = array(null);
$this->disconnect = array(null);
}
}
/**
* Lazy open connections to all Stomp servers, if in manual failover
* mode. This means the queue servers don't speak to each other, so
* we have to listen to all of them to make sure we get all events.
*/
protected function _connectAll()
{
if (!common_config('queue', 'stomp_manual_failover')) {
return $this->_connect();
}
if (empty($this->cons)) {
$this->cons = array();
$this->transactionCount = array();
$this->transaction = array();
foreach ($this->servers as $idx => $server) {
try {
$this->cons[] = $this->_doConnect($server);
$this->disconnect[] = null;
} catch (Exception $e) {
// s'okay, we'll live
$this->cons[] = null;
$this->disconnect[] = time();
}
$this->transactionCount[] = 0;
$this->transaction[] = null;
}
if (empty($this->cons)) {
throw new ServerException("No queue servers reachable...");
return false;
}
}
}
protected function _reconnect($idx)
{
try {
$con = $this->_doConnect($this->servers[$idx]);
} catch (Exception $e) {
$this->_log(LOG_ERR, $e->getMessage());
$con = null;
}
if ($con) {
$this->cons[$idx] = $con;
$this->disconnect[$idx] = null;
// now we have to listen to everything...
// @fixme refactor this nicer. :P
$host = $con->getServer();
$this->_log(LOG_INFO, "Resubscribing to $this->control on $host");
$con->subscribe($this->control);
foreach ($this->subscriptions as $site => $queues) {
foreach ($queues as $queue) {
$this->_log(LOG_INFO, "Resubscribing to $queue on $host");
$con->subscribe($queue);
}
}
$this->begin($idx);
} else {
// Try again later...
$this->disconnect[$idx] = time();
}
}
protected function _doConnect($server)
{
$this->_log(LOG_INFO, "Connecting to '$server' as '$this->username'...");
$con = new LiberalStomp($server);
if ($con->connect($this->username, $this->password)) {
$this->_log(LOG_INFO, "Connected.");
} else {
$this->_log(LOG_ERR, 'Failed to connect to queue server');
throw new ServerException('Failed to connect to queue server');
}
return $con;
}
/**
* Subscribe to all enabled notice queues for the current site.
*/
@ -317,7 +503,11 @@ class StompQueueManager extends QueueManager
$rawqueue = $this->queueName($queue);
$this->subscriptions[$site][$queue] = $rawqueue;
$this->_log(LOG_INFO, "Subscribing to $rawqueue");
$this->con->subscribe($rawqueue);
foreach ($this->cons as $con) {
if ($con) {
$con->subscribe($rawqueue);
}
}
}
}
@ -331,7 +521,11 @@ class StompQueueManager extends QueueManager
if (!empty($this->subscriptions[$site])) {
foreach ($this->subscriptions[$site] as $queue => $rawqueue) {
$this->_log(LOG_INFO, "Unsubscribing from $rawqueue");
$this->con->unsubscribe($rawqueue);
foreach ($this->cons as $con) {
if ($con) {
$con->unsubscribe($rawqueue);
}
}
unset($this->subscriptions[$site][$queue]);
}
}
@ -346,27 +540,31 @@ class StompQueueManager extends QueueManager
* Side effects: in multi-site mode, may reset site configuration to
* match the site that queued the event.
*
* @param int $idx connection index
* @param StompFrame $frame
* @return bool
*/
protected function handleItem($frame)
protected function handleItem($idx, $frame)
{
$this->defaultIdx = $idx;
list($site, $queue) = $this->parseDestination($frame->headers['destination']);
if ($site != $this->currentSite()) {
$this->stats('switch');
StatusNet::init($site);
}
$host = $this->cons[$idx]->getServer();
if (is_numeric($frame->body)) {
$id = intval($frame->body);
$info = "notice $id posted at {$frame->headers['created']} in queue $queue";
$info = "notice $id posted at {$frame->headers['created']} in queue $queue from $host";
$notice = Notice::staticGet('id', $id);
if (empty($notice)) {
$this->_log(LOG_WARNING, "Skipping missing $info");
$this->ack($frame);
$this->commit();
$this->begin();
$this->ack($idx, $frame);
$this->commit($idx);
$this->begin($idx);
$this->stats('badnotice', $queue);
return false;
}
@ -374,39 +572,47 @@ class StompQueueManager extends QueueManager
$item = $notice;
} else {
// @fixme should we serialize, or json, or what here?
$info = "string posted at {$frame->headers['created']} in queue $queue";
$info = "string posted at {$frame->headers['created']} in queue $queue from $host";
$item = $frame->body;
}
$handler = $this->getHandler($queue);
if (!$handler) {
$this->_log(LOG_ERR, "Missing handler class; skipping $info");
$this->ack($frame);
$this->commit();
$this->begin();
$this->ack($idx, $frame);
$this->commit($idx);
$this->begin($idx);
$this->stats('badhandler', $queue);
return false;
}
$ok = $handler->handle($item);
// If there's an exception when handling,
// log the error and let it get requeued.
try {
$ok = $handler->handle($item);
} catch (Exception $e) {
$this->_log(LOG_ERR, "Exception on queue $queue: " . $e->getMessage());
$ok = false;
}
if (!$ok) {
$this->_log(LOG_WARNING, "Failed handling $info");
// FIXME we probably shouldn't have to do
// this kind of queue management ourselves;
// if we don't ack, it should resend...
$this->ack($frame);
$this->ack($idx, $frame);
$this->enqueue($item, $queue);
$this->commit();
$this->begin();
$this->commit($idx);
$this->begin($idx);
$this->stats('requeued', $queue);
return false;
}
$this->_log(LOG_INFO, "Successfully handled $info");
$this->ack($frame);
$this->commit();
$this->begin();
$this->ack($idx, $frame);
$this->commit($idx);
$this->begin($idx);
$this->stats('handled', $queue);
return true;
}
@ -414,10 +620,11 @@ class StompQueueManager extends QueueManager
/**
* Process a control signal broadcast.
*
* @param int $idx connection index
* @param array $frame Stomp frame
* @return bool true to continue; false to stop further processing.
*/
protected function handleControlSignal($frame)
protected function handleControlSignal($idx, $frame)
{
$message = trim($frame->body);
if (strpos($message, ':') !== false) {
@ -441,12 +648,12 @@ class StompQueueManager extends QueueManager
$this->_log(LOG_ERR, "Ignoring unrecognized control message: $message");
}
$this->ack($frame);
$this->commit();
$this->begin();
$this->ack($idx, $frame);
$this->commit($idx);
$this->begin($idx);
return $shutdown;
}
/**
* Set us up with queue subscriptions for a new site added at runtime,
* triggered by a broadcast to the 'statusnet-control' topic.
@ -520,47 +727,49 @@ class StompQueueManager extends QueueManager
common_log($level, 'StompQueueManager: '.$msg);
}
protected function begin()
protected function begin($idx)
{
if ($this->useTransactions) {
if ($this->transaction) {
if (!empty($this->transaction[$idx])) {
throw new Exception("Tried to start transaction in the middle of a transaction");
}
$this->transactionCount++;
$this->transaction = $this->master->id . '-' . $this->transactionCount . '-' . time();
$this->con->begin($this->transaction);
$this->transactionCount[$idx]++;
$this->transaction[$idx] = $this->master->id . '-' . $this->transactionCount[$idx] . '-' . time();
$this->cons[$idx]->begin($this->transaction[$idx]);
}
}
protected function ack($frame)
protected function ack($idx, $frame)
{
if ($this->useTransactions) {
if (!$this->transaction) {
if (empty($this->transaction[$idx])) {
throw new Exception("Tried to ack but not in a transaction");
}
$this->cons[$idx]->ack($frame, $this->transaction[$idx]);
} else {
$this->cons[$idx]->ack($frame);
}
$this->con->ack($frame, $this->transaction);
}
protected function commit()
protected function commit($idx)
{
if ($this->useTransactions) {
if (!$this->transaction) {
if (empty($this->transaction[$idx])) {
throw new Exception("Tried to commit but not in a transaction");
}
$this->con->commit($this->transaction);
$this->transaction = null;
$this->cons[$idx]->commit($this->transaction[$idx]);
$this->transaction[$idx] = null;
}
}
protected function rollback()
protected function rollback($idx)
{
if ($this->useTransactions) {
if (!$this->transaction) {
if (empty($this->transaction[$idx])) {
throw new Exception("Tried to rollback but not in a transaction");
}
$this->con->commit($this->transaction);
$this->transaction = null;
$this->cons[$idx]->commit($this->transaction[$idx]);
$this->transaction[$idx] = null;
}
}
}

View File

@ -178,7 +178,6 @@ function common_ensure_session()
}
if (isset($id)) {
session_id($id);
setcookie(session_name(), $id);
}
@session_start();
if (!isset($_SESSION['started'])) {
@ -990,10 +989,16 @@ function common_enqueue_notice($notice)
static $localTransports = array('omb',
'ping');
static $allTransports = array('sms', 'plugin');
$transports = $allTransports;
$transports = array();
if (common_config('sms', 'enabled')) {
$transports[] = 'sms';
}
if (Event::hasHandler('HandleQueuedNotice')) {
$transports[] = 'plugin';
}
// @fixme move these checks into QueueManager and/or individual handlers
if ($notice->is_local == Notice::LOCAL_PUBLIC ||
$notice->is_local == Notice::LOCAL_NONPUBLIC) {
$transports = array_merge($transports, $localTransports);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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);
}
}

View File

@ -71,7 +71,7 @@ class GeonamesPlugin extends Plugin
$loc = $this->getCache(array('name' => $name,
'language' => $language));
if (!empty($loc)) {
if ($loc !== false) {
$location = $loc;
return false;
}
@ -87,12 +87,20 @@ class GeonamesPlugin extends Plugin
return true;
}
if (count($geonames) == 0) {
// no results
$this->setCache(array('name' => $name,
'language' => $language),
null);
return true;
}
$n = $geonames[0];
$location = new Location();
$location->lat = (string)$n->lat;
$location->lon = (string)$n->lng;
$location->lat = $this->canonical($n->lat);
$location->lon = $this->canonical($n->lng);
$location->names[$language] = (string)$n->name;
$location->location_id = (string)$n->geonameId;
$location->location_ns = self::LOCATION_NS;
@ -125,7 +133,7 @@ class GeonamesPlugin extends Plugin
$loc = $this->getCache(array('id' => $id));
if (!empty($loc)) {
if ($loc !== false) {
$location = $loc;
return false;
}
@ -157,8 +165,9 @@ class GeonamesPlugin extends Plugin
$location->location_id = (string)$last->geonameId;
$location->location_ns = self::LOCATION_NS;
$location->lat = (string)$last->lat;
$location->lon = (string)$last->lng;
$location->lat = $this->canonical($last->lat);
$location->lon = $this->canonical($last->lng);
$location->names[$language] = implode(', ', array_reverse($parts));
$this->setCache(array('id' => (string)$last->geonameId),
@ -186,13 +195,15 @@ class GeonamesPlugin extends Plugin
function onLocationFromLatLon($lat, $lon, $language, &$location)
{
$lat = rtrim($lat, "0");
$lon = rtrim($lon, "0");
// Make sure they're canonical
$lat = $this->canonical($lat);
$lon = $this->canonical($lon);
$loc = $this->getCache(array('lat' => $lat,
'lon' => $lon));
if (!empty($loc)) {
if ($loc !== false) {
$location = $loc;
return false;
}
@ -207,6 +218,14 @@ class GeonamesPlugin extends Plugin
return true;
}
if (count($geonames) == 0) {
// no results
$this->setCache(array('lat' => $lat,
'lon' => $lon),
null);
return true;
}
$n = $geonames[0];
$parts = array();
@ -225,8 +244,8 @@ class GeonamesPlugin extends Plugin
$location->location_id = (string)$n->geonameId;
$location->location_ns = self::LOCATION_NS;
$location->lat = (string)$lat;
$location->lon = (string)$lon;
$location->lat = $this->canonical($n->lat);
$location->lon = $this->canonical($n->lng);
$location->names[$language] = implode(', ', $parts);
@ -264,7 +283,7 @@ class GeonamesPlugin extends Plugin
$n = $this->getCache(array('id' => $id,
'language' => $language));
if (!empty($n)) {
if ($n !== false) {
$name = $n;
return false;
}
@ -278,6 +297,13 @@ class GeonamesPlugin extends Plugin
return false;
}
if (count($geonames) == 0) {
$this->setCache(array('id' => $id,
'language' => $language),
null);
return false;
}
$parts = array();
foreach ($geonames as $level) {
@ -412,17 +438,29 @@ class GeonamesPlugin extends Plugin
throw new Exception("HTTP error code " . $result->code);
}
$document = new SimpleXMLElement($result->getBody());
$body = $result->getBody();
if (empty($document)) {
throw new Exception("No results in response");
if (empty($body)) {
throw new Exception("Empty HTTP body in response");
}
// This will throw an exception if the XML is mal-formed
$document = new SimpleXMLElement($body);
// No children, usually no results
$children = $document->children();
if (count($children) == 0) {
return array();
}
if (isset($document->status)) {
throw new Exception("Error #".$document->status['value']." ('".$document->status['message']."')");
}
// Array of elements
// Array of elements, >0 elements
return $document->geoname;
}
@ -438,4 +476,12 @@ class GeonamesPlugin extends Plugin
'names for locations based on user-provided lat/long pairs.'));
return true;
}
function canonical($coord)
{
$coord = rtrim($coord, "0");
$coord = rtrim($coord, ".");
return $coord;
}
}

View File

@ -96,8 +96,11 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin
}
}
function autoRegister($username)
function autoRegister($username, $nickname)
{
if(is_null($nickname)){
$nickname = $username;
}
$entry = $this->ldap_get_user($username,$this->attributes);
if($entry){
$registration_data = array();
@ -107,6 +110,7 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin
if(isset($registration_data['email']) && !empty($registration_data['email'])){
$registration_data['email_confirmed']=true;
}
$registration_data['nickname'] = $nickname;
//set the database saved password to a random string.
$registration_data['password']=common_good_rand(16);
return User::register($registration_data);

View File

@ -59,6 +59,8 @@ class MemcachePlugin extends Plugin
public $persistent = null;
public $defaultExpiry = 86400; // 24h
/**
* Initialize the plugin
*
@ -110,6 +112,9 @@ class MemcachePlugin extends Plugin
function onStartCacheSet(&$key, &$value, &$flag, &$expiry, &$success)
{
$this->_ensureConn();
if ($expiry === null) {
$expiry = $this->defaultExpiry;
}
$success = $this->_conn->set($key, $value, $flag, $expiry);
Event::handle('EndCacheSet', array($key, $value, $flag,
$expiry));

View File

@ -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;
}
}

View File

@ -87,7 +87,7 @@ class RealtimePlugin extends Plugin
$scripts = $this->_getScripts();
foreach ($scripts as $script) {
$action->script(common_path($script));
$action->script($script);
}
$user = common_current_user();
@ -307,7 +307,7 @@ class RealtimePlugin extends Plugin
function _getScripts()
{
return array('plugins/Realtime/realtimeupdate.js');
return array(common_path('plugins/Realtime/realtimeupdate.js'));
}
function _updateInitialize($timeline, $user_id)

View File

@ -95,9 +95,7 @@ RealtimeUpdate = {
$("#notices_primary .notice:first").css({display:"none"});
$("#notices_primary .notice:first").fadeIn(1000);
SN.U.FormXHR($('#'+noticeItemID+' .form_favor'));
SN.U.NoticeReplyTo($('#'+noticeItemID));
SN.U.FormXHR($('#'+noticeItemID+' .form_repeat'));
SN.U.NoticeWithAttachment($('#'+noticeItemID));
},
@ -136,7 +134,7 @@ RealtimeUpdate = {
ni = "<li class=\"hentry notice\" id=\"notice-"+unique+"\">"+
"<div class=\"entry-title\">"+
"<span class=\"vcard author\">"+
"<a href=\""+user['profile_url']+"\" class=\"url\">"+
"<a href=\""+user['profile_url']+"\" class=\"url\" title=\""+user['name']+"\">"+
"<img src=\""+user['profile_image_url']+"\" class=\"avatar photo\" width=\"48\" height=\"48\" alt=\""+user['screen_name']+"\"/>"+
"<span class=\"nickname fn\">"+user['screen_name']+"</span>"+
"</a>"+
@ -180,7 +178,7 @@ RealtimeUpdate = {
ni = ni+"</div>";
"</li>";
ni = ni+"</li>";
return ni;
},

View File

@ -47,10 +47,13 @@ class ReverseUsernameAuthenticationPlugin extends AuthenticationPlugin
return $username == strrev($password);
}
function autoRegister($username)
function autoRegister($username, $nickname)
{
if(is_null($nickname)){
$nickname = $username;
}
$registration_data = array();
$registration_data['nickname'] = $username ;
$registration_data['nickname'] = $nickname ;
return User::register($registration_data);
}

View File

@ -182,21 +182,6 @@ class UserFlagPlugin extends Plugin
return true;
}
/**
* Add our plugin's CSS to page output
*
* @param Action $action action being shown
*
* @return boolean hook result
*/
function onEndShowStatusNetStyles($action)
{
$action->cssLink(common_path('plugins/UserFlag/userflag.css'),
null, 'screen, projection, tv');
return true;
}
/**
* Initialize any flagging buttons on the page
*
@ -208,8 +193,8 @@ class UserFlagPlugin extends Plugin
function onEndShowScripts($action)
{
$action->inlineScript('if ($(".form_entity_flag").length > 0) { '.
'SN.U.FormXHR($(".form_entity_flag")); '.
'}');
'$(".form_entity_flag").bind("click", function() {'.
'SN.U.FormXHR($(this)); return false; }); }');
return true;
}

View File

@ -54,7 +54,7 @@ class ClearFlagForm extends ProfileActionForm
function formClass()
{
return 'form_entity_clearflag';
return 'form_user_clearflag';
}
/**

View File

@ -1,4 +0,0 @@
.entity_flag input.submit,
.entity_flag p {
background:url(icon_flag.gif) 5px 5px no-repeat;
}

View File

@ -0,0 +1,92 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Plugin to limit number of users that can register (best for cloud providers)
*
* 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 Evan Prodromou <evan@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')) {
exit(1);
}
/**
* Plugin to limit number of users that can register (best for cloud providers)
*
* For cloud providers whose freemium model is based on how many
* users can register. We use it on the StatusNet Cloud.
*
* @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 UserLimitPlugin extends Plugin
{
public $maxUsers = null;
function onStartUserRegister(&$user, &$profile)
{
$this->_checkMaxUsers();
return true;
}
function onStartRegistrationTry($action)
{
$this->_checkMaxUsers();
return true;
}
function _checkMaxUsers()
{
if (!is_null($this->maxUsers)) {
$cls = new User();
$cnt = $cls->count();
if ($cnt >= $this->maxUsers) {
$msg = sprintf(_('Cannot register; maximum number of users (%d) reached.'),
$this->maxUsers);
throw new ClientException($msg);
}
}
}
function onPluginVersion(&$versions)
{
$versions[] = array('name' => 'UserLimit',
'version' => STATUSNET_VERSION,
'author' => 'Evan Prodromou',
'homepage' => 'http://status.net/wiki/Plugin:UserLimit',
'description' =>
_m('Limit the number of users who can register.'));
return true;
}
}

Some files were not shown because too many files have changed in this diff Show More