Merge branch '1.0.x' into qna

* 1.0.x: (26 commits)
  Fix typo in documentation.
  Fix i18n.
  Change formatting of i18n slightly. Translator hints were not picked up in pot file. May now they are.
  Localisation updates from http://translatewiki.net.
  Translator documentation updated. i18n/L10n updates. Superfluous whitespace removed. Add FIXME for missing class documentation.
  Update translator documentation. Remove superfluous whitespace. L10n/I18n updates. FIXMEs added for missing documentation or headers.
  Update translator documentation. i18n/L10n updates. Superfluous whitespace removed. Add FIXME in files with missing documentation.
  Fallback for RSVP display when Event is deleted
  Enhancement for 'ajax' form class: submit buttons behave more like normal submissions, submitting their name/values through a hidden field.
  Retool group join queue list ajax forms to use two buttons in one form, making it more ajax-submit-friendly. Needs util.js fixes for AJAX submission input buttons...
  Some fixes to make the notice stream class work
  let Inbox class go fingerpokin' in streams
  New NoticeStream class to reify streams of notices
  Refactoring on notification mail generation: common profile & footer chunks pulled out, notifications added for group joins.
  Fix typo in cf45c978
  Mass replacement of #-comments with //-comments
  Add pending members list to group navigation, if group has joins moderated or if it has pending requests open
  Split up some list/form classes, and get the 'approve' and 'cancel' links on group member queue working.
  Pending members queue list -- doesn't yet allow approval.
  Logic to have group joins turn into pending joins automatically when group is set to mod; allow users to cancel their pending group requests.
  ...
This commit is contained in:
Zach Copley 2011-03-24 14:03:04 -07:00
commit c1a27922ba
188 changed files with 20674 additions and 8548 deletions

View File

@ -742,19 +742,19 @@ EndUnsubscribe: when an unsubscribe is done
StartJoinGroup: when a user is joining a group
- $group: the group being joined
- $user: the user joining
- $profile: the local or remote user joining
EndJoinGroup: when a user finishes joining a group
- $group: the group being joined
- $user: the user joining
- $profile: the local or remote user joining
StartLeaveGroup: when a user is leaving a group
- $group: the group being left
- $user: the user leaving
- $profile: the local or remote user leaving
EndLeaveGroup: when a user has left a group
- $group: the group being left
- $user: the user leaving
- $profile: the local or remote user leaving
StartShowContentLicense: Showing the default license for content
- $action: the current action

View File

@ -126,10 +126,7 @@ class ApiGroupJoinAction extends ApiAuthAction
}
try {
if (Event::handle('StartJoinGroup', array($this->group, $this->user))) {
Group_member::join($this->group->id, $this->user->id);
Event::handle('EndJoinGroup', array($this->group, $this->user));
}
$this->user->joinGroup($this->group);
} catch (Exception $e) {
// TRANS: Server error displayed when joining a group failed in the database.
// TRANS: %1$s is the joining user's nickname, $2$s is the group nickname for which the join failed.

View File

@ -117,10 +117,7 @@ class ApiGroupLeaveAction extends ApiAuthAction
}
try {
if (Event::handle('StartLeaveGroup', array($this->group,$this->user))) {
Group_member::leave($this->group->id, $this->user->id);
Event::handle('EndLeaveGroup', array($this->group, $this->user));
}
$this->user->leaveGroup($this->group);
} catch (Exception $e) {
// TRANS: Server error displayed when leaving a group failed in the database.
// TRANS: %1$s is the leaving user's nickname, $2$s is the group nickname for which the leave failed.

190
actions/approvegroup.php Normal file
View File

@ -0,0 +1,190 @@
<?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 ApprovegroupAction extends Action
{
var $group = null;
/**
* Prepare to run
*/
function prepare($args)
{
parent::prepare($args);
if (!common_logged_in()) {
// TRANS: Client error displayed when trying to leave a group while not logged in.
$this->clientError(_('You must be logged in to leave a group.'));
return false;
}
$nickname_arg = $this->trimmed('nickname');
$id = intval($this->arg('id'));
if ($id) {
$this->group = User_group::staticGet('id', $id);
} else if ($nickname_arg) {
$nickname = common_canonical_nickname($nickname_arg);
// Permanent redirect on non-canonical nickname
if ($nickname_arg != $nickname) {
$args = array('nickname' => $nickname);
common_redirect(common_local_url('leavegroup', $args), 301);
return false;
}
$local = Local_group::staticGet('nickname', $nickname);
if (!$local) {
// TRANS: Client error displayed when trying to leave a non-local group.
$this->clientError(_('No such group.'), 404);
return false;
}
$this->group = User_group::staticGet('id', $local->group_id);
} else {
// TRANS: Client error displayed when trying to leave a group without providing a group name or group ID.
$this->clientError(_('No nickname or ID.'), 404);
return false;
}
if (!$this->group) {
// TRANS: Client error displayed when trying to leave a non-existing group.
$this->clientError(_('No such group.'), 404);
return false;
}
$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')) {
if ($cur->isAdmin($this->group)) {
$this->profile = Profile::staticGet('id', $this->arg('profile_id'));
} 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);
return false;
}
} else {
// TRANS: Client error displayed trying to approve group membership without specifying a profile to approve.
$this->clientError(_('Must specify a profile.'));
return false;
}
$this->request = Group_join_queue::pkeyGet(array('profile_id' => $this->profile->id,
'group_id' => $this->group->id));
if (empty($this->request)) {
// TRANS: Client error displayed trying to approve group membership for a non-existing request.
$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;
}
/**
* Handle the request
*
* On POST, add the current user to the group
*
* @param array $args unused
*
* @return void
*/
function handle($args)
{
parent::handle($args);
try {
if ($this->approve) {
$this->profile->completeJoinGroup($this->group);
} elseif ($this->cancel) {
$this->profile->cancelJoinGroup($this->group);
}
} catch (Exception $e) {
common_log(LOG_ERROR, "Exception canceling group sub: " . $e->getMessage());
// 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.
$this->serverError(sprintf(_('Could not cancel request for user %1$s to join group %2$s.'),
$this->profile->nickname, $this->group->nickname));
return;
}
if ($this->boolean('ajax')) {
$this->startHTML('text/xml;charset=utf-8');
$this->elementStart('head');
// TRANS: Title for leave group page after group join request is approved/disapproved.
$this->element('title', null, sprintf(_m('TITLE','%1$s\'s request for %2$s'),
$this->profile->nickname,
$this->group->nickname));
$this->elementEnd('head');
$this->elementStart('body');
if ($this->approve) {
$this->element('p', 'success', _m('Join request approved.'));
} elseif ($this->cancel) {
$this->element('p', 'success', _m('Join request canceled.'));
}
$this->elementEnd('body');
$this->elementEnd('html');
} else {
common_redirect(common_local_url('groupmembers', array('nickname' =>
$this->group->nickname)),
303);
}
}
}

View File

@ -275,10 +275,7 @@ class AtompubmembershipfeedAction extends ApiAuthAction
throw new ClientException(_('Blocked by admin.'));
}
if (Event::handle('StartJoinGroup', array($group, $this->auth_user))) {
$membership = Group_member::join($group->id, $this->auth_user->id);
Event::handle('EndJoinGroup', array($group, $this->auth_user));
}
$this->auth_user->joinGroup($group);
Event::handle('EndAtomPubNewActivity', array($activity, $membership));
}

View File

@ -151,10 +151,7 @@ class AtompubshowmembershipAction extends ApiAuthAction
" membership."), 403);
}
if (Event::handle('StartLeaveGroup', array($this->_group, $this->auth_user))) {
Group_member::leave($this->_group->id, $this->auth_user->id);
Event::handle('EndLeaveGroup', array($this->_group, $this->auth_user));
}
$this->auth_user->leaveGroup($this->_group);
return;
}

172
actions/cancelgroup.php Normal file
View File

@ -0,0 +1,172 @@
<?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 CancelgroupAction extends Action
{
var $group = null;
/**
* Prepare to run
*/
function prepare($args)
{
parent::prepare($args);
if (!common_logged_in()) {
// TRANS: Client error displayed when trying to leave a group while not logged in.
$this->clientError(_('You must be logged in to leave a group.'));
return false;
}
$nickname_arg = $this->trimmed('nickname');
$id = intval($this->arg('id'));
if ($id) {
$this->group = User_group::staticGet('id', $id);
} else if ($nickname_arg) {
$nickname = common_canonical_nickname($nickname_arg);
// Permanent redirect on non-canonical nickname
if ($nickname_arg != $nickname) {
$args = array('nickname' => $nickname);
common_redirect(common_local_url('leavegroup', $args), 301);
return false;
}
$local = Local_group::staticGet('nickname', $nickname);
if (!$local) {
// TRANS: Client error displayed when trying to leave a non-local group.
$this->clientError(_('No such group.'), 404);
return false;
}
$this->group = User_group::staticGet('id', $local->group_id);
} else {
// TRANS: Client error displayed when trying to leave a group without providing a group name or group ID.
$this->clientError(_('No nickname or ID.'), 404);
return false;
}
if (!$this->group) {
// TRANS: Client error displayed when trying to leave a non-existing group.
$this->clientError(_('No such group.'), 404);
return false;
}
$cur = common_current_user();
if (empty($cur)) {
// TRANS: Client error displayed when trying to leave a group while not logged in.
$this->clientError(_('Must be logged in.'), 403);
return false;
}
if ($this->arg('profile_id')) {
if ($cur->isAdmin($this->group)) {
$this->profile = Profile::staticGet('id', $this->arg('profile_id'));
} 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);
return false;
}
} else {
$this->profile = $cur->getProfile();
}
$this->request = Group_join_queue::pkeyGet(array('profile_id' => $this->profile->id,
'group_id' => $this->group->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);
}
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);
try {
$this->profile->cancelJoinGroup($this->group);
} catch (Exception $e) {
common_log(LOG_ERROR, "Exception canceling group sub: " . $e->getMessage());
// 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.
$this->serverError(sprintf(_('Could not cancel request for user %1$s to join group %2$s.'),
$this->profile->nickname, $this->group->nickname));
return;
}
if ($this->boolean('ajax')) {
$this->startHTML('text/xml;charset=utf-8');
$this->elementStart('head');
// 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->profile->nickname,
$this->group->nickname));
$this->elementEnd('head');
$this->elementStart('body');
$jf = new JoinForm($this, $this->group);
$jf->show();
$this->elementEnd('body');
$this->elementEnd('html');
} else {
common_redirect(common_local_url('groupmembers', array('nickname' =>
$this->group->nickname)),
303);
}
}
}

View File

