Merge branch 'testing' into 0.9.x

Conflicts:
	actions/apioauthauthorize.php
This commit is contained in:
Evan Prodromou 2010-01-31 15:27:58 -05:00
commit 779204b194
47 changed files with 2864 additions and 350 deletions

View File

@ -699,3 +699,18 @@ StartShowContentLicense: Showing the default license for content
EndShowContentLicense: Showing the default license for content EndShowContentLicense: Showing the default license for content
- $action: the current action - $action: the current action
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

51
README
View File

@ -2,8 +2,8 @@
README README
------ ------
StatusNet 0.9.0 ("Stand") Beta 3 StatusNet 0.9.0 ("Stand") Beta 4
20 Jan 2010 27 Jan 2010
This is the README file for StatusNet (formerly Laconica), the Open This is the README file for StatusNet (formerly Laconica), the Open
Source microblogging platform. It includes installation instructions, Source microblogging platform. It includes installation instructions,
@ -597,26 +597,19 @@ server is probably a good idea for high-volume sites.
needs as a parameter the install path; if you run it from the needs as a parameter the install path; if you run it from the
StatusNet dir, "." should suffice. 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 * xmppdaemon.php - listens for new XMPP messages from users and stores
them as notices in the database. them as notices in the database; also pulls queued XMPP output from
* jabberqueuehandler.php - sends queued notices in the database to queuedaemon.php to push out to clients.
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.
Note that these queue daemons are pretty raw, and need your care. In These two daemons will automatically restart in most cases of failure
particular, they leak memory, and you may want to restart them on a including memory leaks (if a memory_limit is set), but may still die
regular (daily or so) basis with a cron job. Also, if they lose or behave oddly if they lose connections to the XMPP or queue servers.
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', It may be a good idea to use a daemon-monitoring service, like 'monit',
to check their status and keep them running. to check their status and keep them running.
All the daemons write their process IDs (pids) to /var/run/ by All the daemons write their process IDs (pids) to /var/run/ by
@ -626,7 +619,7 @@ daemons.
Since version 0.8.0, it's now possible to use a STOMP server instead of 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" 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 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 Sitemaps
-------- --------
@ -712,10 +705,12 @@ subdirectory to add a new language to your system. You'll need to
compile the ".po" files into ".mo" files, however. compile the ".po" files into ".mo" files, however.
Contributions of translation information to StatusNet are very easy: 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 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. 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 Backups
------- -------
@ -1501,6 +1496,20 @@ interface. It also makes the user's profile the root URL.
enabled: Whether to run in "single user mode". Default false. enabled: Whether to run in "single user mode". Default false.
nickname: nickname of the single user. 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 Plugins
======= =======

View File

