Merge commit 'origin/testing' into 0.9.x

Conflicts:
	lib/action.php
	lib/adminpanelaction.php
This commit is contained in:
Brion Vibber 2010-03-04 06:07:28 -08:00
commit b218aee94e
65 changed files with 2414 additions and 762 deletions

View File

@ -363,6 +363,14 @@ EndProfileRemoteSubscribe: After showing the link to remote subscription
- $userprofile: UserProfile widget - $userprofile: UserProfile widget
- &$profile: the profile being shown - &$profile: the profile being shown
StartGroupSubscribe: Before showing the link to remote subscription
- $action: the current action
- $group: the group being shown
EndGroupSubscribe: After showing the link to remote subscription
- $action: the current action
- $group: the group being shown
StartProfilePageProfileSection: Starting to show the section of the StartProfilePageProfileSection: Starting to show the section of the
profile page with the actual profile data; profile page with the actual profile data;
hook to prevent showing the profile (e.g.) hook to prevent showing the profile (e.g.)
@ -770,12 +778,30 @@ StartShowSubscriptionsContent: before showing the subscriptions content
EndShowSubscriptionsContent: after showing the subscriptions content EndShowSubscriptionsContent: after showing the subscriptions content
- $action: the current action - $action: the current action
StartShowUserGroupsContent: before showing the user groups content
- $action: the current action
EndShowUserGroupsContent: after showing the user groups content
- $action: the current action
StartShowAllContent: before showing the all (you and friends) content StartShowAllContent: before showing the all (you and friends) content
- $action: the current action - $action: the current action
EndShowAllContent: after showing the all (you and friends) content EndShowAllContent: after showing the all (you and friends) content
- $action: the current action - $action: the current action
StartShowSubscriptionsMiniList: at the start of subscriptions mini list
- $action: the current action
EndShowSubscriptionsMiniList: at the end of subscriptions mini list
- $action: the current action
StartShowGroupsMiniList: at the start of groups mini list
- $action: the current action
EndShowGroupsMiniList: at the end of groups mini list
- $action: the current action
StartDeleteUserForm: starting the data in the form for deleting a user StartDeleteUserForm: starting the data in the form for deleting a user
- $action: action being shown - $action: action being shown
- $user: user being deleted - $user: user being deleted

View File

@ -83,6 +83,7 @@ class AllrssAction extends Rss10Action
function getNotices($limit=0) function getNotices($limit=0)
{ {
$cur = common_current_user(); $cur = common_current_user();
$user = $this->user;
if (!empty($cur) && $cur->id == $user->id) { if (!empty($cur) && $cur->id == $user->id) {
$notice = $this->user->noticeInbox(0, $limit); $notice = $this->user->noticeInbox(0, $limit);
@ -90,7 +91,6 @@ class AllrssAction extends Rss10Action
$notice = $this->user->noticesWithFriends(0, $limit); $notice = $this->user->noticesWithFriends(0, $limit);
} }
$user = $this->user;
$notice = $user->noticesWithFriends(0, $limit); $notice = $user->noticesWithFriends(0, $limit);
$notices = array(); $notices = array();

View File

@ -104,17 +104,8 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
function showTimeline() function showTimeline()
{ {
$sitename = common_config('site', 'name'); // We'll pull common formatting out of this for other formats
$avatar = $this->group->homepage_logo; $atom = new AtomGroupNoticeFeed($this->group);
$title = sprintf(_("%s timeline"), $this->group->nickname);
$subtitle = sprintf(
_('Updates from %1$s on %2$s!'),
$this->group->nickname,
$sitename
);
$logo = ($avatar) ? $avatar : User_group::defaultLogo(AVATAR_PROFILE_SIZE);
switch($this->format) { switch($this->format) {
case 'xml': case 'xml':
@ -123,11 +114,11 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
case 'rss': case 'rss':
$this->showRssTimeline( $this->showRssTimeline(
$this->notices, $this->notices,
$title, $atom->title,
$this->group->homeUrl(), $this->group->homeUrl(),
$subtitle, $atom->subtitle,
null, null,
$logo $atom->logo
); );
break; break;
case 'atom': case 'atom':
@ -136,38 +127,22 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
try { try {
$atom = new AtomGroupNoticeFeed($this->group);
// @todo set all this Atom junk up inside the feed class
$atom->setId($id);
$atom->setTitle($title);
$atom->setSubtitle($subtitle);
$atom->setLogo($logo);
$atom->setUpdated('now');
$atom->addAuthorRaw($this->group->asAtomAuthor()); $atom->addAuthorRaw($this->group->asAtomAuthor());
$atom->setActivitySubject($this->group->asActivitySubject()); $atom->setActivitySubject($this->group->asActivitySubject());
$atom->addLink($this->group->homeUrl());
$id = $this->arg('id'); $id = $this->arg('id');
$aargs = array('format' => 'atom'); $aargs = array('format' => 'atom');
if (!empty($id)) { if (!empty($id)) {
$aargs['id'] = $id; $aargs['id'] = $id;
} }
$self = $this->getSelfUri('ApiTimelineGroup', $aargs);
$atom->setId($this->getSelfUri('ApiTimelineGroup', $aargs)); $atom->setId($self);
$atom->setSelfLink($self);
$atom->addLink(
$this->getSelfUri('ApiTimelineGroup', $aargs),
array('rel' => 'self', 'type' => 'application/atom+xml')
);
$atom->addEntryFromNotices($this->notices); $atom->addEntryFromNotices($this->notices);
//$this->raw($atom->getString()); $this->raw($atom->getString());
print $atom->getString(); // temp hack until PuSH feeds are redone cleanly
} catch (Atom10FeedException $e) { } catch (Atom10FeedException $e) {
$this->serverError( $this->serverError(

View File

@ -112,19 +112,17 @@ class ApiTimelineUserAction extends ApiBareAuthAction
function showTimeline() function showTimeline()
{ {
$profile = $this->user->getProfile(); $profile = $this->user->getProfile();
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
$sitename = common_config('site', 'name'); // We'll use the shared params from the Atom stub
$title = sprintf(_("%s timeline"), $this->user->nickname); // for other feed types.
$atom = new AtomUserNoticeFeed($this->user);
$title = $atom->title;
$link = common_local_url( $link = common_local_url(
'showstream', 'showstream',
array('nickname' => $this->user->nickname) array('nickname' => $this->user->nickname)
); );
$subtitle = sprintf( $subtitle = $atom->subtitle;
_('Updates from %1$s on %2$s!'), $logo = $atom->logo;
$this->user->nickname, $sitename
);
$logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE);
// FriendFeed's SUP protocol // FriendFeed's SUP protocol
// Also added RSS and Atom feeds // Also added RSS and Atom feeds
@ -146,47 +144,18 @@ class ApiTimelineUserAction extends ApiBareAuthAction
header('Content-Type: application/atom+xml; charset=utf-8'); header('Content-Type: application/atom+xml; charset=utf-8');
// @todo set all this Atom junk up inside the feed class
$atom = new AtomUserNoticeFeed($this->user);
$atom->setTitle($title);
$atom->setSubtitle($subtitle);
$atom->setLogo($logo);
$atom->setUpdated('now');
$atom->addLink(
common_local_url(
'showstream',
array('nickname' => $this->user->nickname)
)
);
$id = $this->arg('id'); $id = $this->arg('id');
$aargs = array('format' => 'atom'); $aargs = array('format' => 'atom');
if (!empty($id)) { if (!empty($id)) {
$aargs['id'] = $id; $aargs['id'] = $id;
} }
$self = $this->getSelfUri('ApiTimelineUser', $aargs);
$atom->setId($this->getSelfUri('ApiTimelineUser', $aargs)); $atom->setId($self);
$atom->setSelfLink($self);
$atom->addLink(
$this->getSelfUri('ApiTimelineUser', $aargs),
array('rel' => 'self', 'type' => 'application/atom+xml')
);
$atom->addLink(
$suplink,
array(
'rel' => 'http://api.friendfeed.com/2008/03#sup',
'type' => 'application/json'
)
);
$atom->addEntryFromNotices($this->notices); $atom->addEntryFromNotices($this->notices);
#$this->raw($atom->getString()); $this->raw($atom->getString());
print $atom->getString(); // temporary for output buffering
break; break;
case 'json': case 'json':

99
actions/grantrole.php Normal file
View File

@ -0,0 +1,99 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Action class to sandbox an abusive user
*
* 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 Action
* @package StatusNet
* @author Evan Prodromou <evan@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')) {
exit(1);
}
/**
* Sandbox a user.
*
* @category Action
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*/
class GrantRoleAction extends ProfileFormAction
{
/**
* Check parameters
*
* @param array $args action arguments (URL, GET, POST)
*
* @return boolean success flag
*/
function prepare($args)
{
if (!parent::prepare($args)) {
return false;
}
$this->role = $this->arg('role');
if (!Profile_role::isValid($this->role)) {
$this->clientError(_("Invalid role."));
return false;
}
if (!Profile_role::isSettable($this->role)) {
$this->clientError(_("This role is reserved and cannot be set."));
return false;
}
$cur = common_current_user();
assert(!empty($cur)); // checked by parent
if (!$cur->hasRight(Right::GRANTROLE)) {
$this->clientError(_("You cannot grant user roles on this site."));
return false;
}
assert(!empty($this->profile)); // checked by parent
if ($this->profile->hasRole($this->role)) {
$this->clientError(_("User already has this role."));
return false;
}
return true;
}
/**
* Sandbox a user.
*
* @return void
*/
function handlePost()
{
$this->profile->grantRole($this->role);
}
}

View File

@ -99,7 +99,7 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction
$application = $profile->getApplications($offset, $limit); $application = $profile->getApplications($offset, $limit);
$cnt == 0; $cnt = 0;
if (!empty($application)) { if (!empty($application)) {
$al = new ApplicationList($application, $user, $this, true); $al = new ApplicationList($application, $user, $this, true);
@ -112,7 +112,7 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction
$this->pagination($this->page > 1, $cnt > APPS_PER_PAGE, $this->pagination($this->page > 1, $cnt > APPS_PER_PAGE,
$this->page, 'connectionssettings', $this->page, 'connectionssettings',
array('nickname' => $this->user->nickname)); array('nickname' => $user->nickname));
} }
/** /**

99
actions/revokerole.php Normal file
View File

@ -0,0 +1,99 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Action class to sandbox an abusive user
*
* 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 Action
* @package StatusNet
* @author Evan Prodromou <evan@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')) {
exit(1);
}
/**
* Sandbox a user.
*
* @category Action
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*/
class RevokeRoleAction extends ProfileFormAction
{
/**
* Check parameters
*
* @param array $args action arguments (URL, GET, POST)
*
* @return boolean success flag
*/
function prepare($args)
{
if (!parent::prepare($args)) {
return false;
}
$this->role = $this->arg('role');
if (!Profile_role::isValid($this->role)) {
$this->clientError(_("Invalid role."));
return false;
}
if (!Profile_role::isSettable($this->role)) {
$this->clientError(_("This role is reserved and cannot be set."));
return false;
}
$cur = common_current_user();
assert(!empty($cur)); // checked by parent
if (!$cur->hasRight(Right::REVOKEROLE)) {
$this->clientError(_("You cannot revoke user roles on this site."));
return false;
}
assert(!empty($this->profile)); // checked by parent
if (!$this->profile->hasRole($this->role)) {
$this->clientError(_("User doesn't have this role."));
return false;
}
return true;
}
/**
* Sandbox a user.
*
* @return void
*/
function handlePost()
{
$this->profile->revokeRole($this->role);
}
}

View File

@ -301,6 +301,7 @@ class ShowgroupAction extends GroupDesignAction
$this->element('h2', null, _('Group actions')); $this->element('h2', null, _('Group actions'));
$this->elementStart('ul'); $this->elementStart('ul');
$this->elementStart('li', 'entity_subscribe'); $this->elementStart('li', 'entity_subscribe');
if (Event::handle('StartGroupSubscribe', array($this, $this->group))) {
$cur = common_current_user(); $cur = common_current_user();
if ($cur) { if ($cur) {
if ($cur->isMember($this->group)) { if ($cur->isMember($this->group)) {
@ -311,9 +312,9 @@ class ShowgroupAction extends GroupDesignAction
$jf->show(); $jf->show();
} }
} }
Event::handle('EndGroupSubscribe', array($this, $this->group));
}
$this->elementEnd('li'); $this->elementEnd('li');
$this->elementEnd('ul'); $this->elementEnd('ul');
$this->elementEnd('div'); $this->elementEnd('div');
} }

View File

@ -66,7 +66,7 @@ class SiteadminpanelAction extends AdminPanelAction
function getInstructions() function getInstructions()
{ {
return _('Basic settings for this StatusNet site.'); return _('Basic settings for this StatusNet site');
} }
/** /**
@ -90,10 +90,11 @@ class SiteadminpanelAction extends AdminPanelAction
function saveSettings() function saveSettings()
{ {
static $settings = array('site' => array('name', 'broughtby', 'broughtbyurl', static $settings = array(
'site' => array('name', 'broughtby', 'broughtbyurl',
'email', 'timezone', 'language', 'email', 'timezone', 'language',
'site', 'textlimit', 'dupelimit'), 'site', 'textlimit', 'dupelimit'),
'snapshot' => array('run', 'reporturl', 'frequency')); );
$values = array(); $values = array();
@ -158,25 +159,6 @@ class SiteadminpanelAction extends AdminPanelAction
$this->clientError(sprintf(_('Unknown language "%s".'), $values['site']['language'])); $this->clientError(sprintf(_('Unknown language "%s".'), $values['site']['language']));
} }
// Validate report URL
if (!is_null($values['snapshot']['reporturl']) &&
!Validate::uri($values['snapshot']['reporturl'], array('allowed_schemes' => array('http', 'https')))) {
$this->clientError(_("Invalid snapshot report URL."));
}
// Validate snapshot run value
if (!in_array($values['snapshot']['run'], array('web', 'cron', 'never'))) {
$this->clientError(_("Invalid snapshot run value."));
}
// Validate snapshot run value
if (!Validate::number($values['snapshot']['frequency'])) {
$this->clientError(_("Snapshot frequency must be a number."));
}
// Validate text limit // Validate text limit
if (!Validate::number($values['site']['textlimit'], array('min' => 140))) { if (!Validate::number($values['site']['textlimit'], array('min' => 140))) {
@ -277,40 +259,14 @@ class SiteAdminPanelForm extends AdminForm
$this->unli(); $this->unli();
$this->li(); $this->li();
$this->out->dropdown('language', _('Language'), $this->out->dropdown('language', _('Default language'),
get_nice_language_list(), _('Default site language'), get_nice_language_list(), _('Site language when autodetection from browser settings is not available'),
false, $this->value('language')); false, $this->value('language'));
$this->unli(); $this->unli();
$this->out->elementEnd('ul'); $this->out->elementEnd('ul');
$this->out->elementEnd('fieldset'); $this->out->elementEnd('fieldset');
$this->out->elementStart('fieldset', array('id' => 'settings_admin_snapshots'));
$this->out->element('legend', null, _('Snapshots'));
$this->out->elementStart('ul', 'form_data');
$this->li();
$snapshot = array('web' => _('Randomly during Web hit'),
'cron' => _('In a scheduled job'),
'never' => _('Never'));
$this->out->dropdown('run', _('Data snapshots'),
$snapshot, _('When to send statistical data to status.net servers'),
false, $this->value('run', 'snapshot'));
$this->unli();
$this->li();
$this->input('frequency', _('Frequency'),
_('Snapshots will be sent once every N web hits'),
'snapshot');
$this->unli();
$this->li();
$this->input('reporturl', _('Report URL'),
_('Snapshots will be sent to this URL'),
'snapshot');
$this->unli();
$this->out->elementEnd('ul');
$this->out->elementEnd('fieldset');
$this->out->elementStart('fieldset', array('id' => 'settings_admin_limits')); $this->out->elementStart('fieldset', array('id' => 'settings_admin_limits'));
$this->out->element('legend', null, _('Limits')); $this->out->element('legend', null, _('Limits'));
$this->out->elementStart('ul', 'form_data'); $this->out->elementStart('ul', 'form_data');

View File

@ -0,0 +1,201 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Site notice administration panel
*
* 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 Settings
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
exit(1);
}
require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php';
/**
* Update the site-wide notice text
*
* @category Admin
* @package StatusNet
* @author Zach Copley <zach@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 SitenoticeadminpanelAction extends AdminPanelAction
{
/**
* Returns the page title
*
* @return string page title
*/
function title()
{
return _('Site Notice');
}
/**
* Instructions for using this form.
*
* @return string instructions
*/
function getInstructions()
{
return _('Edit site-wide message');
}
/**
* Show the site notice admin panel form
*
* @return void
*/
function showForm()
{
$form = new SiteNoticeAdminPanelForm($this);
$form->show();
return;
}
/**
* Save settings from the form
*
* @return void
*/
function saveSettings()
{
$siteNotice = $this->trimmed('site-notice');
// assert(all values are valid);
// This throws an exception on validation errors
$this->validate(&$siteNotice);
$config = new Config();
$result = Config::save('site', 'notice', $siteNotice);
if (!result) {
$this->ServerError(_("Unable to save site notice."));
}
}
function validate(&$siteNotice)
{
// Validate notice text
if (mb_strlen($siteNotice) > 255) {
$this->clientError(
_('Max length for the site-wide notice is 255 chars')
);
}
// scrub HTML input
$config = array(
'safe' => 1,
'deny_attribute' => 'id,style,on*'
);
$siteNotice = htmLawed($siteNotice, $config);
}
}
class SiteNoticeAdminPanelForm extends AdminForm
{
/**
* ID of the form
*
* @return int ID of the form
*/
function id()
{
return 'form_site_notice_admin_panel';
}
/**
* class of the form
*
* @return string class of the form
*/
function formClass()
{
return 'form_settings';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
return common_local_url('sitenoticeadminpanel');
}
/**
* Data elements of the form
*
* @return void
*/
function formData()
{
$this->out->elementStart('ul', 'form_data');
$this->out->elementStart('li');
$this->out->textarea(
'site-notice',
_('Site notice text'),
common_config('site', 'notice'),
_('Site-wide notice text (255 chars max; HTML okay)')
);
$this->out->elementEnd('li');
$this->out->elementEnd('ul');
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
$this->out->submit(
'submit',
_('Save'),
'submit',
null,
_('Save site notice')
);
}
}

View File

@ -0,0 +1,251 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Snapshots administration panel
*
* 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 Settings
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
exit(1);
}
/**
* Manage snapshots
*
* @category Admin
* @package StatusNet
* @author Zach Copley <zach@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 SnapshotadminpanelAction extends AdminPanelAction
{
/**
* Returns the page title
*
* @return string page title
*/
function title()
{
return _('Snapshots');
}
/**
* Instructions for using this form.
*
* @return string instructions
*/
function getInstructions()
{
return _('Manage snapshot configuration');
}
/**
* Show the snapshots admin panel form
*
* @return void
*/
function showForm()
{
$form = new SnapshotAdminPanelForm($this);
$form->show();
return;
}
/**
* Save settings from the form
*
* @return void
*/
function saveSettings()
{
static $settings = array(
'snapshot' => array('run', 'reporturl', 'frequency')
);
$values = array();
foreach ($settings as $section => $parts) {
foreach ($parts as $setting) {
$values[$section][$setting] = $this->trimmed($setting);
}
}
// This throws an exception on validation errors
$this->validate($values);
// assert(all values are valid);
$config = new Config();
$config->query('BEGIN');
foreach ($settings as $section => $parts) {
foreach ($parts as $setting) {
Config::save($section, $setting, $values[$section][$setting]);
}
}
$config->query('COMMIT');
return;
}
function validate(&$values)
{
// Validate snapshot run value
if (!in_array($values['snapshot']['run'], array('web', 'cron', 'never'))) {
$this->clientError(_("Invalid snapshot run value."));
}
// Validate snapshot frequency value
if (!Validate::number($values['snapshot']['frequency'])) {
$this->clientError(_("Snapshot frequency must be a number."));
}
// Validate report URL
if (!is_null($values['snapshot']['reporturl'])
&& !Validate::uri(
$values['snapshot']['reporturl'],
array('allowed_schemes' => array('http', 'https')
)
)) {
$this->clientError(_("Invalid snapshot report URL."));
}
}
}
class SnapshotAdminPanelForm extends AdminForm
{
/**
* ID of the form
*
* @return int ID of the form
*/
function id()
{
return 'form_snapshot_admin_panel';
}
/**
* class of the form
*
* @return string class of the form
*/
function formClass()
{
return 'form_settings';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
return common_local_url('snapshotadminpanel');
}
/**
* Data elements of the form
*
* @return void
*/
function formData()
{
$this->out->elementStart(
'fieldset',
array('id' => 'settings_admin_snapshots')
);
$this->out->element('legend', null, _('Snapshots'));
$this->out->elementStart('ul', 'form_data');
$this->li();
$snapshot = array(
'web' => _('Randomly during Web hit'),
'cron' => _('In a scheduled job'),
'never' => _('Never')
);
$this->out->dropdown(
'run',
_('Data snapshots'),
$snapshot,
_('When to send statistical data to status.net servers'),
false,
$this->value('run', 'snapshot')
);
$this->unli();
$this->li();
$this->input(
'frequency',
_('Frequency'),
_('Snapshots will be sent once every N web hits'),
'snapshot'
);
$this->unli();
$this->li();
$this->input(
'reporturl',
_('Report URL'),
_('Snapshots will be sent to this URL'),
'snapshot'
);
$this->unli();
$this->out->elementEnd('ul');
$this->out->elementEnd('fieldset');
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
$this->out->submit(
'submit',
_('Save'),
'submit',
null,
_('Save snapshot settings')
);
}
}

View File

@ -143,9 +143,12 @@ class SubscribersListItem extends SubscriptionListItem
function showActions() function showActions()
{ {
$this->startActions(); $this->startActions();
if (Event::handle('StartProfileListItemActionElements', array($this))) {
$this->showSubscribeButton(); $this->showSubscribeButton();
// Relevant code! // Relevant code!
$this->showBlockForm(); $this->showBlockForm();
Event::handle('EndProfileListItemActionElements', array($this));
}
$this->endActions(); $this->endActions();
} }

View File

@ -130,6 +130,7 @@ class UsergroupsAction extends OwnerDesignAction
_('Search for more groups')); _('Search for more groups'));
$this->elementEnd('p'); $this->elementEnd('p');
if (Event::handle('StartShowUserGroupsContent', array($this))) {
$offset = ($this->page-1) * GROUPS_PER_PAGE; $offset = ($this->page-1) * GROUPS_PER_PAGE;
$limit = GROUPS_PER_PAGE + 1; $limit = GROUPS_PER_PAGE + 1;
@ -146,6 +147,9 @@ class UsergroupsAction extends OwnerDesignAction
$this->pagination($this->page > 1, $cnt > GROUPS_PER_PAGE, $this->pagination($this->page > 1, $cnt > GROUPS_PER_PAGE,
$this->page, 'usergroups', $this->page, 'usergroups',
array('nickname' => $this->user->nickname)); array('nickname' => $this->user->nickname));
Event::handle('EndShowUserGroupsContent', array($this));
}
} }
function showEmptyListMessage() function showEmptyListMessage()

View File

@ -290,5 +290,12 @@ class File extends Memcached_DataObject
} }
return $enclosure; return $enclosure;
} }
// quick back-compat hack, since there's still code using this
function isEnclosure()
{
$enclosure = $this->getEnclosure();
return !empty($enclosure);
}
} }

