Merge branch '0.8.x' into userdesign

* 0.8.x: (32 commits)
  updates to Status_network
  makeadmin action
  make admins of groups
  show aliases when showing a group
  Link and distribute notices tagged for a group alias
  Code for adding and saving group aliases
  Styles for group block
  add correct li for css magic for block stuff
  typo in profileminilist class
  return count from show
  try to get the right class for profileminilist
  fix perms for classes/statusnet.ini
  fixup perms for classes
  Added Group_alias class
  add a table for group aliases
  Cross-browser notice_attach
  Allow users to be unblocked from a group
  Some UI improvements for blocking and unblocking
  The rest of the things necessary to make group block work
  Make group block work
  ...

Conflicts:
	db/laconica.sql
	lib/common.php
This commit is contained in:
Zach Copley 2009-06-15 11:50:08 -07:00
commit 92f095f589
46 changed files with 2091 additions and 238 deletions

8
README
View File

@ -1196,7 +1196,6 @@ reporturl: URL to post statistics to. Defaults to Laconica developers'
set 'run' to 'never' than to set this value to something
nonsensical.
attachments
-----------
@ -1226,6 +1225,13 @@ user_quota: total size in bytes a user can store on this server. Each user
monthly_quota: total size permitted in the current month. This is the total
size in bytes that a user can upload each month.
group
-----
Options for group functionality.
maxaliases: maximum number of aliases a group can have. Default 3. Set
to 0 or less to prevent aliases in a group.
Troubleshooting
===============

View File

@ -180,7 +180,7 @@ class BlockAction extends Action
if ($action) {
common_redirect(common_local_url($action, $args), 303);
} else {
common_redirect(common_local_url('subscriptions',
common_redirect(common_local_url('subscribers',
array('nickname' => $cur->nickname)),
303);
}

View File

@ -0,0 +1,315 @@
<?php
/**
* Laconica, 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 Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @copyright 2008-2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) {
exit(1);
}
/**
* List of profiles blocked from this group
*
* @category Group
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
class BlockedfromgroupAction extends Action
{
var $page = null;
function isReadOnly($args)
{
return true;
}
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('blockedfromgroup', $args), 301);
return false;
}
if (!$nickname) {
$this->clientError(_('No nickname'), 404);
return false;
}
$this->group = User_group::staticGet('nickname', $nickname);
if (!$this->group) {
$this->clientError(_('No such group'), 404);
return false;
}
return true;
}
function title()
{
if ($this->page == 1) {
return sprintf(_('%s blocked profiles'),
$this->group->nickname);
} else {
return sprintf(_('%s blocked profiles, page %d'),
$this->group->nickname,
$this->page);
}
}
function handle($args)
{
parent::handle($args);
$this->showPage();
}
function showPageNotice()
{
$this->element('p', 'instructions',
_('A list of the users blocked from joining this group.'));
}
function showLocalNav()
{
$nav = new GroupNav($this, $this->group);
$nav->show();
}
function showContent()
{
$offset = ($this->page-1) * PROFILES_PER_PAGE;
$limit = PROFILES_PER_PAGE + 1;
$cnt = 0;
$blocked = $this->group->getBlocked($offset, $limit);
if ($blocked) {
$blocked_list = new GroupBlockList($blocked, $this->group, $this);
$cnt = $blocked_list->show();
}
$blocked->free();
$this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE,
$this->page, 'blockedfromgroup',
array('nickname' => $this->group->nickname));
}
}
class GroupBlockList extends ProfileList
{
var $group = null;
function __construct($profile, $group, $action)
{
parent::__construct($profile, $action);
$this->group = $group;
}
function newListItem($profile)
{
return new GroupBlockListItem($profile, $this->group, $this->action);
}
}
class GroupBlockListItem extends ProfileListItem
{
var $group = null;
function __construct($profile, $group, $action)
{
parent::__construct($profile, $action);
$this->group = $group;
}
function showActions()
{
$this->startActions();
$this->showGroupUnblockForm();
$this->endActions();
}
function showGroupUnblockForm()
{
$user = common_current_user();
if (!empty($user) && $user->id != $this->profile->id && $user->isAdmin($this->group)) {
$this->out->elementStart('li', 'entity_block');
$bf = new GroupUnblockForm($this->out, $this->profile, $this->group,
array('action' => 'blockedfromgroup',
'nickname' => $this->group->nickname));
$bf->show();
$this->out->elementEnd('li');
}
}
}
/**
* Form for unblocking a user from a group
*
* @category Form
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Sarven Capadisli <csarven@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*
* @see UnblockForm
*/
class GroupUnblockForm 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 'unblock-' . $this->profile->id;
}
/**
* class of the form
*
* @return string class of the form
*/
function formClass()
{
return 'form_group_unblock';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
return common_local_url('groupunblock');
}
/**
* Legend of the Form
*
* @return void
*/
function formLegend()
{
$this->out->element('legend', null, _('Unblock user from group'));
}
/**
* Data elements of the form
*
* @return void
*/
function formData()
{
$this->out->hidden('unblockto-' . $this->profile->id,
$this->profile->id,
'unblockto');
$this->out->hidden('unblockgroup-' . $this->group->id,
$this->group->id,
'unblockgroup');
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', _('Unblock'), 'submit', null, _('Unblock this user'));
}
}

View File

