Merge remote-tracking branch 'mainline/1.0.x' into people_tags_rebase

Conflicts:
	actions/tagother.php
	classes/Profile.php
	classes/Profile_tag.php
	js/util.min.js
This commit is contained in:
Shashi Gowda 2011-03-30 15:43:13 +05:30
commit 5a2bab07b2
253 changed files with 25537 additions and 9804 deletions

2
README
View File

@ -1472,6 +1472,8 @@ Configuration options specific to notices.
contentlimit: max length of the plain-text content of a notice. contentlimit: max length of the plain-text content of a notice.
Default is null, meaning to use the site-wide text limit. Default is null, meaning to use the site-wide text limit.
0 means no limit. 0 means no limit.
defaultscope: default scope for notices. Defaults to 0; set to
1 to keep notices private to this site by default.
message message
------- -------

View File

@ -78,22 +78,6 @@ class ApiStatusesRetweetAction extends ApiAuthAction
$this->user = $this->auth_user; $this->user = $this->auth_user;
if ($this->user->id == $this->original->profile_id) {
// TRANS: Client error displayed trying to repeat an own notice through the API.
$this->clientError(_('Cannot repeat your own notice.'),
400, $this->format);
return false;
}
$profile = $this->user->getProfile();
if ($profile->hasRepeated($id)) {
// TRANS: Client error displayed trying to re-repeat a notice through the API.
$this->clientError(_('Already repeated that notice.'),
400, $this->format);
return false;
}
return true; return true;
} }

View File

