diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Shorturl_api.php | 66 | ||||
-rw-r--r-- | lib/accountsettingsaction.php | 61 | ||||
-rw-r--r-- | lib/action.php | 39 | ||||
-rw-r--r-- | lib/command.php | 16 | ||||
-rw-r--r-- | lib/commandinterpreter.php | 42 | ||||
-rw-r--r-- | lib/common.php | 252 | ||||
-rw-r--r-- | lib/curlclient.php | 179 | ||||
-rw-r--r-- | lib/default.php | 232 | ||||
-rw-r--r-- | lib/deleteaction.php | 74 | ||||
-rw-r--r-- | lib/facebookaction.php | 30 | ||||
-rw-r--r-- | lib/facebookutil.php | 7 | ||||
-rw-r--r-- | lib/groupeditform.php | 24 | ||||
-rw-r--r-- | lib/htmloutputter.php | 2 | ||||
-rw-r--r-- | lib/httpclient.php | 122 | ||||
-rw-r--r-- | lib/logingroupnav.php | 35 | ||||
-rw-r--r-- | lib/messageform.php | 17 | ||||
-rw-r--r-- | lib/noticeform.php | 65 | ||||
-rw-r--r-- | lib/noticelist.php | 14 | ||||
-rw-r--r-- | lib/oauthstore.php | 348 | ||||
-rw-r--r-- | lib/omb.php | 329 | ||||
-rw-r--r-- | lib/openid.php | 280 | ||||
-rw-r--r-- | lib/plugin.php | 14 | ||||
-rw-r--r-- | lib/right.php | 50 | ||||
-rw-r--r-- | lib/router.php | 13 | ||||
-rw-r--r-- | lib/rssaction.php | 59 | ||||
-rw-r--r-- | lib/schema.php | 680 | ||||
-rw-r--r-- | lib/settingsaction.php | 4 | ||||
-rw-r--r-- | lib/twitter.php | 7 | ||||
-rw-r--r-- | lib/twitterapi.php | 19 | ||||
-rw-r--r-- | lib/twitteroauthclient.php | 8 | ||||
-rw-r--r-- | lib/unqueuemanager.php | 9 | ||||
-rw-r--r-- | lib/util.php | 136 |
32 files changed, 2102 insertions, 1131 deletions
diff --git a/lib/Shorturl_api.php b/lib/Shorturl_api.php index 6402dbc09..18ae7719b 100644 --- a/lib/Shorturl_api.php +++ b/lib/Shorturl_api.php @@ -19,7 +19,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -class ShortUrlApi +abstract class ShortUrlApi { protected $service_url; protected $long_limit = 27; @@ -35,11 +35,9 @@ class ShortUrlApi return $url; } - protected function shorten_imp($url) { - return "To Override"; - } + protected abstract function shorten_imp($url); - private function is_long($url) { + protected function is_long($url) { return strlen($url) >= common_config('site', 'shorturllength'); } @@ -71,61 +69,3 @@ class ShortUrlApi } } -class LilUrl extends ShortUrlApi -{ - function __construct() - { - parent::__construct('http://ur1.ca/'); - } - - protected function shorten_imp($url) { - $data['longurl'] = $url; - $response = $this->http_post($data); - if (!$response) return $url; - $y = @simplexml_load_string($response); - if (!isset($y->body)) return $url; - $x = $y->body->p[0]->a->attributes(); - if (isset($x['href'])) return $x['href']; - return $url; - } -} - - -class PtitUrl extends ShortUrlApi -{ - function __construct() - { - parent::__construct('http://ptiturl.com/?creer=oui&action=Reduire&url='); - } - - protected function shorten_imp($url) { - $response = $this->http_get($url); - if (!$response) return $url; - $response = $this->tidy($response); - $y = @simplexml_load_string($response); - if (!isset($y->body)) return $url; - $xml = $y->body->center->table->tr->td->pre->a->attributes(); - if (isset($xml['href'])) return $xml['href']; - return $url; - } -} - -class TightUrl extends ShortUrlApi -{ - function __construct() - { - parent::__construct('http://2tu.us/?save=y&url='); - } - - protected function shorten_imp($url) { - $response = $this->http_get($url); - if (!$response) return $url; - $response = $this->tidy($response); - $y = @simplexml_load_string($response); - if (!isset($y->body)) return $url; - $xml = $y->body->p[0]->code[0]->a->attributes(); - if (isset($xml['href'])) return $xml['href']; - return $url; - } -} - diff --git a/lib/accountsettingsaction.php b/lib/accountsettingsaction.php index 798116163..a004a3ed9 100644 --- a/lib/accountsettingsaction.php +++ b/lib/accountsettingsaction.php @@ -98,42 +98,39 @@ class AccountSettingsNav extends Widget function show() { - # action => array('prompt', 'title') - $menu = - array('profilesettings' => - array(_('Profile'), - _('Change your profile settings')), - 'avatarsettings' => - array(_('Avatar'), - _('Upload an avatar')), - 'passwordsettings' => - array(_('Password'), - _('Change your password')), - 'emailsettings' => - array(_('Email'), - _('Change email handling')), - 'openidsettings' => - array(_('OpenID'), - _('Add or remove OpenIDs')), - 'userdesignsettings' => - array(_('Design'), - _('Design your profile')), - 'othersettings' => - array(_('Other'), - _('Other options'))); - $action_name = $this->action->trimmed('action'); $this->action->elementStart('ul', array('class' => 'nav')); - foreach ($menu as $menuaction => $menudesc) { - if ($menuaction == 'openidsettings' && - !common_config('openid', 'enabled')) { - continue; + if (Event::handle('StartAccountSettingsNav', array(&$this->action))) { + + $menu = + array('profilesettings' => + array(_('Profile'), + _('Change your profile settings')), + 'avatarsettings' => + array(_('Avatar'), + _('Upload an avatar')), + 'passwordsettings' => + array(_('Password'), + _('Change your password')), + 'emailsettings' => + array(_('Email'), + _('Change email handling')), + 'userdesignsettings' => + array(_('Design'), + _('Design your profile')), + 'othersettings' => + array(_('Other'), + _('Other options'))); + + foreach ($menu as $menuaction => $menudesc) { + $this->action->menuItem(common_local_url($menuaction), + $menudesc[0], + $menudesc[1], + $action_name === $menuaction); } - $this->action->menuItem(common_local_url($menuaction), - $menudesc[0], - $menudesc[1], - $action_name === $menuaction); + + Event::handle('EndAccountSettingsNav', array(&$this->action)); } $this->action->elementEnd('ul'); diff --git a/lib/action.php b/lib/action.php index 670eb498c..1b2f73752 100644 --- a/lib/action.php +++ b/lib/action.php @@ -120,14 +120,16 @@ class Action extends HTMLOutputter // lawsuit { // XXX: attributes (profile?) $this->elementStart('head'); - $this->showTitle(); - $this->showShortcutIcon(); - $this->showStylesheets(); - $this->showScripts(); - $this->showOpenSearch(); - $this->showFeeds(); - $this->showDescription(); - $this->extraHead(); + if (Event::handle('StartShowHeadElements', array($this))) { + $this->showTitle(); + $this->showShortcutIcon(); + $this->showStylesheets(); + $this->showOpenSearch(); + $this->showFeeds(); + $this->showDescription(); + $this->extraHead(); + Event::handle('EndShowHeadElements', array($this)); + } $this->elementEnd('head'); } @@ -352,6 +354,7 @@ class Action extends HTMLOutputter // lawsuit Event::handle('EndShowFooter', array($this)); } $this->elementEnd('div'); + $this->showScripts(); $this->elementEnd('body'); } @@ -442,17 +445,12 @@ class Action extends HTMLOutputter // lawsuit _('Logout'), _('Logout from the site'), false, 'nav_logout'); } else { - if (!common_config('site', 'openidonly')) { - if (!common_config('site', 'closed')) { - $this->menuItem(common_local_url('register'), - _('Register'), _('Create an account'), false, 'nav_register'); - } - $this->menuItem(common_local_url('login'), - _('Login'), _('Login to the site'), false, 'nav_login'); - } else { - $this->menuItem(common_local_url('openidlogin'), - _('OpenID'), _('Login with OpenID'), false, 'nav_openid'); + if (!common_config('site', 'closed')) { + $this->menuItem(common_local_url('register'), + _('Register'), _('Create an account'), false, 'nav_register'); } + $this->menuItem(common_local_url('login'), + _('Login'), _('Login to the site'), false, 'nav_login'); } $this->menuItem(common_local_url('doc', array('title' => 'help')), _('Help'), _('Help me!'), false, 'nav_help'); @@ -530,7 +528,10 @@ class Action extends HTMLOutputter // lawsuit $this->showContentBlock(); Event::handle('EndShowContentBlock', array($this)); } - $this->showAside(); + if (Event::handle('StartShowAside', array($this))) { + $this->showAside(); + Event::handle('EndShowAside', array($this)); + } $this->elementEnd('div'); } diff --git a/lib/command.php b/lib/command.php index 01b14f83e..11d40b8e1 100644 --- a/lib/command.php +++ b/lib/command.php @@ -312,16 +312,20 @@ class MessageCommand extends Command function execute($channel) { $other = User::staticGet('nickname', common_canonical_nickname($this->other)); + $len = mb_strlen($this->text); + if ($len == 0) { $channel->error($this->user, _('No content!')); return; - } else if ($len > 140) { - $content = common_shorten_links($content); - if (mb_strlen($content) > 140) { - $channel->error($this->user, sprintf(_('Message too long - maximum is 140 characters, you sent %d'), $len)); - return; - } + } + + $this->text = common_shorten_links($this->text); + + if (Message::contentTooLong($this->text)) { + $channel->error($this->user, sprintf(_('Message too long - maximum is %d characters, you sent %d'), + Message::maxContent(), mb_strlen($this->text))); + return; } if (!$other) { diff --git a/lib/commandinterpreter.php b/lib/commandinterpreter.php index 6e4340e5d..60fc4c3c4 100644 --- a/lib/commandinterpreter.php +++ b/lib/commandinterpreter.php @@ -28,7 +28,7 @@ class CommandInterpreter # XXX: localise $text = preg_replace('/\s+/', ' ', trim($text)); - list($cmd, $arg) = explode(' ', $text, 2); + list($cmd, $arg) = $this->split_arg($text); # We try to support all the same commands as Twitter, see # http://getsatisfaction.com/twitter/topics/what_are_the_twitter_commands @@ -43,7 +43,7 @@ class CommandInterpreter return new HelpCommand($user); case 'on': if ($arg) { - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -54,7 +54,7 @@ class CommandInterpreter } case 'off': if ($arg) { - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -74,7 +74,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -84,7 +84,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -95,7 +95,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -106,7 +106,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -117,7 +117,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -128,7 +128,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if (!$extra) { return null; } else { @@ -138,7 +138,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -148,7 +148,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -158,7 +158,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -173,7 +173,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -183,7 +183,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($word, $extra) = explode(' ', $arg, 2); + list($word, $extra) = $this->split_arg($arg); if ($extra) { return null; } else if ($word == 'off') { @@ -195,7 +195,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($word, $extra) = explode(' ', $arg, 2); + list($word, $extra) = $this->split_arg($arg); if ($extra) { return null; } else if ($word == 'all') { @@ -213,5 +213,17 @@ class CommandInterpreter return false; } } + + /** + * Split arguments without triggering a PHP notice warning + */ + function split_arg($text) + { + $pieces = explode(' ', $text, 2); + if (count($pieces) == 1) { + $pieces[] = null; + } + return $pieces; + } } diff --git a/lib/common.php b/lib/common.php index 0b4e03184..ce33c871b 100644 --- a/lib/common.php +++ b/lib/common.php @@ -19,10 +19,10 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -define('STATUSNET_VERSION', '0.8.2dev'); +define('STATUSNET_VERSION', '0.9.0dev'); define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility -define('STATUSNET_CODENAME', 'Second Guessing'); +define('STATUSNET_CODENAME', 'Stand'); define('AVATAR_PROFILE_SIZE', 96); define('AVATAR_STREAM_SIZE', 48); @@ -53,6 +53,7 @@ require_once('DB/DataObject/Cast.php'); # for dates if (!function_exists('gettext')) { require_once("php-gettext/gettext.inc"); } + require_once(INSTALLDIR.'/lib/language.php'); // This gets included before the config file, so that admin code and plugins @@ -93,206 +94,17 @@ if (isset($path)) { null; } -// default configuration, overwritten in config.php +require_once(INSTALLDIR.'/lib/default.php'); + +// Set config values initially to default values -$config = - array('site' => - array('name' => 'Just another StatusNet microblog', - 'server' => $_server, - 'theme' => 'default', - 'path' => $_path, - 'logfile' => null, - 'logo' => null, - 'logdebug' => false, - 'fancy' => false, - 'locale_path' => INSTALLDIR.'/locale', - 'language' => 'en_US', - 'languages' => get_all_languages(), - 'email' => - array_key_exists('SERVER_ADMIN', $_SERVER) ? $_SERVER['SERVER_ADMIN'] : null, - 'broughtby' => null, - 'timezone' => 'UTC', - 'broughtbyurl' => null, - 'closed' => false, - 'inviteonly' => false, - 'openidonly' => false, - 'private' => false, - 'ssl' => 'never', - 'sslserver' => null, - 'shorturllength' => 30, - 'dupelimit' => 60), # default for same person saying the same thing - 'syslog' => - array('appname' => 'statusnet', # for syslog - 'priority' => 'debug', # XXX: currently ignored - 'facility' => LOG_USER), - 'queue' => - array('enabled' => false, - 'subsystem' => 'db', # default to database, or 'stomp' - 'stomp_server' => null, - 'queue_basename' => 'statusnet', - 'stomp_username' => null, - 'stomp_password' => null, - ), - 'license' => - array('url' => 'http://creativecommons.org/licenses/by/3.0/', - 'title' => 'Creative Commons Attribution 3.0', - 'image' => 'http://i.creativecommons.org/l/by/3.0/80x15.png'), - 'mail' => - array('backend' => 'mail', - 'params' => null), - 'nickname' => - array('blacklist' => array(), - 'featured' => array()), - 'profile' => - array('banned' => array()), - 'avatar' => - array('server' => null, - 'dir' => INSTALLDIR . '/avatar/', - 'path' => $_path . '/avatar/'), - 'background' => - array('server' => null, - 'dir' => INSTALLDIR . '/background/', - 'path' => $_path . '/background/'), - 'public' => - array('localonly' => true, - 'blacklist' => array(), - 'autosource' => array()), - 'theme' => - array('server' => null, - 'dir' => null, - 'path'=> null), - 'throttle' => - array('enabled' => false, // whether to throttle edits; false by default - 'count' => 20, // number of allowed messages in timespan - 'timespan' => 600), // timespan for throttling - 'xmpp' => - array('enabled' => false, - 'server' => 'INVALID SERVER', - 'port' => 5222, - 'user' => 'update', - 'encryption' => true, - 'resource' => 'uniquename', - 'password' => 'blahblahblah', - 'host' => null, # only set if != server - 'debug' => false, # print extra debug info - 'public' => array()), # JIDs of users who want to receive the public stream - 'openid' => - array('enabled' => true), - 'invite' => - array('enabled' => true), - 'sphinx' => - array('enabled' => false, - 'server' => 'localhost', - 'port' => 3312), - 'tag' => - array('dropoff' => 864000.0), - 'popular' => - array('dropoff' => 864000.0), - 'daemon' => - array('piddir' => '/var/run', - 'user' => false, - 'group' => false), - 'emailpost' => - array('enabled' => true), - 'sms' => - array('enabled' => true), - 'twitterbridge' => - array('enabled' => false), - 'integration' => - array('source' => 'StatusNet', # source attribute for Twitter - 'taguri' => $_server.',2009'), # base for tag URIs - 'twitter' => - array('enabled' => true, - 'consumer_key' => null, - 'consumer_secret' => null), - 'memcached' => - array('enabled' => false, - 'server' => 'localhost', - 'base' => null, - 'port' => 11211), - 'ping' => - array('notify' => array()), - 'inboxes' => - array('enabled' => true), # on by default for new sites - 'newuser' => - array('default' => null, - 'welcome' => null), - 'snapshot' => - array('run' => 'web', - 'frequency' => 10000, - 'reporturl' => 'http://status.net/stats/report'), - 'attachments' => - array('server' => null, - 'dir' => INSTALLDIR . '/file/', - 'path' => $_path . '/file/', - 'supported' => array('image/png', - 'image/jpeg', - 'image/gif', - 'image/svg+xml', - 'audio/mpeg', - 'audio/x-speex', - 'application/ogg', - 'application/pdf', - 'application/vnd.oasis.opendocument.text', - 'application/vnd.oasis.opendocument.text-template', - 'application/vnd.oasis.opendocument.graphics', - 'application/vnd.oasis.opendocument.graphics-template', - 'application/vnd.oasis.opendocument.presentation', - 'application/vnd.oasis.opendocument.presentation-template', - 'application/vnd.oasis.opendocument.spreadsheet', - 'application/vnd.oasis.opendocument.spreadsheet-template', - 'application/vnd.oasis.opendocument.chart', - 'application/vnd.oasis.opendocument.chart-template', - 'application/vnd.oasis.opendocument.image', - 'application/vnd.oasis.opendocument.image-template', - 'application/vnd.oasis.opendocument.formula', - 'application/vnd.oasis.opendocument.formula-template', - 'application/vnd.oasis.opendocument.text-master', - 'application/vnd.oasis.opendocument.text-web', - 'application/x-zip', - 'application/zip', - 'text/plain', - 'video/mpeg', - 'video/mp4', - 'video/quicktime', - 'video/mpeg'), - 'file_quota' => 5000000, - 'user_quota' => 50000000, - 'monthly_quota' => 15000000, - 'uploads' => true, - 'filecommand' => '/usr/bin/file', - ), - 'group' => - array('maxaliases' => 3), - 'oohembed' => array('endpoint' => 'http://oohembed.com/oohembed/'), - 'search' => - array('type' => 'fulltext'), - 'sessions' => - array('handle' => false, // whether to handle sessions ourselves - 'debug' => false), // debugging output for sessions - 'design' => - array('backgroundcolor' => null, // null -> 'use theme default' - 'contentcolor' => null, - 'sidebarcolor' => null, - 'textcolor' => null, - 'linkcolor' => null, - 'backgroundimage' => null, - 'disposition' => null), - ); +$config = $default; + +// default configuration, overwritten in config.php $config['db'] = &PEAR::getStaticProperty('DB_DataObject','options'); -$config['db'] = - array('database' => 'YOU HAVE TO SET THIS IN config.php', - 'schema_location' => INSTALLDIR . '/classes', - 'class_location' => INSTALLDIR . '/classes', - 'require_prefix' => 'classes/', - 'class_prefix' => '', - 'mirror' => null, - 'utf8' => true, - 'db_driver' => 'DB', # XXX: JanRain libs only work with DB - 'quote_identifiers' => false, - 'type' => 'mysql' ); +$config['db'] = $default['db']; // Backward compatibility @@ -382,13 +194,25 @@ if ($_db_name != 'statusnet' && !array_key_exists('ini_'.$_db_name, $config['db' $config['db']['ini_'.$_db_name] = INSTALLDIR.'/classes/statusnet.ini'; } -// Ignore openidonly if OpenID is disabled - -if (!$config['openid']['enabled']) { - $config['site']['openidonly'] = false; +function __autoload($cls) +{ + if (file_exists(INSTALLDIR.'/classes/' . $cls . '.php')) { + require_once(INSTALLDIR.'/classes/' . $cls . '.php'); + } else if (file_exists(INSTALLDIR.'/lib/' . strtolower($cls) . '.php')) { + require_once(INSTALLDIR.'/lib/' . strtolower($cls) . '.php'); + } else if (mb_substr($cls, -6) == 'Action' && + file_exists(INSTALLDIR.'/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php')) { + require_once(INSTALLDIR.'/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php'); + } else if ($cls == 'OAuthRequest') { + require_once('OAuth.php'); + } else { + Event::handle('Autoload', array(&$cls)); + } } // XXX: how many of these could be auto-loaded on use? +// XXX: note that these files should not use config options +// at compile time since DB config options are not yet loaded. require_once 'Validate.php'; require_once 'markdown.php'; @@ -404,24 +228,20 @@ require_once INSTALLDIR.'/lib/twitter.php'; require_once INSTALLDIR.'/lib/clientexception.php'; require_once INSTALLDIR.'/lib/serverexception.php'; -// XXX: other formats here +// Load settings from database; note we need autoload for this -define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER); +Config::loadSettings(); -function __autoload($class) -{ - if ($class == 'OAuthRequest') { - require_once('OAuth.php'); - } else if (file_exists(INSTALLDIR.'/classes/' . $class . '.php')) { - require_once(INSTALLDIR.'/classes/' . $class . '.php'); - } else if (file_exists(INSTALLDIR.'/lib/' . strtolower($class) . '.php')) { - require_once(INSTALLDIR.'/lib/' . strtolower($class) . '.php'); - } else if (mb_substr($class, -6) == 'Action' && - file_exists(INSTALLDIR.'/actions/' . strtolower(mb_substr($class, 0, -6)) . '.php')) { - require_once(INSTALLDIR.'/actions/' . strtolower(mb_substr($class, 0, -6)) . '.php'); - } +// XXX: if plugins should check the schema at runtime, do that here. + +if ($config['db']['schemacheck'] == 'runtime') { + Event::handle('CheckSchema'); } +// XXX: other formats here + +define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER); + // Give plugins a chance to initialize in a fully-prepared environment Event::handle('InitializePlugin'); diff --git a/lib/curlclient.php b/lib/curlclient.php new file mode 100644 index 000000000..36fc7d157 --- /dev/null +++ b/lib/curlclient.php @@ -0,0 +1,179 @@ +n<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Utility class for wrapping Curl + * + * 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 HTTP + * @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); +} + +define(CURLCLIENT_VERSION, "0.1"); + +/** + * Wrapper for Curl + * + * Makes Curl HTTP client calls within our HTTPClient framework + * + * @category HTTP + * @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 CurlClient extends HTTPClient +{ + function __construct() + { + } + + function head($url, $headers=null) + { + $ch = curl_init($url); + + $this->setup($ch); + + curl_setopt_array($ch, + array(CURLOPT_NOBODY => true)); + + if (!is_null($headers)) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + } + + $result = curl_exec($ch); + + curl_close($ch); + + return $this->parseResults($result); + } + + function get($url, $headers=null) + { + $ch = curl_init($url); + + $this->setup($ch); + + if (!is_null($headers)) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + } + + $result = curl_exec($ch); + + curl_close($ch); + + return $this->parseResults($result); + } + + function post($url, $headers=null, $body=null) + { + $ch = curl_init($url); + + $this->setup($ch); + + curl_setopt($ch, CURLOPT_POST, true); + + if (!is_null($body)) { + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + } + + if (!is_null($headers)) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + } + + $result = curl_exec($ch); + + curl_close($ch); + + return $this->parseResults($result); + } + + function setup($ch) + { + curl_setopt_array($ch, + array(CURLOPT_USERAGENT => $this->userAgent(), + CURLOPT_HEADER => true, + CURLOPT_RETURNTRANSFER => true)); + } + + function userAgent() + { + $version = curl_version(); + return parent::userAgent() . " CurlClient/".CURLCLIENT_VERSION . " cURL/" . $version['version']; + } + + function parseResults($results) + { + $resp = new HTTPResponse(); + + $lines = explode("\r\n", $results); + + if (preg_match("#^HTTP/1.[01] (\d\d\d) .+$#", $lines[0], $match)) { + $resp->code = $match[1]; + } else { + throw Exception("Bad format: initial line is not HTTP status line"); + } + + $lastk = null; + + for ($i = 1; $i < count($lines); $i++) { + $l =& $lines[$i]; + if (mb_strlen($l) == 0) { + $resp->body = implode("\r\n", array_slice($lines, $i + 1)); + break; + } + if (preg_match("#^(\S+):\s+(.*)$#", $l, $match)) { + $k = $match[1]; + $v = $match[2]; + + if (array_key_exists($k, $resp->headers)) { + if (is_array($resp->headers[$k])) { + $resp->headers[$k][] = $v; + } else { + $resp->headers[$k] = array($resp->headers[$k], $v); + } + } else { + $resp->headers[$k] = $v; + } + $lastk = $k; + } else if (preg_match("#^\s+(.*)$#", $l, $match)) { + // continuation line + if (is_null($lastk)) { + throw Exception("Bad format: initial whitespace in headers"); + } + $h =& $resp->headers[$lastk]; + if (is_array($h)) { + $n = count($h); + $h[$n-1] .= $match[1]; + } else { + $h .= $match[1]; + } + } + } + + return $resp; + } +} diff --git a/lib/default.php b/lib/default.php new file mode 100644 index 000000000..329b041e9 --- /dev/null +++ b/lib/default.php @@ -0,0 +1,232 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Default settings for core configuration + * + * 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 Config + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @copyright 2008-9 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/ + */ + +$default = + array('site' => + array('name' => 'Just another StatusNet microblog', + 'server' => $_server, + 'theme' => 'default', + 'path' => $_path, + 'logfile' => null, + 'logo' => null, + 'logdebug' => false, + 'fancy' => false, + 'locale_path' => INSTALLDIR.'/locale', + 'language' => 'en_US', + 'languages' => get_all_languages(), + 'email' => + array_key_exists('SERVER_ADMIN', $_SERVER) ? $_SERVER['SERVER_ADMIN'] : null, + 'broughtby' => null, + 'timezone' => 'UTC', + 'broughtbyurl' => null, + 'closed' => false, + 'inviteonly' => false, + 'private' => false, + 'ssl' => 'never', + 'sslserver' => null, + 'shorturllength' => 30, + 'dupelimit' => 60, # default for same person saying the same thing + 'textlimit' => 140, + ), + 'db' => + array('database' => 'YOU HAVE TO SET THIS IN config.php', + 'schema_location' => INSTALLDIR . '/classes', + 'class_location' => INSTALLDIR . '/classes', + 'require_prefix' => 'classes/', + 'class_prefix' => '', + 'mirror' => null, + 'utf8' => true, + 'db_driver' => 'DB', # XXX: JanRain libs only work with DB + 'quote_identifiers' => false, + 'type' => 'mysql', + 'schemacheck' => 'runtime'), // 'runtime' or 'script' + 'syslog' => + array('appname' => 'statusnet', # for syslog + 'priority' => 'debug', # XXX: currently ignored + 'facility' => LOG_USER), + 'queue' => + array('enabled' => false, + 'subsystem' => 'db', # default to database, or 'stomp' + 'stomp_server' => null, + 'queue_basename' => 'statusnet', + 'stomp_username' => null, + 'stomp_password' => null, + ), + 'license' => + array('url' => 'http://creativecommons.org/licenses/by/3.0/', + 'title' => 'Creative Commons Attribution 3.0', + 'image' => 'http://i.creativecommons.org/l/by/3.0/80x15.png'), + 'mail' => + array('backend' => 'mail', + 'params' => null), + 'nickname' => + array('blacklist' => array(), + 'featured' => array()), + 'profile' => + array('banned' => array(), + 'biolimit' => null), + 'avatar' => + array('server' => null, + 'dir' => INSTALLDIR . '/avatar/', + 'path' => $_path . '/avatar/'), + 'background' => + array('server' => null, + 'dir' => INSTALLDIR . '/background/', + 'path' => $_path . '/background/'), + 'public' => + array('localonly' => true, + 'blacklist' => array(), + 'autosource' => array()), + 'theme' => + array('server' => null, + 'dir' => null, + 'path'=> null), + 'throttle' => + array('enabled' => false, // whether to throttle edits; false by default + 'count' => 20, // number of allowed messages in timespan + 'timespan' => 600), // timespan for throttling + 'xmpp' => + array('enabled' => false, + 'server' => 'INVALID SERVER', + 'port' => 5222, + 'user' => 'update', + 'encryption' => true, + 'resource' => 'uniquename', + 'password' => 'blahblahblah', + 'host' => null, # only set if != server + 'debug' => false, # print extra debug info + 'public' => array()), # JIDs of users who want to receive the public stream + 'invite' => + array('enabled' => true), + 'sphinx' => + array('enabled' => false, + 'server' => 'localhost', + 'port' => 3312), + 'tag' => + array('dropoff' => 864000.0), + 'popular' => + array('dropoff' => 864000.0), + 'daemon' => + array('piddir' => '/var/run', + 'user' => false, + 'group' => false), + 'emailpost' => + array('enabled' => true), + 'sms' => + array('enabled' => true), + 'twitterbridge' => + array('enabled' => false), + 'integration' => + array('source' => 'StatusNet', # source attribute for Twitter + 'taguri' => $_server.',2009'), # base for tag URIs + 'twitter' => + array('enabled' => true, + 'consumer_key' => null, + 'consumer_secret' => null), + 'memcached' => + array('enabled' => false, + 'server' => 'localhost', + 'base' => null, + 'port' => 11211), + 'ping' => + array('notify' => array()), + 'inboxes' => + array('enabled' => true), # on by default for new sites + 'newuser' => + array('default' => null, + 'welcome' => null), + 'snapshot' => + array('run' => 'web', + 'frequency' => 10000, + 'reporturl' => 'http://status.net/stats/report'), + 'attachments' => + array('server' => null, + 'dir' => INSTALLDIR . '/file/', + 'path' => $_path . '/file/', + 'supported' => array('image/png', + 'image/jpeg', + 'image/gif', + 'image/svg+xml', + 'audio/mpeg', + 'audio/x-speex', + 'application/ogg', + 'application/pdf', + 'application/vnd.oasis.opendocument.text', + 'application/vnd.oasis.opendocument.text-template', + 'application/vnd.oasis.opendocument.graphics', + 'application/vnd.oasis.opendocument.graphics-template', + 'application/vnd.oasis.opendocument.presentation', + 'application/vnd.oasis.opendocument.presentation-template', + 'application/vnd.oasis.opendocument.spreadsheet', + 'application/vnd.oasis.opendocument.spreadsheet-template', + 'application/vnd.oasis.opendocument.chart', + 'application/vnd.oasis.opendocument.chart-template', + 'application/vnd.oasis.opendocument.image', + 'application/vnd.oasis.opendocument.image-template', + 'application/vnd.oasis.opendocument.formula', + 'application/vnd.oasis.opendocument.formula-template', + 'application/vnd.oasis.opendocument.text-master', + 'application/vnd.oasis.opendocument.text-web', + 'application/x-zip', + 'application/zip', + 'text/plain', + 'video/mpeg', + 'video/mp4', + 'video/quicktime', + 'video/mpeg'), + 'file_quota' => 5000000, + 'user_quota' => 50000000, + 'monthly_quota' => 15000000, + 'uploads' => true, + 'filecommand' => '/usr/bin/file', + ), + 'group' => + array('maxaliases' => 3, + 'desclimit' => null), + 'oohembed' => array('endpoint' => 'http://oohembed.com/oohembed/'), + 'search' => + array('type' => 'fulltext'), + 'sessions' => + array('handle' => false, // whether to handle sessions ourselves + 'debug' => false), // debugging output for sessions + 'design' => + array('backgroundcolor' => null, // null -> 'use theme default' + 'contentcolor' => null, + 'sidebarcolor' => null, + 'textcolor' => null, + 'linkcolor' => null, + 'backgroundimage' => null, + 'disposition' => null), + 'notice' => + array('contentlimit' => null), + 'message' => + array('contentlimit' => null), + 'http' => + array('client' => 'curl'), // XXX: should this be the default? + ); diff --git a/lib/deleteaction.php b/lib/deleteaction.php deleted file mode 100644 index f702820c6..000000000 --- a/lib/deleteaction.php +++ /dev/null @@ -1,74 +0,0 @@ -<?php -/** - * StatusNet, the distributed open-source microblogging tool - * - * Base class for deleting things - * - * 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 Personal - * @package StatusNet - * @author Evan Prodromou <evan@status.net> - * @author Sarven Capadisli <csarven@status.net> - * @copyright 2008 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -class DeleteAction extends Action -{ - var $user = null; - var $notice = null; - var $profile = null; - var $user_profile = null; - - function prepare($args) - { - parent::prepare($args); - - $this->user = common_current_user(); - $notice_id = $this->trimmed('notice'); - $this->notice = Notice::staticGet($notice_id); - - if (!$this->notice) { - common_user_error(_('No such notice.')); - exit; - } - - $this->profile = $this->notice->getProfile(); - $this->user_profile = $this->user->getProfile(); - - return true; - } - - function handle($args) - { - parent::handle($args); - - if (!common_logged_in()) { - common_user_error(_('Not logged in.')); - exit; - } else if ($this->notice->profile_id != $this->user_profile->id) { - common_user_error(_('Can\'t delete this notice.')); - exit; - } - } - -} diff --git a/lib/facebookaction.php b/lib/facebookaction.php index 5cbb9be53..3f3a8d3b0 100644 --- a/lib/facebookaction.php +++ b/lib/facebookaction.php @@ -35,7 +35,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) require_once INSTALLDIR.'/lib/facebookutil.php'; require_once INSTALLDIR.'/lib/noticeform.php'; - class FacebookAction extends Action { @@ -181,7 +180,6 @@ class FacebookAction extends Action } - // Make this into a widget later function showLocalNav() { @@ -241,7 +239,6 @@ class FacebookAction extends Action $this->endHTML(); } - function showInstructions() { @@ -257,13 +254,8 @@ class FacebookAction extends Action $this->elementStart('dd'); $this->elementStart('p'); $this->text(sprintf($loginmsg_part1, common_config('site', 'name'))); - if (!common_config('site', 'openidonly')) { - $this->element('a', - array('href' => common_local_url('register')), _('Register')); - } else { - $this->element('a', - array('href' => common_local_url('openidlogin')), _('Register')); - } + $this->element('a', + array('href' => common_local_url('register')), _('Register')); $this->text($loginmsg_part2); $this->elementEnd('p'); $this->elementEnd('dd'); @@ -272,7 +264,6 @@ class FacebookAction extends Action $this->elementEnd('div'); } - function showLoginForm($msg = null) { @@ -317,7 +308,6 @@ class FacebookAction extends Action } - function updateProfileBox($notice) { @@ -399,7 +389,6 @@ class FacebookAction extends Action $this->xw->openURI('php://output'); } - /** * Generate pagination links * @@ -458,8 +447,9 @@ class FacebookAction extends Action } else { $content_shortened = common_shorten_links($content); - if (mb_strlen($content_shortened) > 140) { - $this->showPage(_('That\'s too long. Max notice size is 140 chars.')); + if (Notice::contentTooLong($content_shortened)) { + $this->showPage(sprintf(_('That\'s too long. Max notice size is %d chars.'), + Notice::maxContent())); return; } } @@ -478,11 +468,11 @@ class FacebookAction extends Action $replyto = $this->trimmed('inreplyto'); - $notice = Notice::saveNew($user->id, $content, - 'web', 1, ($replyto == 'false') ? null : $replyto); - - if (is_string($notice)) { - $this->showPage($notice); + try { + $notice = Notice::saveNew($user->id, $content, + 'web', 1, ($replyto == 'false') ? null : $replyto); + } catch (Exception $e) { + $this->showPage($e->getMessage()); return; } diff --git a/lib/facebookutil.php b/lib/facebookutil.php index ad61b6f0a..f5121609d 100644 --- a/lib/facebookutil.php +++ b/lib/facebookutil.php @@ -109,7 +109,6 @@ function facebookBroadcastNotice($notice) $can_update = $facebook->api_client->users_hasAppPermission('status_update', $fbuid); - if (!empty($attachments) && $can_publish == 1) { $fbattachment = format_attachments($attachments); $facebook->api_client->stream_publish($status, $fbattachment, @@ -180,7 +179,11 @@ function format_attachments($attachments) foreach($attachments as $attachment) { - $fbmedia = get_fbmedia_for_attachment($attachment); + if($enclosure = $attachment->getEnclosure()){ + $fbmedia = get_fbmedia_for_attachment($enclosure); + }else{ + $fbmedia = get_fbmedia_for_attachment($attachment); + } if($fbmedia){ $fbattachment['media'][]=$fbmedia; }else{ diff --git a/lib/groupeditform.php b/lib/groupeditform.php index a649c2ee3..433f6a138 100644 --- a/lib/groupeditform.php +++ b/lib/groupeditform.php @@ -150,27 +150,33 @@ class GroupEditForm extends Form $this->out->elementStart('li'); $this->out->hidden('groupid', $id); $this->out->input('nickname', _('Nickname'), - ($this->out->arg('nickname')) ? $this->out->arg('nickname') : $nickname, - _('1-64 lowercase letters or numbers, no punctuation or spaces')); + ($this->out->arg('nickname')) ? $this->out->arg('nickname') : $nickname, + _('1-64 lowercase letters or numbers, no punctuation or spaces')); $this->out->elementEnd('li'); $this->out->elementStart('li'); $this->out->input('fullname', _('Full name'), - ($this->out->arg('fullname')) ? $this->out->arg('fullname') : $fullname); + ($this->out->arg('fullname')) ? $this->out->arg('fullname') : $fullname); $this->out->elementEnd('li'); $this->out->elementStart('li'); $this->out->input('homepage', _('Homepage'), - ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage, - _('URL of the homepage or blog of the group or topic')); + ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage, + _('URL of the homepage or blog of the group or topic')); $this->out->elementEnd('li'); $this->out->elementStart('li'); + $desclimit = User_group::maxDescription(); + if ($desclimit == 0) { + $descinstr = _('Describe the group or topic'); + } else { + $descinstr = sprintf(_('Describe the group or topic in %d characters'), $desclimit); + } $this->out->textarea('description', _('Description'), - ($this->out->arg('description')) ? $this->out->arg('description') : $description, - _('Describe the group or topic in 140 chars')); + ($this->out->arg('description')) ? $this->out->arg('description') : $description, + $descinstr); $this->out->elementEnd('li'); $this->out->elementStart('li'); $this->out->input('location', _('Location'), - ($this->out->arg('location')) ? $this->out->arg('location') : $location, - _('Location for the group, if any, like "City, State (or Region), Country"')); + ($this->out->arg('location')) ? $this->out->arg('location') : $location, + _('Location for the group, if any, like "City, State (or Region), Country"')); $this->out->elementEnd('li'); if (common_config('group', 'maxaliases') > 0) { $aliases = (empty($this->group)) ? array() : $this->group->getAliases(); diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 2ff9380cc..ce83295fb 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -106,7 +106,7 @@ class HTMLOutputter extends XMLOutputter } } - header('Content-Type: '.$type.'; charset=UTF-8'); + header('Content-Type: '.$type); $this->extraHeaders(); if (preg_match("/.*\/.*xml/", $type)) { diff --git a/lib/httpclient.php b/lib/httpclient.php new file mode 100644 index 000000000..c8c8ae5b2 --- /dev/null +++ b/lib/httpclient.php @@ -0,0 +1,122 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Utility for doing HTTP-related things + * + * 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); +} + +/** + * Useful structure for HTTP responses + * + * We make HTTP calls in several places, and we have several different + * ways of doing them. This class hides the specifics of what underlying + * library (curl or PHP-HTTP or whatever) that's used. + * + * @category HTTP + * @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 HTTPResponse +{ + public $code = null; + public $headers = null; + public $body = null; +} + +/** + * Utility class for doing HTTP client stuff + * + * We make HTTP calls in several places, and we have several different + * ways of doing them. This class hides the specifics of what underlying + * library (curl or PHP-HTTP or whatever) that's used. + * + * @category HTTP + * @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 HTTPClient +{ + static $_client = null; + + static function start() + { + if (!is_null(self::$_client)) { + return self::$_client; + } + + $type = common_config('http', 'client'); + + switch ($type) { + case 'curl': + self::$_client = new CurlClient(); + break; + default: + throw new Exception("Unknown HTTP client type '$type'"); + break; + } + + return self::$_client; + } + + function head($url, $headers) + { + throw new Exception("HEAD method unimplemented"); + } + + function get($url, $headers) + { + throw new Exception("GET method unimplemented"); + } + + function post($url, $headers, $body) + { + throw new Exception("POST method unimplemented"); + } + + function put($url, $headers, $body) + { + throw new Exception("PUT method unimplemented"); + } + + function delete($url, $headers) + { + throw new Exception("DELETE method unimplemented"); + } + + function userAgent() + { + return "StatusNet/".STATUSNET_VERSION." (".STATUSNET_CODENAME.")"; + } +} diff --git a/lib/logingroupnav.php b/lib/logingroupnav.php index f740e329a..b545fbf26 100644 --- a/lib/logingroupnav.php +++ b/lib/logingroupnav.php @@ -69,30 +69,25 @@ class LoginGroupNav extends Widget function show() { - // action => array('prompt', 'title') - $menu = array(); + $action_name = $this->action->trimmed('action'); + + $this->action->elementStart('ul', array('class' => 'nav')); + + if (Event::handle('StartLoginGroupNav', array(&$this->action))) { + + $this->action->menuItem(common_local_url('login'), + _('Login'), + _('Login with a username and password'), + $action_name === 'login'); - if (!common_config('site','openidonly')) { - $menu['login'] = array(_('Login'), - _('Login with a username and password')); if (!(common_config('site','closed') || common_config('site','inviteonly'))) { - $menu['register'] = array(_('Register'), - _('Sign up for a new account')); + $this->action->menuItem(common_local_url('register'), + _('Register'), + _('Sign up for a new account'), + $action_name === 'register'); } - } - if (common_config('openid', 'enabled')) { - $menu['openidlogin'] = array(_('OpenID'), - _('Login or register with OpenID')); - } - - $action_name = $this->action->trimmed('action'); - $this->action->elementStart('ul', array('class' => 'nav')); - foreach ($menu as $menuaction => $menudesc) { - $this->action->menuItem(common_local_url($menuaction), - $menudesc[0], - $menudesc[1], - $action_name === $menuaction); + Event::handle('EndLoginGroupNav', array(&$this->action)); } $this->action->elementEnd('ul'); diff --git a/lib/messageform.php b/lib/messageform.php index 6431bfdcc..e25ebfa08 100644 --- a/lib/messageform.php +++ b/lib/messageform.php @@ -140,12 +140,19 @@ class MessageForm extends Form 'rows' => 4, 'name' => 'content'), ($this->content) ? $this->content : ''); - $this->out->elementStart('dl', 'form_note'); - $this->out->element('dt', null, _('Available characters')); - $this->out->element('dd', array('id' => 'notice_text-count'), - '140'); - $this->out->elementEnd('dl'); + $contentLimit = Message::maxContent(); + + $this->out->element('script', array('type' => 'text/javascript'), + 'maxLength = ' . $contentLimit . ';'); + + if ($contentLimit > 0) { + $this->out->elementStart('dl', 'form_note'); + $this->out->element('dt', null, _('Available characters')); + $this->out->element('dd', array('id' => 'notice_text-count'), + $contentLimit); + $this->out->elementEnd('dl'); + } } /** diff --git a/lib/noticeform.php b/lib/noticeform.php index 350e37db8..9864d15eb 100644 --- a/lib/noticeform.php +++ b/lib/noticeform.php @@ -90,7 +90,7 @@ class NoticeForm extends Form $this->action = $action; $this->content = $content; $this->inreplyto = $inreplyto; - + if ($user) { $this->user = $user; } else { @@ -124,7 +124,6 @@ class NoticeForm extends Form return common_local_url('newnotice'); } - /** * Legend of the Form * @@ -135,7 +134,6 @@ class NoticeForm extends Form $this->out->element('legend', null, _('Send a notice')); } - /** * Data elements * @@ -144,31 +142,44 @@ class NoticeForm extends Form function formData() { - $this->out->element('label', array('for' => 'notice_data-text'), - sprintf(_('What\'s up, %s?'), $this->user->nickname)); - // XXX: vary by defined max size - $this->out->element('textarea', array('id' => 'notice_data-text', - 'cols' => 35, - 'rows' => 4, - 'name' => 'status_textarea'), - ($this->content) ? $this->content : ''); - $this->out->elementStart('dl', 'form_note'); - $this->out->element('dt', null, _('Available characters')); - $this->out->element('dd', array('id' => 'notice_text-count'), - '140'); - $this->out->elementEnd('dl'); - if (common_config('attachments', 'uploads')) { - $this->out->element('label', array('for' => 'notice_data-attach'),_('Attach')); - $this->out->element('input', array('id' => 'notice_data-attach', - 'type' => 'file', - 'name' => 'attach', - 'title' => _('Attach a file'))); - $this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota')); - } - if ($this->action) { - $this->out->hidden('notice_return-to', $this->action, 'returnto'); + if (Event::handle('StartShowNoticeFormData', array($this))) { + $this->out->element('label', array('for' => 'notice_data-text'), + sprintf(_('What\'s up, %s?'), $this->user->nickname)); + // XXX: vary by defined max size + $this->out->element('textarea', array('id' => 'notice_data-text', + 'cols' => 35, + 'rows' => 4, + 'name' => 'status_textarea'), + ($this->content) ? $this->content : ''); + + $contentLimit = Notice::maxContent(); + + $this->out->element('script', array('type' => 'text/javascript'), + 'maxLength = ' . $contentLimit . ';'); + + if ($contentLimit > 0) { + $this->out->elementStart('dl', 'form_note'); + $this->out->element('dt', null, _('Available characters')); + $this->out->element('dd', array('id' => 'notice_text-count'), + $contentLimit); + $this->out->elementEnd('dl'); + } + + if (common_config('attachments', 'uploads')) { + $this->out->element('label', array('for' => 'notice_data-attach'),_('Attach')); + $this->out->element('input', array('id' => 'notice_data-attach', + 'type' => 'file', + 'name' => 'attach', + 'title' => _('Attach a file'))); + $this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota')); + } + if ($this->action) { + $this->out->hidden('notice_return-to', $this->action, 'returnto'); + } + $this->out->hidden('notice_in-reply-to', $this->inreplyto, 'inreplyto'); + + Event::handle('StartShowNoticeFormData', array($this)); } - $this->out->hidden('notice_in-reply-to', $this->inreplyto, 'inreplyto'); } /** diff --git a/lib/noticelist.php b/lib/noticelist.php index d4cd3ff6e..6c296f82a 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -178,9 +178,12 @@ class NoticeListItem extends Widget function show() { $this->showStart(); - $this->showNotice(); - $this->showNoticeInfo(); - $this->showNoticeOptions(); + if (Event::handle('StartShowNoticeItem', array($this))) { + $this->showNotice(); + $this->showNoticeInfo(); + $this->showNoticeOptions(); + Event::handle('EndShowNoticeItem', array($this)); + } $this->showEnd(); } @@ -469,7 +472,10 @@ class NoticeListItem extends Widget function showDeleteLink() { $user = common_current_user(); - if ($user && $this->notice->profile_id == $user->id) { + + if (!empty($user) && + ($this->notice->profile_id == $user->id || $user->hasRight(Right::deleteOthersNotice))) { + $deleteurl = common_local_url('deletenotice', array('notice' => $this->notice->id)); $this->out->element('a', array('href' => $deleteurl, diff --git a/lib/oauthstore.php b/lib/oauthstore.php index 6db07b20f..d617a7df7 100644 --- a/lib/oauthstore.php +++ b/lib/oauthstore.php @@ -19,13 +19,12 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once(INSTALLDIR.'/lib/omb.php'); +require_once 'libomb/datastore.php'; class StatusNetOAuthDataStore extends OAuthDataStore { // We keep a record of who's contacted us - function lookup_consumer($consumer_key) { $con = Consumer::staticGet('consumer_key', $consumer_key); @@ -44,7 +43,9 @@ class StatusNetOAuthDataStore extends OAuthDataStore function lookup_token($consumer, $token_type, $token_key) { $t = new Token(); - $t->consumer_key = $consumer->key; + if (!is_null($consumer)) { + $t->consumer_key = $consumer->key; + } $t->tok = $token_key; $t->type = ($token_type == 'access') ? 1 : 0; if ($t->find(true)) { @@ -154,4 +155,345 @@ class StatusNetOAuthDataStore extends OAuthDataStore { return $this->new_access_token($consumer); } + + /** + * Revoke specified OAuth token + * + * Revokes the authorization token specified by $token_key. + * Throws exceptions in case of error. + * + * @param string $token_key The token to be revoked + * + * @access public + **/ + public function revoke_token($token_key) { + $rt = new Token(); + $rt->tok = $token_key; + $rt->type = 0; + $rt->state = 0; + if (!$rt->find(true)) { + throw new Exception('Tried to revoke unknown token'); + } + if (!$rt->delete()) { + throw new Exception('Failed to delete revoked token'); + } + } + + /** + * Authorize specified OAuth token + * + * Authorizes the authorization token specified by $token_key. + * Throws exceptions in case of error. + * + * @param string $token_key The token to be authorized + * + * @access public + **/ + public function authorize_token($token_key) { + $rt = new Token(); + $rt->tok = $token_key; + $rt->type = 0; + $rt->state = 0; + if (!$rt->find(true)) { + throw new Exception('Tried to authorize unknown token'); + } + $orig_rt = clone($rt); + $rt->state = 1; # Authorized but not used + if (!$rt->update($orig_rt)) { + throw new Exception('Failed to authorize token'); + } + } + + /** + * Get profile by identifying URI + * + * Returns an OMB_Profile object representing the OMB profile identified by + * $identifier_uri. + * Returns null if there is no such OMB profile. + * Throws exceptions in case of other error. + * + * @param string $identifier_uri The OMB identifier URI specifying the + * requested profile + * + * @access public + * + * @return OMB_Profile The corresponding profile + **/ + public function getProfile($identifier_uri) { + /* getProfile is only used for remote profiles by libomb. + TODO: Make it work with local ones anyway. */ + $remote = Remote_profile::staticGet('uri', $identifier_uri); + if (!$remote) throw new Exception('No such remote profile'); + $profile = Profile::staticGet('id', $remote->id); + if (!$profile) throw new Exception('No profile for remote user'); + + require_once INSTALLDIR.'/lib/omb.php'; + return profile_to_omb_profile($identifier_uri, $profile); + } + + /** + * Save passed profile + * + * Stores the OMB profile $profile. Overwrites an existing entry. + * Throws exceptions in case of error. + * + * @param OMB_Profile $profile The OMB profile which should be saved + * + * @access public + **/ + public function saveProfile($omb_profile) { + if (common_profile_url($omb_profile->getNickname()) == + $omb_profile->getProfileURL()) { + throw new Exception('Not implemented'); + } else { + $remote = Remote_profile::staticGet('uri', $omb_profile->getIdentifierURI()); + + if ($remote) { + $exists = true; + $profile = Profile::staticGet($remote->id); + $orig_remote = clone($remote); + $orig_profile = clone($profile); + # XXX: compare current postNotice and updateProfile URLs to the ones + # stored in the DB to avoid (possibly...) above attack + } else { + $exists = false; + $remote = new Remote_profile(); + $remote->uri = $omb_profile->getIdentifierURI(); + $profile = new Profile(); + } + + $profile->nickname = $omb_profile->getNickname(); + $profile->profileurl = $omb_profile->getProfileURL(); + + $fullname = $omb_profile->getFullname(); + $profile->fullname = is_null($fullname) ? '' : $fullname; + $homepage = $omb_profile->getHomepage(); + $profile->homepage = is_null($homepage) ? '' : $homepage; + $bio = $omb_profile->getBio(); + $profile->bio = is_null($bio) ? '' : $bio; + $location = $omb_profile->getLocation(); + $profile->location = is_null($location) ? '' : $location; + + if ($exists) { + $profile->update($orig_profile); + } else { + $profile->created = DB_DataObject_Cast::dateTime(); # current time + $id = $profile->insert(); + if (!$id) { + throw new Exception(_('Error inserting new profile')); + } + $remote->id = $id; + } + + $avatar_url = $omb_profile->getAvatarURL(); + if ($avatar_url) { + if (!$this->add_avatar($profile, $avatar_url)) { + throw new Exception(_('Error inserting avatar')); + } + } else { + $avatar = $profile->getOriginalAvatar(); + if($avatar) $avatar->delete(); + $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); + if($avatar) $avatar->delete(); + $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE); + if($avatar) $avatar->delete(); + $avatar = $profile->getAvatar(AVATAR_MINI_SIZE); + if($avatar) $avatar->delete(); + } + + if ($exists) { + if (!$remote->update($orig_remote)) { + throw new Exception(_('Error updating remote profile')); + } + } else { + $remote->created = DB_DataObject_Cast::dateTime(); # current time + if (!$remote->insert()) { + throw new Exception(_('Error inserting remote profile')); + } + } + } + } + + function add_avatar($profile, $url) + { + $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar'); + copy($url, $temp_filename); + $imagefile = new ImageFile($profile->id, $temp_filename); + $filename = Avatar::filename($profile->id, + image_type_to_extension($imagefile->type), + null, + common_timestamp()); + rename($temp_filename, Avatar::path($filename)); + return $profile->setOriginal($filename); + } + + /** + * Save passed notice + * + * Stores the OMB notice $notice. The datastore may change the passed notice. + * This might by neccessary for URIs depending on a database key. Note that + * it is the user’s duty to present a mechanism for his OMB_Datastore to + * appropriately change his OMB_Notice. + * Throws exceptions in case of error. + * + * @param OMB_Notice $notice The OMB notice which should be saved + * + * @access public + **/ + public function saveNotice(&$omb_notice) { + if (Notice::staticGet('uri', $omb_notice->getIdentifierURI())) { + throw new Exception(_('Duplicate notice')); + } + $author_uri = $omb_notice->getAuthor()->getIdentifierURI(); + common_log(LOG_DEBUG, $author_uri, __FILE__); + $author = Remote_profile::staticGet('uri', $author_uri); + if (!$author) { + $author = User::staticGet('uri', $author_uri); + } + if (!$author) { + throw new Exception('No such user'); + } + + common_log(LOG_DEBUG, print_r($author, true), __FILE__); + + $notice = Notice::saveNew($author->id, + $omb_notice->getContent(), + 'omb', + false, + null, + $omb_notice->getIdentifierURI()); + + common_broadcast_notice($notice, true); + } + + /** + * Get subscriptions of a given profile + * + * Returns an array containing subscription informations for the specified + * profile. Every array entry should in turn be an array with keys + * 'uri´: The identifier URI of the subscriber + * 'token´: The subscribe token + * 'secret´: The secret token + * Throws exceptions in case of error. + * + * @param string $subscribed_user_uri The OMB identifier URI specifying the + * subscribed profile + * + * @access public + * + * @return mixed An array containing the subscriptions or 0 if no + * subscription has been found. + **/ + public function getSubscriptions($subscribed_user_uri) { + $sub = new Subscription(); + + $user = $this->_getAnyProfile($subscribed_user_uri); + + $sub->subscribed = $user->id; + + if (!$sub->find(true)) { + return 0; + } + + /* Since we do not use OMB_Service_Provider’s action methods, there + is no need to actually return the subscriptions. */ + return 1; + } + + private function _getAnyProfile($uri) + { + $user = Remote_profile::staticGet('uri', $uri); + if (!$user) { + $user = User::staticGet('uri', $uri); + } + if (!$user) { + throw new Exception('No such user'); + } + return $user; + } + + /** + * Delete a subscription + * + * Deletes the subscription from $subscriber_uri to $subscribed_user_uri. + * Throws exceptions in case of error. + * + * @param string $subscriber_uri The OMB identifier URI specifying the + * subscribing profile + * + * @param string $subscribed_user_uri The OMB identifier URI specifying the + * subscribed profile + * + * @access public + **/ + public function deleteSubscription($subscriber_uri, $subscribed_user_uri) + { + $sub = new Subscription(); + + $subscribed = $this->_getAnyProfile($subscribed_user_uri); + $subscriber = $this->_getAnyProfile($subscriber_uri); + + $sub->subscribed = $subscribed->id; + $sub->subscriber = $subscriber->id; + + $sub->delete(); + } + + /** + * Save a subscription + * + * Saves the subscription from $subscriber_uri to $subscribed_user_uri. + * Throws exceptions in case of error. + * + * @param string $subscriber_uri The OMB identifier URI specifying + * the subscribing profile + * + * @param string $subscribed_user_uri The OMB identifier URI specifying + * the subscribed profile + * @param OAuthToken $token The access token + * + * @access public + **/ + public function saveSubscription($subscriber_uri, $subscribed_user_uri, + $token) + { + $sub = new Subscription(); + + $subscribed = $this->_getAnyProfile($subscribed_user_uri); + $subscriber = $this->_getAnyProfile($subscriber_uri); + + $sub->subscribed = $subscribed->id; + $sub->subscriber = $subscriber->id; + + $sub_exists = $sub->find(true); + + if ($sub_exists) { + $orig_sub = clone($sub); + } else { + $sub->created = DB_DataObject_Cast::dateTime(); + } + + $sub->token = $token->key; + $sub->secret = $token->secret; + + if ($sub_exists) { + $result = $sub->update($orig_sub); + } else { + $result = $sub->insert(); + } + + if (!$result) { + common_log_db_error($sub, ($sub_exists) ? 'UPDATE' : 'INSERT', __FILE__); + throw new Exception(_('Couldn\'t insert new subscription.')); + return; + } + + /* Notify user, if necessary. */ + + if ($subscribed instanceof User) { + mail_subscribe_notify_profile($subscribed, + Profile::staticGet($subscriber->id)); + } + } } +?> diff --git a/lib/omb.php b/lib/omb.php index 7dca760c6..0566701ff 100644 --- a/lib/omb.php +++ b/lib/omb.php @@ -19,34 +19,18 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once('OAuth.php'); -require_once(INSTALLDIR.'/lib/oauthstore.php'); - -require_once(INSTALLDIR.'/classes/Consumer.php'); -require_once(INSTALLDIR.'/classes/Nonce.php'); -require_once(INSTALLDIR.'/classes/Token.php'); - -require_once('Auth/Yadis/Yadis.php'); - -define('OAUTH_NAMESPACE', 'http://oauth.net/core/1.0/'); -define('OMB_NAMESPACE', 'http://openmicroblogging.org/protocol/0.1'); -define('OMB_VERSION_01', 'http://openmicroblogging.org/protocol/0.1'); -define('OAUTH_DISCOVERY', 'http://oauth.net/discovery/1.0'); - -define('OMB_ENDPOINT_UPDATEPROFILE', OMB_NAMESPACE.'/updateProfile'); -define('OMB_ENDPOINT_POSTNOTICE', OMB_NAMESPACE.'/postNotice'); -define('OAUTH_ENDPOINT_REQUEST', OAUTH_NAMESPACE.'endpoint/request'); -define('OAUTH_ENDPOINT_AUTHORIZE', OAUTH_NAMESPACE.'endpoint/authorize'); -define('OAUTH_ENDPOINT_ACCESS', OAUTH_NAMESPACE.'endpoint/access'); -define('OAUTH_ENDPOINT_RESOURCE', OAUTH_NAMESPACE.'endpoint/resource'); -define('OAUTH_AUTH_HEADER', OAUTH_NAMESPACE.'parameters/auth-header'); -define('OAUTH_POST_BODY', OAUTH_NAMESPACE.'parameters/post-body'); -define('OAUTH_HMAC_SHA1', OAUTH_NAMESPACE.'signature/HMAC-SHA1'); +require_once INSTALLDIR.'/lib/oauthstore.php'; +require_once 'OAuth.php'; +require_once 'libomb/constants.php'; +require_once 'libomb/service_consumer.php'; +require_once 'libomb/notice.php'; +require_once 'libomb/profile.php'; +require_once 'Auth/Yadis/Yadis.php'; function omb_oauth_consumer() { static $con = null; - if (!$con) { + if (is_null($con)) { $con = new OAuthConsumer(common_root_url(), ''); } return $con; @@ -55,7 +39,7 @@ function omb_oauth_consumer() function omb_oauth_server() { static $server = null; - if (!$server) { + if (is_null($server)) { $server = new OAuthServer(omb_oauth_datastore()); $server->add_signature_method(omb_hmac_sha1()); } @@ -65,7 +49,7 @@ function omb_oauth_server() function omb_oauth_datastore() { static $store = null; - if (!$store) { + if (is_null($store)) { $store = new StatusNetOAuthDataStore(); } return $store; @@ -74,57 +58,18 @@ function omb_oauth_datastore() function omb_hmac_sha1() { static $hmac_method = null; - if (!$hmac_method) { + if (is_null($hmac_method)) { $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); } return $hmac_method; } -function omb_get_services($xrd, $type) +function omb_broadcast_notice($notice) { - return $xrd->services(array(omb_service_filter($type))); -} - -function omb_service_filter($type) -{ - return create_function('$s', - 'return omb_match_service($s, \''.$type.'\');'); -} - -function omb_match_service($service, $type) -{ - return in_array($type, $service->getTypes()); -} - -function omb_service_uri($service) -{ - if (!$service) { - return null; - } - $uris = $service->getURIs(); - if (!$uris) { - return null; - } - return $uris[0]; -} - -function omb_local_id($service) -{ - if (!$service) { - return null; - } - $els = $service->getElements('xrd:LocalID'); - if (!$els) { - return null; - } - $el = $els[0]; - return $service->parser->content($el); -} -function omb_broadcast_remote_subscribers($notice) -{ + $omb_notice = notice_to_omb_notice($notice); - # First, get remote users subscribed to this profile + /* Get remote users subscribed to this profile. */ $rp = new Remote_profile(); $rp->query('SELECT postnoticeurl, token, secret ' . @@ -135,170 +80,148 @@ function omb_broadcast_remote_subscribers($notice) $posted = array(); while ($rp->fetch()) { - if (!array_key_exists($rp->postnoticeurl, $posted)) { - common_log(LOG_DEBUG, 'Posting to ' . $rp->postnoticeurl); - if (omb_post_notice_keys($notice, $rp->postnoticeurl, $rp->token, $rp->secret)) { - common_log(LOG_DEBUG, 'Finished to ' . $rp->postnoticeurl); - $posted[$rp->postnoticeurl] = true; - } else { - common_log(LOG_DEBUG, 'Failed posting to ' . $rp->postnoticeurl); - } + if (isset($posted[$rp->postnoticeurl])) { + /* We already posted to this url. */ + continue; } - } - - $rp->free(); - unset($rp); + common_debug('Posting to ' . $rp->postnoticeurl, __FILE__); + + /* Post notice. */ + $service = new Laconica_OMB_Service_Consumer( + array(OMB_ENDPOINT_POSTNOTICE => $rp->postnoticeurl)); + try { + $service->setToken($rp->token, $rp->secret); + $service->postNotice($omb_notice); + } catch (Exception $e) { + common_log(LOG_ERR, 'Failed posting to ' . $rp->postnoticeurl); + common_log(LOG_ERR, 'Error status '.$e); + continue; + } + $posted[$rp->postnoticeurl] = true; - return true; -} + common_debug('Finished to ' . $rp->postnoticeurl, __FILE__); + } -function omb_post_notice($notice, $remote_profile, $subscription) -{ - return omb_post_notice_keys($notice, $remote_profile->postnoticeurl, $subscription->token, $subscription->secret); + return; } -function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret) +function omb_broadcast_profile($profile) { - $user = User::staticGet('id', $notice->profile_id); + $user = User::staticGet('id', $profile->id); if (!$user) { return false; } - $con = omb_oauth_consumer(); + $profile = $user->getProfile(); - $token = new OAuthToken($tk, $secret); - - $url = $postnoticeurl; - $parsed = parse_url($url); - $params = array(); - parse_str($parsed['query'], $params); - - $req = OAuthRequest::from_consumer_and_token($con, $token, - 'POST', $url, $params); - - $req->set_parameter('omb_version', OMB_VERSION_01); - $req->set_parameter('omb_listenee', $user->uri); - $req->set_parameter('omb_notice', $notice->uri); - $req->set_parameter('omb_notice_content', $notice->content); - $req->set_parameter('omb_notice_url', common_local_url('shownotice', - array('notice' => - $notice->id))); - $req->set_parameter('omb_notice_license', common_config('license', 'url')); + $omb_profile = profile_to_omb_profile($user->uri, $profile, true); - $user->free(); - unset($user); + /* Get remote users subscribed to this profile. */ + $rp = new Remote_profile(); - $req->sign_request(omb_hmac_sha1(), $con, $token); + $rp->query('SELECT updateprofileurl, token, secret ' . + 'FROM subscription JOIN remote_profile ' . + 'ON subscription.subscriber = remote_profile.id ' . + 'WHERE subscription.subscribed = ' . $profile->id . ' '); - # We re-use this tool's fetcher, since it's pretty good + $posted = array(); - $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); + while ($rp->fetch()) { + if (isset($posted[$rp->updateprofileurl])) { + /* We already posted to this url. */ + continue; + } + common_debug('Posting to ' . $rp->updateprofileurl, __FILE__); + + /* Update profile. */ + $service = new StatusNet_OMB_Service_Consumer( + array(OMB_ENDPOINT_UPDATEPROFILE => $rp->updateprofileurl)); + try { + $service->setToken($rp->token, $rp->secret); + $service->updateProfile($omb_profile); + } catch (Exception $e) { + common_log(LOG_ERR, 'Failed posting to ' . $rp->updateprofileurl); + common_log(LOG_ERR, 'Error status '.$e); + continue; + } + $posted[$rp->updateprofileurl] = true; - if (!$fetcher) { - common_log(LOG_WARNING, 'Failed to initialize Yadis fetcher.', __FILE__); - return false; + common_debug('Finished to ' . $rp->updateprofileurl, __FILE__); } - $result = $fetcher->post($req->get_normalized_http_url(), - $req->to_postdata(), - array('User-Agent: StatusNet/' . STATUSNET_VERSION)); - - if ($result->status == 403) { # not authorized, don't send again - common_debug('403 result, deleting subscription', __FILE__); - # FIXME: figure out how to delete this - # $subscription->delete(); - return false; - } else if ($result->status != 200) { - common_debug('Error status '.$result->status, __FILE__); - return false; - } else { # success! - parse_str($result->body, $return); - if ($return['omb_version'] == OMB_VERSION_01) { - return true; - } else { - return false; - } - } + return; } -function omb_broadcast_profile($profile) -{ - # First, get remote users subscribed to this profile - # XXX: use a join here rather than looping through results - $sub = new Subscription(); - $sub->subscribed = $profile->id; - if ($sub->find()) { - $updated = array(); - while ($sub->fetch()) { - $rp = Remote_profile::staticGet('id', $sub->subscriber); - if ($rp) { - if (!array_key_exists($rp->updateprofileurl, $updated)) { - if (omb_update_profile($profile, $rp, $sub)) { - $updated[$rp->updateprofileurl] = true; - } - } - } - } +class StatusNet_OMB_Service_Consumer extends OMB_Service_Consumer { + public function __construct($urls) + { + $this->services = $urls; + $this->datastore = omb_oauth_datastore(); + $this->oauth_consumer = omb_oauth_consumer(); + $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); } + } -function omb_update_profile($profile, $remote_profile, $subscription) +function profile_to_omb_profile($uri, $profile, $force = false) { - $user = User::staticGet($profile->id); - $con = omb_oauth_consumer(); - $token = new OAuthToken($subscription->token, $subscription->secret); - $url = $remote_profile->updateprofileurl; - $parsed = parse_url($url); - $params = array(); - parse_str($parsed['query'], $params); - $req = OAuthRequest::from_consumer_and_token($con, $token, - "POST", $url, $params); - $req->set_parameter('omb_version', OMB_VERSION_01); - $req->set_parameter('omb_listenee', $user->uri); - $req->set_parameter('omb_listenee_profile', common_profile_url($profile->nickname)); - $req->set_parameter('omb_listenee_nickname', $profile->nickname); - - # We use blanks to force emptying any existing values in these optional fields - - $req->set_parameter('omb_listenee_fullname', - ($profile->fullname) ? $profile->fullname : ''); - $req->set_parameter('omb_listenee_homepage', - ($profile->homepage) ? $profile->homepage : ''); - $req->set_parameter('omb_listenee_bio', - ($profile->bio) ? $profile->bio : ''); - $req->set_parameter('omb_listenee_location', - ($profile->location) ? $profile->location : ''); + $omb_profile = new OMB_Profile($uri); + $omb_profile->setNickname($profile->nickname); + $omb_profile->setLicenseURL(common_config('license', 'url')); + if (!is_null($profile->fullname)) { + $omb_profile->setFullname($profile->fullname); + } elseif ($force) { + $omb_profile->setFullname(''); + } + if (!is_null($profile->homepage)) { + $omb_profile->setHomepage($profile->homepage); + } elseif ($force) { + $omb_profile->setHomepage(''); + } + if (!is_null($profile->bio)) { + $omb_profile->setBio($profile->bio); + } elseif ($force) { + $omb_profile->setBio(''); + } + if (!is_null($profile->location)) { + $omb_profile->setLocation($profile->location); + } elseif ($force) { + $omb_profile->setLocation(''); + } + if (!is_null($profile->profileurl)) { + $omb_profile->setProfileURL($profile->profileurl); + } elseif ($force) { + $omb_profile->setProfileURL(''); + } $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); - $req->set_parameter('omb_listenee_avatar', - ($avatar) ? $avatar->url : ''); + if ($avatar) { + $omb_profile->setAvatarURL($avatar->url); + } elseif ($force) { + $omb_profile->setAvatarURL(''); + } + return $omb_profile; +} - $req->sign_request(omb_hmac_sha1(), $con, $token); +function notice_to_omb_notice($notice) +{ + /* Create an OMB_Notice for $notice. */ + $user = User::staticGet('id', $notice->profile_id); - # We re-use this tool's fetcher, since it's pretty good + if (!$user) { + return null; + } - $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); + $profile = $user->getProfile(); - $result = $fetcher->post($req->get_normalized_http_url(), - $req->to_postdata(), - array('User-Agent: StatusNet/' . STATUSNET_VERSION)); + $omb_notice = new OMB_Notice(profile_to_omb_profile($user->uri, $profile), + $notice->uri, + $notice->content); + $omb_notice->setURL(common_local_url('shownotice', array('notice' => + $notice->id))); + $omb_notice->setLicenseURL(common_config('license', 'url')); - if (empty($result) || !$result) { - common_debug("Unable to contact " . $req->get_normalized_http_url()); - } else if ($result->status == 403) { # not authorized, don't send again - common_debug('403 result, deleting subscription', __FILE__); - $subscription->delete(); - return false; - } else if ($result->status != 200) { - common_debug('Error status '.$result->status, __FILE__); - return false; - } else { # success! - parse_str($result->body, $return); - if (isset($return['omb_version']) && $return['omb_version'] === OMB_VERSION_01) { - return true; - } else { - return false; - } - } + return $omb_notice; } +?> diff --git a/lib/openid.php b/lib/openid.php deleted file mode 100644 index 7a2c46f00..000000000 --- a/lib/openid.php +++ /dev/null @@ -1,280 +0,0 @@ -<?php -/* - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -require_once(INSTALLDIR.'/classes/User_openid.php'); - -require_once('Auth/OpenID.php'); -require_once('Auth/OpenID/Consumer.php'); -require_once('Auth/OpenID/SReg.php'); -require_once('Auth/OpenID/MySQLStore.php'); - -# About one year cookie expiry - -define('OPENID_COOKIE_EXPIRY', round(365.25 * 24 * 60 * 60)); -define('OPENID_COOKIE_KEY', 'lastusedopenid'); - -function oid_store() -{ - static $store = null; - if (!$store) { - # Can't be called statically - $user = new User(); - $conn = $user->getDatabaseConnection(); - $store = new Auth_OpenID_MySQLStore($conn); - } - return $store; -} - -function oid_consumer() -{ - $store = oid_store(); - $consumer = new Auth_OpenID_Consumer($store); - return $consumer; -} - -function oid_clear_last() -{ - oid_set_last(''); -} - -function oid_set_last($openid_url) -{ - common_set_cookie(OPENID_COOKIE_KEY, - $openid_url, - time() + OPENID_COOKIE_EXPIRY); -} - -function oid_get_last() -{ - if (empty($_COOKIE[OPENID_COOKIE_KEY])) { - return null; - } - $openid_url = $_COOKIE[OPENID_COOKIE_KEY]; - if ($openid_url && strlen($openid_url) > 0) { - return $openid_url; - } else { - return null; - } -} - -function oid_link_user($id, $canonical, $display) -{ - - $oid = new User_openid(); - $oid->user_id = $id; - $oid->canonical = $canonical; - $oid->display = $display; - $oid->created = DB_DataObject_Cast::dateTime(); - - if (!$oid->insert()) { - $err = PEAR::getStaticProperty('DB_DataObject','lastError'); - common_debug('DB error ' . $err->code . ': ' . $err->message, __FILE__); - return false; - } - - return true; -} - -function oid_get_user($openid_url) -{ - $user = null; - $oid = User_openid::staticGet('canonical', $openid_url); - if ($oid) { - $user = User::staticGet('id', $oid->user_id); - } - return $user; -} - -function oid_check_immediate($openid_url, $backto=null) -{ - if (!$backto) { - $action = $_REQUEST['action']; - $args = common_copy_args($_GET); - unset($args['action']); - $backto = common_local_url($action, $args); - } - common_debug('going back to "' . $backto . '"', __FILE__); - - common_ensure_session(); - - $_SESSION['openid_immediate_backto'] = $backto; - common_debug('passed-in variable is "' . $backto . '"', __FILE__); - common_debug('session variable is "' . $_SESSION['openid_immediate_backto'] . '"', __FILE__); - - oid_authenticate($openid_url, - 'finishimmediate', - true); -} - -function oid_authenticate($openid_url, $returnto, $immediate=false) -{ - - $consumer = oid_consumer(); - - if (!$consumer) { - common_server_error(_('Cannot instantiate OpenID consumer object.')); - return false; - } - - common_ensure_session(); - - $auth_request = $consumer->begin($openid_url); - - // Handle failure status return values. - if (!$auth_request) { - return _('Not a valid OpenID.'); - } else if (Auth_OpenID::isFailure($auth_request)) { - return sprintf(_('OpenID failure: %s'), $auth_request->message); - } - - $sreg_request = Auth_OpenID_SRegRequest::build(// Required - array(), - // Optional - array('nickname', - 'email', - 'fullname', - 'language', - 'timezone', - 'postcode', - 'country')); - - if ($sreg_request) { - $auth_request->addExtension($sreg_request); - } - - $trust_root = common_root_url(true); - $process_url = common_local_url($returnto); - - if ($auth_request->shouldSendRedirect()) { - $redirect_url = $auth_request->redirectURL($trust_root, - $process_url, - $immediate); - if (!$redirect_url) { - } else if (Auth_OpenID::isFailure($redirect_url)) { - return sprintf(_('Could not redirect to server: %s'), $redirect_url->message); - } else { - common_redirect($redirect_url, 303); - } - } else { - // Generate form markup and render it. - $form_id = 'openid_message'; - $form_html = $auth_request->formMarkup($trust_root, $process_url, - $immediate, array('id' => $form_id)); - - # XXX: This is cheap, but things choke if we don't escape ampersands - # in the HTML attributes - - $form_html = preg_replace('/&/', '&', $form_html); - - // Display an error if the form markup couldn't be generated; - // otherwise, render the HTML. - if (Auth_OpenID::isFailure($form_html)) { - common_server_error(sprintf(_('Could not create OpenID form: %s'), $form_html->message)); - } else { - $action = new AutosubmitAction(); // see below - $action->form_html = $form_html; - $action->form_id = $form_id; - $action->prepare(array('action' => 'autosubmit')); - $action->handle(array('action' => 'autosubmit')); - } - } -} - -# Half-assed attempt at a module-private function - -function _oid_print_instructions() -{ - common_element('div', 'instructions', - _('This form should automatically submit itself. '. - 'If not, click the submit button to go to your '. - 'OpenID provider.')); -} - -# update a user from sreg parameters - -function oid_update_user(&$user, &$sreg) -{ - - $profile = $user->getProfile(); - - $orig_profile = clone($profile); - - if ($sreg['fullname'] && strlen($sreg['fullname']) <= 255) { - $profile->fullname = $sreg['fullname']; - } - - if ($sreg['country']) { - if ($sreg['postcode']) { - # XXX: use postcode to get city and region - # XXX: also, store postcode somewhere -- it's valuable! - $profile->location = $sreg['postcode'] . ', ' . $sreg['country']; - } else { - $profile->location = $sreg['country']; - } - } - - # XXX save language if it's passed - # XXX save timezone if it's passed - - if (!$profile->update($orig_profile)) { - common_server_error(_('Error saving the profile.')); - return false; - } - - $orig_user = clone($user); - - if ($sreg['email'] && Validate::email($sreg['email'], true)) { - $user->email = $sreg['email']; - } - - if (!$user->update($orig_user)) { - common_server_error(_('Error saving the user.')); - return false; - } - - return true; -} - -class AutosubmitAction extends Action -{ - var $form_html = null; - var $form_id = null; - - function handle($args) - { - parent::handle($args); - $this->showPage(); - } - - function title() - { - return _('OpenID Auto-Submit'); - } - - function showContent() - { - $this->raw($this->form_html); - $this->element('script', null, - '$(document).ready(function() { ' . - ' $(\'#'. $this->form_id .'\').submit(); '. - '});'); - } -} diff --git a/lib/plugin.php b/lib/plugin.php index 87d7be5a7..59bf3ba9d 100644 --- a/lib/plugin.php +++ b/lib/plugin.php @@ -76,4 +76,18 @@ class Plugin { return true; } + + /* + * the name of the shortener + * shortenerInfo associative array with additional information. One possible element is 'freeService' which can be true or false + * shortener array, first element is the name of the class, second element is an array to be passed as constructor parameters to the class + */ + function registerUrlShortener($name, $shortenerInfo, $shortener) + { + global $_shorteners; + if(!is_array($_shorteners)){ + $_shorteners=array(); + } + $_shorteners[$name]=array('info'=>$shortenerInfo, 'callInfo'=>$shortener); + } } diff --git a/lib/right.php b/lib/right.php new file mode 100644 index 000000000..4e0096d46 --- /dev/null +++ b/lib/right.php @@ -0,0 +1,50 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Class for user rights + * + * 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 Authorization + * @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') && !defined('LACONICA')) { + exit(1); +} + +/** + * class for rights + * + * Mostly for holding the rights constants + * + * @category Authorization + * @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 Right +{ + const deleteOthersNotice = 'deleteothersnotice'; +} + diff --git a/lib/router.php b/lib/router.php index 5529e60ac..91f886bce 100644 --- a/lib/router.php +++ b/lib/router.php @@ -50,8 +50,7 @@ class Router var $m = null; static $inst = null; static $bare = array('requesttoken', 'accesstoken', 'userauthorization', - 'postnotice', 'updateprofile', 'finishremotesubscribe', - 'finishopenidlogin', 'finishaddopenid'); + 'postnotice', 'updateprofile', 'finishremotesubscribe'); static function get() { @@ -76,7 +75,6 @@ class Router $m->connect('', array('action' => 'public')); $m->connect('rss', array('action' => 'publicrss')); - $m->connect('xrds', array('action' => 'publicxrds')); $m->connect('featuredrss', array('action' => 'featuredrss')); $m->connect('favoritedrss', array('action' => 'favoritedrss')); $m->connect('opensearch/people', array('action' => 'opensearch', @@ -128,7 +126,6 @@ class Router // exceptional - $m->connect('main/openid', array('action' => 'openidlogin')); $m->connect('main/remote', array('action' => 'remotesubscribe')); $m->connect('main/remote?nickname=:nickname', array('action' => 'remotesubscribe'), array('nickname' => '[A-Za-z0-9_-]+')); @@ -138,7 +135,7 @@ class Router // settings - foreach (array('profile', 'avatar', 'password', 'openid', 'im', + foreach (array('profile', 'avatar', 'password', 'im', 'email', 'sms', 'twitter', 'userdesign', 'other') as $s) { $m->connect('settings/'.$s, array('action' => $s.'settings')); } @@ -244,6 +241,10 @@ class Router array('nickname' => '[a-zA-Z0-9]+')); } + $m->connect('group/:nickname/foaf', + array('action' => 'foafgroup'), + array('nickname' => '[a-zA-Z0-9]+')); + $m->connect('group/:nickname/blocked', array('action' => 'blockedfromgroup'), array('nickname' => '[a-zA-Z0-9]+')); @@ -463,7 +464,7 @@ class Router // user stuff foreach (array('subscriptions', 'subscribers', - 'nudge', 'xrds', 'all', 'foaf', + 'nudge', 'all', 'foaf', 'xrds', 'replies', 'inbox', 'outbox', 'microsummary') as $a) { $m->connect(':nickname/'.$a, array('action' => $a), diff --git a/lib/rssaction.php b/lib/rssaction.php index 60611e48d..faf6bec7d 100644 --- a/lib/rssaction.php +++ b/lib/rssaction.php @@ -78,25 +78,12 @@ class Rss10Action extends Action function prepare($args) { parent::prepare($args); + $this->limit = (int) $this->trimmed('limit'); + if ($this->limit == 0) { $this->limit = DEFAULT_RSS_LIMIT; } - return true; - } - - /** - * Handle a request - * - * @param array $args Arguments from $_REQUEST - * - * @return void - */ - - function handle($args) - { - // Parent handling, including cache check - parent::handle($args); if (common_config('site', 'private')) { if (!isset($_SERVER['PHP_AUTH_USER'])) { @@ -122,8 +109,21 @@ class Rss10Action extends Action } } - // Get the list of notices - $this->notices = $this->getNotices($this->limit); + return true; + } + + /** + * Handle a request + * + * @param array $args Arguments from $_REQUEST + * + * @return void + */ + + function handle($args) + { + // Parent handling, including cache check + parent::handle($args); $this->showRss(); } @@ -140,7 +140,7 @@ class Rss10Action extends Action } /** - * Get the notices to output in this stream + * Get the notices to output in this stream. * * @return array an array of Notice objects sorted in reverse chron */ @@ -258,26 +258,27 @@ class Rss10Action extends Action $attachments = $notice->attachments(); if($attachments){ foreach($attachments as $attachment){ - if ($attachment->isEnclosure()) { + $enclosure=$attachment->getEnclosure(); + if ($enclosure) { // DO NOT move xmlns declaration to root element. Making it // the default namespace here improves compatibility with // real-world feed readers. $attribs = array( - 'rdf:resource' => $attachment->url, - 'url' => $attachment->url, + 'rdf:resource' => $enclosure->url, + 'url' => $enclosure->url, 'xmlns' => 'http://purl.oclc.org/net/rss_2.0/enc#' ); - if ($attachment->title) { - $attribs['dc:title'] = $attachment->title; + if ($enclosure->title) { + $attribs['dc:title'] = $enclosure->title; } - if ($attachment->modified) { - $attribs['dc:date'] = common_date_w3dtf($attachment->modified); + if ($enclosure->modified) { + $attribs['dc:date'] = common_date_w3dtf($enclosure->modified); } - if ($attachment->size) { - $attribs['length'] = $attachment->size; + if ($enclosure->size) { + $attribs['length'] = $enclosure->size; } - if ($attachment->mimetype) { - $attribs['type'] = $attachment->mimetype; + if ($enclosure->mimetype) { + $attribs['type'] = $enclosure->mimetype; } $this->element('enclosure', $attribs); } diff --git a/lib/schema.php b/lib/schema.php new file mode 100644 index 000000000..1e0c1f3e9 --- /dev/null +++ b/lib/schema.php @@ -0,0 +1,680 @@ +<?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 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 "; + } + + return $sql; + } +} + +/** + * A class encapsulating the structure of a table. + * + * @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 TableDef +{ + /** name of the table */ + public $name; + /** array of ColumnDef objects for the columns. */ + public $columns; +} + +/** + * A class encapsulating the structure of a column in a table. + * + * @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 ColumnDef +{ + /** name of the column. */ + public $name; + /** type of column, e.g. 'int', 'varchar' */ + public $type; + /** size of the column. */ + public $size; + /** boolean flag; can it be null? */ + public $nullable; + /** + * type of key: null = no key; 'PRI' => primary; + * 'UNI' => unique key; 'MUL' => multiple values. + */ + public $key; + /** default value if any. */ + public $default; + /** 'extra' stuff. Returned by MySQL, largely + * unused. */ + public $extra; + + /** + * Constructor. + * + * @param string $name name of the column + * @param string $type type of the column + * @param int $size size of the column + * @param boolean $nullable can this be null? + * @param string $key type of key + * @param value $default default value + * @param value $extra unused + */ + + function __construct($name=null, $type=null, $size=null, + $nullable=true, $key=null, $default=null, + $extra=null) + { + $this->name = strtolower($name); + $this->type = strtolower($type); + $this->size = $size+0; + $this->nullable = $nullable; + $this->key = $key; + $this->default = $default; + $this->extra = $extra; + } + + /** + * Compares this columndef with another to see + * if they're functionally equivalent. + * + * @param ColumnDef $other column to compare + * + * @return boolean true if equivalent, otherwise false. + */ + + function equals($other) + { + return ($this->name == $other->name && + $this->_typeMatch($other) && + $this->_defaultMatch($other) && + $this->_nullMatch($other) && + $this->key == $other->key); + } + + /** + * Does the type of this column match the + * type of the other column? + * + * Checks the type and size of a column. Tries + * to ignore differences between synonymous + * data types, like 'integer' and 'int'. + * + * @param ColumnDef $other other column to check + * + * @return boolean true if they're about equivalent + */ + + private function _typeMatch($other) + { + switch ($this->type) { + case 'integer': + case 'int': + return ($other->type == 'integer' || + $other->type == 'int'); + break; + default: + return ($this->type == $other->type && + $this->size == $other->size); + } + } + + /** + * Does the default behaviour of this column match + * the other? + * + * @param ColumnDef $other other column to check + * + * @return boolean true if defaults are effectively the same. + */ + + private function _defaultMatch($other) + { + return ((is_null($this->default) && is_null($other->default)) || + ($this->default == $other->default)); + } + + /** + * Does the null behaviour of this column match + * the other? + * + * @param ColumnDef $other other column to check + * + * @return boolean true if these columns 'null' the same. + */ + + private function _nullMatch($other) + { + return ((!is_null($this->default) && !is_null($other->default) && + $this->default == $other->default) || + ($this->nullable == $other->nullable)); + } +} diff --git a/lib/settingsaction.php b/lib/settingsaction.php index a1f305f5b..c3669868d 100644 --- a/lib/settingsaction.php +++ b/lib/settingsaction.php @@ -77,9 +77,7 @@ class SettingsAction extends CurrentUserDesignAction // _all_ our settings are important common_set_returnto($this->selfUrl()); $user = common_current_user(); - if ($user->hasOpenID()) { - common_redirect(common_local_url('openidlogin'), 303); - } else { + if (Event::handle('RedirectToLogin', array($this, $user))) { common_redirect(common_local_url('login'), 303); } } else if ($_SERVER['REQUEST_METHOD'] == 'POST') { diff --git a/lib/twitter.php b/lib/twitter.php index 676c9b20a..b49e2e119 100644 --- a/lib/twitter.php +++ b/lib/twitter.php @@ -165,9 +165,10 @@ function broadcast_twitter($notice) } function broadcast_oauth($notice, $flink) { - $user = $flink->getUser(); $statustxt = format_status($notice); + // Convert !groups to #hashes + $statustxt = preg_replace('/(^|\s)!([A-Za-z0-9]{1,64})/', "\\1#\\2", $statustxt); $token = TwitterOAuthClient::unpackToken($flink->credentials); $client = new TwitterOAuthClient($token->key, $token->secret); $status = null; @@ -222,6 +223,10 @@ function broadcast_basicauth($notice, $flink) $user->nickname, $user->id); common_log(LOG_WARNING, $errmsg); + $errmsg = sprintf('No data returned by Twitter API when ' . + 'trying to send update for %1$s (user id %2$s).', + $user->nickname, $user->id); + common_log(LOG_WARNING, $errmsg); return false; } diff --git a/lib/twitterapi.php b/lib/twitterapi.php index 3bac400e2..4a5de6ab3 100644 --- a/lib/twitterapi.php +++ b/lib/twitterapi.php @@ -274,11 +274,12 @@ class TwitterapiAction extends Action $enclosures = array(); foreach ($attachments as $attachment) { - if ($attachment->isEnclosure()) { + $enclosure_o=$attachment->getEnclosure(); + if ($enclosure_o) { $enclosure = array(); - $enclosure['url'] = $attachment->url; - $enclosure['mimetype'] = $attachment->mimetype; - $enclosure['size'] = $attachment->size; + $enclosure['url'] = $enclosure_o->url; + $enclosure['mimetype'] = $enclosure_o->mimetype; + $enclosure['size'] = $enclosure_o->size; $enclosures[] = $enclosure; } } @@ -594,7 +595,6 @@ class TwitterapiAction extends Action $this->init_document('rss'); - $this->elementStart('channel'); $this->element('title', null, $title); $this->element('link', null, $link); if (!is_null($suplink)) { @@ -620,7 +620,6 @@ class TwitterapiAction extends Action } } - $this->elementEnd('channel'); $this->end_twitter_rss(); } @@ -667,7 +666,6 @@ class TwitterapiAction extends Action $this->init_document('rss'); - $this->elementStart('channel'); $this->element('title', null, $title); $this->element('link', null, $link); $this->element('description', null, $subtitle); @@ -686,7 +684,6 @@ class TwitterapiAction extends Action } } - $this->elementEnd('channel'); $this->end_twitter_rss(); } @@ -999,11 +996,14 @@ class TwitterapiAction extends Action function init_twitter_rss() { $this->startXML(); - $this->elementStart('rss', array('version' => '2.0')); + $this->elementStart('rss', array('version' => '2.0', 'xmlns:atom'=>'http://www.w3.org/2005/Atom')); + $this->elementStart('channel'); + Event::handle('StartApiRss', array($this)); } function end_twitter_rss() { + $this->elementEnd('channel'); $this->elementEnd('rss'); $this->endXML(); } @@ -1015,6 +1015,7 @@ class TwitterapiAction extends Action $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', 'xml:lang' => 'en-US', 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0')); + Event::handle('StartApiAtom', array($this)); } function end_twitter_atom() diff --git a/lib/twitteroauthclient.php b/lib/twitteroauthclient.php index e37fa05f0..bad2b74ca 100644 --- a/lib/twitteroauthclient.php +++ b/lib/twitteroauthclient.php @@ -118,7 +118,7 @@ class TwitterOAuthClient extends OAuthClient } /** - * Calls Twitter's /stutuses/update API method + * Calls Twitter's /statuses/update API method * * @param string $status text of the status * @param int $in_reply_to_status_id optional id of the status it's @@ -137,7 +137,7 @@ class TwitterOAuthClient extends OAuthClient } /** - * Calls Twitter's /stutuses/friends_timeline API method + * Calls Twitter's /statuses/friends_timeline API method * * @param int $since_id show statuses after this id * @param int $max_id show statuses before this id @@ -167,7 +167,7 @@ class TwitterOAuthClient extends OAuthClient } /** - * Calls Twitter's /stutuses/friends API method + * Calls Twitter's /statuses/friends API method * * @param int $id id of the user whom you wish to see friends of * @param int $user_id numerical user id @@ -197,7 +197,7 @@ class TwitterOAuthClient extends OAuthClient } /** - * Calls Twitter's /stutuses/friends/ids API method + * Calls Twitter's /statuses/friends/ids API method * * @param int $id id of the user whom you wish to see friends of * @param int $user_id numerical user id diff --git a/lib/unqueuemanager.php b/lib/unqueuemanager.php index 3cdad0b54..6cfe5bcbd 100644 --- a/lib/unqueuemanager.php +++ b/lib/unqueuemanager.php @@ -39,7 +39,7 @@ class UnQueueManager case 'omb': if ($this->_isLocal($notice)) { require_once(INSTALLDIR.'/lib/omb.php'); - omb_broadcast_remote_subscribers($notice); + omb_broadcast_notice($notice); } break; case 'public': @@ -72,8 +72,13 @@ class UnQueueManager require_once(INSTALLDIR.'/lib/jabber.php'); jabber_broadcast_notice($notice); break; + case 'plugin': + Event::handle('HandleQueuedNotice', array(&$notice)); + break; default: - throw ServerException("UnQueueManager: Unknown queue: $type"); + if (Event::handle('UnqueueHandleNotice', array(&$notice, $queue))) { + throw ServerException("UnQueueManager: Unknown queue: $queue"); + } } } diff --git a/lib/util.php b/lib/util.php index 9b299cb14..be10647fc 100644 --- a/lib/util.php +++ b/lib/util.php @@ -391,10 +391,10 @@ function common_render_content($text, $notice) { $r = common_render_text($text); $id = $notice->profile_id; - $r = preg_replace('/(^|\s+)@([A-Za-z0-9]{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r); + $r = preg_replace('/(^|[\s\.\,\:\;]+)@([A-Za-z0-9]{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r); $r = preg_replace('/^T ([A-Z0-9]{1,64}) /e', "'T '.common_at_link($id, '\\1').' '", $r); - $r = preg_replace('/(^|\s+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r); - $r = preg_replace('/(^|\s)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r); + $r = preg_replace('/(^|[\s\.\,\:\;]+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r); + $r = preg_replace('/(^|[\s\.\,\:\;]+)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r); return $r; } @@ -493,7 +493,7 @@ function callback_helper($matches, $callback, $notice_id) { }while($original_url!=$url); if(empty($notice_id)){ - $result = call_user_func_array($callback,$url); + $result = call_user_func_array($callback, array($url)); }else{ $result = call_user_func_array($callback, array(array($url,$notice_id)) ); } @@ -522,21 +522,22 @@ function common_linkify($url) { if(strpos($url, '@') !== false && strpos($url, ':') === false) { //url is an email address without the mailto: protocol - return XMLStringer::estring('a', array('href' => "mailto:$url", 'rel' => 'external'), $url); - } + $canon = "mailto:$url"; + $longurl = "mailto:$url"; + }else{ - $canon = File_redirection::_canonUrl($url); + $canon = File_redirection::_canonUrl($url); - $longurl_data = File_redirection::where($url); - if (is_array($longurl_data)) { - $longurl = $longurl_data['url']; - } elseif (is_string($longurl_data)) { - $longurl = $longurl_data; - } else { - throw new ServerException("Can't linkify url '$url'"); + $longurl_data = File_redirection::where($canon); + if (is_array($longurl_data)) { + $longurl = $longurl_data['url']; + } elseif (is_string($longurl_data)) { + $longurl = $longurl_data; + } else { + throw new ServerException("Can't linkify url '$url'"); + } } - - $attrs = array('href' => $canon, 'rel' => 'external'); + $attrs = array('href' => $canon, 'title' => $longurl, 'rel' => 'external'); $is_attachment = false; $attachment_id = null; @@ -584,7 +585,8 @@ function common_linkify($url) { function common_shorten_links($text) { - if (mb_strlen($text) <= 140) return $text; + $maxLength = Notice::maxContent(); + if ($maxLength == 0 || mb_strlen($text) <= $maxLength) return $text; return common_replace_urls_callback($text, array('File_redirection', 'makeShort')); } @@ -729,14 +731,10 @@ function common_relative_profile($sender, $nickname, $dt=null) function common_local_url($action, $args=null, $params=null, $fragment=null) { - static $sensitive = array('login', 'register', 'passwordsettings', - 'twittersettings', 'finishopenidlogin', - 'finishaddopenid', 'api'); - $r = Router::get(); $path = $r->build($action, $args, $params, $fragment); - $ssl = in_array($action, $sensitive); + $ssl = common_is_sensitive($action); if (common_config('site','fancy')) { $url = common_path(mb_substr($path, 1), $ssl); @@ -750,6 +748,19 @@ function common_local_url($action, $args=null, $params=null, $fragment=null) return $url; } +function common_is_sensitive($action) +{ + static $sensitive = array('login', 'register', 'passwordsettings', + 'twittersettings', 'api'); + $ssl = null; + + if (Event::handle('SensitiveAction', array($action, &$ssl))) { + $ssl = in_array($action, $sensitive); + } + + return $ssl; +} + function common_path($relative, $ssl=false) { $pathpart = (common_config('site', 'path')) ? common_config('site', 'path')."/" : ''; @@ -888,7 +899,8 @@ function common_enqueue_notice($notice) 'twitter', 'facebook', 'ping'); - static $allTransports = array('sms'); + + static $allTransports = array('sms', 'plugin'); $transports = $allTransports; @@ -906,11 +918,16 @@ function common_enqueue_notice($notice) } } - $qm = QueueManager::get(); + if (Event::handle('StartEnqueueNotice', array($notice, &$transports))) { + + $qm = QueueManager::get(); + + foreach ($transports as $transport) + { + $qm->enqueue($notice, $transport); + } - foreach ($transports as $transport) - { - $qm->enqueue($notice, $transport); + Event::handle('EndEnqueueNotice', array($notice, $transports)); } return true; @@ -1148,7 +1165,7 @@ function common_negotiate_type($cprefs, $sprefs) } if ('text/html' === $besttype) { - return "text/html"; + return "text/html; charset=utf-8"; } return $besttype; } @@ -1156,7 +1173,8 @@ function common_negotiate_type($cprefs, $sprefs) function common_config($main, $sub) { global $config; - return isset($config[$main][$sub]) ? $config[$main][$sub] : false; + return (array_key_exists($main, $config) && + array_key_exists($sub, $config[$main])) ? $config[$main][$sub] : false; } function common_copy_args($from) @@ -1363,57 +1381,19 @@ function common_shorten_url($long_url) } else { $svc = $user->urlshorteningservice; } - - $curlh = curl_init(); - curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 20); // # seconds to wait - curl_setopt($curlh, CURLOPT_USERAGENT, 'StatusNet'); - curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true); - - switch($svc) { - case 'ur1.ca': - require_once INSTALLDIR.'/lib/Shorturl_api.php'; - $short_url_service = new LilUrl; - $short_url = $short_url_service->shorten($long_url); - break; - - case '2tu.us': - $short_url_service = new TightUrl; - require_once INSTALLDIR.'/lib/Shorturl_api.php'; - $short_url = $short_url_service->shorten($long_url); - break; - - case 'ptiturl.com': - require_once INSTALLDIR.'/lib/Shorturl_api.php'; - $short_url_service = new PtitUrl; - $short_url = $short_url_service->shorten($long_url); - break; - - case 'bit.ly': - curl_setopt($curlh, CURLOPT_URL, 'http://bit.ly/api?method=shorten&long_url='.urlencode($long_url)); - $short_url = current(json_decode(curl_exec($curlh))->results)->hashUrl; - break; - - case 'is.gd': - curl_setopt($curlh, CURLOPT_URL, 'http://is.gd/api.php?longurl='.urlencode($long_url)); - $short_url = curl_exec($curlh); - break; - case 'snipr.com': - curl_setopt($curlh, CURLOPT_URL, 'http://snipr.com/site/snip?r=simple&link='.urlencode($long_url)); - $short_url = curl_exec($curlh); - break; - case 'metamark.net': - curl_setopt($curlh, CURLOPT_URL, 'http://metamark.net/api/rest/simple?long_url='.urlencode($long_url)); - $short_url = curl_exec($curlh); - break; - case 'tinyurl.com': - curl_setopt($curlh, CURLOPT_URL, 'http://tinyurl.com/api-create.php?url='.urlencode($long_url)); - $short_url = curl_exec($curlh); - break; - default: - $short_url = false; + global $_shorteners; + if (!isset($_shorteners[$svc])) { + //the user selected service doesn't exist, so default to ur1.ca + $svc = 'ur1.ca'; + } + if (!isset($_shorteners[$svc])) { + // no shortener plugins installed. + return $long_url; } - curl_close($curlh); + $reflectionObj = new ReflectionClass($_shorteners[$svc]['callInfo'][0]); + $short_url_service = $reflectionObj->newInstanceArgs($_shorteners[$svc]['callInfo'][1]); + $short_url = $short_url_service->shorten($long_url); return $short_url; } |