@ -171,6 +171,7 @@ class EditgroupAction extends Action
$homepage = $this->trimmed('homepage');
$description = $this->trimmed('description');
$location = $this->trimmed('location');
$aliasstring = $this->trimmed('aliases');
if (!Validate::string($nickname, array('min_length' => 1,
'max_length' => 64,
@ -201,6 +202,39 @@ class EditgroupAction extends Action
return;
}
if (!empty($aliasstring)) {
$aliases = array_map('common_canonical_nickname', array_unique(preg_split('/[\s,]+/', $aliasstring)));
} else {
$aliases = array();
}
if (count($aliases) > common_config('group', 'maxaliases')) {
$this->showForm(sprintf(_('Too many aliases! Maximum %d.'),
common_config('group', 'maxaliases')));
return;
}
foreach ($aliases as $alias) {
if (!Validate::string($alias, array('min_length' => 1,
'max_length' => 64,
'format' => NICKNAME_FMT))) {
$this->showForm(sprintf(_('Invalid alias: "%s"'), $alias));
return;
}
if ($this->nicknameExists($alias)) {
$this->showForm(sprintf(_('Alias "%s" already in use. Try another one.'),
$alias));
return;
}
// XXX assumes alphanum nicknames
if (strcmp($alias, $nickname) == 0) {
$this->showForm(_('Alias can\'t be the same as nickname.'));
return;
}
}
$this->group->query('BEGIN');
$orig = clone($this->group);
$this->group->nickname = $nickname;
@ -217,6 +251,14 @@ class EditgroupAction extends Action
$this->serverError(_('Could not update group.'));
}
$result = $this->group->setAliases($aliases);
if (!$result) {
$this->serverError(_('Could not create aliases.'));
}
$this->group->query('COMMIT');
if ($this->group->nickname != $orig->nickname) {
common_redirect(common_local_url('editgroup',
array('nickname' => $nickname)),
@ -229,9 +271,20 @@ class EditgroupAction extends Action
function nicknameExists($nickname)
{
$group = User_group::staticGet('nickname', $nickname);
return (!is_null($group) &&
$group != false &&
$group->id != $this->group->id);
if (!empty($group) &&
$group->id != $this->group->id) {
return true;
}
$alias = Group_alias::staticGet('alias', $nickname);
if (!empty($alias) &&
$alias->group_id != $this->group->id) {
return true;
}
return false;
}
}

215
actions/groupblock.php Normal file
View File

@ -0,0 +1,215 @@
<?php
/**
* Block a user from a group action class.
*
* PHP version 5
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, Controlez-Vous, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('LACONICA')) {
exit(1);
}
/**
* Block a user from a group
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*/
class GroupblockAction extends Action
{
var $profile = null;
var $group = null;
/**
* Take arguments for running
*
* @param array $args $_REQUEST args
*
* @return boolean success flag
*/
function prepare($args)
{
parent::prepare($args);
if (!common_logged_in()) {
$this->clientError(_('Not logged in.'));
return false;
}
$token = $this->trimmed('token');
if (empty($token) || $token != common_session_token()) {
$this->clientError(_('There was a problem with your session token. Try again, please.'));
return;
}
$id = $this->trimmed('blockto');
if (empty($id)) {
$this->clientError(_('No profile specified.'));
return false;
}
$this->profile = Profile::staticGet('id', $id);
if (empty($this->profile)) {
$this->clientError(_('No profile with that ID.'));
return false;
}
$group_id = $this->trimmed('blockgroup');
if (empty($group_id)) {
$this->clientError(_('No group specified.'));
return false;
}
$this->group = User_group::staticGet('id', $group_id);
if (empty($this->group)) {
$this->clientError(_('No such group.'));
return false;
}
$user = common_current_user();
if (!$user->isAdmin($this->group)) {
$this->clientError(_('Only an admin can block group members.'), 401);
return false;
}
if (Group_block::isBlocked($this->group, $this->profile)) {
$this->clientError(_('User is already blocked from group.'));
return false;
}
// XXX: could have proactive blocks, but we don't have UI for it.
if (!$this->profile->isMember($this->group)) {
$this->clientError(_('User is not a member of group.'));
return false;
}
return true;
}
/**
* Handle request
*
* Shows a page with list of favorite notices
*
* @param array $args $_REQUEST args; handled in prepare()
*
* @return void
*/
function handle($args)
{
parent::handle($args);
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if ($this->arg('no')) {
common_redirect(common_local_url('groupmembers',
array('nickname' => $this->group->nickname)),
303);
} elseif ($this->arg('yes')) {
$this->blockProfile();
} elseif ($this->arg('blockto')) {
$this->showPage();
}
}
}
function showContent() {
$this->areYouSureForm();
}
function title() {
return _('Block user from group');
}
function showNoticeForm() {
// nop
}
/**
* Confirm with user.
*
* Shows a confirmation form.
*
* @return void
*/
function areYouSureForm()
{
$id = $this->profile->id;
$this->element('p', null,
sprintf(_('Are you sure you want to block user "%s" from the group "%s"? '.
'They will be removed from the group, unable to post, and '.
'unable to subscribe to the group in the future.'),
$this->profile->getBestName(),
$this->group->getBestName()));
$this->elementStart('form', array('id' => 'block-' . $id,
'method' => 'post',
'class' => 'block',
'action' => common_local_url('groupblock')));
$this->hidden('token', common_session_token());
$this->hidden('blockto-' . $this->profile->id,
$this->profile->id,
'blockto');
$this->hidden('blockgroup-' . $this->group->id,
$this->group->id,
'blockgroup');
foreach ($this->args as $k => $v) {
if (substr($k, 0, 9) == 'returnto-') {
$this->hidden($k, $v);
}
}
$this->submit('no', _('No'));
$this->submit('yes', _('Yes'));
$this->elementEnd('form');
}
/**
* Actually block a user.
*
* @return void
*/
function blockProfile()
{
$block = Group_block::blockProfile($this->group, $this->profile,
common_current_user());
if (empty($block)) {
$this->serverError(_("Database error blocking user from group."));
return false;
}
// Now, gotta figure where we go back to
foreach ($this->args as $k => $v) {
if ($k == 'returnto-action') {
$action = $v;
} elseif (substr($k, 0, 9) == 'returnto-') {
$args[substr($k, 9)] = $v;
}
}
if ($action) {
common_redirect(common_local_url($action, $args), 303);
} else {
common_redirect(common_local_url('groupmembers',
array('nickname' => $this->group->nickname)),
303);
}
}
}

View File

@ -127,7 +127,7 @@ class GroupmembersAction extends Action
$members = $this->group->getMembers($offset, $limit);
if ($members) {
$member_list = new ProfileList($members, null, $this);
$member_list = new GroupMemberList($members, $this->group, $this);
$cnt = $member_list->show();
}
@ -138,3 +138,326 @@ class GroupmembersAction extends Action
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 showActions()
{
$this->startActions();
$this->showSubscribeButton();
$this->showMakeAdminForm();
$this->showGroupBlockForm();
$this->endActions();
}
function showMakeAdminForm()
{
$user = common_current_user();
if (!empty($user) && $user->id != $this->profile->id && $user->isAdmin($this->group) &&
!$this->profile->isAdmin($this->group)) {
$this->out->elementStart('li', 'entity_make_admin');
$maf = new MakeAdminForm($this->out, $this->profile, $this->group,
array('action' => 'groupmembers',
'nickname' => $this->group->nickname));
$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,
array('action' => 'groupmembers',
'nickname' => $this->group->nickname));
$bf->show();
$this->out->elementEnd('li');
}
}
}
/**
* Form for blocking a user from a group
*
* @category Form
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Sarven Capadisli <csarven@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*
* @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()
{
$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', _('Block'), 'submit', null, _('Block this user'));
}
}
/**
* Form for making a user an admin for a group
*
* @category Form
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @author Sarven Capadisli <csarven@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
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()
{
$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', _('Make Admin'), 'submit', null, _('Make this user an admin'));
}
}

149
actions/groupunblock.php Normal file
View File

@ -0,0 +1,149 @@
<?php
/**
* Block a user from a group action class.
*
* PHP version 5
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, Controlez-Vous, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('LACONICA')) {
exit(1);
}
/**
* Unlock a user from a group
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*/
class GroupunblockAction extends Action
{
var $profile = null;
var $group = null;
/**
* Take arguments for running
*
* @param array $args $_REQUEST args
*
* @return boolean success flag
*/
function prepare($args)
{
parent::prepare($args);
if (!common_logged_in()) {
$this->clientError(_('Not logged in.'));
return false;
}
$token = $this->trimmed('token');
if (empty($token) || $token != common_session_token()) {
$this->clientError(_('There was a problem with your session token. Try again, please.'));
return;
}
$id = $this->trimmed('unblockto');
if (empty($id)) {
$this->clientError(_('No profile specified.'));
return false;
}
$this->profile = Profile::staticGet('id', $id);
if (empty($this->profile)) {
$this->clientError(_('No profile with that ID.'));
return false;
}
$group_id = $this->trimmed('unblockgroup');
if (empty($group_id)) {
$this->clientError(_('No group specified.'));
return false;
}
$this->group = User_group::staticGet('id', $group_id);
if (empty($this->group)) {
$this->clientError(_('No such group.'));
return false;
}
$user = common_current_user();
if (!$user->isAdmin($this->group)) {
$this->clientError(_('Only an admin can unblock group members.'), 401);
return false;
}
if (!Group_block::isBlocked($this->group, $this->profile)) {
$this->clientError(_('User is not blocked from group.'));
return false;
}
return true;
}
/**
* Handle request
*
* @param array $args $_REQUEST args; handled in prepare()
*
* @return void
*/
function handle($args)
{
parent::handle($args);
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->unblockProfile();
}
}
/**
* Unblock a user.
*
* @return void
*/
function unblockProfile()
{
$result = Group_block::unblockProfile($this->group, $this->profile);
if (!$result) {
$this->serverError(_('Error removing the block.'));
return;
}
foreach ($this->args as $k => $v) {
if ($k == 'returnto-action') {
$action = $v;
} else if (substr($k, 0, 9) == 'returnto-') {
$args[substr($k, 9)] = $v;
}
}
if ($action) {
common_redirect(common_local_url($action, $args), 303);
} else {
common_redirect(common_local_url('blockedfromgroup',
array('nickname' => $this->group->nickname)),
303);
}
}
}