View File

@ -211,6 +211,8 @@ class Notice extends Memcached_DataObject
* extracting ! tags from content * extracting ! tags from content
* array 'tags' list of hashtag strings to save with the notice * array 'tags' list of hashtag strings to save with the notice
* in place of extracting # tags from content * in place of extracting # tags from content
* array 'urls' list of attached/referred URLs to save with the
* notice in place of extracting links from content
* @fixme tag override * @fixme tag override
* *
* @return Notice * @return Notice
@ -380,8 +382,11 @@ class Notice extends Memcached_DataObject
$notice->saveTags(); $notice->saveTags();
} }
// @fixme pass in data for URLs too? if (isset($urls)) {
$notice->saveKnownUrls($urls);
} else {
$notice->saveUrls(); $notice->saveUrls();
}
// Prepare inbox delivery, may be queued to background. // Prepare inbox delivery, may be queued to background.
$notice->distribute(); $notice->distribute();
@ -427,6 +432,25 @@ class Notice extends Memcached_DataObject
common_replace_urls_callback($this->content, array($this, 'saveUrl'), $this->id); common_replace_urls_callback($this->content, array($this, 'saveUrl'), $this->id);
} }
/**
* Save the given URLs as related links/attachments to the db
*
* follow redirects and save all available file information
* (mimetype, date, size, oembed, etc.)
*
* @return void
*/
function saveKnownUrls($urls)
{
// @fixme validation?
foreach ($urls as $url) {
File::processNew($url, $this->id);
}
}
/**
* @private callback
*/
function saveUrl($data) { function saveUrl($data) {
list($url, $notice_id) = $data; list($url, $notice_id) = $data;
File::processNew($url, $notice_id); File::processNew($url, $notice_id);
@ -853,7 +877,7 @@ class Notice extends Memcached_DataObject
foreach (array_unique($match[1]) as $nickname) { foreach (array_unique($match[1]) as $nickname) {
/* XXX: remote groups. */ /* XXX: remote groups. */
$group = User_group::getForNickname($nickname); $group = User_group::getForNickname($nickname, $profile);
if (empty($group)) { if (empty($group)) {
continue; continue;
@ -1082,7 +1106,7 @@ class Notice extends Memcached_DataObject
return $groups; return $groups;
} }
function asAtomEntry($namespace=false, $source=false) function asAtomEntry($namespace=false, $source=false, $author=true)
{ {
$profile = $this->getProfile(); $profile = $this->getProfile();
@ -1127,8 +1151,10 @@ class Notice extends Memcached_DataObject
$xs->element('title', null, $this->content); $xs->element('title', null, $this->content);
if ($author) {
$xs->raw($profile->asAtomAuthor()); $xs->raw($profile->asAtomAuthor());
$xs->raw($profile->asActivityActor()); $xs->raw($profile->asActivityActor());
}
$xs->element('link', array('rel' => 'alternate', $xs->element('link', array('rel' => 'alternate',
'type' => 'text/html', 'type' => 'text/html',

View File

@ -282,6 +282,32 @@ class Profile extends Memcached_DataObject
} }
} }
function getGroups($offset=0, $limit=null)
{
$qry =
'SELECT user_group.* ' .
'FROM user_group JOIN group_member '.
'ON user_group.id = group_member.group_id ' .
'WHERE group_member.profile_id = %d ' .
'ORDER BY group_member.created DESC ';
if ($offset>0 && !is_null($limit)) {
if ($offset) {
if (common_config('db','type') == 'pgsql') {
$qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
} else {
$qry .= ' LIMIT ' . $offset . ', ' . $limit;
}
}
}
$groups = new User_group();
$cnt = $groups->query(sprintf($qry, $this->id));
return $groups;
}
function avatarUrl($size=AVATAR_PROFILE_SIZE) function avatarUrl($size=AVATAR_PROFILE_SIZE)
{ {
$avatar = $this->getAvatar($size); $avatar = $this->getAvatar($size);
@ -717,6 +743,10 @@ class Profile extends Memcached_DataObject
case Right::CONFIGURESITE: case Right::CONFIGURESITE:
$result = $this->hasRole(Profile_role::ADMINISTRATOR); $result = $this->hasRole(Profile_role::ADMINISTRATOR);
break; break;
case Right::GRANTROLE:
case Right::REVOKEROLE:
$result = $this->hasRole(Profile_role::OWNER);
break;
case Right::NEWNOTICE: case Right::NEWNOTICE:
case Right::NEWMESSAGE: case Right::NEWMESSAGE:
case Right::SUBSCRIBE: case Right::SUBSCRIBE:

View File

@ -53,4 +53,21 @@ class Profile_role extends Memcached_DataObject
const ADMINISTRATOR = 'administrator'; const ADMINISTRATOR = 'administrator';
const SANDBOXED = 'sandboxed'; const SANDBOXED = 'sandboxed';
const SILENCED = 'silenced'; const SILENCED = 'silenced';
public static function isValid($role)
{
// @fixme could probably pull this from class constants
$known = array(self::OWNER,
self::MODERATOR,
self::ADMINISTRATOR,
self::SANDBOXED,
self::SILENCED);
return in_array($role, $known);
}
public static function isSettable($role)
{
$allowedRoles = array('administrator', 'moderator');
return self::isValid($role) && in_array($role, $allowedRoles);
}
} }

View File

@ -613,28 +613,8 @@ class User extends Memcached_DataObject
function getGroups($offset=0, $limit=null) function getGroups($offset=0, $limit=null)
{ {
$qry = $profile = $this->getProfile();
'SELECT user_group.* ' . return $profile->getGroups($offset, $limit);
'FROM user_group JOIN group_member '.
'ON user_group.id = group_member.group_id ' .
'WHERE group_member.profile_id = %d ' .
'ORDER BY group_member.created DESC ';
if ($offset>0 && !is_null($limit)) {
if ($offset) {
if (common_config('db','type') == 'pgsql') {
$qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
} else {
$qry .= ' LIMIT ' . $offset . ', ' . $limit;
}
}
}
$groups = new User_group();
$cnt = $groups->query(sprintf($qry, $this->id));
return $groups;
} }
function getSubscriptions($offset=0, $limit=null) function getSubscriptions($offset=0, $limit=null)

View File

@ -279,12 +279,26 @@ class User_group extends Memcached_DataObject
return true; return true;
} }
static function getForNickname($nickname) static function getForNickname($nickname, $profile=null)
{ {
$nickname = common_canonical_nickname($nickname); $nickname = common_canonical_nickname($nickname);
$group = User_group::staticGet('nickname', $nickname);
// Are there any matching remote groups this profile's in?
if ($profile) {
$group = $profile->getGroups();
while ($group->fetch()) {
if ($group->nickname == $nickname) {
// @fixme is this the best way?
return clone($group);
}
}
}
// If not, check local groups.
$group = Local_group::staticGet('nickname', $nickname);
if (!empty($group)) { if (!empty($group)) {
return $group; return User_group::staticGet('id', $group->group_id);
} }
$alias = Group_alias::staticGet('alias', $nickname); $alias = Group_alias::staticGet('alias', $nickname);
if (!empty($alias)) { if (!empty($alias)) {
@ -442,6 +456,11 @@ class User_group extends Memcached_DataObject
$group->query('BEGIN'); $group->query('BEGIN');
if (empty($uri)) {
// fill in later...
$uri = null;
}
$group->nickname = $nickname; $group->nickname = $nickname;
$group->fullname = $fullname; $group->fullname = $fullname;
$group->homepage = $homepage; $group->homepage = $homepage;

View File

@ -200,7 +200,7 @@ function checkMirror($action_obj, $args)
function isLoginAction($action) function isLoginAction($action)
{ {
static $loginActions = array('login', 'recoverpassword', 'api', 'doc', 'register', 'publicxrds'); static $loginActions = array('login', 'recoverpassword', 'api', 'doc', 'register', 'publicxrds', 'otp');
$login = null; $login = null;

View File

@ -31,6 +31,7 @@
* @author Robin Millette <millette@controlyourself.ca> * @author Robin Millette <millette@controlyourself.ca>
* @author Sarven Capadisli <csarven@status.net> * @author Sarven Capadisli <csarven@status.net>
* @author Tom Adams <tom@holizz.com> * @author Tom Adams <tom@holizz.com>
* @author Zach Copley <zach@status.net>
* @license GNU Affero General Public License http://www.gnu.org/licenses/ * @license GNU Affero General Public License http://www.gnu.org/licenses/
* @version 0.9.x * @version 0.9.x
* @link http://status.net * @link http://status.net
@ -490,15 +491,25 @@ function showForm()
<p class="form_guide">Database name</p> <p class="form_guide">Database name</p>
</li> </li>
<li> <li>
<label for="username">Username</label> <label for="username">DB username</label>
<input type="text" id="username" name="username" /> <input type="text" id="username" name="username" />
<p class="form_guide">Database username</p> <p class="form_guide">Database username</p>
</li> </li>
<li> <li>
<label for="password">Password</label> <label for="password">DB password</label>
<input type="password" id="password" name="password" /> <input type="password" id="password" name="password" />
<p class="form_guide">Database password (optional)</p> <p class="form_guide">Database password (optional)</p>
</li> </li>
<li>
<label for="admin_nickname">Administrator nickname</label>
<input type="text" id="admin_nickname" name="admin_nickname" />
<p class="form_guide">Nickname for the initial StatusNet user (administrator)</p>
</li>
<li>
<label for="initial_user_password">Administrator password</label>
<input type="password" id="admin_password" name="admin_password" />
<p class="form_guide">Password for the initial StatusNet user (administrator)</p>
</li>
</ul> </ul>
<input type="submit" name="submit" class="submit" value="Submit" /> <input type="submit" name="submit" class="submit" value="Submit" />
</fieldset> </fieldset>
@ -521,6 +532,10 @@ function handlePost()
$password = $_POST['password']; $password = $_POST['password'];
$sitename = $_POST['sitename']; $sitename = $_POST['sitename'];
$fancy = !empty($_POST['fancy']); $fancy = !empty($_POST['fancy']);
$adminNick = $_POST['admin_nickname'];
$adminPass = $_POST['admin_password'];
$server = $_SERVER['HTTP_HOST']; $server = $_SERVER['HTTP_HOST'];
$path = substr(dirname($_SERVER['PHP_SELF']), 1); $path = substr(dirname($_SERVER['PHP_SELF']), 1);
@ -552,6 +567,16 @@ STR;
$fail = true; $fail = true;
} }
if (empty($adminNick)) {
updateStatus("No initial StatusNet user nickname specified.", true);
$fail = true;
}
if (empty($adminPass)) {
updateStatus("No initial StatusNet user password specified.", true);
$fail = true;
}
if ($fail) { if ($fail) {
showForm(); showForm();
return; return;
@ -574,13 +599,29 @@ STR;
return; return;
} }
// Okay, cross fingers and try to register an initial user
if (registerInitialUser($adminNick, $adminPass)) {
updateStatus(
"An initial user with the administrator role has been created."
);
} else {
updateStatus(
"Could not create initial StatusNet user (administrator).",
true
);
showForm();
return;
}
/* /*
TODO https needs to be considered TODO https needs to be considered
*/ */
$link = "http://".$server.'/'.$path; $link = "http://".$server.'/'.$path;
updateStatus("StatusNet has been installed at $link"); updateStatus("StatusNet has been installed at $link");
updateStatus("You can visit your <a href='$link'>new StatusNet site</a>."); updateStatus(
"You can visit your <a href='$link'>new StatusNet site</a> (login as '$adminNick')."
);
} }
function Pgsql_Db_installer($host, $database, $username, $password) function Pgsql_Db_installer($host, $database, $username, $password)
@ -756,6 +797,33 @@ function runDbScript($filename, $conn, $type = 'mysqli')
return true; return true;
} }
function registerInitialUser($nickname, $password)
{
define('STATUSNET', true);
define('LACONICA', true); // compatibility
require_once INSTALLDIR . '/lib/common.php';
$user = User::register(
array('nickname' => $nickname,
'password' => $password,
'fullname' => $nickname
)
);
if (empty($user)) {
return false;
}
// give initial user carte blanche
$user->grantRole('owner');
$user->grantRole('moderator');
$user->grantRole('administrator');
return true;
}
?> ?>
<?php echo"<?"; ?> xml version="1.0" encoding="UTF-8" <?php echo "?>"; ?> <?php echo"<?"; ?> xml version="1.0" encoding="UTF-8" <?php echo "?>"; ?>
<!DOCTYPE html <!DOCTYPE html
@ -765,10 +833,10 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
<head> <head>
<title>Install StatusNet</title> <title>Install StatusNet</title>
<link rel="shortcut icon" href="favicon.ico"/> <link rel="shortcut icon" href="favicon.ico"/>
<link rel="stylesheet" type="text/css" href="theme/default/css/display.css?version=0.8" media="screen, projection, tv"/> <link rel="stylesheet" type="text/css" href="theme/default/css/display.css" media="screen, projection, tv"/>
<!--[if IE]><link rel="stylesheet" type="text/css" href="theme/base/css/ie.css?version=0.8" /><![endif]--> <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/base/css/ie.css" /><![endif]-->
<!--[if lte IE 6]><link rel="stylesheet" type="text/css" theme/base/css/ie6.css?version=0.8" /><![endif]--> <!--[if lte IE 6]><link rel="stylesheet" type="text/css" theme/base/css/ie6.css" /><![endif]-->
<!--[if IE]><link rel="stylesheet" type="text/css" href="theme/default/css/ie.css?version=0.8" /><![endif]--> <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/default/css/ie.css" /><![endif]-->
<script src="js/jquery.min.js"></script> <script src="js/jquery.min.js"></script>
<script src="js/install.js"></script> <script src="js/install.js"></script>
</head> </head>
@ -784,10 +852,12 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
</div> </div>
<div id="core"> <div id="core">
<div id="content"> <div id="content">
<div id="content_inner">
<h1>Install StatusNet</h1> <h1>Install StatusNet</h1>
<?php main(); ?> <?php main(); ?>
</div> </div>
</div> </div>
</div> </div>
</div>
</body> </body>
</html> </html>

View File

@ -53,7 +53,7 @@ var SN = { // StatusNet
NoticeLocationNs: 'notice_data-location_ns', NoticeLocationNs: 'notice_data-location_ns',
NoticeGeoName: 'notice_data-geo_name', NoticeGeoName: 'notice_data-geo_name',
NoticeDataGeo: 'notice_data-geo', NoticeDataGeo: 'notice_data-geo',
NoticeDataGeoCookie: 'notice_data-geo_cookie', NoticeDataGeoCookie: 'NoticeDataGeo',
NoticeDataGeoSelected: 'notice_data-geo_selected', NoticeDataGeoSelected: 'notice_data-geo_selected',
StatusNetInstance:'StatusNetInstance' StatusNetInstance:'StatusNetInstance'
} }
@ -423,8 +423,11 @@ var SN = { // StatusNet
}; };
notice.find('a.attachment').click(function() { notice.find('a.attachment').click(function() {
$().jOverlay({url: $('address .url')[0].href+'attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'}); var attachId = ($(this).attr('id').substring('attachment'.length + 1));
if (attachId) {
$().jOverlay({url: $('address .url')[0].href+'attachment/' + attachId + '/ajax'});
return false; return false;
}
}); });
if ($('#shownotice').length == 0) { if ($('#shownotice').length == 0) {
@ -494,7 +497,7 @@ var SN = { // StatusNet
$('#'+SN.C.S.NoticeLocationId).val(''); $('#'+SN.C.S.NoticeLocationId).val('');
$('#'+SN.C.S.NoticeDataGeo).attr('checked', false); $('#'+SN.C.S.NoticeDataGeo).attr('checked', false);
$.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled', { path: '/', expires: SN.U.GetFullYear(2029, 0, 1) }); $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled', { path: '/' });
} }
function getJSONgeocodeURL(geocodeURL, data) { function getJSONgeocodeURL(geocodeURL, data) {
@ -537,7 +540,7 @@ var SN = { // StatusNet
NDG: true NDG: true
}; };
$.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue), { path: '/', expires: SN.U.GetFullYear(2029, 0, 1) }); $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue), { path: '/' });
}); });
} }