@ -185,6 +185,7 @@ class EditgroupAction extends GroupDesignAction
$description = $this->trimmed('description');
$location = $this->trimmed('location');
$aliasstring = $this->trimmed('aliases');
$join_policy = intval($this->arg('join_policy'));
if ($this->nicknameExists($nickname)) {
// TRANS: Group edit form validation error.
@ -265,6 +266,7 @@ class EditgroupAction extends GroupDesignAction
$this->group->description = $description;
$this->group->location = $location;
$this->group->mainpage = common_local_url('showgroup', array('nickname' => $nickname));
$this->group->join_policy = $join_policy;
$result = $this->group->update($orig);

View File

@ -152,376 +152,3 @@ class GroupmembersAction extends GroupDesignAction
array('nickname' => $this->group->nickname));
}
}
class GroupMemberList extends ProfileList
{
var $group = null;
function __construct($profile, $group, $action)
{
parent::__construct($profile, $action);
$this->group = $group;
}
function newListItem($profile)
{
return new GroupMemberListItem($profile, $this->group, $this->action);
}
}
class GroupMemberListItem extends ProfileListItem
{
var $group = null;
function __construct($profile, $group, $action)
{
parent::__construct($profile, $action);
$this->group = $group;
}
function showFullName()
{
parent::showFullName();
if ($this->profile->isAdmin($this->group)) {
$this->out->text(' '); // for separating the classes.
// TRANS: Indicator in group members list that this user is a group administrator.
$this->out->element('span', 'role', _('Admin'));
}
}
function showActions()
{
$this->startActions();
if (Event::handle('StartProfileListItemActionElements', array($this))) {
$this->showSubscribeButton();
$this->showMakeAdminForm();
$this->showGroupBlockForm();
Event::handle('EndProfileListItemActionElements', array($this));
}
$this->endActions();
}
function showMakeAdminForm()
{
$user = common_current_user();
if (!empty($user) &&
$user->id != $this->profile->id &&
($user->isAdmin($this->group) || $user->hasRight(Right::MAKEGROUPADMIN)) &&
!$this->profile->isAdmin($this->group)) {
$this->out->elementStart('li', 'entity_make_admin');
$maf = new MakeAdminForm($this->out, $this->profile, $this->group,
$this->returnToArgs());
$maf->show();
$this->out->elementEnd('li');
}
}
function showGroupBlockForm()
{
$user = common_current_user();
if (!empty($user) && $user->id != $this->profile->id && $user->isAdmin($this->group)) {
$this->out->elementStart('li', 'entity_block');
$bf = new GroupBlockForm($this->out, $this->profile, $this->group,
$this->returnToArgs());
$bf->show();
$this->out->elementEnd('li');
}
}
function linkAttributes()
{
$aAttrs = parent::linkAttributes();
if (common_config('nofollow', 'members')) {
$aAttrs['rel'] .= ' nofollow';
}
return $aAttrs;
}
function homepageAttributes()
{
$aAttrs = parent::linkAttributes();
if (common_config('nofollow', 'members')) {
$aAttrs['rel'] = 'nofollow';
}
return $aAttrs;
}
/**
* Fetch necessary return-to arguments for the profile forms
* to return to this list when they're done.
*
* @return array
*/
protected function returnToArgs()
{
$args = array('action' => 'groupmembers',
'nickname' => $this->group->nickname);
$page = $this->out->arg('page');
if ($page) {
$args['param-page'] = $page;
}
return $args;
}
}
/**
* Form for blocking a user from 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 BlockForm
*/
class GroupBlockForm extends Form
{
/**
* Profile of user to block
*/
var $profile = null;
/**
* Group to block the user from
*/
var $group = null;
/**
* Return-to args
*/
var $args = null;
/**
* Constructor
*
* @param HTMLOutputter $out output channel
* @param Profile $profile profile of user to block
* @param User_group $group group to block user from
* @param array $args return-to args
*/
function __construct($out=null, $profile=null, $group=null, $args=null)
{
parent::__construct($out);
$this->profile = $profile;
$this->group = $group;
$this->args = $args;
}
/**
* ID of the form
*
* @return int ID of the form
*/
function id()
{
// This should be unique for the page.
return 'block-' . $this->profile->id;
}
/**
* class of the form
*
* @return string class of the form
*/
function formClass()
{
return 'form_group_block';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
return common_local_url('groupblock');
}
/**
* Legend of the Form
*
* @return void
*/
function formLegend()
{
// TRANS: Form legend for form to block user from a group.
$this->out->element('legend', null, _('Block user from group'));
}
/**
* Data elements of the form
*
* @return void
*/
function formData()
{
$this->out->hidden('blockto-' . $this->profile->id,
$this->profile->id,
'blockto');
$this->out->hidden('blockgroup-' . $this->group->id,
$this->group->id,
'blockgroup');
if ($this->args) {
foreach ($this->args as $k => $v) {
$this->out->hidden('returnto-' . $k, $v);
}
}
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
$this->out->submit(
'submit',
// TRANS: Button text for the form that will block a user from a group.
_m('BUTTON','Block'),
'submit',
null,
// TRANS: Submit button title.
_m('TOOLTIP', 'Block this user'));
}
}
/**
* Form for making a user an admin for 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/
*/
class MakeAdminForm extends Form
{
/**
* Profile of user to block
*/
var $profile = null;
/**
* Group to block the user from
*/
var $group = null;
/**
* Return-to args
*/
var $args = null;
/**
* Constructor
*
* @param HTMLOutputter $out output channel
* @param Profile $profile profile of user to block
* @param User_group $group group to block user from
* @param array $args return-to args
*/
function __construct($out=null, $profile=null, $group=null, $args=null)
{
parent::__construct($out);
$this->profile = $profile;
$this->group = $group;
$this->args = $args;
}
/**
* ID of the form
*
* @return int ID of the form
*/
function id()
{
// This should be unique for the page.
return 'makeadmin-' . $this->profile->id;
}
/**
* class of the form
*
* @return string class of the form
*/
function formClass()
{
return 'form_make_admin';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
return common_local_url('makeadmin', array('nickname' => $this->group->nickname));
}
/**
* Legend of the Form
*
* @return void
*/
function formLegend()
{
// TRANS: Form legend for form to make a user a group admin.
$this->out->element('legend', null, _('Make user an admin of the group'));
}
/**
* Data elements of the form
*
* @return void
*/
function formData()
{
$this->out->hidden('profileid-' . $this->profile->id,
$this->profile->id,
'profileid');
$this->out->hidden('groupid-' . $this->group->id,
$this->group->id,
'groupid');
if ($this->args) {
foreach ($this->args as $k => $v) {
$this->out->hidden('returnto-' . $k, $v);
}
}
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
$this->out->submit(
'submit',
// TRANS: Button text for the form that will make a user administrator.
_m('BUTTON','Make Admin'),
'submit',
null,
// TRANS: Submit button title.
_m('TOOLTIP','Make this user an admin'));
}
}

191
actions/groupqueue.php Normal file
View File

@ -0,0 +1,191 @@
<?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');
require_once INSTALLDIR.'/lib/publicgroupnav.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 GroupqueueAction extends GroupDesignAction
{
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);
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
$nickname_arg = $this->arg('nickname');
$nickname = common_canonical_nickname($nickname_arg);
// Permanent redirect on non-canonical nickname
if ($nickname_arg != $nickname) {
$args = array('nickname' => $nickname);
if ($this->page != 1) {
$args['page'] = $this->page;
}
common_redirect(common_local_url('groupqueue', $args), 301);
return false;
}
if (!$nickname) {
// TRANS: Client error displayed when trying to view group members without providing a group nickname.
$this->clientError(_('No nickname.'), 404);
return false;
}
$local = Local_group::staticGet('nickname', $nickname);
if (!$local) {
// TRANS: Client error displayed when trying to view group members for a non-existing group.
$this->clientError(_('No such group.'), 404);
return false;
}
$this->group = User_group::staticGet('id', $local->group_id);
if (!$this->group) {
// TRANS: Client error displayed when trying to view group members for an object that is not a group.
$this->clientError(_('No such group.'), 404);
return false;
}
$cur = common_current_user();
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.'));
return false;
}
return true;
}
function title()
{
if ($this->page == 1) {
// 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.
return sprintf(_('%s group members awaiting approval'),
$this->group->nickname);
} else {
// 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.
return sprintf(_('%1$s group members awaiting approval, page %2$d'),
$this->group->nickname,
$this->page);
}
}
function handle($args)
{
parent::handle($args);
$this->showPage();
}
function showPageNotice()
{
$this->element('p', 'instructions',
// TRANS: Page notice for group members page.
_('A list of users awaiting approval to join this group.'));
}
function showObjectNav()
{
$nav = new GroupNav($this, $this->group);
$nav->show();
}
function showContent()
{
$offset = ($this->page-1) * PROFILES_PER_PAGE;
$limit = PROFILES_PER_PAGE + 1;
$cnt = 0;
$members = $this->group->getRequests($offset, $limit);
if ($members) {
// @fixme change!
$member_list = new GroupQueueList($members, $this->group, $this);
$cnt = $member_list->show();
}
$members->free();
$this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE,
$this->page, 'groupmembers',
array('nickname' => $this->group->nickname));
}
}
class GroupQueueList extends GroupMemberList
{
function newListItem($profile)
{
return new GroupQueueListItem($profile, $this->group, $this->action);
}
}
class GroupQueueListItem extends GroupMemberListItem
{
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 ApproveGroupForm($this->out, $this->group, $this->profile);
$form->show();
$this->out->elementEnd('li');
}
}

View File

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

View File