View File

@ -96,6 +96,11 @@ class JoingroupAction extends Action
return false;
}
if (Group_block::isBlocked($this->group, $cur->getProfile())) {
$this->clientError(_('You have been blocked from that group by the admin.'), 403);
return false;
}
return true;
}

166
actions/makeadmin.php Normal file
View File

@ -0,0 +1,166 @@
<?php
/**
* Make another user an admin of a group
*
* PHP version 5
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, Controlez-Vous, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('LACONICA')) {
exit(1);
}
/**
* Make another user an admin of a group
*
* @category Action
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://laconi.ca/
*/
class MakeadminAction extends Action
{
var $profile = null;
var $group = null;
/**
* Take arguments for running
*
* @param array $args $_REQUEST args
*
* @return boolean success flag
*/
function prepare($args)
{
parent::prepare($args);
if (!common_logged_in()) {
$this->clientError(_('Not logged in.'));
return false;
}
$token = $this->trimmed('token');
if (empty($token) || $token != common_session_token()) {
$this->clientError(_('There was a problem with your session token. Try again, please.'));
return;
}
$id = $this->trimmed('profileid');
if (empty($id)) {
$this->clientError(_('No profile specified.'));
return false;
}
$this->profile = Profile::staticGet('id', $id);
if (empty($this->profile)) {
$this->clientError(_('No profile with that ID.'));
return false;
}
$group_id = $this->trimmed('groupid');
if (empty($group_id)) {
$this->clientError(_('No group specified.'));
return false;
}
$this->group = User_group::staticGet('id', $group_id);
if (empty($this->group)) {
$this->clientError(_('No such group.'));
return false;
}
$user = common_current_user();
if (!$user->isAdmin($this->group)) {
$this->clientError(_('Only an admin can make another user an admin.'), 401);
return false;
}
if ($this->profile->isAdmin($this->group)) {
$this->clientError(sprintf(_('%s is already an admin for group "%s".'),
$this->profile->getBestName(),
$this->group->getBestName()),
401);
return false;
}
return true;
}
/**
* Handle request
*
* @param array $args $_REQUEST args; handled in prepare()
*
* @return void
*/
function handle($args)
{
parent::handle($args);
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->makeAdmin();
}
}
/**
* Make user an admin
*
* @return void
*/
function makeAdmin()
{
$member = Group_member::pkeyGet(array('group_id' => $this->group->id,
'profile_id' => $this->profile->id));
if (empty($member)) {
$this->serverError(_('Can\'t get membership record for %s in group %s'),
$this->profile->getBestName(),
$this->group->getBestName());
}
$orig = clone($member);
$member->is_admin = 1;
$result = $member->update($orig);
if (!$result) {
common_log_db_error($member, 'UPDATE', __FILE__);
$this->serverError(_('Can\'t make %s an admin for group %s'),
$this->profile->getBestName(),
$this->group->getBestName());
}
foreach ($this->args as $k => $v) {
if ($k == 'returnto-action') {
$action = $v;
} else if (substr($k, 0, 9) == 'returnto-') {
$args[substr($k, 9)] = $v;
}
}
if ($action) {
common_redirect(common_local_url($action, $args), 303);
} else {
common_redirect(common_local_url('groupmembers',
array('nickname' => $this->group->nickname)),
303);
}
}
}

View File

@ -123,6 +123,7 @@ class NewgroupAction extends Action
$homepage = $this->trimmed('homepage');
$description = $this->trimmed('description');
$location = $this->trimmed('location');
$aliasstring = $this->trimmed('aliases');
if (!Validate::string($nickname, array('min_length' => 1,
'max_length' => 64,
@ -153,6 +154,37 @@ class NewgroupAction extends Action
return;
}
if (!empty($aliasstring)) {
$aliases = array_map('common_canonical_nickname', array_unique(preg_split('/[\s,]+/', $aliasstring)));
} else {
$aliases = array();
}
if (count($aliases) > common_config('group', 'maxaliases')) {
$this->showForm(sprintf(_('Too many aliases! Maximum %d.'),
common_config('group', 'maxaliases')));
return;
}
foreach ($aliases as $alias) {
if (!Validate::string($alias, array('min_length' => 1,
'max_length' => 64,
'format' => NICKNAME_FMT))) {
$this->showForm(sprintf(_('Invalid alias: "%s"'), $alias));
return;
}
if ($this->nicknameExists($alias)) {
$this->showForm(sprintf(_('Alias "%s" already in use. Try another one.'),
$alias));
return;
}
// XXX assumes alphanum nicknames
if (strcmp($alias, $nickname) == 0) {
$this->showForm(_('Alias can\'t be the same as nickname.'));
return;
}
}
$cur = common_current_user();
// Checked in prepare() above
@ -177,6 +209,12 @@ class NewgroupAction extends Action
$this->serverError(_('Could not create group.'));
}
$result = $group->setAliases($aliases);
if (!$result) {
$this->serverError(_('Could not create aliases.'));
}
$member = new Group_member();
$member->group_id = $group->id;
@ -199,7 +237,18 @@ class NewgroupAction extends Action
function nicknameExists($nickname)
{
$group = User_group::staticGet('nickname', $nickname);
return (!is_null($group) && $group != false);
if (!empty($group)) {
return true;
}
$alias = Group_alias::staticGet('alias', $nickname);
if (!empty($alias)) {
return true;
}
return false;
}
}

View File

@ -272,6 +272,17 @@ class ShowgroupAction extends Action
$this->elementEnd('dl');
}
if (common_config('group', 'maxaliases') > 0) {
$aliases = $this->group->getAliases();
if (!empty($aliases)) {
$this->elementStart('dl', 'entity_aliases');
$this->element('dt', null, _('Aliases'));
$this->element('dd', 'aliases', implode(' ', $aliases));
$this->elementEnd('dl');
}
}
$this->elementEnd('div');
$this->elementStart('div', 'entity_actions');
@ -283,7 +294,7 @@ class ShowgroupAction extends Action
if ($cur->isMember($this->group)) {
$lf = new LeaveForm($this, $this->group);
$lf->show();
} else {
} else if (!Group_block::isBlocked($this->group, $cur->getProfile())) {
$jf = new JoinForm($this, $this->group);
$jf->show();
}
@ -344,7 +355,7 @@ class ShowgroupAction extends Action
$this->element('h2', null, _('Members'));
$pml = new ProfileMiniList($member, null, $this);
$pml = new ProfileMiniList($member, $this);
$cnt = $pml->show();
if ($cnt == 0) {
$this->element('p', null, _('(None)'));

View File

@ -320,10 +320,14 @@ class ShowstreamAction extends ProfileAction
$blocked = $cur->hasBlocked($this->profile);
$this->elementStart('li', 'entity_block');
if ($blocked) {
$ubf = new UnblockForm($this, $this->profile);
$ubf = new UnblockForm($this, $this->profile,
array('action' => 'showstream',
'nickname' => $this->profile->nickname));
$ubf->show();
} else {
$bf = new BlockForm($this, $this->profile);
$bf = new BlockForm($this, $this->profile,
array('action' => 'showstream',
'nickname' => $this->profile->nickname));
$bf->show();
}
$this->elementEnd('li');

View File

@ -130,18 +130,34 @@ class SubscribersAction extends GalleryAction
}
}
class SubscribersList extends ProfileList
class SubscribersList extends SubscriptionList
{
function showBlockForm()
function newListItem($profile)
{
$bf = new BlockForm($this->out, $this->profile,
array('action' => 'subscribers',
'nickname' => $this->owner->nickname));
$bf->show();
}
function isReadOnly($args)
{
return true;
return new SubscribersListItem($profile, $this->owner, $this->action);
}
}
class SubscribersListItem extends SubscriptionListItem
{
function showActions()
{
$this->startActions();
$this->showSubscribeButton();
// Relevant code!
$this->showBlockForm();
$this->endActions();
}
function showBlockForm()
{
$user = common_current_user();
if (!empty($user) && $this->owner->id == $user->id) {
$bf = new BlockForm($this->out, $this->profile,
array('action' => 'subscribers',
'nickname' => $this->owner->nickname));
$bf->show();
}
}
}