View File

@ -420,13 +420,6 @@ class Action extends HTMLOutputter // lawsuit
function showPrimaryNav() function showPrimaryNav()
{ {
$user = common_current_user(); $user = common_current_user();
$connect = '';
if (common_config('xmpp', 'enabled')) {
$connect = 'imsettings';
} else if (common_config('sms', 'enabled')) {
$connect = 'smssettings';
}
$this->elementStart('dl', array('id' => 'site_nav_global_primary')); $this->elementStart('dl', array('id' => 'site_nav_global_primary'));
$this->element('dt', null, _('Primary site navigation')); $this->element('dt', null, _('Primary site navigation'));
$this->elementStart('dd'); $this->elementStart('dd');
@ -442,14 +435,12 @@ class Action extends HTMLOutputter // lawsuit
$tooltip = _m('TOOLTIP', 'Change your email, avatar, password, profile'); $tooltip = _m('TOOLTIP', 'Change your email, avatar, password, profile');
// TRANS: Main menu option when logged in for access to user settings // TRANS: Main menu option when logged in for access to user settings
$this->menuItem(common_local_url('profilesettings'), $this->menuItem(common_local_url('profilesettings'),
_m('MENU', 'Account'), $tooltip, false, 'nav_account'); _('Account'), $tooltip, false, 'nav_account');
if ($connect) {
// TRANS: Tooltip for main menu option "Services" // TRANS: Tooltip for main menu option "Services"
$tooltip = _m('TOOLTIP', 'Connect to services'); $tooltip = _m('TOOLTIP', 'Connect to services');
// TRANS: Main menu option when logged in and connection are possible for access to options to connect to other services // TRANS: Main menu option when logged in and connection are possible for access to options to connect to other services
$this->menuItem(common_local_url($connect), $this->menuItem(common_local_url('oauthconnectionssettings'),
_m('MENU', 'Connect'), $tooltip, false, 'nav_connect'); _('Connect'), $tooltip, false, 'nav_connect');
}
if ($user->hasRight(Right::CONFIGURESITE)) { if ($user->hasRight(Right::CONFIGURESITE)) {
// TRANS: Tooltip for menu option "Admin" // TRANS: Tooltip for menu option "Admin"
$tooltip = _m('TOOLTIP', 'Change site configuration'); $tooltip = _m('TOOLTIP', 'Change site configuration');

View File

@ -1044,6 +1044,7 @@ class Activity
public $id; // ID of the activity public $id; // ID of the activity
public $title; // title of the activity public $title; // title of the activity
public $categories = array(); // list of AtomCategory objects public $categories = array(); // list of AtomCategory objects
public $enclosures = array(); // list of enclosure URL references
/** /**
* Turns a regular old Atom <entry> into a magical activity * Turns a regular old Atom <entry> into a magical activity
@ -1059,6 +1060,18 @@ class Activity
} }
$this->entry = $entry; $this->entry = $entry;
// @fixme Don't send in a DOMDocument
if ($feed instanceof DOMDocument) {
common_log(
LOG_WARNING,
'Activity::__construct() - '
. 'DOMDocument passed in for feed by mistake. '
. "Expecting a 'feed' DOMElement."
);
$feed = $feed->getElementsByTagName('feed')->item(0);
}
$this->feed = $feed; $this->feed = $feed;
$pubEl = $this->_child($entry, self::PUBLISHED, self::ATOM); $pubEl = $this->_child($entry, self::PUBLISHED, self::ATOM);
@ -1140,6 +1153,10 @@ class Activity
$this->categories[] = new AtomCategory($catEl); $this->categories[] = new AtomCategory($catEl);
} }
} }
foreach (ActivityUtils::getLinks($entry, 'enclosure') as $link) {
$this->enclosures[] = $link->getAttribute('href');
}
} }
/** /**

View File

@ -175,6 +175,34 @@ class AdminPanelAction extends Action
$this->showForm(); $this->showForm();
} }
/**
* Show content block. Overrided just to add a special class
* to the content div to allow styling.
*
* @return nothing
*/
function showContentBlock()
{
$this->elementStart('div', array('id' => 'content', 'class' => 'admin'));
$this->showPageTitle();
$this->showPageNoticeBlock();
$this->elementStart('div', array('id' => 'content_inner'));
// show the actual content (forms, lists, whatever)
$this->showContent();
$this->elementEnd('div');
$this->elementEnd('div');
}
/**
* There is no data for aside, so, we don't output
*
* @return nothing
*/
function showAside()
{
}
/** /**
* show human-readable instructions for the page, or * show human-readable instructions for the page, or
* a success/failure on save. * a success/failure on save.
@ -345,32 +373,48 @@ class AdminPanelNav extends Widget
// TRANS: Menu item title/tooltip // TRANS: Menu item title/tooltip
$menu_title = _('User configuration'); $menu_title = _('User configuration');
// TRANS: Menu item for site administration // TRANS: Menu item for site administration
$this->out->menuItem(common_local_url('useradminpanel'), _m('MENU', 'User'), $this->out->menuItem(common_local_url('useradminpanel'), _('User'),
$menu_title, $action_name == 'useradminpanel', 'nav_design_admin_panel'); $menu_title, $action_name == 'useradminpanel', 'nav_user_admin_panel');
} }
if (AdminPanelAction::canAdmin('access')) { if (AdminPanelAction::canAdmin('access')) {
// TRANS: Menu item title/tooltip // TRANS: Menu item title/tooltip
$menu_title = _('Access configuration'); $menu_title = _('Access configuration');
// TRANS: Menu item for site administration // TRANS: Menu item for site administration
$this->out->menuItem(common_local_url('accessadminpanel'), _m('MENU', 'Access'), $this->out->menuItem(common_local_url('accessadminpanel'), _('Access'),
$menu_title, $action_name == 'accessadminpanel', 'nav_design_admin_panel'); $menu_title, $action_name == 'accessadminpanel', 'nav_access_admin_panel');
} }
if (AdminPanelAction::canAdmin('paths')) { if (AdminPanelAction::canAdmin('paths')) {
// TRANS: Menu item title/tooltip // TRANS: Menu item title/tooltip
$menu_title = _('Paths configuration'); $menu_title = _('Paths configuration');
// TRANS: Menu item for site administration // TRANS: Menu item for site administration
$this->out->menuItem(common_local_url('pathsadminpanel'), _m('MENU', 'Paths'), $this->out->menuItem(common_local_url('pathsadminpanel'), _('Paths'),
$menu_title, $action_name == 'pathsadminpanel', 'nav_design_admin_panel'); $menu_title, $action_name == 'pathsadminpanel', 'nav_paths_admin_panel');
} }
if (AdminPanelAction::canAdmin('sessions')) { if (AdminPanelAction::canAdmin('sessions')) {
// TRANS: Menu item title/tooltip // TRANS: Menu item title/tooltip
$menu_title = _('Sessions configuration'); $menu_title = _('Sessions configuration');
// TRANS: Menu item for site administration // TRANS: Menu item for site administration
$this->out->menuItem(common_local_url('sessionsadminpanel'), _m('MENU', 'Sessions'), $this->out->menuItem(common_local_url('sessionsadminpanel'), _('Sessions'),
$menu_title, $action_name == 'sessionsadminpanel', 'nav_design_admin_panel'); $menu_title, $action_name == 'sessionsadminpanel', 'nav_sessions_admin_panel');
}
if (AdminPanelAction::canAdmin('sitenotice')) {
// TRANS: Menu item title/tooltip
$menu_title = _('Edit site notice');
// TRANS: Menu item for site administration
$this->out->menuItem(common_local_url('sitenoticeadminpanel'), _('Site notice'),
$menu_title, $action_name == 'sitenoticeadminpanel', 'nav_sitenotice_admin_panel');
}
if (AdminPanelAction::canAdmin('snapshot')) {
// TRANS: Menu item title/tooltip
$menu_title = _('Snapshots configuration');
// TRANS: Menu item for site administration
$this->out->menuItem(common_local_url('snapshotadminpanel'), _('Snapshots'),
$menu_title, $action_name == 'snapshotadminpanel', 'nav_snapshot_admin_panel');
} }
Event::handle('EndAdminPanelNav', array($this)); Event::handle('EndAdminPanelNav', array($this));

View File

@ -1,105 +0,0 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Class for building / manipulating an Atom entry in memory
*
* 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 Feed
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
exit(1);
}
class Atom10EntryException extends Exception
{
}
/**
* Class for manipulating an Atom entry in memory. Get the entry as an XML
* string with Atom10Entry::getString().
*
* @category Feed
* @package StatusNet
* @author Zach Copley <zach@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 Atom10Entry extends XMLStringer
{
private $namespaces;
private $categories;
private $content;
private $contributors;
private $id;
private $links;
private $published;
private $rights;
private $source;
private $summary;
private $title;
function __construct($indent = true) {
parent::__construct($indent);
$this->namespaces = array();
}
function addNamespace($namespace, $uri)
{
$ns = array($namespace => $uri);
$this->namespaces = array_merge($this->namespaces, $ns);
}
function initEntry()
{
}
function endEntry()
{
}
/**
* Check that all required elements have been set, etc.
* Throws an Atom10EntryException if something's missing.
*
* @return void
*/
function validate()
{
}
function getString()
{
$this->validate();
$this->initEntry();
$this->renderEntries();
$this->endEntry();
return $this->xw->outputMemory();
}
}

View File

@ -49,6 +49,8 @@ class Atom10FeedException extends Exception
class Atom10Feed extends XMLStringer class Atom10Feed extends XMLStringer
{ {
public $xw; public $xw;
// @fixme most of these should probably be read-only properties
private $namespaces; private $namespaces;
private $authors; private $authors;
private $subject; private $subject;
@ -57,10 +59,12 @@ class Atom10Feed extends XMLStringer
private $generator; private $generator;
private $icon; private $icon;
private $links; private $links;
private $logo; private $selfLink;
private $selfLinkType;
public $logo;
private $rights; private $rights;
private $subtitle; public $subtitle;
private $title; public $title;
private $published; private $published;
private $updated; private $updated;
private $entries; private $entries;
@ -172,6 +176,14 @@ class Atom10Feed extends XMLStringer
} }
$this->elementStart('feed', $commonAttrs); $this->elementStart('feed', $commonAttrs);
$this->element(
'generator', array(
'url' => 'http://status.net',
'version' => STATUSNET_VERSION
),
'StatusNet'
);
$this->element('id', null, $this->id); $this->element('id', null, $this->id);
$this->element('title', null, $this->title); $this->element('title', null, $this->title);
$this->element('subtitle', null, $this->subtitle); $this->element('subtitle', null, $this->subtitle);
@ -184,6 +196,10 @@ class Atom10Feed extends XMLStringer
$this->renderAuthors(); $this->renderAuthors();
if ($this->selfLink) {
$this->addLink($this->selfLink, array('rel' => 'self',
'type' => $this->selfLinkType));
}
$this->renderLinks(); $this->renderLinks();
} }
@ -253,6 +269,12 @@ class Atom10Feed extends XMLStringer
$this->id = $id; $this->id = $id;
} }
function setSelfLink($url, $type='application/atom+xml')
{
$this->selfLink = $url;
$this->selfLinkType = $type;
}
function setTitle($title) function setTitle($title)
{ {
$this->title = $title; $this->title = $title;

View File

@ -49,14 +49,42 @@ class AtomGroupNoticeFeed extends AtomNoticeFeed
/** /**
* Constructor * Constructor
* *
* @param Group $group the group for the feed (optional) * @param Group $group the group for the feed
* @param boolean $indent flag to turn indenting on or off * @param boolean $indent flag to turn indenting on or off
* *
* @return void * @return void
*/ */
function __construct($group = null, $indent = true) { function __construct($group, $indent = true) {
parent::__construct($indent); parent::__construct($indent);
$this->group = $group; $this->group = $group;
$title = sprintf(_("%s timeline"), $group->nickname);
$this->setTitle($title);
$sitename = common_config('site', 'name');
$subtitle = sprintf(
_('Updates from %1$s on %2$s!'),
$group->nickname,
$sitename
);
$this->setSubtitle($subtitle);
$avatar = $group->homepage_logo;
$logo = ($avatar) ? $avatar : User_group::defaultLogo(AVATAR_PROFILE_SIZE);
$this->setLogo($logo);
$this->setUpdated('now');
$self = common_local_url('ApiTimelineGroup',
array('id' => $group->id,
'format' => 'atom'));
$this->setId($self);
$this->setSelfLink($self);
$this->addAuthorRaw($group->asAtomAuthor());
$this->setActivitySubject($group->asActivitySubject());
$this->addLink($group->homeUrl());
} }
function getGroup() function getGroup()

View File

@ -107,9 +107,19 @@ class AtomNoticeFeed extends Atom10Feed
*/ */
function addEntryFromNotice($notice) function addEntryFromNotice($notice)
{ {
$this->addEntryRaw($notice->asAtomEntry()); $source = $this->showSource();
$author = $this->showAuthor();
$this->addEntryRaw($notice->asAtomEntry(false, $source, $author));
} }
function showSource()
{
return true;
}
function showAuthor()
{
return true;
}
} }