@ -303,8 +303,9 @@ class ApiOauthAuthorizeAction extends ApiOauthAction
$access = ($this->app->access_type & Oauth_application::$writeAccess) ? $access = ($this->app->access_type & Oauth_application::$writeAccess) ?
'access and update' : 'access'; 'access and update' : 'access';
$msg = _("The application <strong>%1$s</strong> by <strong>%2$s</strong> would like " . $msg = _('The application <strong>%1$s</strong> by ' .
"the ability to <strong>%3$s</strong> your account data."); '<strong>%2$s</strong> would like the ability ' .
'to <strong>%3$s</strong> your account data.');
$this->raw(sprintf($msg, $this->raw(sprintf($msg,
$this->app->name, $this->app->name,

View File

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

View File

@ -131,12 +131,20 @@ class PublicAction extends Action
return _('Public timeline'); return _('Public timeline');
} }
} }
function extraHead() function extraHead()
{ {
parent::extraHead(); parent::extraHead();
$this->element('meta', array('http-equiv' => 'X-XRDS-Location', $this->element('meta', array('http-equiv' => 'X-XRDS-Location',
'content' => common_local_url('publicxrds'))); '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

@ -178,6 +178,15 @@ class ShowstreamAction extends ProfileAction
$this->element('link', array('rel' => 'microsummary', $this->element('link', array('rel' => 'microsummary',
'href' => common_local_url('microsummary', 'href' => common_local_url('microsummary',
array('nickname' => $this->profile->nickname)))); 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() function showProfile()

View File

@ -147,6 +147,7 @@ class Memcached_DataObject extends DB_DataObject
{ {
$result = parent::insert(); $result = parent::insert();
if ($result) { if ($result) {
$this->fixupTimestamps();
$this->encache(); // in case of cached negative lookups $this->encache(); // in case of cached negative lookups
} }
return $result; return $result;
@ -159,6 +160,7 @@ class Memcached_DataObject extends DB_DataObject
} }
$result = parent::update($orig); $result = parent::update($orig);
if ($result) { if ($result) {
$this->fixupTimestamps();
$this->encache(); $this->encache();
} }
return $result; 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() * - internal functions use this rather than $this->query()
* *
* Overridden to do logging. * 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 // WARNING WARNING if we end up actually using multiple DBs at a time
// we'll need some fancier logic here. // 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) { foreach ($_DB_DATAOBJECT['CONNECTIONS'] as $index => $conn) {
if (!empty($conn)) { if (!empty($conn)) {
$conn->disconnect(); $conn->disconnect();
@ -529,4 +531,51 @@ class Memcached_DataObject extends DB_DataObject
return $c->delete($cacheKey); 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) { foreach(array_unique($hashtags) as $hashtag) {
/* elide characters we don't want in the tag */ /* elide characters we don't want in the tag */
$this->saveTag($hashtag); $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; return true;
} }
@ -326,9 +326,7 @@ class Notice extends Memcached_DataObject
# XXX: someone clever could prepend instead of clearing the cache # XXX: someone clever could prepend instead of clearing the cache
$notice->blowOnInsert(); $notice->blowOnInsert();
$qm = QueueManager::get(); $notice->distribute();
$qm->enqueue($notice, 'distrib');
return $notice; return $notice;
} }
@ -1374,8 +1372,6 @@ class Notice extends Memcached_DataObject
} }
$reply->free(); $reply->free();
return $ids;
} }
function clearRepeats() function clearRepeats()
@ -1445,4 +1441,31 @@ class Notice extends Memcached_DataObject
$gi->free(); $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

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

View File

@ -64,8 +64,12 @@ class Session extends Memcached_DataObject
$session = Session::staticGet('id', $id); $session = Session::staticGet('id', $id);
if (empty($session)) { if (empty($session)) {
self::logdeb("Couldn't find '$id'");
return ''; return '';
} else { } else {
self::logdeb("Found '$id', returning " .
strlen($session->session_data) .
" chars of data");
return (string)$session->session_data; return (string)$session->session_data;
} }
} }
@ -77,14 +81,24 @@ class Session extends Memcached_DataObject
$session = Session::staticGet('id', $id); $session = Session::staticGet('id', $id);
if (empty($session)) { if (empty($session)) {
self::logdeb("'$id' doesn't yet exist; inserting.");
$session = new Session(); $session = new Session();
$session->id = $id; $session->id = $id;
$session->session_data = $session_data; $session->session_data = $session_data;
$session->created = common_sql_now(); $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 { } else {
self::logdeb("'$id' already exists; updating.");
if (strcmp($session->session_data, $session_data) == 0) { if (strcmp($session->session_data, $session_data) == 0) {
self::logdeb("Not writing session '$id'; unchanged"); self::logdeb("Not writing session '$id'; unchanged");
return true; return true;
@ -95,7 +109,16 @@ class Session extends Memcached_DataObject
$session->session_data = $session_data; $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); $session = Session::staticGet('id', $id);
if (!empty($session)) { if (empty($session)) {
return $session->delete(); 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(); $session->free();
self::logdeb("Found " . count($ids) . " ids to delete.");
foreach ($ids as $id) { foreach ($ids as $id) {
self::logdeb("Destroying session '$id'.");
self::destroy($id); self::destroy($id);
} }
} }

View File

@ -209,8 +209,6 @@ class User extends Memcached_DataObject
$profile = new Profile(); $profile = new Profile();
$profile->query('BEGIN');
if(!empty($email)) if(!empty($email))
{ {
$email = common_canonical_email($email); $email = common_canonical_email($email);
@ -220,7 +218,7 @@ class User extends Memcached_DataObject
$profile->nickname = $nickname; $profile->nickname = $nickname;
if(! User::allowed_nickname($nickname)){ if(! User::allowed_nickname($nickname)){
common_log(LOG_WARNING, sprintf("Attempted to register a nickname that is not allowed: %s", $profile->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); $profile->profileurl = common_profile_url($nickname);
@ -248,16 +246,8 @@ class User extends Memcached_DataObject
$profile->created = common_sql_now(); $profile->created = common_sql_now();
$id = $profile->insert();
if (empty($id)) {
common_log_db_error($profile, 'INSERT', __FILE__);
return false;
}
$user = new User(); $user = new User();
$user->id = $id;
$user->nickname = $nickname; $user->nickname = $nickname;
if (!empty($password)) { // may not have a password for OpenID users if (!empty($password)) { // may not have a password for OpenID users
@ -282,109 +272,126 @@ class User extends Memcached_DataObject
$user->inboxed = 1; $user->inboxed = 1;
$user->created = common_sql_now(); $user->created = common_sql_now();
$user->uri = common_user_uri($user);
$result = $user->insert(); if (Event::handle('StartUserRegister', array(&$user, &$profile))) {
if (!$result) { $profile->query('BEGIN');
common_log_db_error($user, 'INSERT', __FILE__);
return false;
}
// Everyone gets an inbox $id = $profile->insert();
$inbox = new Inbox(); if (empty($id)) {
common_log_db_error($profile, 'INSERT', __FILE__);
$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__);
return false; return false;
} }
}
if (!empty($code) && $user->email) { $user->id = $id;
$user->emailChanged(); $user->uri = common_user_uri($user);
}
// 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)) { // Everyone gets an inbox
$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(); $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) { if (!$result) {
common_log_db_error($defsub, 'INSERT', __FILE__); common_log_db_error($confirm, 'INSERT', __FILE__);
return false; 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; return $user;
@ -925,4 +932,30 @@ class User extends Memcached_DataObject
return $share; 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(0, 1);
if ($pr->fetch($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 logo = 2
created = 142 created = 142
modified = 384 modified = 384
tags = 34
[status_network__keys] [status_network__keys]
nickname = K nickname = K

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 queue_item_new rename to queue_item;
alter table consumer 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 verifier varchar(255) comment 'verifier string for OAuth 1.0a',
add verified_callback varchar(255) comment 'verified callback URL for OAuth 1.0a'; add verified_callback varchar(255) comment 'verified callback URL for OAuth 1.0a';

View File

@ -146,12 +146,27 @@ function formatBacktraceLine($n, $line)
return $out; return $out;
} }
function checkMirror($action_obj, $args) function setupRW()
{ {
global $config; global $config;
static $alwaysRW = array('session', 'remember_me'); 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 (common_config('db', 'mirror') && $action_obj->isReadOnly($args)) {
if (is_array(common_config('db', 'mirror'))) { if (is_array(common_config('db', 'mirror'))) {
// "load balancing", ha ha // "load balancing", ha ha
@ -162,16 +177,6 @@ function checkMirror($action_obj, $args)
$mirror = common_config('db', 'mirror'); $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 // everyone else uses the mirror
$config['db']['database'] = $mirror; $config['db']['database'] = $mirror;
@ -237,9 +242,13 @@ function main()
PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError'); PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError');
// Make sure RW database is setup
setupRW();
// XXX: we need a little more structure in this script // 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(); $user = common_current_user();
@ -276,8 +285,9 @@ function main()
if (!$user && common_config('site', 'private') if (!$user && common_config('site', 'private')
&& !isLoginAction($action) && !isLoginAction($action)
&& !preg_match('/rss$/', $action) && !preg_match('/rss$/', $action)
&& !preg_match('/^Api/', $action) && $action != 'robotstxt'
) { && !preg_match('/^Api/', $action)) {
// set returnto // set returnto
$rargs =& common_copy_args($args); $rargs =& common_copy_args($args);
unset($rargs['action']); unset($rargs['action']);

View File

@ -299,7 +299,7 @@ class ApiAction extends Action
} }
} }
if ($include_user) { if ($include_user && $profile) {
# Don't get notice (recursive!) # Don't get notice (recursive!)
$twitter_user = $this->twitterUserArray($profile, false); $twitter_user = $this->twitterUserArray($profile, false);
$twitter_status['user'] = $twitter_user; $twitter_status['user'] = $twitter_user;

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 //exit with 200 response, if this is checking fancy from the installer
if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; } if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; }
define('STATUSNET_VERSION', '0.9.0beta3'); define('STATUSNET_VERSION', '0.9.0beta4');
define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility
define('STATUSNET_CODENAME', 'Stand'); define('STATUSNET_CODENAME', 'Stand');
@ -115,6 +115,10 @@ function __autoload($cls)
require_once 'Validate.php'; require_once 'Validate.php';
require_once 'markdown.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/util.php';
require_once INSTALLDIR.'/lib/action.php'; require_once INSTALLDIR.'/lib/action.php';
require_once INSTALLDIR.'/lib/mail.php'; require_once INSTALLDIR.'/lib/mail.php';
@ -136,6 +140,3 @@ try {
exit; 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 'control_channel' => '/topic/statusnet-control', // broadcasts to all queue daemons
'stomp_username' => null, 'stomp_username' => null,
'stomp_password' => 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) '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 '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 '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' => 'license' =>
array('type' => 'cc', # can be 'cc', 'allrightsreserved', 'private' array('type' => 'cc', # can be 'cc', 'allrightsreserved', 'private'
@ -269,4 +272,8 @@ $default =
'singleuser' => 'singleuser' =>
array('enabled' => false, array('enabled' => false,
'nickname' => null), '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? // 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 try {
common_enqueue_notice($notice);
common_enqueue_notice($notice); } catch (Exception $e) {
$this->logit($notice, $e);
}
return true; 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

@ -358,7 +358,7 @@ function jabber_broadcast_notice($notice)
common_log(LOG_WARNING, 'Refusing to broadcast notice with ' . common_log(LOG_WARNING, 'Refusing to broadcast notice with ' .
'unknown profile ' . common_log_objstring($notice), 'unknown profile ' . common_log_objstring($notice),
__FILE__); __FILE__);
return false; return true; // not recoverable; discard.
} }
$msg = jabber_format_notice($profile, $notice); $msg = jabber_format_notice($profile, $notice);
@ -437,7 +437,7 @@ function jabber_public_notice($notice)
common_log(LOG_WARNING, 'Refusing to broadcast notice with ' . common_log(LOG_WARNING, 'Refusing to broadcast notice with ' .
'unknown profile ' . common_log_objstring($notice), 'unknown profile ' . common_log_objstring($notice),
__FILE__); __FILE__);
return false; return true; // not recoverable; discard.
} }
$msg = jabber_format_notice($profile, $notice); $msg = jabber_format_notice($profile, $notice);

View File

@ -40,7 +40,7 @@ class JabberQueueHandler extends QueueHandler
try { try {
return jabber_broadcast_notice($notice); return jabber_broadcast_notice($notice);
} catch (XMPPHP_Exception $e) { } catch (XMPPHP_Exception $e) {
$this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
return false; return false;
} }
} }

View File

@ -33,6 +33,22 @@ class LiberalStomp extends Stomp
return $this->_socket; 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 * Make socket connection to the server
* We also set the stream to non-blocking mode, since we'll be * 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... // @fixme this sometimes hangs in blocking mode...
// shouldn't we have been idle until we found there's more data? // shouldn't we have been idle until we found there's more data?
$read = fread($this->_socket, $rb); $read = fread($this->_socket, $rb);
if ($read === false) { if ($read === false || ($read === '' && feof($this->_socket))) {
$this->_reconnect(); // @fixme possibly attempt an auto reconnect as old code?
throw new StompException("Error reading");
//$this->_reconnect();
// @fixme this will lose prior items // @fixme this will lose prior items
return $this->readFrames(); //return $this->readFrames();
} }
$data .= $read; $data .= $read;
if (strpos($data, "\x00") !== false) { 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

@ -39,7 +39,7 @@ class OmbQueueHandler extends QueueHandler
function handle($notice) function handle($notice)
{ {
if ($this->is_remote($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; return true;
} else { } else {
require_once(INSTALLDIR.'/lib/omb.php'); 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

@ -38,7 +38,7 @@ class PublicQueueHandler extends QueueHandler
try { try {
return jabber_public_notice($notice); return jabber_public_notice($notice);
} catch (XMPPHP_Exception $e) { } catch (XMPPHP_Exception $e) {
$this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
return false; return false;
} }
} }

View File

@ -73,6 +73,8 @@ class Router
if (Event::handle('StartInitializeRouter', array(&$m))) { if (Event::handle('StartInitializeRouter', array(&$m))) {
$m->connect('robots.txt', array('action' => 'robotstxt'));
$m->connect('opensearch/people', array('action' => 'opensearch', $m->connect('opensearch/people', array('action' => 'opensearch',
'type' => 'people')); 'type' => 'people'));
$m->connect('opensearch/notice', array('action' => 'opensearch', $m->connect('opensearch/notice', array('action' => 'opensearch',
@ -649,7 +651,16 @@ class Router
if (common_config('singleuser', 'enabled')) { 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', foreach (array('subscriptions', 'subscribers',
'all', 'foaf', 'xrds', 'all', 'foaf', 'xrds',
@ -697,6 +708,10 @@ class Router
'nickname' => $nickname), 'nickname' => $nickname),
array('tag' => '[a-zA-Z0-9]+')); array('tag' => '[a-zA-Z0-9]+'));
$m->connect('rsd.xml',
array('action' => 'rsd',
'nickname' => $nickname));
$m->connect('', $m->connect('',
array('action' => 'showstream', array('action' => 'showstream',
'nickname' => $nickname)); 'nickname' => $nickname));
@ -711,6 +726,7 @@ class Router
$m->connect('featured', array('action' => 'featured')); $m->connect('featured', array('action' => 'featured'));
$m->connect('favorited/', array('action' => 'favorited')); $m->connect('favorited/', array('action' => 'favorited'));
$m->connect('favorited', array('action' => 'favorited')); $m->connect('favorited', array('action' => 'favorited'));
$m->connect('rsd.xml', array('action' => 'rsd'));
foreach (array('subscriptions', 'subscribers', foreach (array('subscriptions', 'subscribers',
'nudge', 'all', 'foaf', 'xrds', 'nudge', 'all', 'foaf', 'xrds',
@ -758,6 +774,10 @@ class Router
array('nickname' => '[a-zA-Z0-9]{1,64}'), array('nickname' => '[a-zA-Z0-9]{1,64}'),
array('tag' => '[a-zA-Z0-9]+')); 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', $m->connect(':nickname',
array('action' => 'showstream'), array('action' => 'showstream'),
array('nickname' => '[a-zA-Z0-9]{1,64}')); array('nickname' => '[a-zA-Z0-9]{1,64}'));

View File

@ -75,64 +75,14 @@ class Schema
static function get() static function get()
{ {
$type = common_config('db', 'type');
if (empty(self::$_single)) { if (empty(self::$_single)) {
self::$_single = new Schema(); $schemaClass = ucfirst($type).'Schema';
self::$_single = new $schemaClass();
} }
return self::$_single; 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. * Gets a ColumnDef object for a single column.
* *
@ -523,7 +473,7 @@ class Schema
} else { } else {
$sql .= ($cd->nullable) ? "null " : "not null "; $sql .= ($cd->nullable) ? "null " : "not null ";
} }
if (!empty($cd->auto_increment)) { if (!empty($cd->auto_increment)) {
$sql .= " auto_increment "; $sql .= " auto_increment ";
} }

View File

@ -29,28 +29,36 @@
*/ */
require_once 'Stomp.php'; require_once 'Stomp.php';
require_once 'Stomp/Exception.php';
class StompQueueManager extends QueueManager class StompQueueManager extends QueueManager
{ {
var $server = null; protected $servers;
var $username = null; protected $username;
var $password = null; protected $password;
var $base = null; protected $base;
var $con = null;
protected $control; protected $control;
protected $useTransactions = true;
protected $sites = array(); protected $sites = array();
protected $subscriptions = array(); protected $subscriptions = array();
protected $useTransactions = true; protected $cons = array(); // all open connections
protected $transaction = null; protected $disconnect = array();
protected $transactionCount = 0; protected $transaction = array();
protected $transactionCount = array();
protected $defaultIdx = 0;
function __construct() function __construct()
{ {
parent::__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->username = common_config('queue', 'stomp_username');
$this->password = common_config('queue', 'stomp_password'); $this->password = common_config('queue', 'stomp_password');
$this->base = common_config('queue', 'queue_basename'); $this->base = common_config('queue', 'queue_basename');
@ -99,9 +107,9 @@ class StompQueueManager extends QueueManager
$message .= ':' . $param; $message .= ':' . $param;
} }
$this->_connect(); $this->_connect();
$result = $this->con->send($this->control, $result = $this->_send($this->control,
$message, $message,
array ('created' => common_sql_now())); array ('created' => common_sql_now()));
if ($result) { if ($result) {
$this->_log(LOG_INFO, "Sent control ping to queue daemons: $message"); $this->_log(LOG_INFO, "Sent control ping to queue daemons: $message");
return true; return true;
@ -166,28 +174,59 @@ class StompQueueManager extends QueueManager
/** /**
* Saves a notice object reference into the queue item table. * Saves a notice object reference into the queue item table.
* @return boolean true on success * @return boolean true on success
* @throws StompException on connection or send error
*/ */
public function enqueue($object, $queue) 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); $msg = $this->encode($object);
$rep = $this->logrep($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 $con = $this->cons[$idx];
$host = $con->getServer();
$result = $this->con->send($this->queueName($queue), $result = $con->send($this->queueName($queue), $msg, $props);
$msg, // BODY of the message
array ('created' => common_sql_now(),
'persistent' => 'true'));
if (!$result) { 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; 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); $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() 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) public function handleInput($socket)
{ {
assert($socket === $this->con->getSocket()); $idx = $this->connectionFromSocket($socket);
$con = $this->cons[$idx];
$host = $con->getServer();
$ok = true; $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) { foreach ($frames as $frame) {
$dest = $frame->headers['destination']; $dest = $frame->headers['destination'];
if ($dest == $this->control) { if ($dest == $this->control) {
if (!$this->handleControlSignal($frame)) { if (!$this->handleControlSignal($idx, $frame)) {
// We got a control event that requests a shutdown; // We got a control event that requests a shutdown;
// close out and stop handling anything else! // close out and stop handling anything else!
break; break;
} }
} else { } else {
$ok = $ok && $this->handleItem($frame); $ok = $ok && $this->handleItem($idx, $frame);
} }
} }
return $ok; 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 * 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. * Side effects: in multi-site mode, may reset site configuration.
* *
@ -240,9 +330,14 @@ class StompQueueManager extends QueueManager
public function start($master) public function start($master)
{ {
parent::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) { if ($this->sites) {
foreach ($this->sites as $server) { foreach ($this->sites as $server) {
StatusNet::init($server); StatusNet::init($server);
@ -251,10 +346,14 @@ class StompQueueManager extends QueueManager
} else { } else {
$this->doSubscribe(); $this->doSubscribe();
} }
$this->begin(); foreach ($this->cons as $i => $con) {
if ($con) {
$this->begin($i);
}
}
return true; return true;
} }
/** /**
* Subscribe to all the queues we're going to need to handle... * 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, // If there are any outstanding delivered messages we haven't processed,
// free them for another thread to take. // free them for another thread to take.
$this->rollback(); foreach ($this->cons as $i => $con) {
$this->con->unsubscribe($this->control); if ($con) {
$this->rollback($i);
$con->unsubscribe($this->control);
}
}
if ($this->sites) { if ($this->sites) {
foreach ($this->sites as $server) { foreach ($this->sites as $server) {
StatusNet::init($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() protected function _connect()
{ {
if (empty($this->con)) { if (empty($this->cons)) {
$this->_log(LOG_INFO, "Connecting to '$this->server' as '$this->username'..."); $list = $this->servers;
$this->con = new LiberalStomp($this->server); if (count($list) > 1) {
shuffle($list); // Randomize to spread load
if ($this->con->connect($this->username, $this->password)) { $url = 'failover://(' . implode(',', $list) . ')';
$this->_log(LOG_INFO, "Connected.");
} else { } else {
$this->_log(LOG_ERR, 'Failed to connect to queue server'); $url = $list[0];
throw new ServerException('Failed to connect to queue server'); }
$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. * Subscribe to all enabled notice queues for the current site.
*/ */
@ -317,7 +503,11 @@ class StompQueueManager extends QueueManager
$rawqueue = $this->queueName($queue); $rawqueue = $this->queueName($queue);
$this->subscriptions[$site][$queue] = $rawqueue; $this->subscriptions[$site][$queue] = $rawqueue;
$this->_log(LOG_INFO, "Subscribing to $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])) { if (!empty($this->subscriptions[$site])) {
foreach ($this->subscriptions[$site] as $queue => $rawqueue) { foreach ($this->subscriptions[$site] as $queue => $rawqueue) {
$this->_log(LOG_INFO, "Unsubscribing from $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]); unset($this->subscriptions[$site][$queue]);
} }
} }
@ -346,27 +540,31 @@ class StompQueueManager extends QueueManager
* Side effects: in multi-site mode, may reset site configuration to * Side effects: in multi-site mode, may reset site configuration to
* match the site that queued the event. * match the site that queued the event.
* *
* @param int $idx connection index
* @param StompFrame $frame * @param StompFrame $frame
* @return bool * @return bool
*/ */
protected function handleItem($frame) protected function handleItem($idx, $frame)
{ {
$this->defaultIdx = $idx;
list($site, $queue) = $this->parseDestination($frame->headers['destination']); list($site, $queue) = $this->parseDestination($frame->headers['destination']);
if ($site != $this->currentSite()) { if ($site != $this->currentSite()) {
$this->stats('switch'); $this->stats('switch');
StatusNet::init($site); StatusNet::init($site);
} }
$host = $this->cons[$idx]->getServer();
if (is_numeric($frame->body)) { if (is_numeric($frame->body)) {
$id = intval($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); $notice = Notice::staticGet('id', $id);
if (empty($notice)) { if (empty($notice)) {
$this->_log(LOG_WARNING, "Skipping missing $info"); $this->_log(LOG_WARNING, "Skipping missing $info");
$this->ack($frame); $this->ack($idx, $frame);
$this->commit(); $this->commit($idx);
$this->begin(); $this->begin($idx);
$this->stats('badnotice', $queue); $this->stats('badnotice', $queue);
return false; return false;
} }
@ -374,39 +572,47 @@ class StompQueueManager extends QueueManager
$item = $notice; $item = $notice;
} else { } else {
// @fixme should we serialize, or json, or what here? // @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; $item = $frame->body;
} }
$handler = $this->getHandler($queue); $handler = $this->getHandler($queue);
if (!$handler) { if (!$handler) {
$this->_log(LOG_ERR, "Missing handler class; skipping $info"); $this->_log(LOG_ERR, "Missing handler class; skipping $info");
$this->ack($frame); $this->ack($idx, $frame);
$this->commit(); $this->commit($idx);
$this->begin(); $this->begin($idx);
$this->stats('badhandler', $queue); $this->stats('badhandler', $queue);
return false; 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) { if (!$ok) {
$this->_log(LOG_WARNING, "Failed handling $info"); $this->_log(LOG_WARNING, "Failed handling $info");
// FIXME we probably shouldn't have to do // FIXME we probably shouldn't have to do
// this kind of queue management ourselves; // this kind of queue management ourselves;
// if we don't ack, it should resend... // if we don't ack, it should resend...
$this->ack($frame); $this->ack($idx, $frame);
$this->enqueue($item, $queue); $this->enqueue($item, $queue);
$this->commit(); $this->commit($idx);
$this->begin(); $this->begin($idx);
$this->stats('requeued', $queue); $this->stats('requeued', $queue);
return false; return false;
} }
$this->_log(LOG_INFO, "Successfully handled $info"); $this->_log(LOG_INFO, "Successfully handled $info");
$this->ack($frame); $this->ack($idx, $frame);
$this->commit(); $this->commit($idx);
$this->begin(); $this->begin($idx);
$this->stats('handled', $queue); $this->stats('handled', $queue);
return true; return true;
} }
@ -414,10 +620,11 @@ class StompQueueManager extends QueueManager
/** /**
* Process a control signal broadcast. * Process a control signal broadcast.
* *
* @param int $idx connection index
* @param array $frame Stomp frame * @param array $frame Stomp frame
* @return bool true to continue; false to stop further processing. * @return bool true to continue; false to stop further processing.
*/ */
protected function handleControlSignal($frame) protected function handleControlSignal($idx, $frame)
{ {
$message = trim($frame->body); $message = trim($frame->body);
if (strpos($message, ':') !== false) { if (strpos($message, ':') !== false) {
@ -441,12 +648,12 @@ class StompQueueManager extends QueueManager
$this->_log(LOG_ERR, "Ignoring unrecognized control message: $message"); $this->_log(LOG_ERR, "Ignoring unrecognized control message: $message");
} }
$this->ack($frame); $this->ack($idx, $frame);
$this->commit(); $this->commit($idx);
$this->begin(); $this->begin($idx);
return $shutdown; return $shutdown;
} }
/** /**
* Set us up with queue subscriptions for a new site added at runtime, * Set us up with queue subscriptions for a new site added at runtime,
* triggered by a broadcast to the 'statusnet-control' topic. * triggered by a broadcast to the 'statusnet-control' topic.
@ -520,47 +727,49 @@ class StompQueueManager extends QueueManager
common_log($level, 'StompQueueManager: '.$msg); common_log($level, 'StompQueueManager: '.$msg);
} }
protected function begin() protected function begin($idx)
{ {
if ($this->useTransactions) { if ($this->useTransactions) {
if ($this->transaction) { if (!empty($this->transaction[$idx])) {
throw new Exception("Tried to start transaction in the middle of a transaction"); throw new Exception("Tried to start transaction in the middle of a transaction");
} }
$this->transactionCount++; $this->transactionCount[$idx]++;
$this->transaction = $this->master->id . '-' . $this->transactionCount . '-' . time(); $this->transaction[$idx] = $this->master->id . '-' . $this->transactionCount[$idx] . '-' . time();
$this->con->begin($this->transaction); $this->cons[$idx]->begin($this->transaction[$idx]);
} }
} }
protected function ack($frame) protected function ack($idx, $frame)
{ {
if ($this->useTransactions) { if ($this->useTransactions) {
if (!$this->transaction) { if (empty($this->transaction[$idx])) {
throw new Exception("Tried to ack but not in a transaction"); 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->useTransactions) {
if (!$this->transaction) { if (empty($this->transaction[$idx])) {
throw new Exception("Tried to commit but not in a transaction"); throw new Exception("Tried to commit but not in a transaction");
} }
$this->con->commit($this->transaction); $this->cons[$idx]->commit($this->transaction[$idx]);
$this->transaction = null; $this->transaction[$idx] = null;
} }
} }
protected function rollback() protected function rollback($idx)
{ {
if ($this->useTransactions) { if ($this->useTransactions) {
if (!$this->transaction) { if (empty($this->transaction[$idx])) {
throw new Exception("Tried to rollback but not in a transaction"); throw new Exception("Tried to rollback but not in a transaction");
} }
$this->con->commit($this->transaction); $this->cons[$idx]->commit($this->transaction[$idx]);
$this->transaction = null; $this->transaction[$idx] = null;
} }
} }
} }

View File

@ -178,7 +178,6 @@ function common_ensure_session()
} }
if (isset($id)) { if (isset($id)) {
session_id($id); session_id($id);
setcookie(session_name(), $id);
} }
@session_start(); @session_start();
if (!isset($_SESSION['started'])) { if (!isset($_SESSION['started'])) {

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

View File

@ -59,6 +59,8 @@ class MemcachePlugin extends Plugin
public $persistent = null; public $persistent = null;
public $defaultExpiry = 86400; // 24h
/** /**
* Initialize the plugin * Initialize the plugin
* *
@ -110,6 +112,9 @@ class MemcachePlugin extends Plugin
function onStartCacheSet(&$key, &$value, &$flag, &$expiry, &$success) function onStartCacheSet(&$key, &$value, &$flag, &$expiry, &$success)
{ {
$this->_ensureConn(); $this->_ensureConn();
if ($expiry === null) {
$expiry = $this->defaultExpiry;
}
$success = $this->_conn->set($key, $value, $flag, $expiry); $success = $this->_conn->set($key, $value, $flag, $expiry);
Event::handle('EndCacheSet', array($key, $value, $flag, Event::handle('EndCacheSet', array($key, $value, $flag,
$expiry)); $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(); $scripts = $this->_getScripts();
foreach ($scripts as $script) { foreach ($scripts as $script) {
$action->script(common_path($script)); $action->script($script);
} }
$user = common_current_user(); $user = common_current_user();
@ -307,7 +307,7 @@ class RealtimePlugin extends Plugin
function _getScripts() function _getScripts()
{ {
return array('plugins/Realtime/realtimeupdate.js'); return array(common_path('plugins/Realtime/realtimeupdate.js'));
} }
function _updateInitialize($timeline, $user_id) function _updateInitialize($timeline, $user_id)

View File

@ -132,11 +132,11 @@ RealtimeUpdate = {
user = data['user']; user = data['user'];
html = data['html'].replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'"').replace(/&amp;/g,'&'); html = data['html'].replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'"').replace(/&amp;/g,'&');
source = data['source'].replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'"').replace(/&amp;/g,'&'); source = data['source'].replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'"').replace(/&amp;/g,'&');
console.log(data);
ni = "<li class=\"hentry notice\" id=\"notice-"+unique+"\">"+ ni = "<li class=\"hentry notice\" id=\"notice-"+unique+"\">"+
"<div class=\"entry-title\">"+ "<div class=\"entry-title\">"+
"<span class=\"vcard author\">"+ "<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']+"\"/>"+ "<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>"+ "<span class=\"nickname fn\">"+user['screen_name']+"</span>"+
"</a>"+ "</a>"+

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

View File

@ -1,5 +1,9 @@
#!/bin/bash #!/bin/bash
# live fast! die young!
set -e
source /etc/statusnet/setup.cfg source /etc/statusnet/setup.cfg
export nickname=$1 export nickname=$1

View File

@ -109,7 +109,13 @@ class QueueDaemon extends SpawningDaemon
$master = new QueueMaster($this->get_id()); $master = new QueueMaster($this->get_id());
$master->init($this->all); $master->init($this->all);
$master->service(); try {
$master->service();
} catch (Exception $e) {
common_log(LOG_ERR, "Unhandled exception: " . $e->getMessage() . ' ' .
str_replace("\n", " ", $e->getTraceAsString()));
return self::EXIT_ERR;
}
$this->log(LOG_INFO, 'finished servicing the queue'); $this->log(LOG_INFO, 'finished servicing the queue');

82
scripts/sendemail.php Executable file
View File

@ -0,0 +1,82 @@
#!/usr/bin/env php
<?php
/*
* StatusNet - a distributed open-source microblogging tool
* Copyright (C) 2008, 2009, StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
$shortoptions = 'i:n:';
$longoptions = array('id=', 'nickname=', 'subject=');
$helptext = <<<END_OF_USEREMAIL_HELP
sendemail.php [options] < <message body>
Sends given email text to user.
-i --id id of the user to query
-n --nickname nickname of the user to query
--subject mail subject line (required)
END_OF_USEREMAIL_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc';
if (have_option('i', 'id')) {
$id = get_option_value('i', 'id');
$user = User::staticGet('id', $id);
if (empty($user)) {
print "Can't find user with ID $id\n";
exit(1);
}
} else if (have_option('n', 'nickname')) {
$nickname = get_option_value('n', 'nickname');
$user = User::staticGet('nickname', $nickname);
if (empty($user)) {
print "Can't find user with nickname '$nickname'\n";
exit(1);
}
} else {
print "You must provide a user by --id or --nickname\n";
exit(1);
}
if (empty($user->email)) {
// @fixme unconfirmed address?
print "No email registered for user '$user->nickname'\n";
exit(1);
}
if (!have_option('subject')) {
echo "You must provide a subject line for the mail in --subject='...' param.\n";
exit(1);
}
$subject = get_option_value('subject');
if (posix_isatty(STDIN)) {
print "You must provide message input on stdin!\n";
exit(1);
}
$body = file_get_contents('php://stdin');
print "Sending to $user->email...";
if (mail_to_user($user, $subject, $body)) {
print " done\n";
} else {
print " failed.\n";
exit(1);
}

84
scripts/settag.php Normal file
View File

@ -0,0 +1,84 @@
#!/usr/bin/env php
<?php
/*
* StatusNet - a distributed open-source microblogging tool
* Copyright (C) 2008, 2009, StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
$shortoptions = 'd';
$longoptions = array('delete');
$helptext = <<<END_OF_SETTAG_HELP
settag.php [options] <site> <tag>
Set the tag <tag> for site <site>.
With -d, delete the tag.
END_OF_SETTAG_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc';
if (count($args) != 2) {
show_help();
exit(1);
}
$nickname = $args[0];
$tag = strtolower($args[1]);
$sn = Status_network::memGet('nickname', $nickname);
if (empty($sn)) {
print "No such site.\n";
exit(-1);
}
$tags = $sn->getTags();
$i = array_search($tag, $tags);
if ($i !== false) {
if (have_option('d', 'delete')) { // Delete
unset($tags[$i]);
$orig = clone($sn);
$sn->tags = implode('|', $tags);
$result = $sn->update($orig);
if (!$result) {
print "Couldn't update.\n";
exit(-1);
}
} else {
print "Already set.\n";
exit(-1);
}
} else {
if (have_option('d', 'delete')) { // Delete
print "No such tag.\n";
exit(-1);
} else {
$tags[] = $tag;
$orig = clone($sn);
$sn->tags = implode('|', $tags);
$result = $sn->update($orig);
if (!$result) {
print "Couldn't update.\n";
exit(-1);
}
}
}

View File

@ -11,4 +11,8 @@ export AVATARBASE=/var/www/avatar.example.net
export BACKGROUNDBASE=/var/www/background.example.net export BACKGROUNDBASE=/var/www/background.example.net
export FILEBASE=/var/www/file.example.net export FILEBASE=/var/www/file.example.net
export PWDGEN="pwgen 20" export PWDGEN="pwgen 20"
export PHPBASE=/var/www/statusnet
export WILDCARD=example.net
export MAILTEMPLATE=/etc/statusnet/newsite-mail.txt
export MAILSUBJECT="Your new StatusNet site"
export POSTINSTALL=/etc/statusnet/morestuff.sh

View File

@ -1,10 +1,28 @@
#!/bin/bash #!/bin/bash
# live fast! die young!
set -e
source /etc/statusnet/setup.cfg source /etc/statusnet/setup.cfg
export nickname=$1 # setup_status_net.sh mysite 'My Site' '1user' 'owner@example.com' 'Firsty McLastname'
export sitename=$2
export nickname="$1"
export sitename="$2"
export tags="$3"
export email="$4"
export fullname="$5"
# Fixme: if this is changed later we need to update profile URLs
# for the created user.
export server="$nickname.$WILDCARD"
# End-user info
export userpass=`$PWDGEN`
export roles="administrator moderator owner"
# DB info
export password=`$PWDGEN` export password=`$PWDGEN`
export database=$nickname$DBBASE export database=$nickname$DBBASE
export username=$nickname$USERBASE export username=$nickname$USERBASE
@ -21,8 +39,8 @@ mysql -h $DBHOST -u $ADMIN --password=$ADMINPASS $SITEDB << ENDOFCOMMANDS
GRANT ALL ON $database.* TO '$username'@'localhost' IDENTIFIED BY '$password'; GRANT ALL ON $database.* TO '$username'@'localhost' IDENTIFIED BY '$password';
GRANT ALL ON $database.* TO '$username'@'%' IDENTIFIED BY '$password'; GRANT ALL ON $database.* TO '$username'@'%' IDENTIFIED BY '$password';
INSERT INTO status_network (nickname, dbhost, dbuser, dbpass, dbname, sitename, created) INSERT INTO status_network (nickname, dbhost, dbuser, dbpass, dbname, sitename, created, tags)
VALUES ('$nickname', '$DBHOSTNAME', '$username', '$password', '$database', '$sitename', now()); VALUES ('$nickname', '$DBHOSTNAME', '$username', '$password', '$database', '$sitename', now(), '$tags');
ENDOFCOMMANDS ENDOFCOMMANDS
@ -30,3 +48,39 @@ for top in $AVATARBASE $FILEBASE $BACKGROUNDBASE; do
mkdir $top/$nickname mkdir $top/$nickname
chmod a+w $top/$nickname chmod a+w $top/$nickname
done done
php $PHPBASE/scripts/registeruser.php \
-s"$server" \
-n"$nickname" \
-f"$fullname" \
-w"$userpass" \
-e"$email"
for role in $roles
do
php $PHPBASE/scripts/userrole.php \
-s"$server" \
-n"$nickname" \
-r"$role"
done
if [ -f "$MAILTEMPLATE" ]
then
# fixme how safe is this? are sitenames sanitized?
cat $MAILTEMPLATE | \
sed "s/\$nickname/$nickname/" | \
sed "s/\$sitename/$sitename/" | \
sed "s/\$userpass/$userpass/" | \
php $PHPBASE/scripts/sendemail.php \
-s"$server" \
-n"$nickname" \
--subject="$MAILSUBJECT"
else
echo "No mail template, not sending email."
fi
if [ -f "$POSTINSTALL" ]
then
echo "Running $POSTINSTALL ..."
source "$POSTINSTALL"
fi

View File

@ -1130,8 +1130,17 @@ top:3px;
} }
.dialogbox .submit_dialogbox { .dialogbox .submit_dialogbox {
text-indent:0;
font-weight:bold; font-weight:bold;
text-indent:0;
min-width:46px;
}
#wrap form.processing input.submit,
.entity_actions a.processing,
.dialogbox.processing .submit_dialogbox {
cursor:wait;
outline:none;
text-indent:-9999px;
} }
.notice-options { .notice-options {

View File

@ -196,11 +196,12 @@ background-color:transparent;
} }
#wrap form.processing input.submit, #wrap form.processing input.submit,
.entity_actions a.processing { .entity_actions a.processing,
.dialogbox.processing .submit_dialogbox {
background:#FFFFFF url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%; background:#FFFFFF url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
cursor:wait; }
text-indent:-9999px; .notice-options .form_repeat.processing {
outline:none; background-image:none;
} }
#content { #content {

View File

@ -196,11 +196,12 @@ background-color:transparent;
} }
#wrap form.processing input.submit, #wrap form.processing input.submit,
.entity_actions a.processing { .entity_actions a.processing,
.dialogbox.processing .submit_dialogbox {
background:#FFFFFF url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%; background:#FFFFFF url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
cursor:wait; }
text-indent:-9999px; .notice-options .form_repeat.processing {
outline:none; background-image:none;
} }
#content { #content {