@ -98,6 +98,7 @@ class ApprovegroupAction extends Action
$cur = common_current_user(); $cur = common_current_user();
if (empty($cur)) { if (empty($cur)) {
// TRANS: Client error displayed trying to approve group membership while not logged in.
$this->clientError(_('Must be logged in.'), 403); $this->clientError(_('Must be logged in.'), 403);
return false; return false;
} }
@ -105,10 +106,12 @@ class ApprovegroupAction extends Action
if ($cur->isAdmin($this->group)) { if ($cur->isAdmin($this->group)) {
$this->profile = Profile::staticGet('id', $this->arg('profile_id')); $this->profile = Profile::staticGet('id', $this->arg('profile_id'));
} else { } else {
// TRANS: Client error displayed trying to approve group membership while not a group administrator.
$this->clientError(_('Only group admin can approve or cancel join requests.'), 403); $this->clientError(_('Only group admin can approve or cancel join requests.'), 403);
return false; return false;
} }
} else { } else {
// TRANS: Client error displayed trying to approve group membership without specifying a profile to approve.
$this->clientError(_('Must specify a profile.')); $this->clientError(_('Must specify a profile.'));
return false; return false;
} }
@ -117,8 +120,21 @@ class ApprovegroupAction extends Action
'group_id' => $this->group->id)); 'group_id' => $this->group->id));
if (empty($this->request)) { if (empty($this->request)) {
// TRANS: Client error displayed trying to approve group membership for a non-existing request.
// TRANS: %s is a nickname.
$this->clientError(sprintf(_('%s is not in the moderation queue for this group.'), $this->profile->nickname), 403); $this->clientError(sprintf(_('%s is not in the moderation queue for this group.'), $this->profile->nickname), 403);
} }
$this->approve = (bool)$this->arg('approve');
$this->cancel = (bool)$this->arg('cancel');
if (!$this->approve && !$this->cancel) {
// TRANS: Client error displayed trying to approve/deny group membership.
$this->clientError(_('Internal error: received neither cancel nor abort.'));
}
if ($this->approve && $this->cancel) {
// TRANS: Client error displayed trying to approve/deny group membership.
$this->clientError(_('Internal error: received both cancel and abort.'));
}
return true; return true;
} }
@ -136,9 +152,13 @@ class ApprovegroupAction extends Action
parent::handle($args); parent::handle($args);
try { try {
$this->profile->completeJoinGroup($this->group); if ($this->approve) {
$this->request->complete();
} elseif ($this->cancel) {
$this->request->abort();
}
} catch (Exception $e) { } catch (Exception $e) {
common_log(LOG_ERROR, "Exception canceling group sub: " . $e->getMessage()); common_log(LOG_ERR, "Exception canceling group sub: " . $e->getMessage());
// TRANS: Server error displayed when cancelling a queued group join request fails. // TRANS: Server error displayed when cancelling a queued group join request fails.
// TRANS: %1$s is the leaving user's nickname, $2$s is the group nickname for which the leave failed. // TRANS: %1$s is the leaving user's nickname, $2$s is the group nickname for which the leave failed.
$this->serverError(sprintf(_('Could not cancel request for user %1$s to join group %2$s.'), $this->serverError(sprintf(_('Could not cancel request for user %1$s to join group %2$s.'),
@ -149,14 +169,20 @@ class ApprovegroupAction extends Action
if ($this->boolean('ajax')) { if ($this->boolean('ajax')) {
$this->startHTML('text/xml;charset=utf-8'); $this->startHTML('text/xml;charset=utf-8');
$this->elementStart('head'); $this->elementStart('head');
// TRANS: Title for leave group page after leaving. // TRANS: Title for leave group page after group join request is approved/disapproved.
$this->element('title', null, sprintf(_m('TITLE','%1$s left group %2$s'), // TRANS: %1$s is the user nickname, %2$s is the group nickname.
$this->element('title', null, sprintf(_m('TITLE','%1$s\'s request for %2$s'),
$this->profile->nickname, $this->profile->nickname,
$this->group->nickname)); $this->group->nickname));
$this->elementEnd('head'); $this->elementEnd('head');
$this->elementStart('body'); $this->elementStart('body');
$jf = new JoinForm($this, $this->group); if ($this->approve) {
$jf->show(); // TRANS: Message on page for group admin after approving a join request.
$this->element('p', 'success', _('Join request approved.'));
} elseif ($this->cancel) {
// TRANS: Message on page for group admin after rejecting a join request.
$this->element('p', 'success', _('Join request canceled.'));
}
$this->elementEnd('body'); $this->elementEnd('body');
$this->elementEnd('html'); $this->elementEnd('html');
} else { } else {

145
actions/approvesub.php Normal file
View File

@ -0,0 +1,145 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Approve group subscription request
*
* 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 Group
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2008-2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
/**
* Leave a group
*
* This is the action for leaving a group. It works more or less like the subscribe action
* for users.
*
* @category Group
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class ApprovesubAction extends Action
{
var $profile = null;
/**
* Prepare to run
*/
function prepare($args)
{
parent::prepare($args);
$cur = common_current_user();
if (empty($cur)) {
// TRANS: Client error displayed trying to approve group membership while not logged in.
$this->clientError(_('Must be logged in.'), 403);
return false;
}
if ($this->arg('profile_id')) {
$this->profile = Profile::staticGet('id', $this->arg('profile_id'));
} else {
// TRANS: Client error displayed trying to approve subscriptionswithout specifying a profile to approve.
$this->clientError(_('Must specify a profile.'));
return false;
}
$this->request = Subscription_queue::pkeyGet(array('subscriber' => $this->profile->id,
'subscribed' => $cur->id));
if (empty($this->request)) {
// TRANS: Client error displayed trying to approve subscription for a non-existing request.
$this->clientError(sprintf(_('%s is not in the moderation queue for your subscriptions.'), $this->profile->nickname), 403);
}
$this->approve = (bool)$this->arg('approve');
$this->cancel = (bool)$this->arg('cancel');
if (!$this->approve && !$this->cancel) {
// TRANS: Client error displayed trying to approve/deny subscription.
$this->clientError(_('Internal error: received neither cancel nor abort.'));
}
if ($this->approve && $this->cancel) {
// TRANS: Client error displayed trying to approve/deny subscription
$this->clientError(_('Internal error: received both cancel and abort.'));
}
return true;
}
/**
* Handle the request
*
* On POST, add the current user to the group
*
* @param array $args unused
*
* @return void
*/
function handle($args)
{
parent::handle($args);
$cur = common_current_user();
try {
if ($this->approve) {
$this->request->complete();
} elseif ($this->cancel) {
$this->request->abort();
}
} catch (Exception $e) {
common_log(LOG_ERR, "Exception canceling sub: " . $e->getMessage());
// TRANS: Server error displayed when cancelling a queued subscription request fails.
// TRANS: %1$s is the leaving user's nickname, $2$s is the nickname for which the leave failed.
$this->serverError(sprintf(_('Could not cancel or approve request for user %1$s to join group %2$s.'),
$this->profile->nickname, $cur->nickname));
return;
}
if ($this->boolean('ajax')) {
$this->startHTML('text/xml;charset=utf-8');
$this->elementStart('head');
// TRANS: Title for subscription approval ajax return
// TRANS: %1$s is the approved user's nickname
$this->element('title', null, sprintf(_m('TITLE','%1$s\'s request'),
$this->profile->nickname));
$this->elementEnd('head');
$this->elementStart('body');
if ($this->approve) {
// TRANS: Message on page for user after approving a subscription request.
$this->element('p', 'success', _('Subscription approved.'));
} elseif ($this->cancel) {
// TRANS: Message on page for user after rejecting a subscription request.
$this->element('p', 'success', _('Subscription canceled.'));
}
$this->elementEnd('body');
$this->elementEnd('html');
} else {
common_redirect(common_local_url('subqueue', array('nickname' =>
$cur->nickname)),
303);
}
}
}

View File

@ -141,7 +141,7 @@ class AtompubmembershipfeedAction extends ApiAuthAction
// TRANS: Title for group membership feed. // TRANS: Title for group membership feed.
// TRANS: %s is a username. // TRANS: %s is a username.
$feed->setTitle(sprintf(_("%s group memberships"), $feed->setTitle(sprintf(_('Group memberships of %s'),
$this->_profile->getBestName())); $this->_profile->getBestName()));
// TRANS: Subtitle for group membership feed. // TRANS: Subtitle for group membership feed.
@ -237,8 +237,7 @@ class AtompubmembershipfeedAction extends ApiAuthAction
if (Event::handle('StartAtomPubNewActivity', array(&$activity))) { if (Event::handle('StartAtomPubNewActivity', array(&$activity))) {
if ($activity->verb != ActivityVerb::JOIN) { if ($activity->verb != ActivityVerb::JOIN) {
// TRANS: Client error displayed when not using the POST verb. // TRANS: Client error displayed when not using the join verb.
// TRANS: Do not translate POST.
throw new ClientException(_('Can only handle join activities.')); throw new ClientException(_('Can only handle join activities.'));
return; return;
} }

View File

@ -68,7 +68,6 @@ class CancelgroupAction extends Action
$nickname = common_canonical_nickname($nickname_arg); $nickname = common_canonical_nickname($nickname_arg);
// Permanent redirect on non-canonical nickname // Permanent redirect on non-canonical nickname
if ($nickname_arg != $nickname) { if ($nickname_arg != $nickname) {
$args = array('nickname' => $nickname); $args = array('nickname' => $nickname);
common_redirect(common_local_url('leavegroup', $args), 301); common_redirect(common_local_url('leavegroup', $args), 301);
@ -98,6 +97,7 @@ class CancelgroupAction extends Action
$cur = common_current_user(); $cur = common_current_user();
if (empty($cur)) { if (empty($cur)) {
// TRANS: Client error displayed when trying to leave a group while not logged in.
$this->clientError(_('Must be logged in.'), 403); $this->clientError(_('Must be logged in.'), 403);
return false; return false;
} }
@ -105,6 +105,8 @@ class CancelgroupAction extends Action
if ($cur->isAdmin($this->group)) { if ($cur->isAdmin($this->group)) {
$this->profile = Profile::staticGet('id', $this->arg('profile_id')); $this->profile = Profile::staticGet('id', $this->arg('profile_id'));
} else { } else {
// TRANS: Client error displayed when trying to approve or cancel a group join request without
// TRANS: being a group administrator.
$this->clientError(_('Only group admin can approve or cancel join requests.'), 403); $this->clientError(_('Only group admin can approve or cancel join requests.'), 403);
return false; return false;
} }
@ -116,6 +118,8 @@ class CancelgroupAction extends Action
'group_id' => $this->group->id)); 'group_id' => $this->group->id));
if (empty($this->request)) { if (empty($this->request)) {
// TRANS: Client error displayed when trying to approve a non-existing group join request.
// TRANS: %s is a user nickname.
$this->clientError(sprintf(_('%s is not in the moderation queue for this group.'), $this->profile->nickname), 403); $this->clientError(sprintf(_('%s is not in the moderation queue for this group.'), $this->profile->nickname), 403);
} }
return true; return true;
@ -135,9 +139,9 @@ class CancelgroupAction extends Action
parent::handle($args); parent::handle($args);
try { try {
$this->profile->cancelJoinGroup($this->group); $this->request->abort();
} catch (Exception $e) { } catch (Exception $e) {
common_log(LOG_ERROR, "Exception canceling group sub: " . $e->getMessage()); common_log(LOG_ERR, "Exception canceling group sub: " . $e->getMessage());
// TRANS: Server error displayed when cancelling a queued group join request fails. // TRANS: Server error displayed when cancelling a queued group join request fails.
// TRANS: %1$s is the leaving user's nickname, $2$s is the group nickname for which the leave failed. // TRANS: %1$s is the leaving user's nickname, $2$s is the group nickname for which the leave failed.
$this->serverError(sprintf(_('Could not cancel request for user %1$s to join group %2$s.'), $this->serverError(sprintf(_('Could not cancel request for user %1$s to join group %2$s.'),
@ -149,6 +153,7 @@ class CancelgroupAction extends Action
$this->startHTML('text/xml;charset=utf-8'); $this->startHTML('text/xml;charset=utf-8');
$this->elementStart('head'); $this->elementStart('head');
// TRANS: Title for leave group page after leaving. // TRANS: Title for leave group page after leaving.
// TRANS: %s$s is the leaving user's name, %2$s is the group name.
$this->element('title', null, sprintf(_m('TITLE','%1$s left group %2$s'), $this->element('title', null, sprintf(_m('TITLE','%1$s left group %2$s'),
$this->profile->nickname, $this->profile->nickname,
$this->group->nickname)); $this->group->nickname));

View File

@ -0,0 +1,123 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Leave a group
*
* 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 Group
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2008-2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
/**
* Leave a group
*
* This is the action for leaving a group. It works more or less like the subscribe action
* for users.
*
* @category Group
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class CancelsubscriptionAction extends Action
{
function handle($args)
{
parent::handle($args);
if ($this->boolean('ajax')) {
StatusNet::setApi(true);
}
if (!common_logged_in()) {
// TRANS: Client error displayed when trying to leave a group while not logged in.
$this->clientError(_('Not logged in.'));
return;
}
$user = common_current_user();
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
common_redirect(common_local_url('subscriptions',
array('nickname' => $user->nickname)));
return;
}
/* Use a session token for CSRF protection. */
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->clientError(_('There was a problem with your session token. ' .
'Try again, please.'));
return;
}
$other_id = $this->arg('unsubscribeto');
if (!$other_id) {
// TRANS: Client error displayed when trying to leave a group without specifying an ID.
$this->clientError(_('No profile ID in request.'));
return;
}
$other = Profile::staticGet('id', $other_id);
if (!$other) {
// TRANS: Client error displayed when trying to leave a non-existing group.
$this->clientError(_('No profile with that ID.'));
return;
}
$this->request = Subscription_queue::pkeyGet(array('subscriber' => $user->id,
'subscribed' => $other->id));
if (empty($this->request)) {
// TRANS: Client error displayed when trying to approve a non-existing group join request.
// TRANS: %s is a user nickname.
$this->clientError(sprintf(_('%s is not in the moderation queue for this group.'), $this->profile->nickname), 403);
}
$this->request->abort();
if ($this->boolean('ajax')) {
$this->startHTML('text/xml;charset=utf-8');
$this->elementStart('head');
// TRANS: Title after unsubscribing from a group.
$this->element('title', null, _m('TITLE','Unsubscribed'));
$this->elementEnd('head');
$this->elementStart('body');
$subscribe = new SubscribeForm($this, $other);
$subscribe->show();
$this->elementEnd('body');
$this->elementEnd('html');
} else {
common_redirect(common_local_url('subscriptions',
array('nickname' => $user->nickname)),
303);
}
}
}

View File

@ -52,7 +52,7 @@ class GroupqueueAction extends GroupDesignAction
return true; return true;
} }
// fixme most of this belongs in a base class, sounds common to most group actions? // @todo FIXME: most of this belongs in a base class, sounds common to most group actions?
function prepare($args) function prepare($args)
{ {
parent::prepare($args); parent::prepare($args);
@ -96,6 +96,7 @@ class GroupqueueAction extends GroupDesignAction
$cur = common_current_user(); $cur = common_current_user();
if (!$cur || !$cur->isAdmin($this->group)) { if (!$cur || !$cur->isAdmin($this->group)) {
// TRANS: Client error displayed when trying to approve group applicants without being a group administrator.
$this->clientError(_('Only the group admin may approve users.')); $this->clientError(_('Only the group admin may approve users.'));
return false; return false;
} }
@ -105,12 +106,12 @@ class GroupqueueAction extends GroupDesignAction
function title() function title()
{ {
if ($this->page == 1) { if ($this->page == 1) {
// TRANS: Title of the page showing pending group members still awaiting approval to join the group. // TRANS: Title of the first page showing pending group members still awaiting approval to join the group.
// TRANS: %s is the name of the group. // TRANS: %s is the name of the group.
return sprintf(_('%s group members awaiting approval'), return sprintf(_('%s group members awaiting approval'),
$this->group->nickname); $this->group->nickname);
} else { } else {
// TRANS: Title of the page showing pending group members still awaiting approval to join the group. // TRANS: Title of all but the first page showing pending group members still awaiting approval to join the group.
// TRANS: %1$s is the name of the group, %2$d is the page number of the members list. // TRANS: %1$s is the name of the group, %2$d is the page number of the members list.
return sprintf(_('%1$s group members awaiting approval, page %2$d'), return sprintf(_('%1$s group members awaiting approval, page %2$d'),
$this->group->nickname, $this->group->nickname,
@ -155,11 +156,12 @@ class GroupqueueAction extends GroupDesignAction
$members->free(); $members->free();
$this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE, $this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE,
$this->page, 'groupmembers', $this->page, 'groupqueue',
array('nickname' => $this->group->nickname)); array('nickname' => $this->group->nickname));
} }
} }
// @todo FIXME: documentation missing.
class GroupQueueList extends GroupMemberList class GroupQueueList extends GroupMemberList
{ {
function newListItem($profile) function newListItem($profile)
@ -168,32 +170,24 @@ class GroupQueueList extends GroupMemberList
} }
} }
// @todo FIXME: documentation missing.
class GroupQueueListItem extends GroupMemberListItem class GroupQueueListItem extends GroupMemberListItem
{ {
function showActions() function showActions()
{ {
$this->startActions(); $this->startActions();
if (Event::handle('StartProfileListItemActionElements', array($this))) { if (Event::handle('StartProfileListItemActionElements', array($this))) {
$this->showApproveButton(); $this->showApproveButtons();
$this->showCancelButton();
Event::handle('EndProfileListItemActionElements', array($this)); Event::handle('EndProfileListItemActionElements', array($this));
} }
$this->endActions(); $this->endActions();
} }
function showApproveButton() function showApproveButtons()
{ {
$this->out->elementStart('li', 'entity_join'); $this->out->elementStart('li', 'entity_approval');
$form = new ApproveGroupForm($this->out, $this->group, $this->profile); $form = new ApproveGroupForm($this->out, $this->group, $this->profile);
$form->show(); $form->show();
$this->out->elementEnd('li'); $this->out->elementEnd('li');
} }
function showCancelButton()
{
$this->out->elementStart('li', 'entity_leave');
$bf = new CancelGroupForm($this->out, $this->group, $this->profile);
$bf->show();
$this->out->elementEnd('li');
}
} }

View File

@ -54,7 +54,7 @@ class InviteAction extends CurrentUserDesignAction
function sendInvitations() function sendInvitations()
{ {
# CSRF protection // CSRF protection
$token = $this->trimmed('token'); $token = $this->trimmed('token');
if (!$token || $token != common_session_token()) { if (!$token || $token != common_session_token()) {
$this->showForm(_('There was a problem with your session token. Try again, please.')); $this->showForm(_('There was a problem with your session token. Try again, please.'));

View File

@ -154,7 +154,8 @@ class JoingroupAction extends Action
$form = new CancelGroupForm($this, $this->group); $form = new CancelGroupForm($this, $this->group);
} else { } else {
// wtf? // wtf?
throw new Exception(_m("Unknown error joining group.")); // TRANS: Exception thrown when there is an unknown error joining a group.
throw new Exception(_("Unknown error joining group."));
} }
$form->show(); $form->show();
$this->elementEnd('body'); $this->elementEnd('body');

View File

@ -209,6 +209,10 @@ class NewnoticeAction extends Action
$author_id = $user->id; $author_id = $user->id;
$text = $content_shortened; $text = $content_shortened;
// Does the heavy-lifting for getting "To:" information
ToSelector::fillOptions($this, $options);
if (Event::handle('StartNoticeSaveWeb', array($this, &$author_id, &$text, &$options))) { if (Event::handle('StartNoticeSaveWeb', array($this, &$author_id, &$text, &$options))) {
$notice = Notice::saveNew($user->id, $content_shortened, 'web', $options); $notice = Notice::saveNew($user->id, $content_shortened, 'web', $options);
@ -344,7 +348,9 @@ class NewnoticeAction extends Action
$inreplyto = null; $inreplyto = null;
} }
$notice_form = new NoticeForm($this, '', $content, null, $inreplyto); $notice_form = new NoticeForm($this, array('content' => $content,
'inreplyto' => $inreplyto));
$notice_form->show(); $notice_form->show();
} }

View File

@ -156,7 +156,7 @@ class PasswordsettingsAction extends SettingsAction
$newpassword = $this->arg('newpassword'); $newpassword = $this->arg('newpassword');
$confirm = $this->arg('confirm'); $confirm = $this->arg('confirm');
# Some validation // Some validation
if (strlen($newpassword) < 6) { if (strlen($newpassword) < 6) {
// TRANS: Form validation error on page where to change password. // TRANS: Form validation error on page where to change password.

View File

@ -33,8 +33,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1); exit(1);
} }
/** /**
* Change profile settings * Change profile settings
* *
@ -46,7 +44,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
class ProfilesettingsAction extends SettingsAction class ProfilesettingsAction extends SettingsAction
{ {
/** /**
@ -127,15 +124,15 @@ class ProfilesettingsAction extends SettingsAction
// TRANS: Tooltip for field label in form for profile settings. Plural // TRANS: Tooltip for field label in form for profile settings. Plural
// TRANS: is decided by the number of characters available for the // TRANS: is decided by the number of characters available for the
// TRANS: biography (%d). // TRANS: biography (%d).
$bioInstr = sprintf(_m('Describe yourself and your interests in %d character', $bioInstr = sprintf(_m('Describe yourself and your interests in %d character.',
'Describe yourself and your interests in %d characters', 'Describe yourself and your interests in %d characters.',
$maxBio), $maxBio),
$maxBio); $maxBio);
} else { } else {
// TRANS: Tooltip for field label in form for profile settings. // TRANS: Tooltip for field label in form for profile settings.
$bioInstr = _('Describe yourself and your interests'); $bioInstr = _('Describe yourself and your interests.');
} }
// TRANS: Text area label in form for profile settings where users can provide. // TRANS: Text area label in form for profile settings where users can provide
// TRANS: their biography. // TRANS: their biography.
$this->textarea('bio', _('Bio'), $this->textarea('bio', _('Bio'),
($this->arg('bio')) ? $this->arg('bio') : $profile->bio, ($this->arg('bio')) ? $this->arg('bio') : $profile->bio,
@ -146,7 +143,7 @@ class ProfilesettingsAction extends SettingsAction
$this->input('location', _('Location'), $this->input('location', _('Location'),
($this->arg('location')) ? $this->arg('location') : $profile->location, ($this->arg('location')) ? $this->arg('location') : $profile->location,
// TRANS: Tooltip for field label in form for profile settings. // TRANS: Tooltip for field label in form for profile settings.
_('Where you are, like "City, State (or Region), Country"')); _('Where you are, like "City, State (or Region), Country".'));
$this->elementEnd('li'); $this->elementEnd('li');
if (common_config('location', 'share') == 'user') { if (common_config('location', 'share') == 'user') {
$this->elementStart('li'); $this->elementStart('li');
@ -192,6 +189,19 @@ class ProfilesettingsAction extends SettingsAction
($this->arg('autosubscribe')) ? ($this->arg('autosubscribe')) ?
$this->boolean('autosubscribe') : $user->autosubscribe); $this->boolean('autosubscribe') : $user->autosubscribe);
$this->elementEnd('li'); $this->elementEnd('li');
$this->elementStart('li');
$this->dropdown('subscribe_policy',
// TRANS: Dropdown field label on profile settings, for what policies to apply when someone else tries to subscribe to your updates.
_('Subscription policy'),
// TRANS: Dropdown field option for following policy.
array(User::SUBSCRIBE_POLICY_OPEN => _('Let anyone follow me'),
// TRANS: Dropdown field option for following policy.
User::SUBSCRIBE_POLICY_MODERATE => _('Ask me first')),
// TRANS: Dropdown field title on group edit form.
_('Whether other users need your permission to follow your updates.'),
false,
(empty($user->subscribe_policy)) ? User::SUBSCRIBE_POLICY_OPEN : $user->subscribe_policy);
$this->elementEnd('li');
} }
$this->elementEnd('ul'); $this->elementEnd('ul');
// TRANS: Button to save input in profile settings. // TRANS: Button to save input in profile settings.
@ -234,6 +244,7 @@ class ProfilesettingsAction extends SettingsAction
$bio = $this->trimmed('bio'); $bio = $this->trimmed('bio');
$location = $this->trimmed('location'); $location = $this->trimmed('location');
$autosubscribe = $this->boolean('autosubscribe'); $autosubscribe = $this->boolean('autosubscribe');
$subscribe_policy = $this->trimmed('subscribe_policy');
$language = $this->trimmed('language'); $language = $this->trimmed('language');
$timezone = $this->trimmed('timezone'); $timezone = $this->trimmed('timezone');
$tagstring = $this->trimmed('tags'); $tagstring = $this->trimmed('tags');
@ -339,11 +350,12 @@ class ProfilesettingsAction extends SettingsAction
} }
// XXX: XOR // XXX: XOR
if ($user->autosubscribe ^ $autosubscribe) { if (($user->autosubscribe ^ $autosubscribe) || $user->subscribe_policy != $subscribe_policy) {
$original = clone($user); $original = clone($user);
$user->autosubscribe = $autosubscribe; $user->autosubscribe = $autosubscribe;
$user->subscribe_policy = $subscribe_policy;
$result = $user->update($original); $result = $user->update($original);
@ -351,7 +363,7 @@ class ProfilesettingsAction extends SettingsAction
common_log_db_error($user, 'UPDATE', __FILE__); common_log_db_error($user, 'UPDATE', __FILE__);
// TRANS: Server error thrown when user profile settings could not be updated to // TRANS: Server error thrown when user profile settings could not be updated to
// TRANS: automatically subscribe to any subscriber. // TRANS: automatically subscribe to any subscriber.
$this->serverError(_('Could not update user for autosubscribe.')); $this->serverError(_('Could not update user for autosubscribe or subscribe_policy.'));
return; return;
} }
} }

View File

@ -85,8 +85,11 @@ class PublicAction extends Action
common_set_returnto($this->selfUrl()); common_set_returnto($this->selfUrl());
$this->notice = Notice::publicStream(($this->page-1)*NOTICES_PER_PAGE, $stream = new PublicNoticeStream(PublicNoticeStream::THREADED);
NOTICES_PER_PAGE + 1); $this->notice = $stream->getNotices(($this->page-1)*NOTICES_PER_PAGE,
NOTICES_PER_PAGE + 1,
0,
0);
if (!$this->notice) { if (!$this->notice) {
// TRANS: Server error displayed when a public timeline cannot be retrieved. // TRANS: Server error displayed when a public timeline cannot be retrieved.

View File

@ -100,7 +100,7 @@ class PublictagcloudAction extends Action
function showContent() function showContent()
{ {
# This should probably be cached rather than recalculated // This should probably be cached rather than recalculated
$tags = new Notice_tag(); $tags = new Notice_tag();
#Need to clear the selection and then only re-add the field #Need to clear the selection and then only re-add the field

View File

@ -19,7 +19,7 @@
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
# You have 24 hours to claim your password // You have 24 hours to claim your password
define('MAX_RECOVERY_TIME', 24 * 60 * 60); define('MAX_RECOVERY_TIME', 24 * 60 * 60);
@ -81,7 +81,7 @@ class RecoverpasswordAction extends Action
$touched = strtotime($confirm->modified); $touched = strtotime($confirm->modified);
$email = $confirm->address; $email = $confirm->address;
# Burn this code // Burn this code
$result = $confirm->delete(); $result = $confirm->delete();
@ -92,8 +92,8 @@ class RecoverpasswordAction extends Action
return; return;
} }
# These should be reaped, but for now we just check mod time // These should be reaped, but for now we just check mod time
# Note: it's still deleted; let's avoid a second attempt! // Note: it's still deleted; let's avoid a second attempt!
if ((time() - $touched) > MAX_RECOVERY_TIME) { if ((time() - $touched) > MAX_RECOVERY_TIME) {
common_log(LOG_WARNING, common_log(LOG_WARNING,
@ -105,8 +105,8 @@ class RecoverpasswordAction extends Action
return; return;
} }
# If we used an outstanding confirmation to send the email, // If we used an outstanding confirmation to send the email,
# it's been confirmed at this point. // it's been confirmed at this point.
if (!$user->email) { if (!$user->email) {
$orig = clone($user); $orig = clone($user);
@ -120,7 +120,7 @@ class RecoverpasswordAction extends Action
} }
} }
# Success! // Success!
$this->setTempUser($user); $this->setTempUser($user);
$this->showPasswordForm(); $this->showPasswordForm();
@ -289,7 +289,7 @@ class RecoverpasswordAction extends Action
} }
} }
# See if it's an unconfirmed email address // See if it's an unconfirmed email address
if (!$user) { if (!$user) {
// Warning: it may actually be legit to have multiple folks // Warning: it may actually be legit to have multiple folks
@ -314,7 +314,7 @@ class RecoverpasswordAction extends Action
return; return;
} }
# Try to get an unconfirmed email address if they used a user name // Try to get an unconfirmed email address if they used a user name
if (!$user->email && !$confirm_email) { if (!$user->email && !$confirm_email) {
$confirm_email = new Confirm_address(); $confirm_email = new Confirm_address();
@ -332,7 +332,7 @@ class RecoverpasswordAction extends Action
return; return;
} }
# Success! We have a valid user and a confirmed or unconfirmed email address // Success! We have a valid user and a confirmed or unconfirmed email address
$confirm = new Confirm_address(); $confirm = new Confirm_address();
$confirm->code = common_confirmation_code(128); $confirm->code = common_confirmation_code(128);
@ -380,7 +380,7 @@ class RecoverpasswordAction extends Action
function resetPassword() function resetPassword()
{ {
# CSRF protection // CSRF protection
$token = $this->trimmed('token'); $token = $this->trimmed('token');
if (!$token || $token != common_session_token()) { if (!$token || $token != common_session_token()) {
// TRANS: Form validation error message. // TRANS: Form validation error message.
@ -410,7 +410,7 @@ class RecoverpasswordAction extends Action
return; return;
} }
# OK, we're ready to go // OK, we're ready to go
$original = clone($user); $original = clone($user);

View File

@ -531,7 +531,7 @@ class RegisterAction extends Action
$this->elementEnd('li'); $this->elementEnd('li');
} }
$this->elementEnd('ul'); $this->elementEnd('ul');
// TRANS: Field label on account registration page. // TRANS: Button text to register a user on account registration page.
$this->submit('submit', _m('BUTTON','Register')); $this->submit('submit', _m('BUTTON','Register'));
$this->elementEnd('fieldset'); $this->elementEnd('fieldset');
$this->elementEnd('form'); $this->elementEnd('form');

View File

@ -73,12 +73,6 @@ class RepeatAction extends Action
return false; return false;
} }
if ($this->user->id == $this->notice->profile_id) {
// TRANS: Client error displayed when trying to repeat an own notice.
$this->clientError(_('You cannot repeat your own notice.'));
return false;
}
$token = $this->trimmed('token-'.$id); $token = $this->trimmed('token-'.$id);
if (empty($token) || $token != common_session_token()) { if (empty($token) || $token != common_session_token()) {
@ -86,14 +80,6 @@ class RepeatAction extends Action
return false; return false;
} }
$profile = $this->user->getProfile();
if ($profile->hasRepeated($id)) {
// TRANS: Client error displayed when trying to repeat an already repeated notice.
$this->clientError(_('You already repeated that notice.'));
return false;
}
return true; return true;
} }

View File

@ -40,7 +40,6 @@ if (!defined('STATUSNET')) {
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/ * @link http://status.net/
*/ */
class SandboxAction extends ProfileFormAction class SandboxAction extends ProfileFormAction
{ {
/** /**
@ -50,7 +49,6 @@ class SandboxAction extends ProfileFormAction
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare($args)
{ {
if (!parent::prepare($args)) { if (!parent::prepare($args)) {
@ -62,6 +60,7 @@ class SandboxAction extends ProfileFormAction
assert(!empty($cur)); // checked by parent assert(!empty($cur)); // checked by parent
if (!$cur->hasRight(Right::SANDBOXUSER)) { if (!$cur->hasRight(Right::SANDBOXUSER)) {
// TRANS: Client error displayed trying to sandbox users on a site where the feature is not enabled.
$this->clientError(_('You cannot sandbox users on this site.')); $this->clientError(_('You cannot sandbox users on this site.'));
return false; return false;
} }
@ -69,6 +68,7 @@ class SandboxAction extends ProfileFormAction
assert(!empty($this->profile)); // checked by parent assert(!empty($this->profile)); // checked by parent
if ($this->profile->isSandboxed()) { if ($this->profile->isSandboxed()) {
// TRANS: Client error displayed trying to sandbox an already sandboxed user.
$this->clientError(_('User is already sandboxed.')); $this->clientError(_('User is already sandboxed.'));
return false; return false;
} }
@ -81,7 +81,6 @@ class SandboxAction extends ProfileFormAction
* *
* @return void * @return void
*/ */
function handlePost() function handlePost()
{ {
$this->profile->sandbox(); $this->profile->sandbox();

View File

@ -40,7 +40,6 @@ if (!defined('STATUSNET')) {
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
class SessionsadminpanelAction extends AdminPanelAction class SessionsadminpanelAction extends AdminPanelAction
{ {
/** /**
@ -48,10 +47,10 @@ class SessionsadminpanelAction extends AdminPanelAction
* *
* @return string page title * @return string page title
*/ */
function title() function title()
{ {
return _('Sessions'); // TRANS: Title for the sessions administration panel.
return _m('TITLE','Sessions');
} }
/** /**
@ -59,9 +58,9 @@ class SessionsadminpanelAction extends AdminPanelAction
* *
* @return string instructions * @return string instructions
*/ */
function getInstructions() function getInstructions()
{ {
// TRANS: Instructions for the sessions administration panel.
return _('Session settings for this StatusNet site'); return _('Session settings for this StatusNet site');
} }
@ -70,7 +69,6 @@ class SessionsadminpanelAction extends AdminPanelAction
* *
* @return void * @return void
*/ */
function showForm() function showForm()
{ {
$form = new SessionsAdminPanelForm($this); $form = new SessionsAdminPanelForm($this);
@ -83,7 +81,6 @@ class SessionsadminpanelAction extends AdminPanelAction
* *
* @return void * @return void
*/ */
function saveSettings() function saveSettings()
{ {
static $booleans = array('sessions' => array('handle', 'debug')); static $booleans = array('sessions' => array('handle', 'debug'));
@ -123,6 +120,7 @@ class SessionsadminpanelAction extends AdminPanelAction
} }
} }
// @todo FIXME: Class documentation missing.
class SessionsAdminPanelForm extends AdminForm class SessionsAdminPanelForm extends AdminForm
{ {
/** /**
@ -130,7 +128,6 @@ class SessionsAdminPanelForm extends AdminForm
* *
* @return int ID of the form * @return int ID of the form
*/ */
function id() function id()
{ {
return 'sessionsadminpanel'; return 'sessionsadminpanel';
@ -141,7 +138,6 @@ class SessionsAdminPanelForm extends AdminForm
* *
* @return string class of the form * @return string class of the form
*/ */
function formClass() function formClass()
{ {
return 'form_settings'; return 'form_settings';
@ -152,7 +148,6 @@ class SessionsAdminPanelForm extends AdminForm
* *
* @return string URL of the action * @return string URL of the action
*/ */
function action() function action()
{ {
return common_local_url('sessionsadminpanel'); return common_local_url('sessionsadminpanel');
@ -163,24 +158,31 @@ class SessionsAdminPanelForm extends AdminForm
* *
* @return void * @return void
*/ */
function formData() function formData()
{ {
$this->out->elementStart('fieldset', array('id' => 'settings_user_sessions')); $this->out->elementStart('fieldset', array('id' => 'settings_user_sessions'));
$this->out->element('legend', null, _('Sessions')); // TRANS: Fieldset legend on the sessions administration panel.
$this->out->element('legend', null, _m('LEGEND','Sessions'));
$this->out->elementStart('ul', 'form_data'); $this->out->elementStart('ul', 'form_data');
$this->li(); $this->li();
// TRANS: Checkbox title on the sessions administration panel.
// TRANS: Indicates if StatusNet should handle session administration.
$this->out->checkbox('handle', _('Handle sessions'), $this->out->checkbox('handle', _('Handle sessions'),
(bool) $this->value('handle', 'sessions'), (bool) $this->value('handle', 'sessions'),
_('Whether to handle sessions ourselves.')); // TRANS: Checkbox title on the sessions administration panel.
// TRANS: Indicates if StatusNet should handle session administration.
_('Handle sessions ourselves.'));
$this->unli(); $this->unli();
$this->li(); $this->li();
// TRANS: Checkbox label on the sessions administration panel.
// TRANS: Indicates if StatusNet should write session debugging output.
$this->out->checkbox('debug', _('Session debugging'), $this->out->checkbox('debug', _('Session debugging'),
(bool) $this->value('debug', 'sessions'), (bool) $this->value('debug', 'sessions'),
_('Turn on debugging output for sessions.')); // TRANS: Checkbox title on the sessions administration panel.
_('Enable debugging output for sessions.'));
$this->unli(); $this->unli();
$this->out->elementEnd('ul'); $this->out->elementEnd('ul');
@ -193,9 +195,14 @@ class SessionsAdminPanelForm extends AdminForm
* *
* @return void * @return void
*/ */
function formActions() function formActions()
{ {
$this->out->submit('submit', _('Save'), 'submit', null, _('Save site settings')); $this->out->submit('submit',
// TRANS: Submit button text on the sessions administration panel.
_m('BUTTON','Save'),
'submit',
null,
// TRANS: Title for submit button on the sessions administration panel.
_('Save session settings'));
} }
} }

View File

@ -75,11 +75,13 @@ class ShowApplicationAction extends OwnerDesignAction
$this->owner = User::staticGet($this->application->owner); $this->owner = User::staticGet($this->application->owner);
if (!common_logged_in()) { if (!common_logged_in()) {
// TRANS: Client error displayed trying to display an OAuth application while not logged in.
$this->clientError(_('You must be logged in to view an application.')); $this->clientError(_('You must be logged in to view an application.'));
return false; return false;
} }
if (empty($this->application)) { if (empty($this->application)) {
// TRANS: Client error displayed trying to display a non-existing OAuth application.
$this->clientError(_('No such application.'), 404); $this->clientError(_('No such application.'), 404);
return false; return false;
} }
@ -87,6 +89,7 @@ class ShowApplicationAction extends OwnerDesignAction
$cur = common_current_user(); $cur = common_current_user();
if ($cur->id != $this->owner->id) { if ($cur->id != $this->owner->id) {
// TRANS: Client error displayed trying to display an OAuth application for which the logged in user is not the owner.
$this->clientError(_('You are not the owner of this application.'), 401); $this->clientError(_('You are not the owner of this application.'), 401);
return false; return false;
} }
@ -148,6 +151,7 @@ class ShowApplicationAction extends OwnerDesignAction
$consumer = $this->application->getConsumer(); $consumer = $this->application->getConsumer();
$this->elementStart('div', 'entity_profile vcard'); $this->elementStart('div', 'entity_profile vcard');
// TRANS: Header on the OAuth application page.
$this->element('h2', null, _('Application profile')); $this->element('h2', null, _('Application profile'));
if (!empty($this->application->icon)) { if (!empty($this->application->icon)) {
$this->element('img', array('src' => $this->application->icon, $this->element('img', array('src' => $this->application->icon,
@ -176,7 +180,12 @@ class ShowApplicationAction extends OwnerDesignAction
$userCnt = $appUsers->count(); $userCnt = $appUsers->count();
$this->raw(sprintf( $this->raw(sprintf(
_('Created by %1$s - %2$s access by default - %3$d users'), // TRANS: Information output on an OAuth application page.
// TRANS: %1$s is the application creator, %2$s is "read-only" or "read-write",
// TRANS: %3$d is the number of users using the OAuth application.
_m('Created by %1$s - %2$s access by default - %3$d user',
'Created by %1$s - %2$s access by default - %3$d users',
$userCnt),
$profile->getBestName(), $profile->getBestName(),
$defaultAccess, $defaultAccess,
$userCnt $userCnt
@ -186,13 +195,15 @@ class ShowApplicationAction extends OwnerDesignAction
$this->elementEnd('div'); $this->elementEnd('div');
$this->elementStart('div', 'entity_actions'); $this->elementStart('div', 'entity_actions');
// TRANS: Header on the OAuth application page.
$this->element('h2', null, _('Application actions')); $this->element('h2', null, _('Application actions'));
$this->elementStart('ul'); $this->elementStart('ul');
$this->elementStart('li', 'entity_edit'); $this->elementStart('li', 'entity_edit');
$this->element('a', $this->element('a',
array('href' => common_local_url('editapplication', array('href' => common_local_url('editapplication',
array('id' => $this->application->id))), array('id' => $this->application->id))),
'Edit'); // TRANS: Link text to edit application on the OAuth application page.
_m('EDITAPP','Edit'));
$this->elementEnd('li'); $this->elementEnd('li');
$this->elementStart('li', 'entity_reset_keysecret'); $this->elementStart('li', 'entity_reset_keysecret');
@ -209,6 +220,8 @@ class ShowApplicationAction extends OwnerDesignAction
'id' => 'reset', 'id' => 'reset',
'name' => 'reset', 'name' => 'reset',
'class' => 'submit', 'class' => 'submit',
// TRANS: Button text on the OAuth application page.
// TRANS: Resets the OAuth consumer key and secret.
'value' => _('Reset key & secret'), 'value' => _('Reset key & secret'),
'onClick' => 'return confirmReset()')); 'onClick' => 'return confirmReset()'));
$this->elementEnd('fieldset'); $this->elementEnd('fieldset');
@ -225,7 +238,8 @@ class ShowApplicationAction extends OwnerDesignAction
$this->elementStart('fieldset'); $this->elementStart('fieldset');
$this->hidden('token', common_session_token()); $this->hidden('token', common_session_token());
$this->submit('delete', _('Delete')); // TRANS: Submit button text the OAuth application page to delete an application.
$this->submit('delete', _m('BUTTON','Delete'));
$this->elementEnd('fieldset'); $this->elementEnd('fieldset');
$this->elementEnd('form'); $this->elementEnd('form');
$this->elementEnd('li'); $this->elementEnd('li');
@ -234,6 +248,7 @@ class ShowApplicationAction extends OwnerDesignAction
$this->elementEnd('div'); $this->elementEnd('div');
$this->elementStart('div', 'entity_data'); $this->elementStart('div', 'entity_data');
// TRANS: Header on the OAuth application page.
$this->element('h2', null, _('Application info')); $this->element('h2', null, _('Application info'));
$this->element('div', $this->element('div',
'entity_consumer_key', 'entity_consumer_key',
@ -252,7 +267,8 @@ class ShowApplicationAction extends OwnerDesignAction
$this->element('div', 'entity_authorize_url', common_local_url('ApiOauthAuthorize')); $this->element('div', 'entity_authorize_url', common_local_url('ApiOauthAuthorize'));
$this->element('p', 'note', $this->element('p', 'note',
_('Note: We support HMAC-SHA1 signatures. We do not support the plaintext signature method.')); // TRANS: Note on the OAuth application page about signature support.
_('Note: HMAC-SHA1 signatures are supported. The plaintext signature method is not supported.'));
$this->elementEnd('div'); $this->elementEnd('div');
$this->elementStart('p', array('id' => 'application_action')); $this->elementStart('p', array('id' => 'application_action'));
@ -272,6 +288,7 @@ class ShowApplicationAction extends OwnerDesignAction
{ {
parent::showScripts(); parent::showScripts();
// TRANS: Text in confirmation dialog to reset consumer key and secret for an OAuth application.
$msg = _('Are you sure you want to reset your consumer key and secret?'); $msg = _('Are you sure you want to reset your consumer key and secret?');
$js = 'function confirmReset() { '; $js = 'function confirmReset() { ';

View File

@ -324,6 +324,8 @@ class ShowgroupAction extends GroupDesignAction
$this->element('h2', null, _('Statistics')); $this->element('h2', null, _('Statistics'));
$this->elementStart('dl'); $this->elementStart('dl');
// TRANS: Label for group creation date.
$this->element('dt', null, _m('LABEL','Created')); $this->element('dt', null, _m('LABEL','Created'));
$this->element('dd', 'entity_created', date('j M Y', $this->element('dd', 'entity_created', date('j M Y',
strtotime($this->group->created))); strtotime($this->group->created)));
@ -363,6 +365,18 @@ class ShowgroupAction extends GroupDesignAction
$this->raw(common_markup_to_html($m)); $this->raw(common_markup_to_html($m));
$this->elementEnd('div'); $this->elementEnd('div');
} }
function noticeFormOptions()
{
$options = parent::noticeFormOptions();
$cur = common_current_user();
if (!empty($cur) && $cur->isMember($this->group)) {
$options['to_group'] = $this->group;
}
return $options;
}
} }
class GroupAdminSection extends ProfileSection class GroupAdminSection extends ProfileSection
@ -382,8 +396,8 @@ class GroupAdminSection extends ProfileSection
function title() function title()
{ {
// TRANS: Header for list of group administrators on a group page (h2). // TRANS: Title for list of group administrators on a group page.
return _('Admins'); return _m('TITLE','Admins');
} }
function divId() function divId()

View File

@ -44,25 +44,21 @@ require_once INSTALLDIR.'/lib/feedlist.php';
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
class ShownoticeAction extends OwnerDesignAction class ShownoticeAction extends OwnerDesignAction
{ {
/** /**
* Notice object to show * Notice object to show
*/ */
var $notice = null; var $notice = null;
/** /**
* Profile of the notice object * Profile of the notice object
*/ */
var $profile = null; var $profile = null;
/** /**
* Avatar of the profile of the notice object * Avatar of the profile of the notice object
*/ */
var $avatar = null; var $avatar = null;
/** /**
@ -74,7 +70,6 @@ class ShownoticeAction extends OwnerDesignAction
* *
* @return success flag * @return success flag
*/ */
function prepare($args) function prepare($args)
{ {
parent::prepare($args); parent::prepare($args);
@ -82,24 +77,25 @@ class ShownoticeAction extends OwnerDesignAction
StatusNet::setApi(true); StatusNet::setApi(true);
} }
$id = $this->arg('notice'); $this->notice = $this->getNotice();
$this->notice = Notice::staticGet($id); $cur = common_current_user();
if (empty($this->notice)) { if (!empty($cur)) {
// Did we used to have it, and it got deleted? $curProfile = $cur->getProfile();
$deleted = Deleted_notice::staticGet($id); } else {
if (!empty($deleted)) { $curProfile = null;
$this->clientError(_('Notice deleted.'), 410); }
} else {
$this->clientError(_('No such notice.'), 404); if (!$this->notice->inScope($curProfile)) {
} // TRANS: Client exception thrown when trying a view a notice the user has no access to.
return false; throw new ClientException(_('Not available.'), 403);
} }
$this->profile = $this->notice->getProfile(); $this->profile = $this->notice->getProfile();
if (empty($this->profile)) { if (empty($this->profile)) {
// TRANS: Server error displayed trying to show a notice without a connected profile.
$this->serverError(_('Notice has no profile.'), 500); $this->serverError(_('Notice has no profile.'), 500);
return false; return false;
} }
@ -111,12 +107,38 @@ class ShownoticeAction extends OwnerDesignAction
return true; return true;
} }
/**
* Fetch the notice to show. This may be overridden by child classes to
* customize what we fetch without duplicating all of the prepare() method.
*
* @return Notice
*/
function getNotice()
{
$id = $this->arg('notice');
$notice = Notice::staticGet('id', $id);
if (empty($notice)) {
// Did we used to have it, and it got deleted?
$deleted = Deleted_notice::staticGet($id);
if (!empty($deleted)) {
// TRANS: Client error displayed trying to show a deleted notice.
$this->clientError(_('Notice deleted.'), 410);
} else {
// TRANS: Client error displayed trying to show a non-existing notice.
$this->clientError(_('No such notice.'), 404);
}
return false;
}
return $notice;
}
/** /**
* Is this action read-only? * Is this action read-only?
* *
* @return boolean true * @return boolean true
*/ */
function isReadOnly($args) function isReadOnly($args)
{ {
return true; return true;
@ -130,7 +152,6 @@ class ShownoticeAction extends OwnerDesignAction
* *
* @return int last-modified date as unix timestamp * @return int last-modified date as unix timestamp
*/ */
function lastModified() function lastModified()
{ {
return max(strtotime($this->notice->modified), return max(strtotime($this->notice->modified),
@ -147,7 +168,6 @@ class ShownoticeAction extends OwnerDesignAction
* *
* @return string etag * @return string etag
*/ */
function etag() function etag()
{ {
$avtime = ($this->avatar) ? $avtime = ($this->avatar) ?
@ -167,11 +187,12 @@ class ShownoticeAction extends OwnerDesignAction
* *
* @return string title of the page * @return string title of the page
*/ */
function title() function title()
{ {
$base = $this->profile->getFancyName(); $base = $this->profile->getFancyName();
// TRANS: Title of the page that shows a notice.
// TRANS: %1$s is a user name, %2$s is the notice creation date/time.
return sprintf(_('%1$s\'s status on %2$s'), return sprintf(_('%1$s\'s status on %2$s'),
$base, $base,
common_exact_date($this->notice->created)); common_exact_date($this->notice->created));
@ -186,7 +207,6 @@ class ShownoticeAction extends OwnerDesignAction
* *
* @return void * @return void
*/ */
function handle($args) function handle($args)
{ {
parent::handle($args); parent::handle($args);
@ -218,7 +238,6 @@ class ShownoticeAction extends OwnerDesignAction
* *
* @return void * @return void
*/ */
function showLocalNavBlock() function showLocalNavBlock()
{ {
} }
@ -230,7 +249,6 @@ class ShownoticeAction extends OwnerDesignAction
* *
* @return void * @return void
*/ */
function showContent() function showContent()
{ {
$this->elementStart('ol', array('class' => 'notices xoxo')); $this->elementStart('ol', array('class' => 'notices xoxo'));
@ -245,7 +263,8 @@ class ShownoticeAction extends OwnerDesignAction
$this->xw->startDocument('1.0', 'UTF-8'); $this->xw->startDocument('1.0', 'UTF-8');
$this->elementStart('html'); $this->elementStart('html');
$this->elementStart('head'); $this->elementStart('head');
$this->element('title', null, _('Notice')); // TRANS: Title for page that shows a notice.
$this->element('title', null, _m('TITLE','Notice'));
$this->elementEnd('head'); $this->elementEnd('head');
$this->elementStart('body'); $this->elementStart('body');
$nli = new NoticeListItem($this->notice, $this); $nli = new NoticeListItem($this->notice, $this);
@ -259,7 +278,6 @@ class ShownoticeAction extends OwnerDesignAction
* *
* @return void * @return void
*/ */
function showPageNoticeBlock() function showPageNoticeBlock()
{ {
} }
@ -269,7 +287,6 @@ class ShownoticeAction extends OwnerDesignAction
* *
* @return void * @return void
*/ */
function showAside() { function showAside() {
} }
@ -280,7 +297,6 @@ class ShownoticeAction extends OwnerDesignAction
* *
* @return void * @return void
*/ */
function extraHead() function extraHead()
{ {
$user = User::staticGet($this->profile->id); $user = User::staticGet($this->profile->id);
@ -323,16 +339,16 @@ class ShownoticeAction extends OwnerDesignAction
} }
} }
// @todo FIXME: Class documentation missing.
class SingleNoticeItem extends DoFollowListItem class SingleNoticeItem extends DoFollowListItem
{ {
/** /**
* recipe function for displaying a single notice. * Recipe function for displaying a single notice.
* *
* We overload to show attachments. * We overload to show attachments.
* *
* @return void * @return void
*/ */
function show() function show()
{ {
$this->showStart(); $this->showStart();
@ -363,7 +379,6 @@ class SingleNoticeItem extends DoFollowListItem
* *
* @return void * @return void
*/ */
function showAvatar() function showAvatar()
{ {
$avatar_size = AVATAR_PROFILE_SIZE; $avatar_size = AVATAR_PROFILE_SIZE;

View File

@ -65,7 +65,8 @@ class ShowstreamAction extends ProfileAction
$base = $this->profile->getFancyName(); $base = $this->profile->getFancyName();
if (!empty($this->tag)) { if (!empty($this->tag)) {
if ($this->page == 1) { if ($this->page == 1) {
// TRANS: Page title showing tagged notices in one user's stream. %1$s is the username, %2$s is the hash tag. // TRANS: Page title showing tagged notices in one user's stream.
// TRANS: %1$s is the username, %2$s is the hash tag.
return sprintf(_('%1$s tagged %2$s'), $base, $this->tag); return sprintf(_('%1$s tagged %2$s'), $base, $this->tag);
} else { } else {
// TRANS: Page title showing tagged notices in one user's stream. // TRANS: Page title showing tagged notices in one user's stream.
@ -153,6 +154,8 @@ class ShowstreamAction extends ProfileAction
array( array(
'id' => $this->user->id, 'id' => $this->user->id,
'format' => 'atom')), 'format' => 'atom')),
// TRANS: Title for link to notice feed.
// TRANS: %s is a user nickname.
sprintf(_('Notice feed for %s (Atom)'), sprintf(_('Notice feed for %s (Atom)'),
$this->user->nickname)), $this->user->nickname)),
new Feed(Feed::FOAF, new Feed(Feed::FOAF,
@ -275,6 +278,18 @@ class ShowstreamAction extends ProfileAction
$cloud = new PersonalTagCloudSection($this, $this->user); $cloud = new PersonalTagCloudSection($this, $this->user);
$cloud->show(); $cloud->show();
} }
function noticeFormOptions()
{
$options = parent::noticeFormOptions();
$cur = common_current_user();
if (empty($cur) || $cur->id != $this->profile->id) {
$options['to_profile'] = $this->profile;
}
return $options;
}
} }
// We don't show the author for a profile, since we already know who it is! // We don't show the author for a profile, since we already know who it is!

View File

@ -40,7 +40,6 @@ if (!defined('STATUSNET')) {
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/ * @link http://status.net/
*/ */
class SilenceAction extends ProfileFormAction class SilenceAction extends ProfileFormAction
{ {
/** /**
@ -50,7 +49,6 @@ class SilenceAction extends ProfileFormAction
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare($args)
{ {
if (!parent::prepare($args)) { if (!parent::prepare($args)) {
@ -62,6 +60,7 @@ class SilenceAction extends ProfileFormAction
assert(!empty($cur)); // checked by parent assert(!empty($cur)); // checked by parent
if (!$cur->hasRight(Right::SILENCEUSER)) { if (!$cur->hasRight(Right::SILENCEUSER)) {
// TRANS: Client error displayed trying to silence a user on a site where the feature is not enabled.
$this->clientError(_('You cannot silence users on this site.')); $this->clientError(_('You cannot silence users on this site.'));
return false; return false;
} }
@ -69,6 +68,7 @@ class SilenceAction extends ProfileFormAction
assert(!empty($this->profile)); // checked by parent assert(!empty($this->profile)); // checked by parent
if ($this->profile->isSilenced()) { if ($this->profile->isSilenced()) {
// TRANS: Client error displayed trying to silence an already silenced user.
$this->clientError(_('User is already silenced.')); $this->clientError(_('User is already silenced.'));
return false; return false;
} }
@ -81,7 +81,6 @@ class SilenceAction extends ProfileFormAction
* *
* @return void * @return void
*/ */
function handlePost() function handlePost()
{ {
$this->profile->silence(); $this->profile->silence();

View File

@ -44,7 +44,6 @@ if (!defined('STATUSNET')) {
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
class SiteadminpanelAction extends AdminPanelAction class SiteadminpanelAction extends AdminPanelAction
{ {
/** /**
@ -52,10 +51,10 @@ class SiteadminpanelAction extends AdminPanelAction
* *
* @return string page title * @return string page title
*/ */
function title() function title()
{ {
return _('Site'); // TRANS: Title for site administration panel.
return _m('TITLE','Site');
} }
/** /**
@ -63,9 +62,9 @@ class SiteadminpanelAction extends AdminPanelAction
* *
* @return string instructions * @return string instructions
*/ */
function getInstructions() function getInstructions()
{ {
// TRANS: Instructions for site administration panel.
return _('Basic settings for this StatusNet site'); return _('Basic settings for this StatusNet site');
} }
@ -74,7 +73,6 @@ class SiteadminpanelAction extends AdminPanelAction
* *
* @return void * @return void
*/ */
function showForm() function showForm()
{ {
$form = new SiteAdminPanelForm($this); $form = new SiteAdminPanelForm($this);
@ -87,7 +85,6 @@ class SiteadminpanelAction extends AdminPanelAction
* *
* @return void * @return void
*/ */
function saveSettings() function saveSettings()
{ {
static $settings = array( static $settings = array(
@ -130,6 +127,7 @@ class SiteadminpanelAction extends AdminPanelAction
// Validate site name // Validate site name
if (empty($values['site']['name'])) { if (empty($values['site']['name'])) {
// TRANS: Client error displayed trying to save an empty site name.
$this->clientError(_('Site name must have non-zero length.')); $this->clientError(_('Site name must have non-zero length.'));
} }
@ -138,9 +136,11 @@ class SiteadminpanelAction extends AdminPanelAction
$values['site']['email'] = common_canonical_email($values['site']['email']); $values['site']['email'] = common_canonical_email($values['site']['email']);
if (empty($values['site']['email'])) { if (empty($values['site']['email'])) {
// TRANS: Client error displayed trying to save site settings without a contact address.
$this->clientError(_('You must have a valid contact email address.')); $this->clientError(_('You must have a valid contact email address.'));
} }
if (!Validate::email($values['site']['email'], common_config('email', 'check_domain'))) { if (!Validate::email($values['site']['email'], common_config('email', 'check_domain'))) {
// TRANS: Client error displayed trying to save site settings without a valid contact address.
$this->clientError(_('Not a valid email address.')); $this->clientError(_('Not a valid email address.'));
} }
@ -148,6 +148,7 @@ class SiteadminpanelAction extends AdminPanelAction
if (is_null($values['site']['timezone']) || if (is_null($values['site']['timezone']) ||
!in_array($values['site']['timezone'], DateTimeZone::listIdentifiers())) { !in_array($values['site']['timezone'], DateTimeZone::listIdentifiers())) {
// TRANS: Client error displayed trying to save site settings without a timezone.
$this->clientError(_('Timezone not selected.')); $this->clientError(_('Timezone not selected.'));
return; return;
} }
@ -156,24 +157,28 @@ class SiteadminpanelAction extends AdminPanelAction
if (!is_null($values['site']['language']) && if (!is_null($values['site']['language']) &&
!in_array($values['site']['language'], array_keys(get_nice_language_list()))) { !in_array($values['site']['language'], array_keys(get_nice_language_list()))) {
// TRANS: Client error displayed trying to save site settings with an invalid language code.
// TRANS: %s is the invalid language code.
$this->clientError(sprintf(_('Unknown language "%s".'), $values['site']['language'])); $this->clientError(sprintf(_('Unknown language "%s".'), $values['site']['language']));
} }
// Validate text limit // Validate text limit
if (!Validate::number($values['site']['textlimit'], array('min' => 0))) { if (!Validate::number($values['site']['textlimit'], array('min' => 0))) {
$this->clientError(_("Minimum text limit is 0 (unlimited).")); // TRANS: Client error displayed trying to save site settings with a text limit below 0.
$this->clientError(_('Minimum text limit is 0 (unlimited).'));
} }
// Validate dupe limit // Validate dupe limit
if (!Validate::number($values['site']['dupelimit'], array('min' => 1))) { if (!Validate::number($values['site']['dupelimit'], array('min' => 1))) {
// TRANS: Client error displayed trying to save site settings with a text limit below 1.
$this->clientError(_("Dupe limit must be one or more seconds.")); $this->clientError(_("Dupe limit must be one or more seconds."));
} }
} }
} }
// @todo FIXME: Class documentation missing.
class SiteAdminPanelForm extends AdminForm class SiteAdminPanelForm extends AdminForm
{ {
/** /**
@ -181,7 +186,6 @@ class SiteAdminPanelForm extends AdminForm
* *
* @return int ID of the form * @return int ID of the form
*/ */
function id() function id()
{ {
return 'form_site_admin_panel'; return 'form_site_admin_panel';
@ -192,7 +196,6 @@ class SiteAdminPanelForm extends AdminForm
* *
* @return string class of the form * @return string class of the form
*/ */
function formClass() function formClass()
{ {
return 'form_settings'; return 'form_settings';
@ -203,7 +206,6 @@ class SiteAdminPanelForm extends AdminForm
* *
* @return string URL of the action * @return string URL of the action
*/ */
function action() function action()
{ {
return common_local_url('siteadminpanel'); return common_local_url('siteadminpanel');
@ -214,35 +216,44 @@ class SiteAdminPanelForm extends AdminForm
* *
* @return void * @return void
*/ */
function formData() function formData()
{ {
$this->out->elementStart('fieldset', array('id' => 'settings_admin_general')); $this->out->elementStart('fieldset', array('id' => 'settings_admin_general'));
$this->out->element('legend', null, _('General')); // TRANS: Fieldset legend on site settings panel.
$this->out->element('legend', null, _m('LEGEND','General'));
$this->out->elementStart('ul', 'form_data'); $this->out->elementStart('ul', 'form_data');
$this->li(); $this->li();
$this->input('name', _('Site name'), // TRANS: Field label on site settings panel.
_('The name of your site, like "Yourcompany Microblog"')); $this->input('name', _m('LABEL','Site name'),
// TRANS: Field title on site settings panel.
_('The name of your site, like "Yourcompany Microblog".'));
$this->unli(); $this->unli();
$this->li(); $this->li();
// TRANS: Field label on site settings panel.
$this->input('broughtby', _('Brought by'), $this->input('broughtby', _('Brought by'),
_('Text used for credits link in footer of each page')); // TRANS: Field title on site settings panel.
_('Text used for credits link in footer of each page.'));
$this->unli(); $this->unli();
$this->li(); $this->li();
// TRANS: Field label on site settings panel.
$this->input('broughtbyurl', _('Brought by URL'), $this->input('broughtbyurl', _('Brought by URL'),
_('URL used for credits link in footer of each page')); // TRANS: Field title on site settings panel.
_('URL used for credits link in footer of each page.'));
$this->unli(); $this->unli();
$this->li(); $this->li();
// TRANS: Field label on site settings panel.
$this->input('email', _('Email'), $this->input('email', _('Email'),
_('Contact email address for your site')); // TRANS: Field title on site settings panel.
_('Contact email address for your site.'));
$this->unli(); $this->unli();
$this->out->elementEnd('ul'); $this->out->elementEnd('ul');
$this->out->elementEnd('fieldset'); $this->out->elementEnd('fieldset');
$this->out->elementStart('fieldset', array('id' => 'settings_admin_local')); $this->out->elementStart('fieldset', array('id' => 'settings_admin_local'));
$this->out->element('legend', null, _('Local')); // TRANS: Fieldset legend on site settings panel.
$this->out->element('legend', null, _m('LEGEND','Local'));
$this->out->elementStart('ul', 'form_data'); $this->out->elementStart('ul', 'form_data');
$timezones = array(); $timezones = array();
@ -253,14 +264,20 @@ class SiteAdminPanelForm extends AdminForm
asort($timezones); asort($timezones);
$this->li(); $this->li();
// TRANS: Dropdown label on site settings panel.
$this->out->dropdown('timezone', _('Default timezone'), $this->out->dropdown('timezone', _('Default timezone'),
// TRANS: Dropdown title on site settings panel.
$timezones, _('Default timezone for the site; usually UTC.'), $timezones, _('Default timezone for the site; usually UTC.'),
true, $this->value('timezone')); true, $this->value('timezone'));
$this->unli(); $this->unli();
$this->li(); $this->li();
$this->out->dropdown('language', _('Default language'), $this->out->dropdown('language',
get_nice_language_list(), _('Site language when autodetection from browser settings is not available'), // TRANS: Dropdown label on site settings panel.
_('Default language'),
get_nice_language_list(),
// TRANS: Dropdown title on site settings panel.
_('Site language when autodetection from browser settings is not available'),
false, $this->value('language')); false, $this->value('language'));
$this->unli(); $this->unli();
@ -268,14 +285,23 @@ class SiteAdminPanelForm extends AdminForm
$this->out->elementEnd('fieldset'); $this->out->elementEnd('fieldset');
$this->out->elementStart('fieldset', array('id' => 'settings_admin_limits')); $this->out->elementStart('fieldset', array('id' => 'settings_admin_limits'));
$this->out->element('legend', null, _('Limits')); // TRANS: Fieldset legend on site settings panel.
$this->out->element('legend', null, _m('LEGEND','Limits'));
$this->out->elementStart('ul', 'form_data'); $this->out->elementStart('ul', 'form_data');
$this->li(); $this->li();
$this->input('textlimit', _('Text limit'), _('Maximum number of characters for notices.')); $this->input('textlimit',
// TRANS: Field label on site settings panel.
_('Text limit'),
// TRANS: Field title on site settings panel.
_('Maximum number of characters for notices.'));
$this->unli(); $this->unli();
$this->li(); $this->li();
$this->input('dupelimit', _('Dupe limit'), _('How long users must wait (in seconds) to post the same thing again.')); $this->input('dupelimit',
// TRANS: Field label on site settings panel.
_('Dupe limit'),
// TRANS: Field title on site settings panel.
_('How long users must wait (in seconds) to post the same thing again.'));
$this->unli(); $this->unli();
$this->out->elementEnd('ul'); $this->out->elementEnd('ul');
$this->out->elementEnd('fieldset'); $this->out->elementEnd('fieldset');
@ -286,9 +312,14 @@ class SiteAdminPanelForm extends AdminForm
* *
* @return void * @return void
*/ */
function formActions() function formActions()
{ {
$this->out->submit('submit', _('Save'), 'submit', null, _('Save site settings')); $this->out->submit('submit',
// TRANS: Button text for saving site settings.
_m('BUTTON','Save'),
'submit',
null,
// TRANS: Button title for saving site settings.
_('Save site settings'));
} }
} }

View File

@ -40,7 +40,6 @@ if (!defined('STATUSNET')) {
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
class SnapshotadminpanelAction extends AdminPanelAction class SnapshotadminpanelAction extends AdminPanelAction
{ {
/** /**
@ -48,10 +47,10 @@ class SnapshotadminpanelAction extends AdminPanelAction
* *
* @return string page title * @return string page title
*/ */
function title() function title()
{ {
return _('Snapshots'); // TRANS: Title for admin panel to configure snapshots.
return _m('TITLE','Snapshots');
} }
/** /**
@ -59,9 +58,9 @@ class SnapshotadminpanelAction extends AdminPanelAction
* *
* @return string instructions * @return string instructions
*/ */
function getInstructions() function getInstructions()
{ {
// TRANS: Instructions for admin panel to configure snapshots.
return _('Manage snapshot configuration'); return _('Manage snapshot configuration');
} }
@ -70,7 +69,6 @@ class SnapshotadminpanelAction extends AdminPanelAction
* *
* @return void * @return void
*/ */
function showForm() function showForm()
{ {
$form = new SnapshotAdminPanelForm($this); $form = new SnapshotAdminPanelForm($this);
@ -83,7 +81,6 @@ class SnapshotadminpanelAction extends AdminPanelAction
* *
* @return void * @return void
*/ */
function saveSettings() function saveSettings()
{ {
static $settings = array( static $settings = array(
@ -124,12 +121,14 @@ class SnapshotadminpanelAction extends AdminPanelAction
// Validate snapshot run value // Validate snapshot run value
if (!in_array($values['snapshot']['run'], array('web', 'cron', 'never'))) { if (!in_array($values['snapshot']['run'], array('web', 'cron', 'never'))) {
// TRANS: Client error displayed on admin panel for snapshots when providing an invalid run value.
$this->clientError(_('Invalid snapshot run value.')); $this->clientError(_('Invalid snapshot run value.'));
} }
// Validate snapshot frequency value // Validate snapshot frequency value
if (!Validate::number($values['snapshot']['frequency'])) { if (!Validate::number($values['snapshot']['frequency'])) {
// TRANS: Client error displayed on admin panel for snapshots when providing an invalid value for frequency.
$this->clientError(_('Snapshot frequency must be a number.')); $this->clientError(_('Snapshot frequency must be a number.'));
} }
@ -141,11 +140,13 @@ class SnapshotadminpanelAction extends AdminPanelAction
array('allowed_schemes' => array('http', 'https') array('allowed_schemes' => array('http', 'https')
) )
)) { )) {
// TRANS: Client error displayed on admin panel for snapshots when providing an invalid report URL.
$this->clientError(_('Invalid snapshot report URL.')); $this->clientError(_('Invalid snapshot report URL.'));
} }
} }
} }
// @todo FIXME: add documentation
class SnapshotAdminPanelForm extends AdminForm class SnapshotAdminPanelForm extends AdminForm
{ {
/** /**
@ -153,7 +154,6 @@ class SnapshotAdminPanelForm extends AdminForm
* *
* @return int ID of the form * @return int ID of the form
*/ */
function id() function id()
{ {
return 'form_snapshot_admin_panel'; return 'form_snapshot_admin_panel';
@ -164,7 +164,6 @@ class SnapshotAdminPanelForm extends AdminForm
* *
* @return string class of the form * @return string class of the form
*/ */
function formClass() function formClass()
{ {
return 'form_settings'; return 'form_settings';
@ -175,7 +174,6 @@ class SnapshotAdminPanelForm extends AdminForm
* *
* @return string URL of the action * @return string URL of the action
*/ */
function action() function action()
{ {
return common_local_url('snapshotadminpanel'); return common_local_url('snapshotadminpanel');
@ -186,26 +184,31 @@ class SnapshotAdminPanelForm extends AdminForm
* *
* @return void * @return void
*/ */
function formData() function formData()
{ {
$this->out->elementStart( $this->out->elementStart(
'fieldset', 'fieldset',
array('id' => 'settings_admin_snapshots') array('id' => 'settings_admin_snapshots')
); );
$this->out->element('legend', null, _('Snapshots')); // TRANS: Fieldset legend on admin panel for snapshots.
$this->out->element('legend', null, _m('LEGEND','Snapshots'));
$this->out->elementStart('ul', 'form_data'); $this->out->elementStart('ul', 'form_data');
$this->li(); $this->li();
$snapshot = array( $snapshot = array(
// TRANS: Option in dropdown for snapshot method in admin panel for snapshots.
'web' => _('Randomly during web hit'), 'web' => _('Randomly during web hit'),
// TRANS: Option in dropdown for snapshot method in admin panel for snapshots.
'cron' => _('In a scheduled job'), 'cron' => _('In a scheduled job'),
// TRANS: Option in dropdown for snapshot method in admin panel for snapshots.
'never' => _('Never') 'never' => _('Never')
); );
$this->out->dropdown( $this->out->dropdown(
'run', 'run',
// TRANS: Dropdown label for snapshot method in admin panel for snapshots.
_('Data snapshots'), _('Data snapshots'),
$snapshot, $snapshot,
_('When to send statistical data to status.net servers'), // TRANS: Dropdown title for snapshot method in admin panel for snapshots.
_('When to send statistical data to status.net servers.'),
false, false,
$this->value('run', 'snapshot') $this->value('run', 'snapshot')
); );
@ -214,8 +217,10 @@ class SnapshotAdminPanelForm extends AdminForm
$this->li(); $this->li();
$this->input( $this->input(
'frequency', 'frequency',
// TRANS: Input field label for snapshot frequency in admin panel for snapshots.
_('Frequency'), _('Frequency'),
_('Snapshots will be sent once every N web hits'), // TRANS: Input field title for snapshot frequency in admin panel for snapshots.
_('Snapshots will be sent once every N web hits.'),
'snapshot' 'snapshot'
); );
$this->unli(); $this->unli();
@ -223,8 +228,10 @@ class SnapshotAdminPanelForm extends AdminForm
$this->li(); $this->li();
$this->input( $this->input(
'reporturl', 'reporturl',
// TRANS: Input field label for snapshot report URL in admin panel for snapshots.
_('Report URL'), _('Report URL'),
_('Snapshots will be sent to this URL'), // TRANS: Input field title for snapshot report URL in admin panel for snapshots.
_('Snapshots will be sent to this URL.'),
'snapshot' 'snapshot'
); );
$this->unli(); $this->unli();
@ -237,15 +244,16 @@ class SnapshotAdminPanelForm extends AdminForm
* *
* @return void * @return void
*/ */
function formActions() function formActions()
{ {
$this->out->submit( $this->out->submit(
'submit', 'submit',
_('Save'), // TRANS: Button text to save snapshot settings.
_m('BUTTON','Save'),
'submit', 'submit',
null, null,
_('Save snapshot settings') // TRANS: Title for button to save snapshot settings.
_('Save snapshot settings.')
); );
} }
} }

142
actions/subqueue.php Normal file
View File

@ -0,0 +1,142 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* List of group members
*
* 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 Group
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2008-2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once(INSTALLDIR.'/lib/profilelist.php');
/**
* List of group members
*
* @category Group
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class SubqueueAction extends GalleryAction
{
var $page = null;
function isReadOnly($args)
{
return true;
}
// @todo FIXME: most of this belongs in a base class, sounds common to most group actions?
function prepare($args)
{
parent::prepare($args);
$cur = common_current_user();
if (!$cur || $cur->id != $this->profile->id) {
// TRANS: Client error displayed when trying to approve group applicants without being a group administrator.
$this->clientError(_('You may only approve your own pending subscriptions.'));
return false;
}
return true;
}
function title()
{
if ($this->page == 1) {
// TRANS: Title of the first page showing pending subscribers still awaiting approval.
// TRANS: %s is the name of the user.
return sprintf(_('%s subscribers awaiting approval'),
$this->profile->nickname);
} else {
// TRANS: Title of all but the first page showing pending subscribersmembers still awaiting approval.
// TRANS: %1$s is the name of the user, %2$d is the page number of the members list.
return sprintf(_('%1$s subscribers awaiting approval, page %2$d'),
$this->profile->nickname,
$this->page);
}
}
function showPageNotice()
{
$this->element('p', 'instructions',
// TRANS: Page notice for group members page.
_('A list of users awaiting approval to subscribe to you.'));
}
function showContent()
{
$offset = ($this->page-1) * PROFILES_PER_PAGE;
$limit = PROFILES_PER_PAGE + 1;
$cnt = 0;
$members = $this->profile->getRequests($offset, $limit);
if ($members) {
// @fixme change!
$member_list = new SubQueueList($members, $this);
$cnt = $member_list->show();
}
$members->free();
$this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE,
$this->page, 'subqueue',
array('nickname' => $this->profile->nickname)); // urgh
}
}
class SubQueueList extends ProfileList
{
function newListItem($profile)
{
return new SubQueueListItem($profile, $this->action);
}
}
class SubQueueListItem extends ProfileListItem
{
function showActions()
{
$this->startActions();
if (Event::handle('StartProfileListItemActionElements', array($this))) {
$this->showApproveButtons();
Event::handle('EndProfileListItemActionElements', array($this));
}
$this->endActions();
}
function showApproveButtons()
{
$this->out->elementStart('li', 'entity_approval');
$form = new ApproveSubForm($this->out, $this->profile);
$form->show();
$this->out->elementEnd('li');
}
}

View File

@ -139,8 +139,8 @@ class SubscribeAction extends Action
{ {
// Throws exception on error // Throws exception on error
Subscription::start($this->user->getProfile(), $sub = Subscription::start($this->user->getProfile(),
$this->other); $this->other);
if ($this->boolean('ajax')) { if ($this->boolean('ajax')) {
$this->startHTML('text/xml;charset=utf-8'); $this->startHTML('text/xml;charset=utf-8');
@ -149,8 +149,12 @@ class SubscribeAction extends Action
$this->element('title', null, _('Subscribed')); $this->element('title', null, _('Subscribed'));
$this->elementEnd('head'); $this->elementEnd('head');
$this->elementStart('body'); $this->elementStart('body');
$unsubscribe = new UnsubscribeForm($this, $this->other); if ($sub instanceof Subscription) {
$unsubscribe->show(); $form = new UnsubscribeForm($this, $this->other);
} else {
$form = new CancelSubscriptionForm($this, $this->other);
}
$form->show();
$this->elementEnd('body'); $this->elementEnd('body');
$this->elementEnd('html'); $this->elementEnd('html');
} else { } else {

View File

@ -61,8 +61,8 @@ class SupAction extends Action
{ {
$notice = new Notice(); $notice = new Notice();
# XXX: cache this. Depends on how big this protocol becomes; // XXX: cache this. Depends on how big this protocol becomes;
# Re-doing this query every 15 seconds isn't the end of the world. // Re-doing this query every 15 seconds isn't the end of the world.
$divider = common_sql_date(time() - $seconds); $divider = common_sql_date(time() - $seconds);

View File

@ -19,9 +19,9 @@
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
// @todo FIXME: documentation missing.
class TagAction extends Action class TagAction extends Action
{ {
var $notice; var $notice;
function prepare($args) function prepare($args)
@ -48,7 +48,7 @@ class TagAction extends Action
$this->notice = Notice_tag::getStream($this->tag, (($this->page-1)*NOTICES_PER_PAGE), NOTICES_PER_PAGE + 1); $this->notice = Notice_tag::getStream($this->tag, (($this->page-1)*NOTICES_PER_PAGE), NOTICES_PER_PAGE + 1);
if($this->page > 1 && $this->notice->N == 0){ if($this->page > 1 && $this->notice->N == 0){
// TRANS: Server error when page not found (404) // TRANS: Server error when page not found (404).
$this->serverError(_('No such page.'),$code=404); $this->serverError(_('No such page.'),$code=404);
} }
@ -88,18 +88,24 @@ class TagAction extends Action
return array(new Feed(Feed::RSS1, return array(new Feed(Feed::RSS1,
common_local_url('tagrss', common_local_url('tagrss',
array('tag' => $this->tag)), array('tag' => $this->tag)),
// TRANS: Link label for feed on "notices with tag" page.
// TRANS: %s is the tag the feed is for.
sprintf(_('Notice feed for tag %s (RSS 1.0)'), sprintf(_('Notice feed for tag %s (RSS 1.0)'),
$this->tag)), $this->tag)),
new Feed(Feed::RSS2, new Feed(Feed::RSS2,
common_local_url('ApiTimelineTag', common_local_url('ApiTimelineTag',
array('format' => 'rss', array('format' => 'rss',
'tag' => $this->tag)), 'tag' => $this->tag)),
// TRANS: Link label for feed on "notices with tag" page.
// TRANS: %s is the tag the feed is for.
sprintf(_('Notice feed for tag %s (RSS 2.0)'), sprintf(_('Notice feed for tag %s (RSS 2.0)'),
$this->tag)), $this->tag)),
new Feed(Feed::ATOM, new Feed(Feed::ATOM,
common_local_url('ApiTimelineTag', common_local_url('ApiTimelineTag',
array('format' => 'atom', array('format' => 'atom',
'tag' => $this->tag)), 'tag' => $this->tag)),
// TRANS: Link label for feed on "notices with tag" page.
// TRANS: %s is the tag the feed is for.
sprintf(_('Notice feed for tag %s (Atom)'), sprintf(_('Notice feed for tag %s (Atom)'),
$this->tag))); $this->tag)));
} }

View File

@ -22,7 +22,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
require_once(INSTALLDIR.'/lib/rssaction.php'); require_once(INSTALLDIR.'/lib/rssaction.php');
// Formatting of RSS handled by Rss10Action // Formatting of RSS handled by Rss10Action
class TagrssAction extends Rss10Action class TagrssAction extends Rss10Action
{ {
var $tag; var $tag;
@ -32,6 +31,7 @@ class TagrssAction extends Rss10Action
$tag = common_canonical_tag($this->trimmed('tag')); $tag = common_canonical_tag($this->trimmed('tag'));
$this->tag = Notice_tag::staticGet('tag', $tag); $this->tag = Notice_tag::staticGet('tag', $tag);
if (!$this->tag) { if (!$this->tag) {
// TRANS: Client error when requesting a tag feed for a non-existing tag.
$this->clientError(_('No such tag.')); $this->clientError(_('No such tag.'));
return false; return false;
} else { } else {
@ -62,6 +62,8 @@ class TagrssAction extends Rss10Action
$c = array('url' => common_local_url('tagrss', array('tag' => $tagname)), $c = array('url' => common_local_url('tagrss', array('tag' => $tagname)),
'title' => $tagname, 'title' => $tagname,
'link' => common_local_url('tagrss', array('tag' => $tagname)), 'link' => common_local_url('tagrss', array('tag' => $tagname)),
// TRANS: Tag feed description.
// TRANS: %1$s is the tag name, %2$s is the StatusNet sitename.
'description' => sprintf(_('Updates tagged with %1$s on %2$s!'), 'description' => sprintf(_('Updates tagged with %1$s on %2$s!'),
$tagname, common_config('site', 'name'))); $tagname, common_config('site', 'name')));
return $c; return $c;

View File

@ -40,7 +40,6 @@ if (!defined('STATUSNET')) {
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/ * @link http://status.net/
*/ */
class UnsandboxAction extends ProfileFormAction class UnsandboxAction extends ProfileFormAction
{ {
/** /**
@ -50,7 +49,6 @@ class UnsandboxAction extends ProfileFormAction
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare($args)
{ {
if (!parent::prepare($args)) { if (!parent::prepare($args)) {
@ -62,6 +60,7 @@ class UnsandboxAction extends ProfileFormAction
assert(!empty($cur)); // checked by parent assert(!empty($cur)); // checked by parent
if (!$cur->hasRight(Right::SANDBOXUSER)) { if (!$cur->hasRight(Right::SANDBOXUSER)) {
// TRANS: Client error on page to unsandbox a user when the feature is not enabled.
$this->clientError(_('You cannot sandbox users on this site.')); $this->clientError(_('You cannot sandbox users on this site.'));
return false; return false;
} }
@ -69,6 +68,7 @@ class UnsandboxAction extends ProfileFormAction
assert(!empty($this->profile)); // checked by parent assert(!empty($this->profile)); // checked by parent
if (!$this->profile->isSandboxed()) { if (!$this->profile->isSandboxed()) {
// TRANS: Client error on page to unsilence a user when the to be unsandboxed user has not been sandboxed.
$this->clientError(_('User is not sandboxed.')); $this->clientError(_('User is not sandboxed.'));
return false; return false;
} }
@ -81,7 +81,6 @@ class UnsandboxAction extends ProfileFormAction
* *
* @return void * @return void
*/ */
function handlePost() function handlePost()
{ {
$this->profile->unsandbox(); $this->profile->unsandbox();

View File

@ -40,7 +40,6 @@ if (!defined('STATUSNET')) {
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/ * @link http://status.net/
*/ */
class UnsilenceAction extends ProfileFormAction class UnsilenceAction extends ProfileFormAction
{ {
/** /**
@ -50,7 +49,6 @@ class UnsilenceAction extends ProfileFormAction
* *
* @return boolean success flag * @return boolean success flag
*/ */
function prepare($args) function prepare($args)
{ {
if (!parent::prepare($args)) { if (!parent::prepare($args)) {
@ -62,6 +60,7 @@ class UnsilenceAction extends ProfileFormAction
assert(!empty($cur)); // checked by parent assert(!empty($cur)); // checked by parent
if (!$cur->hasRight(Right::SILENCEUSER)) { if (!$cur->hasRight(Right::SILENCEUSER)) {
// TRANS: Client error on page to unsilence a user when the feature is not enabled.
$this->clientError(_('You cannot silence users on this site.')); $this->clientError(_('You cannot silence users on this site.'));
return false; return false;
} }
@ -69,6 +68,7 @@ class UnsilenceAction extends ProfileFormAction
assert(!empty($this->profile)); // checked by parent assert(!empty($this->profile)); // checked by parent
if (!$this->profile->isSilenced()) { if (!$this->profile->isSilenced()) {
// TRANS: Client error on page to unsilence a user when the to be unsilenced user has not been silenced.
$this->clientError(_('User is not silenced.')); $this->clientError(_('User is not silenced.'));
return false; return false;
} }
@ -81,7 +81,6 @@ class UnsilenceAction extends ProfileFormAction
* *
* @return void * @return void
*/ */
function handlePost() function handlePost()
{ {
$this->profile->unsilence(); $this->profile->unsilence();

View File

@ -44,11 +44,11 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
*/ */
class UnsubscribeAction extends Action class UnsubscribeAction extends Action
{ {
function handle($args) function handle($args)
{ {
parent::handle($args); parent::handle($args);
if (!common_logged_in()) { if (!common_logged_in()) {
// TRANS: Client error displayed when trying to unsubscribe while not logged in.
$this->clientError(_('Not logged in.')); $this->clientError(_('Not logged in.'));
return; return;
} }
@ -74,6 +74,7 @@ class UnsubscribeAction extends Action
$other_id = $this->arg('unsubscribeto'); $other_id = $this->arg('unsubscribeto');
if (!$other_id) { if (!$other_id) {
// TRANS: Client error displayed when trying to unsubscribe without providing a profile ID.
$this->clientError(_('No profile ID in request.')); $this->clientError(_('No profile ID in request.'));
return; return;
} }
@ -81,6 +82,7 @@ class UnsubscribeAction extends Action
$other = Profile::staticGet('id', $other_id); $other = Profile::staticGet('id', $other_id);
if (!$other) { if (!$other) {
// TRANS: Client error displayed when trying to unsubscribe while providing a non-existing profile ID.
$this->clientError(_('No profile with that ID.')); $this->clientError(_('No profile with that ID.'));
return; return;
} }
@ -95,6 +97,7 @@ class UnsubscribeAction extends Action
if ($this->boolean('ajax')) { if ($this->boolean('ajax')) {
$this->startHTML('text/xml;charset=utf-8'); $this->startHTML('text/xml;charset=utf-8');
$this->elementStart('head'); $this->elementStart('head');
// TRANS: Page title for page to unsubscribe.
$this->element('title', null, _('Unsubscribed')); $this->element('title', null, _('Unsubscribed'));
$this->elementEnd('head'); $this->elementEnd('head');
$this->elementStart('body'); $this->elementStart('body');

View File

@ -45,7 +45,6 @@ require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
*/ */
class UpdateprofileAction extends Action class UpdateprofileAction extends Action
{ {
/** /**
* For initializing members of the class. * For initializing members of the class.
* *
@ -61,8 +60,10 @@ class UpdateprofileAction extends Action
$license = $_POST['omb_listenee_license']; $license = $_POST['omb_listenee_license'];
$site_license = common_config('license', 'url'); $site_license = common_config('license', 'url');
if (!common_compatible_license($license, $site_license)) { if (!common_compatible_license($license, $site_license)) {
$this->clientError(sprintf(_('Listenee stream license %1$s is not '. // TRANS: Client error displayed when trying to update profile with an incompatible license.
'compatible with site license %2$s.'), // TRANS: %1$s is the license incompatible with site license %2$s.
$this->clientError(sprintf(_('Listenee stream license "%1$s" is not '.
'compatible with site license "%2$s".'),
$license, $site_license)); $license, $site_license));
return false; return false;
} }

View File

@ -32,8 +32,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1); exit(1);
} }
/** /**
* Miscellaneous settings actions * Miscellaneous settings actions
* *
@ -46,7 +44,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
class UrlsettingsAction extends SettingsAction class UrlsettingsAction extends SettingsAction
{ {
/** /**
@ -54,9 +51,9 @@ class UrlsettingsAction extends SettingsAction
* *
* @return string Title of the page * @return string Title of the page
*/ */
function title() function title()
{ {
// TRANS: Title of URL settings tab in profile settings.
return _('URL settings'); return _('URL settings');
} }
@ -65,7 +62,6 @@ class UrlsettingsAction extends SettingsAction
* *
* @return instructions for use * @return instructions for use
*/ */
function getInstructions() function getInstructions()
{ {
// TRANS: Instructions for tab "Other" in user profile settings. // TRANS: Instructions for tab "Other" in user profile settings.
@ -85,7 +81,6 @@ class UrlsettingsAction extends SettingsAction
* *
* @return void * @return void
*/ */
function showContent() function showContent()
{ {
$user = common_current_user(); $user = common_current_user();
@ -118,11 +113,12 @@ class UrlsettingsAction extends SettingsAction
// Include default values // Include default values
// TRANS: Default value for URL shortening settings.
$services['none'] = _('[none]'); $services['none'] = _('[none]');
// TRANS: Default value for URL shortening settings.
$services['internal'] = _('[internal]'); $services['internal'] = _('[internal]');
if ($services) { if ($services) {
asort($services); asort($services);
$this->elementStart('li'); $this->elementStart('li');
@ -135,16 +131,20 @@ class UrlsettingsAction extends SettingsAction
} }
$this->elementStart('li'); $this->elementStart('li');
$this->input('maxurllength', $this->input('maxurllength',
// TRANS: Field label in URL settings in profile.
_('URL longer than'), _('URL longer than'),
(!is_null($this->arg('maxurllength'))) ? (!is_null($this->arg('maxurllength'))) ?
$this->arg('maxurllength') : User_urlshortener_prefs::maxUrlLength($user), $this->arg('maxurllength') : User_urlshortener_prefs::maxUrlLength($user),
// TRANS: Field title in URL settings in profile.
_('URLs longer than this will be shortened, 0 means always shorten.')); _('URLs longer than this will be shortened, 0 means always shorten.'));
$this->elementEnd('li'); $this->elementEnd('li');
$this->elementStart('li'); $this->elementStart('li');
$this->input('maxnoticelength', $this->input('maxnoticelength',
// TRANS: Field label in URL settings in profile.
_('Text longer than'), _('Text longer than'),
(!is_null($this->arg('maxnoticelength'))) ? (!is_null($this->arg('maxnoticelength'))) ?
$this->arg('maxnoticelength') : User_urlshortener_prefs::maxNoticeLength($user), $this->arg('maxnoticelength') : User_urlshortener_prefs::maxNoticeLength($user),
// TRANS: Field title in URL settings in profile.
_('URLs in notices longer than this will be shortened, 0 means always shorten.')); _('URLs in notices longer than this will be shortened, 0 means always shorten.'));
$this->elementEnd('li'); $this->elementEnd('li');
$this->elementEnd('ul'); $this->elementEnd('ul');
@ -162,7 +162,6 @@ class UrlsettingsAction extends SettingsAction
* *
* @return void * @return void
*/ */
function handlePost() function handlePost()
{ {
// CSRF protection // CSRF protection
@ -184,13 +183,15 @@ class UrlsettingsAction extends SettingsAction
$maxurllength = $this->trimmed('maxurllength'); $maxurllength = $this->trimmed('maxurllength');
if (!Validate::number($maxurllength, array('min' => 0))) { if (!Validate::number($maxurllength, array('min' => 0))) {
throw new ClientException(_('Invalid number for max url length.')); // TRANS: Client exception thrown when the maximum URL settings value is invalid in profile URL settings.
throw new ClientException(_('Invalid number for maximum URL length.'));
} }
$maxnoticelength = $this->trimmed('maxnoticelength'); $maxnoticelength = $this->trimmed('maxnoticelength');
if (!Validate::number($maxnoticelength, array('min' => 0))) { if (!Validate::number($maxnoticelength, array('min' => 0))) {
throw new ClientException(_('Invalid number for max notice length.')); // TRANS: Client exception thrown when the maximum notice length settings value is invalid in profile URL settings.
throw new ClientException(_('Invalid number for maximum notice length.'));
} }
$user = common_current_user(); $user = common_current_user();
@ -235,6 +236,7 @@ class UrlsettingsAction extends SettingsAction
} }
if (!$result) { if (!$result) {
// TRANS: Server exception thrown in profile URL settings when preferences could not be saved.
throw new ServerException(_('Error saving user URL shortening preferences.')); throw new ServerException(_('Error saving user URL shortening preferences.'));
} }

View File

@ -111,7 +111,7 @@ class UserauthorizationAction extends Action
function showPageNotice() function showPageNotice()
{ {
// TRANS: Page notice on "Auhtorize subscription" page. // TRANS: Page notice on "Authorize subscription" page.
$this->element('p', null, _('Please check these details to make sure '. $this->element('p', null, _('Please check these details to make sure '.
'that you want to subscribe to this ' . 'that you want to subscribe to this ' .
'users notices. If you didnt just ask ' . 'users notices. If you didnt just ask ' .
@ -243,10 +243,10 @@ class UserauthorizationAction extends Action
{ {
// TRANS: Accept message header from Authorise subscription page. // TRANS: Accept message header from Authorise subscription page.
common_show_header(_('Subscription authorized')); common_show_header(_('Subscription authorized'));
// TRANS: Accept message text from Authorise subscription page.
$this->element('p', null, $this->element('p', null,
// TRANS: Accept message text from Authorise subscription page.
_('The subscription has been authorized, but no '. _('The subscription has been authorized, but no '.
'callback URL was passed. Check with the sites ' . 'callback URL was passed. Check with the site\'s ' .
'instructions for details on how to authorize the ' . 'instructions for details on how to authorize the ' .
'subscription. Your subscription token is:')); 'subscription. Your subscription token is:'));
$this->element('blockquote', 'token', $tok); $this->element('blockquote', 'token', $tok);
@ -257,10 +257,10 @@ class UserauthorizationAction extends Action
{ {
// TRANS: Reject message header from Authorise subscription page. // TRANS: Reject message header from Authorise subscription page.
common_show_header(_('Subscription rejected')); common_show_header(_('Subscription rejected'));
// TRANS: Reject message from Authorise subscription page.
$this->element('p', null, $this->element('p', null,
// TRANS: Reject message from Authorise subscription page.
_('The subscription has been rejected, but no '. _('The subscription has been rejected, but no '.
'callback URL was passed. Check with the sites ' . 'callback URL was passed. Check with the site\'s ' .
'instructions for details on how to fully reject ' . 'instructions for details on how to fully reject ' .
'the subscription.')); 'the subscription.'));
common_show_footer(); common_show_footer();

View File

@ -112,7 +112,7 @@ class UserrssAction extends Rss10Action
return ($avatar) ? $avatar->url : null; return ($avatar) ? $avatar->url : null;
} }
# override parent to add X-SUP-ID URL // override parent to add X-SUP-ID URL
function initRss($limit=0) function initRss($limit=0)
{ {

View File

@ -27,7 +27,7 @@ class Avatar extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */ /* the code above is auto generated do not remove the tag below */
###END_AUTOCODE ###END_AUTOCODE
# We clean up the file, too // We clean up the file, too
function delete() function delete()
{ {

View File

@ -79,70 +79,16 @@ class Fave extends Memcached_DataObject
function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $own=false, $since_id=0, $max_id=0) function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $own=false, $since_id=0, $max_id=0)
{ {
$ids = Notice::stream(array('Fave', '_streamDirect'), $stream = new FaveNoticeStream($user_id, $own);
array($user_id, $own),
($own) ? 'fave:ids_by_user_own:'.$user_id : return $stream->getNotices($offset, $limit, $since_id, $max_id);
'fave:ids_by_user:'.$user_id,
$offset, $limit, $since_id, $max_id);
return $ids;
} }
/** function idStream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $own=false, $since_id=0, $max_id=0)
* Note that the sorting for this is by order of *fave* not order of *notice*.
*
* @fixme add since_id, max_id support?
*
* @param <type> $user_id
* @param <type> $own
* @param <type> $offset
* @param <type> $limit
* @param <type> $since_id
* @param <type> $max_id
* @return <type>
*/
function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id)
{ {
$fav = new Fave(); $stream = new FaveNoticeStream($user_id, $own);
$qry = null;
if ($own) { return $stream->getNoticeIds($offset, $limit, $since_id, $max_id);
$qry = 'SELECT fave.* FROM fave ';
$qry .= 'WHERE fave.user_id = ' . $user_id . ' ';
} else {
$qry = 'SELECT fave.* FROM fave ';
$qry .= 'INNER JOIN notice ON fave.notice_id = notice.id ';
$qry .= 'WHERE fave.user_id = ' . $user_id . ' ';
$qry .= 'AND notice.is_local != ' . Notice::GATEWAY . ' ';
}
if ($since_id != 0) {
$qry .= 'AND notice_id > ' . $since_id . ' ';
}
if ($max_id != 0) {
$qry .= 'AND notice_id <= ' . $max_id . ' ';
}
// NOTE: we sort by fave time, not by notice time!
$qry .= 'ORDER BY modified DESC ';
if (!is_null($offset)) {
$qry .= "LIMIT $limit OFFSET $offset";
}
$fav->query($qry);
$ids = array();
while ($fav->fetch()) {
$ids[] = $fav->notice_id;
}
$fav->free();
unset($fav);
return $ids;
} }
function asActivity() function asActivity()

View File

@ -449,52 +449,8 @@ class File extends Memcached_DataObject
function stream($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) function stream($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
{ {
$ids = Notice::stream(array($this, '_streamDirect'), $stream = new FileNoticeStream($this);
array(), return $stream->getNotices($offset, $limit, $since_id, $max_id);
'file:notice-ids:'.$this->url,
$offset, $limit, $since_id, $max_id);
return Notice::getStreamByIds($ids);
}
/**
* Stream of notices linking to this URL
*
* @param integer $offset Offset to show; default is 0
* @param integer $limit Limit of notices to show
* @param integer $since_id Since this notice
* @param integer $max_id Before this notice
*
* @return array ids of notices that link to this file
*/
function _streamDirect($offset, $limit, $since_id, $max_id)
{
$f2p = new File_to_post();
$f2p->selectAdd();
$f2p->selectAdd('post_id');
$f2p->file_id = $this->id;
Notice::addWhereSinceId($f2p, $since_id, 'post_id', 'modified');
Notice::addWhereMaxId($f2p, $max_id, 'post_id', 'modified');
$f2p->orderBy('modified DESC, post_id DESC');
if (!is_null($offset)) {
$f2p->limit($offset, $limit);
}
$ids = array();
if ($f2p->find()) {
while ($f2p->fetch()) {
$ids[] = $f2p->post_id;
}
}
return $ids;
} }
function noticeCount() function noticeCount()

View File

@ -91,7 +91,7 @@ class Foreign_link extends Memcached_DataObject
$this->profilesync = 0; $this->profilesync = 0;
} }
# Convenience methods // Convenience methods
function getForeignUser() function getForeignUser()
{ {
$fuser = new Foreign_user(); $fuser = new Foreign_user();

View File

@ -65,7 +65,7 @@ class Foreign_user extends Memcached_DataObject
} }
} }
if (count($parts) == 0) { if (count($parts) == 0) {
# No changes // No changes
return true; return true;
} }
$toupdate = implode(', ', $parts); $toupdate = implode(', ', $parts);

View File

@ -55,4 +55,80 @@ class Group_join_queue extends Managed_DataObject
$rq->insert(); $rq->insert();
return $rq; return $rq;
} }
function getMember()
{
$member = Profile::staticGet('id', $this->profile_id);
if (empty($member)) {
// TRANS: Exception thrown providing an invalid profile ID.
// TRANS: %s is the invalid profile ID.
throw new Exception(sprintf(_('Profile ID %s is invalid.'),$this->profile_id));
}
return $member;
}
function getGroup()
{
$group = User_group::staticGet('id', $this->group_id);
if (empty($group)) {
// TRANS: Exception thrown providing an invalid group ID.
// TRANS: %s is the invalid group ID.
throw new Exception(sprintf(_('Group ID %s is invalid.'),$this->group_id));
}
return $group;
}
/**
* Abort the pending group join...
*
* @param User_group $group
*/
function abort()
{
$profile = $this->getMember();
$group = $this->getGroup();
if ($request) {
if (Event::handle('StartCancelJoinGroup', array($profile, $group))) {
$this->delete();
Event::handle('EndCancelJoinGroup', array($profile, $group));
}
}
}
/**
* Complete a pending group join...
*
* @return Group_member object on success
*/
function complete()
{
$join = null;
$profile = $this->getMember();
$group = $this->getGroup();
if (Event::handle('StartJoinGroup', array($profile, $group))) {
$join = Group_member::join($group->id, $profile->id);
$this->delete();
Event::handle('EndJoinGroup', array($profile, $group));
}
if (!$join) {
throw new Exception('Internal error: group join failed.');
}
$join->notify();
return $join;
}
/**
* Send notifications via email etc to group administrators about
* this exciting new pending moderation queue item!
*/
public function notify()
{
$joiner = Profile::staticGet('id', $this->profile_id);
$group = User_group::staticGet('id', $this->group_id);
mail_notify_group_join_pending($group, $joiner);
}
} }

View File

@ -162,4 +162,13 @@ class Group_member extends Memcached_DataObject
return $act; return $act;
} }
/**
* Send notifications via email etc to group administrators about
* this exciting new membership!
*/
public function notify()
{
mail_notify_group_join($this->getGroup(), $this->getMember());
}
} }

View File

@ -233,7 +233,7 @@ class Inbox extends Memcached_DataObject
// Do a bulk lookup for the first $limit items // Do a bulk lookup for the first $limit items
// Fast path when nothing's deleted. // Fast path when nothing's deleted.
$firstChunk = array_slice($ids, 0, $offset + $limit); $firstChunk = array_slice($ids, 0, $offset + $limit);
$notices = Notice::getStreamByIds($firstChunk); $notices = NoticeStream::getStreamByIds($firstChunk);
assert($notices instanceof ArrayWrapper); assert($notices instanceof ArrayWrapper);
$items = $notices->_items; $items = $notices->_items;
@ -292,7 +292,7 @@ class Inbox extends Memcached_DataObject
// Do a bulk lookup for the first $limit items // Do a bulk lookup for the first $limit items
// Fast path when nothing's deleted. // Fast path when nothing's deleted.
$firstChunk = array_slice($ids, 0, $limit); $firstChunk = array_slice($ids, 0, $limit);
$notices = Notice::getStreamByIds($firstChunk); $notices = NoticeStream::getStreamByIds($firstChunk);
$wanted = count($firstChunk); // raw entry count in the inbox up to our $limit $wanted = count($firstChunk); // raw entry count in the inbox up to our $limit
if ($notices->N >= $wanted) { if ($notices->N >= $wanted) {

View File

@ -34,7 +34,7 @@ class Memcached_DataObject extends Safe_DataObject
{ {
if (is_null($v)) { if (is_null($v)) {
$v = $k; $v = $k;
# XXX: HACK! // XXX: HACK!
$i = new $cls; $i = new $cls;
$keys = $i->keys(); $keys = $i->keys();
$k = $keys[0]; $k = $keys[0];

View File

@ -45,7 +45,7 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
/* We keep 200 notices, the max number of notices available per API request, /* We keep 200 notices, the max number of notices available per API request,
* in the memcached cache. */ * in the memcached cache. */
define('NOTICE_CACHE_WINDOW', 200); define('NOTICE_CACHE_WINDOW', CachingNoticeStream::CACHE_WINDOW);
define('MAX_BOXCARS', 128); define('MAX_BOXCARS', 128);
@ -73,6 +73,7 @@ class Notice extends Memcached_DataObject
public $location_ns; // int(4) public $location_ns; // int(4)
public $repeat_of; // int(4) public $repeat_of; // int(4)
public $object_type; // varchar(255) public $object_type; // varchar(255)
public $scope; // int(4)
/* Static get */ /* Static get */
function staticGet($k,$v=NULL) function staticGet($k,$v=NULL)
@ -89,6 +90,12 @@ class Notice extends Memcached_DataObject
const LOCAL_NONPUBLIC = -1; const LOCAL_NONPUBLIC = -1;
const GATEWAY = -2; const GATEWAY = -2;
const PUBLIC_SCOPE = 0; // Useful fake constant
const SITE_SCOPE = 1;
const ADDRESSEE_SCOPE = 2;
const GROUP_SCOPE = 4;
const FOLLOWER_SCOPE = 8;
function getProfile() function getProfile()
{ {
$profile = Profile::staticGet('id', $this->profile_id); $profile = Profile::staticGet('id', $this->profile_id);
@ -243,6 +250,7 @@ class Notice extends Memcached_DataObject
* notice in place of extracting links from content * notice in place of extracting links from content
* boolean 'distribute' whether to distribute the notice, default true * boolean 'distribute' whether to distribute the notice, default true
* string 'object_type' URL of the associated object type (default ActivityObject::NOTE) * string 'object_type' URL of the associated object type (default ActivityObject::NOTE)
* int 'scope' Scope bitmask; default to SITE_SCOPE on private sites, 0 otherwise
* *
* @fixme tag override * @fixme tag override
* *
@ -254,6 +262,7 @@ class Notice extends Memcached_DataObject
'url' => null, 'url' => null,
'reply_to' => null, 'reply_to' => null,
'repeat_of' => null, 'repeat_of' => null,
'scope' => null,
'distribute' => true); 'distribute' => true);
if (!empty($options)) { if (!empty($options)) {
@ -312,7 +321,7 @@ class Notice extends Memcached_DataObject
$autosource = common_config('public', 'autosource'); $autosource = common_config('public', 'autosource');
# Sandboxed are non-false, but not 1, either // Sandboxed are non-false, but not 1, either
if (!$profile->hasRight(Right::PUBLICNOTICE) || if (!$profile->hasRight(Right::PUBLICNOTICE) ||
($source && $autosource && in_array($source, $autosource))) { ($source && $autosource && in_array($source, $autosource))) {
@ -336,6 +345,37 @@ class Notice extends Memcached_DataObject
// Handle repeat case // Handle repeat case
if (isset($repeat_of)) { if (isset($repeat_of)) {
// Check for a private one
$repeat = Notice::staticGet('id', $repeat_of);
if (empty($repeat)) {
throw new ClientException(_('Cannot repeat; original notice is missing or deleted.'));
}
if ($profile->id == $repeat->profile_id) {
// TRANS: Client error displayed when trying to repeat an own notice.
throw new ClientException(_('You cannot repeat your own notice.'));
}
if ($repeat->scope != Notice::SITE_SCOPE &&
$repeat->scope != Notice::PUBLIC_SCOPE) {
// TRANS: Client error displayed when trying to repeat a non-public notice.
throw new ClientException(_('Cannot repeat a private notice.'), 403);
}
if (!$repeat->inScope($profile)) {
// The generic checks above should cover this, but let's be sure!
// TRANS: Client error displayed when trying to repeat a notice you cannot access.
throw new ClientException(_('Cannot repeat a notice you cannot read.'), 403);
}
if ($profile->hasRepeated($repeat->id)) {
// TRANS: Client error displayed when trying to repeat an already repeated notice.
throw new ClientException(_('You already repeated that notice.'));
}
$notice->repeat_of = $repeat_of; $notice->repeat_of = $repeat_of;
} else { } else {
$notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final); $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final);
@ -343,6 +383,12 @@ class Notice extends Memcached_DataObject
if (!empty($notice->reply_to)) { if (!empty($notice->reply_to)) {
$reply = Notice::staticGet('id', $notice->reply_to); $reply = Notice::staticGet('id', $notice->reply_to);
if (!$reply->inScope($profile)) {
// TRANS: Client error displayed when trying to reply to a notice a the target has no access to.
// TRANS: %1$s is a user nickname, %2$d is a notice ID (number).
throw new ClientException(sprintf(_('%1$s has no access to notice %2$d.'),
$profile->nickname, $reply->id), 403);
}
$notice->conversation = $reply->conversation; $notice->conversation = $reply->conversation;
} }
@ -368,6 +414,12 @@ class Notice extends Memcached_DataObject
$notice->object_type = $object_type; $notice->object_type = $object_type;
} }
if (is_null($scope)) { // 0 is a valid value
$notice->scope = common_config('notice', 'defaultscope');
} else {
$notice->scope = $scope;
}
if (Event::handle('StartNoticeSave', array(&$notice))) { if (Event::handle('StartNoticeSave', array(&$notice))) {
// XXX: some of these functions write to the DB // XXX: some of these functions write to the DB
@ -410,8 +462,8 @@ class Notice extends Memcached_DataObject
} }
# Clear the cache for subscribed users, so they'll update at next request // Clear the cache for subscribed users, so they'll update at next request
# XXX: someone clever could prepend instead of clearing the cache // XXX: someone clever could prepend instead of clearing the cache
$notice->blowOnInsert(); $notice->blowOnInsert();
@ -554,7 +606,7 @@ class Notice extends Memcached_DataObject
if (empty($profile)) { if (empty($profile)) {
return false; return false;
} }
$notice = $profile->getNotices(0, NOTICE_CACHE_WINDOW); $notice = $profile->getNotices(0, CachingNoticeStream::CACHE_WINDOW);
if (!empty($notice)) { if (!empty($notice)) {
$last = 0; $last = 0;
while ($notice->fetch()) { while ($notice->fetch()) {
@ -565,8 +617,8 @@ class Notice extends Memcached_DataObject
} }
} }
} }
# If we get here, oldest item in cache window is not // If we get here, oldest item in cache window is not
# old enough for dupe limit; do direct check against DB // old enough for dupe limit; do direct check against DB
$notice = new Notice(); $notice = new Notice();
$notice->profile_id = $profile_id; $notice->profile_id = $profile_id;
$notice->content = $content; $notice->content = $content;
@ -582,16 +634,16 @@ class Notice extends Memcached_DataObject
if (empty($profile)) { if (empty($profile)) {
return false; return false;
} }
# Get the Nth notice // Get the Nth notice
$notice = $profile->getNotices(common_config('throttle', 'count') - 1, 1); $notice = $profile->getNotices(common_config('throttle', 'count') - 1, 1);
if ($notice && $notice->fetch()) { if ($notice && $notice->fetch()) {
# If the Nth notice was posted less than timespan seconds ago // If the Nth notice was posted less than timespan seconds ago
if (time() - strtotime($notice->created) <= common_config('throttle', 'timespan')) { if (time() - strtotime($notice->created) <= common_config('throttle', 'timespan')) {
# Then we throttle // Then we throttle
return false; return false;
} }
} }
# Either not N notices in the stream, OR the Nth was not posted within timespan seconds // Either not N notices in the stream, OR the Nth was not posted within timespan seconds
return true; return true;
} }
@ -635,134 +687,19 @@ class Notice extends Memcached_DataObject
return $att; return $att;
} }
function getStreamByIds($ids)
{
$cache = Cache::instance();
if (!empty($cache)) {
$notices = array();
foreach ($ids as $id) {
$n = Notice::staticGet('id', $id);
if (!empty($n)) {
$notices[] = $n;
}
}
return new ArrayWrapper($notices);
} else {
$notice = new Notice();
if (empty($ids)) {
//if no IDs requested, just return the notice object
return $notice;
}
$notice->whereAdd('id in (' . implode(', ', $ids) . ')');
$notice->find();
$temp = array();
while ($notice->fetch()) {
$temp[$notice->id] = clone($notice);
}
$wrapped = array();
foreach ($ids as $id) {
if (array_key_exists($id, $temp)) {
$wrapped[] = $temp[$id];
}
}
return new ArrayWrapper($wrapped);
}
}
function publicStream($offset=0, $limit=20, $since_id=0, $max_id=0) function publicStream($offset=0, $limit=20, $since_id=0, $max_id=0)
{ {
$ids = Notice::stream(array('Notice', '_publicStreamDirect'), $stream = new PublicNoticeStream();
array(), return $stream->getNotices($offset, $limit, $since_id, $max_id);
'public',
$offset, $limit, $since_id, $max_id);
return Notice::getStreamByIds($ids);
} }
function _publicStreamDirect($offset=0, $limit=20, $since_id=0, $max_id=0)
{
$notice = new Notice();
$notice->selectAdd(); // clears it
$notice->selectAdd('id');
$notice->orderBy('created DESC, id DESC');
if (!is_null($offset)) {
$notice->limit($offset, $limit);
}
if (common_config('public', 'localonly')) {
$notice->whereAdd('is_local = ' . Notice::LOCAL_PUBLIC);
} else {
# -1 == blacklisted, -2 == gateway (i.e. Twitter)
$notice->whereAdd('is_local !='. Notice::LOCAL_NONPUBLIC);
$notice->whereAdd('is_local !='. Notice::GATEWAY);
}
Notice::addWhereSinceId($notice, $since_id);
Notice::addWhereMaxId($notice, $max_id);
$ids = array();
if ($notice->find()) {
while ($notice->fetch()) {
$ids[] = $notice->id;
}
}
$notice->free();
$notice = NULL;
return $ids;
}
function conversationStream($id, $offset=0, $limit=20, $since_id=0, $max_id=0) function conversationStream($id, $offset=0, $limit=20, $since_id=0, $max_id=0)
{ {
$ids = Notice::stream(array('Notice', '_conversationStreamDirect'), $stream = new ConversationNoticeStream($id);
array($id),
'notice:conversation_ids:'.$id,
$offset, $limit, $since_id, $max_id);
return Notice::getStreamByIds($ids); return $stream->getNotices($offset, $limit, $since_id, $max_id);
}
function _conversationStreamDirect($id, $offset=0, $limit=20, $since_id=0, $max_id=0)
{
$notice = new Notice();
$notice->selectAdd(); // clears it
$notice->selectAdd('id');
$notice->conversation = $id;
$notice->orderBy('created DESC, id DESC');
if (!is_null($offset)) {
$notice->limit($offset, $limit);
}
Notice::addWhereSinceId($notice, $since_id);
Notice::addWhereMaxId($notice, $max_id);
$ids = array();
if ($notice->find()) {
while ($notice->fetch()) {
$ids[] = $notice->id;
}
}
$notice->free();
$notice = NULL;
return $ids;
} }
/** /**
@ -1655,61 +1592,6 @@ class Notice extends Memcached_DataObject
} }
} }
function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0)
{
$cache = Cache::instance();
if (empty($cache) ||
$since_id != 0 || $max_id != 0 ||
is_null($limit) ||
($offset + $limit) > NOTICE_CACHE_WINDOW) {
return call_user_func_array($fn, array_merge($args, array($offset, $limit, $since_id,
$max_id)));
}
$idkey = Cache::key($cachekey);
$idstr = $cache->get($idkey);
if ($idstr !== false) {
// Cache hit! Woohoo!
$window = explode(',', $idstr);
$ids = array_slice($window, $offset, $limit);
return $ids;
}
$laststr = $cache->get($idkey.';last');
if ($laststr !== false) {
$window = explode(',', $laststr);
$last_id = $window[0];
$new_ids = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW,
$last_id, 0, null)));
$new_window = array_merge($new_ids, $window);
$new_windowstr = implode(',', $new_window);
$result = $cache->set($idkey, $new_windowstr);
$result = $cache->set($idkey . ';last', $new_windowstr);
$ids = array_slice($new_window, $offset, $limit);
return $ids;
}
$window = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW,
0, 0, null)));
$windowstr = implode(',', $window);
$result = $cache->set($idkey, $windowstr);
$result = $cache->set($idkey . ';last', $windowstr);
$ids = array_slice($window, $offset, $limit);
return $ids;
}
/** /**
* Determine which notice, if any, a new notice is in reply to. * Determine which notice, if any, a new notice is in reply to.
@ -1820,6 +1702,15 @@ class Notice extends Memcached_DataObject
return $location; return $location;
} }
/**
* Convenience function for posting a repeat of an existing message.
*
* @param int $repeater_id: profile ID of user doing the repeat
* @param string $source: posting source key, eg 'web', 'api', etc
* @return Notice
*
* @throws Exception on failure or permission problems
*/
function repeat($repeater_id, $source) function repeat($repeater_id, $source)
{ {
$author = Profile::staticGet('id', $this->profile_id); $author = Profile::staticGet('id', $this->profile_id);
@ -1841,8 +1732,13 @@ class Notice extends Memcached_DataObject
$content = mb_substr($content, 0, $maxlen - 4) . ' ...'; $content = mb_substr($content, 0, $maxlen - 4) . ' ...';
} }
return self::saveNew($repeater_id, $content, $source, // Scope is same as this one's
array('repeat_of' => $this->id));
return self::saveNew($repeater_id,
$content,
$source,
array('repeat_of' => $this->id,
'scope' => $this->scope));
} }
// These are supposed to be in chron order! // These are supposed to be in chron order!
@ -1867,7 +1763,7 @@ class Notice extends Memcached_DataObject
} }
} }
return Notice::getStreamByIds($ids); return NoticeStream::getStreamByIds($ids);
} }
function _repeatStreamDirect($limit) function _repeatStreamDirect($limit)
@ -2296,4 +2192,94 @@ class Notice extends Memcached_DataObject
($this->is_local != Notice::GATEWAY)); ($this->is_local != Notice::GATEWAY));
} }
} }
/**
* Check that the given profile is allowed to read, respond to, or otherwise
* act on this notice.
*
* The $scope member is a bitmask of scopes, representing a logical AND of the
* scope requirement. So, 0x03 (Notice::ADDRESSEE_SCOPE | Notice::SITE_SCOPE) means
* "only visible to people who are mentioned in the notice AND are users on this site."
* Users on the site who are not mentioned in the notice will not be able to see the
* notice.
*
* @param Profile $profile The profile to check; pass null to check for public/unauthenticated users.
*
* @return boolean whether the profile is in the notice's scope
*/
function inScope($profile)
{
// If there's no scope, anyone (even anon) is in scope.
if ($this->scope == 0) {
return true;
}
// If there's scope, anon cannot be in scope
if (empty($profile)) {
return false;
}
// Author is always in scope
if ($this->profile_id == $profile->id) {
return true;
}
// Only for users on this site
if ($this->scope & Notice::SITE_SCOPE) {
$user = $profile->getUser();
if (empty($user)) {
return false;
}
}
// Only for users mentioned in the notice
if ($this->scope & Notice::ADDRESSEE_SCOPE) {
// XXX: just query for the single reply
$replies = $this->getReplies();
if (!in_array($profile->id, $replies)) {
return false;
}
}
// Only for members of the given group
if ($this->scope & Notice::GROUP_SCOPE) {
// XXX: just query for the single membership
$groups = $this->getGroups();
$foundOne = false;
foreach ($groups as $group) {
if ($profile->isMember($group)) {
$foundOne = true;
break;
}
}
if (!$foundOne) {
return false;
}
}
// Only for followers of the author
if ($this->scope & Notice::FOLLOWER_SCOPE) {
$author = $this->getProfile();
if (!Subscription::exists($profile, $author)) {
return false;
}
}
return true;
}
} }

View File

@ -36,43 +36,11 @@ class Notice_tag extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */ /* the code above is auto generated do not remove the tag below */
###END_AUTOCODE ###END_AUTOCODE
static function getStream($tag, $offset=0, $limit=20) { static function getStream($tag, $offset=0, $limit=20, $sinceId=0, $maxId=0)
$ids = Notice::stream(array('Notice_tag', '_streamDirect'),
array($tag),
'notice_tag:notice_ids:' . Cache::keyize($tag),
$offset, $limit);
return Notice::getStreamByIds($ids);
}
function _streamDirect($tag, $offset, $limit, $since_id, $max_id)
{ {
$nt = new Notice_tag(); $stream = new TagNoticeStream($tag);
$nt->tag = $tag; return $stream->getNotices($offset, $limit, $sinceId, $maxId);
$nt->selectAdd();
$nt->selectAdd('notice_id');
Notice::addWhereSinceId($nt, $since_id, 'notice_id');
Notice::addWhereMaxId($nt, $max_id, 'notice_id');
$nt->orderBy('created DESC, notice_id DESC');
if (!is_null($offset)) {
$nt->limit($offset, $limit);
}
$ids = array();
if ($nt->find()) {
while ($nt->fetch()) {
$ids[] = $nt->notice_id;
}
}
return $ids;
} }
function blowCache($blowLast=false) function blowCache($blowLast=false)

View File

@ -51,7 +51,7 @@ class Oauth_application_user extends Memcached_DataObject
} }
} }
if (count($parts) == 0) { if (count($parts) == 0) {
# No changes // No changes
return true; return true;
} }
$toupdate = implode(', ', $parts); $toupdate = implode(', ', $parts);

View File

@ -93,7 +93,7 @@ class Profile extends Memcached_DataObject
$avatar->url = Avatar::url($filename); $avatar->url = Avatar::url($filename);
$avatar->created = DB_DataObject_Cast::dateTime(); # current time $avatar->created = DB_DataObject_Cast::dateTime(); # current time
# XXX: start a transaction here // XXX: start a transaction here
if (!$this->delete_avatars() || !$avatar->insert()) { if (!$this->delete_avatars() || !$avatar->insert()) {
@unlink(Avatar::path($filename)); @unlink(Avatar::path($filename));
@ -101,7 +101,7 @@ class Profile extends Memcached_DataObject
} }
foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) { foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) {
# We don't do a scaled one if original is our scaled size // We don't do a scaled one if original is our scaled size
if (!($avatar->width == $size && $avatar->height == $size)) { if (!($avatar->width == $size && $avatar->height == $size)) {
$scaled_filename = $imagefile->resize($size); $scaled_filename = $imagefile->resize($size);
@ -168,7 +168,7 @@ class Profile extends Memcached_DataObject
function getFancyName() function getFancyName()
{ {
if ($this->fullname) { if ($this->fullname) {
// TRANS: Full name of a profile or group followed by nickname in parens // TRANS: Full name of a profile or group (%1$s) followed by nickname (%2$s) in parentheses.
return sprintf(_m('FANCYNAME','%1$s (%2$s)'), $this->fullname, $this->nickname); return sprintf(_m('FANCYNAME','%1$s (%2$s)'), $this->fullname, $this->nickname);
} else { } else {
return $this->nickname; return $this->nickname;
@ -180,7 +180,6 @@ class Profile extends Memcached_DataObject
* *
* @return mixed Notice or null * @return mixed Notice or null
*/ */
function getCurrentNotice() function getCurrentNotice()
{ {
$notice = $this->getNotices(0, 1); $notice = $this->getNotices(0, 1);
@ -198,90 +197,16 @@ class Profile extends Memcached_DataObject
function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
{ {
$ids = Notice::stream(array($this, '_streamTaggedDirect'), $stream = new TaggedProfileNoticeStream($this, $tag);
array($tag),
'profile:notice_ids_tagged:' . $this->id . ':' . $tag, return $stream->getNotices($offset, $limit, $since_id, $max_id);
$offset, $limit, $since_id, $max_id);
return Notice::getStreamByIds($ids);
} }
function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
{ {
// XXX: I'm not sure this is going to be any faster. It probably isn't. $stream = new ProfileNoticeStream($this);
$ids = Notice::stream(array($this, '_streamDirect'),
array(),
'profile:notice_ids:' . $this->id,
$offset, $limit, $since_id, $max_id);
return Notice::getStreamByIds($ids); return $stream->getNotices($offset, $limit, $since_id, $max_id);
}
function _streamTaggedDirect($tag, $offset, $limit, $since_id, $max_id)
{
// XXX It would be nice to do this without a join
// (necessary to do it efficiently on accounts with long history)
$notice = new Notice();
$query =
"select id from notice join notice_tag on id=notice_id where tag='".
$notice->escape($tag) .
"' and profile_id=" . intval($this->id);
$since = Notice::whereSinceId($since_id, 'id', 'notice.created');
if ($since) {
$query .= " and ($since)";
}
$max = Notice::whereMaxId($max_id, 'id', 'notice.created');
if ($max) {
$query .= " and ($max)";
}
$query .= ' order by notice.created DESC, id DESC';
if (!is_null($offset)) {
$query .= " LIMIT " . intval($limit) . " OFFSET " . intval($offset);
}
$notice->query($query);
$ids = array();
while ($notice->fetch()) {
$ids[] = $notice->id;
}
return $ids;
}
function _streamDirect($offset, $limit, $since_id, $max_id)
{
$notice = new Notice();
$notice->profile_id = $this->id;
$notice->selectAdd();
$notice->selectAdd('id');
Notice::addWhereSinceId($notice, $since_id);
Notice::addWhereMaxId($notice, $max_id);
$notice->orderBy('created DESC, id DESC');
if (!is_null($offset)) {
$notice->limit($offset, $limit);
}
$notice->find();
$ids = array();
while ($notice->fetch()) {
$ids[] = $notice->id;
}
return $ids;
} }
function isMember($group) function isMember($group)
@ -532,55 +457,20 @@ class Profile extends Memcached_DataObject
*/ */
function joinGroup(User_group $group) function joinGroup(User_group $group)
{ {
$ok = null; $join = null;
if ($group->join_policy == User_group::JOIN_POLICY_MODERATE) { if ($group->join_policy == User_group::JOIN_POLICY_MODERATE) {
$ok = Group_join_queue::saveNew($this, $group); $join = Group_join_queue::saveNew($this, $group);
} else { } else {
if (Event::handle('StartJoinGroup', array($group, $this))) { if (Event::handle('StartJoinGroup', array($group, $this))) {
$ok = Group_member::join($group->id, $this->id); $join = Group_member::join($group->id, $this->id);
Event::handle('EndJoinGroup', array($group, $this)); Event::handle('EndJoinGroup', array($group, $this));
} }
} }
return $ok; if ($join) {
} // Send any applicable notifications...
$join->notify();
/**
* Cancel a pending group join...
*
* @param User_group $group
*/
function cancelJoinGroup(User_group $group)
{
$request = Group_join_queue::pkeyGet(array('profile_id' => $this->id,
'group_id' => $group->id));
if ($request) {
if (Event::handle('StartCancelJoinGroup', array($group, $this))) {
$request->delete();
Event::handle('EndCancelJoinGroup', array($group, $this));
}
} }
} return $join;
/**
* Complete a pending group join on our end...
*
* @param User_group $group
*/
function completeJoinGroup(User_group $group)
{
$ok = null;
$request = Group_join_queue::pkeyGet(array('profile_id' => $this->id,
'group_id' => $group->id));
if ($request) {
if (Event::handle('StartJoinGroup', array($group, $this))) {
$ok = Group_member::join($group->id, $this->id);
$request->delete();
Event::handle('EndJoinGroup', array($group, $this));
}
} else {
throw new Exception(_m('Invalid group join approval: not pending.'));
}
return $ok;
} }
/** /**
@ -642,7 +532,6 @@ class Profile extends Memcached_DataObject
return new ArrayWrapper($profiles); return new ArrayWrapper($profiles);
} }
function getTaggedSubscribers($tag) function getTaggedSubscribers($tag)
{ {
$qry = $qry =
@ -668,6 +557,36 @@ class Profile extends Memcached_DataObject
return $tagged; return $tagged;
} }
/**
* Get pending subscribers, who have not yet been approved.
*
* @param int $offset
* @param int $limit
* @return Profile
*/
function getRequests($offset=0, $limit=null)
{
$qry =
'SELECT profile.* ' .
'FROM profile JOIN subscription_queue '.
'ON profile.id = subscription_queue.subscriber ' .
'WHERE subscription_queue.subscribed = %d ' .
'ORDER BY subscription_queue.created DESC ';
if ($limit != null) {
if (common_config('db','type') == 'pgsql') {
$qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
} else {
$qry .= ' LIMIT ' . $offset . ', ' . $limit;
}
}
$members = new Profile();
$members->query(sprintf($qry, $this->id));
return $members;
}
function subscriptionCount() function subscriptionCount()
{ {
$c = Cache::instance(); $c = Cache::instance();
@ -726,6 +645,17 @@ class Profile extends Memcached_DataObject
return Subscription::exists($this, $other); return Subscription::exists($this, $other);
} }
/**
* Check if a pending subscription request is outstanding for this...
*
* @param Profile $other
* @return boolean
*/
function hasPendingSubscription($other)
{
return Subscription_queue::exists($this, $other);
}
/** /**
* Are these two profiles subscribed to each other? * Are these two profiles subscribed to each other?
* *
@ -748,7 +678,7 @@ class Profile extends Memcached_DataObject
// This is the stream of favorite notices, in rev chron // This is the stream of favorite notices, in rev chron
// order. This forces it into cache. // order. This forces it into cache.
$ids = Fave::stream($this->id, 0, NOTICE_CACHE_WINDOW); $ids = Fave::idStream($this->id, 0, CachingNoticeStream::CACHE_WINDOW);
// If it's in the list, then it's a fave // If it's in the list, then it's a fave
@ -760,7 +690,7 @@ class Profile extends Memcached_DataObject
// then the cache has all available faves, so this one // then the cache has all available faves, so this one
// is not a fave. // is not a fave.
if (count($ids) < NOTICE_CACHE_WINDOW) { if (count($ids) < CachingNoticeStream::CACHE_WINDOW) {
return false; return false;
} }
@ -1359,4 +1289,44 @@ class Profile extends Memcached_DataObject
return $profile; return $profile;
} }
function canRead(Notice $notice)
{
if ($notice->scope & Notice::SITE_SCOPE) {
$user = $this->getUser();
if (empty($user)) {
return false;
}
}
if ($notice->scope & Notice::ADDRESSEE_SCOPE) {
$replies = $notice->getReplies();
if (!in_array($this->id, $replies)) {
$groups = $notice->getGroups();
$foundOne = false;
foreach ($groups as $group) {
if ($this->isMember($group)) {
$foundOne = true;
break;
}
}
if (!$foundOne) {
return false;
}
}
}
if ($notice->scope & Notice::FOLLOWER_SCOPE) {
$author = $notice->getProfile();
if (!Subscription::exists($this, $author)) {
return false;
}
}
return true;
}
} }

View File

@ -106,11 +106,11 @@ class Profile_tag extends Memcached_DataObject
$ptag = new Profile_tag(); $ptag = new Profile_tag();
# Delete stuff that's in old and not in new // Delete stuff that's in old and not in new
$to_delete = array_diff($oldtags, $newtags); $to_delete = array_diff($oldtags, $newtags);
# Insert stuff that's in new and not in old // Insert stuff that's in new and not in old
$to_insert = array_diff($newtags, $oldtags); $to_insert = array_diff($newtags, $oldtags);
@ -271,4 +271,19 @@ class Profile_tag extends Memcached_DataObject
} }
return true; return true;
} }
// Return profiles with a given tag
static function getTagged($tagger, $tag) {
$profile = new Profile();
$profile->query('SELECT profile.* ' .
'FROM profile JOIN profile_tag ' .
'ON profile.id = profile_tag.tagged ' .
'WHERE profile_tag.tagger = ' . $tagger . ' ' .
'AND profile_tag.tag = "' . $tag . '" ');
$tagged = array();
while ($profile->fetch()) {
$tagged[] = clone($profile);
}
return true;
}
} }

View File

@ -46,9 +46,9 @@ class Queue_item extends Memcached_DataObject
$cnt = $qi->find(true); $cnt = $qi->find(true);
if ($cnt) { if ($cnt) {
# XXX: potential race condition // XXX: potential race condition
# can we force it to only update if claimed is still null // can we force it to only update if claimed is still null
# (or old)? // (or old)?
common_log(LOG_INFO, 'claiming queue item id = ' . $qi->id . common_log(LOG_INFO, 'claiming queue item id = ' . $qi->id .
' for transport ' . $qi->transport); ' for transport ' . $qi->transport);
$orig = clone($qi); $orig = clone($qi);

View File

@ -38,35 +38,8 @@ class Reply extends Memcached_DataObject
function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
{ {
$ids = Notice::stream(array('Reply', '_streamDirect'), $stream = new ReplyNoticeStream($user_id);
array($user_id),
'reply:stream:' . $user_id,
$offset, $limit, $since_id, $max_id);
return $ids;
}
function _streamDirect($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) return $stream->getNotices($offset, $limit, $since_id, $max_id);
{
$reply = new Reply();
$reply->profile_id = $user_id;
Notice::addWhereSinceId($reply, $since_id, 'notice_id', 'modified');
Notice::addWhereMaxId($reply, $max_id, 'notice_id', 'modified');
$reply->orderBy('modified DESC, notice_id DESC');
if (!is_null($offset)) {
$reply->limit($offset, $limit);
}
$ids = array();
if ($reply->find()) {
while ($reply->fetch()) {
$ids[] = $reply->notice_id;
}
}
return $ids;
} }
} }

View File

@ -27,6 +27,7 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Subscription extends Memcached_DataObject class Subscription extends Memcached_DataObject
{ {
const CACHE_WINDOW = 201; const CACHE_WINDOW = 201;
const FORCE = true;
###START_AUTOCODE ###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */ /* the code below is auto generated do not remove the above tag */
@ -58,11 +59,12 @@ class Subscription extends Memcached_DataObject
* *
* @param Profile $subscriber party to receive new notices * @param Profile $subscriber party to receive new notices
* @param Profile $other party sending notices; publisher * @param Profile $other party sending notices; publisher
* @param bool $force pass Subscription::FORCE to override local subscription approval
* *
* @return Subscription new subscription * @return mixed Subscription or Subscription_queue: new subscription info
*/ */
static function start($subscriber, $other) static function start($subscriber, $other, $force=false)
{ {
// @fixme should we enforce this as profiles in callers instead? // @fixme should we enforce this as profiles in callers instead?
if ($subscriber instanceof User) { if ($subscriber instanceof User) {
@ -88,35 +90,39 @@ class Subscription extends Memcached_DataObject
} }
if (Event::handle('StartSubscribe', array($subscriber, $other))) { if (Event::handle('StartSubscribe', array($subscriber, $other))) {
$sub = self::saveNew($subscriber->id, $other->id);
$sub->notify();
self::blow('user:notices_with_friends:%d', $subscriber->id);
self::blow('subscription:by-subscriber:'.$subscriber->id);
self::blow('subscription:by-subscribed:'.$other->id);
$subscriber->blowSubscriptionCount();
$other->blowSubscriberCount();
$otherUser = User::staticGet('id', $other->id); $otherUser = User::staticGet('id', $other->id);
if ($otherUser && $otherUser->subscribe_policy == User::SUBSCRIBE_POLICY_MODERATE && !$force) {
$sub = Subscription_queue::saveNew($subscriber, $other);
$sub->notify();
} else {
$sub = self::saveNew($subscriber->id, $other->id);
$sub->notify();
if (!empty($otherUser) && self::blow('user:notices_with_friends:%d', $subscriber->id);
$otherUser->autosubscribe &&
!self::exists($other, $subscriber) &&
!$subscriber->hasBlocked($other)) {
try { self::blow('subscription:by-subscriber:'.$subscriber->id);
self::start($other, $subscriber); self::blow('subscription:by-subscribed:'.$other->id);
} catch (Exception $e) {
common_log(LOG_ERR, "Exception during autosubscribe of {$other->nickname} to profile {$subscriber->id}: {$e->getMessage()}"); $subscriber->blowSubscriptionCount();
$other->blowSubscriberCount();
if (!empty($otherUser) &&
$otherUser->autosubscribe &&
!self::exists($other, $subscriber) &&
!$subscriber->hasBlocked($other)) {
try {
self::start($other, $subscriber);
} catch (Exception $e) {
common_log(LOG_ERR, "Exception during autosubscribe of {$other->nickname} to profile {$subscriber->id}: {$e->getMessage()}");
}
} }
} }
Event::handle('EndSubscribe', array($subscriber, $other)); Event::handle('EndSubscribe', array($subscriber, $other));
} }
return true; return $sub;
} }
/** /**
@ -146,9 +152,9 @@ class Subscription extends Memcached_DataObject
function notify() function notify()
{ {
# XXX: add other notifications (Jabber, SMS) here // XXX: add other notifications (Jabber, SMS) here
# XXX: queue this and handle it offline // XXX: queue this and handle it offline
# XXX: Whatever happens, do it in Twitter-like API, too // XXX: Whatever happens, do it in Twitter-like API, too
$this->notifyEmail(); $this->notifyEmail();
} }
@ -261,8 +267,8 @@ class Subscription extends Memcached_DataObject
common_date_iso8601($this->created)); common_date_iso8601($this->created));
$act->time = strtotime($this->created); $act->time = strtotime($this->created);
// TRANS: Activity tile when subscribing to another person. // TRANS: Activity title when subscribing to another person.
$act->title = _("Follow"); $act->title = _m('TITLE','Follow');
// TRANS: Notification given when one person starts following another. // TRANS: Notification given when one person starts following another.
// TRANS: %1$s is the subscriber, %2$s is the subscribed. // TRANS: %1$s is the subscriber, %2$s is the subscribed.
$act->content = sprintf(_('%1$s is now following %2$s.'), $act->content = sprintf(_('%1$s is now following %2$s.'),
@ -295,7 +301,6 @@ class Subscription extends Memcached_DataObject
* *
* @return Subscription stream of subscriptions; use fetch() to iterate * @return Subscription stream of subscriptions; use fetch() to iterate
*/ */
static function bySubscriber($subscriberId, static function bySubscriber($subscriberId,
$offset = 0, $offset = 0,
$limit = PROFILES_PER_PAGE) $limit = PROFILES_PER_PAGE)
@ -356,7 +361,6 @@ class Subscription extends Memcached_DataObject
* *
* @return Subscription stream of subscriptions; use fetch() to iterate * @return Subscription stream of subscriptions; use fetch() to iterate
*/ */
static function bySubscribed($subscribedId, static function bySubscribed($subscribedId,
$offset = 0, $offset = 0,
$limit = PROFILES_PER_PAGE) $limit = PROFILES_PER_PAGE)
@ -414,7 +418,6 @@ class Subscription extends Memcached_DataObject
* *
* @return boolean success flag. * @return boolean success flag.
*/ */
function update($orig=null) function update($orig=null)
{ {
$result = parent::update($orig); $result = parent::update($orig);

View File

@ -0,0 +1,105 @@
<?php
/**
* Table Definition for subscription_queue
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Subscription_queue extends Managed_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'subscription_queue'; // table name
public $subscriber;
public $subscribed;
public $created;
/* Static get */
function staticGet($k,$v=null)
{ return Memcached_DataObject::staticGet('Subscription_queue',$k,$v); }
/* Pkey get */
function pkeyGet($k)
{ return Memcached_DataObject::pkeyGet('Subscription_queue',$k); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
public static function schemaDef()
{
return array(
'description' => 'Holder for subscription requests awaiting moderation.',
'fields' => array(
'subscriber' => array('type' => 'int', 'not null' => true, 'description' => 'remote or local profile making the request'),
'subscribed' => array('type' => 'int', 'not null' => true, 'description' => 'remote or local profile being subscribed to'),
'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
),
'primary key' => array('subscriber', 'subscribed'),
'indexes' => array(
'subscription_queue_subscriber_created_idx' => array('subscriber', 'created'),
'subscription_queue_subscribed_created_idx' => array('subscribed', 'created'),
),
'foreign keys' => array(
'subscription_queue_subscriber_fkey' => array('profile', array('subscriber' => 'id')),
'subscription_queue_subscribed_fkey' => array('profile', array('subscribed' => 'id')),
)
);
}
public static function saveNew(Profile $subscriber, Profile $subscribed)
{
$rq = new Subscription_queue();
$rq->subscriber = $subscriber->id;
$rq->subscribed = $subscribed->id;
$rq->created = common_sql_now();
$rq->insert();
return $rq;
}
function exists($subscriber, $other)
{
$sub = Subscription_queue::pkeyGet(array('subscriber' => $subscriber->id,
'subscribed' => $other->id));
return (empty($sub)) ? false : true;
}
/**
* Complete a pending subscription, as we've got approval of some sort.
*
* @return Subscription
*/
public function complete()
{
$subscriber = Profile::staticGet('id', $this->subscriber);
$subscribed = Profile::staticGet('id', $this->subscribed);
$sub = Subscription::start($subscriber, $subscribed, Subscription::FORCE);
if ($sub) {
$this->delete();
}
return $sub;
}
/**
* Cancel an outstanding subscription request to the other profile.
*/
public function abort()
{
$subscriber = Profile::staticGet('id', $this->subscriber);
$subscribed = Profile::staticGet('id', $this->subscribed);
if (Event::handle('StartCancelSubscription', array($subscriber, $subscribed))) {
$this->delete();
Event::handle('EndCancelSubscription', array($subscriber, $subscribed));
}
}
/**
* Send notifications via email etc to group administrators about
* this exciting new pending moderation queue item!
*/
public function notify()
{
$other = Profile::staticGet('id', $this->subscriber);
$listenee = User::staticGet('id', $this->subscribed);
mail_subscribe_pending_notify_profile($listenee, $other);
}
}

View File

@ -30,6 +30,9 @@ require_once 'Validate.php';
class User extends Memcached_DataObject class User extends Memcached_DataObject
{ {
const SUBSCRIBE_POLICY_OPEN = 0;
const SUBSCRIBE_POLICY_MODERATE = 1;
###START_AUTOCODE ###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */ /* the code below is auto generated do not remove the above tag */
@ -55,6 +58,7 @@ class User extends Memcached_DataObject
public $smsemail; // varchar(255) public $smsemail; // varchar(255)
public $uri; // varchar(255) unique_key public $uri; // varchar(255) unique_key
public $autosubscribe; // tinyint(1) public $autosubscribe; // tinyint(1)
public $subscribe_policy; // tinyint(1)
public $urlshorteningservice; // varchar(50) default_ur1.ca public $urlshorteningservice; // varchar(50) default_ur1.ca
public $inboxed; // tinyint(1) public $inboxed; // tinyint(1)
public $design_id; // int(4) public $design_id; // int(4)
@ -86,6 +90,12 @@ class User extends Memcached_DataObject
return $profile->isSubscribed($other); return $profile->isSubscribed($other);
} }
function hasPendingSubscription($other)
{
$profile = $this->getProfile();
return $profile->hasPendingSubscription($other);
}
// 'update' won't write key columns, so we have to do it ourselves. // 'update' won't write key columns, so we have to do it ourselves.
function updateKeys(&$orig) function updateKeys(&$orig)
@ -448,8 +458,7 @@ class User extends Memcached_DataObject
function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
{ {
$ids = Reply::stream($this->id, $offset, $limit, $since_id, $before_id); return Reply::stream($this->id, $offset, $limit, $since_id, $before_id);
return Notice::getStreamByIds($ids);
} }
function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) { function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) {
@ -465,8 +474,7 @@ class User extends Memcached_DataObject
function favoriteNotices($own=false, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) function favoriteNotices($own=false, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
{ {
$ids = Fave::stream($this->id, $offset, $limit, $own, $since_id, $max_id); return Fave::stream($this->id, $offset, $limit, $own, $since_id, $max_id);
return Notice::getStreamByIds($ids);
} }
function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
@ -769,95 +777,18 @@ class User extends Memcached_DataObject
function repeatedByMe($offset=0, $limit=20, $since_id=null, $max_id=null) function repeatedByMe($offset=0, $limit=20, $since_id=null, $max_id=null)
{ {
$ids = Notice::stream(array($this, '_repeatedByMeDirect'), $stream = new RepeatedByMeNoticeStream($this);
array(), return $stream->getNotices($offset, $limit, $since_id, $max_id);
'user:repeated_by_me:'.$this->id,
$offset, $limit, $since_id, $max_id, null);
return Notice::getStreamByIds($ids);
} }
function _repeatedByMeDirect($offset, $limit, $since_id, $max_id)
{
$notice = new Notice();
$notice->selectAdd(); // clears it
$notice->selectAdd('id');
$notice->profile_id = $this->id;
$notice->whereAdd('repeat_of IS NOT NULL');
$notice->orderBy('created DESC, id DESC');
if (!is_null($offset)) {
$notice->limit($offset, $limit);
}
Notice::addWhereSinceId($notice, $since_id);
Notice::addWhereMaxId($notice, $max_id);
$ids = array();
if ($notice->find()) {
while ($notice->fetch()) {
$ids[] = $notice->id;
}
}
$notice->free();
$notice = NULL;
return $ids;
}
function repeatsOfMe($offset=0, $limit=20, $since_id=null, $max_id=null) function repeatsOfMe($offset=0, $limit=20, $since_id=null, $max_id=null)
{ {
$ids = Notice::stream(array($this, '_repeatsOfMeDirect'), $stream = new RepeatsOfMeNoticeStream($this);
array(),
'user:repeats_of_me:'.$this->id,
$offset, $limit, $since_id, $max_id);
return Notice::getStreamByIds($ids); return $stream->getNotices($offset, $limit, $since_id, $max_id);
} }
function _repeatsOfMeDirect($offset, $limit, $since_id, $max_id)
{
$qry =
'SELECT DISTINCT original.id AS id ' .
'FROM notice original JOIN notice rept ON original.id = rept.repeat_of ' .
'WHERE original.profile_id = ' . $this->id . ' ';
$since = Notice::whereSinceId($since_id, 'original.id', 'original.created');
if ($since) {
$qry .= "AND ($since) ";
}
$max = Notice::whereMaxId($max_id, 'original.id', 'original.created');
if ($max) {
$qry .= "AND ($max) ";
}
$qry .= 'ORDER BY original.created, original.id DESC ';
if (!is_null($offset)) {
$qry .= "LIMIT $limit OFFSET $offset";
}
$ids = array();
$notice = new Notice();
$notice->query($qry);
while ($notice->fetch()) {
$ids[] = $notice->id;
}
$notice->free();
$notice = NULL;
return $ids;
}
function repeatedToMe($offset=0, $limit=20, $since_id=null, $max_id=null) function repeatedToMe($offset=0, $limit=20, $since_id=null, $max_id=null)
{ {
@ -1034,5 +965,4 @@ class User extends Memcached_DataObject
return $apps; return $apps;
} }
} }

View File

@ -87,42 +87,11 @@ class User_group extends Memcached_DataObject
function getNotices($offset, $limit, $since_id=null, $max_id=null) function getNotices($offset, $limit, $since_id=null, $max_id=null)
{ {
$ids = Notice::stream(array($this, '_streamDirect'), $stream = new GroupNoticeStream($this);
array(),
'user_group:notice_ids:' . $this->id,
$offset, $limit, $since_id, $max_id);
return Notice::getStreamByIds($ids); return $stream->getNotices($offset, $limit, $since_id, $max_id);
} }
function _streamDirect($offset, $limit, $since_id, $max_id)
{
$inbox = new Group_inbox();
$inbox->group_id = $this->id;
$inbox->selectAdd();
$inbox->selectAdd('notice_id');
Notice::addWhereSinceId($inbox, $since_id, 'notice_id');
Notice::addWhereMaxId($inbox, $max_id, 'notice_id');
$inbox->orderBy('created DESC, notice_id DESC');
if (!is_null($offset)) {
$inbox->limit($offset, $limit);
}
$ids = array();
if ($inbox->find()) {
while ($inbox->fetch()) {
$ids[] = $inbox->notice_id;
}
}
return $ids;
}
function allowedNickname($nickname) function allowedNickname($nickname)
{ {
@ -306,11 +275,11 @@ class User_group extends Memcached_DataObject
$oldaliases = $this->getAliases(); $oldaliases = $this->getAliases();
# Delete stuff that's old that not in new // Delete stuff that's old that not in new
$to_delete = array_diff($oldaliases, $newaliases); $to_delete = array_diff($oldaliases, $newaliases);
# Insert stuff that's in new and not in old // Insert stuff that's in new and not in old
$to_insert = array_diff($newaliases, $oldaliases); $to_insert = array_diff($newaliases, $oldaliases);

View File

@ -337,6 +337,7 @@ location_id = 1
location_ns = 1 location_ns = 1
repeat_of = 1 repeat_of = 1
object_type = 2 object_type = 2
scope = 1
[notice__keys] [notice__keys]
id = N id = N
@ -627,6 +628,7 @@ smsreplies = 17
smsemail = 2 smsemail = 2
uri = 2 uri = 2
autosubscribe = 17 autosubscribe = 17
subscribe_policy = 17
urlshorteningservice = 2 urlshorteningservice = 2
inboxed = 17 inboxed = 17
design_id = 1 design_id = 1

View File

@ -118,6 +118,7 @@ $schema['user'] = array(
'smsemail' => array('type' => 'varchar', 'length' => 255, 'description' => 'built from sms and carrier'), 'smsemail' => array('type' => 'varchar', 'length' => 255, 'description' => 'built from sms and carrier'),
'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universally unique identifier, usually a tag URI'), 'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universally unique identifier, usually a tag URI'),
'autosubscribe' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'automatically subscribe to users who subscribe to us'), 'autosubscribe' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'automatically subscribe to users who subscribe to us'),
'subscribe_policy' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => '0 = anybody can subscribe; 1 = require approval'),
'urlshorteningservice' => array('type' => 'varchar', 'length' => 50, 'default' => 'internal', 'description' => 'service to use for auto-shortening URLs'), 'urlshorteningservice' => array('type' => 'varchar', 'length' => 50, 'default' => 'internal', 'description' => 'service to use for auto-shortening URLs'),
'inboxed' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'has an inbox been created for this user?'), 'inboxed' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'has an inbox been created for this user?'),
'design_id' => array('type' => 'int', 'description' => 'id of a design'), 'design_id' => array('type' => 'int', 'description' => 'id of a design'),
@ -202,6 +203,9 @@ $schema['notice'] = array(
'location_ns' => array('type' => 'int', 'description' => 'namespace for location'), 'location_ns' => array('type' => 'int', 'description' => 'namespace for location'),
'repeat_of' => array('type' => 'int', 'description' => 'notice this is a repeat of'), 'repeat_of' => array('type' => 'int', 'description' => 'notice this is a repeat of'),
'object_type' => array('type' => 'varchar', 'length' => 255, 'description' => 'URI representing activity streams object type', 'default' => 'http://activitystrea.ms/schema/1.0/note'), 'object_type' => array('type' => 'varchar', 'length' => 255, 'description' => 'URI representing activity streams object type', 'default' => 'http://activitystrea.ms/schema/1.0/note'),
'scope' => array('type' => 'int',
'default' => '1',
'description' => 'bit map for distribution scope; 0 = everywhere; 1 = this server only; 2 = addressees; 4 = followers'),
), ),
'primary key' => array('id'), 'primary key' => array('id'),
'unique keys' => array( 'unique keys' => array(
@ -1099,3 +1103,5 @@ $schema['schema_version'] = array(
); );
$schema['group_join_queue'] = Group_join_queue::schemaDef(); $schema['group_join_queue'] = Group_join_queue::schemaDef();
$schema['subscription_queue'] = Subscription_queue::schemaDef();

View File

@ -1,32 +1,40 @@
<?php <?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */ /* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+ /**
// | PHP Version 5 | * PHP Version 5
// +----------------------------------------------------------------------+ *
// | Copyright (c) 1997-2004 The PHP Group | * Copyright (c) 1997-2004 The PHP Group
// +----------------------------------------------------------------------+ *
// | This source file is subject to version 3.0 of the PHP license, | * This source file is subject to version 3.0 of the PHP license,
// | that is bundled with this package in the file LICENSE, and is | * that is bundled with this package in the file LICENSE, and is
// | available through the world-wide-web at the following url: | * available through the world-wide-web at the following url:
// | http://www.php.net/license/3_0.txt. | * http://www.php.net/license/3_0.txt.
// | If you did not receive a copy of the PHP license and are unable to | * If you did not receive a copy of the PHP license and are unable to
// | obtain it through the world-wide-web, please send a note to | * obtain it through the world-wide-web, please send a note to
// | license@php.net so we can mail you a copy immediately. | * license@php.net so we can mail you a copy immediately.
// +----------------------------------------------------------------------+ *
// | Author: Andrei Zmievski <andrei@php.net> | * @category Console
// +----------------------------------------------------------------------+ * @package Console_Getopt
// * @author Andrei Zmievski <andrei@php.net>
// $Id: Getopt.php,v 1.4 2007/06/12 14:58:56 cellog Exp $ * @license http://www.php.net/license/3_0.txt PHP 3.0
* @version CVS: $Id: Getopt.php 306067 2010-12-08 00:13:31Z dufuz $
* @link http://pear.php.net/package/Console_Getopt
*/
require_once 'PEAR.php'; require_once 'PEAR.php';
/** /**
* Command-line options parsing class. * Command-line options parsing class.
* *
* @author Andrei Zmievski <andrei@php.net> * @category Console
* * @package Console_Getopt
* @author Andrei Zmievski <andrei@php.net>
* @license http://www.php.net/license/3_0.txt PHP 3.0
* @link http://pear.php.net/package/Console_Getopt
*/ */
class Console_Getopt { class Console_Getopt
{
/** /**
* Parses the command-line options. * Parses the command-line options.
* *
@ -53,45 +61,60 @@ class Console_Getopt {
* *
* Most of the semantics of this function are based on GNU getopt_long(). * Most of the semantics of this function are based on GNU getopt_long().
* *
* @param array $args an array of command-line arguments * @param array $args an array of command-line arguments
* @param string $short_options specifies the list of allowed short options * @param string $short_options specifies the list of allowed short options
* @param array $long_options specifies the list of allowed long options * @param array $long_options specifies the list of allowed long options
* @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
* *
* @return array two-element array containing the list of parsed options and * @return array two-element array containing the list of parsed options and
* the non-option arguments * the non-option arguments
*
* @access public * @access public
*
*/ */
function getopt2($args, $short_options, $long_options = null) function getopt2($args, $short_options, $long_options = null, $skip_unknown = false)
{ {
return Console_Getopt::doGetopt(2, $args, $short_options, $long_options); return Console_Getopt::doGetopt(2, $args, $short_options, $long_options, $skip_unknown);
} }
/** /**
* This function expects $args to start with the script name (POSIX-style). * This function expects $args to start with the script name (POSIX-style).
* Preserved for backwards compatibility. * Preserved for backwards compatibility.
*
* @param array $args an array of command-line arguments
* @param string $short_options specifies the list of allowed short options
* @param array $long_options specifies the list of allowed long options
*
* @see getopt2() * @see getopt2()
* @return array two-element array containing the list of parsed options and
* the non-option arguments
*/ */
function getopt($args, $short_options, $long_options = null) function getopt($args, $short_options, $long_options = null, $skip_unknown = false)
{ {
return Console_Getopt::doGetopt(1, $args, $short_options, $long_options); return Console_Getopt::doGetopt(1, $args, $short_options, $long_options, $skip_unknown);
} }
/** /**
* The actual implementation of the argument parsing code. * The actual implementation of the argument parsing code.
*
* @param int $version Version to use
* @param array $args an array of command-line arguments
* @param string $short_options specifies the list of allowed short options
* @param array $long_options specifies the list of allowed long options
* @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
*
* @return array
*/ */
function doGetopt($version, $args, $short_options, $long_options = null) function doGetopt($version, $args, $short_options, $long_options = null, $skip_unknown = false)
{ {
// in case you pass directly readPHPArgv() as the first arg // in case you pass directly readPHPArgv() as the first arg
if (PEAR::isError($args)) { if (PEAR::isError($args)) {
return $args; return $args;
} }
if (empty($args)) { if (empty($args)) {
return array(array(), array()); return array(array(), array());
} }
$opts = array();
$non_opts = array(); $non_opts = $opts = array();
settype($args, 'array'); settype($args, 'array');
@ -111,7 +134,6 @@ class Console_Getopt {
reset($args); reset($args);
while (list($i, $arg) = each($args)) { while (list($i, $arg) = each($args)) {
/* The special element '--' means explicit end of /* The special element '--' means explicit end of
options. Treat the rest of the arguments as non-options options. Treat the rest of the arguments as non-options
and end the loop. */ and end the loop. */
@ -124,17 +146,27 @@ class Console_Getopt {
$non_opts = array_merge($non_opts, array_slice($args, $i)); $non_opts = array_merge($non_opts, array_slice($args, $i));
break; break;
} elseif (strlen($arg) > 1 && $arg{1} == '-') { } elseif (strlen($arg) > 1 && $arg{1} == '-') {
$error = Console_Getopt::_parseLongOption(substr($arg, 2), $long_options, $opts, $args); $error = Console_Getopt::_parseLongOption(substr($arg, 2),
if (PEAR::isError($error)) $long_options,
$opts,
$args,
$skip_unknown);
if (PEAR::isError($error)) {
return $error; return $error;
}
} elseif ($arg == '-') { } elseif ($arg == '-') {
// - is stdin // - is stdin
$non_opts = array_merge($non_opts, array_slice($args, $i)); $non_opts = array_merge($non_opts, array_slice($args, $i));
break; break;
} else { } else {
$error = Console_Getopt::_parseShortOption(substr($arg, 1), $short_options, $opts, $args); $error = Console_Getopt::_parseShortOption(substr($arg, 1),
if (PEAR::isError($error)) $short_options,
$opts,
$args,
$skip_unknown);
if (PEAR::isError($error)) {
return $error; return $error;
}
} }
} }
@ -142,19 +174,31 @@ class Console_Getopt {
} }
/** /**
* @access private * Parse short option
* *
* @param string $arg Argument
* @param string[] $short_options Available short options
* @param string[][] &$opts
* @param string[] &$args
* @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
*
* @access private
* @return void
*/ */
function _parseShortOption($arg, $short_options, &$opts, &$args) function _parseShortOption($arg, $short_options, &$opts, &$args, $skip_unknown)
{ {
for ($i = 0; $i < strlen($arg); $i++) { for ($i = 0; $i < strlen($arg); $i++) {
$opt = $arg{$i}; $opt = $arg{$i};
$opt_arg = null; $opt_arg = null;
/* Try to find the short option in the specifier string. */ /* Try to find the short option in the specifier string. */
if (($spec = strstr($short_options, $opt)) === false || $arg{$i} == ':') if (($spec = strstr($short_options, $opt)) === false || $arg{$i} == ':') {
{ if ($skip_unknown === true) {
return PEAR::raiseError("Console_Getopt: unrecognized option -- $opt"); break;
}
$msg = "Console_Getopt: unrecognized option -- $opt";
return PEAR::raiseError($msg);
} }
if (strlen($spec) > 1 && $spec{1} == ':') { if (strlen($spec) > 1 && $spec{1} == ':') {
@ -173,11 +217,14 @@ class Console_Getopt {
break; break;
} else if (list(, $opt_arg) = each($args)) { } else if (list(, $opt_arg) = each($args)) {
/* Else use the next argument. */; /* Else use the next argument. */;
if (Console_Getopt::_isShortOpt($opt_arg) || Console_Getopt::_isLongOpt($opt_arg)) { if (Console_Getopt::_isShortOpt($opt_arg)
return PEAR::raiseError("Console_Getopt: option requires an argument -- $opt"); || Console_Getopt::_isLongOpt($opt_arg)) {
$msg = "option requires an argument --$opt";
return PEAR::raiseError("Console_Getopt:" . $msg);
} }
} else { } else {
return PEAR::raiseError("Console_Getopt: option requires an argument -- $opt"); $msg = "option requires an argument --$opt";
return PEAR::raiseError("Console_Getopt:" . $msg);
} }
} }
} }
@ -187,36 +234,54 @@ class Console_Getopt {
} }
/** /**
* @access private * Checks if an argument is a short option
* *
* @param string $arg Argument to check
*
* @access private
* @return bool
*/ */
function _isShortOpt($arg) function _isShortOpt($arg)
{ {
return strlen($arg) == 2 && $arg[0] == '-' && preg_match('/[a-zA-Z]/', $arg[1]); return strlen($arg) == 2 && $arg[0] == '-'
&& preg_match('/[a-zA-Z]/', $arg[1]);
} }
/** /**
* @access private * Checks if an argument is a long option
* *
* @param string $arg Argument to check
*
* @access private
* @return bool
*/ */
function _isLongOpt($arg) function _isLongOpt($arg)
{ {
return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' && return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' &&
preg_match('/[a-zA-Z]+$/', substr($arg, 2)); preg_match('/[a-zA-Z]+$/', substr($arg, 2));
} }
/** /**
* @access private * Parse long option
* *
* @param string $arg Argument
* @param string[] $long_options Available long options
* @param string[][] &$opts
* @param string[] &$args
*
* @access private
* @return void|PEAR_Error
*/ */
function _parseLongOption($arg, $long_options, &$opts, &$args) function _parseLongOption($arg, $long_options, &$opts, &$args, $skip_unknown)
{ {
@list($opt, $opt_arg) = explode('=', $arg, 2); @list($opt, $opt_arg) = explode('=', $arg, 2);
$opt_len = strlen($opt); $opt_len = strlen($opt);
for ($i = 0; $i < count($long_options); $i++) { for ($i = 0; $i < count($long_options); $i++) {
$long_opt = $long_options[$i]; $long_opt = $long_options[$i];
$opt_start = substr($long_opt, 0, $opt_len); $opt_start = substr($long_opt, 0, $opt_len);
$long_opt_name = str_replace('=', '', $long_opt); $long_opt_name = str_replace('=', '', $long_opt);
/* Option doesn't match. Go on to the next one. */ /* Option doesn't match. Go on to the next one. */
@ -224,7 +289,7 @@ class Console_Getopt {
continue; continue;
} }
$opt_rest = substr($long_opt, $opt_len); $opt_rest = substr($long_opt, $opt_len);
/* Check that the options uniquely matches one of the allowed /* Check that the options uniquely matches one of the allowed
options. */ options. */
@ -233,12 +298,15 @@ class Console_Getopt {
} else { } else {
$next_option_rest = ''; $next_option_rest = '';
} }
if ($opt_rest != '' && $opt{0} != '=' && if ($opt_rest != '' && $opt{0} != '=' &&
$i + 1 < count($long_options) && $i + 1 < count($long_options) &&
$opt == substr($long_options[$i+1], 0, $opt_len) && $opt == substr($long_options[$i+1], 0, $opt_len) &&
$next_option_rest != '' && $next_option_rest != '' &&
$next_option_rest{0} != '=') { $next_option_rest{0} != '=') {
return PEAR::raiseError("Console_Getopt: option --$opt is ambiguous");
$msg = "Console_Getopt: option --$opt is ambiguous";
return PEAR::raiseError($msg);
} }
if (substr($long_opt, -1) == '=') { if (substr($long_opt, -1) == '=') {
@ -246,37 +314,47 @@ class Console_Getopt {
/* Long option requires an argument. /* Long option requires an argument.
Take the next argument if one wasn't specified. */; Take the next argument if one wasn't specified. */;
if (!strlen($opt_arg) && !(list(, $opt_arg) = each($args))) { if (!strlen($opt_arg) && !(list(, $opt_arg) = each($args))) {
return PEAR::raiseError("Console_Getopt: option --$opt requires an argument"); $msg = "Console_Getopt: option requires an argument --$opt";
return PEAR::raiseError($msg);
} }
if (Console_Getopt::_isShortOpt($opt_arg) || Console_Getopt::_isLongOpt($opt_arg)) {
return PEAR::raiseError("Console_Getopt: option requires an argument --$opt"); if (Console_Getopt::_isShortOpt($opt_arg)
|| Console_Getopt::_isLongOpt($opt_arg)) {
$msg = "Console_Getopt: option requires an argument --$opt";
return PEAR::raiseError($msg);
} }
} }
} else if ($opt_arg) { } else if ($opt_arg) {
return PEAR::raiseError("Console_Getopt: option --$opt doesn't allow an argument"); $msg = "Console_Getopt: option --$opt doesn't allow an argument";
return PEAR::raiseError($msg);
} }
$opts[] = array('--' . $opt, $opt_arg); $opts[] = array('--' . $opt, $opt_arg);
return; return;
} }
if ($skip_unknown === true) {
return;
}
return PEAR::raiseError("Console_Getopt: unrecognized option --$opt"); return PEAR::raiseError("Console_Getopt: unrecognized option --$opt");
} }
/** /**
* Safely read the $argv PHP array across different PHP configurations. * Safely read the $argv PHP array across different PHP configurations.
* Will take care on register_globals and register_argc_argv ini directives * Will take care on register_globals and register_argc_argv ini directives
* *
* @access public * @access public
* @return mixed the $argv PHP array or PEAR error if not registered * @return mixed the $argv PHP array or PEAR error if not registered
*/ */
function readPHPArgv() function readPHPArgv()
{ {
global $argv; global $argv;
if (!is_array($argv)) { if (!is_array($argv)) {
if (!@is_array($_SERVER['argv'])) { if (!@is_array($_SERVER['argv'])) {
if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) { if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
return PEAR::raiseError("Console_Getopt: Could not read cmd args (register_argc_argv=Off?)"); $msg = "Could not read cmd args (register_argc_argv=Off?)";
return PEAR::raiseError("Console_Getopt: " . $msg);
} }
return $GLOBALS['HTTP_SERVER_VARS']['argv']; return $GLOBALS['HTTP_SERVER_VARS']['argv'];
} }
@ -286,5 +364,3 @@ class Console_Getopt {
} }
} }
?>

View File

@ -15,7 +15,7 @@
* @author Alan Knowles <alan@akbkhome.com> * @author Alan Knowles <alan@akbkhome.com>
* @copyright 1997-2006 The PHP Group * @copyright 1997-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01 * @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: DataObject.php 291349 2009-11-27 09:15:18Z alan_k $ * @version CVS: $Id: DataObject.php 301030 2010-07-07 02:26:31Z alan_k $
* @link http://pear.php.net/package/DB_DataObject * @link http://pear.php.net/package/DB_DataObject
*/ */
@ -235,7 +235,7 @@ class DB_DataObject extends DB_DataObject_Overload
* @access private * @access private
* @var string * @var string
*/ */
var $_DB_DataObject_version = "1.9.0"; var $_DB_DataObject_version = "1.9.5";
/** /**
* The Database table (used by table extends) * The Database table (used by table extends)
@ -369,6 +369,32 @@ class DB_DataObject extends DB_DataObject_Overload
return $_DB_DATAOBJECT['CACHE'][$lclass][$key]; return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
} }
/**
* build the basic select query.
*
* @access private
*/
function _build_select()
{
global $_DB_DATAOBJECT;
$quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
if ($quoteIdentifiers) {
$this->_connect();
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
}
$sql = 'SELECT ' .
$this->_query['data_select'] . " \n" .
' FROM ' . ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table) . " \n" .
$this->_join . " \n" .
$this->_query['condition'] . " \n" .
$this->_query['group_by'] . " \n" .
$this->_query['having'] . " \n";
return $sql;
}
/** /**
* find results, either normal or crosstable * find results, either normal or crosstable
* *
@ -411,20 +437,21 @@ class DB_DataObject extends DB_DataObject_Overload
$query_before = $this->_query; $query_before = $this->_query;
$this->_build_condition($this->table()) ; $this->_build_condition($this->table()) ;
$quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
$this->_connect(); $this->_connect();
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
/* We are checking for method modifyLimitQuery as it is PEAR DB specific */
$sql = 'SELECT ' .
$this->_query['data_select'] . " \n" .
' FROM ' . ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table) . " \n" .
$this->_join . " \n" .
$this->_query['condition'] . " \n" .
$this->_query['group_by'] . " \n" .
$this->_query['having'] . " \n" .
$this->_query['order_by'] . " \n";
$sql = $this->_build_select();
foreach ($this->_query['unions'] as $union_ar) {
$sql .= $union_ar[1] . $union_ar[0]->_build_select() . " \n";
}
$sql .= $this->_query['order_by'] . " \n";
/* We are checking for method modifyLimitQuery as it is PEAR DB specific */
if ((!isset($_DB_DATAOBJECT['CONFIG']['db_driver'])) || if ((!isset($_DB_DATAOBJECT['CONFIG']['db_driver'])) ||
($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) { ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) {
/* PEAR DB specific */ /* PEAR DB specific */
@ -578,6 +605,85 @@ class DB_DataObject extends DB_DataObject_Overload
return true; return true;
} }
/**
* fetches all results as an array,
*
* return format is dependant on args.
* if selectAdd() has not been called on the object, then it will add the correct columns to the query.
*
* A) Array of values (eg. a list of 'id')
*
* $x = DB_DataObject::factory('mytable');
* $x->whereAdd('something = 1')
* $ar = $x->fetchAll('id');
* -- returns array(1,2,3,4,5)
*
* B) Array of values (not from table)
*
* $x = DB_DataObject::factory('mytable');
* $x->whereAdd('something = 1');
* $x->selectAdd();
* $x->selectAdd('distinct(group_id) as group_id');
* $ar = $x->fetchAll('group_id');
* -- returns array(1,2,3,4,5)
* *
* C) A key=>value associative array
*
* $x = DB_DataObject::factory('mytable');
* $x->whereAdd('something = 1')
* $ar = $x->fetchAll('id','name');
* -- returns array(1=>'fred',2=>'blogs',3=> .......
*
* D) array of objects
* $x = DB_DataObject::factory('mytable');
* $x->whereAdd('something = 1');
* $ar = $x->fetchAll();
*
* E) array of arrays (for example)
* $x = DB_DataObject::factory('mytable');
* $x->whereAdd('something = 1');
* $ar = $x->fetchAll(false,false,'toArray');
*
*
* @param string|false $k key
* @param string|false $v value
* @param string|false $method method to call on each result to get array value (eg. 'toArray')
* @access public
* @return array format dependant on arguments, may be empty
*/
function fetchAll($k= false, $v = false, $method = false)
{
// should it even do this!!!?!?
if ($k !== false &&
( // only do this is we have not been explicit..
empty($this->_query['data_select']) ||
($this->_query['data_select'] == '*')
)
) {
$this->selectAdd();
$this->selectAdd($k);
if ($v !== false) {
$this->selectAdd($v);
}
}
$this->find();
$ret = array();
while ($this->fetch()) {
if ($v !== false) {
$ret[$this->$k] = $this->$v;
continue;
}
$ret[] = $k === false ?
($method == false ? clone($this) : $this->$method())
: $this->$k;
}
return $ret;
}
/** /**
* Adds a condition to the WHERE statement, defaults to AND * Adds a condition to the WHERE statement, defaults to AND
* *
@ -622,6 +728,47 @@ class DB_DataObject extends DB_DataObject_Overload
return $r; return $r;
} }
/**
* Adds a 'IN' condition to the WHERE statement
*
* $object->whereAddIn('id', $array, 'int'); //minimal usage
* $object->whereAddIn('price', $array, 'float', 'OR'); // cast to float, and call whereAdd with 'OR'
* $object->whereAddIn('name', $array, 'string'); // quote strings
*
* @param string $key key column to match
* @param array $list list of values to match
* @param string $type string|int|integer|float|bool cast to type.
* @param string $logic optional logic to call whereAdd with eg. "OR" (defaults to "AND")
* @access public
* @return string|PEAR::Error - previous condition or Error when invalid args found
*/
function whereAddIn($key, $list, $type, $logic = 'AND')
{
$not = '';
if ($key[0] == '!') {
$not = 'NOT ';
$key = substr($key, 1);
}
// fix type for short entry.
$type = $type == 'int' ? 'integer' : $type;
if ($type == 'string') {
$this->_connect();
}
$ar = array();
foreach($list as $k) {
settype($k, $type);
$ar[] = $type =='string' ? $this->_quote($k) : $k;
}
if (!$ar) {
return $not ? $this->_query['condition'] : $this->whereAdd("1=0");
}
return $this->whereAdd("$key $not IN (". implode(',', $ar). ')', $logic );
}
/** /**
* Adds a order by condition * Adds a order by condition
* *
@ -1175,7 +1322,7 @@ class DB_DataObject extends DB_DataObject_Overload
$seq = $this->sequenceKey(); $seq = $this->sequenceKey();
if ($seq[0] !== false) { if ($seq[0] !== false) {
$keys = array($seq[0]); $keys = array($seq[0]);
if (empty($this->{$keys[0]}) && $dataObject !== true) { if (!isset($this->{$keys[0]}) && $dataObject !== true) {
$this->raiseError("update: trying to perform an update without $this->raiseError("update: trying to perform an update without
the key set, and argument to update is not the key set, and argument to update is not
DB_DATAOBJECT_WHEREADD_ONLY DB_DATAOBJECT_WHEREADD_ONLY
@ -1670,6 +1817,7 @@ class DB_DataObject extends DB_DataObject_Overload
'limit_start' => '', // the LIMIT condition 'limit_start' => '', // the LIMIT condition
'limit_count' => '', // the LIMIT condition 'limit_count' => '', // the LIMIT condition
'data_select' => '*', // the columns to be SELECTed 'data_select' => '*', // the columns to be SELECTed
'unions' => array(), // the added unions
); );
@ -2032,9 +2180,9 @@ class DB_DataObject extends DB_DataObject_Overload
$seqname = false; $seqname = false;
if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) { if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) {
$usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table]; $seqname = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table];
if (strpos($usekey,':') !== false) { if (strpos($seqname,':') !== false) {
list($usekey,$seqname) = explode(':',$usekey); list($usekey,$seqname) = explode(':',$seqname);
} }
} }
@ -3068,9 +3216,9 @@ class DB_DataObject extends DB_DataObject_Overload
} }
/** /**
* IS THIS SUPPORTED/USED ANYMORE???? * getLinkArray
*return a list of options for a linked table * Fetch an array of related objects. This should be used in conjunction with a <dbname>.links.ini file configuration (see the introduction on linking for details on this).
* * You may also use this with all parameters to specify, the column and related table.
* This is highly dependant on naming columns 'correctly' :) * This is highly dependant on naming columns 'correctly' :)
* using colname = xxxxx_yyyyyy * using colname = xxxxx_yyyyyy
* xxxxxx = related table; (yyyyy = user defined..) * xxxxxx = related table; (yyyyy = user defined..)
@ -3078,7 +3226,21 @@ class DB_DataObject extends DB_DataObject_Overload
* stores it in $this->_xxxxx_yyyyy * stores it in $this->_xxxxx_yyyyy
* *
* @access public * @access public
* @return array of results (empty array on failure) * @param string $column - either column or column.xxxxx
* @param string $table - name of table to look up value in
* @return array - array of results (empty array on failure)
*
* Example - Getting the related objects
*
* $person = new DataObjects_Person;
* $person->get(12);
* $children = $person->getLinkArray('children');
*
* echo 'There are ', count($children), ' descendant(s):<br />';
* foreach ($children as $child) {
* echo $child->name, '<br />';
* }
*
*/ */
function &getLinkArray($row, $table = null) function &getLinkArray($row, $table = null)
{ {
@ -3123,6 +3285,46 @@ class DB_DataObject extends DB_DataObject_Overload
return $ret; return $ret;
} }
/**
* unionAdd - adds another dataobject to this, building a unioned query.
*
* usage:
* $doTable1 = DB_DataObject::factory("table1");
* $doTable2 = DB_DataObject::factory("table2");
*
* $doTable1->selectAdd();
* $doTable1->selectAdd("col1,col2");
* $doTable1->whereAdd("col1 > 100");
* $doTable1->orderBy("col1");
*
* $doTable2->selectAdd();
* $doTable2->selectAdd("col1, col2");
* $doTable2->whereAdd("col2 = 'v'");
*
* $doTable1->unionAdd($doTable2);
* $doTable1->find();
*
* Note: this model may be a better way to implement joinAdd?, eg. do the building in find?
*
*
* @param $obj object|false the union object or false to reset
* @param optional $is_all string 'ALL' to do all.
* @returns $obj object|array the added object, or old list if reset.
*/
function unionAdd($obj,$is_all= '')
{
if ($obj === false) {
$ret = $this->_query['unions'];
$this->_query['unions'] = array();
return $ret;
}
$this->_query['unions'][] = array($obj, 'UNION ' . $is_all . ' ') ;
return $obj;
}
/** /**
* The JOIN condition * The JOIN condition
* *
@ -3248,31 +3450,46 @@ class DB_DataObject extends DB_DataObject_Overload
/* otherwise see if there are any links from this table to the obj. */ /* otherwise see if there are any links from this table to the obj. */
//print_r($this->links()); //print_r($this->links());
if (($ofield === false) && ($links = $this->links())) { if (($ofield === false) && ($links = $this->links())) {
foreach ($links as $k => $v) { // this enables for support for arrays of links in ini file.
/* link contains {this column} = {linked table}:{linked column} */ // link contains this_column[] = linked_table:linked_column
$ar = explode(':', $v); // or standard way.
// Feature Request #4266 - Allow joins with multiple keys // link contains this_column = linked_table:linked_column
if (strpos($k, ',') !== false) { foreach ($links as $k => $linkVar) {
$k = explode(',', $k);
}
if (strpos($ar[1], ',') !== false) {
$ar[1] = explode(',', $ar[1]);
}
if ($ar[0] == $obj->__table) { if (!is_array($linkVar)) {
$linkVar = array($linkVar);
}
foreach($linkVar as $v) {
/* link contains {this column} = {linked table}:{linked column} */
$ar = explode(':', $v);
// Feature Request #4266 - Allow joins with multiple keys
if (strpos($k, ',') !== false) {
$k = explode(',', $k);
}
if (strpos($ar[1], ',') !== false) {
$ar[1] = explode(',', $ar[1]);
}
if ($ar[0] != $obj->__table) {
continue;
}
if ($joinCol !== false) { if ($joinCol !== false) {
if ($k == $joinCol) { if ($k == $joinCol) {
// got it!?
$tfield = $k; $tfield = $k;
$ofield = $ar[1]; $ofield = $ar[1];
break; break;
} else {
continue;
} }
} else { continue;
$tfield = $k;
$ofield = $ar[1];
break;
} }
$tfield = $k;
$ofield = $ar[1];
break;
} }
} }
} }
@ -3280,23 +3497,30 @@ class DB_DataObject extends DB_DataObject_Overload
//print_r($obj->links()); //print_r($obj->links());
if (!$ofield && ($olinks = $obj->links())) { if (!$ofield && ($olinks = $obj->links())) {
foreach ($olinks as $k => $v) { foreach ($olinks as $k => $linkVar) {
/* link contains {this column} = {linked table}:{linked column} */ /* link contains {this column} = array ( {linked table}:{linked column} )*/
$ar = explode(':', $v); if (!is_array($linkVar)) {
$linkVar = array($linkVar);
// Feature Request #4266 - Allow joins with multiple keys
$links_key_array = strpos($k,',');
if ($links_key_array !== false) {
$k = explode(',', $k);
} }
foreach($linkVar as $v) {
$ar_array = strpos($ar[1],','); /* link contains {this column} = {linked table}:{linked column} */
if ($ar_array !== false) { $ar = explode(':', $v);
$ar[1] = explode(',', $ar[1]);
}
if ($ar[0] == $this->__table) { // Feature Request #4266 - Allow joins with multiple keys
$links_key_array = strpos($k,',');
if ($links_key_array !== false) {
$k = explode(',', $k);
}
$ar_array = strpos($ar[1],',');
if ($ar_array !== false) {
$ar[1] = explode(',', $ar[1]);
}
if ($ar[0] != $this->__table) {
continue;
}
// you have explictly specified the column // you have explictly specified the column
// and the col is listed here.. // and the col is listed here..
@ -3315,6 +3539,7 @@ class DB_DataObject extends DB_DataObject_Overload
$ofield = $k; $ofield = $k;
$tfield = $ar[1]; $tfield = $ar[1];
break; break;
} }
} }
} }
@ -3573,7 +3798,14 @@ class DB_DataObject extends DB_DataObject_Overload
if (!$k) { if (!$k) {
continue; // ignore empty keys!!! what continue; // ignore empty keys!!! what
} }
if (is_object($from) && isset($from->{sprintf($format,$k)})) {
$chk = is_object($from) &&
(version_compare(phpversion(), "5.1.0" , ">=") ?
property_exists($from, sprintf($format,$k)) : // php5.1
array_key_exists( sprintf($format,$k), get_class_vars($from)) //older
);
// if from has property ($format($k)
if ($chk) {
$kk = (strtolower($k) == 'from') ? '_from' : $k; $kk = (strtolower($k) == 'from') ? '_from' : $k;
if (method_exists($this,'set'.$kk)) { if (method_exists($this,'set'.$kk)) {
$ret = $this->{'set'.$kk}($from->{sprintf($format,$k)}); $ret = $this->{'set'.$kk}($from->{sprintf($format,$k)});

View File

@ -15,7 +15,7 @@
* @author Alan Knowles <alan@akbkhome.com> * @author Alan Knowles <alan@akbkhome.com>
* @copyright 1997-2006 The PHP Group * @copyright 1997-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01 * @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: Generator.php 289384 2009-10-09 00:17:26Z alan_k $ * @version CVS: $Id: Generator.php 298560 2010-04-25 23:01:51Z alan_k $
* @link http://pear.php.net/package/DB_DataObject * @link http://pear.php.net/package/DB_DataObject
*/ */
@ -383,8 +383,8 @@ class DB_DataObject_Generator extends DB_DataObject
return false; return false;
} }
$__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
if (!in_array($__DB->phptype, array('mysql','mysqli'))) { if (!in_array($__DB->phptype, array('mysql', 'mysqli', 'pgsql'))) {
echo "WARNING: cant handle non-mysql introspection for defaults."; echo "WARNING: cant handle non-mysql and pgsql introspection for defaults.";
return; // cant handle non-mysql introspection for defaults. return; // cant handle non-mysql introspection for defaults.
} }
@ -392,33 +392,72 @@ class DB_DataObject_Generator extends DB_DataObject
$fk = array(); $fk = array();
foreach($this->tables as $this->table) {
$quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $DB->quoteIdentifier($table) : $this->table;
$res =& $DB->query('SHOW CREATE TABLE ' . $quotedTable ); switch ($DB->phptype) {
if (PEAR::isError($res)) {
die($res->getMessage());
}
$text = $res->fetchRow(DB_FETCHMODE_DEFAULT, 0); case 'pgsql':
$treffer = array(); foreach($this->tables as $this->table) {
// Extract FOREIGN KEYS $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $DB->quoteIdentifier($table) : $this->table;
preg_match_all( $res =& $DB->query("SELECT
"/FOREIGN KEY \(`(\w*)`\) REFERENCES `(\w*)` \(`(\w*)`\)/i", pg_catalog.pg_get_constraintdef(r.oid, true) AS condef
$text[1], FROM pg_catalog.pg_constraint r,
$treffer, pg_catalog.pg_class c
PREG_SET_ORDER); WHERE c.oid=r.conrelid
AND r.contype = 'f'
AND c.relname = '" . $quotedTable . "'");
if (PEAR::isError($res)) {
die($res->getMessage());
}
if (count($treffer) < 1) { while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) {
continue; $treffer = array();
} // this only picks up one of these.. see this for why: http://pear.php.net/bugs/bug.php?id=17049
for ($i = 0; $i < count($treffer); $i++) { preg_match(
$fk[$this->table][$treffer[$i][1]] = $treffer[$i][2] . ":" . $treffer[$i][3]; "/FOREIGN KEY \((\w*)\) REFERENCES (\w*)\((\w*)\)/i",
} $row['condef'],
$treffer);
if (!count($treffer)) {
continue;
}
$fk[$this->table][$treffer[1]] = $treffer[2] . ":" . $treffer[3];
}
}
break;
case 'mysql':
case 'mysqli':
default:
foreach($this->tables as $this->table) {
$quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $DB->quoteIdentifier($table) : $this->table;
$res =& $DB->query('SHOW CREATE TABLE ' . $quotedTable );
if (PEAR::isError($res)) {
die($res->getMessage());
}
$text = $res->fetchRow(DB_FETCHMODE_DEFAULT, 0);
$treffer = array();
// Extract FOREIGN KEYS
preg_match_all(
"/FOREIGN KEY \(`(\w*)`\) REFERENCES `(\w*)` \(`(\w*)`\)/i",
$text[1],
$treffer,
PREG_SET_ORDER);
if (!count($treffer)) {
continue;
}
foreach($treffer as $i=> $tref) {
$fk[$this->table][$tref[1]] = $tref[2] . ":" . $tref[3];
}
}
} }
$links_ini = ""; $links_ini = "";
foreach($fk as $table => $details) { foreach($fk as $table => $details) {
@ -861,10 +900,8 @@ class DB_DataObject_Generator extends DB_DataObject
$body = "\n ###START_AUTOCODE\n"; $body = "\n ###START_AUTOCODE\n";
$body .= " /* the code below is auto generated do not remove the above tag */\n\n"; $body .= " /* the code below is auto generated do not remove the above tag */\n\n";
// table // table
$padding = (30 - strlen($this->table));
$padding = ($padding < 2) ? 2 : $padding;
$p = str_repeat(' ',$padding) ; $p = str_repeat(' ',max(2, (18 - strlen($this->table)))) ;
$options = &PEAR::getStaticProperty('DB_DataObject','options'); $options = &PEAR::getStaticProperty('DB_DataObject','options');
@ -887,6 +924,7 @@ class DB_DataObject_Generator extends DB_DataObject
// Only include the $_database property if the omit_database_var is unset or false // Only include the $_database property if the omit_database_var is unset or false
if (isset($options["database_{$this->_database}"]) && empty($GLOBALS['_DB_DATAOBJECT']['CONFIG']['generator_omit_database_var'])) { if (isset($options["database_{$this->_database}"]) && empty($GLOBALS['_DB_DATAOBJECT']['CONFIG']['generator_omit_database_var'])) {
$p = str_repeat(' ', max(2, (16 - strlen($this->table))));
$body .= " {$var} \$_database = '{$this->_database}'; {$p}// database name (used with database_{*} config)\n"; $body .= " {$var} \$_database = '{$this->_database}'; {$p}// database name (used with database_{*} config)\n";
} }
@ -900,6 +938,7 @@ class DB_DataObject_Generator extends DB_DataObject
// show nice information! // show nice information!
$connections = array(); $connections = array();
$sets = array(); $sets = array();
foreach($defs as $t) { foreach($defs as $t) {
if (!strlen(trim($t->name))) { if (!strlen(trim($t->name))) {
continue; continue;
@ -915,10 +954,7 @@ class DB_DataObject_Generator extends DB_DataObject
continue; continue;
} }
$p = str_repeat(' ',max(2, (30 - strlen($t->name))));
$padding = (30 - strlen($t->name));
if ($padding < 2) $padding =2;
$p = str_repeat(' ',$padding) ;
$length = empty($t->len) ? '' : '('.$t->len.')'; $length = empty($t->len) ? '' : '('.$t->len.')';
$body .=" {$var} \${$t->name}; {$p}// {$t->type}$length {$t->flags}\n"; $body .=" {$var} \${$t->name}; {$p}// {$t->type}$length {$t->flags}\n";
@ -926,9 +962,11 @@ class DB_DataObject_Generator extends DB_DataObject
// can not do set as PEAR::DB table info doesnt support it. // can not do set as PEAR::DB table info doesnt support it.
//if (substr($t->Type,0,3) == "set") //if (substr($t->Type,0,3) == "set")
// $sets[$t->Field] = "array".substr($t->Type,3); // $sets[$t->Field] = "array".substr($t->Type,3);
$body .= $this->derivedHookVar($t,$padding); $body .= $this->derivedHookVar($t,strlen($p));
} }
$body .= $this->derivedHookPostVar($defs);
// THIS IS TOTALLY BORKED old FC creation // THIS IS TOTALLY BORKED old FC creation
// IT WILL BE REMOVED!!!!! in DataObjects 1.6 // IT WILL BE REMOVED!!!!! in DataObjects 1.6
// grep -r __clone * to find all it's uses // grep -r __clone * to find all it's uses
@ -1078,7 +1116,21 @@ class DB_DataObject_Generator extends DB_DataObject
// It MUST NOT be changed here!!! // It MUST NOT be changed here!!!
return ""; return "";
} }
/**
* hook for after var lines (
* called at the end of the output of var line have generated, override to add extra var
* lines
*
* @param array cols containing array of objects with type,len,flags etc. from tableInfo call
* @access public
* @return string added to class eg. functions.
*/
function derivedHookPostVar($t)
{
// This is so derived generator classes can generate variabels
// It MUST NOT be changed here!!!
return "";
}
/** /**
* hook to add extra page-level (in terms of phpDocumentor) DocBlock * hook to add extra page-level (in terms of phpDocumentor) DocBlock
* *

View File

@ -6,21 +6,15 @@
* *
* PHP versions 4 and 5 * PHP versions 4 and 5
* *
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category pear * @category pear
* @package PEAR * @package PEAR
* @author Sterling Hughes <sterling@php.net> * @author Sterling Hughes <sterling@php.net>
* @author Stig Bakken <ssb@php.net> * @author Stig Bakken <ssb@php.net>
* @author Tomas V.V.Cox <cox@idecnet.com> * @author Tomas V.V.Cox <cox@idecnet.com>
* @author Greg Beaver <cellog@php.net> * @author Greg Beaver <cellog@php.net>
* @copyright 1997-2008 The PHP Group * @copyright 1997-2010 The Authors
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id: PEAR.php,v 1.104 2008/01/03 20:26:34 cellog Exp $ * @version CVS: $Id: PEAR.php 307683 2011-01-23 21:56:12Z dufuz $
* @link http://pear.php.net/package/PEAR * @link http://pear.php.net/package/PEAR
* @since File available since Release 0.1 * @since File available since Release 0.1
*/ */
@ -52,15 +46,6 @@ if (substr(PHP_OS, 0, 3) == 'WIN') {
define('PEAR_OS', 'Unix'); // blatant assumption define('PEAR_OS', 'Unix'); // blatant assumption
} }
// instant backwards compatibility
if (!defined('PATH_SEPARATOR')) {
if (OS_WINDOWS) {
define('PATH_SEPARATOR', ';');
} else {
define('PATH_SEPARATOR', ':');
}
}
$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN; $GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN;
$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE; $GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE;
$GLOBALS['_PEAR_destructor_object_list'] = array(); $GLOBALS['_PEAR_destructor_object_list'] = array();
@ -92,8 +77,8 @@ $GLOBALS['_PEAR_error_handler_stack'] = array();
* @author Tomas V.V. Cox <cox@idecnet.com> * @author Tomas V.V. Cox <cox@idecnet.com>
* @author Greg Beaver <cellog@php.net> * @author Greg Beaver <cellog@php.net>
* @copyright 1997-2006 The PHP Group * @copyright 1997-2006 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: 1.7.2 * @version Release: 1.9.2
* @link http://pear.php.net/package/PEAR * @link http://pear.php.net/package/PEAR
* @see PEAR_Error * @see PEAR_Error
* @since Class available since PHP 4.0.2 * @since Class available since PHP 4.0.2
@ -101,8 +86,6 @@ $GLOBALS['_PEAR_error_handler_stack'] = array();
*/ */
class PEAR class PEAR
{ {
// {{{ properties
/** /**
* Whether to enable internal debug messages. * Whether to enable internal debug messages.
* *
@ -153,10 +136,6 @@ class PEAR
*/ */
var $_expected_errors = array(); var $_expected_errors = array();
// }}}
// {{{ constructor
/** /**
* Constructor. Registers this object in * Constructor. Registers this object in
* $_PEAR_destructor_object_list for destructor emulation if a * $_PEAR_destructor_object_list for destructor emulation if a
@ -173,9 +152,11 @@ class PEAR
if ($this->_debug) { if ($this->_debug) {
print "PEAR constructor called, class=$classname\n"; print "PEAR constructor called, class=$classname\n";
} }
if ($error_class !== null) { if ($error_class !== null) {
$this->_error_class = $error_class; $this->_error_class = $error_class;
} }
while ($classname && strcasecmp($classname, "pear")) { while ($classname && strcasecmp($classname, "pear")) {
$destructor = "_$classname"; $destructor = "_$classname";
if (method_exists($this, $destructor)) { if (method_exists($this, $destructor)) {
@ -192,9 +173,6 @@ class PEAR
} }
} }
// }}}
// {{{ destructor
/** /**
* Destructor (the emulated type of...). Does nothing right now, * Destructor (the emulated type of...). Does nothing right now,
* but is included for forward compatibility, so subclass * but is included for forward compatibility, so subclass
@ -212,9 +190,6 @@ class PEAR
} }
} }
// }}}
// {{{ getStaticProperty()
/** /**
* If you have a class that's mostly/entirely static, and you need static * If you have a class that's mostly/entirely static, and you need static
* properties, you can use this method to simulate them. Eg. in your method(s) * properties, you can use this method to simulate them. Eg. in your method(s)
@ -233,15 +208,14 @@ class PEAR
if (!isset($properties[$class])) { if (!isset($properties[$class])) {
$properties[$class] = array(); $properties[$class] = array();
} }
if (!array_key_exists($var, $properties[$class])) { if (!array_key_exists($var, $properties[$class])) {
$properties[$class][$var] = null; $properties[$class][$var] = null;
} }
return $properties[$class][$var]; return $properties[$class][$var];
} }
// }}}
// {{{ registerShutdownFunc()
/** /**
* Use this function to register a shutdown method for static * Use this function to register a shutdown method for static
* classes. * classes.
@ -262,9 +236,6 @@ class PEAR
$GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args); $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args);
} }
// }}}
// {{{ isError()
/** /**
* Tell whether a value is a PEAR error. * Tell whether a value is a PEAR error.
* *
@ -278,20 +249,18 @@ class PEAR
*/ */
function isError($data, $code = null) function isError($data, $code = null)
{ {
if (is_a($data, 'PEAR_Error')) { if (!is_a($data, 'PEAR_Error')) {
if (is_null($code)) { return false;
return true;
} elseif (is_string($code)) {
return $data->getMessage() == $code;
} else {
return $data->getCode() == $code;
}
} }
return false;
}
// }}} if (is_null($code)) {
// {{{ setErrorHandling() return true;
} elseif (is_string($code)) {
return $data->getMessage() == $code;
}
return $data->getCode() == $code;
}
/** /**
* Sets how errors generated by this object should be handled. * Sets how errors generated by this object should be handled.
@ -331,7 +300,6 @@ class PEAR
* *
* @since PHP 4.0.5 * @since PHP 4.0.5
*/ */
function setErrorHandling($mode = null, $options = null) function setErrorHandling($mode = null, $options = null)
{ {
if (isset($this) && is_a($this, 'PEAR')) { if (isset($this) && is_a($this, 'PEAR')) {
@ -369,9 +337,6 @@ class PEAR
} }
} }
// }}}
// {{{ expectError()
/** /**
* This method is used to tell which errors you expect to get. * This method is used to tell which errors you expect to get.
* Expected errors are always returned with error mode * Expected errors are always returned with error mode
@ -394,12 +359,9 @@ class PEAR
} else { } else {
array_push($this->_expected_errors, array($code)); array_push($this->_expected_errors, array($code));
} }
return sizeof($this->_expected_errors); return count($this->_expected_errors);
} }
// }}}
// {{{ popExpect()
/** /**
* This method pops one element off the expected error codes * This method pops one element off the expected error codes
* stack. * stack.
@ -411,9 +373,6 @@ class PEAR
return array_pop($this->_expected_errors); return array_pop($this->_expected_errors);
} }
// }}}
// {{{ _checkDelExpect()
/** /**
* This method checks unsets an error code if available * This method checks unsets an error code if available
* *
@ -425,8 +384,7 @@ class PEAR
function _checkDelExpect($error_code) function _checkDelExpect($error_code)
{ {
$deleted = false; $deleted = false;
foreach ($this->_expected_errors as $key => $error_array) {
foreach ($this->_expected_errors AS $key => $error_array) {
if (in_array($error_code, $error_array)) { if (in_array($error_code, $error_array)) {
unset($this->_expected_errors[$key][array_search($error_code, $error_array)]); unset($this->_expected_errors[$key][array_search($error_code, $error_array)]);
$deleted = true; $deleted = true;
@ -437,12 +395,10 @@ class PEAR
unset($this->_expected_errors[$key]); unset($this->_expected_errors[$key]);
} }
} }
return $deleted; return $deleted;
} }
// }}}
// {{{ delExpect()
/** /**
* This method deletes all occurences of the specified element from * This method deletes all occurences of the specified element from
* the expected error codes stack. * the expected error codes stack.
@ -455,34 +411,26 @@ class PEAR
function delExpect($error_code) function delExpect($error_code)
{ {
$deleted = false; $deleted = false;
if ((is_array($error_code) && (0 != count($error_code)))) { if ((is_array($error_code) && (0 != count($error_code)))) {
// $error_code is a non-empty array here; // $error_code is a non-empty array here; we walk through it trying
// we walk through it trying to unset all // to unset all values
// values foreach ($error_code as $key => $error) {
foreach($error_code as $key => $error) { $deleted = $this->_checkDelExpect($error) ? true : false;
if ($this->_checkDelExpect($error)) {
$deleted = true;
} else {
$deleted = false;
}
} }
return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
} elseif (!empty($error_code)) { } elseif (!empty($error_code)) {
// $error_code comes alone, trying to unset it // $error_code comes alone, trying to unset it
if ($this->_checkDelExpect($error_code)) { if ($this->_checkDelExpect($error_code)) {
return true; return true;
} else {
return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
} }
} else {
// $error_code is empty
return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME
}
}
// }}} return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
// {{{ raiseError() }
// $error_code is empty
return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME
}
/** /**
* This method is a wrapper that returns an instance of the * This method is a wrapper that returns an instance of the
@ -538,13 +486,20 @@ class PEAR
$message = $message->getMessage(); $message = $message->getMessage();
} }
if (isset($this) && isset($this->_expected_errors) && sizeof($this->_expected_errors) > 0 && sizeof($exp = end($this->_expected_errors))) { if (
isset($this) &&
isset($this->_expected_errors) &&
count($this->_expected_errors) > 0 &&
count($exp = end($this->_expected_errors))
) {
if ($exp[0] == "*" || if ($exp[0] == "*" ||
(is_int(reset($exp)) && in_array($code, $exp)) || (is_int(reset($exp)) && in_array($code, $exp)) ||
(is_string(reset($exp)) && in_array($message, $exp))) { (is_string(reset($exp)) && in_array($message, $exp))
) {
$mode = PEAR_ERROR_RETURN; $mode = PEAR_ERROR_RETURN;
} }
} }
// No mode given, try global ones // No mode given, try global ones
if ($mode === null) { if ($mode === null) {
// Class error handler // Class error handler
@ -565,46 +520,52 @@ class PEAR
} else { } else {
$ec = 'PEAR_Error'; $ec = 'PEAR_Error';
} }
if (intval(PHP_VERSION) < 5) { if (intval(PHP_VERSION) < 5) {
// little non-eval hack to fix bug #12147 // little non-eval hack to fix bug #12147
include 'PEAR/FixPHP5PEARWarnings.php'; include 'PEAR/FixPHP5PEARWarnings.php';
return $a; return $a;
} }
if ($skipmsg) { if ($skipmsg) {
$a = new $ec($code, $mode, $options, $userinfo); $a = new $ec($code, $mode, $options, $userinfo);
} else { } else {
$a = new $ec($message, $code, $mode, $options, $userinfo); $a = new $ec($message, $code, $mode, $options, $userinfo);
} }
return $a; return $a;
} }
// }}}
// {{{ throwError()
/** /**
* Simpler form of raiseError with fewer options. In most cases * Simpler form of raiseError with fewer options. In most cases
* message, code and userinfo are enough. * message, code and userinfo are enough.
* *
* @param string $message * @param mixed $message a text error message or a PEAR error object
* *
* @param int $code a numeric error code (it is up to your class
* to define these if you want to use codes)
*
* @param string $userinfo If you need to pass along for example debug
* information, this parameter is meant for that.
*
* @access public
* @return object a PEAR error object
* @see PEAR::raiseError
*/ */
function &throwError($message = null, function &throwError($message = null, $code = null, $userinfo = null)
$code = null,
$userinfo = null)
{ {
if (isset($this) && is_a($this, 'PEAR')) { if (isset($this) && is_a($this, 'PEAR')) {
$a = &$this->raiseError($message, $code, null, null, $userinfo); $a = &$this->raiseError($message, $code, null, null, $userinfo);
return $a; return $a;
} else {
$a = &PEAR::raiseError($message, $code, null, null, $userinfo);
return $a;
} }
$a = &PEAR::raiseError($message, $code, null, null, $userinfo);
return $a;
} }
// }}}
function staticPushErrorHandling($mode, $options = null) function staticPushErrorHandling($mode, $options = null)
{ {
$stack = &$GLOBALS['_PEAR_error_handler_stack']; $stack = &$GLOBALS['_PEAR_error_handler_stack'];
$def_mode = &$GLOBALS['_PEAR_default_error_mode']; $def_mode = &$GLOBALS['_PEAR_default_error_mode'];
$def_options = &$GLOBALS['_PEAR_default_error_options']; $def_options = &$GLOBALS['_PEAR_default_error_options'];
$stack[] = array($def_mode, $def_options); $stack[] = array($def_mode, $def_options);
@ -673,8 +634,6 @@ class PEAR
return true; return true;
} }
// {{{ pushErrorHandling()
/** /**
* Push a new error handler on top of the error handler options stack. With this * Push a new error handler on top of the error handler options stack. With this
* you can easily override the actual error handler for some code and restore * you can easily override the actual error handler for some code and restore
@ -708,9 +667,6 @@ class PEAR
return true; return true;
} }
// }}}
// {{{ popErrorHandling()
/** /**
* Pop the last error handler used * Pop the last error handler used
* *
@ -732,9 +688,6 @@ class PEAR
return true; return true;
} }
// }}}
// {{{ loadExtension()
/** /**
* OS independant PHP extension load. Remember to take care * OS independant PHP extension load. Remember to take care
* on the correct extension name for case sensitive OSes. * on the correct extension name for case sensitive OSes.
@ -744,31 +697,38 @@ class PEAR
*/ */
function loadExtension($ext) function loadExtension($ext)
{ {
if (!extension_loaded($ext)) { if (extension_loaded($ext)) {
// if either returns true dl() will produce a FATAL error, stop that return true;
if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) {
return false;
}
if (OS_WINDOWS) {
$suffix = '.dll';
} elseif (PHP_OS == 'HP-UX') {
$suffix = '.sl';
} elseif (PHP_OS == 'AIX') {
$suffix = '.a';
} elseif (PHP_OS == 'OSX') {
$suffix = '.bundle';
} else {
$suffix = '.so';
}
return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
} }
return true;
}
// }}} // if either returns true dl() will produce a FATAL error, stop that
if (
function_exists('dl') === false ||
ini_get('enable_dl') != 1 ||
ini_get('safe_mode') == 1
) {
return false;
}
if (OS_WINDOWS) {
$suffix = '.dll';
} elseif (PHP_OS == 'HP-UX') {
$suffix = '.sl';
} elseif (PHP_OS == 'AIX') {
$suffix = '.a';
} elseif (PHP_OS == 'OSX') {
$suffix = '.bundle';
} else {
$suffix = '.so';
}
return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
}
} }
// {{{ _PEAR_call_destructors() if (PEAR_ZE2) {
include_once 'PEAR5.php';
}
function _PEAR_call_destructors() function _PEAR_call_destructors()
{ {
@ -777,9 +737,16 @@ function _PEAR_call_destructors()
sizeof($_PEAR_destructor_object_list)) sizeof($_PEAR_destructor_object_list))
{ {
reset($_PEAR_destructor_object_list); reset($_PEAR_destructor_object_list);
if (PEAR::getStaticProperty('PEAR', 'destructlifo')) { if (PEAR_ZE2) {
$destructLifoExists = PEAR5::getStaticProperty('PEAR', 'destructlifo');
} else {
$destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo');
}
if ($destructLifoExists) {
$_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list); $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list);
} }
while (list($k, $objref) = each($_PEAR_destructor_object_list)) { while (list($k, $objref) = each($_PEAR_destructor_object_list)) {
$classname = get_class($objref); $classname = get_class($objref);
while ($classname) { while ($classname) {
@ -798,14 +765,17 @@ function _PEAR_call_destructors()
} }
// Now call the shutdown functions // Now call the shutdown functions
if (is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !empty($GLOBALS['_PEAR_shutdown_funcs'])) { if (
isset($GLOBALS['_PEAR_shutdown_funcs']) &&
is_array($GLOBALS['_PEAR_shutdown_funcs']) &&
!empty($GLOBALS['_PEAR_shutdown_funcs'])
) {
foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) { foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) {
call_user_func_array($value[0], $value[1]); call_user_func_array($value[0], $value[1]);
} }
} }
} }
// }}}
/** /**
* Standard PEAR error class for PHP 4 * Standard PEAR error class for PHP 4
* *
@ -817,16 +787,14 @@ function _PEAR_call_destructors()
* @author Tomas V.V. Cox <cox@idecnet.com> * @author Tomas V.V. Cox <cox@idecnet.com>
* @author Gregory Beaver <cellog@php.net> * @author Gregory Beaver <cellog@php.net>
* @copyright 1997-2006 The PHP Group * @copyright 1997-2006 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: 1.7.2 * @version Release: 1.9.2
* @link http://pear.php.net/manual/en/core.pear.pear-error.php * @link http://pear.php.net/manual/en/core.pear.pear-error.php
* @see PEAR::raiseError(), PEAR::throwError() * @see PEAR::raiseError(), PEAR::throwError()
* @since Class available since PHP 4.0.2 * @since Class available since PHP 4.0.2
*/ */
class PEAR_Error class PEAR_Error
{ {
// {{{ properties
var $error_message_prefix = ''; var $error_message_prefix = '';
var $mode = PEAR_ERROR_RETURN; var $mode = PEAR_ERROR_RETURN;
var $level = E_USER_NOTICE; var $level = E_USER_NOTICE;
@ -835,9 +803,6 @@ class PEAR_Error
var $userinfo = ''; var $userinfo = '';
var $backtrace = null; var $backtrace = null;
// }}}
// {{{ constructor
/** /**
* PEAR_Error constructor * PEAR_Error constructor
* *
@ -868,12 +833,20 @@ class PEAR_Error
$this->code = $code; $this->code = $code;
$this->mode = $mode; $this->mode = $mode;
$this->userinfo = $userinfo; $this->userinfo = $userinfo;
if (!PEAR::getStaticProperty('PEAR_Error', 'skiptrace')) {
if (PEAR_ZE2) {
$skiptrace = PEAR5::getStaticProperty('PEAR_Error', 'skiptrace');
} else {
$skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace');
}
if (!$skiptrace) {
$this->backtrace = debug_backtrace(); $this->backtrace = debug_backtrace();
if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) { if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) {
unset($this->backtrace[0]['object']); unset($this->backtrace[0]['object']);
} }
} }
if ($mode & PEAR_ERROR_CALLBACK) { if ($mode & PEAR_ERROR_CALLBACK) {
$this->level = E_USER_NOTICE; $this->level = E_USER_NOTICE;
$this->callback = $options; $this->callback = $options;
@ -881,20 +854,25 @@ class PEAR_Error
if ($options === null) { if ($options === null) {
$options = E_USER_NOTICE; $options = E_USER_NOTICE;
} }
$this->level = $options; $this->level = $options;
$this->callback = null; $this->callback = null;
} }
if ($this->mode & PEAR_ERROR_PRINT) { if ($this->mode & PEAR_ERROR_PRINT) {
if (is_null($options) || is_int($options)) { if (is_null($options) || is_int($options)) {
$format = "%s"; $format = "%s";
} else { } else {
$format = $options; $format = $options;
} }
printf($format, $this->getMessage()); printf($format, $this->getMessage());
} }
if ($this->mode & PEAR_ERROR_TRIGGER) { if ($this->mode & PEAR_ERROR_TRIGGER) {
trigger_error($this->getMessage(), $this->level); trigger_error($this->getMessage(), $this->level);
} }
if ($this->mode & PEAR_ERROR_DIE) { if ($this->mode & PEAR_ERROR_DIE) {
$msg = $this->getMessage(); $msg = $this->getMessage();
if (is_null($options) || is_int($options)) { if (is_null($options) || is_int($options)) {
@ -907,47 +885,39 @@ class PEAR_Error
} }
die(sprintf($format, $msg)); die(sprintf($format, $msg));
} }
if ($this->mode & PEAR_ERROR_CALLBACK) {
if (is_callable($this->callback)) { if ($this->mode & PEAR_ERROR_CALLBACK && is_callable($this->callback)) {
call_user_func($this->callback, $this); call_user_func($this->callback, $this);
}
} }
if ($this->mode & PEAR_ERROR_EXCEPTION) { if ($this->mode & PEAR_ERROR_EXCEPTION) {
trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING); trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING);
eval('$e = new Exception($this->message, $this->code);throw($e);'); eval('$e = new Exception($this->message, $this->code);throw($e);');
} }
} }
// }}}
// {{{ getMode()
/** /**
* Get the error mode from an error object. * Get the error mode from an error object.
* *
* @return int error mode * @return int error mode
* @access public * @access public
*/ */
function getMode() { function getMode()
{
return $this->mode; return $this->mode;
} }
// }}}
// {{{ getCallback()
/** /**
* Get the callback function/method from an error object. * Get the callback function/method from an error object.
* *
* @return mixed callback function or object/method array * @return mixed callback function or object/method array
* @access public * @access public
*/ */
function getCallback() { function getCallback()
{
return $this->callback; return $this->callback;
} }
// }}}
// {{{ getMessage()
/** /**
* Get the error message from an error object. * Get the error message from an error object.
* *
@ -959,10 +929,6 @@ class PEAR_Error
return ($this->error_message_prefix . $this->message); return ($this->error_message_prefix . $this->message);
} }
// }}}
// {{{ getCode()
/** /**
* Get error code from an error object * Get error code from an error object
* *
@ -974,9 +940,6 @@ class PEAR_Error
return $this->code; return $this->code;
} }
// }}}
// {{{ getType()
/** /**
* Get the name of this error/exception. * Get the name of this error/exception.
* *
@ -988,9 +951,6 @@ class PEAR_Error
return get_class($this); return get_class($this);
} }
// }}}
// {{{ getUserInfo()
/** /**
* Get additional user-supplied information. * Get additional user-supplied information.
* *
@ -1002,9 +962,6 @@ class PEAR_Error
return $this->userinfo; return $this->userinfo;
} }
// }}}
// {{{ getDebugInfo()
/** /**
* Get additional debug information supplied by the application. * Get additional debug information supplied by the application.
* *
@ -1016,9 +973,6 @@ class PEAR_Error
return $this->getUserInfo(); return $this->getUserInfo();
} }
// }}}
// {{{ getBacktrace()
/** /**
* Get the call backtrace from where the error was generated. * Get the call backtrace from where the error was generated.
* Supported with PHP 4.3.0 or newer. * Supported with PHP 4.3.0 or newer.
@ -1038,9 +992,6 @@ class PEAR_Error
return $this->backtrace[$frame]; return $this->backtrace[$frame];
} }
// }}}
// {{{ addUserInfo()
function addUserInfo($info) function addUserInfo($info)
{ {
if (empty($this->userinfo)) { if (empty($this->userinfo)) {
@ -1050,14 +1001,10 @@ class PEAR_Error
} }
} }
// }}}
// {{{ toString()
function __toString() function __toString()
{ {
return $this->getMessage(); return $this->getMessage();
} }
// }}}
// {{{ toString()
/** /**
* Make a string representation of this object. * Make a string representation of this object.
@ -1065,7 +1012,8 @@ class PEAR_Error
* @return string a string with an object summary * @return string a string with an object summary
* @access public * @access public
*/ */
function toString() { function toString()
{
$modes = array(); $modes = array();
$levels = array(E_USER_NOTICE => 'notice', $levels = array(E_USER_NOTICE => 'notice',
E_USER_WARNING => 'warning', E_USER_WARNING => 'warning',
@ -1104,8 +1052,6 @@ class PEAR_Error
$this->error_message_prefix, $this->error_message_prefix,
$this->userinfo); $this->userinfo);
} }
// }}}
} }
/* /*
@ -1115,4 +1061,3 @@ class PEAR_Error
* c-basic-offset: 4 * c-basic-offset: 4
* End: * End:
*/ */
?>

985
extlib/PEAR/ErrorStack.php Normal file
View File

@ -0,0 +1,985 @@
<?php
/**
* Error Stack Implementation
*
* This is an incredibly simple implementation of a very complex error handling
* facility. It contains the ability
* to track multiple errors from multiple packages simultaneously. In addition,
* it can track errors of many levels, save data along with the error, context
* information such as the exact file, line number, class and function that
* generated the error, and if necessary, it can raise a traditional PEAR_Error.
* It has built-in support for PEAR::Log, to log errors as they occur
*
* Since version 0.2alpha, it is also possible to selectively ignore errors,
* through the use of an error callback, see {@link pushCallback()}
*
* Since version 0.3alpha, it is possible to specify the exception class
* returned from {@link push()}
*
* Since version PEAR1.3.2, ErrorStack no longer instantiates an exception class. This can
* still be done quite handily in an error callback or by manipulating the returned array
* @category Debugging
* @package PEAR_ErrorStack
* @author Greg Beaver <cellog@php.net>
* @copyright 2004-2008 Greg Beaver
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id: ErrorStack.php 307683 2011-01-23 21:56:12Z dufuz $
* @link http://pear.php.net/package/PEAR_ErrorStack
*/
/**
* Singleton storage
*
* Format:
* <pre>
* array(
* 'package1' => PEAR_ErrorStack object,
* 'package2' => PEAR_ErrorStack object,
* ...
* )
* </pre>
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_SINGLETON']
*/
$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] = array();
/**
* Global error callback (default)
*
* This is only used if set to non-false. * is the default callback for
* all packages, whereas specific packages may set a default callback
* for all instances, regardless of whether they are a singleton or not.
*
* To exclude non-singletons, only set the local callback for the singleton
* @see PEAR_ErrorStack::setDefaultCallback()
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']
*/
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] = array(
'*' => false,
);
/**
* Global Log object (default)
*
* This is only used if set to non-false. Use to set a default log object for
* all stacks, regardless of instantiation order or location
* @see PEAR_ErrorStack::setDefaultLogger()
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
*/
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = false;
/**
* Global Overriding Callback
*
* This callback will override any error callbacks that specific loggers have set.
* Use with EXTREME caution
* @see PEAR_ErrorStack::staticPushCallback()
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
*/
$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
/**#@+
* One of four possible return values from the error Callback
* @see PEAR_ErrorStack::_errorCallback()
*/
/**
* If this is returned, then the error will be both pushed onto the stack
* and logged.
*/
define('PEAR_ERRORSTACK_PUSHANDLOG', 1);
/**
* If this is returned, then the error will only be pushed onto the stack,
* and not logged.
*/
define('PEAR_ERRORSTACK_PUSH', 2);
/**
* If this is returned, then the error will only be logged, but not pushed
* onto the error stack.
*/
define('PEAR_ERRORSTACK_LOG', 3);
/**
* If this is returned, then the error is completely ignored.
*/
define('PEAR_ERRORSTACK_IGNORE', 4);
/**
* If this is returned, then the error is logged and die() is called.
*/
define('PEAR_ERRORSTACK_DIE', 5);
/**#@-*/
/**
* Error code for an attempt to instantiate a non-class as a PEAR_ErrorStack in
* the singleton method.
*/
define('PEAR_ERRORSTACK_ERR_NONCLASS', 1);
/**
* Error code for an attempt to pass an object into {@link PEAR_ErrorStack::getMessage()}
* that has no __toString() method
*/
define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2);
/**
* Error Stack Implementation
*
* Usage:
* <code>
* // global error stack
* $global_stack = &PEAR_ErrorStack::singleton('MyPackage');
* // local error stack
* $local_stack = new PEAR_ErrorStack('MyPackage');
* </code>
* @author Greg Beaver <cellog@php.net>
* @version 1.9.2
* @package PEAR_ErrorStack
* @category Debugging
* @copyright 2004-2008 Greg Beaver
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id: ErrorStack.php 307683 2011-01-23 21:56:12Z dufuz $
* @link http://pear.php.net/package/PEAR_ErrorStack
*/
class PEAR_ErrorStack {
/**
* Errors are stored in the order that they are pushed on the stack.
* @since 0.4alpha Errors are no longer organized by error level.
* This renders pop() nearly unusable, and levels could be more easily
* handled in a callback anyway
* @var array
* @access private
*/
var $_errors = array();
/**
* Storage of errors by level.
*
* Allows easy retrieval and deletion of only errors from a particular level
* @since PEAR 1.4.0dev
* @var array
* @access private
*/
var $_errorsByLevel = array();
/**
* Package name this error stack represents
* @var string
* @access protected
*/
var $_package;
/**
* Determines whether a PEAR_Error is thrown upon every error addition
* @var boolean
* @access private
*/
var $_compat = false;
/**
* If set to a valid callback, this will be used to generate the error
* message from the error code, otherwise the message passed in will be
* used
* @var false|string|array
* @access private
*/
var $_msgCallback = false;
/**
* If set to a valid callback, this will be used to generate the error
* context for an error. For PHP-related errors, this will be a file
* and line number as retrieved from debug_backtrace(), but can be
* customized for other purposes. The error might actually be in a separate
* configuration file, or in a database query.
* @var false|string|array
* @access protected
*/
var $_contextCallback = false;
/**
* If set to a valid callback, this will be called every time an error
* is pushed onto the stack. The return value will be used to determine
* whether to allow an error to be pushed or logged.
*
* The return value must be one an PEAR_ERRORSTACK_* constant
* @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
* @var false|string|array
* @access protected
*/
var $_errorCallback = array();
/**
* PEAR::Log object for logging errors
* @var false|Log
* @access protected
*/
var $_logger = false;
/**
* Error messages - designed to be overridden
* @var array
* @abstract
*/
var $_errorMsgs = array();
/**
* Set up a new error stack
*
* @param string $package name of the package this error stack represents
* @param callback $msgCallback callback used for error message generation
* @param callback $contextCallback callback used for context generation,
* defaults to {@link getFileLine()}
* @param boolean $throwPEAR_Error
*/
function PEAR_ErrorStack($package, $msgCallback = false, $contextCallback = false,
$throwPEAR_Error = false)
{
$this->_package = $package;
$this->setMessageCallback($msgCallback);
$this->setContextCallback($contextCallback);
$this->_compat = $throwPEAR_Error;
}
/**
* Return a single error stack for this package.
*
* Note that all parameters are ignored if the stack for package $package
* has already been instantiated
* @param string $package name of the package this error stack represents
* @param callback $msgCallback callback used for error message generation
* @param callback $contextCallback callback used for context generation,
* defaults to {@link getFileLine()}
* @param boolean $throwPEAR_Error
* @param string $stackClass class to instantiate
* @static
* @return PEAR_ErrorStack
*/
function &singleton($package, $msgCallback = false, $contextCallback = false,
$throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack')
{
if (isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
}
if (!class_exists($stackClass)) {
if (function_exists('debug_backtrace')) {
$trace = debug_backtrace();
}
PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_NONCLASS,
'exception', array('stackclass' => $stackClass),
'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)',
false, $trace);
}
$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package] =
new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error);
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
}
/**
* Internal error handler for PEAR_ErrorStack class
*
* Dies if the error is an exception (and would have died anyway)
* @access private
*/
function _handleError($err)
{
if ($err['level'] == 'exception') {
$message = $err['message'];
if (isset($_SERVER['REQUEST_URI'])) {
echo '<br />';
} else {
echo "\n";
}
var_dump($err['context']);
die($message);
}
}
/**
* Set up a PEAR::Log object for all error stacks that don't have one
* @param Log $log
* @static
*/
function setDefaultLogger(&$log)
{
if (is_object($log) && method_exists($log, 'log') ) {
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
} elseif (is_callable($log)) {
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
}
}
/**
* Set up a PEAR::Log object for this error stack
* @param Log $log
*/
function setLogger(&$log)
{
if (is_object($log) && method_exists($log, 'log') ) {
$this->_logger = &$log;
} elseif (is_callable($log)) {
$this->_logger = &$log;
}
}
/**
* Set an error code => error message mapping callback
*
* This method sets the callback that can be used to generate error
* messages for any instance
* @param array|string Callback function/method
*/
function setMessageCallback($msgCallback)
{
if (!$msgCallback) {
$this->_msgCallback = array(&$this, 'getErrorMessage');
} else {
if (is_callable($msgCallback)) {
$this->_msgCallback = $msgCallback;
}
}
}
/**
* Get an error code => error message mapping callback
*
* This method returns the current callback that can be used to generate error
* messages
* @return array|string|false Callback function/method or false if none
*/
function getMessageCallback()
{
return $this->_msgCallback;
}
/**
* Sets a default callback to be used by all error stacks
*
* This method sets the callback that can be used to generate error
* messages for a singleton
* @param array|string Callback function/method
* @param string Package name, or false for all packages
* @static
*/
function setDefaultCallback($callback = false, $package = false)
{
if (!is_callable($callback)) {
$callback = false;
}
$package = $package ? $package : '*';
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$package] = $callback;
}
/**
* Set a callback that generates context information (location of error) for an error stack
*
* This method sets the callback that can be used to generate context
* information for an error. Passing in NULL will disable context generation
* and remove the expensive call to debug_backtrace()
* @param array|string|null Callback function/method
*/
function setContextCallback($contextCallback)
{
if ($contextCallback === null) {
return $this->_contextCallback = false;
}
if (!$contextCallback) {
$this->_contextCallback = array(&$this, 'getFileLine');
} else {
if (is_callable($contextCallback)) {
$this->_contextCallback = $contextCallback;
}
}
}
/**
* Set an error Callback
* If set to a valid callback, this will be called every time an error
* is pushed onto the stack. The return value will be used to determine
* whether to allow an error to be pushed or logged.
*
* The return value must be one of the ERRORSTACK_* constants.
*
* This functionality can be used to emulate PEAR's pushErrorHandling, and
* the PEAR_ERROR_CALLBACK mode, without affecting the integrity of
* the error stack or logging
* @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
* @see popCallback()
* @param string|array $cb
*/
function pushCallback($cb)
{
array_push($this->_errorCallback, $cb);
}
/**
* Remove a callback from the error callback stack
* @see pushCallback()
* @return array|string|false
*/
function popCallback()
{
if (!count($this->_errorCallback)) {
return false;
}
return array_pop($this->_errorCallback);
}
/**
* Set a temporary overriding error callback for every package error stack
*
* Use this to temporarily disable all existing callbacks (can be used
* to emulate the @ operator, for instance)
* @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
* @see staticPopCallback(), pushCallback()
* @param string|array $cb
* @static
*/
function staticPushCallback($cb)
{
array_push($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'], $cb);
}
/**
* Remove a temporary overriding error callback
* @see staticPushCallback()
* @return array|string|false
* @static
*/
function staticPopCallback()
{
$ret = array_pop($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK']);
if (!is_array($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'])) {
$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
}
return $ret;
}
/**
* Add an error to the stack
*
* If the message generator exists, it is called with 2 parameters.
* - the current Error Stack object
* - an array that is in the same format as an error. Available indices
* are 'code', 'package', 'time', 'params', 'level', and 'context'
*
* Next, if the error should contain context information, this is
* handled by the context grabbing method.
* Finally, the error is pushed onto the proper error stack
* @param int $code Package-specific error code
* @param string $level Error level. This is NOT spell-checked
* @param array $params associative array of error parameters
* @param string $msg Error message, or a portion of it if the message
* is to be generated
* @param array $repackage If this error re-packages an error pushed by
* another package, place the array returned from
* {@link pop()} in this parameter
* @param array $backtrace Protected parameter: use this to pass in the
* {@link debug_backtrace()} that should be used
* to find error context
* @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
* thrown. If a PEAR_Error is returned, the userinfo
* property is set to the following array:
*
* <code>
* array(
* 'code' => $code,
* 'params' => $params,
* 'package' => $this->_package,
* 'level' => $level,
* 'time' => time(),
* 'context' => $context,
* 'message' => $msg,
* //['repackage' => $err] repackaged error array/Exception class
* );
* </code>
*
* Normally, the previous array is returned.
*/
function push($code, $level = 'error', $params = array(), $msg = false,
$repackage = false, $backtrace = false)
{
$context = false;
// grab error context
if ($this->_contextCallback) {
if (!$backtrace) {
$backtrace = debug_backtrace();
}
$context = call_user_func($this->_contextCallback, $code, $params, $backtrace);
}
// save error
$time = explode(' ', microtime());
$time = $time[1] + $time[0];
$err = array(
'code' => $code,
'params' => $params,
'package' => $this->_package,
'level' => $level,
'time' => $time,
'context' => $context,
'message' => $msg,
);
if ($repackage) {
$err['repackage'] = $repackage;
}
// set up the error message, if necessary
if ($this->_msgCallback) {
$msg = call_user_func_array($this->_msgCallback,
array(&$this, $err));
$err['message'] = $msg;
}
$push = $log = true;
$die = false;
// try the overriding callback first
$callback = $this->staticPopCallback();
if ($callback) {
$this->staticPushCallback($callback);
}
if (!is_callable($callback)) {
// try the local callback next
$callback = $this->popCallback();
if (is_callable($callback)) {
$this->pushCallback($callback);
} else {
// try the default callback
$callback = isset($GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package]) ?
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package] :
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']['*'];
}
}
if (is_callable($callback)) {
switch(call_user_func($callback, $err)){
case PEAR_ERRORSTACK_IGNORE:
return $err;
break;
case PEAR_ERRORSTACK_PUSH:
$log = false;
break;
case PEAR_ERRORSTACK_LOG:
$push = false;
break;
case PEAR_ERRORSTACK_DIE:
$die = true;
break;
// anything else returned has the same effect as pushandlog
}
}
if ($push) {
array_unshift($this->_errors, $err);
if (!isset($this->_errorsByLevel[$err['level']])) {
$this->_errorsByLevel[$err['level']] = array();
}
$this->_errorsByLevel[$err['level']][] = &$this->_errors[0];
}
if ($log) {
if ($this->_logger || $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']) {
$this->_log($err);
}
}
if ($die) {
die();
}
if ($this->_compat && $push) {
return $this->raiseError($msg, $code, null, null, $err);
}
return $err;
}
/**
* Static version of {@link push()}
*
* @param string $package Package name this error belongs to
* @param int $code Package-specific error code
* @param string $level Error level. This is NOT spell-checked
* @param array $params associative array of error parameters
* @param string $msg Error message, or a portion of it if the message
* is to be generated
* @param array $repackage If this error re-packages an error pushed by
* another package, place the array returned from
* {@link pop()} in this parameter
* @param array $backtrace Protected parameter: use this to pass in the
* {@link debug_backtrace()} that should be used
* to find error context
* @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
* thrown. see docs for {@link push()}
* @static
*/
function staticPush($package, $code, $level = 'error', $params = array(),
$msg = false, $repackage = false, $backtrace = false)
{
$s = &PEAR_ErrorStack::singleton($package);
if ($s->_contextCallback) {
if (!$backtrace) {
if (function_exists('debug_backtrace')) {
$backtrace = debug_backtrace();
}
}
}
return $s->push($code, $level, $params, $msg, $repackage, $backtrace);
}
/**
* Log an error using PEAR::Log
* @param array $err Error array
* @param array $levels Error level => Log constant map
* @access protected
*/
function _log($err)
{
if ($this->_logger) {
$logger = &$this->_logger;
} else {
$logger = &$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'];
}
if (is_a($logger, 'Log')) {
$levels = array(
'exception' => PEAR_LOG_CRIT,
'alert' => PEAR_LOG_ALERT,
'critical' => PEAR_LOG_CRIT,
'error' => PEAR_LOG_ERR,
'warning' => PEAR_LOG_WARNING,
'notice' => PEAR_LOG_NOTICE,
'info' => PEAR_LOG_INFO,
'debug' => PEAR_LOG_DEBUG);
if (isset($levels[$err['level']])) {
$level = $levels[$err['level']];
} else {
$level = PEAR_LOG_INFO;
}
$logger->log($err['message'], $level, $err);
} else { // support non-standard logs
call_user_func($logger, $err);
}
}
/**
* Pop an error off of the error stack
*
* @return false|array
* @since 0.4alpha it is no longer possible to specify a specific error
* level to return - the last error pushed will be returned, instead
*/
function pop()
{
$err = @array_shift($this->_errors);
if (!is_null($err)) {
@array_pop($this->_errorsByLevel[$err['level']]);
if (!count($this->_errorsByLevel[$err['level']])) {
unset($this->_errorsByLevel[$err['level']]);
}
}
return $err;
}
/**
* Pop an error off of the error stack, static method
*
* @param string package name
* @return boolean
* @since PEAR1.5.0a1
*/
function staticPop($package)
{
if ($package) {
if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
return false;
}
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->pop();
}
}
/**
* Determine whether there are any errors on the stack
* @param string|array Level name. Use to determine if any errors
* of level (string), or levels (array) have been pushed
* @return boolean
*/
function hasErrors($level = false)
{
if ($level) {
return isset($this->_errorsByLevel[$level]);
}
return count($this->_errors);
}
/**
* Retrieve all errors since last purge
*
* @param boolean set in order to empty the error stack
* @param string level name, to return only errors of a particular severity
* @return array
*/
function getErrors($purge = false, $level = false)
{
if (!$purge) {
if ($level) {
if (!isset($this->_errorsByLevel[$level])) {
return array();
} else {
return $this->_errorsByLevel[$level];
}
} else {
return $this->_errors;
}
}
if ($level) {
$ret = $this->_errorsByLevel[$level];
foreach ($this->_errorsByLevel[$level] as $i => $unused) {
// entries are references to the $_errors array
$this->_errorsByLevel[$level][$i] = false;
}
// array_filter removes all entries === false
$this->_errors = array_filter($this->_errors);
unset($this->_errorsByLevel[$level]);
return $ret;
}
$ret = $this->_errors;
$this->_errors = array();
$this->_errorsByLevel = array();
return $ret;
}
/**
* Determine whether there are any errors on a single error stack, or on any error stack
*
* The optional parameter can be used to test the existence of any errors without the need of
* singleton instantiation
* @param string|false Package name to check for errors
* @param string Level name to check for a particular severity
* @return boolean
* @static
*/
function staticHasErrors($package = false, $level = false)
{
if ($package) {
if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
return false;
}
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->hasErrors($level);
}
foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
if ($obj->hasErrors($level)) {
return true;
}
}
return false;
}
/**
* Get a list of all errors since last purge, organized by package
* @since PEAR 1.4.0dev BC break! $level is now in the place $merge used to be
* @param boolean $purge Set to purge the error stack of existing errors
* @param string $level Set to a level name in order to retrieve only errors of a particular level
* @param boolean $merge Set to return a flat array, not organized by package
* @param array $sortfunc Function used to sort a merged array - default
* sorts by time, and should be good for most cases
* @static
* @return array
*/
function staticGetErrors($purge = false, $level = false, $merge = false,
$sortfunc = array('PEAR_ErrorStack', '_sortErrors'))
{
$ret = array();
if (!is_callable($sortfunc)) {
$sortfunc = array('PEAR_ErrorStack', '_sortErrors');
}
foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
$test = $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->getErrors($purge, $level);
if ($test) {
if ($merge) {
$ret = array_merge($ret, $test);
} else {
$ret[$package] = $test;
}
}
}
if ($merge) {
usort($ret, $sortfunc);
}
return $ret;
}
/**
* Error sorting function, sorts by time
* @access private
*/
function _sortErrors($a, $b)
{
if ($a['time'] == $b['time']) {
return 0;
}
if ($a['time'] < $b['time']) {
return 1;
}
return -1;
}
/**
* Standard file/line number/function/class context callback
*
* This function uses a backtrace generated from {@link debug_backtrace()}
* and so will not work at all in PHP < 4.3.0. The frame should
* reference the frame that contains the source of the error.
* @return array|false either array('file' => file, 'line' => line,
* 'function' => function name, 'class' => class name) or
* if this doesn't work, then false
* @param unused
* @param integer backtrace frame.
* @param array Results of debug_backtrace()
* @static
*/
function getFileLine($code, $params, $backtrace = null)
{
if ($backtrace === null) {
return false;
}
$frame = 0;
$functionframe = 1;
if (!isset($backtrace[1])) {
$functionframe = 0;
} else {
while (isset($backtrace[$functionframe]['function']) &&
$backtrace[$functionframe]['function'] == 'eval' &&
isset($backtrace[$functionframe + 1])) {
$functionframe++;
}
}
if (isset($backtrace[$frame])) {
if (!isset($backtrace[$frame]['file'])) {
$frame++;
}
$funcbacktrace = $backtrace[$functionframe];
$filebacktrace = $backtrace[$frame];
$ret = array('file' => $filebacktrace['file'],
'line' => $filebacktrace['line']);
// rearrange for eval'd code or create function errors
if (strpos($filebacktrace['file'], '(') &&
preg_match(';^(.*?)\((\d+)\) : (.*?)\\z;', $filebacktrace['file'],
$matches)) {
$ret['file'] = $matches[1];
$ret['line'] = $matches[2] + 0;
}
if (isset($funcbacktrace['function']) && isset($backtrace[1])) {
if ($funcbacktrace['function'] != 'eval') {
if ($funcbacktrace['function'] == '__lambda_func') {
$ret['function'] = 'create_function() code';
} else {
$ret['function'] = $funcbacktrace['function'];
}
}
}
if (isset($funcbacktrace['class']) && isset($backtrace[1])) {
$ret['class'] = $funcbacktrace['class'];
}
return $ret;
}
return false;
}
/**
* Standard error message generation callback
*
* This method may also be called by a custom error message generator
* to fill in template values from the params array, simply
* set the third parameter to the error message template string to use
*
* The special variable %__msg% is reserved: use it only to specify
* where a message passed in by the user should be placed in the template,
* like so:
*
* Error message: %msg% - internal error
*
* If the message passed like so:
*
* <code>
* $stack->push(ERROR_CODE, 'error', array(), 'server error 500');
* </code>
*
* The returned error message will be "Error message: server error 500 -
* internal error"
* @param PEAR_ErrorStack
* @param array
* @param string|false Pre-generated error message template
* @static
* @return string
*/
function getErrorMessage(&$stack, $err, $template = false)
{
if ($template) {
$mainmsg = $template;
} else {
$mainmsg = $stack->getErrorMessageTemplate($err['code']);
}
$mainmsg = str_replace('%__msg%', $err['message'], $mainmsg);
if (is_array($err['params']) && count($err['params'])) {
foreach ($err['params'] as $name => $val) {
if (is_array($val)) {
// @ is needed in case $val is a multi-dimensional array
$val = @implode(', ', $val);
}
if (is_object($val)) {
if (method_exists($val, '__toString')) {
$val = $val->__toString();
} else {
PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_OBJTOSTRING,
'warning', array('obj' => get_class($val)),
'object %obj% passed into getErrorMessage, but has no __toString() method');
$val = 'Object';
}
}
$mainmsg = str_replace('%' . $name . '%', $val, $mainmsg);
}
}
return $mainmsg;
}
/**
* Standard Error Message Template generator from code
* @return string
*/
function getErrorMessageTemplate($code)
{
if (!isset($this->_errorMsgs[$code])) {
return '%__msg%';
}
return $this->_errorMsgs[$code];
}
/**
* Set the Error Message Template array
*
* The array format must be:
* <pre>
* array(error code => 'message template',...)
* </pre>
*
* Error message parameters passed into {@link push()} will be used as input
* for the error message. If the template is 'message %foo% was %bar%', and the
* parameters are array('foo' => 'one', 'bar' => 'six'), the error message returned will
* be 'message one was six'
* @return string
*/
function setErrorMessageTemplate($template)
{
$this->_errorMsgs = $template;
}
/**
* emulate PEAR::raiseError()
*
* @return PEAR_Error
*/
function raiseError()
{
require_once 'PEAR.php';
$args = func_get_args();
return call_user_func_array(array('PEAR', 'raiseError'), $args);
}
}
$stack = &PEAR_ErrorStack::singleton('PEAR_ErrorStack');
$stack->pushCallback(array('PEAR_ErrorStack', '_handleError'));
?>

View File

@ -5,21 +5,15 @@
* *
* PHP versions 4 and 5 * PHP versions 4 and 5
* *
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category pear * @category pear
* @package PEAR * @package PEAR
* @author Tomas V. V. Cox <cox@idecnet.com> * @author Tomas V. V. Cox <cox@idecnet.com>
* @author Hans Lellelid <hans@velum.net> * @author Hans Lellelid <hans@velum.net>
* @author Bertrand Mansion <bmansion@mamasam.com> * @author Bertrand Mansion <bmansion@mamasam.com>
* @author Greg Beaver <cellog@php.net> * @author Greg Beaver <cellog@php.net>
* @copyright 1997-2008 The PHP Group * @copyright 1997-2009 The Authors
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id: Exception.php,v 1.29 2008/01/03 20:26:35 cellog Exp $ * @version CVS: $Id: Exception.php 307683 2011-01-23 21:56:12Z dufuz $
* @link http://pear.php.net/package/PEAR * @link http://pear.php.net/package/PEAR
* @since File available since Release 1.3.3 * @since File available since Release 1.3.3
*/ */
@ -93,9 +87,9 @@
* @author Hans Lellelid <hans@velum.net> * @author Hans Lellelid <hans@velum.net>
* @author Bertrand Mansion <bmansion@mamasam.com> * @author Bertrand Mansion <bmansion@mamasam.com>
* @author Greg Beaver <cellog@php.net> * @author Greg Beaver <cellog@php.net>
* @copyright 1997-2008 The PHP Group * @copyright 1997-2009 The Authors
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: 1.7.2 * @version Release: 1.9.2
* @link http://pear.php.net/package/PEAR * @link http://pear.php.net/package/PEAR
* @since Class available since Release 1.3.3 * @since Class available since Release 1.3.3
* *
@ -331,21 +325,21 @@ class PEAR_Exception extends Exception
$trace = $this->getTraceSafe(); $trace = $this->getTraceSafe();
$causes = array(); $causes = array();
$this->getCauseMessage($causes); $this->getCauseMessage($causes);
$html = '<table border="1" cellspacing="0">' . "\n"; $html = '<table style="border: 1px" cellspacing="0">' . "\n";
foreach ($causes as $i => $cause) { foreach ($causes as $i => $cause) {
$html .= '<tr><td colspan="3" bgcolor="#ff9999">' $html .= '<tr><td colspan="3" style="background: #ff9999">'
. str_repeat('-', $i) . ' <b>' . $cause['class'] . '</b>: ' . str_repeat('-', $i) . ' <b>' . $cause['class'] . '</b>: '
. htmlspecialchars($cause['message']) . ' in <b>' . $cause['file'] . '</b> ' . htmlspecialchars($cause['message']) . ' in <b>' . $cause['file'] . '</b> '
. 'on line <b>' . $cause['line'] . '</b>' . 'on line <b>' . $cause['line'] . '</b>'
. "</td></tr>\n"; . "</td></tr>\n";
} }
$html .= '<tr><td colspan="3" bgcolor="#aaaaaa" align="center"><b>Exception trace</b></td></tr>' . "\n" $html .= '<tr><td colspan="3" style="background-color: #aaaaaa; text-align: center; font-weight: bold;">Exception trace</td></tr>' . "\n"
. '<tr><td align="center" bgcolor="#cccccc" width="20"><b>#</b></td>' . '<tr><td style="text-align: center; background: #cccccc; width:20px; font-weight: bold;">#</td>'
. '<td align="center" bgcolor="#cccccc"><b>Function</b></td>' . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Function</td>'
. '<td align="center" bgcolor="#cccccc"><b>Location</b></td></tr>' . "\n"; . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Location</td></tr>' . "\n";
foreach ($trace as $k => $v) { foreach ($trace as $k => $v) {
$html .= '<tr><td align="center">' . $k . '</td>' $html .= '<tr><td style="text-align: center;">' . $k . '</td>'
. '<td>'; . '<td>';
if (!empty($v['class'])) { if (!empty($v['class'])) {
$html .= $v['class'] . $v['type']; $html .= $v['class'] . $v['type'];
@ -373,7 +367,7 @@ class PEAR_Exception extends Exception
. ':' . (isset($v['line']) ? $v['line'] : 'unknown') . ':' . (isset($v['line']) ? $v['line'] : 'unknown')
. '</td></tr>' . "\n"; . '</td></tr>' . "\n";
} }
$html .= '<tr><td align="center">' . ($k+1) . '</td>' $html .= '<tr><td style="text-align: center;">' . ($k+1) . '</td>'
. '<td>{main}</td>' . '<td>{main}</td>'
. '<td>&nbsp;</td></tr>' . "\n" . '<td>&nbsp;</td></tr>' . "\n"
. '</table>'; . '</table>';
@ -393,5 +387,3 @@ class PEAR_Exception extends Exception
return $causeMsg . $this->getTraceAsString(); return $causeMsg . $this->getTraceAsString();
} }
} }
?>

33
extlib/PEAR5.php Normal file
View File

@ -0,0 +1,33 @@
<?php
/**
* This is only meant for PHP 5 to get rid of certain strict warning
* that doesn't get hidden since it's in the shutdown function
*/
class PEAR5
{
/**
* If you have a class that's mostly/entirely static, and you need static
* properties, you can use this method to simulate them. Eg. in your method(s)
* do this: $myVar = &PEAR5::getStaticProperty('myclass', 'myVar');
* You MUST use a reference, or they will not persist!
*
* @access public
* @param string $class The calling classname, to prevent clashes
* @param string $var The variable to retrieve.
* @return mixed A reference to the variable. If not set it will be
* auto initialised to NULL.
*/
static function &getStaticProperty($class, $var)
{
static $properties;
if (!isset($properties[$class])) {
$properties[$class] = array();
}
if (!array_key_exists($var, $properties[$class])) {
$properties[$class][$var] = null;
}
return $properties[$class][$var];
}
}

621
extlib/System.php Normal file
View File

@ -0,0 +1,621 @@
<?php
/**
* File/Directory manipulation
*
* PHP versions 4 and 5
*
* @category pear
* @package System
* @author Tomas V.V.Cox <cox@idecnet.com>
* @copyright 1997-2009 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id: System.php 307683 2011-01-23 21:56:12Z dufuz $
* @link http://pear.php.net/package/PEAR
* @since File available since Release 0.1
*/
/**
* base class
*/
require_once 'PEAR.php';
require_once 'Console/Getopt.php';
$GLOBALS['_System_temp_files'] = array();
/**
* System offers cross plattform compatible system functions
*
* Static functions for different operations. Should work under
* Unix and Windows. The names and usage has been taken from its respectively
* GNU commands. The functions will return (bool) false on error and will
* trigger the error with the PHP trigger_error() function (you can silence
* the error by prefixing a '@' sign after the function call, but this
* is not recommended practice. Instead use an error handler with
* {@link set_error_handler()}).
*
* Documentation on this class you can find in:
* http://pear.php.net/manual/
*
* Example usage:
* if (!@System::rm('-r file1 dir1')) {
* print "could not delete file1 or dir1";
* }
*
* In case you need to to pass file names with spaces,
* pass the params as an array:
*
* System::rm(array('-r', $file1, $dir1));
*
* @category pear
* @package System
* @author Tomas V.V. Cox <cox@idecnet.com>
* @copyright 1997-2006 The PHP Group
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: 1.9.2
* @link http://pear.php.net/package/PEAR
* @since Class available since Release 0.1
* @static
*/
class System
{
/**
* returns the commandline arguments of a function
*
* @param string $argv the commandline
* @param string $short_options the allowed option short-tags
* @param string $long_options the allowed option long-tags
* @return array the given options and there values
* @static
* @access private
*/
function _parseArgs($argv, $short_options, $long_options = null)
{
if (!is_array($argv) && $argv !== null) {
$argv = preg_split('/\s+/', $argv, -1, PREG_SPLIT_NO_EMPTY);
}
return Console_Getopt::getopt2($argv, $short_options);
}
/**
* Output errors with PHP trigger_error(). You can silence the errors
* with prefixing a "@" sign to the function call: @System::mkdir(..);
*
* @param mixed $error a PEAR error or a string with the error message
* @return bool false
* @static
* @access private
*/
function raiseError($error)
{
if (PEAR::isError($error)) {
$error = $error->getMessage();
}
trigger_error($error, E_USER_WARNING);
return false;
}
/**
* Creates a nested array representing the structure of a directory
*
* System::_dirToStruct('dir1', 0) =>
* Array
* (
* [dirs] => Array
* (
* [0] => dir1
* )
*
* [files] => Array
* (
* [0] => dir1/file2
* [1] => dir1/file3
* )
* )
* @param string $sPath Name of the directory
* @param integer $maxinst max. deep of the lookup
* @param integer $aktinst starting deep of the lookup
* @param bool $silent if true, do not emit errors.
* @return array the structure of the dir
* @static
* @access private
*/
function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false)
{
$struct = array('dirs' => array(), 'files' => array());
if (($dir = @opendir($sPath)) === false) {
if (!$silent) {
System::raiseError("Could not open dir $sPath");
}
return $struct; // XXX could not open error
}
$struct['dirs'][] = $sPath = realpath($sPath); // XXX don't add if '.' or '..' ?
$list = array();
while (false !== ($file = readdir($dir))) {
if ($file != '.' && $file != '..') {
$list[] = $file;
}
}
closedir($dir);
natsort($list);
if ($aktinst < $maxinst || $maxinst == 0) {
foreach ($list as $val) {
$path = $sPath . DIRECTORY_SEPARATOR . $val;
if (is_dir($path) && !is_link($path)) {
$tmp = System::_dirToStruct($path, $maxinst, $aktinst+1, $silent);
$struct = array_merge_recursive($struct, $tmp);
} else {
$struct['files'][] = $path;
}
}
}
return $struct;
}
/**
* Creates a nested array representing the structure of a directory and files
*
* @param array $files Array listing files and dirs
* @return array
* @static
* @see System::_dirToStruct()
*/
function _multipleToStruct($files)
{
$struct = array('dirs' => array(), 'files' => array());
settype($files, 'array');
foreach ($files as $file) {
if (is_dir($file) && !is_link($file)) {
$tmp = System::_dirToStruct($file, 0);
$struct = array_merge_recursive($tmp, $struct);
} else {
if (!in_array($file, $struct['files'])) {
$struct['files'][] = $file;
}
}
}
return $struct;
}
/**
* The rm command for removing files.
* Supports multiple files and dirs and also recursive deletes
*
* @param string $args the arguments for rm
* @return mixed PEAR_Error or true for success
* @static
* @access public
*/
function rm($args)
{
$opts = System::_parseArgs($args, 'rf'); // "f" does nothing but I like it :-)
if (PEAR::isError($opts)) {
return System::raiseError($opts);
}
foreach ($opts[0] as $opt) {
if ($opt[0] == 'r') {
$do_recursive = true;
}
}
$ret = true;
if (isset($do_recursive)) {
$struct = System::_multipleToStruct($opts[1]);
foreach ($struct['files'] as $file) {
if (!@unlink($file)) {
$ret = false;
}
}
rsort($struct['dirs']);
foreach ($struct['dirs'] as $dir) {
if (!@rmdir($dir)) {
$ret = false;
}
}
} else {
foreach ($opts[1] as $file) {
$delete = (is_dir($file)) ? 'rmdir' : 'unlink';
if (!@$delete($file)) {
$ret = false;
}
}
}
return $ret;
}
/**
* Make directories.
*
* The -p option will create parent directories
* @param string $args the name of the director(y|ies) to create
* @return bool True for success
* @static
* @access public
*/
function mkDir($args)
{
$opts = System::_parseArgs($args, 'pm:');
if (PEAR::isError($opts)) {
return System::raiseError($opts);
}
$mode = 0777; // default mode
foreach ($opts[0] as $opt) {
if ($opt[0] == 'p') {
$create_parents = true;
} elseif ($opt[0] == 'm') {
// if the mode is clearly an octal number (starts with 0)
// convert it to decimal
if (strlen($opt[1]) && $opt[1]{0} == '0') {
$opt[1] = octdec($opt[1]);
} else {
// convert to int
$opt[1] += 0;
}
$mode = $opt[1];
}
}
$ret = true;
if (isset($create_parents)) {
foreach ($opts[1] as $dir) {
$dirstack = array();
while ((!file_exists($dir) || !is_dir($dir)) &&
$dir != DIRECTORY_SEPARATOR) {
array_unshift($dirstack, $dir);
$dir = dirname($dir);
}
while ($newdir = array_shift($dirstack)) {
if (!is_writeable(dirname($newdir))) {
$ret = false;
break;
}
if (!mkdir($newdir, $mode)) {
$ret = false;
}
}
}
} else {
foreach($opts[1] as $dir) {
if ((@file_exists($dir) || !is_dir($dir)) && !mkdir($dir, $mode)) {
$ret = false;
}
}
}
return $ret;
}
/**
* Concatenate files
*
* Usage:
* 1) $var = System::cat('sample.txt test.txt');
* 2) System::cat('sample.txt test.txt > final.txt');
* 3) System::cat('sample.txt test.txt >> final.txt');
*
* Note: as the class use fopen, urls should work also (test that)
*
* @param string $args the arguments
* @return boolean true on success
* @static
* @access public
*/
function &cat($args)
{
$ret = null;
$files = array();
if (!is_array($args)) {
$args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
}
$count_args = count($args);
for ($i = 0; $i < $count_args; $i++) {
if ($args[$i] == '>') {
$mode = 'wb';
$outputfile = $args[$i+1];
break;
} elseif ($args[$i] == '>>') {
$mode = 'ab+';
$outputfile = $args[$i+1];
break;
} else {
$files[] = $args[$i];
}
}
$outputfd = false;
if (isset($mode)) {
if (!$outputfd = fopen($outputfile, $mode)) {
$err = System::raiseError("Could not open $outputfile");
return $err;
}
$ret = true;
}
foreach ($files as $file) {
if (!$fd = fopen($file, 'r')) {
System::raiseError("Could not open $file");
continue;
}
while ($cont = fread($fd, 2048)) {
if (is_resource($outputfd)) {
fwrite($outputfd, $cont);
} else {
$ret .= $cont;
}
}
fclose($fd);
}
if (is_resource($outputfd)) {
fclose($outputfd);
}
return $ret;
}
/**
* Creates temporary files or directories. This function will remove
* the created files when the scripts finish its execution.
*
* Usage:
* 1) $tempfile = System::mktemp("prefix");
* 2) $tempdir = System::mktemp("-d prefix");
* 3) $tempfile = System::mktemp();
* 4) $tempfile = System::mktemp("-t /var/tmp prefix");
*
* prefix -> The string that will be prepended to the temp name
* (defaults to "tmp").
* -d -> A temporary dir will be created instead of a file.
* -t -> The target dir where the temporary (file|dir) will be created. If
* this param is missing by default the env vars TMP on Windows or
* TMPDIR in Unix will be used. If these vars are also missing
* c:\windows\temp or /tmp will be used.
*
* @param string $args The arguments
* @return mixed the full path of the created (file|dir) or false
* @see System::tmpdir()
* @static
* @access public
*/
function mktemp($args = null)
{
static $first_time = true;
$opts = System::_parseArgs($args, 't:d');
if (PEAR::isError($opts)) {
return System::raiseError($opts);
}
foreach ($opts[0] as $opt) {
if ($opt[0] == 'd') {
$tmp_is_dir = true;
} elseif ($opt[0] == 't') {
$tmpdir = $opt[1];
}
}
$prefix = (isset($opts[1][0])) ? $opts[1][0] : 'tmp';
if (!isset($tmpdir)) {
$tmpdir = System::tmpdir();
}
if (!System::mkDir(array('-p', $tmpdir))) {
return false;
}
$tmp = tempnam($tmpdir, $prefix);
if (isset($tmp_is_dir)) {
unlink($tmp); // be careful possible race condition here
if (!mkdir($tmp, 0700)) {
return System::raiseError("Unable to create temporary directory $tmpdir");
}
}
$GLOBALS['_System_temp_files'][] = $tmp;
if (isset($tmp_is_dir)) {
//$GLOBALS['_System_temp_files'][] = dirname($tmp);
}
if ($first_time) {
PEAR::registerShutdownFunc(array('System', '_removeTmpFiles'));
$first_time = false;
}
return $tmp;
}
/**
* Remove temporary files created my mkTemp. This function is executed
* at script shutdown time
*
* @static
* @access private
*/
function _removeTmpFiles()
{
if (count($GLOBALS['_System_temp_files'])) {
$delete = $GLOBALS['_System_temp_files'];
array_unshift($delete, '-r');
System::rm($delete);
$GLOBALS['_System_temp_files'] = array();
}
}
/**
* Get the path of the temporal directory set in the system
* by looking in its environments variables.
* Note: php.ini-recommended removes the "E" from the variables_order setting,
* making unavaible the $_ENV array, that s why we do tests with _ENV
*
* @static
* @return string The temporary directory on the system
*/
function tmpdir()
{
if (OS_WINDOWS) {
if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) {
return $var;
}
if ($var = isset($_ENV['TEMP']) ? $_ENV['TEMP'] : getenv('TEMP')) {
return $var;
}
if ($var = isset($_ENV['USERPROFILE']) ? $_ENV['USERPROFILE'] : getenv('USERPROFILE')) {
return $var;
}
if ($var = isset($_ENV['windir']) ? $_ENV['windir'] : getenv('windir')) {
return $var;
}
return getenv('SystemRoot') . '\temp';
}
if ($var = isset($_ENV['TMPDIR']) ? $_ENV['TMPDIR'] : getenv('TMPDIR')) {
return $var;
}
return realpath('/tmp');
}
/**
* The "which" command (show the full path of a command)
*
* @param string $program The command to search for
* @param mixed $fallback Value to return if $program is not found
*
* @return mixed A string with the full path or false if not found
* @static
* @author Stig Bakken <ssb@php.net>
*/
function which($program, $fallback = false)
{
// enforce API
if (!is_string($program) || '' == $program) {
return $fallback;
}
// full path given
if (basename($program) != $program) {
$path_elements[] = dirname($program);
$program = basename($program);
} else {
// Honor safe mode
if (!ini_get('safe_mode') || !$path = ini_get('safe_mode_exec_dir')) {
$path = getenv('PATH');
if (!$path) {
$path = getenv('Path'); // some OSes are just stupid enough to do this
}
}
$path_elements = explode(PATH_SEPARATOR, $path);
}
if (OS_WINDOWS) {
$exe_suffixes = getenv('PATHEXT')
? explode(PATH_SEPARATOR, getenv('PATHEXT'))
: array('.exe','.bat','.cmd','.com');
// allow passing a command.exe param
if (strpos($program, '.') !== false) {
array_unshift($exe_suffixes, '');
}
// is_executable() is not available on windows for PHP4
$pear_is_executable = (function_exists('is_executable')) ? 'is_executable' : 'is_file';
} else {
$exe_suffixes = array('');
$pear_is_executable = 'is_executable';
}
foreach ($exe_suffixes as $suff) {
foreach ($path_elements as $dir) {
$file = $dir . DIRECTORY_SEPARATOR . $program . $suff;
if (@$pear_is_executable($file)) {
return $file;
}
}
}
return $fallback;
}
/**
* The "find" command
*
* Usage:
*
* System::find($dir);
* System::find("$dir -type d");
* System::find("$dir -type f");
* System::find("$dir -name *.php");
* System::find("$dir -name *.php -name *.htm*");
* System::find("$dir -maxdepth 1");
*
* Params implmented:
* $dir -> Start the search at this directory
* -type d -> return only directories
* -type f -> return only files
* -maxdepth <n> -> max depth of recursion
* -name <pattern> -> search pattern (bash style). Multiple -name param allowed
*
* @param mixed Either array or string with the command line
* @return array Array of found files
* @static
*
*/
function find($args)
{
if (!is_array($args)) {
$args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
}
$dir = realpath(array_shift($args));
if (!$dir) {
return array();
}
$patterns = array();
$depth = 0;
$do_files = $do_dirs = true;
$args_count = count($args);
for ($i = 0; $i < $args_count; $i++) {
switch ($args[$i]) {
case '-type':
if (in_array($args[$i+1], array('d', 'f'))) {
if ($args[$i+1] == 'd') {
$do_files = false;
} else {
$do_dirs = false;
}
}
$i++;
break;
case '-name':
$name = preg_quote($args[$i+1], '#');
// our magic characters ? and * have just been escaped,
// so now we change the escaped versions to PCRE operators
$name = strtr($name, array('\?' => '.', '\*' => '.*'));
$patterns[] = '('.$name.')';
$i++;
break;
case '-maxdepth':
$depth = $args[$i+1];
break;
}
}
$path = System::_dirToStruct($dir, $depth, 0, true);
if ($do_files && $do_dirs) {
$files = array_merge($path['files'], $path['dirs']);
} elseif ($do_dirs) {
$files = $path['dirs'];
} else {
$files = $path['files'];
}
if (count($patterns)) {
$dsq = preg_quote(DIRECTORY_SEPARATOR, '#');
$pattern = '#(^|'.$dsq.')'.implode('|', $patterns).'($|'.$dsq.')#';
$ret = array();
$files_count = count($files);
for ($i = 0; $i < $files_count; $i++) {
// only search in the part of the file below the current directory
$filepart = basename($files[$i]);
if (preg_match($pattern, $filepart)) {
$ret[] = $files[$i];
}
}
return $ret;
}
return $files;
}
}

View File

@ -1590,6 +1590,18 @@ var SN = { // StatusNet
SN.U.FormXHR($(this)); SN.U.FormXHR($(this));
return false; return false;
}); });
$('form.ajax input[type=submit]').live('click', function() {
// Some forms rely on knowing which submit button was clicked.
// Save a hidden input field which'll be picked up during AJAX
// submit...
var button = $(this);
var form = button.closest('form');
form.find('.hidden-submit-button').remove();
$('<input class="hidden-submit-button" type="hidden" />')
.attr('name', button.attr('name'))
.val(button.val())
.appendTo(form);
});
}, },
/** /**

2
js/util.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -46,7 +46,6 @@ require_once INSTALLDIR.'/lib/peopletags.php';
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
class AccountProfileBlock extends ProfileBlock class AccountProfileBlock extends ProfileBlock
{ {
protected $profile = null; protected $profile = null;
@ -149,9 +148,9 @@ class AccountProfileBlock extends ProfileBlock
$this->out->elementStart('li', 'entity_edit'); $this->out->elementStart('li', 'entity_edit');
$this->out->element('a', array('href' => common_local_url('profilesettings'), $this->out->element('a', array('href' => common_local_url('profilesettings'),
// TRANS: Link title for link on user profile. // TRANS: Link title for link on user profile.
'title' => _('Edit profile settings')), 'title' => _('Edit profile settings.')),
// TRANS: Link text for link on user profile. // TRANS: Link text for link on user profile.
_('Edit')); _m('BUTTON','Edit'));
$this->out->elementEnd('li'); $this->out->elementEnd('li');
} else { // someone else's page } else { // someone else's page
@ -162,6 +161,9 @@ class AccountProfileBlock extends ProfileBlock
if ($cur->isSubscribed($this->profile)) { if ($cur->isSubscribed($this->profile)) {
$usf = new UnsubscribeForm($this->out, $this->profile); $usf = new UnsubscribeForm($this->out, $this->profile);
$usf->show(); $usf->show();
} else if ($cur->hasPendingSubscription($this->profile)) {
$sf = new CancelSubscriptionForm($this->out, $this->profile);
$sf->show();
} else { } else {
$sf = new SubscribeForm($this->out, $this->profile); $sf = new SubscribeForm($this->out, $this->profile);
$sf->show(); $sf->show();
@ -175,9 +177,9 @@ class AccountProfileBlock extends ProfileBlock
$this->out->elementStart('li', 'entity_send-a-message'); $this->out->elementStart('li', 'entity_send-a-message');
$this->out->element('a', array('href' => common_local_url('newmessage', array('to' => $this->user->id)), $this->out->element('a', array('href' => common_local_url('newmessage', array('to' => $this->user->id)),
// TRANS: Link title for link on user profile. // TRANS: Link title for link on user profile.
'title' => _('Send a direct message to this user')), 'title' => _('Send a direct message to this user.')),
// TRANS: Link text for link on user profile. // TRANS: Link text for link on user profile.
_('Message')); _m('BUTTON','Message'));
$this->out->elementEnd('li'); $this->out->elementEnd('li');
// nudge // nudge
@ -303,6 +305,6 @@ class AccountProfileBlock extends ProfileBlock
$this->out->element('a', array('href' => $url, $this->out->element('a', array('href' => $url,
'class' => 'entity_remote_subscribe'), 'class' => 'entity_remote_subscribe'),
// TRANS: Link text for link that will subscribe to a remote profile. // TRANS: Link text for link that will subscribe to a remote profile.
_('Subscribe')); _m('BUTTON','Subscribe'));
} }
} }

View File

@ -663,7 +663,8 @@ class Action extends HTMLOutputter // lawsuit
if (Event::handle('StartMakeEntryForm', array($tag, $this, &$form))) { if (Event::handle('StartMakeEntryForm', array($tag, $this, &$form))) {
if ($tag == 'status') { if ($tag == 'status') {
$form = new NoticeForm($this); $options = $this->noticeFormOptions();
$form = new NoticeForm($this, $options);
} }
Event::handle('EndMakeEntryForm', array($tag, $this, $form)); Event::handle('EndMakeEntryForm', array($tag, $this, $form));
} }
@ -679,6 +680,11 @@ class Action extends HTMLOutputter // lawsuit
$this->elementEnd('div'); $this->elementEnd('div');
} }
function noticeFormOptions()
{
return array();
}
/** /**
* Show anonymous message. * Show anonymous message.
* *

View File

@ -292,7 +292,7 @@ class ApiAction extends Action
if ($get_notice) { if ($get_notice) {
$notice = $profile->getCurrentNotice(); $notice = $profile->getCurrentNotice();
if ($notice) { if ($notice) {
# don't get user! // don't get user!
$twitter_user['status'] = $this->twitterStatusArray($notice, false); $twitter_user['status'] = $this->twitterStatusArray($notice, false);
} }
} }
@ -397,7 +397,7 @@ class ApiAction extends Action
} }
if ($include_user && $profile) { if ($include_user && $profile) {
# Don't get notice (recursive!) // Don't get notice (recursive!)
$twitter_user = $this->twitterUserArray($profile, false); $twitter_user = $this->twitterUserArray($profile, false);
$twitter_status['user'] = $twitter_user; $twitter_status['user'] = $twitter_user;
} }
@ -738,7 +738,7 @@ class ApiAction extends Action
$this->element('guid', null, $entry['guid']); $this->element('guid', null, $entry['guid']);
$this->element('link', null, $entry['link']); $this->element('link', null, $entry['link']);
# RSS only supports 1 enclosure per item // RSS only supports 1 enclosure per item
if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){ if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){
$enclosure = $entry['enclosures'][0]; $enclosure = $entry['enclosures'][0];
$this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null); $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null);
@ -873,7 +873,7 @@ class ApiAction extends Action
} }
if (!is_null($suplink)) { if (!is_null($suplink)) {
# For FriendFeed's SUP protocol // For FriendFeed's SUP protocol
$this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup', $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
'href' => $suplink, 'href' => $suplink,
'type' => 'application/json')); 'type' => 'application/json'));

View File

@ -46,7 +46,6 @@ require_once INSTALLDIR.'/lib/form.php';
* *
* @see UnsubscribeForm * @see UnsubscribeForm
*/ */
class ApproveGroupForm extends Form class ApproveGroupForm extends Form
{ {
/** /**
@ -62,7 +61,6 @@ class ApproveGroupForm extends Form
* @param HTMLOutputter $out output channel * @param HTMLOutputter $out output channel
* @param group $group group to leave * @param group $group group to leave
*/ */
function __construct($out=null, $group=null, $profile=null) function __construct($out=null, $group=null, $profile=null)
{ {
parent::__construct($out); parent::__construct($out);
@ -76,10 +74,9 @@ class ApproveGroupForm extends Form
* *
* @return string ID of the form * @return string ID of the form
*/ */
function id() function id()
{ {
return 'group-cancel-' . $this->group->id; return 'group-queue-' . $this->group->id;
} }
/** /**
@ -87,10 +84,9 @@ class ApproveGroupForm extends Form
* *
* @return string of the form class * @return string of the form class
*/ */
function formClass() function formClass()
{ {
return 'form_group_join ajax'; return 'form_group_queue ajax';
} }
/** /**
@ -98,7 +94,6 @@ class ApproveGroupForm extends Form
* *
* @return string URL of the action * @return string URL of the action
*/ */
function action() function action()
{ {
$params = array(); $params = array();
@ -117,6 +112,9 @@ class ApproveGroupForm extends Form
function formActions() function formActions()
{ {
$this->out->submit('submit', _('Approve')); // TRANS: Submit button text to accept a group membership request on approve group form.
$this->out->submit('approve', _m('BUTTON','Accept'));
// TRANS: Submit button text to reject a group membership request on approve group form.
$this->out->submit('cancel', _m('BUTTON','Reject'));
} }
} }

114
lib/approvesubform.php Normal file
View File

@ -0,0 +1,114 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Form for leaving a group
*
* 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 Form
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Sarven Capadisli <csarven@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/form.php';
/**
* Form for leaving a group
*
* @category Form
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Sarven Capadisli <csarven@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*
* @see UnsubscribeForm
*/
class ApproveSubForm extends Form
{
var $profile = null;
/**
* Constructor
*
* @param HTMLOutputter $out output channel
* @param Profile $profile user whose request to accept or drop
*/
function __construct($out=null, $profile=null)
{
parent::__construct($out);
$this->profile = $profile;
}
/**
* ID of the form
*
* @return string ID of the form
*/
function id()
{
return 'sub-queue-' . $this->profile->id;
}
/**
* class of the form
*
* @return string of the form class
*/
function formClass()
{
return 'form_sub_queue ajax';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
$params = array();
if ($this->profile) {
$params['profile_id'] = $this->profile->id;
}
return common_local_url('approvesub',
array(), $params);
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
// TRANS: Submit button text to accept a subscription request on approve sub form.
$this->out->submit('approve', _m('BUTTON','Accept'));
// TRANS: Submit button text to reject a subscription request on approve sub form.
$this->out->submit('cancel', _m('BUTTON','Reject'));
}
}

129
lib/cachingnoticestream.php Normal file
View File

@ -0,0 +1,129 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* A stream of notices
*
* PHP version 5
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Stream
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* Class for notice streams
*
* @category Stream
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class CachingNoticeStream extends NoticeStream
{
const CACHE_WINDOW = 200;
public $stream = null;
public $cachekey = null;
function __construct($stream, $cachekey)
{
$this->stream = $stream;
$this->cachekey = $cachekey;
}
function getNoticeIds($offset, $limit, $sinceId, $maxId)
{
$cache = Cache::instance();
// We cache self::CACHE_WINDOW elements at the tip of the stream.
// If the cache won't be hit, just generate directly.
if (empty($cache) ||
$sinceId != 0 || $maxId != 0 ||
is_null($limit) ||
($offset + $limit) > self::CACHE_WINDOW) {
return $this->stream->getNoticeIds($offset, $limit, $sinceId, $maxId);
}
// Check the cache to see if we have the stream.
$idkey = Cache::key($this->cachekey);
$idstr = $cache->get($idkey);
if ($idstr !== false) {
// Cache hit! Woohoo!
$window = explode(',', $idstr);
$ids = array_slice($window, $offset, $limit);
return $ids;
}
// Check the cache to see if we have a "last-known-good" version.
// The actual cache gets blown away when new notices are added, but
// the "last" value holds a lot of info. We might need to only generate
// a few at the "tip", which can bound our queries and save lots
// of time.
$laststr = $cache->get($idkey.';last');
if ($laststr !== false) {
$window = explode(',', $laststr);
$last_id = $window[0];
$new_ids = $this->stream->getNoticeIds(0, self::CACHE_WINDOW, $last_id, 0);
$new_window = array_merge($new_ids, $window);
$new_windowstr = implode(',', $new_window);
$result = $cache->set($idkey, $new_windowstr);
$result = $cache->set($idkey . ';last', $new_windowstr);
$ids = array_slice($new_window, $offset, $limit);
return $ids;
}
// No cache hits :( Generate directly and stick the results
// into the cache. Note we generate the full cache window.
$window = $this->stream->getNoticeIds(0, self::CACHE_WINDOW, 0, 0);
$windowstr = implode(',', $window);
$result = $cache->set($idkey, $windowstr);
$result = $cache->set($idkey . ';last', $windowstr);
// Return just the slice that was requested
$ids = array_slice($window, $offset, $limit);
return $ids;
}
}

View File

@ -46,7 +46,6 @@ require_once INSTALLDIR.'/lib/form.php';
* *
* @see UnsubscribeForm * @see UnsubscribeForm
*/ */
class CancelGroupForm extends Form class CancelGroupForm extends Form
{ {
/** /**
@ -62,7 +61,6 @@ class CancelGroupForm extends Form
* @param HTMLOutputter $out output channel * @param HTMLOutputter $out output channel
* @param group $group group to leave * @param group $group group to leave
*/ */
function __construct($out=null, $group=null, $profile=null) function __construct($out=null, $group=null, $profile=null)
{ {
parent::__construct($out); parent::__construct($out);
@ -76,7 +74,6 @@ class CancelGroupForm extends Form
* *
* @return string ID of the form * @return string ID of the form
*/ */
function id() function id()
{ {
return 'group-cancel-' . $this->group->id; return 'group-cancel-' . $this->group->id;
@ -87,7 +84,6 @@ class CancelGroupForm extends Form
* *
* @return string of the form class * @return string of the form class
*/ */
function formClass() function formClass()
{ {
return 'form_group_leave ajax'; return 'form_group_leave ajax';
@ -98,7 +94,6 @@ class CancelGroupForm extends Form
* *
* @return string URL of the action * @return string URL of the action
*/ */
function action() function action()
{ {
$params = array(); $params = array();
@ -114,9 +109,9 @@ class CancelGroupForm extends Form
* *
* @return void * @return void
*/ */
function formActions() function formActions()
{ {
$this->out->submit('submit', _('Cancel join request')); // TRANS: Submit button text on form to cancel group join request.
$this->out->submit('submit', _m('BUTTON','Cancel join request'));
} }
} }

View File

@ -0,0 +1,124 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Form for leaving a group
*
* 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 Form
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Sarven Capadisli <csarven@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/form.php';
/**
* Form for leaving a group
*
* @category Form
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Sarven Capadisli <csarven@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*
* @see UnsubscribeForm
*/
class CancelSubscriptionForm extends Form
{
/**
* user being subscribed to
*/
var $profile = null;
/**
* Constructor
*
* @param HTMLOutputter $out output channel
* @param Profile $profile being subscribed to
*/
function __construct($out=null, $profile=null)
{
parent::__construct($out);
$this->profile = $profile;
}
/**
* ID of the form
*
* @return string ID of the form
*/
function id()
{
return 'subscription-cancel-' . $this->profile->id;
}
/**
* class of the form
*
* @return string of the form class
*/
function formClass()
{
return 'form_unsubscribe ajax';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
return common_local_url('cancelsubscription',
array(),
array('id' => $this->profile->id));
}
/**
* Data elements of the form
*
* @return void
*/
function formData()
{
$this->out->hidden('unsubscribeto-' . $this->profile->id,
$this->profile->id,
'unsubscribeto');
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
// TRANS: Button text for form action to cancel a subscription request.
$this->out->submit('submit', _m('BUTTON','Cancel subscription request'));
}
}

View File

@ -95,9 +95,9 @@ class WebChannel extends Channel
function output($user, $text) function output($user, $text)
{ {
# XXX: buffer all output and send it at the end // XXX: buffer all output and send it at the end
# XXX: even better, redirect to appropriate page // XXX: even better, redirect to appropriate page
# depending on what command was run // depending on what command was run
$this->out->startHTML(); $this->out->startHTML();
$this->out->elementStart('head'); $this->out->elementStart('head');
// TRANS: Title for command results. // TRANS: Title for command results.

View File

@ -635,29 +635,15 @@ class RepeatCommand extends Command
{ {
$notice = $this->getNotice($this->other); $notice = $this->getNotice($this->other);
if($this->user->id == $notice->profile_id) try {
{ $repeat = $notice->repeat($this->user->id, $channel->source());
// TRANS: Error text shown when trying to repeat an own notice. $recipient = $notice->getProfile();
$channel->error($this->user, _('Cannot repeat your own notice.'));
return;
}
if ($this->user->getProfile()->hasRepeated($notice->id)) {
// TRANS: Error text shown when trying to repeat an notice that was already repeated by the user.
$channel->error($this->user, _('Already repeated that notice.'));
return;
}
$repeat = $notice->repeat($this->user->id, $channel->source);
if ($repeat) {
// TRANS: Message given having repeated a notice from another user. // TRANS: Message given having repeated a notice from another user.
// TRANS: %s is the name of the user for which the notice was repeated. // 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)); $channel->output($this->user, sprintf(_('Notice from %s repeated.'), $recipient->nickname));
} else { } catch (Exception $e) {
// TRANS: Error text shown when repeating a notice fails with an unknown reason. $channel->error($this->user, $e->getMessage());
$channel->error($this->user, _('Error repeating notice.'));
} }
} }
} }

View File

@ -0,0 +1,106 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* Notice stream for a conversation
*
* PHP version 5
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Cache
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* Notice stream for a conversation
*
* @category Stream
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class ConversationNoticeStream extends ScopingNoticeStream
{
function __construct($id)
{
parent::__construct(new CachingNoticeStream(new RawConversationNoticeStream($id),
'notice:conversation_ids:'.$id));
}
}
/**
* Notice stream for a conversation
*
* @category Stream
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class RawConversationNoticeStream extends NoticeStream
{
protected $id;
function __construct($id)
{
$this->id = $id;
}
function getNoticeIds($offset, $limit, $since_id, $max_id)
{
$notice = new Notice();
$notice->selectAdd(); // clears it
$notice->selectAdd('id');
$notice->conversation = $this->id;
$notice->orderBy('created DESC, id DESC');
if (!is_null($offset)) {
$notice->limit($offset, $limit);
}
Notice::addWhereSinceId($notice, $since_id);
Notice::addWhereMaxId($notice, $max_id);
$ids = array();
if ($notice->find()) {
while ($notice->fetch()) {
$ids[] = $notice->id;
}
}
$notice->free();
$notice = NULL;
return $ids;
}
}

View File

@ -293,7 +293,8 @@ $default =
array('enabled' => true, array('enabled' => true,
'css' => ''), 'css' => ''),
'notice' => 'notice' =>
array('contentlimit' => null), array('contentlimit' => null,
'defaultscope' => 0), // set to 0 for default open
'message' => 'message' =>
array('contentlimit' => null), array('contentlimit' => null),
'location' => 'location' =>

140
lib/favenoticestream.php Normal file
View File

@ -0,0 +1,140 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* Notice stream for favorites
*
* PHP version 5
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Stream
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* Notice stream for favorites
*
* @category Stream
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class FaveNoticeStream extends ScopingNoticeStream
{
function __construct($user_id, $own)
{
$stream = new RawFaveNoticeStream($user_id, $own);
if ($own) {
$key = 'fave:ids_by_user_own:'.$user_id;
} else {
$key = 'fave:ids_by_user:'.$user_id;
}
parent::__construct(new CachingNoticeStream($stream, $key));
}
}
/**
* Raw notice stream for favorites
*
* @category Stream
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class RawFaveNoticeStream extends NoticeStream
{
protected $user_id;
protected $own;
function __construct($user_id, $own)
{
$this->user_id = $user_id;
$this->own = $own;
}
/**
* Note that the sorting for this is by order of *fave* not order of *notice*.
*
* @fixme add since_id, max_id support?
*
* @param <type> $user_id
* @param <type> $own
* @param <type> $offset
* @param <type> $limit
* @param <type> $since_id
* @param <type> $max_id
* @return <type>
*/
function getNoticeIds($offset, $limit, $since_id, $max_id)
{
$fav = new Fave();
$qry = null;
if ($this->own) {
$qry = 'SELECT fave.* FROM fave ';
$qry .= 'WHERE fave.user_id = ' . $this->user_id . ' ';
} else {
$qry = 'SELECT fave.* FROM fave ';
$qry .= 'INNER JOIN notice ON fave.notice_id = notice.id ';
$qry .= 'WHERE fave.user_id = ' . $this->user_id . ' ';
$qry .= 'AND notice.is_local != ' . Notice::GATEWAY . ' ';
}
if ($since_id != 0) {
$qry .= 'AND notice_id > ' . $since_id . ' ';
}
if ($max_id != 0) {
$qry .= 'AND notice_id <= ' . $max_id . ' ';
}
// NOTE: we sort by fave time, not by notice time!
$qry .= 'ORDER BY modified DESC ';
if (!is_null($offset)) {
$qry .= "LIMIT $limit OFFSET $offset";
}
$fav->query($qry);
$ids = array();
while ($fav->fetch()) {
$ids[] = $fav->notice_id;
}
$fav->free();
unset($fav);
return $ids;
}
}

105
lib/filenoticestream.php Normal file
View File

@ -0,0 +1,105 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* Stream of notices that reference an URL
*
* PHP version 5
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Stream
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
class FileNoticeStream extends ScopingNoticeStream
{
function __construct($file)
{
parent::__construct(new CachingNoticeStream(new RawFileNoticeStream($file),
'file:notice-ids:'.$this->url));
}
}
/**
* Raw stream for a file
*
* @category Stream
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class RawFileNoticeStream extends NoticeStream
{
protected $file = null;
function __construct($file)
{
parent::__construct();
$this->file = $file;
}
/**
* Stream of notices linking to this URL
*
* @param integer $offset Offset to show; default is 0
* @param integer $limit Limit of notices to show
* @param integer $since_id Since this notice
* @param integer $max_id Before this notice
*
* @return array ids of notices that link to this file
*/
function getNoticeIds($offset, $limit, $since_id, $max_id)
{
$f2p = new File_to_post();
$f2p->selectAdd();
$f2p->selectAdd('post_id');
$f2p->file_id = $this->file->id;
Notice::addWhereSinceId($f2p, $since_id, 'post_id', 'modified');
Notice::addWhereMaxId($f2p, $max_id, 'post_id', 'modified');
$f2p->orderBy('modified DESC, post_id DESC');
if (!is_null($offset)) {
$f2p->limit($offset, $limit);
}
$ids = array();
if ($f2p->find()) {
while ($f2p->fetch()) {
$ids[] = $f2p->post_id;
}
}
return $ids;
}
}

View File

@ -0,0 +1,115 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* A notice stream that filters its upstream content
*
* PHP version 5
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Stream
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* A class for presenting a filtered notice stream based on an upstream stream
*
* @category Stream
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
abstract class FilteringNoticeStream extends NoticeStream
{
protected $upstream;
function __construct($upstream)
{
$this->upstream = $upstream;
}
abstract function filter($notice);
function getNotices($offset, $limit, $sinceId, $maxId)
{
// "offset" is virtual; we have to get a lot
$total = $offset + $limit;
$filtered = array();
$startAt = 0;
$askFor = $total;
// Keep going till we have $total notices in $notices array,
// or we get nothing from upstream.
$results = null;
do {
$raw = $this->upstream->getNotices($startAt, $askFor, $sinceId, $maxId);
$results = $raw->N;
if ($results == 0) {
break;
}
while ($raw->fetch()) {
if ($this->filter($raw)) {
$filtered[] = clone($raw);
if (count($filtered) >= $total) {
break;
}
}
}
// XXX: make these smarter; factor hit rate into $askFor
$startAt += $askFor;
$askFor = max($total - count($filtered), NOTICES_PER_PAGE);
} while (count($filtered) < $total && $results !== 0);
return new ArrayWrapper(array_slice($filtered, $offset, $limit));
}
function getNoticeIds($offset, $limit, $sinceId, $maxId)
{
$notices = $this->getNotices($offset, $limit, $sinceId, $maxId);
$ids = array();
while ($notices->fetch()) {
$ids[] = $notices->id;
}
return $ids;
}
}

View File

@ -49,7 +49,7 @@ define('NOTICE_INBOX_SOURCE_FORWARD', 4);
define('NOTICE_INBOX_SOURCE_PROFILE_TAG', 5); define('NOTICE_INBOX_SOURCE_PROFILE_TAG', 5);
define('NOTICE_INBOX_SOURCE_GATEWAY', -1); define('NOTICE_INBOX_SOURCE_GATEWAY', -1);
# append our extlib dir as the last-resort place to find libs // append our extlib dir as the last-resort place to find libs
set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/extlib/'); set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/extlib/');
@ -70,7 +70,7 @@ if (!function_exists('dl')) {
} }
} }
# global configuration object // global configuration object
require_once('PEAR.php'); require_once('PEAR.php');
require_once('PEAR/Exception.php'); require_once('PEAR/Exception.php');

View File

@ -84,7 +84,7 @@ class GalleryAction extends OwnerDesignAction
{ {
parent::handle($args); parent::handle($args);
# Post from the tag dropdown; redirect to a GET // Post from the tag dropdown; redirect to a GET
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
common_redirect($this->selfUrl(), 303); common_redirect($this->selfUrl(), 303);

View File

@ -1,5 +1,5 @@
<?php <?php
// @todo FIXME: standard file header missing.
/** /**
* Form for blocking a user from a group * Form for blocking a user from a group
* *

View File

@ -112,6 +112,7 @@ class GroupEditForm extends Form
*/ */
function formLegend() function formLegend()
{ {
// TRANS: Form legend for group edit form.
$this->out->element('legend', null, _('Create a new group')); $this->out->element('legend', null, _('Create a new group'));
} }
@ -142,44 +143,58 @@ class GroupEditForm extends Form
if (Event::handle('StartGroupEditFormData', array($this))) { if (Event::handle('StartGroupEditFormData', array($this))) {
$this->out->elementStart('li'); $this->out->elementStart('li');
$this->out->hidden('groupid', $id); $this->out->hidden('groupid', $id);
// TRANS: Field label on group edit form.
$this->out->input('nickname', _('Nickname'), $this->out->input('nickname', _('Nickname'),
($this->out->arg('nickname')) ? $this->out->arg('nickname') : $nickname, ($this->out->arg('nickname')) ? $this->out->arg('nickname') : $nickname,
_('1-64 lowercase letters or numbers, no punctuation or spaces')); // TRANS: Field title on group edit form.
_('1-64 lowercase letters or numbers, no punctuation or spaces.'));
$this->out->elementEnd('li'); $this->out->elementEnd('li');
$this->out->elementStart('li'); $this->out->elementStart('li');
// TRANS: Field label on group edit form.
$this->out->input('fullname', _('Full name'), $this->out->input('fullname', _('Full name'),
($this->out->arg('fullname')) ? $this->out->arg('fullname') : $fullname); ($this->out->arg('fullname')) ? $this->out->arg('fullname') : $fullname);
$this->out->elementEnd('li'); $this->out->elementEnd('li');
$this->out->elementStart('li'); $this->out->elementStart('li');
// TRANS: Field label on group edit form; points to "more info" for a group.
$this->out->input('homepage', _('Homepage'), $this->out->input('homepage', _('Homepage'),
($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage, ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage,
// TRANS: Field title on group edit form.
_('URL of the homepage or blog of the group or topic.')); _('URL of the homepage or blog of the group or topic.'));
$this->out->elementEnd('li'); $this->out->elementEnd('li');
$this->out->elementStart('li'); $this->out->elementStart('li');
$desclimit = User_group::maxDescription(); $desclimit = User_group::maxDescription();
if ($desclimit == 0) { if ($desclimit == 0) {
$descinstr = _('Describe the group or topic'); // TRANS: Text area title for group description when there is no text limit.
$descinstr = _('Describe the group or topic.');
} else { } else {
$descinstr = sprintf(_m('Describe the group or topic in %d character or less', // TRANS: Text area title for group description.
'Describe the group or topic in %d characters or less', // TRANS: %d is the number of characters available for the description.
$descinstr = sprintf(_m('Describe the group or topic in %d character or less.',
'Describe the group or topic in %d characters or less.',
$desclimit), $desclimit),
$desclimit); $desclimit);
} }
// TRANS: Text area label on group edit form; contains description of group.
$this->out->textarea('description', _('Description'), $this->out->textarea('description', _('Description'),
($this->out->arg('description')) ? $this->out->arg('description') : $description, ($this->out->arg('description')) ? $this->out->arg('description') : $description,
$descinstr); $descinstr);
$this->out->elementEnd('li'); $this->out->elementEnd('li');
$this->out->elementStart('li'); $this->out->elementStart('li');
// TRANS: Field label on group edit form.
$this->out->input('location', _('Location'), $this->out->input('location', _('Location'),
($this->out->arg('location')) ? $this->out->arg('location') : $location, ($this->out->arg('location')) ? $this->out->arg('location') : $location,
// TRANS: Field title on group edit form.
_('Location for the group, if any, like "City, State (or Region), Country".')); _('Location for the group, if any, like "City, State (or Region), Country".'));
$this->out->elementEnd('li'); $this->out->elementEnd('li');
if (common_config('group', 'maxaliases') > 0) { if (common_config('group', 'maxaliases') > 0) {
$aliases = (empty($this->group)) ? array() : $this->group->getAliases(); $aliases = (empty($this->group)) ? array() : $this->group->getAliases();
$this->out->elementStart('li'); $this->out->elementStart('li');
// TRANS: Field label on group edit form.
$this->out->input('aliases', _('Aliases'), $this->out->input('aliases', _('Aliases'),
($this->out->arg('aliases')) ? $this->out->arg('aliases') : ($this->out->arg('aliases')) ? $this->out->arg('aliases') :
(!empty($aliases)) ? implode(' ', $aliases) : '', (!empty($aliases)) ? implode(' ', $aliases) : '',
// TRANS: Input field title for group aliases.
// TRANS: %d is the maximum number of group aliases available.
sprintf(_m('Extra nicknames for the group, separated with commas or spaces. Maximum %d alias allowed.', sprintf(_m('Extra nicknames for the group, separated with commas or spaces. Maximum %d alias allowed.',
'Extra nicknames for the group, separated with commas or spaces. Maximum %d aliases allowed.', 'Extra nicknames for the group, separated with commas or spaces. Maximum %d aliases allowed.',
common_config('group', 'maxaliases')), common_config('group', 'maxaliases')),
@ -188,9 +203,11 @@ class GroupEditForm extends Form
} }
$this->out->elementStart('li'); $this->out->elementStart('li');
$this->out->dropdown('join_policy', $this->out->dropdown('join_policy',
// TRANS: Dropdown fieldd label on group edit form.
_('Membership policy'), _('Membership policy'),
array(User_group::JOIN_POLICY_OPEN => _('Open to all'), array(User_group::JOIN_POLICY_OPEN => _('Open to all'),
User_group::JOIN_POLICY_MODERATE => _('Admin must approve all members')), User_group::JOIN_POLICY_MODERATE => _('Admin must approve all members')),
// TRANS: Dropdown field title on group edit form.
_('Whether admin approval is required to join this group.'), _('Whether admin approval is required to join this group.'),
false, false,
(empty($this->group->join_policy)) ? User_group::JOIN_POLICY_OPEN : $this->group->join_policy); (empty($this->group->join_policy)) ? User_group::JOIN_POLICY_OPEN : $this->group->join_policy);
@ -207,6 +224,7 @@ class GroupEditForm extends Form
*/ */
function formActions() function formActions()
{ {
// TRANS: Text for save button on group edit form.
$this->out->submit('submit', _m('BUTTON','Save')); $this->out->submit('submit', _m('BUTTON','Save'));
} }
} }

View File

@ -137,7 +137,7 @@ class GroupList extends Widget
$this->out->elementEnd('p'); $this->out->elementEnd('p');
} }
# If we're on a list with an owner (subscriptions or subscribers)... // If we're on a list with an owner (subscriptions or subscribers)...
if (!empty($user) && !empty($this->owner) && $user->id == $this->owner->id) { if (!empty($user) && !empty($this->owner) && $user->id == $this->owner->id) {
$this->showOwnerControls(); $this->showOwnerControls();
@ -149,8 +149,8 @@ class GroupList extends Widget
$this->out->elementStart('div', 'entity_actions'); $this->out->elementStart('div', 'entity_actions');
$this->out->elementStart('ul'); $this->out->elementStart('ul');
$this->out->elementStart('li', 'entity_subscribe'); $this->out->elementStart('li', 'entity_subscribe');
# XXX: special-case for user looking at own // XXX: special-case for user looking at own
# subscriptions page // subscriptions page
if ($user->isMember($this->group)) { if ($user->isMember($this->group)) {
$lf = new LeaveForm($this->out, $this->group); $lf = new LeaveForm($this->out, $this->group);
$lf->show(); $lf->show();

View File

@ -1,4 +1,5 @@
<?php <?php
// @todo FIXME: add documentation.
class GroupMemberList extends ProfileList class GroupMemberList extends ProfileList
{ {

View File

@ -1,4 +1,5 @@
<?php <?php
// @todo FIXME: add documentation.
class GroupMemberListItem extends ProfileListItem class GroupMemberListItem extends ProfileListItem
{ {
@ -17,7 +18,7 @@ class GroupMemberListItem extends ProfileListItem
if ($this->profile->isAdmin($this->group)) { if ($this->profile->isAdmin($this->group)) {
$this->out->text(' '); // for separating the classes. $this->out->text(' '); // for separating the classes.
// TRANS: Indicator in group members list that this user is a group administrator. // TRANS: Indicator in group members list that this user is a group administrator.
$this->out->element('span', 'role', _('Admin')); $this->out->element('span', 'role', _m('GROUPADMIN','Admin'));
} }
} }
@ -102,4 +103,3 @@ class GroupMemberListItem extends ProfileListItem
return $args; return $args;
} }
} }

View File

@ -48,7 +48,6 @@ require_once INSTALLDIR.'/lib/widget.php';
* *
* @see HTMLOutputter * @see HTMLOutputter
*/ */
class GroupNav extends Menu class GroupNav extends Menu
{ {
var $group = null; var $group = null;
@ -58,7 +57,6 @@ class GroupNav extends Menu
* *
* @param Action $action current action, used for output * @param Action $action current action, used for output
*/ */
function __construct($action=null, $group=null) function __construct($action=null, $group=null)
{ {
parent::__construct($action); parent::__construct($action);
@ -70,7 +68,6 @@ class GroupNav extends Menu
* *
* @return void * @return void
*/ */
function show() function show()
{ {
$action_name = $this->action->trimmed('action'); $action_name = $this->action->trimmed('action');
@ -105,7 +102,8 @@ class GroupNav extends Menu
$this->out->menuItem(common_local_url('groupqueue', array('nickname' => $this->out->menuItem(common_local_url('groupqueue', array('nickname' =>
$nickname)), $nickname)),
// TRANS: Menu item in the group navigation page. Only shown for group administrators. // TRANS: Menu item in the group navigation page. Only shown for group administrators.
sprintf(_m('MENU','Pending members (%d)'), $pending), // TRANS: %d is the number of pending members.
sprintf(_m('MENU','Pending members (%d)','Pending members (%d)',$pending), $pending),
// TRANS: Tooltip for menu item in the group navigation page. Only shown for group administrators. // TRANS: Tooltip for menu item in the group navigation page. Only shown for group administrators.
// TRANS: %s is the nickname of the group. // TRANS: %s is the nickname of the group.
sprintf(_m('TOOLTIP','%s pending members'), $nickname), sprintf(_m('TOOLTIP','%s pending members'), $nickname),

103
lib/groupnoticestream.php Normal file
View File

@ -0,0 +1,103 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* Stream of notices for a group
*
* PHP version 5
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Stream
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* Stream of notices for a group
*
* @category Stream
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class GroupNoticeStream extends ScopingNoticeStream
{
function __construct($group)
{
parent::__construct(new CachingNoticeStream(new RawGroupNoticeStream($group),
'user_group:notice_ids:' . $group->id));
}
}
/**
* Stream of notices for a group
*
* @category Stream
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class RawGroupNoticeStream extends NoticeStream
{
protected $group;
function __construct($group)
{
$this->group = $group;
}
function getNoticeIds($offset, $limit, $since_id, $max_id)
{
$inbox = new Group_inbox();
$inbox->group_id = $this->group->id;
$inbox->selectAdd();
$inbox->selectAdd('notice_id');
Notice::addWhereSinceId($inbox, $since_id, 'notice_id');
Notice::addWhereMaxId($inbox, $max_id, 'notice_id');
$inbox->orderBy('created DESC, notice_id DESC');
if (!is_null($offset)) {
$inbox->limit($offset, $limit);
}
$ids = array();
if ($inbox->find()) {
while ($inbox->fetch()) {
$ids[] = $inbox->notice_id;
}
}
return $ids;
}
}

View File

@ -482,9 +482,12 @@ abstract class ImPlugin extends Plugin
$body = trim(strip_tags($body)); $body = trim(strip_tags($body));
$content_shortened = common_shorten_links($body); $content_shortened = common_shorten_links($body);
if (Notice::contentTooLong($content_shortened)) { if (Notice::contentTooLong($content_shortened)) {
$this->sendFromSite($screenname, sprintf(_('Message too long - maximum is %1$d characters, you sent %2$d.'), $this->sendFromSite($screenname,
Notice::maxContent(), sprintf(_m('Message too long - maximum is %1$d character, you sent %2$d.',
mb_strlen($content_shortened))); 'Message too long - maximum is %1$d characters, you sent %2$d.',
Notice::maxContent()),
Notice::maxContent(),
mb_strlen($content_shortened)));
return; return;
} }

View File

@ -245,44 +245,13 @@ function mail_subscribe_notify_profile($listenee, $other)
$other->getBestName(), $other->getBestName(),
common_config('site', 'name')); common_config('site', 'name'));
// TRANS: This is a paragraph in a new-subscriber e-mail.
// TRANS: %s is a URL where the subscriber can be reported as abusive.
$blocklink = sprintf(_("If you believe this account is being used abusively, " .
"you can block them from your subscribers list and " .
"report as spam to site administrators at %s"),
common_local_url('block', array('profileid' => $other->id)));
// TRANS: Main body of new-subscriber notification e-mail. // TRANS: Main body of new-subscriber notification e-mail.
// TRANS: %1$s is the subscriber's long name, %2$s is the StatusNet sitename, // TRANS: %1$s is the subscriber's long name, %2$s is the StatusNet sitename.
// TRANS: %3$s is the subscriber's profile URL, %4$s is the subscriber's location (or empty) $body = sprintf(_('%1$s is now listening to your notices on %2$s.'),
// TRANS: %5$s is the subscriber's homepage URL (or empty), %6%s is the subscriber's bio (or empty)
// TRANS: %7$s is a link to the addressed user's e-mail settings.
$body = sprintf(_('%1$s is now listening to your notices on %2$s.'."\n\n".
"\t".'%3$s'."\n\n".
'%4$s'.
'%5$s'.
'%6$s'.
"\n".'Faithfully yours,'."\n".'%2$s.'."\n\n".
"----\n".
"Change your email address or ".
"notification options at ".'%7$s' ."\n"),
$long_name, $long_name,
common_config('site', 'name'), common_config('site', 'name')) .
$other->profileurl, mail_profile_block($other) .
($other->location) ? mail_footer_block();
// TRANS: Profile info line in new-subscriber notification e-mail.
// TRANS: %s is a location.
sprintf(_("Location: %s"), $other->location) . "\n" : '',
($other->homepage) ?
// TRANS: Profile info line in new-subscriber notification e-mail.
// TRANS: %s is a homepage.
sprintf(_("Homepage: %s"), $other->homepage) . "\n" : '',
(($other->bio) ?
// TRANS: Profile info line in new-subscriber notification e-mail.
// TRANS: %s is biographical information.
sprintf(_("Bio: %s"), $other->bio) . "\n" : '') .
"\n\n" . $blocklink . "\n",
common_local_url('emailsettings'));
// reset localization // reset localization
common_switch_locale(); common_switch_locale();
@ -290,6 +259,112 @@ function mail_subscribe_notify_profile($listenee, $other)
} }
} }
function mail_subscribe_pending_notify_profile($listenee, $other)
{
if ($other->hasRight(Right::EMAILONSUBSCRIBE) &&
$listenee->email && $listenee->emailnotifysub) {
$profile = $listenee->getProfile();
$name = $profile->getBestName();
$long_name = ($other->fullname) ?
($other->fullname . ' (' . $other->nickname . ')') : $other->nickname;
$recipients = $listenee->email;
// use the recipient's localization
common_switch_locale($listenee->language);
$headers = _mail_prepare_headers('subscribe', $listenee->nickname, $other->nickname);
$headers['From'] = mail_notify_from();
$headers['To'] = $name . ' <' . $listenee->email . '>';
// TRANS: Subject of pending new-subscriber notification e-mail.
// TRANS: %1$s is the subscribing user's nickname, %2$s is the StatusNet sitename.
$headers['Subject'] = sprintf(_('%1$s would like to listen to '.
'your notices on %2$s.'),
$other->getBestName(),
common_config('site', 'name'));
// TRANS: Main body of pending new-subscriber notification e-mail.
// TRANS: %1$s is the subscriber's long name, %2$s is the StatusNet sitename.
$body = sprintf(_('%1$s would like to listen to your notices on %2$s. ' .
'You may approve or reject their subscription at %3$s'),
$long_name,
common_config('site', 'name'),
common_local_url('subqueue', array('nickname' => $listenee->nickname))) .
mail_profile_block($other) .
mail_footer_block();
// reset localization
common_switch_locale();
mail_send($recipients, $headers, $body);
}
}
function mail_footer_block()
{
// TRANS: Common footer block for StatusNet notification emails.
// TRANS: %1$s is the StatusNet sitename,
// TRANS: %2$s is a link to the addressed user's e-mail settings.
return "\n\n" . sprintf(_('Faithfully yours,'.
"\n".'%1$s.'."\n\n".
"----\n".
"Change your email address or ".
"notification options at ".'%2$s'),
common_config('site', 'name'),
common_local_url('emailsettings')) . "\n";
}
/**
* Format a block of profile info for a plaintext notification email.
*
* @param Profile $profile
* @return string
*/
function mail_profile_block($profile)
{
// TRANS: Layout for
// TRANS: %1$s is the subscriber's profile URL, %2$s is the subscriber's location (or empty)
// TRANS: %3$s is the subscriber's homepage URL (or empty), %4%s is the subscriber's bio (or empty)
$out = array();
$out[] = "";
$out[] = "";
// TRANS: Profile info line in notification e-mail.
// TRANS: %s is a URL.
$out[] = sprintf(_("Profile: %s"), $profile->profileurl);
if ($profile->location) {
// TRANS: Profile info line in notification e-mail.
// TRANS: %s is a location.
$out[] = sprintf(_("Location: %s"), $profile->location);
}
if ($profile->homepage) {
// TRANS: Profile info line in notification e-mail.
// TRANS: %s is a homepage.
$out[] = sprintf(_("Homepage: %s"), $profile->homepage);
}
if ($profile->bio) {
// TRANS: Profile info line in notification e-mail.
// TRANS: %s is biographical information.
$out[] = sprintf(_("Bio: %s"), $profile->bio);
}
$blocklink = common_local_url('block', array('profileid' => $profile->id));
// This'll let ModPlus add the remote profile info so it's possible
// to block remote users directly...
Event::handle('MailProfileInfoBlockLink', array($profile, &$blocklink));
// TRANS: This is a paragraph in a new-subscriber e-mail.
// TRANS: %s is a URL where the subscriber can be reported as abusive.
$out[] = sprintf(_('If you believe this account is being used abusively, ' .
'you can block them from your subscribers list and ' .
'report as spam to site administrators at %s.'),
$blocklink);
$out[] = "";
return implode("\n", $out);
}
/** /**
* notify a user of their new incoming email address * notify a user of their new incoming email address
* *
@ -317,11 +392,11 @@ function mail_new_incoming_notify($user)
// TRANS: to to post by e-mail, %3$s is a URL to more instructions. // TRANS: to to post by e-mail, %3$s is a URL to more instructions.
$body = sprintf(_("You have a new posting address on %1\$s.\n\n". $body = sprintf(_("You have a new posting address on %1\$s.\n\n".
"Send email to %2\$s to post new messages.\n\n". "Send email to %2\$s to post new messages.\n\n".
"More email instructions at %3\$s.\n\n". "More email instructions at %3\$s."),
"Faithfully yours,\n%1\$s"),
common_config('site', 'name'), common_config('site', 'name'),
$user->incomingemail, $user->incomingemail,
common_local_url('doc', array('title' => 'email'))); common_local_url('doc', array('title' => 'email'))) .
mail_footer_block();
mail_send($user->email, $headers, $body); mail_send($user->email, $headers, $body);
} }
@ -466,7 +541,7 @@ function mail_confirm_sms($code, $nickname, $address)
// TRANS: Main body heading for SMS-by-email address confirmation message. // TRANS: Main body heading for SMS-by-email address confirmation message.
// TRANS: %s is the addressed user's nickname. // TRANS: %s is the addressed user's nickname.
$body = sprintf(_("%s: confirm you own this phone number with this code:"), $nickname); $body = sprintf(_('%s: confirm you own this phone number with this code:'), $nickname);
$body .= "\n\n"; $body .= "\n\n";
$body .= $code; $body .= $code;
$body .= "\n\n"; $body .= "\n\n";
@ -493,18 +568,16 @@ function mail_notify_nudge($from, $to)
// TRANS: Body for 'nudge' notification email. // TRANS: Body for 'nudge' notification email.
// TRANS: %1$s is the nuding user's long name, $2$s is the nudging user's nickname, // TRANS: %1$s is the nuding user's long name, $2$s is the nudging user's nickname,
// TRANS: %3$s is a URL to post notices at, %4$s is the StatusNet sitename. // TRANS: %3$s is a URL to post notices at.
$body = sprintf(_("%1\$s (%2\$s) is wondering what you are up to ". $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". "these days and is inviting you to post some news.\n\n".
"So let's hear from you :)\n\n". "So let's hear from you :)\n\n".
"%3\$s\n\n". "%3\$s\n\n".
"Don't reply to this email; it won't get to them.\n\n". "Don't reply to this email; it won't get to them."),
"With kind regards,\n".
"%4\$s\n"),
$from_profile->getBestName(), $from_profile->getBestName(),
$from->nickname, $from->nickname,
common_local_url('all', array('nickname' => $to->nickname)), common_local_url('all', array('nickname' => $to->nickname))) .
common_config('site', 'name')); mail_footer_block();
common_switch_locale(); common_switch_locale();
$headers = _mail_prepare_headers('nudge', $to->nickname, $from->nickname); $headers = _mail_prepare_headers('nudge', $to->nickname, $from->nickname);
@ -548,21 +621,18 @@ function mail_notify_message($message, $from=null, $to=null)
// TRANS: Body for direct-message notification email. // TRANS: Body for direct-message notification email.
// TRANS: %1$s is the sending user's long name, %2$s is the sending user's nickname, // TRANS: %1$s is the sending user's long name, %2$s is the sending user's nickname,
// TRANS: %3$s is the message content, %4$s a URL to the message, // TRANS: %3$s is the message content, %4$s a URL to the message,
// TRANS: %5$s is the StatusNet sitename.
$body = sprintf(_("%1\$s (%2\$s) sent you a private message:\n\n". $body = sprintf(_("%1\$s (%2\$s) sent you a private message:\n\n".
"------------------------------------------------------\n". "------------------------------------------------------\n".
"%3\$s\n". "%3\$s\n".
"------------------------------------------------------\n\n". "------------------------------------------------------\n\n".
"You can reply to their message here:\n\n". "You can reply to their message here:\n\n".
"%4\$s\n\n". "%4\$s\n\n".
"Don't reply to this email; it won't get to them.\n\n". "Don't reply to this email; it won't get to them."),
"With kind regards,\n".
"%5\$s\n"),
$from_profile->getBestName(), $from_profile->getBestName(),
$from->nickname, $from->nickname,
$message->content, $message->content,
common_local_url('newmessage', array('to' => $from->id)), common_local_url('newmessage', array('to' => $from->id))) .
common_config('site', 'name')); mail_footer_block();
$headers = _mail_prepare_headers('message', $to->nickname, $from->nickname); $headers = _mail_prepare_headers('message', $to->nickname, $from->nickname);
@ -571,7 +641,7 @@ function mail_notify_message($message, $from=null, $to=null)
} }
/** /**
* notify a user that one of their notices has been chosen as a 'fave' * Notify a user that one of their notices has been chosen as a 'fave'
* *
* Doesn't check that the user has an email address nor if they * Doesn't check that the user has an email address nor if they
* want to receive notification of faves. Maybe this happens higher * want to receive notification of faves. Maybe this happens higher
@ -615,9 +685,7 @@ function mail_notify_fave($other, $user, $notice)
"The text of your notice is:\n\n" . "The text of your notice is:\n\n" .
"%4\$s\n\n" . "%4\$s\n\n" .
"You can see the list of %1\$s's favorites here:\n\n" . "You can see the list of %1\$s's favorites here:\n\n" .
"%5\$s\n\n" . "%5\$s"),
"Faithfully yours,\n" .
"%6\$s\n"),
$bestname, $bestname,
common_exact_date($notice->created), common_exact_date($notice->created),
common_local_url('shownotice', common_local_url('shownotice',
@ -626,7 +694,8 @@ function mail_notify_fave($other, $user, $notice)
common_local_url('showfavorites', common_local_url('showfavorites',
array('nickname' => $user->nickname)), array('nickname' => $user->nickname)),
common_config('site', 'name'), common_config('site', 'name'),
$user->nickname); $user->nickname) .
mail_footer_block();
$headers = _mail_prepare_headers('fave', $other->nickname, $user->nickname); $headers = _mail_prepare_headers('fave', $other->nickname, $user->nickname);
@ -635,7 +704,7 @@ function mail_notify_fave($other, $user, $notice)
} }
/** /**
* notify a user that they have received an "attn:" message AKA "@-reply" * Notify a user that they have received an "attn:" message AKA "@-reply"
* *
* @param User $user The user who recevied the notice * @param User $user The user who recevied the notice
* @param Notice $notice The notice that was sent * @param Notice $notice The notice that was sent
@ -677,12 +746,11 @@ function mail_notify_attn($user, $notice)
$subject = sprintf(_('%1$s (@%2$s) sent a notice to your attention'), $bestname, $sender->nickname); $subject = sprintf(_('%1$s (@%2$s) sent a notice to your attention'), $bestname, $sender->nickname);
// TRANS: Body of @-reply notification e-mail. // TRANS: Body of @-reply notification e-mail.
// TRANS: %1$s is the sending user's long name, $2$s is the StatusNet sitename, // TRANS: %1$s is the sending user's name, $2$s is the StatusNet sitename,
// TRANS: %3$s is a URL to the notice, %4$s is the notice text, // TRANS: %3$s is a URL to the notice, %4$s is the notice text,
// TRANS: %5$s is a URL to the full conversion if it exists (otherwise empty), // TRANS: %5$s is a URL to the full conversion if it exists (otherwise empty),
// TRANS: %6$s is a URL to reply to the notice, %7$s is a URL to all @-replied for the addressed user, // TRANS: %6$s is a URL to reply to the notice, %7$s is a URL to all @-replies for the addressed user,
// TRANS: %8$s is a URL to the addressed user's e-mail settings, %9$s is the sender's nickname. $body = sprintf(_("%1\$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". "The notice is here:\n\n".
"\t%3\$s\n\n" . "\t%3\$s\n\n" .
"It reads:\n\n". "It reads:\n\n".
@ -691,11 +759,8 @@ function mail_notify_attn($user, $notice)
"You can reply back here:\n\n". "You can reply back here:\n\n".
"\t%6\$s\n\n" . "\t%6\$s\n\n" .
"The list of all @-replies for you here:\n\n" . "The list of all @-replies for you here:\n\n" .
"%7\$s\n\n" . "%7\$s"),
"Faithfully yours,\n" . $sender->getFancyName(),//%1
"%2\$s\n\n" .
"P.S. You can turn off these email notifications here: %8\$s\n"),
$bestname,//%1
common_config('site', 'name'),//%2 common_config('site', 'name'),//%2
common_local_url('shownotice', common_local_url('shownotice',
array('notice' => $notice->id)),//%3 array('notice' => $notice->id)),//%3
@ -704,10 +769,8 @@ function mail_notify_attn($user, $notice)
common_local_url('newnotice', common_local_url('newnotice',
array('replyto' => $sender->nickname, 'inreplyto' => $notice->id)),//%6 array('replyto' => $sender->nickname, 'inreplyto' => $notice->id)),//%6
common_local_url('replies', common_local_url('replies',
array('nickname' => $user->nickname)),//%7 array('nickname' => $user->nickname))) . //%7
common_local_url('emailsettings'), //%8 mail_footer_block();
$sender->nickname); //%9
$headers = _mail_prepare_headers('mention', $user->nickname, $sender->nickname); $headers = _mail_prepare_headers('mention', $user->nickname, $sender->nickname);
common_switch_locale(); common_switch_locale();
@ -734,3 +797,97 @@ function _mail_prepare_headers($msg_type, $to, $from)
return $headers; return $headers;
} }
/**
* Send notification emails to group administrator.
*
* @param User_group $group
* @param Profile $joiner
*/
function mail_notify_group_join($group, $joiner)
{
// This returns a Profile query...
$admin = $group->getAdmins();
while ($admin->fetch()) {
// We need a local user for email notifications...
$adminUser = User::staticGet('id', $admin->id);
// @fixme check for email preference?
if ($adminUser && $adminUser->email) {
// use the recipient's localization
common_switch_locale($adminUser->language);
$headers = _mail_prepare_headers('join', $admin->nickname, $joiner->nickname);
$headers['From'] = mail_notify_from();
$headers['To'] = $admin->getBestName() . ' <' . $adminUser->email . '>';
// TRANS: Subject of group join notification e-mail.
// TRANS: %1$s is the joining user's nickname, %2$s is the group name, and %3$s is the StatusNet sitename.
$headers['Subject'] = sprintf(_('%1$s has joined '.
'your group %2$s on %3$s.'),
$joiner->getBestName(),
$group->getBestName(),
common_config('site', 'name'));
// TRANS: Main body of group join notification e-mail.
// TRANS: %1$s is the subscriber's long name, %2$s is the group name, and %3$s is the StatusNet sitename,
// TRANS: %4$s is a block of profile info about the subscriber.
// TRANS: %5$s is a link to the addressed user's e-mail settings.
$body = sprintf(_('%1$s has joined your group %2$s on %3$s.'),
$joiner->getFancyName(),
$group->getFancyName(),
common_config('site', 'name')) .
mail_profile_block($joiner) .
mail_footer_block();
// reset localization
common_switch_locale();
mail_send($adminUser->email, $headers, $body);
}
}
}
/**
* Send notification emails to group administrator.
*
* @param User_group $group
* @param Profile $joiner
*/
function mail_notify_group_join_pending($group, $joiner)
{
$admin = $group->getAdmins();
while ($admin->fetch()) {
// We need a local user for email notifications...
$adminUser = User::staticGet('id', $admin->id);
// @fixme check for email preference?
if ($adminUser && $adminUser->email) {
// use the recipient's localization
common_switch_locale($adminUser->language);
$headers = _mail_prepare_headers('join', $admin->nickname, $joiner->nickname);
$headers['From'] = mail_notify_from();
$headers['To'] = $admin->getBestName() . ' <' . $adminUser->email . '>';
// TRANS: Subject of pending group join request notification e-mail.
// TRANS: %1$s is the joining user's nickname, %2$s is the group name, and %3$s is the StatusNet sitename.
$headers['Subject'] = sprintf(_('%1$s wants to join your group %2$s on %3$s.'),
$joiner->getBestName(),
$group->getBestName(),
common_config('site', 'name'));
// TRANS: Main body of pending group join request notification e-mail.
// TRANS: %1$s is the subscriber's long name, %2$s is the group name, and %3$s is the StatusNet sitename,
// TRANS: %4$s is the URL to the moderation queue page.
$body = sprintf(_('%1$s would like to join your group %2$s on %3$s. ' .
'You may approve or reject their group membership at %4$s'),
$joiner->getFancyName(),
$group->getFancyName(),
common_config('site', 'name'),
common_local_url('groupqueue', array('nickname' => $group->nickname))) .
mail_profile_block($joiner) .
mail_footer_block();
// reset localization
common_switch_locale();
mail_send($adminUser->email, $headers, $body);
}
}
}

View File

@ -21,8 +21,8 @@ require_once(INSTALLDIR . '/lib/mail.php');
require_once(INSTALLDIR . '/lib/mediafile.php'); require_once(INSTALLDIR . '/lib/mediafile.php');
require_once('Mail/mimeDecode.php'); require_once('Mail/mimeDecode.php');
# FIXME: we use both Mail_mimeDecode and mailparse // FIXME: we use both Mail_mimeDecode and mailparse
# Need to move everything to mailparse // Need to move everything to mailparse
class MailHandler class MailHandler
{ {

View File

@ -1,4 +1,5 @@
<?php <?php
// @todo FIXME: add standard file header.
/** /**
* Form for making a user an admin for a group * Form for making a user an admin for a group

View File

@ -512,7 +512,7 @@ abstract class MicroAppPlugin extends Plugin
function onEndActivityObjectOutputJson(ActivityObject $obj, array &$out) function onEndActivityObjectOutputJson(ActivityObject $obj, array &$out)
{ {
if (in_array($obj->type, $this->types())) { if (in_array($obj->type, $this->types())) {
$this->activityObjectOutputJson($obj, &$out); $this->activityObjectOutputJson($obj, $out);
} }
return true; return true;
} }

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