View File

@ -49,23 +49,71 @@ class AtomUserNoticeFeed extends AtomNoticeFeed
/** /**
* Constructor * Constructor
* *
* @param User $user the user for the feed (optional) * @param User $user the user for the feed
* @param boolean $indent flag to turn indenting on or off * @param boolean $indent flag to turn indenting on or off
* *
* @return void * @return void
*/ */
function __construct($user = null, $indent = true) { function __construct($user, $indent = true) {
parent::__construct($indent); parent::__construct($indent);
$this->user = $user; $this->user = $user;
if (!empty($user)) { if (!empty($user)) {
$profile = $user->getProfile(); $profile = $user->getProfile();
$this->addAuthor($profile->nickname, $user->uri); $this->addAuthor($profile->nickname, $user->uri);
$this->setActivitySubject($profile->asActivityNoun('subject'));
} }
$title = sprintf(_("%s timeline"), $user->nickname);
$this->setTitle($title);
$sitename = common_config('site', 'name');
$subtitle = sprintf(
_('Updates from %1$s on %2$s!'),
$user->nickname, $sitename
);
$this->setSubtitle($subtitle);
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
$logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE);
$this->setLogo($logo);
$this->setUpdated('now');
$this->addLink(
common_local_url(
'showstream',
array('nickname' => $user->nickname)
)
);
$self = common_local_url('ApiTimelineUser',
array('id' => $user->id,
'format' => 'atom'));
$this->setId($self);
$this->setSelfLink($self);
$this->addLink(
common_local_url('sup', null, null, $user->id),
array(
'rel' => 'http://api.friendfeed.com/2008/03#sup',
'type' => 'application/json'
)
);
} }
function getUser() function getUser()
{ {
return $this->user; return $this->user;
} }
function showSource()
{
return false;
}
function showAuthor()
{
return false;
}
} }

View File

@ -40,7 +40,8 @@ $default =
'logdebug' => false, 'logdebug' => false,
'fancy' => false, 'fancy' => false,
'locale_path' => INSTALLDIR.'/locale', 'locale_path' => INSTALLDIR.'/locale',
'language' => 'en_US', 'language' => 'en',
'langdetect' => true,
'languages' => get_all_languages(), 'languages' => get_all_languages(),
'email' => 'email' =>
array_key_exists('SERVER_ADMIN', $_SERVER) ? $_SERVER['SERVER_ADMIN'] : null, array_key_exists('SERVER_ADMIN', $_SERVER) ? $_SERVER['SERVER_ADMIN'] : null,
@ -53,10 +54,11 @@ $default =
'ssl' => 'never', 'ssl' => 'never',
'sslserver' => null, 'sslserver' => null,
'shorturllength' => 30, 'shorturllength' => 30,
'dupelimit' => 60, # default for same person saying the same thing 'dupelimit' => 60, // default for same person saying the same thing
'textlimit' => 140, 'textlimit' => 140,
'indent' => true, 'indent' => true,
'use_x_sendfile' => false 'use_x_sendfile' => false,
'notice' => null // site wide notice text
), ),
'db' => 'db' =>
array('database' => 'YOU HAVE TO SET THIS IN config.php', array('database' => 'YOU HAVE TO SET THIS IN config.php',
@ -283,7 +285,7 @@ $default =
'OpenID' => null), 'OpenID' => null),
), ),
'admin' => 'admin' =>
array('panels' => array('design', 'site', 'user', 'paths', 'access', 'sessions')), array('panels' => array('design', 'site', 'user', 'paths', 'access', 'sessions', 'sitenotice')),
'singleuser' => 'singleuser' =>
array('enabled' => false, array('enabled' => false,
'nickname' => null), 'nickname' => null),

93
lib/grantroleform.php Normal file
View File

@ -0,0 +1,93 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Form for granting a role
*
* 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>, Brion Vibber <brion@status.net>
* @copyright 2009-2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
exit(1);
}
/**
* Form for sandboxing a user
*
* @category Form
* @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/
*
* @see UnSandboxForm
*/
class GrantRoleForm extends ProfileActionForm
{
function __construct($role, $label, $writer, $profile, $r2args)
{
parent::__construct($writer, $profile, $r2args);
$this->role = $role;
$this->label = $label;
}
/**
* Action this form provides
*
* @return string Name of the action, lowercased.
*/
function target()
{
return 'grantrole';
}
/**
* Title of the form
*
* @return string Title of the form, internationalized
*/
function title()
{
return $this->label;
}
function formData()
{
parent::formData();
$this->out->hidden('role', $this->role);
}
/**
* Description of the form
*
* @return string description of the form, internationalized
*/
function description()
{
return sprintf(_('Grant this user the "%s" role'), $this->label);
}
}

View File

@ -105,7 +105,7 @@ class ProfileAction extends OwnerDesignAction
$this->elementStart('div', array('id' => 'entity_subscriptions', $this->elementStart('div', array('id' => 'entity_subscriptions',
'class' => 'section')); 'class' => 'section'));
if (Event::handle('StartShowSubscriptionsMiniList', array($this))) {
$this->element('h2', null, _('Subscriptions')); $this->element('h2', null, _('Subscriptions'));
$cnt = 0; $cnt = 0;
@ -127,6 +127,8 @@ class ProfileAction extends OwnerDesignAction
$this->elementEnd('p'); $this->elementEnd('p');
} }
Event::handle('EndShowSubscriptionsMiniList', array($this));
}
$this->elementEnd('div'); $this->elementEnd('div');
} }
@ -226,7 +228,7 @@ class ProfileAction extends OwnerDesignAction
$this->elementStart('div', array('id' => 'entity_groups', $this->elementStart('div', array('id' => 'entity_groups',
'class' => 'section')); 'class' => 'section'));
if (Event::handle('StartShowGroupsMiniList', array($this))) {
$this->element('h2', null, _('Groups')); $this->element('h2', null, _('Groups'));
if ($groups) { if ($groups) {
@ -246,6 +248,8 @@ class ProfileAction extends OwnerDesignAction
$this->elementEnd('p'); $this->elementEnd('p');
} }
Event::handle('EndShowGroupsMiniList', array($this));
}
$this->elementEnd('div'); $this->elementEnd('div');
} }
} }

View File

@ -273,18 +273,12 @@ class ProfileListItem extends Widget
$usf = new UnsubscribeForm($this->out, $this->profile); $usf = new UnsubscribeForm($this->out, $this->profile);
$usf->show(); $usf->show();
} else { } else {
$other = User::staticGet('id', $this->profile->id); // We can't initiate sub for a remote OMB profile.
if (!empty($other)) { $remote = Remote_profile::staticGet('id', $this->profile->id);
if (empty($remote)) {
$sf = new SubscribeForm($this->out, $this->profile); $sf = new SubscribeForm($this->out, $this->profile);
$sf->show(); $sf->show();
} }
else {
$url = common_local_url('remotesubscribe',
array('nickname' => $this->profile->nickname));
$this->out->element('a', array('href' => $url,
'class' => 'entity_remote_subscribe'),
_('Subscribe'));
}
} }
$this->out->elementEnd('li'); $this->out->elementEnd('li');
} }

93
lib/revokeroleform.php Normal file
View File

@ -0,0 +1,93 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Form for revoking a role
*
* 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>, Brion Vibber <brion@status.net>
* @copyright 2009-2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
exit(1);
}
/**
* Form for sandboxing a user
*
* @category Form
* @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/
*
* @see UnSandboxForm
*/
class RevokeRoleForm extends ProfileActionForm
{
function __construct($role, $label, $writer, $profile, $r2args)
{
parent::__construct($writer, $profile, $r2args);
$this->role = $role;
$this->label = $label;
}
/**
* Action this form provides
*
* @return string Name of the action, lowercased.
*/
function target()
{
return 'revokerole';
}
/**
* Title of the form
*
* @return string Title of the form, internationalized
*/
function title()
{
return $this->label;
}
function formData()
{
parent::formData();
$this->out->hidden('role', $this->role);
}
/**
* Description of the form
*
* @return string description of the form, internationalized
*/
function description()
{
return sprintf(_('Revoke the "%s" role from this user'), $this->label);
}
}

View File

@ -58,5 +58,7 @@ class Right
const EMAILONSUBSCRIBE = 'emailonsubscribe'; const EMAILONSUBSCRIBE = 'emailonsubscribe';
const EMAILONFAVE = 'emailonfave'; const EMAILONFAVE = 'emailonfave';
const MAKEGROUPADMIN = 'makegroupadmin'; const MAKEGROUPADMIN = 'makegroupadmin';
const GRANTROLE = 'grantrole';
const REVOKEROLE = 'revokerole';
} }

View File