@ -129,10 +129,7 @@ class JoingroupAction extends Action
$cur = common_current_user();
try {
if (Event::handle('StartJoinGroup', array($this->group, $cur))) {
Group_member::join($this->group->id, $cur->id);
Event::handle('EndJoinGroup', array($this->group, $cur));
}
$result = $cur->joinGroup($this->group);
} catch (Exception $e) {
// TRANS: Server error displayed when joining a group failed in the database.
// TRANS: %1$s is the joining user's nickname, $2$s is the group nickname for which the join failed.
@ -150,8 +147,17 @@ class JoingroupAction extends Action
$this->group->nickname));
$this->elementEnd('head');
$this->elementStart('body');
$lf = new LeaveForm($this, $this->group);
$lf->show();
if ($result instanceof Group_member) {
$form = new LeaveForm($this, $this->group);
} else if ($result instanceof Group_join_queue) {
$form = new CancelGroupForm($this, $this->group);
} else {
// wtf?
// TRANS: Exception thrown when there is an unknown error joining a group.
throw new Exception(_("Unknown error joining group."));
}
$form->show();
$this->elementEnd('body');
$this->elementEnd('html');
} else {

View File

@ -123,10 +123,7 @@ class LeavegroupAction extends Action
$cur = common_current_user();
try {
if (Event::handle('StartLeaveGroup', array($this->group, $cur))) {
Group_member::leave($this->group->id, $cur->id);
Event::handle('EndLeaveGroup', array($this->group, $cur));
}
$cur->leaveGroup($this->group);
} catch (Exception $e) {
// TRANS: Server error displayed when leaving a group failed in the database.
// TRANS: %1$s is the leaving user's nickname, $2$s is the group nickname for which the leave failed.

View File

@ -131,6 +131,7 @@ class NewgroupAction extends Action
$description = $this->trimmed('description');
$location = $this->trimmed('location');
$aliasstring = $this->trimmed('aliases');
$join_policy = intval($this->arg('join_policy'));
if ($this->nicknameExists($nickname)) {
// TRANS: Group create form validation error.
@ -215,6 +216,7 @@ class NewgroupAction extends Action
'location' => $location,
'aliases' => $aliases,
'userid' => $cur->id,
'join_policy' => $join_policy,
'local' => true));
$this->group = $group;

View File

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

View File

@ -100,7 +100,7 @@ class PublictagcloudAction extends Action
function showContent()
{
# This should probably be cached rather than recalculated
// This should probably be cached rather than recalculated
$tags = new Notice_tag();
#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); }
# You have 24 hours to claim your password
// You have 24 hours to claim your password
define('MAX_RECOVERY_TIME', 24 * 60 * 60);
@ -81,7 +81,7 @@ class RecoverpasswordAction extends Action
$touched = strtotime($confirm->modified);
$email = $confirm->address;
# Burn this code
// Burn this code
$result = $confirm->delete();
@ -92,8 +92,8 @@ class RecoverpasswordAction extends Action
return;
}
# These should be reaped, but for now we just check mod time
# Note: it's still deleted; let's avoid a second attempt!
// These should be reaped, but for now we just check mod time
// Note: it's still deleted; let's avoid a second attempt!
if ((time() - $touched) > MAX_RECOVERY_TIME) {
common_log(LOG_WARNING,
@ -105,8 +105,8 @@ class RecoverpasswordAction extends Action
return;
}
# If we used an outstanding confirmation to send the email,
# it's been confirmed at this point.
// If we used an outstanding confirmation to send the email,
// it's been confirmed at this point.
if (!$user->email) {
$orig = clone($user);
@ -120,7 +120,7 @@ class RecoverpasswordAction extends Action
}
}
# Success!
// Success!
$this->setTempUser($user);
$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) {
// Warning: it may actually be legit to have multiple folks
@ -314,7 +314,7 @@ class RecoverpasswordAction extends Action
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) {
$confirm_email = new Confirm_address();
@ -332,7 +332,7 @@ class RecoverpasswordAction extends Action
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->code = common_confirmation_code(128);
@ -380,7 +380,7 @@ class RecoverpasswordAction extends Action
function resetPassword()
{
# CSRF protection
// CSRF protection
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
// TRANS: Form validation error message.
@ -410,7 +410,7 @@ class RecoverpasswordAction extends Action
return;
}
# OK, we're ready to go
// OK, we're ready to go
$original = clone($user);

View File

@ -40,7 +40,6 @@ if (!defined('STATUSNET')) {
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*/
class SandboxAction extends ProfileFormAction
{
/**
@ -50,7 +49,6 @@ class SandboxAction extends ProfileFormAction
*
* @return boolean success flag
*/
function prepare($args)
{
if (!parent::prepare($args)) {
@ -62,6 +60,7 @@ class SandboxAction extends ProfileFormAction
assert(!empty($cur)); // checked by parent
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.'));
return false;
}
@ -69,6 +68,7 @@ class SandboxAction extends ProfileFormAction
assert(!empty($this->profile)); // checked by parent
if ($this->profile->isSandboxed()) {
// TRANS: Client error displayed trying to sandbox an already sandboxed user.
$this->clientError(_('User is already sandboxed.'));
return false;
}
@ -81,7 +81,6 @@ class SandboxAction extends ProfileFormAction
*
* @return void
*/
function handlePost()
{
$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
* @link http://status.net/
*/
class SessionsadminpanelAction extends AdminPanelAction
{
/**
@ -48,10 +47,10 @@ class SessionsadminpanelAction extends AdminPanelAction
*
* @return string page 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
*/
function getInstructions()
{
// TRANS: Instructions for the sessions administration panel.
return _('Session settings for this StatusNet site');
}
@ -70,7 +69,6 @@ class SessionsadminpanelAction extends AdminPanelAction
*
* @return void
*/
function showForm()
{
$form = new SessionsAdminPanelForm($this);
@ -83,7 +81,6 @@ class SessionsadminpanelAction extends AdminPanelAction
*
* @return void
*/
function saveSettings()
{
static $booleans = array('sessions' => array('handle', 'debug'));
@ -123,6 +120,7 @@ class SessionsadminpanelAction extends AdminPanelAction
}
}
// @todo FIXME: Class documentation missing.
class SessionsAdminPanelForm extends AdminForm
{
/**
@ -130,7 +128,6 @@ class SessionsAdminPanelForm extends AdminForm
*
* @return int ID of the form
*/
function id()
{
return 'sessionsadminpanel';
@ -141,7 +138,6 @@ class SessionsAdminPanelForm extends AdminForm
*
* @return string class of the form
*/
function formClass()
{
return 'form_settings';
@ -152,7 +148,6 @@ class SessionsAdminPanelForm extends AdminForm
*
* @return string URL of the action
*/
function action()
{
return common_local_url('sessionsadminpanel');
@ -163,24 +158,31 @@ class SessionsAdminPanelForm extends AdminForm
*
* @return void
*/
function formData()
{
$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->li();
// TRANS: Checkbox title on the sessions administration panel.
// TRANS: Indicates if StatusNet should handle session administration.
$this->out->checkbox('handle', _('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->li();
// TRANS: Checkbox label on the sessions administration panel.
// TRANS: Indicates if StatusNet should write session debugging output.
$this->out->checkbox('debug', _('Session debugging'),
(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->out->elementEnd('ul');
@ -193,9 +195,14 @@ class SessionsAdminPanelForm extends AdminForm
*
* @return void
*/
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);
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.'));
return false;
}
if (empty($this->application)) {
// TRANS: Client error displayed trying to display a non-existing OAuth application.
$this->clientError(_('No such application.'), 404);
return false;
}
@ -87,6 +89,7 @@ class ShowApplicationAction extends OwnerDesignAction
$cur = common_current_user();
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);
return false;
}
@ -148,6 +151,7 @@ class ShowApplicationAction extends OwnerDesignAction
$consumer = $this->application->getConsumer();
$this->elementStart('div', 'entity_profile vcard');
// TRANS: Header on the OAuth application page.
$this->element('h2', null, _('Application profile'));
if (!empty($this->application->icon)) {
$this->element('img', array('src' => $this->application->icon,
@ -176,7 +180,12 @@ class ShowApplicationAction extends OwnerDesignAction
$userCnt = $appUsers->count();
$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(),
$defaultAccess,
$userCnt
@ -186,13 +195,15 @@ class ShowApplicationAction extends OwnerDesignAction
$this->elementEnd('div');
$this->elementStart('div', 'entity_actions');
// TRANS: Header on the OAuth application page.
$this->element('h2', null, _('Application actions'));
$this->elementStart('ul');
$this->elementStart('li', 'entity_edit');
$this->element('a',
array('href' => common_local_url('editapplication',
array('id' => $this->application->id))),
'Edit');
// TRANS: Link text to edit application on the OAuth application page.
_m('EDITAPP','Edit'));
$this->elementEnd('li');
$this->elementStart('li', 'entity_reset_keysecret');
@ -209,6 +220,8 @@ class ShowApplicationAction extends OwnerDesignAction
'id' => 'reset',
'name' => 'reset',
'class' => 'submit',
// TRANS: Button text on the OAuth application page.
// TRANS: Resets the OAuth consumer key and secret.
'value' => _('Reset key & secret'),
'onClick' => 'return confirmReset()'));
$this->elementEnd('fieldset');
@ -225,7 +238,8 @@ class ShowApplicationAction extends OwnerDesignAction
$this->elementStart('fieldset');
$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('form');
$this->elementEnd('li');
@ -234,6 +248,7 @@ class ShowApplicationAction extends OwnerDesignAction
$this->elementEnd('div');
$this->elementStart('div', 'entity_data');
// TRANS: Header on the OAuth application page.
$this->element('h2', null, _('Application info'));
$this->element('div',
'entity_consumer_key',
@ -252,7 +267,8 @@ class ShowApplicationAction extends OwnerDesignAction
$this->element('div', 'entity_authorize_url', common_local_url('ApiOauthAuthorize'));
$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->elementStart('p', array('id' => 'application_action'));
@ -272,6 +288,7 @@ class ShowApplicationAction extends OwnerDesignAction
{
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?');
$js = 'function confirmReset() { ';

View File

@ -324,6 +324,8 @@ class ShowgroupAction extends GroupDesignAction
$this->element('h2', null, _('Statistics'));
$this->elementStart('dl');
// TRANS: Label for group creation date.
$this->element('dt', null, _m('LABEL','Created'));
$this->element('dd', 'entity_created', date('j M Y',
strtotime($this->group->created)));
@ -382,8 +384,8 @@ class GroupAdminSection extends ProfileSection
function title()
{
// TRANS: Header for list of group administrators on a group page (h2).
return _('Admins');
// TRANS: Title for list of group administrators on a group page.
return _m('TITLE','Admins');
}
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
* @link http://status.net/
*/
class ShownoticeAction extends OwnerDesignAction
{
/**
* Notice object to show
*/
var $notice = null;
/**
* Profile of the notice object
*/
var $profile = null;
/**
* Avatar of the profile of the notice object
*/
var $avatar = null;
/**
@ -74,7 +70,6 @@ class ShownoticeAction extends OwnerDesignAction
*
* @return success flag
*/
function prepare($args)
{
parent::prepare($args);
@ -90,8 +85,10 @@ class ShownoticeAction extends OwnerDesignAction
// 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;
@ -100,6 +97,7 @@ class ShownoticeAction extends OwnerDesignAction
$this->profile = $this->notice->getProfile();
if (empty($this->profile)) {
// TRANS: Server error displayed trying to show a notice without a connected profile.
$this->serverError(_('Notice has no profile.'), 500);
return false;
}
@ -116,7 +114,6 @@ class ShownoticeAction extends OwnerDesignAction
*
* @return boolean true
*/
function isReadOnly($args)
{
return true;
@ -130,7 +127,6 @@ class ShownoticeAction extends OwnerDesignAction
*
* @return int last-modified date as unix timestamp
*/
function lastModified()
{
return max(strtotime($this->notice->modified),
@ -147,7 +143,6 @@ class ShownoticeAction extends OwnerDesignAction
*
* @return string etag
*/
function etag()
{
$avtime = ($this->avatar) ?
@ -167,11 +162,12 @@ class ShownoticeAction extends OwnerDesignAction
*
* @return string title of the page
*/
function title()
{
$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'),
$base,
common_exact_date($this->notice->created));
@ -186,7 +182,6 @@ class ShownoticeAction extends OwnerDesignAction
*
* @return void
*/
function handle($args)
{
parent::handle($args);
@ -218,7 +213,6 @@ class ShownoticeAction extends OwnerDesignAction
*
* @return void
*/
function showLocalNavBlock()
{
}
@ -230,7 +224,6 @@ class ShownoticeAction extends OwnerDesignAction
*
* @return void
*/
function showContent()
{
$this->elementStart('ol', array('class' => 'notices xoxo'));
@ -245,7 +238,8 @@ class ShownoticeAction extends OwnerDesignAction
$this->xw->startDocument('1.0', 'UTF-8');
$this->elementStart('html');
$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->elementStart('body');
$nli = new NoticeListItem($this->notice, $this);
@ -259,7 +253,6 @@ class ShownoticeAction extends OwnerDesignAction
*
* @return void
*/
function showPageNoticeBlock()
{
}
@ -269,7 +262,6 @@ class ShownoticeAction extends OwnerDesignAction
*
* @return void
*/
function showAside() {
}
@ -280,7 +272,6 @@ class ShownoticeAction extends OwnerDesignAction
*
* @return void
*/
function extraHead()
{
$user = User::staticGet($this->profile->id);
@ -323,16 +314,16 @@ class ShownoticeAction extends OwnerDesignAction
}
}
// @todo FIXME: Class documentation missing.
class SingleNoticeItem extends DoFollowListItem
{
/**
* recipe function for displaying a single notice.
* Recipe function for displaying a single notice.
*
* We overload to show attachments.
*
* @return void
*/
function show()
{
$this->showStart();
@ -363,7 +354,6 @@ class SingleNoticeItem extends DoFollowListItem
*
* @return void
*/
function showAvatar()
{
$avatar_size = AVATAR_PROFILE_SIZE;

View File

@ -65,7 +65,8 @@ class ShowstreamAction extends ProfileAction
$base = $this->profile->getFancyName();
if (!empty($this->tag)) {
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);
} else {
// TRANS: Page title showing tagged notices in one user's stream.
@ -153,6 +154,8 @@ class ShowstreamAction extends ProfileAction
array(
'id' => $this->user->id,
'format' => 'atom')),
// TRANS: Title for link to notice feed.
// TRANS: %s is a user nickname.
sprintf(_('Notice feed for %s (Atom)'),
$this->user->nickname)),
new Feed(Feed::FOAF,

View File

@ -40,7 +40,6 @@ if (!defined('STATUSNET')) {
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*/
class SilenceAction extends ProfileFormAction
{
/**
@ -50,7 +49,6 @@ class SilenceAction extends ProfileFormAction
*
* @return boolean success flag
*/
function prepare($args)
{
if (!parent::prepare($args)) {
@ -62,6 +60,7 @@ class SilenceAction extends ProfileFormAction
assert(!empty($cur)); // checked by parent
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.'));
return false;
}
@ -69,6 +68,7 @@ class SilenceAction extends ProfileFormAction
assert(!empty($this->profile)); // checked by parent
if ($this->profile->isSilenced()) {
// TRANS: Client error displayed trying to silence an already silenced user.
$this->clientError(_('User is already silenced.'));
return false;
}
@ -81,7 +81,6 @@ class SilenceAction extends ProfileFormAction
*
* @return void
*/
function handlePost()
{
$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
* @link http://status.net/
*/
class SiteadminpanelAction extends AdminPanelAction
{
/**
@ -52,10 +51,10 @@ class SiteadminpanelAction extends AdminPanelAction
*
* @return string page 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
*/
function getInstructions()
{
// TRANS: Instructions for site administration panel.
return _('Basic settings for this StatusNet site');
}
@ -74,7 +73,6 @@ class SiteadminpanelAction extends AdminPanelAction
*
* @return void
*/
function showForm()
{
$form = new SiteAdminPanelForm($this);
@ -87,7 +85,6 @@ class SiteadminpanelAction extends AdminPanelAction
*
* @return void
*/
function saveSettings()
{
static $settings = array(
@ -130,6 +127,7 @@ class SiteadminpanelAction extends AdminPanelAction
// Validate 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.'));
}
@ -138,9 +136,11 @@ class SiteadminpanelAction extends AdminPanelAction
$values['site']['email'] = common_canonical_email($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.'));
}
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.'));
}
@ -148,6 +148,7 @@ class SiteadminpanelAction extends AdminPanelAction
if (is_null($values['site']['timezone']) ||
!in_array($values['site']['timezone'], DateTimeZone::listIdentifiers())) {
// TRANS: Client error displayed trying to save site settings without a timezone.
$this->clientError(_('Timezone not selected.'));
return;
}
@ -156,24 +157,28 @@ class SiteadminpanelAction extends AdminPanelAction
if (!is_null($values['site']['language']) &&
!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']));
}
// Validate text limit
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
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."));
}
}
}
// @todo FIXME: Class documentation missing.
class SiteAdminPanelForm extends AdminForm
{
/**
@ -181,7 +186,6 @@ class SiteAdminPanelForm extends AdminForm
*
* @return int ID of the form
*/
function id()
{
return 'form_site_admin_panel';
@ -192,7 +196,6 @@ class SiteAdminPanelForm extends AdminForm
*
* @return string class of the form
*/
function formClass()
{
return 'form_settings';
@ -203,7 +206,6 @@ class SiteAdminPanelForm extends AdminForm
*
* @return string URL of the action
*/
function action()
{
return common_local_url('siteadminpanel');
@ -214,35 +216,44 @@ class SiteAdminPanelForm extends AdminForm
*
* @return void
*/
function formData()
{
$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->li();
$this->input('name', _('Site name'),
_('The name of your site, like "Yourcompany Microblog"'));
// TRANS: Field label on site settings panel.
$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->li();
// TRANS: Field label on site settings panel.
$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->li();
// TRANS: Field label on site settings panel.
$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->li();
// TRANS: Field label on site settings panel.
$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->out->elementEnd('ul');
$this->out->elementEnd('fieldset');
$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');
$timezones = array();
@ -253,14 +264,20 @@ class SiteAdminPanelForm extends AdminForm
asort($timezones);
$this->li();
// TRANS: Dropdown label on site settings panel.
$this->out->dropdown('timezone', _('Default timezone'),
// TRANS: Dropdown title on site settings panel.
$timezones, _('Default timezone for the site; usually UTC.'),
true, $this->value('timezone'));
$this->unli();
$this->li();
$this->out->dropdown('language', _('Default language'),
get_nice_language_list(), _('Site language when autodetection from browser settings is not available'),
$this->out->dropdown('language',
// 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'));
$this->unli();
@ -268,14 +285,23 @@ class SiteAdminPanelForm extends AdminForm
$this->out->elementEnd('fieldset');
$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->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->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->out->elementEnd('ul');
$this->out->elementEnd('fieldset');
@ -286,9 +312,14 @@ class SiteAdminPanelForm extends AdminForm
*
* @return void
*/
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

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

View File

@ -112,7 +112,7 @@ class UserrssAction extends Rss10Action
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)
{

View File

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

View File

@ -79,12 +79,22 @@ class Fave extends Memcached_DataObject
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 NoticeStream(array('Fave', '_streamDirect'),
array($user_id, $own),
($own) ? 'fave:ids_by_user_own:'.$user_id :
'fave:ids_by_user:'.$user_id,
$offset, $limit, $since_id, $max_id);
return $ids;
'fave:ids_by_user:'.$user_id);
return $stream->getNotices($offset, $limit, $since_id, $max_id);
}
function idStream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $own=false, $since_id=0, $max_id=0)
{
$stream = new NoticeStream(array('Fave', '_streamDirect'),
array($user_id, $own),
($own) ? 'fave:ids_by_user_own:'.$user_id :
'fave:ids_by_user:'.$user_id);
return $stream->getNoticeIds($offset, $limit, $since_id, $max_id);
}
/**

View File

@ -449,12 +449,11 @@ class File extends Memcached_DataObject
function stream($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
{
$ids = Notice::stream(array($this, '_streamDirect'),
$stream = new NoticeStream(array($this, '_streamDirect'),
array(),
'file:notice-ids:'.$this->url,
$offset, $limit, $since_id, $max_id);
'file:notice-ids:'.$this->url);
return Notice::getStreamByIds($ids);
return $stream->getNotices($offset, $limit, $since_id, $max_id);
}
/**

View File

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

View File

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

View File

@ -0,0 +1,69 @@
<?php
/**
* Table Definition for request_queue
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Group_join_queue extends Managed_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'group_join_queue'; // table name
public $profile_id;
public $group_id;
public $created;
/* Static get */
function staticGet($k,$v=null)
{ return Memcached_DataObject::staticGet('Group_join_queue',$k,$v); }
/* Pkey get */
function pkeyGet($k)
{ return Memcached_DataObject::pkeyGet('Group_join_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 group join requests awaiting moderation.',
'fields' => array(
'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'remote or local profile making the request'),
'group_id' => array('type' => 'int', 'description' => 'remote or local group to join, if any'),
'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
),
'primary key' => array('profile_id', 'group_id'),
'indexes' => array(
'group_join_queue_profile_id_created_idx' => array('profile_id', 'created'),
'group_join_queue_group_id_created_idx' => array('group_id', 'created'),
),
'foreign keys' => array(
'group_join_queue_profile_id_fkey' => array('profile', array('profile_id' => 'id')),
'group_join_queue_group_id_fkey' => array('user_group', array('group_id' => 'id')),
)
);
}
public static function saveNew(Profile $profile, User_group $group)
{
$rq = new Group_join_queue();
$rq->profile_id = $profile->id;
$rq->group_id = $group->id;
$rq->created = common_sql_now();
$rq->insert();
return $rq;
}
/**
* 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

@ -28,6 +28,7 @@ class Group_member extends Memcached_DataObject
/**
* Method to add a user to a group.
* In most cases, you should call Profile->joinGroup() instead.
*
* @param integer $group_id Group to add to
* @param integer $profile_id Profile being added
@ -161,4 +162,13 @@ class Group_member extends Memcached_DataObject
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
// Fast path when nothing's deleted.
$firstChunk = array_slice($ids, 0, $offset + $limit);
$notices = Notice::getStreamByIds($firstChunk);
$notices = NoticeStream::getStreamByIds($firstChunk);
assert($notices instanceof ArrayWrapper);
$items = $notices->_items;
@ -292,7 +292,7 @@ class Inbox extends Memcached_DataObject
// Do a bulk lookup for the first $limit items
// Fast path when nothing's deleted.
$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
if ($notices->N >= $wanted) {

View File

@ -93,6 +93,7 @@ abstract class Managed_DataObject extends Memcached_DataObject
function keyTypes()
{
$table = call_user_func(array(get_class($this), 'schemaDef'));
$keys = array();
if (!empty($table['unique keys'])) {
foreach ($table['unique keys'] as $idx => $fields) {

View File

@ -34,7 +34,7 @@ class Memcached_DataObject extends Safe_DataObject
{
if (is_null($v)) {
$v = $k;
# XXX: HACK!
// XXX: HACK!
$i = new $cls;
$keys = $i->keys();
$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,
* in the memcached cache. */
define('NOTICE_CACHE_WINDOW', 200);
define('NOTICE_CACHE_WINDOW', NoticeStream::CACHE_WINDOW);
define('MAX_BOXCARS', 128);
@ -312,7 +312,7 @@ class Notice extends Memcached_DataObject
$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) ||
($source && $autosource && in_array($source, $autosource))) {
@ -410,8 +410,8 @@ class Notice extends Memcached_DataObject
}
# Clear the cache for subscribed users, so they'll update at next request
# XXX: someone clever could prepend instead of clearing the cache
// Clear the cache for subscribed users, so they'll update at next request
// XXX: someone clever could prepend instead of clearing the cache
$notice->blowOnInsert();
@ -548,7 +548,7 @@ class Notice extends Memcached_DataObject
if (empty($profile)) {
return false;
}
$notice = $profile->getNotices(0, NOTICE_CACHE_WINDOW);
$notice = $profile->getNotices(0, NoticeStream::CACHE_WINDOW);
if (!empty($notice)) {
$last = 0;
while ($notice->fetch()) {
@ -559,8 +559,8 @@ class Notice extends Memcached_DataObject
}
}
}
# If we get here, oldest item in cache window is not
# old enough for dupe limit; do direct check against DB
// If we get here, oldest item in cache window is not
// old enough for dupe limit; do direct check against DB
$notice = new Notice();
$notice->profile_id = $profile_id;
$notice->content = $content;
@ -576,16 +576,16 @@ class Notice extends Memcached_DataObject
if (empty($profile)) {
return false;
}
# Get the Nth notice
// Get the Nth notice
$notice = $profile->getNotices(common_config('throttle', 'count') - 1, 1);
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')) {
# Then we throttle
// Then we throttle
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;
}
@ -629,54 +629,14 @@ class Notice extends Memcached_DataObject
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)
{
$ids = Notice::stream(array('Notice', '_publicStreamDirect'),
$stream = new NoticeStream(array('Notice', '_publicStreamDirect'),
array(),
'public',
$offset, $limit, $since_id, $max_id);
return Notice::getStreamByIds($ids);
'public');
return $stream->getNotices($offset, $limit, $since_id, $max_id);
}
function _publicStreamDirect($offset=0, $limit=20, $since_id=0, $max_id=0)
@ -695,7 +655,7 @@ class Notice extends Memcached_DataObject
if (common_config('public', 'localonly')) {
$notice->whereAdd('is_local = ' . Notice::LOCAL_PUBLIC);
} else {
# -1 == blacklisted, -2 == gateway (i.e. Twitter)
// -1 == blacklisted, -2 == gateway (i.e. Twitter)
$notice->whereAdd('is_local !='. Notice::LOCAL_NONPUBLIC);
$notice->whereAdd('is_local !='. Notice::GATEWAY);
}
@ -719,12 +679,11 @@ class Notice extends Memcached_DataObject
function conversationStream($id, $offset=0, $limit=20, $since_id=0, $max_id=0)
{
$ids = Notice::stream(array('Notice', '_conversationStreamDirect'),
$stream = new NoticeStream(array('Notice', '_conversationStreamDirect'),
array($id),
'notice:conversation_ids:'.$id,
$offset, $limit, $since_id, $max_id);
'notice:conversation_ids:'.$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)
@ -1540,61 +1499,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.
@ -1752,7 +1656,7 @@ class Notice extends Memcached_DataObject
}
}
return Notice::getStreamByIds($ids);
return NoticeStream::getStreamByIds($ids);
}
function _repeatStreamDirect($limit)

View File

@ -36,14 +36,13 @@ class Notice_tag extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
static function getStream($tag, $offset=0, $limit=20) {
$ids = Notice::stream(array('Notice_tag', '_streamDirect'),
static function getStream($tag, $offset=0, $limit=20, $sinceId=0, $maxId=0)
{
$stream = new NoticeStream(array('Notice_tag', '_streamDirect'),
array($tag),
'notice_tag:notice_ids:' . Cache::keyize($tag),
$offset, $limit);
'notice_tag:notice_ids:' . Cache::keyize($tag));
return Notice::getStreamByIds($ids);
return $stream->getNotices($offset, $limit, $sinceId, $maxId);
}
function _streamDirect($tag, $offset, $limit, $since_id, $max_id)

View File

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

View File

@ -93,7 +93,7 @@ class Profile extends Memcached_DataObject
$avatar->url = Avatar::url($filename);
$avatar->created = DB_DataObject_Cast::dateTime(); # current time
# XXX: start a transaction here
// XXX: start a transaction here
if (!$this->delete_avatars() || !$avatar->insert()) {
@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) {
# 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)) {
$scaled_filename = $imagefile->resize($size);
@ -198,22 +198,20 @@ class Profile extends Memcached_DataObject
function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
{
$ids = Notice::stream(array($this, '_streamTaggedDirect'),
$stream = new NoticeStream(array($this, '_streamTaggedDirect'),
array($tag),
'profile:notice_ids_tagged:' . $this->id . ':' . $tag,
$offset, $limit, $since_id, $max_id);
return Notice::getStreamByIds($ids);
'profile:notice_ids_tagged:'.$this->id.':'.$tag);
return $stream->getNotices($offset, $limit, $since_id, $max_id);
}
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.
$ids = Notice::stream(array($this, '_streamDirect'),
$stream = new NoticeStream(array($this, '_streamDirect'),
array(),
'profile:notice_ids:' . $this->id,
$offset, $limit, $since_id, $max_id);
'profile:notice_ids:' . $this->id);
return Notice::getStreamByIds($ids);
return $stream->getNotices($offset, $limit, $since_id, $max_id);
}
function _streamTaggedDirect($tag, $offset, $limit, $since_id, $max_id)
@ -313,6 +311,13 @@ class Profile extends Memcached_DataObject
}
}
function isPendingMember($group)
{
$request = Group_join_queue::pkeyGet(array('profile_id' => $this->id,
'group_id' => $group->id));
return !empty($request);
}
function getGroups($offset=0, $limit=null)
{
$qry =
@ -339,6 +344,87 @@ class Profile extends Memcached_DataObject
return $groups;
}
/**
* Request to join the given group.
* May throw exceptions on failure.
*
* @param User_group $group
* @return mixed: Group_member on success, Group_join_queue if pending approval, null on some cancels?
*/
function joinGroup(User_group $group)
{
$join = null;
if ($group->join_policy == User_group::JOIN_POLICY_MODERATE) {
$join = Group_join_queue::saveNew($this, $group);
} else {
if (Event::handle('StartJoinGroup', array($group, $this))) {
$join = Group_member::join($group->id, $this->id);
Event::handle('EndJoinGroup', array($group, $this));
}
}
if ($join) {
// Send any applicable notifications...
$join->notify();
}
return $join;
}
/**
* 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));
}
}
}
/**
* Complete a pending group join on our end...
*
* @param User_group $group
*/
function completeJoinGroup(User_group $group)
{
$join = null;
$request = Group_join_queue::pkeyGet(array('profile_id' => $this->id,
'group_id' => $group->id));
if ($request) {
if (Event::handle('StartJoinGroup', array($group, $this))) {
$join = Group_member::join($group->id, $this->id);
$request->delete();
Event::handle('EndJoinGroup', array($group, $this));
}
} else {
// TRANS: Exception thrown trying to approve a non-existing group join request.
throw new Exception(_('Invalid group join approval: not pending.'));
}
if ($join) {
$join->notify();
}
return $join;
}
/**
* Leave a group that this profile is a member of.
*
* @param User_group $group
*/
function leaveGroup(User_group $group)
{
if (Event::handle('StartLeaveGroup', array($group, $this))) {
Group_member::leave($group->id, $this->id);
Event::handle('EndLeaveGroup', array($group, $this));
}
}
function avatarUrl($size=AVATAR_PROFILE_SIZE)
{
$avatar = $this->getAvatar($size);
@ -465,7 +551,7 @@ class Profile extends Memcached_DataObject
// This is the stream of favorite notices, in rev chron
// order. This forces it into cache.
$ids = Fave::stream($this->id, 0, NOTICE_CACHE_WINDOW);
$ids = Fave::idStream($this->id, 0, NoticeStream::CACHE_WINDOW);
// If it's in the list, then it's a fave
@ -477,7 +563,7 @@ class Profile extends Memcached_DataObject
// then the cache has all available faves, so this one
// is not a fave.
if (count($ids) < NOTICE_CACHE_WINDOW) {
if (count($ids) < NoticeStream::CACHE_WINDOW) {
return false;
}

View File

@ -25,7 +25,7 @@ class Profile_tag extends Memcached_DataObject
static function getTags($tagger, $tagged) {
$tags = array();
# XXX: store this in memcached
// XXX: store this in memcached
$profile_tag = new Profile_tag();
$profile_tag->tagger = $tagger;
@ -46,11 +46,11 @@ class Profile_tag extends Memcached_DataObject
$newtags = array_unique($newtags);
$oldtags = Profile_tag::getTags($tagger, $tagged);
# Delete stuff that's old that not in new
// Delete stuff that's old that not in new
$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);
@ -84,7 +84,7 @@ class Profile_tag extends Memcached_DataObject
return true;
}
# Return profiles with a given tag
// Return profiles with a given tag
static function getTagged($tagger, $tag) {
$profile = new Profile();
$profile->query('SELECT profile.* ' .

View File

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

View File

@ -38,11 +38,11 @@ class Reply extends Memcached_DataObject
function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
{
$ids = Notice::stream(array('Reply', '_streamDirect'),
$stream = new NoticeStream(array('Reply', '_streamDirect'),
array($user_id),
'reply:stream:' . $user_id,
$offset, $limit, $since_id, $max_id);
return $ids;
'reply:stream:' . $user_id);
return $stream->getNotices($offset, $limit, $since_id, $max_id);
}
function _streamDirect($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)

View File

@ -146,9 +146,9 @@ class Subscription extends Memcached_DataObject
function notify()
{
# XXX: add other notifications (Jabber, SMS) here
# XXX: queue this and handle it offline
# XXX: Whatever happens, do it in Twitter-like API, too
// XXX: add other notifications (Jabber, SMS) here
// XXX: queue this and handle it offline
// XXX: Whatever happens, do it in Twitter-like API, too
$this->notifyEmail();
}

View File

@ -68,6 +68,9 @@ class User extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
/**
* @return Profile
*/
function getProfile()
{
$profile = Profile::staticGet('id', $this->id);
@ -445,8 +448,7 @@ class User extends Memcached_DataObject
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 Notice::getStreamByIds($ids);
return Reply::stream($this->id, $offset, $limit, $since_id, $before_id);
}
function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) {
@ -462,8 +464,7 @@ class User extends Memcached_DataObject
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 Notice::getStreamByIds($ids);
return Fave::stream($this->id, $offset, $limit, $own, $since_id, $max_id);
}
function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
@ -596,6 +597,30 @@ class User extends Memcached_DataObject
return $profile->getGroups($offset, $limit);
}
/**
* Request to join the given group.
* May throw exceptions on failure.
*
* @param User_group $group
* @return Group_member
*/
function joinGroup(User_group $group)
{
$profile = $this->getProfile();
return $profile->joinGroup($group);
}
/**
* Leave a group that this user is a member of.
*
* @param User_group $group
*/
function leaveGroup(User_group $group)
{
$profile = $this->getProfile();
return $profile->leaveGroup($group);
}
function getSubscriptions($offset=0, $limit=null)
{
$profile = $this->getProfile();
@ -742,12 +767,11 @@ class User extends Memcached_DataObject
function repeatedByMe($offset=0, $limit=20, $since_id=null, $max_id=null)
{
$ids = Notice::stream(array($this, '_repeatedByMeDirect'),
$stream = new NoticeStream(array($this, '_repeatedByMeDirect'),
array(),
'user:repeated_by_me:'.$this->id,
$offset, $limit, $since_id, $max_id, null);
'user:repeated_by_me:'.$this->id);
return Notice::getStreamByIds($ids);
return $stream->getNotices($offset, $limit, $since_id, $max_id);
}
function _repeatedByMeDirect($offset, $limit, $since_id, $max_id)
@ -785,12 +809,11 @@ class User extends Memcached_DataObject
function repeatsOfMe($offset=0, $limit=20, $since_id=null, $max_id=null)
{
$ids = Notice::stream(array($this, '_repeatsOfMeDirect'),
$stream = new NoticeStream(array($this, '_repeatsOfMeDirect'),
array(),
'user:repeats_of_me:'.$this->id,
$offset, $limit, $since_id, $max_id);
'user:repeats_of_me:'.$this->id);
return Notice::getStreamByIds($ids);
return $stream->getNotices($offset, $limit, $since_id, $max_id);
}
function _repeatsOfMeDirect($offset, $limit, $since_id, $max_id)

View File

@ -5,6 +5,9 @@
class User_group extends Memcached_DataObject
{
const JOIN_POLICY_OPEN = 0;
const JOIN_POLICY_MODERATE = 1;
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
@ -24,6 +27,7 @@ class User_group extends Memcached_DataObject
public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
public $uri; // varchar(255) unique_key
public $mainpage; // varchar(255)
public $join_policy; // tinyint
/* Static get */
function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('User_group',$k,$v); }
@ -83,12 +87,11 @@ class User_group extends Memcached_DataObject
function getNotices($offset, $limit, $since_id=null, $max_id=null)
{
$ids = Notice::stream(array($this, '_streamDirect'),
$stream = new NoticeStream(array($this, '_streamDirect'),
array(),
'user_group:notice_ids:' . $this->id,
$offset, $limit, $since_id, $max_id);
'user_group:notice_ids:' . $this->id);
return Notice::getStreamByIds($ids);
return $stream->getNotices($offset, $limit, $since_id, $max_id);
}
function _streamDirect($offset, $limit, $since_id, $max_id)
@ -149,6 +152,36 @@ class User_group extends Memcached_DataObject
return $members;
}
/**
* Get pending members, 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 group_join_queue '.
'ON profile.id = group_join_queue.profile_id ' .
'WHERE group_join_queue.group_id = %d ' .
'ORDER BY group_join_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 getMemberCount()
{
// XXX: WORM cache this
@ -272,11 +305,11 @@ class User_group extends Memcached_DataObject
$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);
# 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);
@ -511,6 +544,11 @@ class User_group extends Memcached_DataObject
$group->uri = $uri;
$group->mainpage = $mainpage;
$group->created = common_sql_now();
if (isset($fields['join_policy'])) {
$group->join_policy = intval($fields['join_policy']);
} else {
$group->join_policy = 0;
}
if (Event::handle('StartGroupSave', array(&$group))) {

View File

@ -621,6 +621,7 @@ created = 142
modified = 384
uri = 2
mainpage = 2
join_policy = 1
[user_group__keys]
id = N

View File

@ -649,6 +649,7 @@ $schema['user_group'] = array(
'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universal identifier'),
'mainpage' => array('type' => 'varchar', 'length' => 255, 'description' => 'page for group info to link to'),
'join_policy' => array('type' => 'int', 'size' => 'tiny', 'description' => '0=open; 1=requires admin approval'),
),
'primary key' => array('id'),
'unique keys' => array(
@ -1025,3 +1026,5 @@ $schema['schema_version'] = array(
),
'primary key' => array('table_name'),
);
$schema['group_join_queue'] = Group_join_queue::schemaDef();

View File

@ -1464,6 +1464,18 @@ var SN = { // StatusNet
SN.U.FormXHR($(this));
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

@ -163,10 +163,7 @@ class ActivityImporter extends QueueHandler
throw new ClientException(_("User is already a member of this group."));
}
if (Event::handle('StartJoinGroup', array($group, $user))) {
Group_member::join($group->id, $user->id);
Event::handle('EndJoinGroup', array($group, $user));
}
$user->joinGroup($group);
}
// XXX: largely cadged from Ostatus_profile::processNote()

View File

@ -116,7 +116,7 @@ class ActivityMover extends QueueHandler
$sink->postActivity($act);
$group = User_group::staticGet('uri', $act->objects[0]->id);
if (!empty($group)) {
Group_member::leave($group->id, $user->id);
$user->leaveGroup($group);
}
break;
case ActivityVerb::FOLLOW:

View File

@ -292,7 +292,7 @@ class ApiAction extends Action
if ($get_notice) {
$notice = $profile->getCurrentNotice();
if ($notice) {
# don't get user!
// don't get user!
$twitter_user['status'] = $this->twitterStatusArray($notice, false);
}
}
@ -397,7 +397,7 @@ class ApiAction extends Action
}
if ($include_user && $profile) {
# Don't get notice (recursive!)
// Don't get notice (recursive!)
$twitter_user = $this->twitterUserArray($profile, false);
$twitter_status['user'] = $twitter_user;
}
@ -698,7 +698,7 @@ class ApiAction extends Action
$this->element('guid', null, $entry['guid']);
$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'])){
$enclosure = $entry['enclosures'][0];
$this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null);
@ -833,7 +833,7 @@ class ApiAction extends Action
}
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',
'href' => $suplink,
'type' => 'application/json'));

120
lib/approvegroupform.php Normal file
View File

@ -0,0 +1,120 @@
<?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 ApproveGroupForm extends Form
{
/**
* group for user to leave
*/
var $group = null;
var $profile = null;
/**
* Constructor
*
* @param HTMLOutputter $out output channel
* @param group $group group to leave
*/
function __construct($out=null, $group=null, $profile=null)
{
parent::__construct($out);
$this->group = $group;
$this->profile = $profile;
}
/**
* ID of the form
*
* @return string ID of the form
*/
function id()
{
return 'group-queue-' . $this->group->id;
}
/**
* class of the form
*
* @return string of the form class
*/
function formClass()
{
return 'form_group_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('approvegroup',
array('id' => $this->group->id), $params);
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
// 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'));
}
}

117
lib/cancelgroupform.php Normal file
View File

@ -0,0 +1,117 @@
<?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 CancelGroupForm extends Form
{
/**
* group for user to leave
*/
var $group = null;
var $profile = null;
/**
* Constructor
*
* @param HTMLOutputter $out output channel
* @param group $group group to leave
*/
function __construct($out=null, $group=null, $profile=null)
{
parent::__construct($out);
$this->group = $group;
$this->profile = $profile;
}
/**
* ID of the form
*
* @return string ID of the form
*/
function id()
{
return 'group-cancel-' . $this->group->id;
}
/**
* class of the form
*
* @return string of the form class
*/
function formClass()
{
return 'form_group_leave 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('cancelgroup',
array('id' => $this->group->id), $params);
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
// TRANS: Submit button text on form to cancel group join request.
$this->out->submit('submit', _('BUTTON','Cancel join request'));
}
}

View File

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

View File

@ -352,10 +352,7 @@ class JoinCommand extends Command
}
try {
if (Event::handle('StartJoinGroup', array($group, $cur))) {
Group_member::join($group->id, $cur->id);
Event::handle('EndJoinGroup', array($group, $cur));
}
$cur->joinGroup($group);
} catch (Exception $e) {
// TRANS: Message given having failed to add a user to a group.
// TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group.
@ -400,10 +397,7 @@ class DropCommand extends Command
}
try {
if (Event::handle('StartLeaveGroup', array($group, $cur))) {
Group_member::leave($group->id, $cur->id);
Event::handle('EndLeaveGroup', array($group, $cur));
}
$cur->leaveGroup($group);
} catch (Exception $e) {
// TRANS: Message given having failed to remove a user from a group.
// TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group.

View File

@ -48,7 +48,7 @@ define('NOTICE_INBOX_SOURCE_REPLY', 3);
define('NOTICE_INBOX_SOURCE_FORWARD', 4);
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/');
@ -69,7 +69,7 @@ if (!function_exists('dl')) {
}
}
# global configuration object
// global configuration object
require_once('PEAR.php');
require_once('PEAR/Exception.php');

View File

@ -84,7 +84,7 @@ class GalleryAction extends OwnerDesignAction
{
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') {
common_redirect($this->selfUrl(), 303);

130
lib/groupblockform.php Normal file
View File

@ -0,0 +1,130 @@
<?php
// @todo FIXME: standard file header missing.
/**
* Form for blocking a user from 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 BlockForm
*/
class GroupBlockForm extends Form
{
/**
* Profile of user to block
*/
var $profile = null;
/**
* Group to block the user from
*/
var $group = null;
/**
* Return-to args
*/
var $args = null;
/**
* Constructor
*
* @param HTMLOutputter $out output channel
* @param Profile $profile profile of user to block
* @param User_group $group group to block user from
* @param array $args return-to args
*/
function __construct($out=null, $profile=null, $group=null, $args=null)
{
parent::__construct($out);
$this->profile = $profile;
$this->group = $group;
$this->args = $args;
}
/**
* ID of the form
*
* @return int ID of the form
*/
function id()
{
// This should be unique for the page.
return 'block-' . $this->profile->id;
}
/**
* class of the form
*
* @return string class of the form
*/
function formClass()
{
return 'form_group_block';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
return common_local_url('groupblock');
}
/**
* Legend of the Form
*
* @return void
*/
function formLegend()
{
// TRANS: Form legend for form to block user from a group.
$this->out->element('legend', null, _('Block user from group'));
}
/**
* Data elements of the form
*
* @return void
*/
function formData()
{
$this->out->hidden('blockto-' . $this->profile->id,
$this->profile->id,
'blockto');
$this->out->hidden('blockgroup-' . $this->group->id,
$this->group->id,
'blockgroup');
if ($this->args) {
foreach ($this->args as $k => $v) {
$this->out->hidden('returnto-' . $k, $v);
}
}
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
$this->out->submit(
'submit',
// TRANS: Button text for the form that will block a user from a group.
_m('BUTTON','Block'),
'submit',
null,
// TRANS: Submit button title.
_m('TOOLTIP', 'Block this user'));
}
}

View File

@ -112,6 +112,7 @@ class GroupEditForm extends Form
*/
function formLegend()
{
// TRANS: Form legend for group edit form.
$this->out->element('legend', null, _('Create a new group'));
}
@ -142,50 +143,75 @@ class GroupEditForm extends Form
if (Event::handle('StartGroupEditFormData', array($this))) {
$this->out->elementStart('li');
$this->out->hidden('groupid', $id);
// TRANS: Field label on group edit form.
$this->out->input('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->elementStart('li');
// TRANS: Field label on group edit form.
$this->out->input('fullname', _('Full name'),
($this->out->arg('fullname')) ? $this->out->arg('fullname') : $fullname);
$this->out->elementEnd('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->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.'));
$this->out->elementEnd('li');
$this->out->elementStart('li');
$desclimit = User_group::maxDescription();
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 {
$descinstr = sprintf(_m('Describe the group or topic in %d character or less',
'Describe the group or topic in %d characters or less',
// TRANS: Text area title for group description.
// 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);
}
// TRANS: Text area label on group edit form; contains description of group.
$this->out->textarea('description', _('Description'),
($this->out->arg('description')) ? $this->out->arg('description') : $description,
$descinstr);
$this->out->elementEnd('li');
$this->out->elementStart('li');
// TRANS: Field label on group edit form.
$this->out->input('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".'));
$this->out->elementEnd('li');
if (common_config('group', 'maxaliases') > 0) {
$aliases = (empty($this->group)) ? array() : $this->group->getAliases();
$this->out->elementStart('li');
// TRANS: Field label on group edit form.
$this->out->input('aliases', _('Aliases'),
($this->out->arg('aliases')) ? $this->out->arg('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.',
'Extra nicknames for the group, separated with commas or spaces. Maximum %d aliases allowed.',
common_config('group', 'maxaliases')),
common_config('group', 'maxaliases')));;
$this->out->elementEnd('li');
}
$this->out->elementStart('li');
$this->out->dropdown('join_policy',
// TRANS: Dropdown fieldd label on group edit form.
_('Membership policy'),
array(User_group::JOIN_POLICY_OPEN => _('Open to all'),
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.'),
false,
(empty($this->group->join_policy)) ? User_group::JOIN_POLICY_OPEN : $this->group->join_policy);
$this->out->elementEnd('li');
Event::handle('EndGroupEditFormData', array($this));
}
$this->out->elementEnd('ul');
@ -198,6 +224,7 @@ class GroupEditForm extends Form
*/
function formActions()
{
// TRANS: Text for save button on group edit form.
$this->out->submit('submit', _m('BUTTON','Save'));
}
}

View File

@ -137,7 +137,7 @@ class GroupList extends Widget
$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) {
$this->showOwnerControls();
@ -149,8 +149,8 @@ class GroupList extends Widget
$this->out->elementStart('div', 'entity_actions');
$this->out->elementStart('ul');
$this->out->elementStart('li', 'entity_subscribe');
# XXX: special-case for user looking at own
# subscriptions page
// XXX: special-case for user looking at own
// subscriptions page
if ($user->isMember($this->group)) {
$lf = new LeaveForm($this->out, $this->group);
$lf->show();

19
lib/groupmemberlist.php Normal file
View File

@ -0,0 +1,19 @@
<?php
// @todo FIXME: add documentation.
class GroupMemberList extends ProfileList
{
var $group = null;
function __construct($profile, $group, $action)
{
parent::__construct($profile, $action);
$this->group = $group;
}
function newListItem($profile)
{
return new GroupMemberListItem($profile, $this->group, $this->action);
}
}

105
lib/groupmemberlistitem.php Normal file
View File

@ -0,0 +1,105 @@
<?php
// @todo FIXME: add documentation.
class GroupMemberListItem extends ProfileListItem
{
var $group = null;
function __construct($profile, $group, $action)
{
parent::__construct($profile, $action);
$this->group = $group;
}
function showFullName()
{
parent::showFullName();
if ($this->profile->isAdmin($this->group)) {
$this->out->text(' '); // for separating the classes.
// TRANS: Indicator in group members list that this user is a group administrator.
$this->out->element('span', 'role', _m('GROUPADMIN','Admin'));
}
}
function showActions()
{
$this->startActions();
if (Event::handle('StartProfileListItemActionElements', array($this))) {
$this->showSubscribeButton();
$this->showMakeAdminForm();
$this->showGroupBlockForm();
Event::handle('EndProfileListItemActionElements', array($this));
}
$this->endActions();
}
function showMakeAdminForm()
{
$user = common_current_user();
if (!empty($user) &&
$user->id != $this->profile->id &&
($user->isAdmin($this->group) || $user->hasRight(Right::MAKEGROUPADMIN)) &&
!$this->profile->isAdmin($this->group)) {
$this->out->elementStart('li', 'entity_make_admin');
$maf = new MakeAdminForm($this->out, $this->profile, $this->group,
$this->returnToArgs());
$maf->show();
$this->out->elementEnd('li');
}
}
function showGroupBlockForm()
{
$user = common_current_user();
if (!empty($user) && $user->id != $this->profile->id && $user->isAdmin($this->group)) {
$this->out->elementStart('li', 'entity_block');
$bf = new GroupBlockForm($this->out, $this->profile, $this->group,
$this->returnToArgs());
$bf->show();
$this->out->elementEnd('li');
}
}
function linkAttributes()
{
$aAttrs = parent::linkAttributes();
if (common_config('nofollow', 'members')) {
$aAttrs['rel'] .= ' nofollow';
}
return $aAttrs;
}
function homepageAttributes()
{
$aAttrs = parent::linkAttributes();
if (common_config('nofollow', 'members')) {
$aAttrs['rel'] = 'nofollow';
}
return $aAttrs;
}
/**
* Fetch necessary return-to arguments for the profile forms
* to return to this list when they're done.
*
* @return array
*/
protected function returnToArgs()
{
$args = array('action' => 'groupmembers',
'nickname' => $this->group->nickname);
$page = $this->out->arg('page');
if ($page) {
$args['param-page'] = $page;
}
return $args;
}
}

View File

@ -48,7 +48,6 @@ require_once INSTALLDIR.'/lib/widget.php';
*
* @see HTMLOutputter
*/
class GroupNav extends Menu
{
var $group = null;
@ -58,7 +57,6 @@ class GroupNav extends Menu
*
* @param Action $action current action, used for output
*/
function __construct($action=null, $group=null)
{
parent::__construct($action);
@ -70,7 +68,6 @@ class GroupNav extends Menu
*
* @return void
*/
function show()
{
$action_name = $this->action->trimmed('action');
@ -100,6 +97,19 @@ class GroupNav extends Menu
$cur = common_current_user();
if ($cur && $cur->isAdmin($this->group)) {
$pending = $this->countPendingMembers();
if ($pending || $this->group->join_policy == User_group::JOIN_POLICY_MODERATE) {
$this->out->menuItem(common_local_url('groupqueue', array('nickname' =>
$nickname)),
// TRANS: Menu item in the group navigation page. Only shown for group administrators.
// 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: %s is the nickname of the group.
sprintf(_m('TOOLTIP','%s pending members'), $nickname),
$action_name == 'groupqueue',
'nav_group_pending');
}
$this->out->menuItem(common_local_url('blockedfromgroup', array('nickname' =>
$nickname)),
// TRANS: Menu item in the group navigation page. Only shown for group administrators.
@ -141,4 +151,11 @@ class GroupNav extends Menu
}
$this->out->elementEnd('ul');
}
function countPendingMembers()
{
$req = new Group_join_queue();
$req->group_id = $this->group->id;
return $req->count();
}
}

View File

@ -97,10 +97,14 @@ class GroupProfileBlock extends ProfileBlock
$this->out->elementStart('li', 'entity_subscribe');
if (Event::handle('StartGroupSubscribe', array($this, $this->group))) {
if ($cur) {
if ($cur->isMember($this->group)) {
$profile = $cur->getProfile();
if ($profile->isMember($this->group)) {
$lf = new LeaveForm($this->out, $this->group);
$lf->show();
} else if (!Group_block::isBlocked($this->group, $cur->getProfile())) {
} else if ($profile->isPendingMember($this->group)) {
$cf = new CancelGroupForm($this->out, $this->group);
$cf->show();
} else if (!Group_block::isBlocked($this->group, $profile)) {
$jf = new JoinForm($this->out, $this->group);
$jf->show();
}

View File

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

View File

@ -245,44 +245,13 @@ function mail_subscribe_notify_profile($listenee, $other)
$other->getBestName(),
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: %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)
// 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"),
// TRANS: %1$s is the subscriber's long name, %2$s is the StatusNet sitename.
$body = sprintf(_('%1$s is now listening to your notices on %2$s.'),
$long_name,
common_config('site', 'name'),
$other->profileurl,
($other->location) ?
// 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'));
common_config('site', 'name')) .
mail_profile_block($other) .
mail_footer_block();
// reset localization
common_switch_locale();
@ -290,6 +259,69 @@ function mail_subscribe_notify_profile($listenee, $other)
}
}
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
*
@ -317,11 +349,11 @@ function mail_new_incoming_notify($user)
// 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".
"Send email to %2\$s to post new messages.\n\n".
"More email instructions at %3\$s.\n\n".
"Faithfully yours,\n%1\$s"),
"More email instructions at %3\$s."),
common_config('site', 'name'),
$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);
}
@ -466,7 +498,7 @@ function mail_confirm_sms($code, $nickname, $address)
// TRANS: Main body heading for SMS-by-email address confirmation message.
// 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 .= $code;
$body .= "\n\n";
@ -493,18 +525,16 @@ function mail_notify_nudge($from, $to)
// 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: %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 ".
"these days and is inviting you to post some news.\n\n".
"So let's hear from you :)\n\n".
"%3\$s\n\n".
"Don't reply to this email; it won't get to them.\n\n".
"With kind regards,\n".
"%4\$s\n"),
"Don't reply to this email; it won't get to them."),
$from_profile->getBestName(),
$from->nickname,
common_local_url('all', array('nickname' => $to->nickname)),
common_config('site', 'name'));
common_local_url('all', array('nickname' => $to->nickname))) .
mail_footer_block();
common_switch_locale();
$headers = _mail_prepare_headers('nudge', $to->nickname, $from->nickname);
@ -548,21 +578,18 @@ function mail_notify_message($message, $from=null, $to=null)
// 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: %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".
"------------------------------------------------------\n".
"%3\$s\n".
"------------------------------------------------------\n\n".
"You can reply to their message here:\n\n".
"%4\$s\n\n".
"Don't reply to this email; it won't get to them.\n\n".
"With kind regards,\n".
"%5\$s\n"),
"Don't reply to this email; it won't get to them."),
$from_profile->getBestName(),
$from->nickname,
$message->content,
common_local_url('newmessage', array('to' => $from->id)),
common_config('site', 'name'));
common_local_url('newmessage', array('to' => $from->id))) .
mail_footer_block();
$headers = _mail_prepare_headers('message', $to->nickname, $from->nickname);
@ -615,9 +642,7 @@ function mail_notify_fave($other, $user, $notice)
"The text of your notice is:\n\n" .
"%4\$s\n\n" .
"You can see the list of %1\$s's favorites here:\n\n" .
"%5\$s\n\n" .
"Faithfully yours,\n" .
"%6\$s\n"),
"%5\$s"),
$bestname,
common_exact_date($notice->created),
common_local_url('shownotice',
@ -626,7 +651,8 @@ function mail_notify_fave($other, $user, $notice)
common_local_url('showfavorites',
array('nickname' => $user->nickname)),
common_config('site', 'name'),
$user->nickname);
$user->nickname) .
mail_footer_block();
$headers = _mail_prepare_headers('fave', $other->nickname, $user->nickname);
@ -677,12 +703,11 @@ function mail_notify_attn($user, $notice)
$subject = sprintf(_('%1$s (@%2$s) sent a notice to your attention'), $bestname, $sender->nickname);
// 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: %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: %8$s is a URL to the addressed user's e-mail settings, %9$s is the sender's nickname.
$body = sprintf(_("%1\$s (@%9\$s) just sent a notice to your attention (an '@-reply') on %2\$s.\n\n".
// TRANS: %6$s is a URL to reply to the notice, %7$s is a URL to all @-replies for the addressed user,
$body = sprintf(_("%1\$s just sent a notice to your attention (an '@-reply') on %2\$s.\n\n".
"The notice is here:\n\n".
"\t%3\$s\n\n" .
"It reads:\n\n".
@ -691,11 +716,8 @@ function mail_notify_attn($user, $notice)
"You can reply back here:\n\n".
"\t%6\$s\n\n" .
"The list of all @-replies for you here:\n\n" .
"%7\$s\n\n" .
"Faithfully yours,\n" .
"%2\$s\n\n" .
"P.S. You can turn off these email notifications here: %8\$s\n"),
$bestname,//%1
"%7\$s"),
$sender->getFancyName(),//%1
common_config('site', 'name'),//%2
common_local_url('shownotice',
array('notice' => $notice->id)),//%3
@ -704,10 +726,8 @@ function mail_notify_attn($user, $notice)
common_local_url('newnotice',
array('replyto' => $sender->nickname, 'inreplyto' => $notice->id)),//%6
common_local_url('replies',
array('nickname' => $user->nickname)),//%7
common_local_url('emailsettings'), //%8
$sender->nickname); //%9
array('nickname' => $user->nickname))) . //%7
mail_footer_block();
$headers = _mail_prepare_headers('mention', $user->nickname, $sender->nickname);
common_switch_locale();
@ -734,3 +754,97 @@ function _mail_prepare_headers($msg_type, $to, $from)
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('Mail/mimeDecode.php');
# FIXME: we use both Mail_mimeDecode and mailparse
# Need to move everything to mailparse
// FIXME: we use both Mail_mimeDecode and mailparse
// Need to move everything to mailparse
class MailHandler
{

126
lib/makeadminform.php Normal file
View File

@ -0,0 +1,126 @@
<?php
// @todo FIXME: add standard file header.
/**
* Form for making a user an admin for 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/
*/
class MakeAdminForm extends Form
{
/**
* Profile of user to block
*/
var $profile = null;
/**
* Group to block the user from
*/
var $group = null;
/**
* Return-to args
*/
var $args = null;
/**
* Constructor
*
* @param HTMLOutputter $out output channel
* @param Profile $profile profile of user to block
* @param User_group $group group to block user from
* @param array $args return-to args
*/
function __construct($out=null, $profile=null, $group=null, $args=null)
{
parent::__construct($out);
$this->profile = $profile;
$this->group = $group;
$this->args = $args;
}
/**
* ID of the form
*
* @return int ID of the form
*/
function id()
{
// This should be unique for the page.
return 'makeadmin-' . $this->profile->id;
}
/**
* class of the form
*
* @return string class of the form
*/
function formClass()
{
return 'form_make_admin';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
return common_local_url('makeadmin', array('nickname' => $this->group->nickname));
}
/**
* Legend of the Form
*
* @return void
*/
function formLegend()
{
// TRANS: Form legend for form to make a user a group admin.
$this->out->element('legend', null, _('Make user an admin of the group'));
}
/**
* Data elements of the form
*
* @return void
*/
function formData()
{
$this->out->hidden('profileid-' . $this->profile->id,
$this->profile->id,
'profileid');
$this->out->hidden('groupid-' . $this->group->id,
$this->group->id,
'groupid');
if ($this->args) {
foreach ($this->args as $k => $v) {
$this->out->hidden('returnto-' . $k, $v);
}
}
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
$this->out->submit(
'submit',
// TRANS: Button text for the form that will make a user administrator.
_m('BUTTON','Make Admin'),
'submit',
null,
// TRANS: Submit button title.
_m('TOOLTIP','Make this user an admin'));
}
}

190
lib/noticestream.php Normal file
View File

@ -0,0 +1,190 @@
<?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 NoticeStream
{
const CACHE_WINDOW = 200;
public $generator = null;
public $args = null;
public $cachekey = null;
function __construct($generator, $args, $cachekey)
{
$this->generator = $generator;
$this->args = $args;
$this->cachekey = $cachekey;
}
function getNotices($offset=0, $limit=20, $sinceId=0, $maxId=0)
{
$ids = $this->getNoticeIds($offset, $limit, $sinceId, $maxId);
$notices = self::getStreamByIds($ids);
return $notices;
}
function getNoticeIds($offset=0, $limit=20, $sinceId=0, $maxId=0)
{
$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->generate($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->generate(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->generate(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;
}
static 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 generate($offset, $limit, $sinceId, $maxId)
{
$args = array_merge($this->args, array($offset,
$limit,
$sinceId,
$maxId));
return call_user_func_array($this->generator, $args);
}
}

View File

@ -264,8 +264,8 @@ class StatusNetOAuthDataStore extends OAuthDataStore
$profile = Profile::staticGet($remote->id);
$orig_remote = clone($remote);
$orig_profile = clone($profile);
# XXX: compare current postNotice and updateProfile URLs to the ones
# stored in the DB to avoid (possibly...) above attack
// XXX: compare current postNotice and updateProfile URLs to the ones
// stored in the DB to avoid (possibly...) above attack
} else {
$exists = false;
$remote = new Remote_profile();

View File

@ -24,7 +24,7 @@ function ping_broadcast_notice($notice) {
return true;
}
# Array of servers, URL => type
// Array of servers, URL => type
$notify = common_config('ping', 'notify');
try {
$profile = $notice->getProfile();

View File

@ -366,7 +366,7 @@ class Router
$m->connect('group/new', array('action' => 'newgroup'));
foreach (array('edit', 'join', 'leave', 'delete') as $v) {
foreach (array('edit', 'join', 'leave', 'delete', 'cancel', 'approve') as $v) {
$m->connect('group/:nickname/'.$v,
array('action' => $v.'group'),
array('nickname' => Nickname::DISPLAY_FMT));
@ -393,6 +393,10 @@ class Router
array('action' => 'makeadmin'),
array('nickname' => Nickname::DISPLAY_FMT));
$m->connect('group/:nickname/members/pending',
array('action' => 'groupqueue'),
array('nickname' => Nickname::DISPLAY_FMT));
$m->connect('group/:id/id',
array('action' => 'groupbyid'),
array('id' => '[0-9]+'));

View File

@ -34,7 +34,7 @@ define('DEFAULT_RSS_LIMIT', 48);
class Rss10Action extends Action
{
# This will contain the details of each feed item's author and be used to generate SIOC data.
// This will contain the details of each feed item's author and be used to generate SIOC data.
var $creators = array();
var $limit = DEFAULT_RSS_LIMIT;
@ -88,10 +88,10 @@ class Rss10Action extends Action
if (common_config('site', 'private')) {
if (!isset($_SERVER['PHP_AUTH_USER'])) {
# This header makes basic auth go
// This header makes basic auth go
header('WWW-Authenticate: Basic realm="StatusNet RSS"');
# If the user hits cancel -- bam!
// If the user hits cancel -- bam!
$this->show_basic_auth_error();
return;
} else {
@ -99,7 +99,7 @@ class Rss10Action extends Action
$password = $_SERVER['PHP_AUTH_PW'];
if (!common_check_user($nickname, $password)) {
# basic authentication failed
// basic authentication failed
list($proxy, $ip) = common_client_ip();
common_log(LOG_WARNING, "Failed RSS auth attempt, nickname = $nickname, proxy = $proxy, ip = $ip.");

View File

@ -391,7 +391,7 @@ abstract class NoticeListActorsItem extends NoticeListItem
$first = array_slice($items, 0, -1);
$last = array_slice($items, -1, 1);
// TRANS: Separator in list of user names like "You, Bob, Mary".
$sepataror = _(', ');
$separator = _(', ');
// TRANS: For building a list such as "You, bob, mary and 5 others have favored this notice".
// TRANS: %1$s is a list of users, separated by a separator (default: ", "), %2$s is the last user in the list.
return sprintf(_m('FAVELIST', '%1$s and %2$s'), implode($separator, $first), implode($separator, $last));
@ -423,8 +423,7 @@ class ThreadedNoticeListFavesItem extends NoticeListActorsItem
} else {
// TRANS: List message for favoured notices.
// TRANS: %d is the number of users that have favoured a notice.
return sprintf(_m(
'FAVELIST',
return sprintf(_m('FAVELIST',
'One person has favored this notice.',
'%d people have favored this notice.',
$count),
@ -483,8 +482,7 @@ class ThreadedNoticeListRepeatsItem extends NoticeListActorsItem
} else {
// TRANS: List message for repeated notices.
// TRANS: %d is the number of users that have repeated a notice.
return sprintf(_m(
'REPEATLIST',
return sprintf(_m('REPEATLIST',
'One person has repeated this notice.',
'%d people have repeated this notice.',
$count),

View File

@ -2137,7 +2137,7 @@ function common_url_to_nickname($url)
$parts = parse_url($url);
# If any of these parts exist, this won't work
// If any of these parts exist, this won't work
foreach ($bad as $badpart) {
if (array_key_exists($badpart, $parts)) {
@ -2145,15 +2145,15 @@ function common_url_to_nickname($url)
}
}
# We just have host and/or path
// We just have host and/or path
# If it's just a host...
// If it's just a host...
if (array_key_exists('host', $parts) &&
(!array_key_exists('path', $parts) || strcmp($parts['path'], '/') == 0))
{
$hostparts = explode('.', $parts['host']);
# Try to catch common idiom of nickname.service.tld
// Try to catch common idiom of nickname.service.tld
if ((count($hostparts) > 2) &&
(strlen($hostparts[count($hostparts) - 2]) > 3) && # try to skip .co.uk, .com.au
@ -2161,12 +2161,12 @@ function common_url_to_nickname($url)
{
return common_nicknamize($hostparts[0]);
} else {
# Do the whole hostname
// Do the whole hostname
return common_nicknamize($parts['host']);
}
} else {
if (array_key_exists('path', $parts)) {
# Strip starting, ending slashes
// Strip starting, ending slashes
$path = preg_replace('@/$@', '', $parts['path']);
$path = preg_replace('@^/@', '', $path);
$path = basename($path);

View File

@ -79,4 +79,17 @@ class Widget
function show()
{
}
/**
* Delegate output methods to the outputter attribute.
*
* @param string $name Name of the method
* @param array $arguments Arguments called
*
* @return mixed Return value of the method.
*/
function __call($name, $arguments)
{
return call_user_func_array(array($this->out, $name), $arguments);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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