summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/action.php53
-rw-r--r--lib/activity.php23
-rw-r--r--lib/adminpanelaction.php51
-rw-r--r--lib/apiaction.php71
-rw-r--r--lib/atom10feed.php2
-rw-r--r--lib/authenticationplugin.php68
-rw-r--r--lib/authorizationplugin.php2
-rw-r--r--lib/command.php29
-rw-r--r--lib/commandinterpreter.php11
-rw-r--r--lib/common.php12
-rw-r--r--lib/default.php1
-rw-r--r--lib/htmloutputter.php55
-rw-r--r--lib/httpclient.php10
-rw-r--r--lib/imagefile.php155
-rw-r--r--lib/iomaster.php2
-rw-r--r--lib/language.php1
-rw-r--r--lib/mail.php67
-rw-r--r--lib/mediafile.php2
-rw-r--r--lib/messageform.php2
-rw-r--r--lib/mysqlschema.php236
-rw-r--r--lib/noticeform.php2
-rw-r--r--lib/noticelist.php12
-rw-r--r--lib/pgsqlschema.php38
-rw-r--r--lib/processmanager.php84
-rw-r--r--lib/queuemanager.php6
-rw-r--r--lib/schema.php6
-rw-r--r--lib/spawningdaemon.php32
-rw-r--r--lib/subs.php2
-rw-r--r--lib/util.php10
29 files changed, 883 insertions, 162 deletions
diff --git a/lib/action.php b/lib/action.php
index 9884f529c..491d7d481 100644
--- a/lib/action.php
+++ b/lib/action.php
@@ -426,39 +426,69 @@ class Action extends HTMLOutputter // lawsuit
$this->elementStart('ul', array('class' => 'nav'));
if (Event::handle('StartPrimaryNav', array($this))) {
if ($user) {
+ // TRANS: Tooltip for main menu option "Personal"
+ $tooltip = _m('TOOLTIP', 'Personal profile and friends timeline');
+ // TRANS: Main menu option when logged in for access to personal profile and friends timeline
$this->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
- _('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
+ _m('MENU', 'Personal'), $tooltip, false, 'nav_home');
+ // TRANS: Tooltip for main menu option "Account"
+ $tooltip = _m('TOOLTIP', 'Change your email, avatar, password, profile');
+ // TRANS: Main menu option when logged in for access to user settings
$this->menuItem(common_local_url('profilesettings'),
- _('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
+ _('Account'), $tooltip, false, 'nav_account');
+ // TRANS: Tooltip for main menu option "Services"
+ $tooltip = _m('TOOLTIP', 'Connect to services');
+ // TRANS: Main menu option when logged in and connection are possible for access to options to connect to other services
$this->menuItem(common_local_url('oauthconnectionssettings'),
- _('Connect'), _('Connect to services'), false, 'nav_connect');
+ _('Connect'), $tooltip, false, 'nav_connect');
if ($user->hasRight(Right::CONFIGURESITE)) {
+ // TRANS: Tooltip for menu option "Admin"
+ $tooltip = _m('TOOLTIP', 'Change site configuration');
+ // TRANS: Main menu option when logged in and site admin for access to site configuration
$this->menuItem(common_local_url('siteadminpanel'),
- _('Admin'), _('Change site configuration'), false, 'nav_admin');
+ _m('MENU', 'Admin'), $tooltip, false, 'nav_admin');
}
if (common_config('invite', 'enabled')) {
+ // TRANS: Tooltip for main menu option "Invite"
+ $tooltip = _m('TOOLTIP', 'Invite friends and colleagues to join you on %s');
+ // TRANS: Main menu option when logged in and invitations are allowed for inviting new users
$this->menuItem(common_local_url('invite'),
- _('Invite'),
- sprintf(_('Invite friends and colleagues to join you on %s'),
+ _m('MENU', 'Invite'),
+ sprintf($tooltip,
common_config('site', 'name')),
false, 'nav_invitecontact');
}
+ // TRANS: Tooltip for main menu option "Logout"
+ $tooltip = _m('TOOLTIP', 'Logout from the site');
+ // TRANS: Main menu option when logged in to log out the current user
$this->menuItem(common_local_url('logout'),
- _('Logout'), _('Logout from the site'), false, 'nav_logout');
+ _m('MENU', 'Logout'), $tooltip, false, 'nav_logout');
}
else {
if (!common_config('site', 'closed')) {
+ // TRANS: Tooltip for main menu option "Register"
+ $tooltip = _m('TOOLTIP', 'Create an account');
+ // TRANS: Main menu option when not logged in to register a new account
$this->menuItem(common_local_url('register'),
- _('Register'), _('Create an account'), false, 'nav_register');
+ _m('MENU', 'Register'), $tooltip, false, 'nav_register');
}
+ // TRANS: Tooltip for main menu option "Login"
+ $tooltip = _m('TOOLTIP', 'Login to the site');
+ // TRANS: Main menu option when not logged in to log in
$this->menuItem(common_local_url('login'),
- _('Login'), _('Login to the site'), false, 'nav_login');
+ _m('MENU', 'Login'), $tooltip, false, 'nav_login');
}
+ // TRANS: Tooltip for main menu option "Help"
+ $tooltip = _m('TOOLTIP', 'Help me!');
+ // TRANS: Main menu option for help on the StatusNet site
$this->menuItem(common_local_url('doc', array('title' => 'help')),
- _('Help'), _('Help me!'), false, 'nav_help');
+ _m('MENU', 'Help'), $tooltip, false, 'nav_help');
if ($user || !common_config('site', 'private')) {
+ // TRANS: Tooltip for main menu option "Search"
+ $tooltip = _m('TOOLTIP', 'Search for people or text');
+ // TRANS: Main menu option when logged in or when the StatusNet instance is not private
$this->menuItem(common_local_url('peoplesearch'),
- _('Search'), _('Search for people or text'), false, 'nav_search');
+ _m('MENU', 'Search'), $tooltip, false, 'nav_search');
}
Event::handle('EndPrimaryNav', array($this));
}
@@ -479,6 +509,7 @@ class Action extends HTMLOutputter // lawsuit
if ($text) {
$this->elementStart('dl', array('id' => 'site_notice',
'class' => 'system_notice'));
+ // TRANS: DT element for site notice. String is hidden in default CSS.
$this->element('dt', null, _('Site notice'));
$this->elementStart('dd', null);
$this->raw($text);
diff --git a/lib/activity.php b/lib/activity.php
index 2cb80f9e1..125d391b0 100644
--- a/lib/activity.php
+++ b/lib/activity.php
@@ -78,7 +78,7 @@ class PoCoAddress
if (!empty($this->formatted)) {
$xs = new XMLStringer(true);
$xs->elementStart('poco:address');
- $xs->element('poco:formatted', null, $this->formatted);
+ $xs->element('poco:formatted', null, common_xml_safe_str($this->formatted));
$xs->elementEnd('poco:address');
return $xs->getString();
}
@@ -279,7 +279,7 @@ class PoCo
);
if (!empty($this->note)) {
- $xs->element('poco:note', null, $this->note);
+ $xs->element('poco:note', null, common_xml_safe_str($this->note));
}
if (!empty($this->address)) {
@@ -805,7 +805,6 @@ class ActivityObject
return $object;
}
-
function asString($tag='activity:object')
{
$xs = new XMLStringer(true);
@@ -817,16 +816,28 @@ class ActivityObject
$xs->element(self::ID, null, $this->id);
if (!empty($this->title)) {
- $xs->element(self::TITLE, null, $this->title);
+ $xs->element(
+ self::TITLE,
+ null,
+ common_xml_safe_str($this->title)
+ );
}
if (!empty($this->summary)) {
- $xs->element(self::SUMMARY, null, $this->summary);
+ $xs->element(
+ self::SUMMARY,
+ null,
+ common_xml_safe_str($this->summary)
+ );
}
if (!empty($this->content)) {
// XXX: assuming HTML content here
- $xs->element(ActivityUtils::CONTENT, array('type' => 'html'), $this->content);
+ $xs->element(
+ ActivityUtils::CONTENT,
+ array('type' => 'html'),
+ common_xml_safe_str($this->content)
+ );
}
if (!empty($this->link)) {
diff --git a/lib/adminpanelaction.php b/lib/adminpanelaction.php
index d43ea7698..a927e2333 100644
--- a/lib/adminpanelaction.php
+++ b/lib/adminpanelaction.php
@@ -69,6 +69,7 @@ class AdminPanelAction extends Action
// User must be logged in.
if (!common_logged_in()) {
+ // TRANS: Client error message
$this->clientError(_('Not logged in.'));
return false;
}
@@ -93,6 +94,7 @@ class AdminPanelAction extends Action
// User must have the right to change admin settings
if (!$user->hasRight(Right::CONFIGURESITE)) {
+ // TRANS: Client error message
$this->clientError(_('You cannot make changes to this site.'));
return false;
}
@@ -104,6 +106,7 @@ class AdminPanelAction extends Action
$name = mb_substr($name, 0, -10);
if (!self::canAdmin($name)) {
+ // TRANS: Client error message
$this->clientError(_('Changes to that panel are not allowed.'), 403);
return false;
}
@@ -134,6 +137,7 @@ class AdminPanelAction extends Action
Config::loadSettings();
$this->success = true;
+ // TRANS: Message after successful saving of administrative settings.
$this->msg = _('Settings saved.');
} catch (Exception $e) {
$this->success = false;
@@ -221,6 +225,7 @@ class AdminPanelAction extends Action
function showForm()
{
+ // TRANS: Client error message
$this->clientError(_('showForm() not implemented.'));
return;
}
@@ -250,6 +255,7 @@ class AdminPanelAction extends Action
function saveSettings()
{
+ // TRANS: Client error message
$this->clientError(_('saveSettings() not implemented.'));
return;
}
@@ -273,6 +279,7 @@ class AdminPanelAction extends Action
$result = $config->delete();
if (!$result) {
common_log_db_error($config, 'DELETE', __FILE__);
+ // TRANS: Client error message
$this->clientError(_("Unable to delete design setting."));
return null;
}
@@ -337,43 +344,67 @@ class AdminPanelNav extends Widget
if (Event::handle('StartAdminPanelNav', array($this))) {
if (AdminPanelAction::canAdmin('site')) {
- $this->out->menuItem(common_local_url('siteadminpanel'), _('Site'),
- _('Basic site configuration'), $action_name == 'siteadminpanel', 'nav_site_admin_panel');
+ // TRANS: Menu item title/tooltip
+ $menu_title = _('Basic site configuration');
+ // TRANS: Menu item for site administration
+ $this->out->menuItem(common_local_url('siteadminpanel'), _m('MENU', 'Site'),
+ $menu_title, $action_name == 'siteadminpanel', 'nav_site_admin_panel');
}
if (AdminPanelAction::canAdmin('design')) {
- $this->out->menuItem(common_local_url('designadminpanel'), _('Design'),
- _('Design configuration'), $action_name == 'designadminpanel', 'nav_design_admin_panel');
+ // TRANS: Menu item title/tooltip
+ $menu_title = _('Design configuration');
+ // TRANS: Menu item for site administration
+ $this->out->menuItem(common_local_url('designadminpanel'), _m('MENU', 'Design'),
+ $menu_title, $action_name == 'designadminpanel', 'nav_design_admin_panel');
}
if (AdminPanelAction::canAdmin('user')) {
+ // TRANS: Menu item title/tooltip
+ $menu_title = _('User configuration');
+ // TRANS: Menu item for site administration
$this->out->menuItem(common_local_url('useradminpanel'), _('User'),
- _('User configuration'), $action_name == 'useradminpanel', 'nav_user_admin_panel');
+ $menu_title, $action_name == 'useradminpanel', 'nav_user_admin_panel');
}
if (AdminPanelAction::canAdmin('access')) {
+ // TRANS: Menu item title/tooltip
+ $menu_title = _('Access configuration');
+ // TRANS: Menu item for site administration
$this->out->menuItem(common_local_url('accessadminpanel'), _('Access'),
- _('Access configuration'), $action_name == 'accessadminpanel', 'nav_access_admin_panel');
+ $menu_title, $action_name == 'accessadminpanel', 'nav_access_admin_panel');
}
if (AdminPanelAction::canAdmin('paths')) {
+ // TRANS: Menu item title/tooltip
+ $menu_title = _('Paths configuration');
+ // TRANS: Menu item for site administration
$this->out->menuItem(common_local_url('pathsadminpanel'), _('Paths'),
- _('Paths configuration'), $action_name == 'pathsadminpanel', 'nav_paths_admin_panel');
+ $menu_title, $action_name == 'pathsadminpanel', 'nav_paths_admin_panel');
}
if (AdminPanelAction::canAdmin('sessions')) {
+ // TRANS: Menu item title/tooltip
+ $menu_title = _('Sessions configuration');
+ // TRANS: Menu item for site administration
$this->out->menuItem(common_local_url('sessionsadminpanel'), _('Sessions'),
- _('Sessions configuration'), $action_name == 'sessionsadminpanel', 'nav_sessions_admin_panel');
+ $menu_title, $action_name == 'sessionsadminpanel', 'nav_sessions_admin_panel');
}
if (AdminPanelAction::canAdmin('sitenotice')) {
+ // TRANS: Menu item title/tooltip
+ $menu_title = _('Edit site notice');
+ // TRANS: Menu item for site administration
$this->out->menuItem(common_local_url('sitenoticeadminpanel'), _('Site notice'),
- _('Edit site notice'), $action_name == 'sitenoticeadminpanel', 'nav_sitenotice_admin_panel');
+ $menu_title, $action_name == 'sitenoticeadminpanel', 'nav_sitenotice_admin_panel');
}
if (AdminPanelAction::canAdmin('snapshot')) {
+ // TRANS: Menu item title/tooltip
+ $menu_title = _('Snapshots configuration');
+ // TRANS: Menu item for site administration
$this->out->menuItem(common_local_url('snapshotadminpanel'), _('Snapshots'),
- _('Snapshots configuration'), $action_name == 'snapshotadminpanel', 'nav_snapshot_admin_panel');
+ $menu_title, $action_name == 'snapshotadminpanel', 'nav_snapshot_admin_panel');
}
Event::handle('EndAdminPanelNav', array($this));
diff --git a/lib/apiaction.php b/lib/apiaction.php
index e4a1df3d1..e6aaf9316 100644
--- a/lib/apiaction.php
+++ b/lib/apiaction.php
@@ -491,7 +491,7 @@ class ApiAction extends Action
$this->showXmlAttachments($twitter_status['attachments']);
break;
case 'geo':
- $this->showGeoRSS($value);
+ $this->showGeoXML($value);
break;
case 'retweeted_status':
$this->showTwitterXmlStatus($value, 'retweeted_status');
@@ -539,7 +539,7 @@ class ApiAction extends Action
}
}
- function showGeoRSS($geo)
+ function showGeoXML($geo)
{
if (empty($geo)) {
// empty geo element
@@ -551,6 +551,17 @@ class ApiAction extends Action
}
}
+ function showGeoRSS($geo)
+ {
+ if (!empty($geo)) {
+ $this->element(
+ 'georss:point',
+ null,
+ $geo['coordinates'][0] . ' ' . $geo['coordinates'][1]
+ );
+ }
+ }
+
function showTwitterRssItem($entry)
{
$this->elementStart('item');
@@ -619,13 +630,25 @@ class ApiAction extends Action
$this->endDocument('xml');
}
- function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null, $logo=null)
+ function showRssTimeline($notice, $title, $link, $subtitle, $suplink = null, $logo = null, $self = null)
{
$this->initDocument('rss');
$this->element('title', null, $title);
$this->element('link', null, $link);
+
+ if (!is_null($self)) {
+ $this->element(
+ 'atom:link',
+ array(
+ 'type' => 'application/rss+xml',
+ 'href' => $self,
+ 'rel' => 'self'
+ )
+ );
+ }
+
if (!is_null($suplink)) {
// For FriendFeed's SUP protocol
$this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
@@ -732,8 +755,12 @@ class ApiAction extends Action
function showTwitterAtomEntry($entry)
{
$this->elementStart('entry');
- $this->element('title', null, $entry['title']);
- $this->element('content', array('type' => 'html'), $entry['content']);
+ $this->element('title', null, common_xml_safe_str($entry['title']));
+ $this->element(
+ 'content',
+ array('type' => 'html'),
+ common_xml_safe_str($entry['content'])
+ );
$this->element('id', null, $entry['id']);
$this->element('published', null, $entry['published']);
$this->element('updated', null, $entry['updated']);
@@ -848,7 +875,7 @@ class ApiAction extends Action
$this->initDocument('atom');
- $this->element('title', null, $title);
+ $this->element('title', null, common_xml_safe_str($title));
$this->element('id', null, $id);
$this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
@@ -858,7 +885,7 @@ class ApiAction extends Action
}
$this->element('updated', null, common_date_iso8601('now'));
- $this->element('subtitle', null, $subtitle);
+ $this->element('subtitle', null, common_xml_safe_str($subtitle));
if (is_array($group)) {
foreach ($group as $g) {
@@ -1138,7 +1165,14 @@ class ApiAction extends Action
function initTwitterRss()
{
$this->startXML();
- $this->elementStart('rss', array('version' => '2.0', 'xmlns:atom'=>'http://www.w3.org/2005/Atom'));
+ $this->elementStart(
+ 'rss',
+ array(
+ 'version' => '2.0',
+ 'xmlns:atom' => 'http://www.w3.org/2005/Atom',
+ 'xmlns:georss' => 'http://www.georss.org/georss'
+ )
+ );
$this->elementStart('channel');
Event::handle('StartApiRss', array($this));
}
@@ -1336,8 +1370,27 @@ class ApiAction extends Action
}
}
- function getSelfUri($action, $aargs)
+ /**
+ * Calculate the complete URI that called up this action. Used for
+ * Atom rel="self" links. Warning: this is funky.
+ *
+ * @return string URL a URL suitable for rel="self" Atom links
+ */
+ function getSelfUri()
{
+ $action = mb_substr(get_class($this), 0, -6); // remove 'Action'
+
+ $id = $this->arg('id');
+ $aargs = array('format' => $this->format);
+ if (!empty($id)) {
+ $aargs['id'] = $id;
+ }
+
+ $tag = $this->arg('tag');
+ if (!empty($tag)) {
+ $aargs['tag'] = $tag;
+ }
+
parse_str($_SERVER['QUERY_STRING'], $params);
$pstring = '';
if (!empty($params)) {
diff --git a/lib/atom10feed.php b/lib/atom10feed.php
index 2d342e785..a46d49f35 100644
--- a/lib/atom10feed.php
+++ b/lib/atom10feed.php
@@ -178,7 +178,7 @@ class Atom10Feed extends XMLStringer
$this->element(
'generator', array(
- 'url' => 'http://status.net',
+ 'uri' => 'http://status.net',
'version' => STATUSNET_VERSION
),
'StatusNet'
diff --git a/lib/authenticationplugin.php b/lib/authenticationplugin.php
index de479a576..0a3763e2e 100644
--- a/lib/authenticationplugin.php
+++ b/lib/authenticationplugin.php
@@ -69,13 +69,17 @@ abstract class AuthenticationPlugin extends Plugin
/**
* Automatically register a user when they attempt to login with valid credentials.
* User::register($data) is a very useful method for this implementation
- * @param username
+ * @param username username (that is used to login and find the user in the authentication provider) of the user to be registered
+ * @param nickname nickname of the user in the SN system. If nickname is null, then set nickname = username
* @return mixed instance of User, or false (if user couldn't be created)
*/
- function autoRegister($username)
+ function autoRegister($username, $nickname = null)
{
+ if(is_null($nickname)){
+ $nickname = $username;
+ }
$registration_data = array();
- $registration_data['nickname'] = $username ;
+ $registration_data['nickname'] = $nickname;
return User::register($registration_data);
}
@@ -92,6 +96,21 @@ abstract class AuthenticationPlugin extends Plugin
return false;
}
+ /**
+ * Given a username, suggest what the nickname should be
+ * Used during autoregistration
+ * Useful if your usernames are ugly, and you want to suggest
+ * nice looking nicknames when users initially sign on
+ * All nicknames returned by this function should be valid
+ * implementations may want to use common_nicknamize() to ensure validity
+ * @param username
+ * @return string nickname
+ */
+ function suggestNicknameForUsername($username)
+ {
+ return common_nicknamize($username);
+ }
+
//------------Below are the methods that connect StatusNet to the implementing Auth plugin------------\\
function onInitializePlugin(){
if(!isset($this->provider_name)){
@@ -108,10 +127,22 @@ abstract class AuthenticationPlugin extends Plugin
function onAutoRegister($nickname, $provider_name, &$user)
{
if($provider_name == $this->provider_name && $this->autoregistration){
- $user = $this->autoregister($nickname);
- if($user){
- User_username::register($user,$nickname,$this->provider_name);
- return false;
+ $suggested_nickname = $this->suggestNicknameForUsername($nickname);
+ $test_user = User::staticGet('nickname', $suggested_nickname);
+ if($test_user) {
+ //someone already exists with the suggested nickname, so used the passed nickname
+ $suggested_nickname = common_nicknamize($nickname);
+ }
+ $test_user = User::staticGet('nickname', $suggested_nickname);
+ if($test_user) {
+ //someone already exists with the suggested nickname
+ //not much else we can do
+ }else{
+ $user = $this->autoRegister($nickname, $suggested_nickname);
+ if($user){
+ User_username::register($user,$nickname,$this->provider_name);
+ return false;
+ }
}
}
}
@@ -122,23 +153,30 @@ abstract class AuthenticationPlugin extends Plugin
$user_username->username=$nickname;
$user_username->provider_name=$this->provider_name;
if($user_username->find() && $user_username->fetch()){
- $username = $user_username->username;
- $authenticated = $this->checkPassword($username, $password);
+ $authenticated = $this->checkPassword($user_username->username, $password);
if($authenticated){
$authenticatedUser = User::staticGet('id', $user_username->user_id);
return false;
}
}else{
- $user = User::staticGet('nickname', $nickname);
+ //$nickname is the username used to login
+ //$suggested_nickname is the nickname the auth provider suggests for that username
+ $suggested_nickname = $this->suggestNicknameForUsername($nickname);
+ $user = User::staticGet('nickname', $suggested_nickname);
if($user){
- //make sure a different provider isn't handling this nickname
+ //make sure this user isn't claimed
$user_username = new User_username();
- $user_username->username=$nickname;
- if(!$user_username->find()){
- //no other provider claims this username, so it's safe for us to handle it
+ $user_username->user_id=$user->id;
+ $we_can_handle = false;
+ if($user_username->find()){
+ //either this provider, or another one, has already claimed this user
+ //so we cannot. Let another plugin try.
+ return;
+ }else{
+ //no other provider claims this user, so it's safe for us to handle it
$authenticated = $this->checkPassword($nickname, $password);
if($authenticated){
- $authenticatedUser = User::staticGet('nickname', $nickname);
+ $authenticatedUser = $user;
User_username::register($authenticatedUser,$nickname,$this->provider_name);
return false;
}
diff --git a/lib/authorizationplugin.php b/lib/authorizationplugin.php
index 733b0c065..07da9b2d1 100644
--- a/lib/authorizationplugin.php
+++ b/lib/authorizationplugin.php
@@ -85,7 +85,7 @@ abstract class AuthorizationPlugin extends Plugin
}
function onStartSetApiUser(&$user) {
- return $this->onStartSetUser(&$user);
+ return $this->onStartSetUser($user);
}
function onStartHasRole($profile, $name, &$has_role) {
diff --git a/lib/command.php b/lib/command.php
index 9d550550f..f7421269d 100644
--- a/lib/command.php
+++ b/lib/command.php
@@ -711,6 +711,34 @@ class LoginCommand extends Command
}
}
+class LoseCommand extends Command
+{
+
+ var $other = null;
+
+ function __construct($user, $other)
+ {
+ parent::__construct($user);
+ $this->other = $other;
+ }
+
+ function execute($channel)
+ {
+ if(!$this->other) {
+ $channel->error($this->user, _('Specify the name of the user to unsubscribe from'));
+ return;
+ }
+
+ $result = Subscription::cancel($this->getProfile($this->other), $this->user->getProfile());
+
+ if ($result) {
+ $channel->output($this->user, sprintf(_('Unsubscribed %s'), $this->other));
+ } else {
+ $channel->error($this->user, $result);
+ }
+ }
+}
+
class SubscriptionsCommand extends Command
{
function handle($channel)
@@ -793,6 +821,7 @@ class HelpCommand extends Command
"d <nickname> <text> - direct message to user\n".
"get <nickname> - get last notice from user\n".
"whois <nickname> - get profile info on user\n".
+ "lose <nickname> - force user to stop following you\n".
"fav <nickname> - add user's last notice as a 'fave'\n".
"fav #<notice_id> - add notice with the given id as a 'fave'\n".
"repeat #<notice_id> - repeat a notice with a given id\n".
diff --git a/lib/commandinterpreter.php b/lib/commandinterpreter.php
index c2add7299..fbc6174bb 100644
--- a/lib/commandinterpreter.php
+++ b/lib/commandinterpreter.php
@@ -47,6 +47,17 @@ class CommandInterpreter
} else {
return new LoginCommand($user);
}
+ case 'lose':
+ if ($arg) {
+ list($other, $extra) = $this->split_arg($arg);
+ if ($extra) {
+ return null;
+ } else {
+ return new LoseCommand($user, $other);
+ }
+ } else {
+ return null;
+ }
case 'subscribers':
if ($arg) {
return null;
diff --git a/lib/common.php b/lib/common.php
index 5d53270e3..047dc5a7b 100644
--- a/lib/common.php
+++ b/lib/common.php
@@ -71,6 +71,7 @@ if (!function_exists('dl')) {
# global configuration object
require_once('PEAR.php');
+require_once('PEAR/Exception.php');
require_once('DB/DataObject.php');
require_once('DB/DataObject/Cast.php'); # for dates
@@ -128,6 +129,17 @@ require_once INSTALLDIR.'/lib/activity.php';
require_once INSTALLDIR.'/lib/clientexception.php';
require_once INSTALLDIR.'/lib/serverexception.php';
+
+//set PEAR error handling to use regular PHP exceptions
+function PEAR_ErrorToPEAR_Exception($err)
+{
+ if ($err->getCode()) {
+ throw new PEAR_Exception($err->getMessage(), $err->getCode());
+ }
+ throw new PEAR_Exception($err->getMessage());
+}
+PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'PEAR_ErrorToPEAR_Exception');
+
try {
StatusNet::init(@$server, @$path, @$conffile);
} catch (NoConfigException $e) {
diff --git a/lib/default.php b/lib/default.php
index 46d3d4774..10f3f1a97 100644
--- a/lib/default.php
+++ b/lib/default.php
@@ -282,6 +282,7 @@ $default =
'Mapstraction' => null,
'OStatus' => null,
'WikiHashtags' => null,
+ 'RSSCloud' => null,
'OpenID' => null),
),
'admin' =>
diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php
index 7315fe2ad..7786b5941 100644
--- a/lib/htmloutputter.php
+++ b/lib/htmloutputter.php
@@ -356,40 +356,47 @@ class HTMLOutputter extends XMLOutputter
if( empty($url['scheme']) && empty($url['host']) && empty($url['query']) && empty($url['fragment']))
{
- $path = common_config('javascript', 'path');
+ if (strpos($src, 'plugins/') === 0 || strpos($src, 'local/') === 0) {
- if (empty($path)) {
- $path = common_config('site', 'path') . '/js/';
- }
+ $src = common_path($src) . '?version=' . STATUSNET_VERSION;
- if ($path[strlen($path)-1] != '/') {
- $path .= '/';
- }
+ }else{
- if ($path[0] != '/') {
- $path = '/'.$path;
- }
+ $path = common_config('javascript', 'path');
- $server = common_config('javascript', 'server');
+ if (empty($path)) {
+ $path = common_config('site', 'path') . '/js/';
+ }
- if (empty($server)) {
- $server = common_config('site', 'server');
- }
+ if ($path[strlen($path)-1] != '/') {
+ $path .= '/';
+ }
- $ssl = common_config('javascript', 'ssl');
+ if ($path[0] != '/') {
+ $path = '/'.$path;
+ }
+
+ $server = common_config('javascript', 'server');
- if (is_null($ssl)) { // null -> guess
- if (common_config('site', 'ssl') == 'always' &&
- !common_config('javascript', 'server')) {
- $ssl = true;
- } else {
- $ssl = false;
+ if (empty($server)) {
+ $server = common_config('site', 'server');
}
- }
- $protocol = ($ssl) ? 'https' : 'http';
+ $ssl = common_config('javascript', 'ssl');
- $src = $protocol.'://'.$server.$path.$src . '?version=' . STATUSNET_VERSION;
+ if (is_null($ssl)) { // null -> guess
+ if (common_config('site', 'ssl') == 'always' &&
+ !common_config('javascript', 'server')) {
+ $ssl = true;
+ } else {
+ $ssl = false;
+ }
+ }
+
+ $protocol = ($ssl) ? 'https' : 'http';
+
+ $src = $protocol.'://'.$server.$path.$src . '?version=' . STATUSNET_VERSION;
+ }
}
$this->element('script', array('type' => $type,
diff --git a/lib/httpclient.php b/lib/httpclient.php
index 4c3af8d7d..64a51353c 100644
--- a/lib/httpclient.php
+++ b/lib/httpclient.php
@@ -120,6 +120,16 @@ class HTTPClient extends HTTP_Request2
{
$this->config['max_redirs'] = 10;
$this->config['follow_redirects'] = true;
+
+ // We've had some issues with keepalive breaking with
+ // HEAD requests, such as to youtube which seems to be
+ // emitting chunked encoding info for an empty body
+ // instead of not emitting anything. This may be a
+ // bug on YouTube's end, but the upstream libray
+ // ought to be investigated to see if we can handle
+ // it gracefully in that case as well.
+ $this->config['protocol_version'] = '1.0';
+
parent::__construct($url, $method, $config);
$this->setHeader('User-Agent', $this->userAgent());
}
diff --git a/lib/imagefile.php b/lib/imagefile.php
index 6bc8e599b..2134623b1 100644
--- a/lib/imagefile.php
+++ b/lib/imagefile.php
@@ -60,6 +60,21 @@ class ImageFile
$this->filepath = $filepath;
$info = @getimagesize($this->filepath);
+
+ if (!(
+ ($info[2] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) ||
+ ($info[2] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) ||
+ $info[2] == IMAGETYPE_BMP ||
+ ($info[2] == IMAGETYPE_WBMP && function_exists('imagecreatefromwbmp')) ||
+ ($info[2] == IMAGETYPE_XBM && function_exists('imagecreatefromxbm')) ||
+ ($info[2] == IMAGETYPE_XPM && function_exists('imagecreatefromxpm')) ||
+ ($info[2] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')))) {
+
+ @unlink($_FILES[$param]['tmp_name']);
+ throw new Exception(_('Unsupported image file format.'));
+ return;
+ }
+
$this->type = ($info) ? $info[2]:$type;
$this->width = ($info) ? $info[0]:$width;
$this->height = ($info) ? $info[1]:$height;
@@ -97,15 +112,6 @@ class ImageFile
return;
}
- if ($info[2] !== IMAGETYPE_GIF &&
- $info[2] !== IMAGETYPE_JPEG &&
- $info[2] !== IMAGETYPE_PNG) {
-
- @unlink($_FILES[$param]['tmp_name']);
- throw new Exception(_('Unsupported image file format.'));
- return;
- }
-
return new ImageFile(null, $_FILES[$param]['tmp_name']);
}
@@ -146,6 +152,18 @@ class ImageFile
case IMAGETYPE_PNG:
$image_src = imagecreatefrompng($this->filepath);
break;
+ case IMAGETYPE_BMP:
+ $image_src = imagecreatefrombmp($this->filepath);
+ break;
+ case IMAGETYPE_WBMP:
+ $image_src = imagecreatefromwbmp($this->filepath);
+ break;
+ case IMAGETYPE_XBM:
+ $image_src = imagecreatefromxbm($this->filepath);
+ break;
+ case IMAGETYPE_XPM:
+ $image_src = imagecreatefromxpm($this->filepath);
+ break;
default:
throw new Exception(_('Unknown file type'));
return;
@@ -153,7 +171,7 @@ class ImageFile
$image_dest = imagecreatetruecolor($size, $size);
- if ($this->type == IMAGETYPE_GIF || $this->type == IMAGETYPE_PNG) {
+ if ($this->type == IMAGETYPE_GIF || $this->type == IMAGETYPE_PNG || $this->type == IMAGETYPE_BMP) {
$transparent_idx = imagecolortransparent($image_src);
@@ -176,6 +194,24 @@ class ImageFile
imagecopyresampled($image_dest, $image_src, 0, 0, $x, $y, $size, $size, $w, $h);
+ if($this->type == IMAGETYPE_BMP) {
+ //we don't want to save BMP... it's an inefficient, rare, antiquated format
+ //save png instead
+ $this->type = IMAGETYPE_PNG;
+ } else if($this->type == IMAGETYPE_WBMP) {
+ //we don't want to save WBMP... it's a rare format that we can't guarantee clients will support
+ //save png instead
+ $this->type = IMAGETYPE_PNG;
+ } else if($this->type == IMAGETYPE_XBM) {
+ //we don't want to save XBM... it's a rare format that we can't guarantee clients will support
+ //save png instead
+ $this->type = IMAGETYPE_PNG;
+ } else if($this->type == IMAGETYPE_XPM) {
+ //we don't want to save XPM... it's a rare format that we can't guarantee clients will support
+ //save png instead
+ $this->type = IMAGETYPE_PNG;
+ }
+
$outname = Avatar::filename($this->id,
image_type_to_extension($this->type),
$size,
@@ -245,4 +281,101 @@ class ImageFile
return $num;
}
-} \ No newline at end of file
+}
+
+//PHP doesn't (as of 2/24/2010) have an imagecreatefrombmp so conditionally define one
+if(!function_exists('imagecreatefrombmp')){
+ //taken shamelessly from http://www.php.net/manual/en/function.imagecreatefromwbmp.php#86214
+ function imagecreatefrombmp($p_sFile)
+ {
+ // Load the image into a string
+ $file = fopen($p_sFile,"rb");
+ $read = fread($file,10);
+ while(!feof($file)&&($read<>""))
+ $read .= fread($file,1024);
+
+ $temp = unpack("H*",$read);
+ $hex = $temp[1];
+ $header = substr($hex,0,108);
+
+ // Process the header
+ // Structure: http://www.fastgraph.com/help/bmp_header_format.html
+ if (substr($header,0,4)=="424d")
+ {
+ // Cut it in parts of 2 bytes
+ $header_parts = str_split($header,2);
+
+ // Get the width 4 bytes
+ $width = hexdec($header_parts[19].$header_parts[18]);
+
+ // Get the height 4 bytes
+ $height = hexdec($header_parts[23].$header_parts[22]);
+
+ // Unset the header params
+ unset($header_parts);
+ }
+
+ // Define starting X and Y
+ $x = 0;
+ $y = 1;
+
+ // Create newimage
+ $image = imagecreatetruecolor($width,$height);
+
+ // Grab the body from the image
+ $body = substr($hex,108);
+
+ // Calculate if padding at the end-line is needed
+ // Divided by two to keep overview.
+ // 1 byte = 2 HEX-chars
+ $body_size = (strlen($body)/2);
+ $header_size = ($width*$height);
+
+ // Use end-line padding? Only when needed
+ $usePadding = ($body_size>($header_size*3)+4);
+
+ // Using a for-loop with index-calculation instaid of str_split to avoid large memory consumption
+ // Calculate the next DWORD-position in the body
+ for ($i=0;$i<$body_size;$i+=3)
+ {
+ // Calculate line-ending and padding
+ if ($x>=$width)
+ {
+ // If padding needed, ignore image-padding
+ // Shift i to the ending of the current 32-bit-block
+ if ($usePadding)
+ $i += $width%4;
+
+ // Reset horizontal position
+ $x = 0;
+
+ // Raise the height-position (bottom-up)
+ $y++;
+
+ // Reached the image-height? Break the for-loop
+ if ($y>$height)
+ break;
+ }
+
+ // Calculation of the RGB-pixel (defined as BGR in image-data)
+ // Define $i_pos as absolute position in the body
+ $i_pos = $i*2;
+ $r = hexdec($body[$i_pos+4].$body[$i_pos+5]);
+ $g = hexdec($body[$i_pos+2].$body[$i_pos+3]);
+ $b = hexdec($body[$i_pos].$body[$i_pos+1]);
+
+ // Calculate and draw the pixel
+ $color = imagecolorallocate($image,$r,$g,$b);
+ imagesetpixel($image,$x,$height-$y,$color);
+
+ // Raise the horizontal position
+ $x++;
+ }
+
+ // Unset the body / free the memory
+ unset($body);
+
+ // Return image-object
+ return $image;
+ }
+}
diff --git a/lib/iomaster.php b/lib/iomaster.php
index d20837ba5..7cfb2c9a0 100644
--- a/lib/iomaster.php
+++ b/lib/iomaster.php
@@ -330,7 +330,7 @@ abstract class IoMaster
* for per-queue and per-site records.
*
* @param string $key counter name
- * @param array $owners list of owner keys like 'queue:jabber' or 'site:stat01'
+ * @param array $owners list of owner keys like 'queue:xmpp' or 'site:stat01'
*/
public function stats($key, $owners=array())
{
diff --git a/lib/language.php b/lib/language.php
index f5ee7fac5..64b59e739 100644
--- a/lib/language.php
+++ b/lib/language.php
@@ -289,6 +289,7 @@ function get_all_languages() {
'ar' => array('q' => 0.8, 'lang' => 'ar', 'name' => 'Arabic', 'direction' => 'rtl'),
'arz' => array('q' => 0.8, 'lang' => 'arz', 'name' => 'Egyptian Spoken Arabic', 'direction' => 'rtl'),
'bg' => array('q' => 0.8, 'lang' => 'bg', 'name' => 'Bulgarian', 'direction' => 'ltr'),
+ 'br' => array('q' => 0.8, 'lang' => 'br', 'name' => 'Breton', 'direction' => 'ltr'),
'ca' => array('q' => 0.5, 'lang' => 'ca', 'name' => 'Catalan', 'direction' => 'ltr'),
'cs' => array('q' => 0.5, 'lang' => 'cs', 'name' => 'Czech', 'direction' => 'ltr'),
'de' => array('q' => 0.8, 'lang' => 'de', 'name' => 'German', 'direction' => 'ltr'),
diff --git a/lib/mail.php b/lib/mail.php
index c724764cc..807b6a363 100644
--- a/lib/mail.php
+++ b/lib/mail.php
@@ -133,12 +133,13 @@ function mail_notify_from()
* @param User &$user user to send email to
* @param string $subject subject of the email
* @param string $body body of the email
+ * @param array $headers optional list of email headers
* @param string $address optional specification of email address
*
* @return boolean success flag
*/
-function mail_to_user(&$user, $subject, $body, $address=null)
+function mail_to_user(&$user, $subject, $body, $headers=array(), $address=null)
{
if (!$address) {
$address = $user->email;
@@ -180,7 +181,9 @@ function mail_confirm_address($user, $code, $nickname, $address)
$nickname, common_config('site', 'name'),
common_local_url('confirmaddress', array('code' => $code)),
common_config('site', 'name'));
- return mail_to_user($user, $subject, $body, $address);
+ $headers = array();
+
+ return mail_to_user($user, $subject, $body, $headers, $address);
}
/**
@@ -231,6 +234,7 @@ function mail_subscribe_notify_profile($listenee, $other)
$recipients = $listenee->email;
+ $headers = _mail_prepare_headers('subscribe', $listenee->nickname, $other->nickname);
$headers['From'] = mail_notify_from();
$headers['To'] = $name . ' <' . $listenee->email . '>';
$headers['Subject'] = sprintf(_('%1$s is now listening to '.
@@ -476,7 +480,10 @@ function mail_notify_nudge($from, $to)
common_local_url('all', array('nickname' => $to->nickname)),
common_config('site', 'name'));
common_init_locale();
- return mail_to_user($to, $subject, $body);
+
+ $headers = _mail_prepare_headers('nudge', $to->nickname, $from->nickname);
+
+ return mail_to_user($to, $subject, $body, $headers);
}
/**
@@ -526,8 +533,10 @@ function mail_notify_message($message, $from=null, $to=null)
common_local_url('newmessage', array('to' => $from->id)),
common_config('site', 'name'));
+ $headers = _mail_prepare_headers('message', $to->nickname, $from->nickname);
+
common_init_locale();
- return mail_to_user($to, $subject, $body);
+ return mail_to_user($to, $subject, $body, $headers);
}
/**
@@ -578,8 +587,10 @@ function mail_notify_fave($other, $user, $notice)
common_config('site', 'name'),
$user->nickname);
+ $headers = _mail_prepare_headers('fave', $other->nickname, $user->nickname);
+
common_init_locale();
- mail_to_user($other, $subject, $body);
+ mail_to_user($other, $subject, $body, $headers);
}
/**
@@ -611,19 +622,19 @@ function mail_notify_attn($user, $notice)
common_init_locale($user->language);
- if ($notice->conversation != $notice->id) {
- $conversationEmailText = "The full conversation can be read here:\n\n".
- "\t%5\$s\n\n ";
- $conversationUrl = common_local_url('conversation',
+ if ($notice->conversation != $notice->id) {
+ $conversationEmailText = "The full conversation can be read here:\n\n".
+ "\t%5\$s\n\n ";
+ $conversationUrl = common_local_url('conversation',
array('id' => $notice->conversation)).'#notice-'.$notice->id;
- } else {
- $conversationEmailText = "%5\$s";
- $conversationUrl = null;
- }
+ } else {
+ $conversationEmailText = "%5\$s";
+ $conversationUrl = null;
+ }
$subject = sprintf(_('%s (@%s) sent a notice to your attention'), $bestname, $sender->nickname);
- $body = sprintf(_("%1\$s (@%9\$s) just sent a notice to your attention (an '@-reply') on %2\$s.\n\n".
+ $body = sprintf(_("%1\$s (@%9\$s) just sent a notice to your attention (an '@-reply') on %2\$s.\n\n".
"The notice is here:\n\n".
"\t%3\$s\n\n" .
"It reads:\n\n".
@@ -641,7 +652,7 @@ function mail_notify_attn($user, $notice)
common_local_url('shownotice',
array('notice' => $notice->id)),//%3
$notice->content,//%4
- $conversationUrl,//%5
+ $conversationUrl,//%5
common_local_url('newnotice',
array('replyto' => $sender->nickname, 'inreplyto' => $notice->id)),//%6
common_local_url('replies',
@@ -649,6 +660,30 @@ function mail_notify_attn($user, $notice)
common_local_url('emailsettings'), //%8
$sender->nickname); //%9
+ $headers = _mail_prepare_headers('mention', $user->nickname, $sender->nickname);
+
common_init_locale();
- mail_to_user($user, $subject, $body);
+ mail_to_user($user, $subject, $body, $headers);
}
+
+/**
+ * Prepare the common mail headers used in notification emails
+ *
+ * @param string $msg_type type of message being sent to the user
+ * @param string $to nickname of the receipient
+ * @param string $from nickname of the user triggering the notification
+ *
+ * @return array list of mail headers to include in the message
+ */
+function _mail_prepare_headers($msg_type, $to, $from)
+{
+ $headers = array(
+ 'X-StatusNet-MessageType' => $msg_type,
+ 'X-StatusNet-TargetUser' => $to,
+ 'X-StatusNet-SourceUser' => $from,
+ 'X-StatusNet-Domain' => common_config('site', 'server')
+ );
+
+ return $headers;
+}
+
diff --git a/lib/mediafile.php b/lib/mediafile.php
index e3d5b1dbc..10d90d008 100644
--- a/lib/mediafile.php
+++ b/lib/mediafile.php
@@ -262,7 +262,7 @@ class MediaFile
$filetype = MIME_Type::autoDetect($stream['uri']);
}
- if (in_array($filetype, common_config('attachments', 'supported'))) {
+ if (common_config('attachments', 'supported') === true || in_array($filetype, common_config('attachments', 'supported'))) {
return $filetype;
}
$media = MIME_Type::getMedia($filetype);
diff --git a/lib/messageform.php b/lib/messageform.php
index 0c568e1bd..b116964da 100644
--- a/lib/messageform.php
+++ b/lib/messageform.php
@@ -175,6 +175,6 @@ class MessageForm extends Form
'class' => 'submit',
'name' => 'message_send',
'type' => 'submit',
- 'value' => _('Send')));
+ 'value' => _m('Send button for sending notice', 'Send')));
}
}
diff --git a/lib/mysqlschema.php b/lib/mysqlschema.php
index 485096ac4..455695366 100644
--- a/lib/mysqlschema.php
+++ b/lib/mysqlschema.php
@@ -90,15 +90,24 @@ class MysqlSchema extends Schema
* @param string $name Name of the table to get
*
* @return TableDef tabledef for that table.
+ * @throws SchemaTableMissingException
*/
public function getTableDef($name)
{
- $res = $this->conn->query('DESCRIBE ' . $name);
+ $query = "SELECT * FROM INFORMATION_SCHEMA.COLUMNS " .
+ "WHERE TABLE_SCHEMA='%s' AND TABLE_NAME='%s'";
+ $schema = $this->conn->dsn['database'];
+ $sql = sprintf($query, $schema, $name);
+ $res = $this->conn->query($sql);
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
}
+ if ($res->numRows() == 0) {
+ $res->free();
+ throw new SchemaTableMissingException("No such table: $name");
+ }
$td = new TableDef();
@@ -111,9 +120,9 @@ class MysqlSchema extends Schema
$cd = new ColumnDef();
- $cd->name = $row['Field'];
+ $cd->name = $row['COLUMN_NAME'];
- $packed = $row['Type'];
+ $packed = $row['COLUMN_TYPE'];
if (preg_match('/^(\w+)\((\d+)\)$/', $packed, $match)) {
$cd->type = $match[1];
@@ -122,18 +131,58 @@ class MysqlSchema extends Schema
$cd->type = $packed;
}
- $cd->nullable = ($row['Null'] == 'YES') ? true : false;
- $cd->key = $row['Key'];
- $cd->default = $row['Default'];
- $cd->extra = $row['Extra'];
+ $cd->nullable = ($row['IS_NULLABLE'] == 'YES') ? true : false;
+ $cd->key = $row['COLUMN_KEY'];
+ $cd->default = $row['COLUMN_DEFAULT'];
+ $cd->extra = $row['EXTRA'];
+
+ // Autoincrement is stuck into the extra column.
+ // Pull it out so we don't accidentally mod it every time...
+ $extra = preg_replace('/(^|\s)auto_increment(\s|$)/i', '$1$2', $cd->extra);
+ if ($extra != $cd->extra) {
+ $cd->extra = trim($extra);
+ $cd->auto_increment = true;
+ }
+
+ // mysql extensions -- not (yet) used by base class
+ $cd->charset = $row['CHARACTER_SET_NAME'];
+ $cd->collate = $row['COLLATION_NAME'];
$td->columns[] = $cd;
}
+ $res->free();
return $td;
}
/**
+ * Pull the given table properties from INFORMATION_SCHEMA.
+ * Most of the good stuff is MySQL extensions.
+ *
+ * @return array
+ * @throws Exception if table info can't be looked up
+ */
+
+ function getTableProperties($table, $props)
+ {
+ $query = "SELECT %s FROM INFORMATION_SCHEMA.TABLES " .
+ "WHERE TABLE_SCHEMA='%s' AND TABLE_NAME='%s'";
+ $schema = $this->conn->dsn['database'];
+ $sql = sprintf($query, implode(',', $props), $schema, $table);
+ $res = $this->conn->query($sql);
+
+ $row = array();
+ $ok = $res->fetchInto($row, DB_FETCHMODE_ASSOC);
+ $res->free();
+
+ if ($ok) {
+ return $row;
+ } else {
+ throw new SchemaTableMissingException("No such table: $table");
+ }
+ }
+
+ /**
* Gets a ColumnDef object for a single column.
*
* Throws an exception if the table is not found.
@@ -185,35 +234,26 @@ class MysqlSchema extends Schema
}
$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) . ")";
+ $idx = $this->_indexList($columns);
+
+ if ($idx['primary']) {
+ $sql .= ",\nconstraint primary key (" . implode(',', $idx['primary']) . ")";
}
- foreach ($uniques as $u) {
- $sql .= ",\nunique index {$name}_{$u}_idx ($u)";
+ foreach ($idx['uniques'] as $u) {
+ $key = $this->_uniqueKey($name, $u);
+ $sql .= ",\nunique index $key ($u)";
}
- foreach ($indices as $i) {
- $sql .= ",\nindex {$name}_{$i}_idx ($i)";
+ foreach ($idx['indices'] as $i) {
+ $key = $this->_key($name, $i);
+ $sql .= ",\nindex $key ($i)";
}
- $sql .= "); ";
+ $sql .= ") ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; ";
- common_log(LOG_INFO, $sql);
$res = $this->conn->query($sql);
if (PEAR::isError($res)) {
@@ -224,6 +264,47 @@ class MysqlSchema extends Schema
}
/**
+ * Look over a list of column definitions and list up which
+ * indices will be present
+ */
+ private function _indexList(array $columns)
+ {
+ $list = array('uniques' => array(),
+ 'primary' => array(),
+ 'indices' => array());
+ foreach ($columns as $cd) {
+ switch ($cd->key) {
+ case 'UNI':
+ $list['uniques'][] = $cd->name;
+ break;
+ case 'PRI':
+ $list['primary'][] = $cd->name;
+ break;
+ case 'MUL':
+ $list['indices'][] = $cd->name;
+ break;
+ }
+ }
+ return $list;
+ }
+
+ /**
+ * Get the unique index key name for a given column on this table
+ */
+ function _uniqueKey($tableName, $columnName)
+ {
+ return $this->_key($tableName, $columnName);
+ }
+
+ /**
+ * Get the index key name for a given column on this table
+ */
+ function _key($tableName, $columnName)
+ {
+ return "{$tableName}_{$columnName}_idx";
+ }
+
+ /**
* Drops a table from the schema
*
* Throws an exception if the table is not found.
@@ -394,21 +475,20 @@ class MysqlSchema extends Schema
try {
$td = $this->getTableDef($tableName);
- } catch (Exception $e) {
- if (preg_match('/no such table/', $e->getMessage())) {
- return $this->createTable($tableName, $columns);
- } else {
- throw $e;
- }
+ } catch (SchemaTableMissingException $e) {
+ return $this->createTable($tableName, $columns);
}
$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();
+ $dropIndex = array();
+ $toadd = array_diff($new, $cur);
+ $todrop = array_diff($cur, $new);
+ $same = array_intersect($new, $cur);
+ $tomod = array();
+ $addIndex = array();
+ $tableProps = array();
foreach ($same as $m) {
$curCol = $this->_byName($td->columns, $m);
@@ -416,10 +496,64 @@ class MysqlSchema extends Schema
if (!$newCol->equals($curCol)) {
$tomod[] = $newCol->name;
+ continue;
+ }
+
+ // Earlier versions may have accidentally left tables at default
+ // charsets which might be latin1 or other freakish things.
+ if ($this->_isString($curCol)) {
+ if ($curCol->charset != 'utf8') {
+ $tomod[] = $newCol->name;
+ continue;
+ }
+ }
+ }
+
+ // Find any indices we have to change...
+ $curIdx = $this->_indexList($td->columns);
+ $newIdx = $this->_indexList($columns);
+
+ if ($curIdx['primary'] != $newIdx['primary']) {
+ if ($curIdx['primary']) {
+ $dropIndex[] = 'drop primary key';
+ }
+ if ($newIdx['primary']) {
+ $keys = implode(',', $newIdx['primary']);
+ $addIndex[] = "add constraint primary key ($keys)";
}
}
- if (count($toadd) + count($todrop) + count($tomod) == 0) {
+ $dropUnique = array_diff($curIdx['uniques'], $newIdx['uniques']);
+ $addUnique = array_diff($newIdx['uniques'], $curIdx['uniques']);
+ foreach ($dropUnique as $columnName) {
+ $dropIndex[] = 'drop key ' . $this->_uniqueKey($tableName, $columnName);
+ }
+ foreach ($addUnique as $columnName) {
+ $addIndex[] = 'add constraint unique key ' . $this->_uniqueKey($tableName, $columnName) . " ($columnName)";;
+ }
+
+ $dropMultiple = array_diff($curIdx['indices'], $newIdx['indices']);
+ $addMultiple = array_diff($newIdx['indices'], $curIdx['indices']);
+ foreach ($dropMultiple as $columnName) {
+ $dropIndex[] = 'drop key ' . $this->_key($tableName, $columnName);
+ }
+ foreach ($addMultiple as $columnName) {
+ $addIndex[] = 'add key ' . $this->_key($tableName, $columnName) . " ($columnName)";
+ }
+
+ // Check for table properties: make sure we're using a sane
+ // engine type and charset/collation.
+ // @fixme make the default engine configurable?
+ $oldProps = $this->getTableProperties($tableName, array('ENGINE', 'TABLE_COLLATION'));
+ if (strtolower($oldProps['ENGINE']) != 'innodb') {
+ $tableProps['ENGINE'] = 'InnoDB';
+ }
+ if (strtolower($oldProps['TABLE_COLLATION']) != 'utf8_bin') {
+ $tableProps['DEFAULT CHARSET'] = 'utf8';
+ $tableProps['COLLATE'] = 'utf8_bin';
+ }
+
+ if (count($dropIndex) + count($toadd) + count($todrop) + count($tomod) + count($addIndex) + count($tableProps) == 0) {
// nothing to do
return true;
}
@@ -429,6 +563,10 @@ class MysqlSchema extends Schema
$phrase = array();
+ foreach ($dropIndex as $indexSql) {
+ $phrase[] = $indexSql;
+ }
+
foreach ($toadd as $columnName) {
$cd = $this->_byName($columns, $columnName);
@@ -445,8 +583,17 @@ class MysqlSchema extends Schema
$phrase[] = 'MODIFY COLUMN ' . $this->_columnSql($cd);
}
+ foreach ($addIndex as $indexSql) {
+ $phrase[] = $indexSql;
+ }
+
+ foreach ($tableProps as $key => $val) {
+ $phrase[] = "$key=$val";
+ }
+
$sql = 'ALTER TABLE ' . $tableName . ' ' . implode(', ', $phrase);
+ common_log(LOG_DEBUG, __METHOD__ . ': ' . $sql);
$res = $this->conn->query($sql);
if (PEAR::isError($res)) {
@@ -519,6 +666,10 @@ class MysqlSchema extends Schema
$sql .= "{$cd->type} ";
}
+ if ($this->_isString($cd)) {
+ $sql .= " CHARACTER SET utf8 ";
+ }
+
if (!empty($cd->default)) {
$sql .= "default {$cd->default} ";
} else {
@@ -535,4 +686,13 @@ class MysqlSchema extends Schema
return $sql;
}
+
+ /**
+ * Is this column a string type?
+ */
+ private function _isString(ColumnDef $cd)
+ {
+ $strings = array('char', 'varchar', 'text');
+ return in_array(strtolower($cd->type), $strings);
+ }
}
diff --git a/lib/noticeform.php b/lib/noticeform.php
index 62df5c941..7278c41a9 100644
--- a/lib/noticeform.php
+++ b/lib/noticeform.php
@@ -233,6 +233,6 @@ class NoticeForm extends Form
'class' => 'submit',
'name' => 'status_submit',
'type' => 'submit',
- 'value' => _('Send')));
+ 'value' => _m('Send button for sending notice', 'Send')));
}
}
diff --git a/lib/noticelist.php b/lib/noticelist.php
index 88a925241..811b7e4f1 100644
--- a/lib/noticelist.php
+++ b/lib/noticelist.php
@@ -442,11 +442,13 @@ class NoticeListItem extends Widget
'title' => $latlon),
$name);
} else {
- $this->out->elementStart('a', array('href' => $url));
- $this->out->element('abbr', array('class' => 'geo',
- 'title' => $latlon),
- $name);
- $this->out->elementEnd('a');
+ $xstr = new XMLStringer(false);
+ $xstr->elementStart('a', array('href' => $url));
+ $xstr->element('abbr', array('class' => 'geo',
+ 'title' => $latlon),
+ $name);
+ $xstr->elementEnd('a');
+ $this->out->raw($xstr->getString());
}
$this->out->elementEnd('span');
}
diff --git a/lib/pgsqlschema.php b/lib/pgsqlschema.php
index 91bc09667..715065d77 100644
--- a/lib/pgsqlschema.php
+++ b/lib/pgsqlschema.php
@@ -61,7 +61,8 @@ class PgsqlSchema extends Schema
public function getTableDef($name)
{
- $res = $this->conn->query("select *, column_default as default, is_nullable as Null, udt_name as Type, column_name AS Field from INFORMATION_SCHEMA.COLUMNS where table_name = '$name'");
+ $res = $this->conn->query("SELECT *, column_default as default, is_nullable as Null,
+ udt_name as Type, column_name AS Field from INFORMATION_SCHEMA.COLUMNS where table_name = '$name'");
if (PEAR::isError($res)) {
throw new Exception($res->getMessage());
@@ -72,6 +73,9 @@ class PgsqlSchema extends Schema
$td->name = $name;
$td->columns = array();
+ if ($res->numRows() == 0 ) {
+ throw new Exception('no such table'); //pretend to be the msyql error. yeah, this sucks.
+ }
$row = array();
while ($res->fetchInto($row, DB_FETCHMODE_ASSOC)) {
@@ -166,12 +170,10 @@ class PgsqlSchema extends Schema
}
if (count($primary) > 0) { // it really should be...
- $sql .= ",\nconstraint primary key (" . implode(',', $primary) . ")";
+ $sql .= ",\n 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)";
@@ -179,6 +181,10 @@ class PgsqlSchema extends Schema
$sql .= "); ";
+
+ foreach ($uniques as $u) {
+ $sql .= "\n CREATE index {$name}_{$u}_idx ON {$name} ($u); ";
+ }
$res = $this->conn->query($sql);
if (PEAR::isError($res)) {
@@ -210,6 +216,22 @@ class PgsqlSchema extends Schema
}
/**
+ * Translate the (mostly) mysql-ish column types into somethings more standard
+ * @param string column type
+ *
+ * @return string postgres happy column type
+ */
+ private function _columnTypeTranslation($type) {
+ $map = array(
+ 'datetime' => 'timestamp'
+ );
+ if(!empty($map[$type])) {
+ return $map[$type];
+ }
+ return $type;
+ }
+
+ /**
* Adds an index to a table.
*
* If no name is provided, a name will be made up based
@@ -359,6 +381,7 @@ class PgsqlSchema extends Schema
try {
$td = $this->getTableDef($tableName);
+
} catch (Exception $e) {
if (preg_match('/no such table/', $e->getMessage())) {
return $this->createTable($tableName, $columns);
@@ -477,11 +500,12 @@ class PgsqlSchema extends Schema
private function _columnSql($cd)
{
$sql = "{$cd->name} ";
+ $type = $this->_columnTypeTranslation($cd->type);
if (!empty($cd->size)) {
- $sql .= "{$cd->type}({$cd->size}) ";
+ $sql .= "{$type}({$cd->size}) ";
} else {
- $sql .= "{$cd->type} ";
+ $sql .= "{$type} ";
}
if (!empty($cd->default)) {
diff --git a/lib/processmanager.php b/lib/processmanager.php
new file mode 100644
index 000000000..6032bfc5c
--- /dev/null
+++ b/lib/processmanager.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * i/o manager to watch for a dead parent process
+ *
+ * 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 QueueManager
+ * @package StatusNet
+ * @author Brion Vibber <brion@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+class ProcessManager extends IoManager
+{
+ protected $socket;
+
+ public static function get()
+ {
+ throw new Exception("Must pass ProcessManager per-instance");
+ }
+
+ public function __construct($socket)
+ {
+ $this->socket = $socket;
+ }
+
+ /**
+ * Tell the i/o queue master if and how we can handle multi-site
+ * processes.
+ *
+ * Return one of:
+ * IoManager::SINGLE_ONLY
+ * IoManager::INSTANCE_PER_SITE
+ * IoManager::INSTANCE_PER_PROCESS
+ */
+ public static function multiSite()
+ {
+ return IoManager::INSTANCE_PER_PROCESS;
+ }
+
+ /**
+ * We won't get any input on it, but if it's broken we'll
+ * know something's gone horribly awry.
+ *
+ * @return array of resources
+ */
+ function getSockets()
+ {
+ return array($this->socket);
+ }
+
+ /**
+ * See if the parent died and request a shutdown...
+ *
+ * @param resource $socket
+ * @return boolean success
+ */
+ function handleInput($socket)
+ {
+ if (feof($socket)) {
+ common_log(LOG_INFO, "Parent process exited; shutting down child.");
+ $this->master->requestShutdown();
+ }
+ return true;
+ }
+}
+
diff --git a/lib/queuemanager.php b/lib/queuemanager.php
index 9fdc80110..87bd356aa 100644
--- a/lib/queuemanager.php
+++ b/lib/queuemanager.php
@@ -213,7 +213,9 @@ abstract class QueueManager extends IoManager
{
if (isset($this->handlers[$queue])) {
$class = $this->handlers[$queue];
- if (class_exists($class)) {
+ if(is_object($class)) {
+ return $class;
+ } else if (class_exists($class)) {
return new $class();
} else {
$this->_log(LOG_ERR, "Nonexistent handler class '$class' for queue '$queue'");
@@ -286,7 +288,7 @@ abstract class QueueManager extends IoManager
* Only registered transports will be reliably picked up!
*
* @param string $transport
- * @param string $class
+ * @param string $class class name or object instance
* @param string $group
*/
public function connect($transport, $class, $group='main')
diff --git a/lib/schema.php b/lib/schema.php
index 137b814e0..1503c96d4 100644
--- a/lib/schema.php
+++ b/lib/schema.php
@@ -485,3 +485,9 @@ class Schema
return $sql;
}
}
+
+class SchemaTableMissingException extends Exception
+{
+ // no-op
+}
+
diff --git a/lib/spawningdaemon.php b/lib/spawningdaemon.php
index fd9ae4355..2f9f6e32e 100644
--- a/lib/spawningdaemon.php
+++ b/lib/spawningdaemon.php
@@ -71,6 +71,8 @@ abstract class SpawningDaemon extends Daemon
*/
function run()
{
+ $this->initPipes();
+
$children = array();
for ($i = 1; $i <= $this->threads; $i++) {
$pid = pcntl_fork();
@@ -129,6 +131,34 @@ abstract class SpawningDaemon extends Daemon
}
/**
+ * Create an IPC socket pair which child processes can use to detect
+ * if the parent process has been killed.
+ */
+ function initPipes()
+ {
+ $sockets = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+ if ($sockets) {
+ $this->parentWriter = $sockets[0];
+ $this->parentReader = $sockets[1];
+ } else {
+ $this->log(LOG_ERROR, "Couldn't create inter-process sockets");
+ exit(1);
+ }
+ }
+
+ /**
+ * Build an IOManager that simply ensures that we have a connection
+ * to the parent process open. If it breaks, the child process will
+ * die.
+ *
+ * @return ProcessManager
+ */
+ public function processManager()
+ {
+ return new ProcessManager($this->parentReader);
+ }
+
+ /**
* Determine whether to respawn an exited subprocess based on its exit code.
* Otherwise we'll respawn all exits by default.
*
@@ -152,6 +182,8 @@ abstract class SpawningDaemon extends Daemon
*/
protected function initAndRunChild($thread)
{
+ // Close the writer end of our parent<->children pipe.
+ fclose($this->parentWriter);
$this->set_id($this->get_id() . "." . $thread);
$this->resetDb();
$exitCode = $this->runThread();
diff --git a/lib/subs.php b/lib/subs.php
index 1c240c475..165bbaa8f 100644
--- a/lib/subs.php
+++ b/lib/subs.php
@@ -42,4 +42,4 @@ function subs_unsubscribe_to($user, $other)
} catch (Exception $e) {
return $e->getMessage();
}
-} \ No newline at end of file
+}
diff --git a/lib/util.php b/lib/util.php
index 44ccc0def..a30d69100 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -1493,7 +1493,15 @@ function common_copy_args($from)
$to = array();
$strip = get_magic_quotes_gpc();
foreach ($from as $k => $v) {
- $to[$k] = ($strip) ? stripslashes($v) : $v;
+ if($strip) {
+ if(is_array($v)) {
+ $to[$k] = common_copy_args($v);
+ } else {
+ $to[$k] = stripslashes($v);
+ }
+ } else {
+ $to[$k] = $v;
+ }
}
return $to;
}