@ -98,6 +98,7 @@ class Router
'groupblock', 'groupunblock', 'groupblock', 'groupunblock',
'sandbox', 'unsandbox', 'sandbox', 'unsandbox',
'silence', 'unsilence', 'silence', 'unsilence',
'grantrole', 'revokerole',
'repeat', 'repeat',
'deleteuser', 'deleteuser',
'geocode', 'geocode',
@ -649,6 +650,8 @@ class Router
$m->connect('admin/access', array('action' => 'accessadminpanel')); $m->connect('admin/access', array('action' => 'accessadminpanel'));
$m->connect('admin/paths', array('action' => 'pathsadminpanel')); $m->connect('admin/paths', array('action' => 'pathsadminpanel'));
$m->connect('admin/sessions', array('action' => 'sessionsadminpanel')); $m->connect('admin/sessions', array('action' => 'sessionsadminpanel'));
$m->connect('admin/sitenotice', array('action' => 'sitenoticeadminpanel'));
$m->connect('admin/snapshot', array('action' => 'snapshotadminpanel'));
$m->connect('getfile/:filename', $m->connect('getfile/:filename',
array('action' => 'getfile'), array('action' => 'getfile'),

View File

@ -354,10 +354,10 @@ class StatusNet
class NoConfigException extends Exception class NoConfigException extends Exception
{ {
public $config_files; public $configFiles;
function __construct($msg, $config_files) { function __construct($msg, $configFiles) {
parent::__construct($msg); parent::__construct($msg);
$this->config_files = $config_files; $this->configFiles = $configFiles;
} }
} }

View File

@ -346,6 +346,16 @@ class UserProfile extends Widget
$this->out->elementEnd('ul'); $this->out->elementEnd('ul');
$this->out->elementEnd('li'); $this->out->elementEnd('li');
} }
if ($cur->hasRight(Right::GRANTROLE)) {
$this->out->elementStart('li', 'entity_role');
$this->out->element('p', null, _('User role'));
$this->out->elementStart('ul');
$this->roleButton('administrator', _m('role', 'Administrator'));
$this->roleButton('moderator', _m('role', 'Moderator'));
$this->out->elementEnd('ul');
$this->out->elementEnd('li');
}
} }
} }
@ -359,6 +369,22 @@ class UserProfile extends Widget
} }
} }
function roleButton($role, $label)
{
list($action, $r2args) = $this->out->returnToArgs();
$r2args['action'] = $action;
$this->out->elementStart('li', "entity_role_$role");
if ($this->user->hasRole($role)) {
$rf = new RevokeRoleForm($role, $label, $this->out, $this->profile, $r2args);
$rf->show();
} else {
$rf = new GrantRoleForm($role, $label, $this->out, $this->profile, $r2args);
$rf->show();
}
$this->out->elementEnd('li');
}
function showRemoteSubscribeLink() function showRemoteSubscribeLink()
{ {
$url = common_local_url('remotesubscribe', $url = common_local_url('remotesubscribe',

View File

@ -105,12 +105,14 @@ function common_language()
// Otherwise, find the best match for the languages requested by the // Otherwise, find the best match for the languages requested by the
// user's browser... // user's browser...
if (common_config('site', 'langdetect')) {
$httplang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : null; $httplang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : null;
if (!empty($httplang)) { if (!empty($httplang)) {
$language = client_prefered_language($httplang); $language = client_prefered_language($httplang);
if ($language) if ($language)
return $language; return $language;
} }
}
// Finally, if none of the above worked, use the site's default... // Finally, if none of the above worked, use the site's default...
return common_config('site', 'language'); return common_config('site', 'language');
@ -853,7 +855,7 @@ function common_valid_profile_tag($str)
function common_group_link($sender_id, $nickname) function common_group_link($sender_id, $nickname)
{ {
$sender = Profile::staticGet($sender_id); $sender = Profile::staticGet($sender_id);
$group = User_group::getForNickname($nickname); $group = User_group::getForNickname($nickname, $sender);
if ($sender && $group && $sender->isMember($group)) { if ($sender && $group && $sender->isMember($group)) {
$attrs = array('href' => $group->permalink(), $attrs = array('href' => $group->permalink(),
'class' => 'url'); 'class' => 'url');

View File

@ -79,6 +79,25 @@ class FacebookPlugin extends Plugin
} }
} }
/**
* Check to see if there is an API key and secret defined
* for Facebook integration.
*
* @return boolean result
*/
static function hasKeys()
{
$apiKey = common_config('facebook', 'apikey');
$apiSecret = common_config('facebook', 'secret');
if (!empty($apiKey) && !empty($apiSecret)) {
return true;
}
return false;
}
/** /**
* Add Facebook app actions to the router table * Add Facebook app actions to the router table
* *
@ -91,6 +110,9 @@ class FacebookPlugin extends Plugin
function onStartInitializeRouter($m) function onStartInitializeRouter($m)
{ {
$m->connect('admin/facebook', array('action' => 'facebookadminpanel'));
if (self::hasKeys()) {
// Facebook App stuff // Facebook App stuff
@ -100,7 +122,6 @@ class FacebookPlugin extends Plugin
array('action' => 'facebooksettings')); array('action' => 'facebooksettings'));
$m->connect('facebook/app/invite.php', array('action' => 'facebookinvite')); $m->connect('facebook/app/invite.php', array('action' => 'facebookinvite'));
$m->connect('facebook/app/remove', array('action' => 'facebookremove')); $m->connect('facebook/app/remove', array('action' => 'facebookremove'));
$m->connect('admin/facebook', array('action' => 'facebookadminpanel'));
// Facebook Connect stuff // Facebook Connect stuff
@ -108,6 +129,7 @@ class FacebookPlugin extends Plugin
$m->connect('main/facebooklogin', array('action' => 'FBConnectLogin')); $m->connect('main/facebooklogin', array('action' => 'FBConnectLogin'));
$m->connect('settings/facebook', array('action' => 'FBConnectSettings')); $m->connect('settings/facebook', array('action' => 'FBConnectSettings'));
$m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver')); $m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver'));
}
return true; return true;
} }
@ -338,6 +360,9 @@ class FacebookPlugin extends Plugin
function reqFbScripts($action) function reqFbScripts($action)
{ {
if (!self::hasKeys()) {
return false;
}
// If you're logged in w/FB Connect, you always need the FB stuff // If you're logged in w/FB Connect, you always need the FB stuff
@ -410,15 +435,8 @@ class FacebookPlugin extends Plugin
function onStartPrimaryNav($action) function onStartPrimaryNav($action)
{ {
if (self::hasKeys()) {
$user = common_current_user(); $user = common_current_user();
$connect = 'FBConnectSettings';
if (common_config('xmpp', 'enabled')) {
$connect = 'imsettings';
} else if (common_config('sms', 'enabled')) {
$connect = 'smssettings';
}
if (!empty($user)) { if (!empty($user)) {
$fbuid = $this->loggedIn(); $fbuid = $this->loggedIn();
@ -445,7 +463,7 @@ class FacebookPlugin extends Plugin
'src' => $iconurl)); 'src' => $iconurl));
$action->elementEnd('li'); $action->elementEnd('li');
}
} }
} }
@ -462,6 +480,7 @@ class FacebookPlugin extends Plugin
function onEndLoginGroupNav(&$action) function onEndLoginGroupNav(&$action)
{ {
if (self::hasKeys()) {
$action_name = $action->trimmed('action'); $action_name = $action->trimmed('action');
@ -469,7 +488,7 @@ class FacebookPlugin extends Plugin
_m('Facebook'), _m('Facebook'),
_m('Login or register using Facebook'), _m('Login or register using Facebook'),
'FBConnectLogin' === $action_name); 'FBConnectLogin' === $action_name);
}
return true; return true;
} }
@ -483,13 +502,15 @@ class FacebookPlugin extends Plugin
function onEndConnectSettingsNav(&$action) function onEndConnectSettingsNav(&$action)
{ {
if (self::hasKeys()) {
$action_name = $action->trimmed('action'); $action_name = $action->trimmed('action');
$action->menuItem(common_local_url('FBConnectSettings'), $action->menuItem(common_local_url('FBConnectSettings'),
_m('Facebook'), _m('Facebook'),
_m('Facebook Connect Settings'), _m('Facebook Connect Settings'),
$action_name === 'FBConnectSettings'); $action_name === 'FBConnectSettings');
}
return true; return true;
} }
@ -503,6 +524,8 @@ class FacebookPlugin extends Plugin
function onStartLogout($action) function onStartLogout($action)
{ {
if (self::hasKeys()) {
$action->logout(); $action->logout();
$fbuid = $this->loggedIn(); $fbuid = $this->loggedIn();
@ -516,7 +539,7 @@ class FacebookPlugin extends Plugin
$e->getMessage()); $e->getMessage());
} }
} }
}
return true; return true;
} }
@ -562,7 +585,9 @@ class FacebookPlugin extends Plugin
function onStartEnqueueNotice($notice, &$transports) function onStartEnqueueNotice($notice, &$transports)
{ {
if (self::hasKeys()) {
array_push($transports, 'facebook'); array_push($transports, 'facebook');
}
return true; return true;
} }
@ -575,7 +600,9 @@ class FacebookPlugin extends Plugin
*/ */
function onEndInitializeQueueManager($manager) function onEndInitializeQueueManager($manager)
{ {
if (self::hasKeys()) {
$manager->connect('facebook', 'FacebookQueueHandler'); $manager->connect('facebook', 'FacebookQueueHandler');
}
return true; return true;
} }

View File

@ -307,23 +307,14 @@ class MobileProfilePlugin extends WAP20Plugin
function _showPrimaryNav($action) function _showPrimaryNav($action)
{ {
$user = common_current_user(); $user = common_current_user();
$connect = '';
if (common_config('xmpp', 'enabled')) {
$connect = 'imsettings';
} else if (common_config('sms', 'enabled')) {
$connect = 'smssettings';
}
$action->elementStart('ul', array('id' => 'site_nav_global_primary')); $action->elementStart('ul', array('id' => 'site_nav_global_primary'));
if ($user) { if ($user) {
$action->menuItem(common_local_url('all', array('nickname' => $user->nickname)), $action->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
_('Home')); _('Home'));
$action->menuItem(common_local_url('profilesettings'), $action->menuItem(common_local_url('profilesettings'),
_('Account')); _('Account'));
if ($connect) { $action->menuItem(common_local_url('oauthconnectionssettings'),
$action->menuItem(common_local_url($connect),
_('Connect')); _('Connect'));
}
if ($user->hasRight(Right::CONFIGURESITE)) { if ($user->hasRight(Right::CONFIGURESITE)) {
$action->menuItem(common_local_url('siteadminpanel'), $action->menuItem(common_local_url('siteadminpanel'),
_('Admin'), _('Change site configuration'), false, 'nav_admin'); _('Admin'), _('Change site configuration'), false, 'nav_admin');

View File

@ -44,15 +44,19 @@ class OStatusPlugin extends Plugin
$m->connect('.well-known/host-meta', $m->connect('.well-known/host-meta',
array('action' => 'hostmeta')); array('action' => 'hostmeta'));
$m->connect('main/xrd', $m->connect('main/xrd',
array('action' => 'xrd')); array('action' => 'userxrd'));
$m->connect('main/ownerxrd',
array('action' => 'ownerxrd'));
$m->connect('main/ostatus', $m->connect('main/ostatus',
array('action' => 'ostatusinit')); array('action' => 'ostatusinit'));
$m->connect('main/ostatus?nickname=:nickname', $m->connect('main/ostatus?nickname=:nickname',
array('action' => 'ostatusinit'), array('nickname' => '[A-Za-z0-9_-]+')); array('action' => 'ostatusinit'), array('nickname' => '[A-Za-z0-9_-]+'));
$m->connect('main/ostatus?group=:group',
array('action' => 'ostatusinit'), array('group' => '[A-Za-z0-9_-]+'));
$m->connect('main/ostatussub', $m->connect('main/ostatussub',
array('action' => 'ostatussub')); array('action' => 'ostatussub'));
$m->connect('main/ostatussub', $m->connect('main/ostatusgroup',
array('action' => 'ostatussub'), array('feed' => '[A-Za-z0-9\.\/\:]+')); array('action' => 'ostatusgroup'));
// PuSH actions // PuSH actions
$m->connect('main/push/hub', array('action' => 'pushhub')); $m->connect('main/push/hub', array('action' => 'pushhub'));
@ -109,7 +113,7 @@ class OStatusPlugin extends Plugin
{ {
if ($action instanceof ShowstreamAction) { if ($action instanceof ShowstreamAction) {
$acct = 'acct:'. $action->profile->nickname .'@'. common_config('site', 'server'); $acct = 'acct:'. $action->profile->nickname .'@'. common_config('site', 'server');
$url = common_local_url('xrd'); $url = common_local_url('userxrd');
$url.= '?uri='. $acct; $url.= '?uri='. $acct;
header('Link: <'.$url.'>; rel="'. Discovery::LRDD_REL.'"; type="application/xrd+xml"'); header('Link: <'.$url.'>; rel="'. Discovery::LRDD_REL.'"; type="application/xrd+xml"');
@ -210,6 +214,22 @@ class OStatusPlugin extends Plugin
return false; return false;
} }
function onStartGroupSubscribe($output, $group)
{
$cur = common_current_user();
if (empty($cur)) {
// Add an OStatus subscribe
$url = common_local_url('ostatusinit',
array('group' => $group->nickname));
$output->element('a', array('href' => $url,
'class' => 'entity_remote_subscribe'),
_m('Join'));
}
return true;
}
/** /**
* Check if we've got remote replies to send via Salmon. * Check if we've got remote replies to send via Salmon.
* *
@ -233,69 +253,69 @@ class OStatusPlugin extends Plugin
function onEndFindMentions($sender, $text, &$mentions) function onEndFindMentions($sender, $text, &$mentions)
{ {
preg_match_all('!(?:^|\s+) $matches = array();
@( # Webfinger:
(?:\w+\.)*\w+ # user // Webfinger matches: @user@example.com
@ # @ if (preg_match_all('!(?:^|\s+)@((?:\w+\.)*\w+@(?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+)!',
(?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+ # domain
| # Profile:
(?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+ # domain
(?:/\w+)+ # /path1(/path2...)
)!x',
$text, $text,
$wmatches, $wmatches,
PREG_OFFSET_CAPTURE); PREG_OFFSET_CAPTURE)) {
foreach ($wmatches[1] as $wmatch) { foreach ($wmatches[1] as $wmatch) {
$target = $wmatch[0]; list($target, $pos) = $wmatch;
$oprofile = null; $this->log(LOG_INFO, "Checking webfinger '$target'");
if (strpos($target, '/') === false) {
$this->log(LOG_INFO, "Checking Webfinger for address '$target'");
try { try {
$oprofile = Ostatus_profile::ensureWebfinger($target); $oprofile = Ostatus_profile::ensureWebfinger($target);
if ($oprofile && !$oprofile->isGroup()) {
$profile = $oprofile->localProfile();
$matches[$pos] = array('mentioned' => array($profile),
'text' => $target,
'position' => $pos,
'url' => $profile->profileurl);
}
} catch (Exception $e) { } catch (Exception $e) {
$this->log(LOG_ERR, "Webfinger check failed: " . $e->getMessage()); $this->log(LOG_ERR, "Webfinger check failed: " . $e->getMessage());
} }
} else { }
$schemes = array('https', 'http'); }
// Profile matches: @example.com/mublog/user
if (preg_match_all('!(?:^|\s+)@((?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+(?:/\w+)+)!',
$text,
$wmatches,
PREG_OFFSET_CAPTURE)) {
foreach ($wmatches[1] as $wmatch) {
list($target, $pos) = $wmatch;
$schemes = array('http', 'https');
foreach ($schemes as $scheme) { foreach ($schemes as $scheme) {
$url = "$scheme://$target"; $url = "$scheme://$target";
$this->log(LOG_INFO, "Checking profile address '$url'"); $this->log(LOG_INFO, "Checking profile address '$url'");
try { try {
$oprofile = Ostatus_profile::ensureProfile($url); $oprofile = Ostatus_profile::ensureProfile($url);
if ($oprofile) { if ($oprofile && !$oprofile->isGroup()) {
continue; $profile = $oprofile->localProfile();
$matches[$pos] = array('mentioned' => array($profile),
'text' => $target,
'position' => $pos,
'url' => $profile->profileurl);
break;
} }
} catch (Exception $e) { } catch (Exception $e) {
$this->log(LOG_ERR, "Profile check failed: " . $e->getMessage()); $this->log(LOG_ERR, "Profile check failed: " . $e->getMessage());
} }
} }
} }
if (empty($oprofile)) {
$this->log(LOG_INFO, "No Ostatus_profile found for address '$target'");
} else {
$this->log(LOG_INFO, "Ostatus_profile found for address '$target'");
if ($oprofile->isGroup()) {
continue;
} }
$profile = $oprofile->localProfile();
$pos = $wmatch[1];
foreach ($mentions as $i => $other) { foreach ($mentions as $i => $other) {
// If we share a common prefix with a local user, override it! // If we share a common prefix with a local user, override it!
if ($other['position'] == $pos) { $pos = $other['position'];
unset($mentions[$i]); if (isset($matches[$pos])) {
$mentions[$i] = $matches[$pos];
unset($matches[$pos]);
} }
} }
$mentions[] = array('mentioned' => array($profile), foreach ($matches as $mention) {
'text' => $target, $mentions[] = $mention;
'position' => $pos,
'url' => $profile->profileurl);
}
} }
return true; return true;
@ -567,7 +587,6 @@ class OStatusPlugin extends Plugin
// Drop the PuSH subscription if there are no other subscribers. // Drop the PuSH subscription if there are no other subscribers.
$oprofile->garbageCollect(); $oprofile->garbageCollect();
$member = Profile::staticGet($user->id); $member = Profile::staticGet($user->id);
$act = new Activity(); $act = new Activity();
@ -711,23 +730,37 @@ class OStatusPlugin extends Plugin
return true; return true;
} }
function onStartShowAllContent($action) function onStartShowUserGroupsContent($action)
{
$this->showEntityRemoteSubscribe($action, 'ostatusgroup');
return true;
}
function onEndShowSubscriptionsMiniList($action)
{ {
$this->showEntityRemoteSubscribe($action); $this->showEntityRemoteSubscribe($action);
return true; return true;
} }
function showEntityRemoteSubscribe($action) function onEndShowGroupsMiniList($action)
{
$this->showEntityRemoteSubscribe($action, 'ostatusgroup');
return true;
}
function showEntityRemoteSubscribe($action, $target='ostatussub')
{ {
$user = common_current_user(); $user = common_current_user();
if ($user && ($user->id == $action->profile->id)) { if ($user && ($user->id == $action->profile->id)) {
$action->elementStart('div', 'entity_actions'); $action->elementStart('div', 'entity_actions');
$action->elementStart('p', array('id' => 'entity_remote_subscribe', $action->elementStart('p', array('id' => 'entity_remote_subscribe',
'class' => 'entity_subscribe')); 'class' => 'entity_subscribe'));
$action->element('a', array('href' => common_local_url('ostatussub'), $action->element('a', array('href' => common_local_url($target),
'class' => 'entity_remote_subscribe') 'class' => 'entity_remote_subscribe')
, _m('Subscribe to remote user')); , _m('Remote'));
$action->elementEnd('p'); $action->elementEnd('p');
$action->elementEnd('div'); $action->elementEnd('div');
} }
@ -779,4 +812,28 @@ class OStatusPlugin extends Plugin
return true; return true;
} }
function onStartProfileListItemActionElements($item)
{
if (!common_logged_in()) {
$profileUser = User::staticGet('id', $item->profile->id);
if (!empty($profileUser)) {
$output = $item->out;
// Add an OStatus subscribe
$output->elementStart('li', 'entity_subscribe');
$url = common_local_url('ostatusinit',
array('nickname' => $profileUser->nickname));
$output->element('a', array('href' => $url,
'class' => 'entity_remote_subscribe'),
_m('Subscribe'));
$output->elementEnd('li');
}
}
return true;
}
} }

View File

@ -32,7 +32,7 @@ class HostMetaAction extends Action
parent::handle(); parent::handle();
$domain = common_config('site', 'server'); $domain = common_config('site', 'server');
$url = common_local_url('xrd'); $url = common_local_url('userxrd');
$url.= '?uri={uri}'; $url.= '?uri={uri}';
$xrd = new XRD(); $xrd = new XRD();

View File

@ -0,0 +1,181 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2009-2010, StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @package OStatusPlugin
* @maintainer Brion Vibber <brion@status.net>
*/
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
/**
* Key UI methods:
*
* showInputForm() - form asking for a remote profile account or URL
* We end up back here on errors
*
* showPreviewForm() - surrounding form for preview-and-confirm
* preview() - display profile for a remote group
*
* success() - redirects to groups page on join
*/
class OStatusGroupAction extends OStatusSubAction
{
protected $profile_uri; // provided acct: or URI of remote entity
protected $oprofile; // Ostatus_profile of remote entity, if valid
function validateRemoteProfile()
{
if (!$this->oprofile->isGroup()) {
// Send us to the user subscription form for conf
$target = common_local_url('ostatussub', array(), array('profile' => $this->profile_uri));
common_redirect($target, 303);
}
}
/**
* Show the initial form, when we haven't yet been given a valid
* remote profile.
*/
function showInputForm()
{
$user = common_current_user();
$profile = $user->getProfile();
$this->elementStart('form', array('method' => 'post',
'id' => 'form_ostatus_sub',
'class' => 'form_settings',
'action' => $this->selfLink()));
$this->hidden('token', common_session_token());
$this->elementStart('fieldset', array('id' => 'settings_feeds'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->input('profile',
_m('Join group'),
$this->profile_uri,
_m("OStatus group's address, like http://example.net/group/nickname"));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('validate', _m('Continue'));
$this->elementEnd('fieldset');
$this->elementEnd('form');
}
/**
* Show a preview for a remote group's profile
* @return boolean true if we're ok to try joining
*/
function preview()
{
$oprofile = $this->oprofile;
$group = $oprofile->localGroup();
$cur = common_current_user();
if ($cur->isMember($group)) {
$this->element('div', array('class' => 'error'),
_m("You are already a member of this group."));
$ok = false;
} else {
$ok = true;
}
$this->showEntity($group,
$group->getProfileUrl(),
$group->homepage_logo,
$group->description);
return $ok;
}
/**
* Redirect on successful remote group join
*/
function success()
{
$cur = common_current_user();
$url = common_local_url('usergroups', array('nickname' => $cur->nickname));
common_redirect($url, 303);
}
/**
* Attempt to finalize subscription.
* validateFeed must have been run first.
*
* Calls showForm on failure or success on success.
*/
function saveFeed()
{
$user = common_current_user();
$group = $this->oprofile->localGroup();
if ($user->isMember($group)) {
// TRANS: OStatus remote group subscription dialog error.
$this->showForm(_m('Already a member!'));
return;
}
if (Event::handle('StartJoinGroup', array($group, $user))) {
$ok = Group_member::join($this->oprofile->group_id, $user->id);
if ($ok) {
Event::handle('EndJoinGroup', array($group, $user));
$this->success();
} else {
// TRANS: OStatus remote group subscription dialog error.
$this->showForm(_m('Remote group join failed!'));
}
} else {
// TRANS: OStatus remote group subscription dialog error.
$this->showForm(_m('Remote group join aborted!'));
}
}
/**
* Title of the page
*
* @return string Title of the page
*/
function title()
{
// TRANS: Page title for OStatus remote group join form
return _m('Confirm joining remote group');
}
/**
* Instructions for use
*
* @return instructions for use
*/
function getInstructions()
{
return _m('You can subscribe to groups from other supported sites. Paste the group\'s profile URI below:');
}
function selfLink()
{
return common_local_url('ostatusgroup');
}
}

View File

@ -29,6 +29,7 @@ class OStatusInitAction extends Action
{ {
var $nickname; var $nickname;
var $group;
var $profile; var $profile;
var $err; var $err;
@ -41,8 +42,9 @@ class OStatusInitAction extends Action
return false; return false;
} }
// Local user the remote wants to subscribe to // Local user or group the remote wants to subscribe to
$this->nickname = $this->trimmed('nickname'); $this->nickname = $this->trimmed('nickname');
$this->group = $this->trimmed('group');
// Webfinger or profile URL of the remote user // Webfinger or profile URL of the remote user
$this->profile = $this->trimmed('profile'); $this->profile = $this->trimmed('profile');
@ -89,25 +91,33 @@ class OStatusInitAction extends Action
function showContent() function showContent()
{ {
if ($this->group) {
$header = sprintf(_m('Join group %s'), $this->group);
$submit = _m('Join');
} else {
$header = sprintf(_m('Subscribe to %s'), $this->nickname);
$submit = _m('Subscribe');
}
$this->elementStart('form', array('id' => 'form_ostatus_connect', $this->elementStart('form', array('id' => 'form_ostatus_connect',
'method' => 'post', 'method' => 'post',
'class' => 'form_settings', 'class' => 'form_settings',
'action' => common_local_url('ostatusinit'))); 'action' => common_local_url('ostatusinit')));
$this->elementStart('fieldset'); $this->elementStart('fieldset');
$this->element('legend', null, sprintf(_m('Subscribe to %s'), $this->nickname)); $this->element('legend', null, $header);
$this->hidden('token', common_session_token()); $this->hidden('token', common_session_token());
$this->elementStart('ul', 'form_data'); $this->elementStart('ul', 'form_data');
$this->elementStart('li', array('id' => 'ostatus_nickname')); $this->elementStart('li', array('id' => 'ostatus_nickname'));
$this->input('nickname', _m('User nickname'), $this->nickname, $this->input('nickname', _m('User nickname'), $this->nickname,
_m('Nickname of the user you want to follow')); _m('Nickname of the user you want to follow'));
$this->hidden('group', $this->group); // pass-through for magic links
$this->elementEnd('li'); $this->elementEnd('li');
$this->elementStart('li', array('id' => 'ostatus_profile')); $this->elementStart('li', array('id' => 'ostatus_profile'));
$this->input('profile', _m('Profile Account'), $this->profile, $this->input('profile', _m('Profile Account'), $this->profile,
_m('Your account id (i.e. user@identi.ca)')); _m('Your account id (i.e. user@identi.ca)'));
$this->elementEnd('li'); $this->elementEnd('li');
$this->elementEnd('ul'); $this->elementEnd('ul');
$this->submit('submit', _m('Subscribe')); $this->submit('submit', $submit);
$this->elementEnd('fieldset'); $this->elementEnd('fieldset');
$this->elementEnd('form'); $this->elementEnd('form');
} }
@ -131,19 +141,17 @@ class OStatusInitAction extends Action
function connectWebfinger($acct) function connectWebfinger($acct)
{ {
$disco = new Discovery; $target_profile = $this->targetProfile();
$disco = new Discovery;
$result = $disco->lookup($acct); $result = $disco->lookup($acct);
if (!$result) { if (!$result) {
$this->clientError(_m("Couldn't look up OStatus account profile.")); $this->clientError(_m("Couldn't look up OStatus account profile."));
} }
foreach ($result->links as $link) { foreach ($result->links as $link) {
if ($link['rel'] == 'http://ostatus.org/schema/1.0/subscribe') { if ($link['rel'] == 'http://ostatus.org/schema/1.0/subscribe') {
// We found a URL - let's redirect! // We found a URL - let's redirect!
$user = User::staticGet('nickname', $this->nickname);
$target_profile = common_local_url('userbyid', array('id' => $user->id));
$url = Discovery::applyTemplate($link['template'], $target_profile); $url = Discovery::applyTemplate($link['template'], $target_profile);
common_log(LOG_INFO, "Sending remote subscriber $acct to $url"); common_log(LOG_INFO, "Sending remote subscriber $acct to $url");
common_redirect($url, 303); common_redirect($url, 303);
@ -155,8 +163,7 @@ class OStatusInitAction extends Action
function connectProfile($subscriber_profile) function connectProfile($subscriber_profile)
{ {
$user = User::staticGet('nickname', $this->nickname); $target_profile = $this->targetProfile();
$target_profile = common_local_url('userbyid', array('id' => $user->id));
// @fixme hack hack! We should look up the remote sub URL from XRDS // @fixme hack hack! We should look up the remote sub URL from XRDS
$suburl = preg_replace('!^(.*)/(.*?)$!', '$1/main/ostatussub', $subscriber_profile); $suburl = preg_replace('!^(.*)/(.*?)$!', '$1/main/ostatussub', $subscriber_profile);
@ -166,6 +173,30 @@ class OStatusInitAction extends Action
common_redirect($suburl, 303); common_redirect($suburl, 303);
} }
/**
* Build the canonical profile URI+URL of the requested user or group
*/
function targetProfile()
{
if ($this->nickname) {
$user = User::staticGet('nickname', $this->nickname);
if ($user) {
return common_local_url('userbyid', array('id' => $user->id));
} else {
$this->clientError("No such user.");
}
} else if ($this->group) {
$group = Local_group::staticGet('id', $this->group);
if ($group) {
return common_local_url('groupbyid', array('id' => $group->group_id));
} else {
$this->clientError("No such group.");
}
} else {
$this->clientError("No local user or group nickname provided.");
}
}
function title() function title()
{ {
return _m('OStatus Connect'); return _m('OStatus Connect');

View File

@ -1,7 +1,7 @@
<?php <?php
/* /*
* StatusNet - the distributed open-source microblogging tool * StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2009, StatusNet, Inc. * Copyright (C) 2009-2010, StatusNet, Inc.
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU Affero General Public License as published by
@ -31,11 +31,9 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
* We end up back here on errors * We end up back here on errors
* *
* showPreviewForm() - surrounding form for preview-and-confirm * showPreviewForm() - surrounding form for preview-and-confirm
* previewUser() - display profile for a remote user * preview() - display profile for a remote user
* previewGroup() - display profile for a remote group
* *
* successUser() - redirects to subscriptions page on subscribe * success() - redirects to subscriptions page on subscribe
* successGroup() - redirects to groups page on join
*/ */
class OStatusSubAction extends Action class OStatusSubAction extends Action
{ {
@ -55,8 +53,7 @@ class OStatusSubAction extends Action
$this->elementStart('form', array('method' => 'post', $this->elementStart('form', array('method' => 'post',
'id' => 'form_ostatus_sub', 'id' => 'form_ostatus_sub',
'class' => 'form_settings', 'class' => 'form_settings',
'action' => 'action' => $this->selfLink()));
common_local_url('ostatussub')));
$this->hidden('token', common_session_token()); $this->hidden('token', common_session_token());
@ -65,9 +62,9 @@ class OStatusSubAction extends Action
$this->elementStart('ul', 'form_data'); $this->elementStart('ul', 'form_data');
$this->elementStart('li'); $this->elementStart('li');
$this->input('profile', $this->input('profile',
_m('Address or profile URL'), _m('Subscribe to'),
$this->profile_uri, $this->profile_uri,
_m('Enter the profile URL of a PubSubHubbub-enabled feed')); _m("OStatus user's address, like nickname@example.com or http://example.net/nickname"));
$this->elementEnd('li'); $this->elementEnd('li');
$this->elementEnd('ul'); $this->elementEnd('ul');
@ -87,11 +84,7 @@ class OStatusSubAction extends Action
*/ */
function showPreviewForm() function showPreviewForm()
{ {
if ($this->oprofile->isGroup()) { $ok = $this->preview();
$ok = $this->previewGroup();
} else {
$ok = $this->previewUser();
}
if (!$ok) { if (!$ok) {
// @fixme maybe provide a cancel button or link back? // @fixme maybe provide a cancel button or link back?
return; return;
@ -104,7 +97,7 @@ class OStatusSubAction extends Action
'id' => 'form_ostatus_sub', 'id' => 'form_ostatus_sub',
'class' => 'form_remote_authorize', 'class' => 'form_remote_authorize',
'action' => 'action' =>
common_local_url('ostatussub'))); $this->selfLink()));
$this->elementStart('fieldset'); $this->elementStart('fieldset');
$this->hidden('token', common_session_token()); $this->hidden('token', common_session_token());
$this->hidden('profile', $this->profile_uri); $this->hidden('profile', $this->profile_uri);
@ -126,7 +119,7 @@ class OStatusSubAction extends Action
* Show a preview for a remote user's profile * Show a preview for a remote user's profile
* @return boolean true if we're ok to try subscribing * @return boolean true if we're ok to try subscribing
*/ */
function previewUser() function preview()
{ {
$oprofile = $this->oprofile; $oprofile = $this->oprofile;
$profile = $oprofile->localProfile(); $profile = $oprofile->localProfile();
@ -150,32 +143,6 @@ class OStatusSubAction extends Action
return $ok; return $ok;
} }
/**
* Show a preview for a remote group's profile
* @return boolean true if we're ok to try joining
*/
function previewGroup()
{
$oprofile = $this->oprofile;
$group = $oprofile->localGroup();
$cur = common_current_user();
if ($cur->isMember($group)) {
$this->element('div', array('class' => 'error'),
_m("You are already a member of this group."));
$ok = false;
} else {
$ok = true;
}
$this->showEntity($group,
$group->getProfileUrl(),
$group->homepage_logo,
$group->description);
return $ok;
}
function showEntity($entity, $profile, $avatar, $note) function showEntity($entity, $profile, $avatar, $note)
{ {
$nickname = $entity->nickname; $nickname = $entity->nickname;
@ -254,23 +221,13 @@ class OStatusSubAction extends Action
/** /**
* Redirect on successful remote user subscription * Redirect on successful remote user subscription
*/ */
function successUser() function success()
{ {
$cur = common_current_user(); $cur = common_current_user();
$url = common_local_url('subscriptions', array('nickname' => $cur->nickname)); $url = common_local_url('subscriptions', array('nickname' => $cur->nickname));
common_redirect($url, 303); common_redirect($url, 303);
} }
/**
* Redirect on successful remote group join
*/
function successGroup()
{
$cur = common_current_user();
$url = common_local_url('usergroups', array('nickname' => $cur->nickname));
common_redirect($url, 303);
}
/** /**
* Pull data for a remote profile and check if it's valid. * Pull data for a remote profile and check if it's valid.
* Fills out error UI string in $this->error * Fills out error UI string in $this->error
@ -278,90 +235,77 @@ class OStatusSubAction extends Action
* *
* @return boolean * @return boolean
*/ */
function validateFeed() function pullRemoteProfile()
{ {
$profile_uri = trim($this->arg('profile')); $this->profile_uri = $this->trimmed('profile');
if ($profile_uri == '') {
$this->showForm(_m('Empty remote profile URL!'));
return;
}
$this->profile_uri = $profile_uri;
try { try {
if (Validate::email($this->profile_uri)) { if (Validate::email($this->profile_uri)) {
$this->oprofile = Ostatus_profile::ensureWebfinger($this->profile_uri); $this->oprofile = Ostatus_profile::ensureWebfinger($this->profile_uri);
} else if (Validate::uri($this->profile_uri)) { } else if (Validate::uri($this->profile_uri)) {
$this->oprofile = Ostatus_profile::ensureProfile($this->profile_uri); $this->oprofile = Ostatus_profile::ensureProfile($this->profile_uri);
} else { } else {
$this->error = _m("Invalid address format."); $this->error = _m("Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname");
common_debug('Invalid address format.', __FILE__);
return false; return false;
} }
return true; return true;
} catch (FeedSubBadURLException $e) { } catch (FeedSubBadURLException $e) {
$this->error = _m('Invalid URL or could not reach server.'); $this->error = _m("Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname");
common_debug('Invalid URL or could not reach server.', __FILE__);
} catch (FeedSubBadResponseException $e) { } catch (FeedSubBadResponseException $e) {
$this->error = _m('Cannot read feed; server returned error.'); $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
common_debug('Cannot read feed; server returned error.', __FILE__);
} catch (FeedSubEmptyException $e) { } catch (FeedSubEmptyException $e) {
$this->error = _m('Cannot read feed; server returned an empty page.'); $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
common_debug('Cannot read feed; server returned an empty page.', __FILE__);
} catch (FeedSubBadHTMLException $e) { } catch (FeedSubBadHTMLException $e) {
$this->error = _m('Bad HTML, could not find feed link.'); $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
common_debug('Bad HTML, could not find feed link.', __FILE__);
} catch (FeedSubNoFeedException $e) { } catch (FeedSubNoFeedException $e) {
$this->error = _m('Could not find a feed linked from this URL.'); $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
common_debug('Could not find a feed linked from this URL.', __FILE__);
} catch (FeedSubUnrecognizedTypeException $e) { } catch (FeedSubUnrecognizedTypeException $e) {
$this->error = _m('Not a recognized feed type.'); $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
} catch (FeedSubException $e) { common_debug('Not a recognized feed type.', __FILE__);
} catch (Exception $e) {
// Any new ones we forgot about // Any new ones we forgot about
$this->error = sprintf(_m('Bad feed URL: %s %s'), get_class($e), $e->getMessage()); $this->error = _m("Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname");
common_debug(sprintf('Bad feed URL: %s %s', get_class($e), $e->getMessage()), __FILE__);
} }
return false; return false;
} }
function validateRemoteProfile()
{
if ($this->oprofile->isGroup()) {
// Send us to the group subscription form for conf
$target = common_local_url('ostatusgroup', array(), array('profile' => $this->profile_uri));
common_redirect($target, 303);
}
}
/** /**
* Attempt to finalize subscription. * Attempt to finalize subscription.
* validateFeed must have been run first. * validateFeed must have been run first.
* *
* Calls showForm on failure or successUser/successGroup on success. * Calls showForm on failure or success on success.
*/ */
function saveFeed() function saveFeed()
{ {
// And subscribe the current user to the local profile // And subscribe the current user to the local profile
$user = common_current_user(); $user = common_current_user();
if ($this->oprofile->isGroup()) {
$group = $this->oprofile->localGroup();
if ($user->isMember($group)) {
// TRANS: OStatus remote group subscription dialog error.
$this->showForm(_m('Already a member!'));
return;
}
if (Event::handle('StartJoinGroup', array($group, $user))) {
$ok = Group_member::join($this->oprofile->group_id, $user->id);
if ($ok) {
Event::handle('EndJoinGroup', array($group, $user));
$this->successGroup();
} else {
// TRANS: OStatus remote group subscription dialog error.
$this->showForm(_m('Remote group join failed!'));
}
} else {
// TRANS: OStatus remote group subscription dialog error.
$this->showForm(_m('Remote group join aborted!'));
}
} else {
$local = $this->oprofile->localProfile(); $local = $this->oprofile->localProfile();
if ($user->isSubscribed($local)) { if ($user->isSubscribed($local)) {
// TRANS: OStatus remote subscription dialog error. // TRANS: OStatus remote subscription dialog error.
$this->showForm(_m('Already subscribed!')); $this->showForm(_m('Already subscribed!'));
} elseif ($this->oprofile->subscribeLocalToRemote($user)) { } elseif ($this->oprofile->subscribeLocalToRemote($user)) {
$this->successUser(); $this->success();
} else { } else {
// TRANS: OStatus remote subscription dialog error. // TRANS: OStatus remote subscription dialog error.
$this->showForm(_m('Remote subscription failed!')); $this->showForm(_m('Remote subscription failed!'));
} }
} }
}
function prepare($args) function prepare($args)
{ {
@ -376,8 +320,9 @@ class OStatusSubAction extends Action
return false; return false;
} }
$this->profile_uri = $this->arg('profile'); if ($this->pullRemoteProfile()) {
$this->validateRemoteProfile();
}
return true; return true;
} }
@ -390,9 +335,6 @@ class OStatusSubAction extends Action
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->handlePost(); $this->handlePost();
} else { } else {
if ($this->arg('profile')) {
$this->validateFeed();
}
$this->showForm(); $this->showForm();
} }
} }
@ -414,7 +356,7 @@ class OStatusSubAction extends Action
return; return;
} }
if ($this->validateFeed()) { if ($this->oprofile) {
if ($this->arg('submit')) { if ($this->arg('submit')) {
$this->saveFeed(); $this->saveFeed();
return; return;
@ -456,7 +398,7 @@ class OStatusSubAction extends Action
function title() function title()
{ {
// TRANS: Page title for OStatus remote subscription form // TRANS: Page title for OStatus remote subscription form
return _m('Authorize subscription'); return _m('Confirm');
} }
/** /**
@ -500,4 +442,9 @@ class OStatusSubAction extends Action
parent::showScripts(); parent::showScripts();
$this->autofocus('feedurl'); $this->autofocus('feedurl');
} }
function selfLink()
{
return common_local_url('ostatussub');
}
} }

View File

@ -0,0 +1,56 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @package OStatusPlugin
* @maintainer James Walker <james@status.net>
*/
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
class OwnerxrdAction extends XrdAction
{
public $uri;
function prepare($args)
{
$this->user = User::siteOwner();
if (!$this->user) {
$this->clientError(_('No such user.'), 404);
return false;
}
$nick = common_canonical_nickname($this->user->nickname);
$acct = 'acct:' . $nick . '@' . common_config('site', 'server');
$this->xrd = new XRD();
// Check to see if a $config['webfinger']['owner'] has been set
if ($owner = common_config('webfinger', 'owner')) {
$this->xrd->subject = Discovery::normalize($owner);
$this->xrd->alias[] = $acct;
} else {
$this->xrd->subject = $acct;
}
return true;
}
}

View File

@ -0,0 +1,48 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @package OStatusPlugin
* @maintainer James Walker <james@status.net>
*/
if (!defined('STATUSNET')) { exit(1); }
class UserxrdAction extends XrdAction
{
function prepare($args)
{
parent::prepare($args);
$this->uri = $this->trimmed('uri');
$acct = Discovery::normalize($this->uri);
list($nick, $domain) = explode('@', substr(urldecode($acct), 5));
$nick = common_canonical_nickname($nick);
$this->user = User::staticGet('nickname', $nick);
if (!$this->user) {
$this->clientError(_('No such user.'), 404);
return false;
}
return true;
}
}

View File

@ -550,7 +550,8 @@ class Ostatus_profile extends Memcached_DataObject
'rendered' => $rendered, 'rendered' => $rendered,
'replies' => array(), 'replies' => array(),
'groups' => array(), 'groups' => array(),
'tags' => array()); 'tags' => array(),
'urls' => array());
// Check for optional attributes... // Check for optional attributes...
@ -595,6 +596,12 @@ class Ostatus_profile extends Memcached_DataObject
} }
} }
// Atom enclosures -> attachment URLs
foreach ($activity->enclosures as $href) {
// @fixme save these locally or....?
$options['urls'][] = $href;
}
try { try {
$saved = Notice::saveNew($oprofile->profile_id, $saved = Notice::saveNew($oprofile->profile_id,
$content, $content,
@ -620,7 +627,8 @@ class Ostatus_profile extends Memcached_DataObject
protected function purify($html) protected function purify($html)
{ {
require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php'; require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php';
$config = array('safe' => 1); $config = array('safe' => 1,
'deny_attribute' => 'id,style,on*');
return htmLawed($html, $config); return htmLawed($html, $config);
} }
@ -1259,6 +1267,11 @@ class Ostatus_profile extends Memcached_DataObject
} }
} }
/**
* @param string $addr webfinger address
* @return Ostatus_profile
* @throws Exception on error conditions
*/
public static function ensureWebfinger($addr) public static function ensureWebfinger($addr)
{ {
// First, try the cache // First, try the cache
@ -1267,7 +1280,8 @@ class Ostatus_profile extends Memcached_DataObject
if ($uri !== false) { if ($uri !== false) {
if (is_null($uri)) { if (is_null($uri)) {
return null; // Negative cache entry
throw new Exception('Not a valid webfinger address.');
} }
$oprofile = Ostatus_profile::staticGet('uri', $uri); $oprofile = Ostatus_profile::staticGet('uri', $uri);
if (!empty($oprofile)) { if (!empty($oprofile)) {
@ -1291,20 +1305,24 @@ class Ostatus_profile extends Memcached_DataObject
try { try {
$result = $disco->lookup($addr); $result = $disco->lookup($addr);
} catch (Exception $e) { } catch (Exception $e) {
// Save negative cache entry so we don't waste time looking it up again.
// @fixme distinguish temporary failures?
self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), null); self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), null);
return null; throw new Exception('Not a valid webfinger address.');
} }
$hints = array('webfinger' => $addr);
foreach ($result->links as $link) { foreach ($result->links as $link) {
switch ($link['rel']) { switch ($link['rel']) {
case Discovery::PROFILEPAGE: case Discovery::PROFILEPAGE:
$profileUrl = $link['href']; $hints['profileurl'] = $profileUrl = $link['href'];
break; break;
case Salmon::NS_REPLIES: case Salmon::NS_REPLIES:
$salmonEndpoint = $link['href']; $hints['salmon'] = $salmonEndpoint = $link['href'];
break; break;
case Discovery::UPDATESFROM: case Discovery::UPDATESFROM:
$feedUrl = $link['href']; $hints['feedurl'] = $feedUrl = $link['href'];
break; break;
case Discovery::HCARD: case Discovery::HCARD:
$hcardUrl = $link['href']; $hcardUrl = $link['href'];
@ -1315,11 +1333,6 @@ class Ostatus_profile extends Memcached_DataObject
} }
} }
$hints = array('webfinger' => $addr,
'profileurl' => $profileUrl,
'feedurl' => $feedUrl,
'salmon' => $salmonEndpoint);
if (isset($hcardUrl)) { if (isset($hcardUrl)) {
$hcardHints = self::slurpHcard($hcardUrl); $hcardHints = self::slurpHcard($hcardUrl);
// Note: Webfinger > hcard // Note: Webfinger > hcard
@ -1402,7 +1415,7 @@ class Ostatus_profile extends Memcached_DataObject
return $oprofile; return $oprofile;
} }
return null; throw new Exception("Couldn't find a valid profile for '$addr'");
} }
function saveHTMLFile($title, $rendered) function saveHTMLFile($title, $rendered)
@ -1492,7 +1505,7 @@ class Ostatus_profile extends Memcached_DataObject
if (array_key_exists('url', $hcard)) { if (array_key_exists('url', $hcard)) {
if (is_string($hcard['url'])) { if (is_string($hcard['url'])) {
$hints['homepage'] = $hcard['url']; $hints['homepage'] = $hcard['url'];
} else if (is_array($hcard['adr'])) { } else if (is_array($hcard['url'])) {
// HACK get the last one; that's how our hcards look // HACK get the last one; that's how our hcards look
$hints['homepage'] = $hcard['url'][count($hcard['url'])-1]; $hints['homepage'] = $hcard['url'][count($hcard['url'])-1];
} }

View File

@ -156,17 +156,31 @@ class MagicEnvelope
public function verify($env) public function verify($env)
{ {
if ($env['alg'] != 'RSA-SHA256') { if ($env['alg'] != 'RSA-SHA256') {
common_log(LOG_DEBUG, "Salmon error: bad algorithm");
return false; return false;
} }
if ($env['encoding'] != MagicEnvelope::ENCODING) { if ($env['encoding'] != MagicEnvelope::ENCODING) {
common_log(LOG_DEBUG, "Salmon error: bad encoding");
return false; return false;
} }
$text = base64_decode($env['data']); $text = base64_decode($env['data']);
$signer_uri = $this->getAuthor($text); $signer_uri = $this->getAuthor($text);
$verifier = Magicsig::fromString($this->getKeyPair($signer_uri)); try {
$keypair = $this->getKeyPair($signer_uri);
} catch (Exception $e) {
common_log(LOG_DEBUG, "Salmon error: ".$e->getMessage());
return false;
}
$verifier = Magicsig::fromString($keypair);
if (!$verifier) {
common_log(LOG_DEBUG, "Salmon error: unable to parse keypair");
return false;
}
return $verifier->verify($env['data'], $env['sig']); return $verifier->verify($env['data'], $env['sig']);
} }

View File

@ -164,46 +164,21 @@ class OStatusQueueHandler extends QueueHandler
*/ */
function userFeedForNotice() function userFeedForNotice()
{ {
// @fixme this feels VERY hacky... $atom = new AtomUserNoticeFeed($this->user);
// should probably be a cleaner way to do it $atom->addEntryFromNotice($this->notice);
$feed = $atom->getString();
ob_start();
$api = new ApiTimelineUserAction();
$api->prepare(array('id' => $this->notice->profile_id,
'format' => 'atom',
'max_id' => $this->notice->id,
'since_id' => $this->notice->id - 1));
$api->showTimeline();
$feed = ob_get_clean();
// ...and override the content-type back to something normal... eww!
// hope there's no other headers that got set while we weren't looking.
header('Content-Type: text/html; charset=utf-8');
common_log(LOG_DEBUG, $feed);
return $feed; return $feed;
} }
function groupFeedForNotice($group_id) function groupFeedForNotice($group_id)
{ {
// @fixme this feels VERY hacky... $group = User_group::staticGet('id', $group_id);
// should probably be a cleaner way to do it
ob_start(); $atom = new AtomGroupNoticeFeed($group);
$api = new ApiTimelineGroupAction(); $atom->addEntryFromNotice($this->notice);
$args = array('id' => $group_id, $feed = $atom->getString();
'format' => 'atom',
'max_id' => $this->notice->id,
'since_id' => $this->notice->id - 1);
$api->prepare($args);
$api->handle($args);
$feed = ob_get_clean();
// ...and override the content-type back to something normal... eww!
// hope there's no other headers that got set while we weren't looking.
header('Content-Type: text/html; charset=utf-8');
common_log(LOG_DEBUG, $feed);
return $feed; return $feed;
} }

View File

@ -57,6 +57,9 @@ class XRD
throw new Exception("Invalid XML"); throw new Exception("Invalid XML");
} }
$xrd_element = $dom->getElementsByTagName('XRD')->item(0); $xrd_element = $dom->getElementsByTagName('XRD')->item(0);
if (!$xrd_element) {
throw new Exception("Invalid XML, missing XRD root");
}
// Check for host-meta host // Check for host-meta host
$host = $xrd_element->getElementsByTagName('Host')->item(0); $host = $xrd_element->getElementsByTagName('Host')->item(0);
@ -149,11 +152,13 @@ class XRD
$link['href'] = $element->getAttribute('href'); $link['href'] = $element->getAttribute('href');
$link['template'] = $element->getAttribute('template'); $link['template'] = $element->getAttribute('template');
foreach ($element->childNodes as $node) { foreach ($element->childNodes as $node) {
if ($node instanceof DOMElement) {
switch($node->tagName) { switch($node->tagName) {
case 'Title': case 'Title':
$link['title'][] = $node->nodeValue; $link['title'][] = $node->nodeValue;
} }
} }
}
return $link; return $link;
} }

View File

@ -29,31 +29,23 @@ class XrdAction extends Action
public $uri; public $uri;
function prepare($args) public $user;
{
parent::prepare($args);
$this->uri = $this->trimmed('uri'); public $xrd;
return true;
}
function handle() function handle()
{ {
$acct = Discovery::normalize($this->uri); $nick = $this->user->nickname;
if (empty($this->xrd)) {
$xrd = new XRD(); $xrd = new XRD();
} else {
list($nick, $domain) = explode('@', substr(urldecode($acct), 5)); $xrd = $this->xrd;
$nick = common_canonical_nickname($nick);
$this->user = User::staticGet('nickname', $nick);
if (!$this->user) {
$this->clientError(_('No such user.'), 404);
return false;
} }
$xrd->subject = $this->uri; if (empty($xrd->subject)) {
$xrd->subject = Discovery::normalize($this->uri);
}
$xrd->alias[] = common_profile_url($nick); $xrd->alias[] = common_profile_url($nick);
$xrd->links[] = array('rel' => Discovery::PROFILEPAGE, $xrd->links[] = array('rel' => Discovery::PROFILEPAGE,
'type' => 'text/html', 'type' => 'text/html',

View File

@ -41,8 +41,34 @@ min-width:96px;
#entity_remote_subscribe { #entity_remote_subscribe {
padding:0; padding:0;
float:right; float:right;
position:relative;
} }
#all #entity_remote_subscribe { .section .entity_actions {
margin-top:-52px; margin-bottom:0;
}
#entity_remote_subscribe .dialogbox {
width:405px;
}
.aside #entity_subscriptions .more,
.aside #entity_groups .more {
float:left;
}
.section #entity_remote_subscribe {
border:0;
}
.section .entity_remote_subscribe {
color:#002FA7;
box-shadow:none;
-moz-box-shadow:none;
-webkit-box-shadow:none;
background-color:transparent;
background-position:0 -1183px;
padding:0 0 0 23px;
border:0;
} }

View File

@ -20,7 +20,7 @@ on Twitter (http://twitter.com/apps). During the application
registration process your application will be assigned a "consumer" key registration process your application will be assigned a "consumer" key
and secret, which the plugin will use to make OAuth requests to Twitter. and secret, which the plugin will use to make OAuth requests to Twitter.
You can either pass the consumer key and secret in when you enable the You can either pass the consumer key and secret in when you enable the
plugin, or set it using the Twitter administration panel. plugin, or set it using the Twitter administration panel**.
When registering your application with Twitter set the type to "Browser" When registering your application with Twitter set the type to "Browser"
and your Callback URL to: and your Callback URL to:
@ -42,11 +42,26 @@ To enable the plugin, add the following to your config.php:
) )
); );
or just:
addPlugin('TwitterBridge');
if you want to set the consumer key and secret from the Twitter bridge
administration panel. (The Twitter bridge wont work at all
unless you configure it with a consumer key and secret.)
* Note: The plugin will still push notices to Twitter for users who * Note: The plugin will still push notices to Twitter for users who
have previously set up the Twitter bridge using their Twitter name and have previously set up the Twitter bridge using their Twitter name and
password under an older version of StatusNet, but all new Twitter password under an older version of StatusNet, but all new Twitter
bridge connections will use OAuth. bridge connections will use OAuth.
** For multi-site setups you can also set a global consumer key and
secret. The Twitter bridge will fall back on the global key pair if
it can't find a local pair, e.g.:
$config['twitter']['global_consumer_key'] = 'YOUR_CONSUMER_KEY'
$config['twitter']['global_consumer_secret'] = 'YOUR_CONSUMER_SECRET'
Administration panel Administration panel
-------------------- --------------------

View File

@ -79,6 +79,30 @@ class TwitterBridgePlugin extends Plugin
} }
} }
/**
* Check to see if there is a consumer key and secret defined
* for Twitter integration.
*
* @return boolean result
*/
static function hasKeys()
{
$ckey = common_config('twitter', 'consumer_key');
$csecret = common_config('twitter', 'consumer_secret');
if (empty($ckey) && empty($csecret)) {
$ckey = common_config('twitter', 'global_consumer_key');
$csecret = common_config('twitter', 'global_consumer_secret');
}
if (!empty($ckey) && !empty($csecret)) {
return true;
}
return false;
}
/** /**
* Add Twitter-related paths to the router table * Add Twitter-related paths to the router table
* *
@ -91,17 +115,25 @@ class TwitterBridgePlugin extends Plugin
function onRouterInitialized($m) function onRouterInitialized($m)
{ {
$m->connect('admin/twitter', array('action' => 'twitteradminpanel'));
if (self::hasKeys()) {
$m->connect( $m->connect(
'twitter/authorization', 'twitter/authorization',
array('action' => 'twitterauthorization') array('action' => 'twitterauthorization')
); );
$m->connect('settings/twitter', array('action' => 'twittersettings')); $m->connect(
'settings/twitter', array(
'action' => 'twittersettings'
)
);
if (common_config('twitter', 'signin')) { if (common_config('twitter', 'signin')) {
$m->connect('main/twitterlogin', array('action' => 'twitterlogin')); $m->connect(
'main/twitterlogin',
array('action' => 'twitterlogin')
);
}
} }
$m->connect('admin/twitter', array('action' => 'twitteradminpanel'));
return true; return true;
} }
@ -117,7 +149,7 @@ class TwitterBridgePlugin extends Plugin
{ {
$action_name = $action->trimmed('action'); $action_name = $action->trimmed('action');
if (common_config('twitter', 'signin')) { if (self::hasKeys() && common_config('twitter', 'signin')) {
$action->menuItem( $action->menuItem(
common_local_url('twitterlogin'), common_local_url('twitterlogin'),
_m('Twitter'), _m('Twitter'),
@ -138,6 +170,7 @@ class TwitterBridgePlugin extends Plugin
*/ */
function onEndConnectSettingsNav(&$action) function onEndConnectSettingsNav(&$action)
{ {
if (self::hasKeys()) {
$action_name = $action->trimmed('action'); $action_name = $action->trimmed('action');
$action->menuItem( $action->menuItem(
@ -146,7 +179,7 @@ class TwitterBridgePlugin extends Plugin
_m('Twitter integration options'), _m('Twitter integration options'),
$action_name === 'twittersettings' $action_name === 'twittersettings'
); );
}
return true; return true;
} }
@ -188,12 +221,12 @@ class TwitterBridgePlugin extends Plugin
*/ */
function onStartEnqueueNotice($notice, &$transports) function onStartEnqueueNotice($notice, &$transports)
{ {
if (self::hasKeys()) {
// Avoid a possible loop // Avoid a possible loop
if ($notice->source != 'twitter') { if ($notice->source != 'twitter') {
array_push($transports, 'twitter'); array_push($transports, 'twitter');
} }
}
return true; return true;
} }
@ -206,12 +239,12 @@ class TwitterBridgePlugin extends Plugin
*/ */
function onGetValidDaemons($daemons) function onGetValidDaemons($daemons)
{ {
if (self::hasKeys()) {
array_push( array_push(
$daemons, $daemons,
INSTALLDIR INSTALLDIR
. '/plugins/TwitterBridge/daemons/synctwitterfriends.php' . '/plugins/TwitterBridge/daemons/synctwitterfriends.php'
); );
if (common_config('twitterimport', 'enabled')) { if (common_config('twitterimport', 'enabled')) {
array_push( array_push(
$daemons, $daemons,
@ -219,6 +252,7 @@ class TwitterBridgePlugin extends Plugin
. '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php' . '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php'
); );
} }
}
return true; return true;
} }
@ -232,7 +266,9 @@ class TwitterBridgePlugin extends Plugin
*/ */
function onEndInitializeQueueManager($manager) function onEndInitializeQueueManager($manager)
{ {
if (self::hasKeys()) {
$manager->connect('twitter', 'TwitterQueueHandler'); $manager->connect('twitter', 'TwitterQueueHandler');
}
return true; return true;
} }

View File

@ -225,6 +225,15 @@ class TwitterAdminPanelForm extends AdminForm
); );
$this->unli(); $this->unli();
$globalConsumerKey = common_config('twitter', 'global_consumer_key');
$globalConsumerSec = common_config('twitter', 'global_consumer_secret');
if (!empty($globalConsumerKey) && !empty($globalConsumerSec)) {
$this->li();
$this->out->element('p', 'form_guide', _('Note: a global consumer key and secret are set.'));
$this->unli();
}
$this->li(); $this->li();
$this->input( $this->input(
'source', 'source',

View File

@ -22,7 +22,7 @@
* @category Integration * @category Integration
* @package StatusNet * @package StatusNet
* @author Zach Copley <zach@status.net> * @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc. * @copyright 2009-2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
@ -61,8 +61,23 @@ class TwitterOAuthClient extends OAuthClient
$consumer_key = common_config('twitter', 'consumer_key'); $consumer_key = common_config('twitter', 'consumer_key');
$consumer_secret = common_config('twitter', 'consumer_secret'); $consumer_secret = common_config('twitter', 'consumer_secret');
parent::__construct($consumer_key, $consumer_secret, if (empty($consumer_key) && empty($consumer_secret)) {
$oauth_token, $oauth_token_secret); $consumer_key = common_config(
'twitter',
'global_consumer_key'
);
$consumer_secret = common_config(
'twitter',
'global_consumer_secret'
);
}
parent::__construct(
$consumer_key,
$consumer_secret,
$oauth_token,
$oauth_token_secret
);
} }
// XXX: the following two functions are to support the horrible hack // XXX: the following two functions are to support the horrible hack

131
tests/UserFeedParseTest.php Normal file
View File

@ -0,0 +1,131 @@
<?php
if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
print "This script must be run from the command line\n";
exit();
}
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
define('STATUSNET', true);
require_once INSTALLDIR . '/lib/common.php';
class UserFeedParseTests extends PHPUnit_Framework_TestCase
{
public function testFeed1()
{
global $_testfeed1;
$dom = DOMDocument::loadXML($_testfeed1);
$this->assertFalse(empty($dom));
$entries = $dom->getElementsByTagName('entry');
$entry1 = $entries->item(0);
$this->assertFalse(empty($entry1));
$feedEl = $dom->getElementsByTagName('feed')->item(0);
$this->assertFalse(empty($feedEl));
// Test actor (from activity:subject)
$act1 = new Activity($entry1, $feedEl);
$this->assertFalse(empty($act1));
$this->assertFalse(empty($act1->actor));
$this->assertEquals($act1->actor->type, ActivityObject::PERSON);
$this->assertEquals($act1->actor->title, 'Zach Copley');
$this->assertEquals($act1->actor->id, 'http://localhost/statusnet/user/1');
$this->assertEquals($act1->actor->link, 'http://localhost/statusnet/zach');
$avatars = $act1->actor->avatarLinks;
$this->assertEquals(
$avatars[0]->url,
'http://localhost/statusnet/theme/default/default-avatar-profile.png'
);
$this->assertEquals(
$avatars[1]->url,
'http://localhost/statusnet/theme/default/default-avatar-stream.png'
);
$this->assertEquals(
$avatars[2]->url,
'http://localhost/statusnet/theme/default/default-avatar-mini.png'
);
$this->assertEquals($act1->actor->displayName, 'Zach Copley');
$poco = $act1->actor->poco;
$this->assertEquals($poco->preferredUsername, 'zach');
$this->assertEquals($poco->address->formatted, 'El Cerrito, CA');
$this->assertEquals($poco->urls[0]->type, 'homepage');
$this->assertEquals($poco->urls[0]->value, 'http://zach.copley.name');
$this->assertEquals($poco->urls[0]->primary, 'true');
$this->assertEquals($poco->note, 'Zach Hack Attack');
// test the post
//var_export($act1);
$this->assertEquals($act1->object->type, 'http://activitystrea.ms/schema/1.0/note');
$this->assertEquals($act1->object->title, 'And now for something completely insane...');
$this->assertEquals($act1->object->content, 'And now for something completely insane...');
$this->assertEquals($act1->object->id, 'http://localhost/statusnet/notice/3');
}
}
$_testfeed1 = <<<TESTFEED1
<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:georss="http://www.georss.org/georss" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:media="http://purl.org/syndication/atommedia" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0">
<id>http://localhost/statusnet/api/statuses/user_timeline/1.atom</id>
<title>zach timeline</title>
<subtitle>Updates from zach on Zach Dev!</subtitle>
<logo>http://localhost/statusnet/theme/default/default-avatar-profile.png</logo>
<updated>2010-03-04T01:41:14+00:00</updated>
<author>
<name>zach</name>
<uri>http://localhost/statusnet/user/1</uri>
</author>
<link href="http://localhost/statusnet/zach" rel="alternate" type="text/html"/>
<link href="http://localhost/statusnet/main/sup#1" rel="http://api.friendfeed.com/2008/03#sup" type="application/json"/>
<link href="http://localhost/statusnet/main/push/hub" rel="hub"/>
<link href="http://localhost/statusnet/main/salmon/user/1" rel="http://salmon-protocol.org/ns/salmon-replies"/>
<link href="http://localhost/statusnet/main/salmon/user/1" rel="http://salmon-protocol.org/ns/salmon-mention"/>
<link href="http://localhost/statusnet/api/statuses/user_timeline/1.atom" rel="self" type="application/atom+xml"/>
<activity:subject>
<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
<id>http://localhost/statusnet/user/1</id>
<title>Zach Copley</title>
<link rel="alternate" type="text/html" href="http://localhost/statusnet/zach"/>
<link rel="avatar" type="image/png" media:width="96" media:height="96" href="http://localhost/statusnet/theme/default/default-avatar-profile.png"/>
<link rel="avatar" type="image/png" media:width="48" media:height="48" href="http://localhost/statusnet/theme/default/default-avatar-stream.png"/>
<link rel="avatar" type="image/png" media:width="24" media:height="24" href="http://localhost/statusnet/theme/default/default-avatar-mini.png"/>
<poco:preferredUsername>zach</poco:preferredUsername>
<poco:displayName>Zach Copley</poco:displayName>
<poco:note>Zach Hack Attack</poco:note>
<poco:address>
<poco:formatted>El Cerrito, CA</poco:formatted>
</poco:address>
<poco:urls>
<poco:type>homepage</poco:type>
<poco:value>http://zach.copley.name</poco:value>
<poco:primary>true</poco:primary>
</poco:urls>
</activity:subject>
<entry>
<title>And now for something completely insane...</title>
<link rel="alternate" type="text/html" href="http://localhost/statusnet/notice/3"/>
<id>http://localhost/statusnet/notice/3</id>
<published>2010-03-04T01:41:07+00:00</published>
<updated>2010-03-04T01:41:07+00:00</updated>
<link rel="ostatus:conversation" href="http://localhost/statusnet/conversation/3"/>
<content type="html">And now for something completely insane...</content>
</entry>
</feed>
TESTFEED1;