View File

@ -137,22 +137,46 @@ class SubscriptionsAction extends GalleryAction
}
}
class SubscriptionsList extends ProfileList
// XXX SubscriptionsList and SubscriptionList are dangerously close
class SubscriptionsList extends SubscriptionList
{
function showOwnerControls($profile)
function newListItem($profile)
{
return new SubscriptionsListItem($profile, $this->owner, $this->action);
}
}
class SubscriptionsListItem extends SubscriptionListItem
{
function showProfile()
{
$this->startProfile();
$this->showAvatar();
$this->showFullName();
$this->showLocation();
$this->showHomepage();
$this->showBio();
$this->showTags();
// Relevant portion!
$this->showOwnerControls();
$this->endProfile();
}
function showOwnerControls()
{
$sub = Subscription::pkeyGet(array('subscriber' => $this->owner->id,
'subscribed' => $profile->id));
'subscribed' => $this->profile->id));
if (!$sub) {
return;
}
$this->out->elementStart('form', array('id' => 'subedit-' . $profile->id,
$this->out->elementStart('form', array('id' => 'subedit-' . $this->profile->id,
'method' => 'post',
'class' => 'form_subscription_edit',
'action' => common_local_url('subedit')));
$this->out->hidden('token', common_session_token());
$this->out->hidden('profile', $profile->id);
$this->out->hidden('profile', $this->profile->id);
$this->out->checkbox('jabber', _('Jabber'), $sub->jabber);
$this->out->checkbox('sms', _('SMS'), $sub->sms);
$this->out->submit('save', _('Save'));

View File

@ -118,7 +118,7 @@ class UnblockAction extends Action
if ($action) {
common_redirect(common_local_url($action, $args), 303);
} else {
common_redirect(common_local_url('subscriptions',
common_redirect(common_local_url('subscribers',
array('nickname' => $cur->nickname)),
303);
}

41
classes/Group_alias.php Normal file
View File

@ -0,0 +1,41 @@
<?php
/**
* Table Definition for group_alias
*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2009, Control Yourself, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('LACONICA')) { exit(1); }
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Group_alias extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'group_alias'; // table name
public $alias; // varchar(64) primary_key not_null
public $group_id; // int(4) not_null
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
/* Static get */
function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('Group_alias',$k,$v); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
}

115
classes/Group_block.php Normal file
View File

@ -0,0 +1,115 @@
<?php
/**
* Table Definition for group_block
*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, Controlez-Vous, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('LACONICA')) { exit(1); }
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Group_block extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'group_block'; // table name
public $group_id; // int(4) primary_key not_null
public $blocked; // int(4) primary_key not_null
public $blocker; // int(4) not_null
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
/* Static get */
function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('Group_block',$k,$v); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
function &pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Group_block', $kv);
}
static function isBlocked($group, $profile)
{
$block = Group_block::pkeyGet(array('group_id' => $group->id,
'blocked' => $profile->id));
return !empty($block);
}
static function blockProfile($group, $profile, $blocker)
{
// Insert the block
$block = new Group_block();
$block->query('BEGIN');
$block->group_id = $group->id;
$block->blocked = $profile->id;
$block->blocker = $blocker->id;
$result = $block->insert();
if (!$result) {
common_log_db_error($block, 'INSERT', __FILE__);
return null;
}
// Delete membership if any
$member = new Group_member();
$member->group_id = $group->id;
$member->profile_id = $profile->id;
if ($member->find(true)) {
$result = $member->delete();
if (!$result) {
common_log_db_error($member, 'DELETE', __FILE__);
return null;
}
}
// Commit, since both have been done
$block->query('COMMIT');
return $block;
}
static function unblockProfile($group, $profile)
{
$block = Group_block::pkeyGet(array('group_id' => $group->id,
'blocked' => $profile->id));
if (empty($block)) {
return null;
}
$result = $block->delete();
if (!$result) {
common_log_db_error($block, 'DELETE', __FILE__);
return null;
}
return true;
}
}

View File

@ -125,7 +125,12 @@ class Notice extends Memcached_DataObject
$profile = Profile::staticGet($profile_id);
$final = common_shorten_links($content);
$final = common_shorten_links($content);
if (mb_strlen($final) > 140) {
common_log(LOG_INFO, 'Rejecting notice that is too long.');
return _('Problem saving notice. Too long.');
}
if (!$profile) {
common_log(LOG_ERR, 'Problem saving notice. Unknown user.');
@ -747,16 +752,16 @@ class Notice extends Memcached_DataObject
foreach (array_unique($match[1]) as $nickname) {
/* XXX: remote groups. */
$group = User_group::staticGet('nickname', $nickname);
$group = User_group::getForNickname($nickname);
if (!$group) {
if (empty($group)) {
continue;
}
// we automatically add a tag for every group name, too
$tag = Notice_tag::pkeyGet(array('tag' => common_canonical_tag($nickname),
'notice_id' => $this->id));
'notice_id' => $this->id));
if (is_null($tag)) {
$this->saveTag($nickname);

View File

@ -12,11 +12,13 @@ class Status_network extends DB_DataObject
public $nickname; // varchar(64) primary_key not_null
public $hostname; // varchar(255) unique_key
public $pathname; // varchar(255) unique_key
public $sitename; // varchar(255)
public $dbhost; // varchar(255)
public $dbuser; // varchar(255)
public $dbpass; // varchar(255)
public $dbname; // varchar(255)
public $sitename; // varchar(255)
public $theme; // varchar(255)
public $logo; // varchar(255)
public $created; // datetime() not_null
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
@ -37,13 +39,19 @@ class Status_network extends DB_DataObject
return true;
}
static function setupSite($servername, $pathname)
static function setupSite($servername, $pathname, $wildcard)
{
global $config;
$parts = explode('.', $servername);
// XXX I18N, probably not crucial for hostnames
// XXX This probably needs a tune up
$sn = Status_network::staticGet('nickname', $parts[0]);
if (0 == strncasecmp(strrev($wildcard), strrev($servername), strlen($wildcard))) {
$parts = explode('.', $servername);
$sn = Status_network::staticGet('nickname', strtolower($parts[0]));
} else {
$sn = Status_network::staticGet('hostname', strtolower($servername));
}
if (!empty($sn)) {
$dbhost = (empty($sn->dbhost)) ? 'localhost' : $sn->dbhost;
@ -52,7 +60,16 @@ class Status_network extends DB_DataObject
$dbname = (empty($sn->dbname)) ? $sn->nickname : $sn->dbname;
$config['db']['database'] = "mysqli://$dbuser:$dbpass@$dbhost/$dbname";
$config['site']['name'] = $sn->sitename;
if (!empty($sn->theme)) {
$config['site']['theme'] = $sn->theme;
}
if (!empty($sn->logo)) {
$config['site']['logo'] = $sn->logo;
}
return true;
} else {
return false;

View File

@ -125,6 +125,29 @@ class User_group extends Memcached_DataObject
return $members;
}
function getBlocked($offset=0, $limit=null)
{
$qry =
'SELECT profile.* ' .
'FROM profile JOIN group_block '.
'ON profile.id = group_block.blocked ' .
'WHERE group_block.group_id = %d ' .
'ORDER BY group_block.modified DESC ';
if ($limit != null) {
if (common_config('db','type') == 'pgsql') {
$qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
} else {
$qry .= ' LIMIT ' . $offset . ', ' . $limit;
}
}
$blocked = new Profile();
$blocked->query(sprintf($qry, $this->id));
return $blocked;
}
function setOriginal($filename)
{
$imagefile = new ImageFile($this->id, Avatar::path($filename));
@ -137,4 +160,83 @@ class User_group extends Memcached_DataObject
common_debug(common_log_objstring($this));
return $this->update($orig);
}
function getBestName()
{
return ($this->fullname) ? $this->fullname : $this->nickname;
}
function getAliases()
{
$aliases = array();
// XXX: cache this
$alias = new Group_alias();
$alias->group_id = $this->id;
if ($alias->find()) {
while ($alias->fetch()) {
$aliases[] = $alias->alias;
}
}
$alias->free();
return $aliases;
}
function setAliases($newaliases) {
$newaliases = array_unique($newaliases);
$oldaliases = $this->getAliases();
# 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
$to_insert = array_diff($newaliases, $oldaliases);
$alias = new Group_alias();
$alias->group_id = $this->id;
foreach ($to_delete as $delalias) {
$alias->alias = $delalias;
$result = $alias->delete();
if (!$result) {
common_log_db_error($alias, 'DELETE', __FILE__);
return false;
}
}
foreach ($to_insert as $insalias) {
$alias->alias = $insalias;
$result = $alias->insert();
if (!$result) {
common_log_db_error($alias, 'INSERT', __FILE__);
return false;
}
}
return true;
}
static function getForNickname($nickname)
{
$nickname = common_canonical_nickname($nickname);
$group = User_group::staticGet('nickname', $nickname);
if (!empty($group)) {
return $group;
}
$alias = Group_alias::staticGet('alias', $nickname);
if (!empty($alias)) {
return User_group::staticGet('id', $alias->group_id);
}
return null;
}
}

View File

@ -170,6 +170,24 @@ id = K
service = K
uri = U
[group_alias]
alias = 130
group_id = 129
modified = 384
[group_alias__keys]
alias = K
[group_block]
group_id = 129
blocked = 129
blocker = 129
modified = 384
[group_block__keys]
group_id = K
blocked = K
[group_inbox]
group_id = 129
notice_id = 129

5
classes/statusnet.ini Executable file → Normal file
View File

@ -1,13 +1,14 @@
[status_network]
nickname = 130
hostname = 2
pathname = 2
sitename = 2
dbhost = 2
dbuser = 2
dbpass = 2
dbname = 2
sitename = 2
theme = 2
logo = 2
created = 142
modified = 384

View File

@ -431,49 +431,50 @@ create table group_inbox (
create table file (
id integer primary key auto_increment,
url varchar(255), mimetype varchar(50),
size integer,
title varchar(255),
date integer(11),
protected integer(1),
url varchar(255) comment 'destination URL after following redirections',
mimetype varchar(50) comment 'mime type of resource',
size integer comment 'size of resource when available',
title varchar(255) comment 'title of resource when available',
date integer(11) comment 'date of resource according to http query',
protected integer(1) comment 'true when URL is private (needs login)',
unique(url)
) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
create table file_oembed (
id integer primary key auto_increment,
file_id integer,
version varchar(20),
type varchar(20),
provider varchar(50),
provider_url varchar(255),
width integer,
height integer,
html text,
title varchar(255),
author_name varchar(50),
author_url varchar(255),
url varchar(255),
file_id integer comment 'oEmbed for that URL/file' references file (id),
version varchar(20) comment 'oEmbed spec. version',
type varchar(20) comment 'oEmbed type: photo, video, link, rich',
provider varchar(50) comment 'name of this oEmbed provider',
provider_url varchar(255) comment 'URL of this oEmbed provider',
width integer comment 'width of oEmbed resource when available',
height integer comment 'height of oEmbed resource when available',
html text comment 'html representation of this oEmbed resource when applicable',
title varchar(255) comment 'title of oEmbed resource when available',
author_name varchar(50) comment 'author name for this oEmbed resource',
author_url varchar(255) comment 'author URL for this oEmbed resource',
url varchar(255) comment 'URL for this oEmbed resource when applicable (photo, link)',
unique(file_id)
) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
create table file_redirection (
id integer primary key auto_increment,
url varchar(255),
file_id integer,
redirections integer,
httpcode integer,
url varchar(255) comment 'short URL (or any other kind of redirect) for file (id)',
file_id integer comment 'short URL for what URL/file' references file (id),
redirections integer comment 'redirect count',
httpcode integer comment 'HTTP status code (20x, 30x, etc.)',
unique(url)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table file_thumbnail (
id integer primary key auto_increment,
file_id integer,
url varchar(255),
width integer,
height integer,
file_id integer comment 'thumbnail for what URL/file' references file (id),
url varchar(255) comment 'URL of thumbnail',
width integer comment 'width of thumbnail',
height integer comment 'height of thumbnail',
unique(file_id),
unique(url)
@ -481,8 +482,8 @@ create table file_thumbnail (
create table file_to_post (
id integer primary key auto_increment,
file_id integer,
post_id integer,
file_id integer comment 'id of URL/file' references file (id),
post_id integer comment 'id of the notice it belongs to' references notice (id),
unique(file_id, post_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
@ -496,3 +497,23 @@ create table design (
linkcolor integer comment 'link color',
backgroundimage varchar(255) comment 'background image, if any'
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table group_block (
group_id integer not null comment 'group profile is blocked from' references user_group (id),
blocked integer not null comment 'profile that is blocked' references profile (id),
blocker integer not null comment 'user making the block' references user (id),
modified timestamp comment 'date of blocking',
constraint primary key (group_id, blocked)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table group_alias (
alias varchar(64) primary key comment 'additional nickname for the group',
group_id integer not null comment 'group profile is blocked from' references user_group (id),
modified timestamp comment 'date alias was created',
index group_alias_group_id_idx (group_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;

View File

@ -5,12 +5,16 @@ create table status_network (
nickname varchar(64) primary key comment 'nickname',
hostname varchar(255) unique key comment 'alternate hostname if any',
pathname varchar(255) unique key comment 'alternate pathname if any',
sitename varchar(255) comment 'display name',
dbhost varchar(255) comment 'database host',
dbuser varchar(255) comment 'database username',
dbpass varchar(255) comment 'database password',
dbname varchar(255) comment 'database name',
sitename varchar(255) comment 'display name',
theme varchar(255) comment 'theme name',
logo varchar(255) comment 'site logo',
created datetime not null comment 'date this record was created',
modified timestamp comment 'date this record was modified'

View File

@ -48,13 +48,18 @@ function handleError($error)
$logmsg .= " : ". $error->getDebugInfo();
}
common_log(LOG_ERR, $logmsg);
$msg = sprintf(_('The database for %s isn\'t responding correctly, '.
'so the site won\'t work properly. '.
'The site admins probably know about the problem, '.
'but you can contact them at %s to make sure. '.
'Otherwise, wait a few minutes and try again.'),
common_config('site', 'name'),
common_config('site', 'email'));
if ($error instanceof DB_DataObject_Error) {
$msg = sprintf(_('The database for %s isn\'t responding correctly, '.
'so the site won\'t work properly. '.
'The site admins probably know about the problem, '.
'but you can contact them at %s to make sure. '.
'Otherwise, wait a few minutes and try again.'),
common_config('site', 'name'),
common_config('site', 'email'));
} else {
$msg = _('An important error occured, probably related to email setup. '.
'Check logfiles for more info..');
}
$dac = new DBErrorAction($msg, 500);
$dac->showPage();
@ -70,7 +75,7 @@ function main()
global $user, $action, $config;
Snapshot::check();
if (!_have_config()) {
$msg = sprintf(_("No configuration file found. Try running ".
"the installation program first."));

View File

@ -206,6 +206,8 @@ $config =
'user_quota' => 50000000,
'monthly_quota' => 15000000,
),
'group' =>
array('maxaliases' => 3),
);
$config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');

View File

@ -111,7 +111,6 @@ class GroupEditForm extends Form
}
}
/**
* Name of the form
*
@ -157,6 +156,16 @@ class GroupEditForm extends Form
($this->out->arg('location')) ? $this->out->arg('location') : $this->group->location,
_('Location for the group, if any, like "City, State (or Region), Country"'));
$this->out->elementEnd('li');
if (common_config('group', 'maxaliases') > 0) {
$aliases = (empty($this->group)) ? array() : $this->group->getAliases();
$this->out->elementStart('li');
$this->out->input('aliases', _('Aliases'),
($this->out->arg('aliases')) ? $this->out->arg('aliases') :
(!empty($aliases)) ? implode(' ', $aliases) : '',
sprintf(_('Extra nicknames for the group, comma- or space- separated, max %d'),
common_config('group', 'maxaliases')));;
$this->out->elementEnd('li');
}
$this->out->elementEnd('ul');
}

View File

@ -166,7 +166,7 @@ class GroupList extends Widget
if ($user->isMember($this->group)) {
$lf = new LeaveForm($this->out, $this->group);
$lf->show();
} else {
} else if (!Group_block::isBlocked($this->group, $user->getProfile())) {
$jf = new JoinForm($this->out, $this->group);
$jf->show();
}

View File

@ -95,6 +95,12 @@ class GroupNav extends Widget
$cur = common_current_user();
if ($cur && $cur->isAdmin($this->group)) {
$this->out->menuItem(common_local_url('blockedfromgroup', array('nickname' =>
$nickname)),
_('Blocked'),
sprintf(_('%s blocked users'), $nickname),
$action_name == 'blockedfromgroup',
'nav_group_blocked');
$this->out->menuItem(common_local_url('editgroup', array('nickname' =>
$nickname)),
_('Admin'),

View File

@ -148,12 +148,12 @@ class NoticeForm extends Form
$this->out->element('dd', array('id' => 'notice_text-count'),
'140');
$this->out->elementEnd('dl');
$this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota'));
$this->out->element('label', array('for' => 'notice_data-attach'), _('Attach'));
$this->out->element('label', array('for' => 'notice_data-attach'),_('Attach'));
$this->out->element('input', array('id' => 'notice_data-attach',
'type' => 'file',
'name' => 'attach',
'title' => _('Attach a file')));
$this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota'));
if ($this->action) {
$this->out->hidden('notice_return-to', $this->action, 'returnto');
}

View File

@ -205,24 +205,10 @@ class NoticeListItem extends Widget
return 'shownotice' !== $this->out->args['action'];
}
/*
function attachmentCount($discriminant = true) {
$file_oembed = new File_oembed;
$query = "select count(*) as c from file_oembed join file_to_post on file_oembed.file_id = file_to_post.file_id where post_id=" . $this->notice->id;
$file_oembed->query($query);
$file_oembed->fetch();
return intval($file_oembed->c);
}
*/
function showWithAttachment() {
}
function showNoticeInfo()
{
$this->out->elementStart('div', 'entry-content');
$this->showNoticeLink();
// $this->showWithAttachment();
$this->showNoticeSource();
$this->showContext();
$this->out->elementEnd('div');

View File

@ -56,20 +56,25 @@ class PeopleSearchResults extends ProfileList
function __construct($profile, $terms, $action)
{
parent::__construct($profile, $terms, $action);
parent::__construct($profile, $action);
$this->terms = array_map('preg_quote',
array_map('htmlspecialchars', $terms));
$this->pattern = '/('.implode('|',$terms).')/i';
}
function newProfileItem($profile)
{
return new PeopleSearchResultItem($profile, $this->action);
}
}
class PeopleSearchResultItem extends ProfileListItem
{
function highlight($text)
{
return preg_replace($this->pattern, '<strong>\\1</strong>', htmlspecialchars($text));
}
function isReadOnly($args)
{
return true;
}
}

View File

@ -109,7 +109,7 @@ class ProfileAction extends OwnerDesignAction
$this->element('h2', null, _('Subscriptions'));
if ($profile) {
$pml = new ProfileMiniList($profile, $this->user, $this);
$pml = new ProfileMiniList($profile, $this);
$cnt = $pml->show();
if ($cnt == 0) {
$this->element('p', null, _('(None)'));
@ -138,7 +138,7 @@ class ProfileAction extends OwnerDesignAction
$this->element('h2', null, _('Subscribers'));
if ($profile) {
$pml = new ProfileMiniList($profile, $this->user, $this);
$pml = new ProfileMiniList($profile, $this);
$cnt = $pml->show();
if ($cnt == 0) {
$this->element('p', null, _('(None)'));

View File

@ -49,25 +49,37 @@ class ProfileList extends Widget
{
/** Current profile, profile query. */
var $profile = null;
/** Owner of this list */
var $owner = null;
/** Action object using us. */
var $action = null;
function __construct($profile, $owner=null, $action=null)
function __construct($profile, $action=null)
{
parent::__construct($action);
$this->profile = $profile;
$this->owner = $owner;
$this->action = $action;
}
function show()
{
$this->startList();
$cnt = $this->showProfiles();
$this->endList();
return $cnt;
}
function startList()
{
$this->out->elementStart('ul', 'profiles');
}
function endList()
{
$this->out->elementEnd('ul');
}
function showProfiles()
{
$cnt = 0;
while ($this->profile->fetch()) {
@ -75,24 +87,66 @@ class ProfileList extends Widget
if($cnt > PROFILES_PER_PAGE) {
break;
}
$this->showProfile();
$pli = $this->newListItem($this->profile);
$pli->show();
}
$this->out->elementEnd('ul');
return $cnt;
}
function showProfile()
function newListItem($profile)
{
return new ProfileListItem($this->profile, $this->action);
}
}
class ProfileListItem extends Widget
{
/** Current profile. */
var $profile = null;
/** Action object using us. */
var $action = null;
function __construct($profile, $action)
{
parent::__construct($action);
$this->profile = $profile;
$this->action = $action;
}
function show()
{
$this->startItem();
$this->showProfile();
$this->showActions();
$this->endItem();
}
function startItem()
{
$this->out->elementStart('li', array('class' => 'profile',
'id' => 'profile-' . $this->profile->id));
}
$user = common_current_user();
$is_own = !is_null($user) && isset($this->owner) && ($user->id === $this->owner->id);
function showProfile()
{
$this->startProfile();
$this->showAvatar();
$this->showFullName();
$this->showLocation();
$this->showHomepage();
$this->showBio();
$this->endProfile();
}
function startProfile()
{
$this->out->elementStart('div', 'entity_profile vcard');
}
function showAvatar()
{
$avatar = $this->profile->getAvatar(AVATAR_STREAM_SIZE);
$this->out->elementStart('a', array('href' => $this->profile->profileurl,
'class' => 'url'));
@ -108,7 +162,10 @@ class ProfileList extends Widget
$this->out->raw($this->highlight($this->profile->nickname));
$this->out->elementEnd('span');
$this->out->elementEnd('a');
}
function showFullName()
{
if (!empty($this->profile->fullname)) {
$this->out->elementStart('dl', 'entity_fn');
$this->out->element('dt', null, 'Full name');
@ -119,6 +176,10 @@ class ProfileList extends Widget
$this->out->elementEnd('dd');
$this->out->elementEnd('dl');
}
}
function showLocation()
{
if (!empty($this->profile->location)) {
$this->out->elementStart('dl', 'entity_location');
$this->out->element('dt', null, _('Location'));
@ -127,6 +188,10 @@ class ProfileList extends Widget
$this->out->elementEnd('dd');
$this->out->elementEnd('dl');
}
}
function showHomepage()
{
if (!empty($this->profile->homepage)) {
$this->out->elementStart('dl', 'entity_url');
$this->out->element('dt', null, _('URL'));
@ -138,6 +203,10 @@ class ProfileList extends Widget
$this->out->elementEnd('dd');
$this->out->elementEnd('dl');
}
}
function showBio()
{
if (!empty($this->profile->bio)) {
$this->out->elementStart('dl', 'entity_note');
$this->out->element('dt', null, _('Note'));
@ -146,57 +215,33 @@ class ProfileList extends Widget
$this->out->elementEnd('dd');
$this->out->elementEnd('dl');
}
}
# If we're on a list with an owner (subscriptions or subscribers)...
if ($this->owner) {
# Get tags
$tags = Profile_tag::getTags($this->owner->id, $this->profile->id);
$this->out->elementStart('dl', 'entity_tags');
$this->out->elementStart('dt');
if ($is_own) {
$this->out->element('a', array('href' => common_local_url('tagother',
array('id' => $this->profile->id))),
_('Tags'));
} else {
$this->out->text(_('Tags'));
}
$this->out->elementEnd('dt');
$this->out->elementStart('dd');
if ($tags) {
$this->out->elementStart('ul', 'tags xoxo');
foreach ($tags as $tag) {
$this->out->elementStart('li');
$this->out->element('span', 'mark_hash', '#');
$this->out->element('a', array('rel' => 'tag',
'href' => common_local_url($this->action->trimmed('action'),
array('nickname' => $this->owner->nickname,
'tag' => $tag))),
$tag);
$this->out->elementEnd('li');
}
$this->out->elementEnd('ul');
} else {
$this->out->text(_('(none)'));
}
$this->out->elementEnd('dd');
$this->out->elementEnd('dl');
}
if ($is_own) {
$this->showOwnerControls($this->profile);
}
function endProfile()
{
$this->out->elementEnd('div');
}
function showActions()
{
$this->startActions();
$this->showSubscribeButton();
$this->endActions();
}
function startActions()
{
$this->out->elementStart('div', 'entity_actions');
$this->out->elementStart('ul');
}
function showSubscribeButton()
{
// Is this a logged-in user, looking at someone else's
// profile?
$user = common_current_user();
if (!empty($user) && $this->profile->id != $user->id) {
$this->out->elementStart('li', 'entity_subscribe');
if ($user->isSubscribed($this->profile)) {
@ -207,33 +252,22 @@ class ProfileList extends Widget
$sf->show();
}
$this->out->elementEnd('li');
$this->out->elementStart('li', 'entity_block');
if ($user->id == $this->owner->id) {
$this->showBlockForm();
}
$this->out->elementEnd('li');
}
$this->out->elementEnd('ul');
$this->out->elementEnd('div');
$this->out->elementEnd('li');
}
/* Override this in subclasses. */
function showOwnerControls($profile)
function endActions()
{
return;
$this->out->elementEnd('ul');
$this->out->elementEnd('div');
}
function endItem()
{
$this->out->elementEnd('li');
}
function highlight($text)
{
return htmlspecialchars($text);
}
function showBlockForm()
{
}
}

View File

@ -47,26 +47,20 @@ define('PROFILES_PER_MINILIST', 27);
class ProfileMiniList extends ProfileList
{
function show()
function startList()
{
$this->out->elementStart('ul', 'entities users xoxo');
$cnt = 0;
while ($this->profile->fetch()) {
$cnt++;
if($cnt > PROFILES_PER_MINILIST) {
break;
}
$this->showProfile();
}
$this->out->elementEnd('ul');
return $cnt;
}
function showProfile()
function newListItem($profile)
{
return new ProfileMiniListItem($profile, $this->action);
}
}
class ProfileMiniListItem extends ProfileListItem
{
function show()
{
$this->out->elementStart('li', 'vcard');
$this->out->elementStart('a', array('title' => $this->profile->getBestName(),

View File

@ -101,7 +101,8 @@ class Router
$main = array('login', 'logout', 'register', 'subscribe',
'unsubscribe', 'confirmaddress', 'recoverpassword',
'invite', 'favor', 'disfavor', 'sup',
'block', 'subedit');
'block', 'unblock', 'subedit',
'groupblock', 'groupunblock');
foreach ($main as $a) {
$m->connect('main/'.$a, array('action' => $a));
@ -164,10 +165,10 @@ class Router
array('action' => 'newnotice'),
array('replyto' => '[A-Za-z0-9_-]+'));
$m->connect('notice/:notice/file',
array('action' => 'file'),
$m->connect('notice/:notice/file',
array('action' => 'file'),
array('notice' => '[0-9]+'));
$m->connect('notice/:notice',
array('action' => 'shownotice'),
array('notice' => '[0-9]+'));
@ -228,6 +229,14 @@ class Router
array('nickname' => '[a-zA-Z0-9]+'));
}
$m->connect('group/:nickname/blocked',
array('action' => 'blockedfromgroup'),
array('nickname' => '[a-zA-Z0-9]+'));
$m->connect('group/:nickname/makeadmin',
array('action' => 'makeadmin'),
array('nickname' => '[a-zA-Z0-9]+'));
$m->connect('group/:id/id',
array('action' => 'groupbyid'),
array('id' => '[0-9]+'));

131
lib/subscriptionlist.php Normal file
View File

@ -0,0 +1,131 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Widget to show a list of profiles
*
* 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 Public
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
* @copyright 2008-2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/profilelist.php';
/**
* Widget to show a list of subscriptions
*
* @category Public
* @package Laconica
* @author Zach Copley <zach@controlyourself.ca>
* @author Evan Prodromou <evan@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
class SubscriptionList extends ProfileList
{
/** Owner of this list */
var $owner = null;
function __construct($profile, $owner=null, $action=null)
{
parent::__construct($profile, $action);
$this->owner = $owner;
}
function newListItem($profile)
{
return new SubscriptionListItem($profile, $this->owner, $this->action);
}
}
class SubscriptionListItem extends ProfileListItem
{
/** Owner of this list */
var $owner = null;
function __construct($profile, $owner, $action)
{
parent::__construct($profile, $action);
$this->owner = $owner;
}
function showProfile()
{
$this->startProfile();
$this->showAvatar();
$this->showFullName();
$this->showLocation();
$this->showHomepage();
$this->showBio();
// Relevant portion!
$this->showTags();
$this->endProfile();
}
function isOwn()
{
$user = common_current_user();
return (!empty($user) && ($this->owner->id == $user->id));
}
function showTags()
{
$tags = Profile_tag::getTags($this->owner->id, $this->profile->id);
$this->out->elementStart('dl', 'entity_tags');
$this->out->elementStart('dt');
if ($this->isOwn()) {
$this->out->element('a', array('href' => common_local_url('tagother',
array('id' => $this->profile->id))),
_('Tags'));
} else {
$this->out->text(_('Tags'));
}
$this->out->elementEnd('dt');
$this->out->elementStart('dd');
if ($tags) {
$this->out->elementStart('ul', 'tags xoxo');
foreach ($tags as $tag) {
$this->out->elementStart('li');
$this->out->element('span', 'mark_hash', '#');
$this->out->element('a', array('rel' => 'tag',
'href' => common_local_url($this->action->trimmed('action'),
array('nickname' => $this->owner->nickname,
'tag' => $tag))),
$tag);
$this->out->elementEnd('li');
}
$this->out->elementEnd('ul');
} else {
$this->out->text(_('(none)'));
}
$this->out->elementEnd('dd');
$this->out->elementEnd('dl');
}
}

View File

@ -70,30 +70,3 @@ function theme_path($relative, $theme=null)
return common_path('theme/'.$theme.'/'.$relative);
}
}
/**
* Gets the full URL of a file in a skin dir based on its relative name
*
* @param string $relative relative path within the theme, skin directory
* @param string $theme name of the theme; defaults to current theme
* @param string $skin name of the skin; defaults to current theme
*
* @return string URL of the file
*/
function skin_path($relative, $theme=null, $skin=null)
{
if (!$theme) {
$theme = common_config('site', 'theme');
}
if (!$skin) {
$skin = common_config('site', 'skin');
}
$server = common_config('theme', 'server');
if ($server) {
return 'http://'.$server.'/'.$theme.'/skin/'.$skin.'/'.$relative;
} else {
return common_path('theme/'.$theme.'/skin/'.$skin.'/'.$relative);
}
}

View File

@ -591,7 +591,7 @@ function common_at_link($sender_id, $nickname)
function common_group_link($sender_id, $nickname)
{
$sender = Profile::staticGet($sender_id);
$group = User_group::staticGet('nickname', common_canonical_nickname($nickname));
$group = User_group::getForNickname($nickname);
if ($group && $sender->isMember($group)) {
$attrs = array('href' => $group->permalink(),
'class' => 'url');

View File

@ -66,7 +66,13 @@ class MailerDaemon
return true;
}
$msg = $this->cleanup_msg($msg);
$this->add_notice($user, $msg);
$err = $this->add_notice($user, $msg);
if (is_string($err)) {
$this->error($from, $err);
return false;
} else {
return true;
}
}
function error($from, $msg)
@ -130,17 +136,15 @@ class MailerDaemon
function add_notice($user, $msg)
{
// should test
// $msg_shortened = common_shorten_links($msg);
// if (mb_strlen($msg_shortened) > 140) ERROR and STOP
$notice = Notice::saveNew($user->id, $msg, 'mail');
if (is_string($notice)) {
$this->log(LOG_ERR, $notice);
return;
return $notice;
}
common_broadcast_notice($notice);
$this->log(LOG_INFO,
'Added notice ' . $notice->id . ' from user ' . $user->nickname);
return true;
}
function parse_message($fname)

View File

@ -445,6 +445,8 @@ width:80.789%;
height:67px;
line-height:1.5;
padding:7px 7px 16px 7px;
position:relative;
z-index:2;
}
#form_notice label {
display:block;
@ -452,23 +454,23 @@ float:left;
font-size:1.3em;
margin-bottom:7px;
}
#form_notice label[for=notice_data-attach] {
text-indent:-9999px;
}
#form_notice label[for=notice_data-attach],
#form_notice #notice_data-attach {
position:absolute;
top:25px;
right:49px;
width:16px;
height:16px;
cursor:pointer;
}
#form_notice #notice_data-attach {
text-indent:-279px;
#form_notice label[for=notice_data-attach] {
text-indent:-9999px;
left:394px;
width:16px;
height:16px;
}
#form_notice #notice_submit label {
display:none;
#form_notice #notice_data-attach {
left:183px;
padding:0;
height:16px;
}
#form_notice .form_note {
position:absolute;
@ -616,6 +618,8 @@ display:block;
.form_user_block input.submit,
.form_user_unblock input.submit,
.form_group_block input.submit,
.form_group_unblock input.submit,
.entity_send-a-message a,
.entity_edit a,
.form_user_nudge input.submit,

View File

@ -8,6 +8,15 @@ top:0;
#form_notice textarea {
width:78%;
}
#form_notice .form_note + label {
position:absolute;
top:25px;
left:380px;
text-indent:-9999px;
height:16px;
width:16px;
display:block;
}
#form_notice #notice_action-submit {
width:17%;
max-width:17%;

View File

@ -57,6 +57,8 @@ a,
div.notice-options input,
.form_user_block input.submit,
.form_user_unblock input.submit,
.form_group_block input.submit,
.form_group_unblock input.submit,
.entity_send-a-message a,
.form_user_nudge input.submit,
.entity_nudge p,
@ -148,6 +150,8 @@ background-image:url(../../base/images/icons/icon_foaf.gif);
.form_user_nudge input.submit,
.form_user_block input.submit,
.form_user_unblock input.submit,
.form_group_block input.submit,
.form_group_unblock input.submit,
.entity_nudge p {
background-position: 0 40%;
background-repeat: no-repeat;
@ -177,7 +181,9 @@ background-image:url(../../base/images/icons/twotone/green/quote.gif);
background-image:url(../../base/images/icons/twotone/green/mail.gif);
}
.form_user_block input.submit,
.form_user_unblock input.submit {
.form_user_unblock input.submit,
.form_group_block input.submit,
.form_group_unblock input.submit {
background-image:url(../../base/images/icons/twotone/green/shield.gif);
}

View File

@ -3,7 +3,12 @@
.notice-options input.submit {
color:#fff;
}
#site_nav_local_views a {
background-color:#ACCCDA;
}
#form_notice .form_note + label {
background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
}
#form_notice #notice_data-attach {
filter: alpha(opacity=0);
}

View File

@ -57,6 +57,8 @@ a,
div.notice-options input,
.form_user_block input.submit,
.form_user_unblock input.submit,
.form_group_block input.submit,
.form_group_unblock input.submit,
.entity_send-a-message a,
.form_user_nudge input.submit,
.entity_nudge p,
@ -148,6 +150,8 @@ background-image:url(../../base/images/icons/icon_foaf.gif);
.form_user_nudge input.submit,
.form_user_block input.submit,
.form_user_unblock input.submit,
.form_group_block input.submit,
.form_group_unblock input.submit,
.entity_nudge p {
background-position: 0 40%;
background-repeat: no-repeat;
@ -177,7 +181,9 @@ background-image:url(../../base/images/icons/twotone/green/quote.gif);
background-image:url(../../base/images/icons/twotone/green/mail.gif);
}
.form_user_block input.submit,
.form_user_unblock input.submit {
.form_user_unblock input.submit,
.form_group_block input.submit,
.form_group_unblock input.submit {
background-image:url(../../base/images/icons/twotone/green/shield.gif);
}

View File

@ -3,7 +3,12 @@
.notice-options input.submit {
color:#fff;
}
#site_nav_local_views a {
background-color:#D0DFE7;
}
#form_notice .form_note + label {
background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
}
#form_notice #notice_data-attach {
filter: alpha(opacity=0);
}