diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/accountsettingsaction.php | 30 | ||||
-rw-r--r-- | lib/action.php | 72 | ||||
-rw-r--r-- | lib/activity.php | 3 | ||||
-rw-r--r-- | lib/adminpanelaction.php | 11 | ||||
-rw-r--r-- | lib/apiaction.php | 4 | ||||
-rw-r--r-- | lib/apiauth.php | 5 | ||||
-rw-r--r-- | lib/applicationeditform.php | 41 | ||||
-rw-r--r-- | lib/applicationlist.php | 17 | ||||
-rw-r--r-- | lib/atomgroupnoticefeed.php | 3 | ||||
-rw-r--r-- | lib/atomusernoticefeed.php | 3 | ||||
-rw-r--r-- | lib/attachmentlist.php | 3 | ||||
-rw-r--r-- | lib/command.php | 36 | ||||
-rw-r--r-- | lib/common.php | 12 | ||||
-rw-r--r-- | lib/deluserqueuehandler.php | 10 | ||||
-rw-r--r-- | lib/htmloutputter.php | 1 | ||||
-rw-r--r-- | lib/implugin.php | 4 | ||||
-rw-r--r-- | lib/installer.php | 578 | ||||
-rw-r--r-- | lib/language.php | 3 | ||||
-rw-r--r-- | lib/mail.php | 45 | ||||
-rw-r--r-- | lib/noticeform.php | 4 | ||||
-rw-r--r-- | lib/noticelist.php | 25 | ||||
-rw-r--r-- | lib/profileformaction.php | 25 | ||||
-rw-r--r-- | lib/redirectingaction.php | 96 | ||||
-rw-r--r-- | lib/statusnet.php | 15 | ||||
-rw-r--r-- | lib/util.php | 49 |
25 files changed, 985 insertions, 110 deletions
diff --git a/lib/accountsettingsaction.php b/lib/accountsettingsaction.php index c79a1f5d7..57740f8b8 100644 --- a/lib/accountsettingsaction.php +++ b/lib/accountsettingsaction.php @@ -105,27 +105,45 @@ class AccountSettingsNav extends Widget $user = common_current_user(); if(Event::handle('StartAccountSettingsProfileMenuItem', array($this, &$menu))){ - $this->showMenuItem('profilesettings',_('Profile'),_('Change your profile settings')); + // TRANS: Link title attribute in user account settings menu. + $title = _('Change your profile settings'); + // TRANS: Link description in user account settings menu. + $this->showMenuItem('profilesettings',_('Profile'),$title); Event::handle('EndAccountSettingsProfileMenuItem', array($this, &$menu)); } if(Event::handle('StartAccountSettingsAvatarMenuItem', array($this, &$menu))){ - $this->showMenuItem('avatarsettings',_('Avatar'),_('Upload an avatar')); + // TRANS: Link title attribute in user account settings menu. + $title = _('Upload an avatar'); + // TRANS: Link description in user account settings menu. + $this->showMenuItem('avatarsettings',_('Avatar'),$title); Event::handle('EndAccountSettingsAvatarMenuItem', array($this, &$menu)); } if(Event::handle('StartAccountSettingsPasswordMenuItem', array($this, &$menu))){ - $this->showMenuItem('passwordsettings',_('Password'),_('Change your password')); + // TRANS: Link title attribute in user account settings menu. + $title = _('Change your password'); + // TRANS: Link description in user account settings menu. + $this->showMenuItem('passwordsettings',_('Password'),$title); Event::handle('EndAccountSettingsPasswordMenuItem', array($this, &$menu)); } if(Event::handle('StartAccountSettingsEmailMenuItem', array($this, &$menu))){ - $this->showMenuItem('emailsettings',_('Email'),_('Change email handling')); + // TRANS: Link title attribute in user account settings menu. + $title = _('Change email handling'); + // TRANS: Link description in user account settings menu. + $this->showMenuItem('emailsettings',_('Email'),$title); Event::handle('EndAccountSettingsEmailMenuItem', array($this, &$menu)); } if(Event::handle('StartAccountSettingsDesignMenuItem', array($this, &$menu))){ - $this->showMenuItem('userdesignsettings',_('Design'),_('Design your profile')); + // TRANS: Link title attribute in user account settings menu. + $title = _('Design your profile'); + // TRANS: Link description in user account settings menu. + $this->showMenuItem('userdesignsettings',_('Design'),$title); Event::handle('EndAccountSettingsDesignMenuItem', array($this, &$menu)); } if(Event::handle('StartAccountSettingsOtherMenuItem', array($this, &$menu))){ - $this->showMenuItem('othersettings',_('Other'),_('Other options')); + // TRANS: Link title attribute in user account settings menu. + $title = _('Other options'); + // TRANS: Link description in user account settings menu. + $this->showMenuItem('othersettings',_('Other'),$title); Event::handle('EndAccountSettingsOtherMenuItem', array($this, &$menu)); } diff --git a/lib/action.php b/lib/action.php index 09113a598..4296ae7de 100644 --- a/lib/action.php +++ b/lib/action.php @@ -141,6 +141,7 @@ class Action extends HTMLOutputter // lawsuit function showTitle() { $this->element('title', null, + // TRANS: Page title. %1$s is the title, %2$s is the site name. sprintf(_("%1\$s - %2\$s"), $this->title(), common_config('site', 'name'))); @@ -156,6 +157,7 @@ class Action extends HTMLOutputter // lawsuit function title() { + // TRANS: Page title for a page without a title set. return _("Untitled page"); } @@ -420,6 +422,7 @@ class Action extends HTMLOutputter // lawsuit { $user = common_current_user(); $this->elementStart('dl', array('id' => 'site_nav_global_primary')); + // TRANS: DT element for primary navigation menu. String is hidden in default CSS. $this->element('dt', null, _('Primary site navigation')); $this->elementStart('dd'); $this->elementStart('ul', array('class' => 'nav')); @@ -427,31 +430,31 @@ class Action extends HTMLOutputter // lawsuit 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)), + // TRANS: Main menu option when logged in for access to personal profile and friends timeline _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'), + // TRANS: Main menu option when logged in for access to user settings _('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'), + // TRANS: Main menu option when logged in and connection are possible for access to options to connect to other services _('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'), + // TRANS: Main menu option when logged in and site admin for access to site configuration _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'), + // TRANS: Main menu option when logged in and invitations are allowed for inviting new users _m('MENU', 'Invite'), sprintf($tooltip, common_config('site', 'name')), @@ -459,16 +462,16 @@ class Action extends HTMLOutputter // lawsuit } // 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'), + // TRANS: Main menu option when logged in to log out the current user _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'), + // TRANS: Main menu option when not logged in to register a new account _m('MENU', 'Register'), $tooltip, false, 'nav_register'); } // TRANS: Tooltip for main menu option "Login" @@ -575,6 +578,7 @@ class Action extends HTMLOutputter // lawsuit function showLocalNavBlock() { $this->elementStart('dl', array('id' => 'site_nav_local_views')); + // TRANS: DT element for local views block. String is hidden in default CSS. $this->element('dt', null, _('Local views')); $this->elementStart('dd'); $this->showLocalNav(); @@ -641,6 +645,7 @@ class Action extends HTMLOutputter // lawsuit $this->elementStart('dl', array('id' => 'page_notice', 'class' => 'system_notice')); + // TRANS: DT element for page notice. String is hidden in default CSS. $this->element('dt', null, _('Page notice')); $this->elementStart('dd'); if (Event::handle('StartShowPageNotice', array($this))) { @@ -743,28 +748,37 @@ class Action extends HTMLOutputter // lawsuit function showSecondaryNav() { $this->elementStart('dl', array('id' => 'site_nav_global_secondary')); + // TRANS: DT element for secondary navigation menu. String is hidden in default CSS. $this->element('dt', null, _('Secondary site navigation')); $this->elementStart('dd', null); $this->elementStart('ul', array('class' => 'nav')); if (Event::handle('StartSecondaryNav', array($this))) { $this->menuItem(common_local_url('doc', array('title' => 'help')), + // TRANS: Secondary navigation menu option leading to help on StatusNet. _('Help')); $this->menuItem(common_local_url('doc', array('title' => 'about')), + // TRANS: Secondary navigation menu option leading to text about StatusNet site. _('About')); $this->menuItem(common_local_url('doc', array('title' => 'faq')), + // TRANS: Secondary navigation menu option leading to Frequently Asked Questions. _('FAQ')); $bb = common_config('site', 'broughtby'); if (!empty($bb)) { $this->menuItem(common_local_url('doc', array('title' => 'tos')), + // TRANS: Secondary navigation menu option leading to Terms of Service. _('TOS')); } $this->menuItem(common_local_url('doc', array('title' => 'privacy')), + // TRANS: Secondary navigation menu option leading to privacy policy. _('Privacy')); $this->menuItem(common_local_url('doc', array('title' => 'source')), + // TRANS: Secondary navigation menu option. _('Source')); $this->menuItem(common_local_url('version'), + // TRANS: Secondary navigation menu option leading to version information on the StatusNet site. _('Version')); $this->menuItem(common_local_url('doc', array('title' => 'contact')), + // TRANS: Secondary navigation menu option leading to contact information on the StatusNet site. _('Contact')); $this->menuItem(common_local_url('doc', array('title' => 'badge')), _('Badge')); @@ -795,16 +809,18 @@ class Action extends HTMLOutputter // lawsuit */ function showStatusNetLicense() { + // TRANS: DT element for StatusNet software license. $this->element('dt', array('id' => 'site_statusnet_license'), _('StatusNet software license')); $this->elementStart('dd', null); - // @fixme drop the final spaces in the messages when at good spot - // to let translations get updated. if (common_config('site', 'broughtby')) { - $instr = _('**%%site.name%%** is a microblogging service brought to you by [%%site.broughtby%%](%%site.broughtbyurl%%). '); + // TRANS: First sentence of the StatusNet site license. Used if 'broughtby' is set. + $instr = _('**%%site.name%%** is a microblogging service brought to you by [%%site.broughtby%%](%%site.broughtbyurl%%).'); } else { - $instr = _('**%%site.name%%** is a microblogging service. '); + // TRANS: First sentence of the StatusNet site license. Used if 'broughtby' is not set. + $instr = _('**%%site.name%%** is a microblogging service.'); } $instr .= ' '; + // TRANS: Second sentence of the StatusNet site license. Mentions the StatusNet source code license. $instr .= sprintf(_('It runs the [StatusNet](http://status.net/) microblogging software, version %s, available under the [GNU Affero General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html).'), STATUSNET_VERSION); $output = common_markup_to_html($instr); $this->raw($output); @@ -820,19 +836,25 @@ class Action extends HTMLOutputter // lawsuit function showContentLicense() { if (Event::handle('StartShowContentLicense', array($this))) { + // TRANS: DT element for StatusNet site content license. $this->element('dt', array('id' => 'site_content_license'), _('Site content license')); $this->elementStart('dd', array('id' => 'site_content_license_cc')); switch (common_config('license', 'type')) { case 'private': + // TRANS: Content license displayed when license is set to 'private'. + // TRANS: %1$s is the site name. $this->element('p', null, sprintf(_('Content and data of %1$s are private and confidential.'), common_config('site', 'name'))); // fall through case 'allrightsreserved': if (common_config('license', 'owner')) { + // TRANS: Content license displayed when license is set to 'allrightsreserved'. + // TRANS: %1$s is the copyright owner. $this->element('p', null, sprintf(_('Content and data copyright by %1$s. All rights reserved.'), common_config('license', 'owner'))); } else { + // TRANS: Content license displayed when license is set to 'allrightsreserved' and no owner is set. $this->element('p', null, _('Content and data copyright by contributors. All rights reserved.')); } break; @@ -845,14 +867,16 @@ class Action extends HTMLOutputter // lawsuit 'width' => '80', 'height' => '15')); $this->text(' '); - //TODO: This is dirty: i18n - $this->text(_('All '.common_config('site', 'name').' content and data are available under the ')); - $this->element('a', array('class' => 'license', - 'rel' => 'external license', - 'href' => common_config('license', 'url')), - common_config('license', 'title')); - $this->text(' '); - $this->text(_('license.')); + // TRANS: license message in footer. %1$s is the site name, %2$s is a link to the license URL, with a licence name set in configuration. + $notice = _('All %1$s content and data are available under the %2$s license.'); + $link = "<a class=\"license\" rel=\"external license\" href=\"" . + htmlspecialchars(common_config('license', 'url')) . + "\">" . + htmlspecialchars(common_config('license', 'title')) . + "</a>"; + $this->raw(sprintf(htmlspecialchars($notice), + htmlspecialchars(common_config('site', 'name')), + $link)); $this->elementEnd('p'); break; } @@ -1146,11 +1170,15 @@ class Action extends HTMLOutputter // lawsuit * * @return nothing */ + // XXX: The messages in this pagination method only tailor to navigating + // notices. In other lists, "Previous"/"Next" type navigation is + // desirable, but not available. function pagination($have_before, $have_after, $page, $action, $args=null) { // Does a little before-after block for next/prev page if ($have_before || $have_after) { $this->elementStart('dl', 'pagination'); + // TRANS: DT element for pagination (previous/next, etc.). $this->element('dt', null, _('Pagination')); $this->elementStart('dd', null); $this->elementStart('ul', array('class' => 'nav')); @@ -1160,6 +1188,8 @@ class Action extends HTMLOutputter // lawsuit $this->elementStart('li', array('class' => 'nav_prev')); $this->element('a', array('href' => common_local_url($action, $args, $pargs), 'rel' => 'prev'), + // TRANS: Pagination message to go to a page displaying information more in the + // TRANS: present than the currently displayed information. _('After')); $this->elementEnd('li'); } @@ -1168,6 +1198,8 @@ class Action extends HTMLOutputter // lawsuit $this->elementStart('li', array('class' => 'nav_next')); $this->element('a', array('href' => common_local_url($action, $args, $pargs), 'rel' => 'next'), + // TRANS: Pagination message to go to a page displaying information more in the + // TRANS: past than the currently displayed information. _('Before')); $this->elementEnd('li'); } @@ -1211,6 +1243,8 @@ class Action extends HTMLOutputter // lawsuit * @return void */ + // XXX: Finding this type of check with the same message about 50 times. + // Possible to refactor? function checkSessionToken() { // CSRF protection diff --git a/lib/activity.php b/lib/activity.php index 5d6230c6d..365bb6258 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -117,7 +117,8 @@ class Activity // Insist on a feed's root DOMElement; don't allow a DOMDocument if ($feed instanceof DOMDocument) { throw new ClientException( - _("Expecting a root feed element but got a whole XML document.") + // TRANS: Client exception thrown when a feed instance is a DOMDocument. + _('Expecting a root feed element but got a whole XML document.') ); } diff --git a/lib/adminpanelaction.php b/lib/adminpanelaction.php index d87981b6a..6c9947608 100644 --- a/lib/adminpanelaction.php +++ b/lib/adminpanelaction.php @@ -69,7 +69,7 @@ class AdminPanelAction extends Action // User must be logged in. if (!common_logged_in()) { - // TRANS: Client error message + // TRANS: Client error message thrown when trying to access the admin panel while not logged in. $this->clientError(_('Not logged in.')); return false; } @@ -94,7 +94,7 @@ class AdminPanelAction extends Action // User must have the right to change admin settings if (!$user->hasRight(Right::CONFIGURESITE)) { - // TRANS: Client error message + // TRANS: Client error message thrown when a user tries to change admin settings but has no access rights. $this->clientError(_('You cannot make changes to this site.')); return false; } @@ -106,7 +106,7 @@ class AdminPanelAction extends Action $name = mb_substr($name, 0, -10); if (!self::canAdmin($name)) { - // TRANS: Client error message + // TRANS: Client error message throw when a certain panel's settings cannot be changed. $this->clientError(_('Changes to that panel are not allowed.'), 403); return false; } @@ -225,7 +225,7 @@ class AdminPanelAction extends Action function showForm() { - // TRANS: Client error message + // TRANS: Client error message. $this->clientError(_('showForm() not implemented.')); return; } @@ -279,7 +279,8 @@ class AdminPanelAction extends Action $result = $config->delete(); if (!$result) { common_log_db_error($config, 'DELETE', __FILE__); - // TRANS: Client error message + // TRANS: Client error message thrown if design settings could not be deleted in + // TRANS: the admin panel Design. $this->clientError(_("Unable to delete design setting.")); return null; } diff --git a/lib/apiaction.php b/lib/apiaction.php index 59dc47c23..d5580abd3 100644 --- a/lib/apiaction.php +++ b/lib/apiaction.php @@ -102,6 +102,7 @@ class ApiAction extends Action function handle($args) { + header('Access-Control-Allow-Origin: *'); parent::handle($args); } @@ -1065,6 +1066,7 @@ class ApiAction extends Action $this->initTwitterAtom(); break; default: + // TRANS: Client error on an API request with an unsupported data format. $this->clientError(_('Not a supported data format.')); break; } @@ -1093,6 +1095,7 @@ class ApiAction extends Action $this->endTwitterRss(); break; default: + // TRANS: Client error on an API request with an unsupported data format. $this->clientError(_('Not a supported data format.')); break; } @@ -1209,6 +1212,7 @@ class ApiAction extends Action $this->showJsonObjects($profile_array); break; default: + // TRANS: Client error on an API request with an unsupported data format. $this->clientError(_('Not a supported data format.')); return; } diff --git a/lib/apiauth.php b/lib/apiauth.php index e78de618e..d6ad7e021 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -91,6 +91,7 @@ class ApiAuthAction extends ApiAction if ($this->isReadOnly($args) == false) { if ($this->access != self::READ_WRITE) { + // TRANS: Client error 401. $msg = _('API resource requires read-write access, ' . 'but you only have read access.'); $this->clientError($msg, 401, $this->format); @@ -273,8 +274,8 @@ class ApiAuthAction extends ApiAction list($proxy, $ip) = common_client_ip(); - $msg = sprintf(_('Failed API auth attempt, nickname = %1$s, ' . - 'proxy = %2$s, ip = %3$s'), + $msg = sprintf( 'Failed API auth attempt, nickname = %1$s, ' . + 'proxy = %2$s, ip = %3$s', $this->auth_user_nickname, $proxy, $ip); diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php index 9b7d05861..81c8fb018 100644 --- a/lib/applicationeditform.php +++ b/lib/applicationeditform.php @@ -133,6 +133,7 @@ class ApplicationEditForm extends Form function formLegend() { + // TRANS: Form legend. $this->out->element('legend', null, _('Edit application')); } @@ -177,10 +178,12 @@ class ApplicationEditForm extends Form } $this->out->element('label', array('for' => 'app_icon'), + // TRANS: Form input field label for application icon. _('Icon')); $this->out->element('input', array('name' => 'app_icon', 'type' => 'file', 'id' => 'app_icon')); + // TRANS: Form guide. $this->out->element('p', 'form_guide', _('Icon for this application')); $this->out->element('input', array('name' => 'MAX_FILE_SIZE', 'type' => 'hidden', @@ -192,6 +195,7 @@ class ApplicationEditForm extends Form $this->out->hidden('application_id', $id); + // TRANS: Form input field label for application name. $this->out->input('name', _('Name'), ($this->out->arg('name')) ? $this->out->arg('name') : $name); @@ -201,11 +205,14 @@ class ApplicationEditForm extends Form $maxDesc = Oauth_application::maxDesc(); if ($maxDesc > 0) { + // TRANS: Form input field instructions. $descInstr = sprintf(_('Describe your application in %d characters'), $maxDesc); } else { + // TRANS: Form input field instructions. $descInstr = _('Describe your application'); } + // TRANS: Form input field label. $this->out->textarea('description', _('Description'), ($this->out->arg('description')) ? $this->out->arg('description') : $description, $descInstr); @@ -213,27 +220,39 @@ class ApplicationEditForm extends Form $this->out->elementEnd('li'); $this->out->elementStart('li'); + // TRANS: Form input field instructions. + $instruction = _('URL of the homepage of this application'); + // TRANS: Form input field label. $this->out->input('source_url', _('Source URL'), ($this->out->arg('source_url')) ? $this->out->arg('source_url') : $source_url, - _('URL of the homepage of this application')); + $instruction); $this->out->elementEnd('li'); $this->out->elementStart('li'); + // TRANS: Form input field instructions. + $instruction = _('Organization responsible for this application'); + // TRANS: Form input field label. $this->out->input('organization', _('Organization'), ($this->out->arg('organization')) ? $this->out->arg('organization') : $organization, - _('Organization responsible for this application')); + $instruction); $this->out->elementEnd('li'); $this->out->elementStart('li'); + // TRANS: Form input field instructions. + $instruction = _('URL for the homepage of the organization'); + // TRANS: Form input field label. $this->out->input('homepage', _('Homepage'), ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage, - _('URL for the homepage of the organization')); + $instruction); $this->out->elementEnd('li'); $this->out->elementStart('li'); + // TRANS: Form input field instructions. + $instruction = _('URL to redirect to after authentication'); + // TRANS: Form input field label. $this->out->input('callback_url', ('Callback URL'), ($this->out->arg('callback_url')) ? $this->out->arg('callback_url') : $callback_url, - _('URL to redirect to after authentication')); + $instruction); $this->out->elementEnd('li'); $this->out->elementStart('li', array('id' => 'application_types')); @@ -255,6 +274,7 @@ class ApplicationEditForm extends Form $this->out->element('label', array('for' => 'app_type-browser', 'class' => 'radio'), + // TRANS: Radio button label for application type _('Browser')); $attrs = array('name' => 'app_type', @@ -271,7 +291,9 @@ class ApplicationEditForm extends Form $this->out->element('label', array('for' => 'app_type-desktop', 'class' => 'radio'), + // TRANS: Radio button label for application type _('Desktop')); + // TRANS: Form guide. $this->out->element('p', 'form_guide', _('Type of application, browser or desktop')); $this->out->elementEnd('li'); @@ -294,6 +316,7 @@ class ApplicationEditForm extends Form $this->out->element('label', array('for' => 'default_access_type-ro', 'class' => 'radio'), + // TRANS: Radio button label for access type. _('Read-only')); $attrs = array('name' => 'default_access_type', @@ -312,7 +335,9 @@ class ApplicationEditForm extends Form $this->out->element('label', array('for' => 'default_access_type-rw', 'class' => 'radio'), + // TRANS: Radio button label for access type. _('Read-write')); + // TRANS: Form guide. $this->out->element('p', 'form_guide', _('Default access for this application: read-only, or read-write')); $this->out->elementEnd('li'); @@ -328,9 +353,13 @@ class ApplicationEditForm extends Form function formActions() { - $this->out->submit('cancel', _('Cancel'), 'submit form_action-primary', + // TRANS: Button label + $this->out->submit('cancel', _m('BUTTON','Cancel'), 'submit form_action-primary', + // TRANS: Submit button title 'cancel', _('Cancel')); - $this->out->submit('save', _('Save'), 'submit form_action-secondary', + // TRANS: Button label + $this->out->submit('save', _m('BUTTON','Save'), 'submit form_action-secondary', + // TRANS: Submit button title 'save', _('Save')); } } diff --git a/lib/applicationlist.php b/lib/applicationlist.php index 3abb1f8aa..904f8981d 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -88,7 +88,6 @@ class ApplicationList extends Widget function showApplication() { - $user = common_current_user(); $this->out->elementStart('li', array('class' => 'application', @@ -133,11 +132,16 @@ class ApplicationList extends Widget $this->out->elementStart('li'); - $access = ($this->application->access_type & Oauth_application::$writeAccess) - ? 'read-write' : 'read-only'; + // TRANS: Application access type + $readWriteText = _('read-write'); + // TRANS: Application access type + $readOnlyText = _('read-only'); - $txt = 'Approved ' . common_date_string($appUser->modified) . - " - $access access."; + $access = ($this->application->access_type & Oauth_application::$writeAccess) + ? $readWriteText : $readOnlyText; + $modifiedDate = common_date_string($appUser->modified); + // TRANS: Used in application list. %1$s is a modified date, %2$s is access type (read-write or read-only) + $txt = sprintf(_('Approved %1$s - "%2$s" access.'),$modifiedDate,$access); $this->out->raw($txt); $this->out->elementEnd('li'); @@ -151,7 +155,8 @@ class ApplicationList extends Widget $this->out->elementStart('fieldset'); $this->out->hidden('id', $this->application->id); $this->out->hidden('token', common_session_token()); - $this->out->submit('revoke', _('Revoke')); + // TRANS: Button label + $this->out->submit('revoke', _m('BUTTON','Revoke')); $this->out->elementEnd('fieldset'); $this->out->elementEnd('form'); $this->out->elementEnd('li'); diff --git a/lib/atomgroupnoticefeed.php b/lib/atomgroupnoticefeed.php index 08c1c707c..b4810d04a 100644 --- a/lib/atomgroupnoticefeed.php +++ b/lib/atomgroupnoticefeed.php @@ -58,11 +58,14 @@ class AtomGroupNoticeFeed extends AtomNoticeFeed parent::__construct($indent); $this->group = $group; + // TRANS: Title in atom group notice feed. %s is a group name. $title = sprintf(_("%s timeline"), $group->nickname); $this->setTitle($title); $sitename = common_config('site', 'name'); $subtitle = sprintf( + // TRANS: Message is used as a subtitle in atom group notice feed. + // TRANS: %1$s is a group name, %2$s is a site name. _('Updates from %1$s on %2$s!'), $group->nickname, $sitename diff --git a/lib/atomusernoticefeed.php b/lib/atomusernoticefeed.php index 428cc2de2..acfcbd75f 100644 --- a/lib/atomusernoticefeed.php +++ b/lib/atomusernoticefeed.php @@ -64,11 +64,14 @@ class AtomUserNoticeFeed extends AtomNoticeFeed $this->setActivitySubject($profile->asActivityNoun('subject')); } + // TRANS: Title in atom user notice feed. %s is a user name. $title = sprintf(_("%s timeline"), $user->nickname); $this->setTitle($title); $sitename = common_config('site', 'name'); $subtitle = sprintf( + // TRANS: Message is used as a subtitle in atom user notice feed. + // TRANS: %1$s is a user name, %2$s is a site name. _('Updates from %1$s on %2$s!'), $user->nickname, $sitename ); diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index 43f836e15..59cab9532 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -84,6 +84,7 @@ class AttachmentList extends Widget if (empty($att)) return 0; $this->out->elementStart('dl', array('id' =>'attachments', 'class' => 'entry-content')); + // TRANS: DT element label in attachment list. $this->out->element('dt', null, _('Attachments')); $this->out->elementStart('dd'); $this->out->elementStart('ol', array('class' => 'attachments')); @@ -260,6 +261,7 @@ class Attachment extends AttachmentListItem 'class' => 'entry-content')); if (!empty($this->oembed->author_name)) { $this->out->elementStart('dl', 'vcard author'); + // TRANS: DT element label in attachment list item. $this->out->element('dt', null, _('Author')); $this->out->elementStart('dd', 'fn'); if (empty($this->oembed->author_url)) { @@ -273,6 +275,7 @@ class Attachment extends AttachmentListItem } if (!empty($this->oembed->provider)) { $this->out->elementStart('dl', 'vcard'); + // TRANS: DT element label in attachment list item. $this->out->element('dt', null, _('Provider')); $this->out->elementStart('dd', 'fn'); if (empty($this->oembed->provider_url)) { diff --git a/lib/command.php b/lib/command.php index 084c61fd1..30db9d069 100644 --- a/lib/command.php +++ b/lib/command.php @@ -122,6 +122,8 @@ class Command } Event::handle('EndCommandGetProfile', array($this, $arg, &$profile)); if (!$profile) { + // TRANS: Message given requesting a profile for a non-existing user. + // TRANS: %s is the nickname of the user for which the profile could not be found. throw new CommandException(sprintf(_('Could not find a user with nickname %s'), $arg)); } return $profile; @@ -140,6 +142,8 @@ class Command } Event::handle('EndCommandGetUser', array($this, $arg, &$user)); if (!$user){ + // TRANS: Message given getting a non-existing user. + // TRANS: %s is the nickname of the user that could not be found. throw new CommandException(sprintf(_('Could not find a local user with nickname %s'), $arg)); } @@ -225,6 +229,8 @@ class NudgeCommand extends Command } // XXX: notify by IM // XXX: notify by SMS + // TRANS: Message given having nudged another user. + // TRANS: %s is the nickname of the user that was nudged. $channel->output($this->user, sprintf(_('Nudge sent to %s'), $recipient->nickname)); } @@ -328,12 +334,16 @@ class JoinCommand extends Command Event::handle('EndJoinGroup', array($group, $cur)); } } catch (Exception $e) { - $channel->error($cur, sprintf(_('Could not join user %s to group %s'), + // TRANS: Message given having failed to add a user to a group. + // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group. + $channel->error($cur, sprintf(_('Could not join user %1$s to group %2$s'), $cur->nickname, $group->nickname)); return; } - $channel->output($cur, sprintf(_('%s joined group %s'), + // TRANS: Message given having added a user to a group. + // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group. + $channel->output($cur, sprintf(_('%1$s joined group %2$s'), $cur->nickname, $group->nickname)); } @@ -370,12 +380,16 @@ class DropCommand extends Command Event::handle('EndLeaveGroup', array($group, $cur)); } } catch (Exception $e) { - $channel->error($cur, sprintf(_('Could not remove user %s to group %s'), + // TRANS: Message given having failed to remove a user from a group. + // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group. + $channel->error($cur, sprintf(_('Could not remove user %1$s from group %2$s'), $cur->nickname, $group->nickname)); return; } - $channel->output($cur, sprintf(_('%s left group %s'), + // TRANS: Message given having removed a user from a group. + // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group. + $channel->output($cur, sprintf(_('%1$s left group %2$s'), $cur->nickname, $group->nickname)); } @@ -395,18 +409,24 @@ class WhoisCommand extends Command { $recipient = $this->getProfile($this->other); + // TRANS: Whois output. + // TRANS: %1$s nickname of the queried user, %2$s is their profile URL. $whois = sprintf(_("%1\$s (%2\$s)"), $recipient->nickname, $recipient->profileurl); if ($recipient->fullname) { + // TRANS: Whois output. %s is the full name of the queried user. $whois .= "\n" . sprintf(_('Fullname: %s'), $recipient->fullname); } if ($recipient->location) { + // TRANS: Whois output. %s is the location of the queried user. $whois .= "\n" . sprintf(_('Location: %s'), $recipient->location); } if ($recipient->homepage) { + // TRANS: Whois output. %s is the homepage of the queried user. $whois .= "\n" . sprintf(_('Homepage: %s'), $recipient->homepage); } if ($recipient->bio) { + // TRANS: Whois output. %s is the bio information of the queried user. $whois .= "\n" . sprintf(_('About: %s'), $recipient->bio); } $channel->output($this->user, $whois); @@ -447,7 +467,9 @@ class MessageCommand extends Command $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'), + // TRANS: Message given if content is too long. + // TRANS: %1$d is the maximum number of characters, %2$d is the number of submitted characters. + $channel->error($this->user, sprintf(_('Message too long - maximum is %1$d characters, you sent %2$d'), Message::maxContent(), mb_strlen($this->text))); return; } @@ -465,6 +487,8 @@ class MessageCommand extends Command $message = Message::saveNew($this->user->id, $other->id, $this->text, $channel->source()); if ($message) { $message->notify(); + // TRANS: Message given have sent a direct message to another user. + // TRANS: %s is the name of the other user. $channel->output($this->user, sprintf(_('Direct message to %s sent'), $this->other)); } else { $channel->error($this->user, _('Error sending direct message.')); @@ -500,6 +524,8 @@ class RepeatCommand extends Command if ($repeat) { + // TRANS: Message given having repeated a notice from another user. + // TRANS: %s is the name of the user for which the notice was repeated. $channel->output($this->user, sprintf(_('Notice from %s repeated'), $recipient->nickname)); } else { $channel->error($this->user, _('Error repeating notice.')); diff --git a/lib/common.php b/lib/common.php index 8d2e6b420..45946c216 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 @@ -127,6 +128,17 @@ require_once INSTALLDIR.'/lib/subs.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/deluserqueuehandler.php b/lib/deluserqueuehandler.php index 4a1233a5e..710303938 100644 --- a/lib/deluserqueuehandler.php +++ b/lib/deluserqueuehandler.php @@ -49,9 +49,13 @@ class DelUserQueueHandler extends QueueHandler return true; } - if (!$user->hasRole(Profile_role::DELETED)) { - common_log(LOG_INFO, "User {$user->nickname} is not pending deletion; aborting."); - return true; + try { + if (!$user->hasRole(Profile_role::DELETED)) { + common_log(LOG_INFO, "User {$user->nickname} is not pending deletion; aborting."); + return true; + } + } catch (UserNoProfileException $unp) { + common_log(LOG_INFO, "Deleting user {$user->nickname} with no profile... probably a good idea!"); } $notice = $this->getNextBatch($user); diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 9d06ba23c..7eccd6cc0 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -100,6 +100,7 @@ class HTMLOutputter extends XMLOutputter $type = common_negotiate_type($cp, $sp); if (!$type) { + // TRANS: Client exception 406 throw new ClientException(_('This page is not available in a '. 'media type you accept'), 406); } diff --git a/lib/implugin.php b/lib/implugin.php index 018b0ecee..7302859a4 100644 --- a/lib/implugin.php +++ b/lib/implugin.php @@ -586,7 +586,9 @@ abstract class ImPlugin extends Plugin function onGetImTransports(&$transports) { - $transports[$this->transport] = array('display' => $this->getDisplayName()); + $transports[$this->transport] = array( + 'display' => $this->getDisplayName(), + 'daemon_screenname' => $this->daemon_screenname()); } function onSendImConfirmationCode($transport, $screenname, $code, $user) diff --git a/lib/installer.php b/lib/installer.php new file mode 100644 index 000000000..d0e46f95c --- /dev/null +++ b/lib/installer.php @@ -0,0 +1,578 @@ +<?php + +/** + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 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/>. + * + * @category Installation + * @package Installation + * + * @author Adrian Lang <mail@adrianlang.de> + * @author Brenda Wallace <shiny@cpan.org> + * @author Brett Taylor <brett@webfroot.co.nz> + * @author Brion Vibber <brion@pobox.com> + * @author CiaranG <ciaran@ciarang.com> + * @author Craig Andrews <candrews@integralblue.com> + * @author Eric Helgeson <helfire@Erics-MBP.local> + * @author Evan Prodromou <evan@status.net> + * @author Robin Millette <millette@controlyourself.ca> + * @author Sarven Capadisli <csarven@status.net> + * @author Tom Adams <tom@holizz.com> + * @author Zach Copley <zach@status.net> + * @license GNU Affero General Public License http://www.gnu.org/licenses/ + * @version 0.9.x + * @link http://status.net + */ + +abstract class Installer +{ + /** Web site info */ + public $sitename, $server, $path, $fancy; + /** DB info */ + public $host, $dbname, $dbtype, $username, $password, $db; + /** Administrator info */ + public $adminNick, $adminPass, $adminEmail, $adminUpdates; + /** Should we skip writing the configuration file? */ + public $skipConfig = false; + + public static $dbModules = array( + 'mysql' => array( + 'name' => 'MySQL', + 'check_module' => 'mysql', // mysqli? + 'installer' => 'mysql_db_installer', + ), + 'pgsql' => array( + 'name' => 'PostgreSQL', + 'check_module' => 'pgsql', + 'installer' => 'pgsql_db_installer', + ), + ); + + /** + * Attempt to include a PHP file and report if it worked, while + * suppressing the annoying warning messages on failure. + */ + private function haveIncludeFile($filename) { + $old = error_reporting(error_reporting() & ~E_WARNING); + $ok = include_once($filename); + error_reporting($old); + return $ok; + } + + /** + * Check if all is ready for installation + * + * @return void + */ + function checkPrereqs() + { + $pass = true; + + if (file_exists(INSTALLDIR.'/config.php')) { + $this->warning('Config file "config.php" already exists.'); + $pass = false; + } + + if (version_compare(PHP_VERSION, '5.2.3', '<')) { + $errors[] = 'Require PHP version 5.2.3 or greater.'; + $pass = false; + } + + // Look for known library bugs + $str = "abcdefghijklmnopqrstuvwxyz"; + $replaced = preg_replace('/[\p{Cc}\p{Cs}]/u', '*', $str); + if ($str != $replaced) { + $this->warning('PHP is linked to a version of the PCRE library ' . + 'that does not support Unicode properties. ' . + 'If you are running Red Hat Enterprise Linux / ' . + 'CentOS 5.4 or earlier, see <a href="' . + 'http://status.net/wiki/Red_Hat_Enterprise_Linux#PCRE_library' . + '">our documentation page</a> on fixing this.'); + $pass = false; + } + + $reqs = array('gd', 'curl', + 'xmlwriter', 'mbstring', 'xml', 'dom', 'simplexml'); + + foreach ($reqs as $req) { + if (!$this->checkExtension($req)) { + $this->warning(sprintf('Cannot load required extension: <code>%s</code>', $req)); + $pass = false; + } + } + + // Make sure we have at least one database module available + $missingExtensions = array(); + foreach (self::$dbModules as $type => $info) { + if (!$this->checkExtension($info['check_module'])) { + $missingExtensions[] = $info['check_module']; + } + } + + if (count($missingExtensions) == count(self::$dbModules)) { + $req = implode(', ', $missingExtensions); + $this->warning(sprintf('Cannot find a database extension. You need at least one of %s.', $req)); + $pass = false; + } + + if (!is_writable(INSTALLDIR)) { + $this->warning(sprintf('Cannot write config file to: <code>%s</code></p>', INSTALLDIR), + sprintf('On your server, try this command: <code>chmod a+w %s</code>', INSTALLDIR)); + $pass = false; + } + + // Check the subdirs used for file uploads + $fileSubdirs = array('avatar', 'background', 'file'); + foreach ($fileSubdirs as $fileSubdir) { + $fileFullPath = INSTALLDIR."/$fileSubdir/"; + if (!is_writable($fileFullPath)) { + $this->warning(sprintf('Cannot write to %s directory: <code>%s</code>', $fileSubdir, $fileFullPath), + sprintf('On your server, try this command: <code>chmod a+w %s</code>', $fileFullPath)); + $pass = false; + } + } + + return $pass; + } + + /** + * Checks if a php extension is both installed and loaded + * + * @param string $name of extension to check + * + * @return boolean whether extension is installed and loaded + */ + function checkExtension($name) + { + if (extension_loaded($name)) { + return true; + } elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) { + // dl will throw a fatal error if it's disabled or we're in safe mode. + // More fun, it may not even exist under some SAPIs in 5.3.0 or later... + $soname = $name . '.' . PHP_SHLIB_SUFFIX; + if (PHP_SHLIB_SUFFIX == 'dll') { + $soname = "php_" . $soname; + } + return @dl($soname); + } else { + return false; + } + } + + /** + * Basic validation on the database paramters + * Side effects: error output if not valid + * + * @return boolean success + */ + function validateDb() + { + $fail = false; + + if (empty($this->host)) { + $this->updateStatus("No hostname specified.", true); + $fail = true; + } + + if (empty($this->database)) { + $this->updateStatus("No database specified.", true); + $fail = true; + } + + if (empty($this->username)) { + $this->updateStatus("No username specified.", true); + $fail = true; + } + + if (empty($this->sitename)) { + $this->updateStatus("No sitename specified.", true); + $fail = true; + } + + return !$fail; + } + + /** + * Basic validation on the administrator user paramters + * Side effects: error output if not valid + * + * @return boolean success + */ + function validateAdmin() + { + $fail = false; + + if (empty($this->adminNick)) { + $this->updateStatus("No initial StatusNet user nickname specified.", true); + $fail = true; + } + if ($this->adminNick && !preg_match('/^[0-9a-z]{1,64}$/', $this->adminNick)) { + $this->updateStatus('The user nickname "' . htmlspecialchars($this->adminNick) . + '" is invalid; should be plain letters and numbers no longer than 64 characters.', true); + $fail = true; + } + // @fixme hardcoded list; should use User::allowed_nickname() + // if/when it's safe to have loaded the infrastructure here + $blacklist = array('main', 'admin', 'twitter', 'settings', 'rsd.xml', 'favorited', 'featured', 'favoritedrss', 'featuredrss', 'rss', 'getfile', 'api', 'groups', 'group', 'peopletag', 'tag', 'user', 'message', 'conversation', 'bookmarklet', 'notice', 'attachment', 'search', 'index.php', 'doc', 'opensearch', 'robots.txt', 'xd_receiver.html', 'facebook'); + if (in_array($this->adminNick, $blacklist)) { + $this->updateStatus('The user nickname "' . htmlspecialchars($this->adminNick) . + '" is reserved.', true); + $fail = true; + } + + if (empty($this->adminPass)) { + $this->updateStatus("No initial StatusNet user password specified.", true); + $fail = true; + } + + return !$fail; + } + + /** + * Set up the database with the appropriate function for the selected type... + * Saves database info into $this->db. + * + * @return mixed array of database connection params on success, false on failure + */ + function setupDatabase() + { + if ($this->db) { + throw new Exception("Bad order of operations: DB already set up."); + } + $method = self::$dbModules[$this->dbtype]['installer']; + $db = call_user_func(array($this, $method), + $this->host, + $this->database, + $this->username, + $this->password); + $this->db = $db; + return $this->db; + } + + /** + * Set up a database on PostgreSQL. + * Will output status updates during the operation. + * + * @param string $host + * @param string $database + * @param string $username + * @param string $password + * @return mixed array of database connection params on success, false on failure + * + * @fixme escape things in the connection string in case we have a funny pass etc + */ + function Pgsql_Db_installer($host, $database, $username, $password) + { + $connstring = "dbname=$database host=$host user=$username"; + + //No password would mean trust authentication used. + if (!empty($password)) { + $connstring .= " password=$password"; + } + $this->updateStatus("Starting installation..."); + $this->updateStatus("Checking database..."); + $conn = pg_connect($connstring); + + if ($conn ===false) { + $this->updateStatus("Failed to connect to database: $connstring"); + return false; + } + + //ensure database encoding is UTF8 + $record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding')); + if ($record->server_encoding != 'UTF8') { + $this->updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding)); + return false; + } + + $this->updateStatus("Running database script..."); + //wrap in transaction; + pg_query($conn, 'BEGIN'); + $res = $this->runDbScript('statusnet_pg.sql', $conn, 'pgsql'); + + if ($res === false) { + $this->updateStatus("Can't run database script.", true); + return false; + } + foreach (array('sms_carrier' => 'SMS carrier', + 'notice_source' => 'notice source', + 'foreign_services' => 'foreign service') + as $scr => $name) { + $this->updateStatus(sprintf("Adding %s data to database...", $name)); + $res = $this->runDbScript($scr.'.sql', $conn, 'pgsql'); + if ($res === false) { + $this->updateStatus(sprintf("Can't run %d script.", $name), true); + return false; + } + } + pg_query($conn, 'COMMIT'); + + if (empty($password)) { + $sqlUrl = "pgsql://$username@$host/$database"; + } else { + $sqlUrl = "pgsql://$username:$password@$host/$database"; + } + + $db = array('type' => 'pgsql', 'database' => $sqlUrl); + + return $db; + } + + /** + * Set up a database on MySQL. + * Will output status updates during the operation. + * + * @param string $host + * @param string $database + * @param string $username + * @param string $password + * @return mixed array of database connection params on success, false on failure + * + * @fixme be consistent about using mysqli vs mysql! + * @fixme escape things in the connection string in case we have a funny pass etc + */ + function Mysql_Db_installer($host, $database, $username, $password) + { + $this->updateStatus("Starting installation..."); + $this->updateStatus("Checking database..."); + + $conn = mysql_connect($host, $username, $password); + if (!$conn) { + $this->updateStatus("Can't connect to server '$host' as '$username'.", true); + return false; + } + $this->updateStatus("Changing to database..."); + $res = mysql_select_db($database, $conn); + if (!$res) { + $this->updateStatus("Can't change to database.", true); + return false; + } + + $this->updateStatus("Running database script..."); + $res = $this->runDbScript('statusnet.sql', $conn); + if ($res === false) { + $this->updateStatus("Can't run database script.", true); + return false; + } + foreach (array('sms_carrier' => 'SMS carrier', + 'notice_source' => 'notice source', + 'foreign_services' => 'foreign service') + as $scr => $name) { + $this->updateStatus(sprintf("Adding %s data to database...", $name)); + $res = $this->runDbScript($scr.'.sql', $conn); + if ($res === false) { + $this->updateStatus(sprintf("Can't run %d script.", $name), true); + return false; + } + } + + $sqlUrl = "mysqli://$username:$password@$host/$database"; + $db = array('type' => 'mysql', 'database' => $sqlUrl); + return $db; + } + + /** + * Write a stock configuration file. + * + * @return boolean success + * + * @fixme escape variables in output in case we have funny chars, apostrophes etc + */ + function writeConf() + { + // assemble configuration file in a string + $cfg = "<?php\n". + "if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n". + + // site name + "\$config['site']['name'] = '{$this->sitename}';\n\n". + + // site location + "\$config['site']['server'] = '{$this->server}';\n". + "\$config['site']['path'] = '{$this->path}'; \n\n". + + // checks if fancy URLs are enabled + ($this->fancy ? "\$config['site']['fancy'] = true;\n\n":''). + + // database + "\$config['db']['database'] = '{$this->db['database']}';\n\n". + ($this->db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":''). + "\$config['db']['type'] = '{$this->db['type']}';\n\n"; + // write configuration file out to install directory + $res = file_put_contents(INSTALLDIR.'/config.php', $cfg); + + return $res; + } + + /** + * Install schema into the database + * + * @param string $filename location of database schema file + * @param dbconn $conn connection to database + * @param string $type type of database, currently mysql or pgsql + * + * @return boolean - indicating success or failure + */ + function runDbScript($filename, $conn, $type = 'mysqli') + { + $sql = trim(file_get_contents(INSTALLDIR . '/db/' . $filename)); + $stmts = explode(';', $sql); + foreach ($stmts as $stmt) { + $stmt = trim($stmt); + if (!mb_strlen($stmt)) { + continue; + } + // FIXME: use PEAR::DB or PDO instead of our own switch + switch ($type) { + case 'mysqli': + $res = mysql_query($stmt, $conn); + if ($res === false) { + $error = mysql_error(); + } + break; + case 'pgsql': + $res = pg_query($conn, $stmt); + if ($res === false) { + $error = pg_last_error(); + } + break; + default: + $this->updateStatus("runDbScript() error: unknown database type ". $type ." provided."); + } + if ($res === false) { + $this->updateStatus("ERROR ($error) for SQL '$stmt'"); + return $res; + } + } + return true; + } + + /** + * Create the initial admin user account. + * Side effect: may load portions of StatusNet framework. + * Side effect: outputs program info + */ + function registerInitialUser() + { + define('STATUSNET', true); + define('LACONICA', true); // compatibility + + require_once INSTALLDIR . '/lib/common.php'; + + $data = array('nickname' => $this->adminNick, + 'password' => $this->adminPass, + 'fullname' => $this->adminNick); + if ($this->adminEmail) { + $data['email'] = $this->adminEmail; + } + $user = User::register($data); + + if (empty($user)) { + return false; + } + + // give initial user carte blanche + + $user->grantRole('owner'); + $user->grantRole('moderator'); + $user->grantRole('administrator'); + + // Attempt to do a remote subscribe to update@status.net + // Will fail if instance is on a private network. + + if ($this->adminUpdates && class_exists('Ostatus_profile')) { + try { + $oprofile = Ostatus_profile::ensureProfileURL('http://update.status.net/'); + Subscription::start($user->getProfile(), $oprofile->localProfile()); + $this->updateStatus("Set up subscription to <a href='http://update.status.net/'>update@status.net</a>."); + } catch (Exception $e) { + $this->updateStatus("Could not set up subscription to <a href='http://update.status.net/'>update@status.net</a>.", true); + } + } + + return true; + } + + /** + * The beef of the installer! + * Create database, config file, and admin user. + * + * Prerequisites: validation of input data. + * + * @return boolean success + */ + function doInstall() + { + $this->db = $this->setupDatabase(); + + if (!$this->db) { + // database connection failed, do not move on to create config file. + return false; + } + + if (!$this->skipConfig) { + $this->updateStatus("Writing config file..."); + $res = $this->writeConf(); + + if (!$res) { + $this->updateStatus("Can't write config file.", true); + return false; + } + } + + if (!empty($this->adminNick)) { + // Okay, cross fingers and try to register an initial user + if ($this->registerInitialUser()) { + $this->updateStatus( + "An initial user with the administrator role has been created." + ); + } else { + $this->updateStatus( + "Could not create initial StatusNet user (administrator).", + true + ); + return false; + } + } + + /* + TODO https needs to be considered + */ + $link = "http://".$this->server.'/'.$this->path; + + $this->updateStatus("StatusNet has been installed at $link"); + $this->updateStatus( + "<strong>DONE!</strong> You can visit your <a href='$link'>new StatusNet site</a> (login as '$this->adminNick'). If this is your first StatusNet install, you may want to poke around our <a href='http://status.net/wiki/Getting_started'>Getting Started guide</a>." + ); + + return true; + } + + /** + * Output a pre-install-time warning message + * @param string $message HTML ok, but should be plaintext-able + * @param string $submessage HTML ok, but should be plaintext-able + */ + abstract function warning($message, $submessage=''); + + /** + * Output an install-time progress message + * @param string $message HTML ok, but should be plaintext-able + * @param boolean $error true if this should be marked as an error condition + */ + abstract function updateStatus($status, $error=false); + +} diff --git a/lib/language.php b/lib/language.php index 6fb6222a1..8009adc9b 100644 --- a/lib/language.php +++ b/lib/language.php @@ -305,7 +305,8 @@ function get_all_languages() { 'fi' => array('q' => 1, 'lang' => 'fi', 'name' => 'Finnish', 'direction' => 'ltr'), 'fa' => array('q' => 1, 'lang' => 'fa', 'name' => 'Persian', 'direction' => 'rtl'), 'fr-fr' => array('q' => 1, 'lang' => 'fr', 'name' => 'French', 'direction' => 'ltr'), - 'ga' => array('q' => 0.5, 'lang' => 'ga', 'name' => 'Galician', 'direction' => 'ltr'), + 'ga' => array('q' => 0.5, 'lang' => 'ga', 'name' => 'Irish', 'direction' => 'ltr'), + 'gl' => array('q' => 0.8, 'lang' => 'gl', 'name' => 'Galician', 'direction' => 'ltr'), 'he' => array('q' => 0.5, 'lang' => 'he', 'name' => 'Hebrew', 'direction' => 'rtl'), 'hsb' => array('q' => 0.8, 'lang' => 'hsb', 'name' => 'Upper Sorbian', 'direction' => 'ltr'), 'ia' => array('q' => 0.8, 'lang' => 'ia', 'name' => 'Interlingua', 'direction' => 'ltr'), diff --git a/lib/mail.php b/lib/mail.php index 807b6a363..c38d9f2f5 100644 --- a/lib/mail.php +++ b/lib/mail.php @@ -170,8 +170,10 @@ function mail_to_user(&$user, $subject, $body, $headers=array(), $address=null) function mail_confirm_address($user, $code, $nickname, $address) { + // TRANS: Subject for address confirmation email $subject = _('Email address confirmation'); + // TRANS: Body for address confirmation email. $body = sprintf(_("Hey, %s.\n\n". "Someone just entered this email address on %s.\n\n" . "If it was you, and you want to confirm your entry, ". @@ -237,11 +239,13 @@ function mail_subscribe_notify_profile($listenee, $other) $headers = _mail_prepare_headers('subscribe', $listenee->nickname, $other->nickname); $headers['From'] = mail_notify_from(); $headers['To'] = $name . ' <' . $listenee->email . '>'; + // TRANS: Subject of new-subscriber notification e-mail $headers['Subject'] = sprintf(_('%1$s is now listening to '. 'your notices on %2$s.'), $other->getBestName(), common_config('site', 'name')); + // TRANS: Main body of new-subscriber notification e-mail $body = sprintf(_('%1$s is now listening to your notices on %2$s.'."\n\n". "\t".'%3$s'."\n\n". '%4$s'. @@ -255,10 +259,13 @@ function mail_subscribe_notify_profile($listenee, $other) common_config('site', 'name'), $other->profileurl, ($other->location) ? + // TRANS: Profile info line in new-subscriber notification e-mail sprintf(_("Location: %s"), $other->location) . "\n" : '', ($other->homepage) ? + // TRANS: Profile info line in new-subscriber notification e-mail sprintf(_("Homepage: %s"), $other->homepage) . "\n" : '', ($other->bio) ? + // TRANS: Profile info line in new-subscriber notification e-mail sprintf(_("Bio: %s"), $other->bio) . "\n\n" : '', common_config('site', 'name'), common_local_url('emailsettings')); @@ -287,9 +294,11 @@ function mail_new_incoming_notify($user) $headers['From'] = $user->incomingemail; $headers['To'] = $name . ' <' . $user->email . '>'; + // TRANS: Subject of notification mail for new posting email address $headers['Subject'] = sprintf(_('New email address for posting to %s'), common_config('site', 'name')); + // TRANS: Body of notification mail for new posting email address $body = sprintf(_("You have a new posting address on %1\$s.\n\n". "Send email to %2\$s to post new messages.\n\n". "More email instructions at %3\$s.\n\n". @@ -414,6 +423,7 @@ function mail_send_sms_notice_address($notice, $smsemail, $incomingemail) $headers['From'] = ($incomingemail) ? $incomingemail : mail_notify_from(); $headers['To'] = $to; + // TRANS: Subject line for SMS-by-email notification messages $headers['Subject'] = sprintf(_('%s status'), $other->getBestName()); @@ -440,11 +450,11 @@ function mail_confirm_sms($code, $nickname, $address) $headers['From'] = mail_notify_from(); $headers['To'] = $nickname . ' <' . $address . '>'; + // TRANS: Subject line for SMS-by-email address confirmation message $headers['Subject'] = _('SMS confirmation'); - // FIXME: I18N - - $body = "$nickname: confirm you own this phone number with this code:"; + // TRANS: Main body heading for SMS-by-email address confirmation message + $body = sprintf(_("%s: confirm you own this phone number with this code:"), $nickname); $body .= "\n\n"; $body .= $code; $body .= "\n\n"; @@ -464,10 +474,12 @@ function mail_confirm_sms($code, $nickname, $address) function mail_notify_nudge($from, $to) { common_init_locale($to->language); + // TRANS: Subject for 'nudge' notification email $subject = sprintf(_('You\'ve been nudged by %s'), $from->nickname); $from_profile = $from->getProfile(); + // TRANS: Body for 'nudge' notification email $body = sprintf(_("%1\$s (%2\$s) is wondering what you are up to ". "these days and is inviting you to post some news.\n\n". "So let's hear from you :)\n\n". @@ -514,10 +526,12 @@ function mail_notify_message($message, $from=null, $to=null) } common_init_locale($to->language); + // TRANS: Subject for direct-message notification email $subject = sprintf(_('New private message from %s'), $from->nickname); $from_profile = $from->getProfile(); + // TRANS: Body for direct-message notification email $body = sprintf(_("%1\$s (%2\$s) sent you a private message:\n\n". "------------------------------------------------------\n". "%3\$s\n". @@ -565,8 +579,10 @@ function mail_notify_fave($other, $user, $notice) common_init_locale($other->language); + // TRANS: Subject for favorite notification email $subject = sprintf(_('%s (@%s) added your notice as a favorite'), $bestname, $user->nickname); + // TRANS: Body for favorite notification email $body = sprintf(_("%1\$s (@%7\$s) just added your notice from %2\$s". " as one of their favorites.\n\n" . "The URL of your notice is:\n\n" . @@ -622,24 +638,25 @@ 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', - array('id' => $notice->conversation)).'#notice-'.$notice->id; - } else { - $conversationEmailText = "%5\$s"; - $conversationUrl = null; - } + if ($notice->hasConversation()) { + $conversationUrl = common_local_url('conversation', + array('id' => $notice->conversation)).'#notice-'.$notice->id; + // TRANS: Line in @-reply notification e-mail. %s is conversation URL. + $conversationEmailText = sprintf(_("The full conversation can be read here:\n\n". + "\t%s"), $conversationUrl) . "\n\n"; + } else { + $conversationEmailText = ''; + } $subject = sprintf(_('%s (@%s) sent a notice to your attention'), $bestname, $sender->nickname); + // TRANS: Body of @-reply notification e-mail. $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". "\t%4\$s\n\n" . - $conversationEmailText . + "%5\$s" . "You can reply back here:\n\n". "\t%6\$s\n\n" . "The list of all @-replies for you here:\n\n" . @@ -652,7 +669,7 @@ function mail_notify_attn($user, $notice) common_local_url('shownotice', array('notice' => $notice->id)),//%3 $notice->content,//%4 - $conversationUrl,//%5 + $conversationEmailText,//%5 common_local_url('newnotice', array('replyto' => $sender->nickname, 'inreplyto' => $notice->id)),//%6 common_local_url('replies', diff --git a/lib/noticeform.php b/lib/noticeform.php index 7278c41a9..84c20a5b3 100644 --- a/lib/noticeform.php +++ b/lib/noticeform.php @@ -212,8 +212,8 @@ class NoticeForm extends Form $this->out->checkbox('notice_data-geo', _('Share my location'), true); $this->out->elementEnd('div'); $this->out->inlineScript(' var NoticeDataGeo_text = {'. - 'ShareDisable: "'._('Do not share my location').'",'. - 'ErrorTimeout: "'._('Sorry, retrieving your geo location is taking longer than expected, please try again later').'"'. + 'ShareDisable: ' .json_encode(_('Do not share my location')).','. + 'ErrorTimeout: ' .json_encode(_('Sorry, retrieving your geo location is taking longer than expected, please try again later')). '}'); } diff --git a/lib/noticelist.php b/lib/noticelist.php index 83c8de9f6..5265326b2 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -426,10 +426,18 @@ class NoticeListItem extends Widget if (empty($name)) { $latdms = $this->decimalDegreesToDMS(abs($lat)); $londms = $this->decimalDegreesToDMS(abs($lon)); + // TRANS: Used in coordinates as abbreviation of north + $north = _('N'); + // TRANS: Used in coordinates as abbreviation of south + $south = _('S'); + // TRANS: Used in coordinates as abbreviation of east + $east = _('E'); + // TRANS: Used in coordinates as abbreviation of west + $west = _('W'); $name = sprintf( _('%1$u°%2$u\'%3$u"%4$s %5$u°%6$u\'%7$u"%8$s'), - $latdms['deg'],$latdms['min'], $latdms['sec'],($lat>0?_('N'):_('S')), - $londms['deg'],$londms['min'], $londms['sec'],($lon>0?_('E'):_('W'))); + $latdms['deg'],$latdms['min'], $latdms['sec'],($lat>0? $north:$south), + $londms['deg'],$londms['min'], $londms['sec'],($lon>0? $east:$west)); } $url = $location->getUrl(); @@ -543,18 +551,7 @@ class NoticeListItem extends Widget function showContext() { - $hasConversation = false; - if (!empty($this->notice->conversation)) { - $conversation = Notice::conversationStream( - $this->notice->conversation, - 1, - 1 - ); - if ($conversation->N > 0) { - $hasConversation = true; - } - } - if ($hasConversation) { + if ($this->notice->hasConversation()) { $conv = Conversation::staticGet( 'id', $this->notice->conversation diff --git a/lib/profileformaction.php b/lib/profileformaction.php index 8a934666e..0ffafe5fb 100644 --- a/lib/profileformaction.php +++ b/lib/profileformaction.php @@ -41,7 +41,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { * @link http://status.net/ */ -class ProfileFormAction extends Action +class ProfileFormAction extends RedirectingAction { var $profile = null; @@ -102,29 +102,6 @@ class ProfileFormAction extends Action } /** - * Return to the calling page based on hidden arguments - * - * @return void - */ - - function returnToArgs() - { - foreach ($this->args as $k => $v) { - if ($k == 'returnto-action') { - $action = $v; - } else if (substr($k, 0, 9) == 'returnto-') { - $args[substr($k, 9)] = $v; - } - } - - if ($action) { - common_redirect(common_local_url($action, $args), 303); - } else { - $this->clientError(_("No return-to arguments.")); - } - } - - /** * handle a POST request * * sub-classes should overload this request diff --git a/lib/redirectingaction.php b/lib/redirectingaction.php new file mode 100644 index 000000000..f11585274 --- /dev/null +++ b/lib/redirectingaction.php @@ -0,0 +1,96 @@ +<?php +/** + * Superclass for actions that redirect to a given return-to page on completion. + * + * PHP version 5 + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2009-2010, 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/>. + * + * @category Action + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Brion Vibber <brion@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Superclass for actions that redirect to a given return-to page on completion. + * + * @category Action + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Brion Vibber <brion@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ + + +class RedirectingAction extends Action +{ + + /** + * Redirect browser to the page our hidden parameters requested, + * or if none given, to the url given by $this->defaultReturnTo(). + * + * To be called only after successful processing. + * + * @fixme rename this -- it obscures Action::returnToArgs() which + * returns a list of arguments, and is a bit confusing. + * + * @return void + */ + function returnToArgs() + { + // Now, gotta figure where we go back to + $action = false; + $args = array(); + $params = array(); + foreach ($this->args as $k => $v) { + if ($k == 'returnto-action') { + $action = $v; + } else if (substr($k, 0, 15) == 'returnto-param-') { + $params[substr($k, 15)] = $v; + } elseif (substr($k, 0, 9) == 'returnto-') { + $args[substr($k, 9)] = $v; + } + } + + if ($action) { + common_redirect(common_local_url($action, $args, $params), 303); + } else { + $url = $this->defaultReturnToUrl(); + } + common_redirect($url, 303); + } + + /** + * If we reached this form without returnto arguments, where should + * we go? May be overridden by subclasses to a reasonable destination + * for that action; default implementation throws an exception. + * + * @return string URL + */ + function defaultReturnTo() + { + $this->clientError(_("No return-to arguments.")); + } +} diff --git a/lib/statusnet.php b/lib/statusnet.php index 98f25c8a0..ac5d10134 100644 --- a/lib/statusnet.php +++ b/lib/statusnet.php @@ -31,6 +31,7 @@ class StatusNet { protected static $have_config; protected static $is_api; + protected static $plugins = array(); /** * Configure and instantiate a plugin into the current configuration. @@ -74,10 +75,23 @@ class StatusNet $inst->$aname = $avalue; } } + + // Record activated plugins for later display/config dump + self::$plugins[] = array($name, $attrs); + return true; } /** + * Get a list of activated plugins in this process. + * @return array of (string $name, array $args) pairs + */ + public static function getActivePlugins() + { + return self::$plugins; + } + + /** * Initialize, or re-initialize, StatusNet global configuration * and plugins. * @@ -237,6 +251,7 @@ class StatusNet global $_server, $_path, $config; Event::clearHandlers(); + self::$plugins = array(); // try to figure out where we are. $server and $path // can be set by including module, else we guess based diff --git a/lib/util.php b/lib/util.php index c78ed33bd..1f3aaf711 100644 --- a/lib/util.php +++ b/lib/util.php @@ -874,7 +874,14 @@ function common_xml_safe_str($str) function common_tag_link($tag) { $canonical = common_canonical_tag($tag); - $url = common_local_url('tag', array('tag' => $canonical)); + if (common_config('singleuser', 'enabled')) { + // regular TagAction isn't set up in 1user mode + $url = common_local_url('showstream', + array('nickname' => common_config('singleuser', 'nickname'), + 'tag' => $canonical)); + } else { + $url = common_local_url('tag', array('tag' => $canonical)); + } $xs = new XMLStringer(); $xs->elementStart('span', 'tag'); $xs->element('a', array('href' => $url, @@ -1055,24 +1062,38 @@ function common_date_string($dt) if ($now < $t) { // that shouldn't happen! return common_exact_date($dt); } else if ($diff < 60) { + // TRANS: Used in notices to indicate when the notice was made compared to now. return _('a few seconds ago'); } else if ($diff < 92) { + // TRANS: Used in notices to indicate when the notice was made compared to now. return _('about a minute ago'); } else if ($diff < 3300) { + // XXX: should support plural. + // TRANS: Used in notices to indicate when the notice was made compared to now. return sprintf(_('about %d minutes ago'), round($diff/60)); } else if ($diff < 5400) { + // TRANS: Used in notices to indicate when the notice was made compared to now. return _('about an hour ago'); } else if ($diff < 22 * 3600) { + // XXX: should support plural. + // TRANS: Used in notices to indicate when the notice was made compared to now. return sprintf(_('about %d hours ago'), round($diff/3600)); } else if ($diff < 37 * 3600) { + // TRANS: Used in notices to indicate when the notice was made compared to now. return _('about a day ago'); } else if ($diff < 24 * 24 * 3600) { + // XXX: should support plural. + // TRANS: Used in notices to indicate when the notice was made compared to now. return sprintf(_('about %d days ago'), round($diff/(24*3600))); } else if ($diff < 46 * 24 * 3600) { + // TRANS: Used in notices to indicate when the notice was made compared to now. return _('about a month ago'); } else if ($diff < 330 * 24 * 3600) { + // XXX: should support plural. + // TRANS: Used in notices to indicate when the notice was made compared to now. return sprintf(_('about %d months ago'), round($diff/(30*24*3600))); } else if ($diff < 480 * 24 * 3600) { + // TRANS: Used in notices to indicate when the notice was made compared to now. return _('about a year ago'); } else { return common_exact_date($dt); @@ -1275,12 +1296,38 @@ function common_mtrand($bytes) return $enc; } +/** + * Record the given URL as the return destination for a future + * form submission, to be read by common_get_returnto(). + * + * @param string $url + * + * @fixme as a session-global setting, this can allow multiple forms + * to conflict and overwrite each others' returnto destinations if + * the user has multiple tabs or windows open. + * + * Should refactor to index with a token or otherwise only pass the + * data along its intended path. + */ function common_set_returnto($url) { common_ensure_session(); $_SESSION['returnto'] = $url; } +/** + * Fetch a return-destination URL previously recorded by + * common_set_returnto(). + * + * @return mixed URL string or null + * + * @fixme as a session-global setting, this can allow multiple forms + * to conflict and overwrite each others' returnto destinations if + * the user has multiple tabs or windows open. + * + * Should refactor to index with a token or otherwise only pass the + * data along its intended path. + */ function common_get_returnto() { common_ensure_session(); |