View File

@ -452,6 +452,13 @@ width:100%;
float:left; float:left;
} }
#content.admin {
width:95.5%;
}
#content.admin #content_inner {
width:66.3%;
}
#aside_primary { #aside_primary {
width:27.917%; width:27.917%;
min-height:259px; min-height:259px;
@ -764,10 +771,12 @@ display:none;
text-align:center; text-align:center;
} }
.entity_moderation { .entity_moderation,
.entity_role {
position:relative; position:relative;
} }
.entity_moderation p { .entity_moderation p,
.entity_role p {
border-radius:4px; border-radius:4px;
-moz-border-radius:4px; -moz-border-radius:4px;
-webkit-border-radius:4px; -webkit-border-radius:4px;
@ -775,13 +784,14 @@ font-weight:bold;
padding-bottom:2px; padding-bottom:2px;
margin-bottom:7px; margin-bottom:7px;
} }
.entity_moderation ul { .entity_moderation ul,
.entity_role ul {
display:none; display:none;
} }
.entity_moderation:hover ul { .entity_moderation:hover ul,
.entity_role:hover ul {
display:block; display:block;
min-width:21%; width:110%;
width:100%;
padding:11px; padding:11px;
position:absolute; position:absolute;
top:-1px; top:-1px;

View File

@ -45,6 +45,11 @@
White pin with green background White pin with green background
White underscore with green background White underscore with green background
White C with green background White C with green background
White magic wand with green background
Green badge with white background
Green sandbox with white background
Green speech bubble broken with white background
Green person with tie with white background
*/ */
Created by various authors Created by various authors

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -49,6 +49,7 @@ box-shadow:3px 3px 7px rgba(194, 194, 194, 0.3);
.pagination .nav_next a, .pagination .nav_next a,
.form_settings fieldset fieldset, .form_settings fieldset fieldset,
.entity_moderation:hover ul, .entity_moderation:hover ul,
.entity_role:hover ul,
.dialogbox { .dialogbox {
border-color:#DDDDDD; border-color:#DDDDDD;
} }
@ -67,6 +68,7 @@ input.submit,
.entity_actions a, .entity_actions a,
.entity_actions input, .entity_actions input,
.entity_moderation p, .entity_moderation p,
.entity_role p,
button { button {
box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
-moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); -moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
@ -127,7 +129,8 @@ a,
.notice-options input, .notice-options input,
.entity_actions a, .entity_actions a,
.entity_actions input, .entity_actions input,
.entity_moderation p { .entity_moderation p,
.entity_role p {
color:#002FA7; color:#002FA7;
} }
@ -190,6 +193,9 @@ button.close,
.entity_sandbox input.submit, .entity_sandbox input.submit,
.entity_silence input.submit, .entity_silence input.submit,
.entity_delete input.submit, .entity_delete input.submit,
.entity_role p,
.entity_role_administrator input.submit,
.entity_role_moderator input.submit,
.notice-options .repeated, .notice-options .repeated,
.form_notice label[for=notice_data-geo], .form_notice label[for=notice_data-geo],
button.minimize, button.minimize,
@ -229,6 +235,7 @@ border-color:transparent;
#site_nav_local_views .current a, #site_nav_local_views .current a,
.entity_send-a-message .form_notice, .entity_send-a-message .form_notice,
.entity_moderation:hover ul, .entity_moderation:hover ul,
.entity_role:hover ul,
.dialogbox { .dialogbox {
background-color:#FFFFFF; background-color:#FFFFFF;
} }
@ -319,6 +326,7 @@ background-position: 5px -852px;
} }
.entity_send-a-message .form_notice, .entity_send-a-message .form_notice,
.entity_moderation:hover ul, .entity_moderation:hover ul,
.entity_role:hover ul,
.dialogbox { .dialogbox {
box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7);
-moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); -moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7);
@ -350,6 +358,27 @@ background-position: 5px -1445px;
.entity_delete input.submit { .entity_delete input.submit {
background-position: 5px -1511px; background-position: 5px -1511px;
} }
.entity_sandbox .form_user_unsandbox input.submit {
background-position: 5px -2568px;
}
.entity_silence .form_user_unsilence input.submit {
background-position: 5px -2633px;
}
.entity_role p {
background-position: 5px -2436px;
}
.entity_role_administrator .form_user_grantrole input.submit {
background-position: 5px -983px;
}
.entity_role_moderator .form_user_grantrole input.submit {
background-position: 5px -1313px;
}
.entity_role_administrator .form_user_revokerole input.submit {
background-position: 5px -2699px;
}
.entity_role_moderator .form_user_revokerole input.submit {
background-position: 5px -2501px;
}
.form_reset_key input.submit { .form_reset_key input.submit {
background-position: 5px -1973px; background-position: 5px -1973px;
} }

View File

@ -49,6 +49,7 @@ box-shadow:3px 3px 7px rgba(194, 194, 194, 0.3);
.pagination .nav_next a, .pagination .nav_next a,
.form_settings fieldset fieldset, .form_settings fieldset fieldset,
.entity_moderation:hover ul, .entity_moderation:hover ul,
.entity_role:hover ul,
.dialogbox { .dialogbox {
border-color:#DDDDDD; border-color:#DDDDDD;
} }
@ -67,6 +68,7 @@ input.submit,
.entity_actions a, .entity_actions a,
.entity_actions input, .entity_actions input,
.entity_moderation p, .entity_moderation p,
.entity_role p,
button { button {
box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
-moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); -moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
@ -128,7 +130,8 @@ a,
.notice-options input, .notice-options input,
.entity_actions a, .entity_actions a,
.entity_actions input, .entity_actions input,
.entity_moderation p { .entity_moderation p,
.entity_role p {
color:#002FA7; color:#002FA7;
} }
@ -191,6 +194,9 @@ button.close,
.entity_sandbox input.submit, .entity_sandbox input.submit,
.entity_silence input.submit, .entity_silence input.submit,
.entity_delete input.submit, .entity_delete input.submit,
.entity_role p,
.entity_role_administrator input.submit,
.entity_role_moderator input.submit,
.notice-options .repeated, .notice-options .repeated,
.form_notice label[for=notice_data-geo], .form_notice label[for=notice_data-geo],
button.minimize, button.minimize,
@ -230,6 +236,7 @@ border-color:transparent;
#site_nav_local_views .current a, #site_nav_local_views .current a,
.entity_send-a-message .form_notice, .entity_send-a-message .form_notice,
.entity_moderation:hover ul, .entity_moderation:hover ul,
.entity_role:hover ul,
.dialogbox { .dialogbox {
background-color:#FFFFFF; background-color:#FFFFFF;
} }
@ -319,6 +326,7 @@ background-position: 5px -852px;
} }
.entity_send-a-message .form_notice, .entity_send-a-message .form_notice,
.entity_moderation:hover ul, .entity_moderation:hover ul,
.entity_role:hover ul,
.dialogbox { .dialogbox {
box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7);
-moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7); -moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7);
@ -350,6 +358,27 @@ background-position: 5px -1445px;
.entity_delete input.submit { .entity_delete input.submit {
background-position: 5px -1511px; background-position: 5px -1511px;
} }
.entity_sandbox .form_user_unsandbox input.submit {
background-position: 5px -2568px;
}
.entity_silence .form_user_unsilence input.submit {
background-position: 5px -2633px;
}
.entity_role p {
background-position: 5px -2436px;
}
.entity_role_administrator .form_user_grantrole input.submit {
background-position: 5px -983px;
}
.entity_role_moderator .form_user_grantrole input.submit {
background-position: 5px -1313px;
}
.entity_role_administrator .form_user_revokerole input.submit {
background-position: 5px -2699px;
}
.entity_role_moderator .form_user_revokerole input.submit {
background-position: 5px -2501px;
}
.form_reset_key input.submit { .form_reset_key input.submit {
background-position: 5px -1973px; background-position: 5px -1973px;
} }