summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--actions/allrss.php2
-rw-r--r--actions/apitimelinegroup.php45
-rw-r--r--actions/apitimelineuser.php51
-rw-r--r--actions/grantrole.php99
-rw-r--r--actions/revokerole.php99
-rw-r--r--actions/siteadminpanel.php56
-rw-r--r--actions/sitenoticeadminpanel.php2
-rw-r--r--actions/snapshotadminpanel.php251
-rw-r--r--classes/Notice.php8
-rw-r--r--classes/Profile.php4
-rw-r--r--classes/Profile_role.php17
-rw-r--r--install.php86
-rw-r--r--lib/activity.php12
-rw-r--r--lib/adminpanelaction.php5
-rw-r--r--lib/atom10entry.php105
-rw-r--r--lib/atom10feed.php28
-rw-r--r--lib/atomgroupnoticefeed.php32
-rw-r--r--lib/atomnoticefeed.php16
-rw-r--r--lib/atomusernoticefeed.php52
-rw-r--r--lib/grantroleform.php93
-rw-r--r--lib/revokeroleform.php93
-rw-r--r--lib/right.php2
-rw-r--r--lib/router.php2
-rw-r--r--lib/statusnet.php6
-rw-r--r--lib/userprofile.php26
-rw-r--r--plugins/OStatus/OStatusPlugin.php6
-rw-r--r--plugins/OStatus/actions/hostmeta.php2
-rw-r--r--plugins/OStatus/actions/ostatusgroup.php4
-rw-r--r--plugins/OStatus/actions/ostatussub.php33
-rw-r--r--plugins/OStatus/actions/ownerxrd.php56
-rw-r--r--plugins/OStatus/actions/userxrd.php48
-rw-r--r--plugins/OStatus/classes/Ostatus_profile.php27
-rw-r--r--plugins/OStatus/lib/magicenvelope.php16
-rw-r--r--plugins/OStatus/lib/ostatusqueuehandler.php45
-rw-r--r--plugins/OStatus/lib/xrd.php3
-rw-r--r--plugins/OStatus/lib/xrdaction.php (renamed from plugins/OStatus/actions/xrd.php)32
-rw-r--r--plugins/OStatus/theme/base/css/ostatus.css3
-rw-r--r--tests/UserFeedParseTest.php131
-rw-r--r--theme/base/css/display.css15
-rw-r--r--theme/base/images/icons/README5
-rw-r--r--theme/base/images/icons/icons-01.gifbin3740 -> 4080 bytes
-rw-r--r--theme/default/css/display.css31
-rw-r--r--theme/identica/css/display.css31
43 files changed, 1329 insertions, 351 deletions
diff --git a/actions/allrss.php b/actions/allrss.php
index 28b1be27d..01e737ad7 100644
--- a/actions/allrss.php
+++ b/actions/allrss.php
@@ -83,6 +83,7 @@ class AllrssAction extends Rss10Action
function getNotices($limit=0)
{
$cur = common_current_user();
+ $user = $this->user;
if (!empty($cur) && $cur->id == $user->id) {
$notice = $this->user->noticeInbox(0, $limit);
@@ -90,7 +91,6 @@ class AllrssAction extends Rss10Action
$notice = $this->user->noticesWithFriends(0, $limit);
}
- $user = $this->user;
$notice = $user->noticesWithFriends(0, $limit);
$notices = array();
diff --git a/actions/apitimelinegroup.php b/actions/apitimelinegroup.php
index e30a08fb5..8f971392b 100644
--- a/actions/apitimelinegroup.php
+++ b/actions/apitimelinegroup.php
@@ -104,30 +104,21 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
function showTimeline()
{
- $sitename = common_config('site', 'name');
- $avatar = $this->group->homepage_logo;
- $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);
+ // We'll pull common formatting out of this for other formats
+ $atom = new AtomGroupNoticeFeed($this->group);
switch($this->format) {
case 'xml':
$this->showXmlTimeline($this->notices);
break;
case 'rss':
- $this->showRssTimeline(
+ $this->showRssTimeline(
$this->notices,
- $title,
+ $atom->title,
$this->group->homeUrl(),
- $subtitle,
+ $atom->subtitle,
null,
- $logo
+ $atom->logo
);
break;
case 'atom':
@@ -136,38 +127,22 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
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->setActivitySubject($this->group->asActivitySubject());
- $atom->addLink($this->group->homeUrl());
-
$id = $this->arg('id');
$aargs = array('format' => 'atom');
if (!empty($id)) {
$aargs['id'] = $id;
}
+ $self = $this->getSelfUri('ApiTimelineGroup', $aargs);
- $atom->setId($this->getSelfUri('ApiTimelineGroup', $aargs));
-
- $atom->addLink(
- $this->getSelfUri('ApiTimelineGroup', $aargs),
- array('rel' => 'self', 'type' => 'application/atom+xml')
- );
+ $atom->setId($self);
+ $atom->setSelfLink($self);
$atom->addEntryFromNotices($this->notices);
- //$this->raw($atom->getString());
- print $atom->getString(); // temp hack until PuSH feeds are redone cleanly
+ $this->raw($atom->getString());
} catch (Atom10FeedException $e) {
$this->serverError(
diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php
index 94491946c..2d0047c04 100644
--- a/actions/apitimelineuser.php
+++ b/actions/apitimelineuser.php
@@ -112,19 +112,17 @@ class ApiTimelineUserAction extends ApiBareAuthAction
function showTimeline()
{
$profile = $this->user->getProfile();
- $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
- $sitename = common_config('site', 'name');
- $title = sprintf(_("%s timeline"), $this->user->nickname);
+ // We'll use the shared params from the Atom stub
+ // for other feed types.
+ $atom = new AtomUserNoticeFeed($this->user);
+ $title = $atom->title;
$link = common_local_url(
'showstream',
array('nickname' => $this->user->nickname)
);
- $subtitle = sprintf(
- _('Updates from %1$s on %2$s!'),
- $this->user->nickname, $sitename
- );
- $logo = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE);
+ $subtitle = $atom->subtitle;
+ $logo = $atom->logo;
// FriendFeed's SUP protocol
// Also added RSS and Atom feeds
@@ -146,47 +144,18 @@ class ApiTimelineUserAction extends ApiBareAuthAction
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');
$aargs = array('format' => 'atom');
if (!empty($id)) {
$aargs['id'] = $id;
}
-
- $atom->setId($this->getSelfUri('ApiTimelineUser', $aargs));
-
- $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'
- )
- );
+ $self = $this->getSelfUri('ApiTimelineUser', $aargs);
+ $atom->setId($self);
+ $atom->setSelfLink($self);
$atom->addEntryFromNotices($this->notices);
- #$this->raw($atom->getString());
- print $atom->getString(); // temporary for output buffering
+ $this->raw($atom->getString());
break;
case 'json':
diff --git a/actions/grantrole.php b/actions/grantrole.php
new file mode 100644
index 000000000..cd6bd4d79
--- /dev/null
+++ b/actions/grantrole.php
@@ -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);
+ }
+}
diff --git a/actions/revokerole.php b/actions/revokerole.php
new file mode 100644
index 000000000..b78c1c25a
--- /dev/null
+++ b/actions/revokerole.php
@@ -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);
+ }
+}
diff --git a/actions/siteadminpanel.php b/actions/siteadminpanel.php
index 4b29819b7..cb3c2e8fd 100644
--- a/actions/siteadminpanel.php
+++ b/actions/siteadminpanel.php
@@ -66,7 +66,7 @@ class SiteadminpanelAction extends AdminPanelAction
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()
{
- static $settings = array('site' => array('name', 'broughtby', 'broughtbyurl',
- 'email', 'timezone', 'language',
- 'site', 'textlimit', 'dupelimit'),
- 'snapshot' => array('run', 'reporturl', 'frequency'));
+ static $settings = array(
+ 'site' => array('name', 'broughtby', 'broughtbyurl',
+ 'email', 'timezone', 'language',
+ 'site', 'textlimit', 'dupelimit'),
+ );
$values = array();
@@ -158,25 +159,6 @@ class SiteadminpanelAction extends AdminPanelAction
$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
if (!Validate::number($values['site']['textlimit'], array('min' => 140))) {
@@ -285,32 +267,6 @@ class SiteAdminPanelForm extends AdminForm
$this->out->elementEnd('ul');
$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->element('legend', null, _('Limits'));
$this->out->elementStart('ul', 'form_data');
diff --git a/actions/sitenoticeadminpanel.php b/actions/sitenoticeadminpanel.php
index 613a2e96b..3931aa982 100644
--- a/actions/sitenoticeadminpanel.php
+++ b/actions/sitenoticeadminpanel.php
@@ -99,7 +99,7 @@ class SitenoticeadminpanelAction extends AdminPanelAction
$result = Config::save('site', 'notice', $siteNotice);
- if (!result) {
+ if (!$result) {
$this->ServerError(_("Unable to save site notice."));
}
}
diff --git a/actions/snapshotadminpanel.php b/actions/snapshotadminpanel.php
new file mode 100644
index 000000000..a0c2315bc
--- /dev/null
+++ b/actions/snapshotadminpanel.php
@@ -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')
+ );
+ }
+}
diff --git a/classes/Notice.php b/classes/Notice.php
index 97cb3b8fb..4c7e6ab4b 100644
--- a/classes/Notice.php
+++ b/classes/Notice.php
@@ -1106,7 +1106,7 @@ class Notice extends Memcached_DataObject
return $groups;
}
- function asAtomEntry($namespace=false, $source=false)
+ function asAtomEntry($namespace=false, $source=false, $author=true)
{
$profile = $this->getProfile();
@@ -1151,8 +1151,10 @@ class Notice extends Memcached_DataObject
$xs->element('title', null, $this->content);
- $xs->raw($profile->asAtomAuthor());
- $xs->raw($profile->asActivityActor());
+ if ($author) {
+ $xs->raw($profile->asAtomAuthor());
+ $xs->raw($profile->asActivityActor());
+ }
$xs->element('link', array('rel' => 'alternate',
'type' => 'text/html',
diff --git a/classes/Profile.php b/classes/Profile.php
index 9c2fa7a0c..0322c9358 100644
--- a/classes/Profile.php
+++ b/classes/Profile.php
@@ -743,6 +743,10 @@ class Profile extends Memcached_DataObject
case Right::CONFIGURESITE:
$result = $this->hasRole(Profile_role::ADMINISTRATOR);
break;
+ case Right::GRANTROLE:
+ case Right::REVOKEROLE:
+ $result = $this->hasRole(Profile_role::OWNER);
+ break;
case Right::NEWNOTICE:
case Right::NEWMESSAGE:
case Right::SUBSCRIBE:
diff --git a/classes/Profile_role.php b/classes/Profile_role.php
index bf2c453ed..d0a0b31f0 100644
--- a/classes/Profile_role.php
+++ b/classes/Profile_role.php
@@ -53,4 +53,21 @@ class Profile_role extends Memcached_DataObject
const ADMINISTRATOR = 'administrator';
const SANDBOXED = 'sandboxed';
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);
+ }
}
diff --git a/install.php b/install.php
index 435f6d63b..41024c901 100644
--- a/install.php
+++ b/install.php
@@ -31,6 +31,7 @@
* @author Robin Millette <millette@controlyourself.ca>
* @author Sarven Capadisli <csarven@status.net>
* @author Tom Adams <tom@holizz.com>
+ * @author Zach Copley <zach@status.net>
* @license GNU Affero General Public License http://www.gnu.org/licenses/
* @version 0.9.x
* @link http://status.net
@@ -490,15 +491,25 @@ function showForm()
<p class="form_guide">Database name</p>
</li>
<li>
- <label for="username">Username</label>
+ <label for="username">DB username</label>
<input type="text" id="username" name="username" />
<p class="form_guide">Database username</p>
</li>
<li>
- <label for="password">Password</label>
+ <label for="password">DB password</label>
<input type="password" id="password" name="password" />
<p class="form_guide">Database password (optional)</p>
</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>
<input type="submit" name="submit" class="submit" value="Submit" />
</fieldset>
@@ -521,6 +532,10 @@ function handlePost()
$password = $_POST['password'];
$sitename = $_POST['sitename'];
$fancy = !empty($_POST['fancy']);
+
+ $adminNick = $_POST['admin_nickname'];
+ $adminPass = $_POST['admin_password'];
+
$server = $_SERVER['HTTP_HOST'];
$path = substr(dirname($_SERVER['PHP_SELF']), 1);
@@ -552,6 +567,16 @@ STR;
$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) {
showForm();
return;
@@ -574,13 +599,29 @@ STR;
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
*/
$link = "http://".$server.'/'.$path;
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)
@@ -756,6 +797,33 @@ function runDbScript($filename, $conn, $type = 'mysqli')
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 "?>"; ?>
<!DOCTYPE html
@@ -765,10 +833,10 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
<head>
<title>Install StatusNet</title>
<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"/>
- <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/base/css/ie.css?version=0.8" /><![endif]-->
- <!--[if lte IE 6]><link rel="stylesheet" type="text/css" theme/base/css/ie6.css?version=0.8" /><![endif]-->
- <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/default/css/ie.css?version=0.8" /><![endif]-->
+ <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" /><![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" /><![endif]-->
<script src="js/jquery.min.js"></script>
<script src="js/install.js"></script>
</head>
@@ -784,8 +852,10 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
</div>
<div id="core">
<div id="content">
- <h1>Install StatusNet</h1>
+ <div id="content_inner">
+ <h1>Install StatusNet</h1>
<?php main(); ?>
+ </div>
</div>
</div>
</div>
diff --git a/lib/activity.php b/lib/activity.php
index 7334239ec..2cb80f9e1 100644
--- a/lib/activity.php
+++ b/lib/activity.php
@@ -1064,6 +1064,18 @@ class Activity
}
$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;
$pubEl = $this->_child($entry, self::PUBLISHED, self::ATOM);
diff --git a/lib/adminpanelaction.php b/lib/adminpanelaction.php
index eb622871e..d1aab3dfc 100644
--- a/lib/adminpanelaction.php
+++ b/lib/adminpanelaction.php
@@ -381,6 +381,11 @@ class AdminPanelNav extends Widget
_('Edit site notice'), $action_name == 'sitenoticeadminpanel', 'nav_sitenotice_admin_panel');
}
+ if (AdminPanelAction::canAdmin('snapshot')) {
+ $this->out->menuItem(common_local_url('snapshotadminpanel'), _('Snapshots'),
+ _('Snapshots configuration'), $action_name == 'snapshotadminpanel', 'nav_snapshot_admin_panel');
+ }
+
Event::handle('EndAdminPanelNav', array($this));
}
$this->action->elementEnd('ul');
diff --git a/lib/atom10entry.php b/lib/atom10entry.php
deleted file mode 100644
index f8f16d594..000000000
--- a/lib/atom10entry.php
+++ /dev/null
@@ -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();
- }
-
-} \ No newline at end of file
diff --git a/lib/atom10feed.php b/lib/atom10feed.php
index 8842840d5..2d342e785 100644
--- a/lib/atom10feed.php
+++ b/lib/atom10feed.php
@@ -49,6 +49,8 @@ class Atom10FeedException extends Exception
class Atom10Feed extends XMLStringer
{
public $xw;
+
+ // @fixme most of these should probably be read-only properties
private $namespaces;
private $authors;
private $subject;
@@ -57,10 +59,12 @@ class Atom10Feed extends XMLStringer
private $generator;
private $icon;
private $links;
- private $logo;
+ private $selfLink;
+ private $selfLinkType;
+ public $logo;
private $rights;
- private $subtitle;
- private $title;
+ public $subtitle;
+ public $title;
private $published;
private $updated;
private $entries;
@@ -172,6 +176,14 @@ class Atom10Feed extends XMLStringer
}
$this->elementStart('feed', $commonAttrs);
+ $this->element(
+ 'generator', array(
+ 'url' => 'http://status.net',
+ 'version' => STATUSNET_VERSION
+ ),
+ 'StatusNet'
+ );
+
$this->element('id', null, $this->id);
$this->element('title', null, $this->title);
$this->element('subtitle', null, $this->subtitle);
@@ -184,6 +196,10 @@ class Atom10Feed extends XMLStringer
$this->renderAuthors();
+ if ($this->selfLink) {
+ $this->addLink($this->selfLink, array('rel' => 'self',
+ 'type' => $this->selfLinkType));
+ }
$this->renderLinks();
}
@@ -253,6 +269,12 @@ class Atom10Feed extends XMLStringer
$this->id = $id;
}
+ function setSelfLink($url, $type='application/atom+xml')
+ {
+ $this->selfLink = $url;
+ $this->selfLinkType = $type;
+ }
+
function setTitle($title)
{
$this->title = $title;
diff --git a/lib/atomgroupnoticefeed.php b/lib/atomgroupnoticefeed.php
index 52ee4c7d6..08c1c707c 100644
--- a/lib/atomgroupnoticefeed.php
+++ b/lib/atomgroupnoticefeed.php
@@ -49,14 +49,42 @@ class AtomGroupNoticeFeed extends AtomNoticeFeed
/**
* 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
*
* @return void
*/
- function __construct($group = null, $indent = true) {
+ function __construct($group, $indent = true) {
parent::__construct($indent);
$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()
diff --git a/lib/atomnoticefeed.php b/lib/atomnoticefeed.php
index 3c3556cb9..e4df731fe 100644
--- a/lib/atomnoticefeed.php
+++ b/lib/atomnoticefeed.php
@@ -107,9 +107,19 @@ class AtomNoticeFeed extends Atom10Feed
*/
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;
+ }
+}
diff --git a/lib/atomusernoticefeed.php b/lib/atomusernoticefeed.php
index 2ad8de455..428cc2de2 100644
--- a/lib/atomusernoticefeed.php
+++ b/lib/atomusernoticefeed.php
@@ -49,23 +49,71 @@ class AtomUserNoticeFeed extends AtomNoticeFeed
/**
* 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
*
* @return void
*/
- function __construct($user = null, $indent = true) {
+ function __construct($user, $indent = true) {
parent::__construct($indent);
$this->user = $user;
if (!empty($user)) {
$profile = $user->getProfile();
$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()
{
return $this->user;
}
+
+ function showSource()
+ {
+ return false;
+ }
+
+ function showAuthor()
+ {
+ return false;
+ }
}
diff --git a/lib/grantroleform.php b/lib/grantroleform.php
new file mode 100644
index 000000000..b5f952746
--- /dev/null
+++ b/lib/grantroleform.php
@@ -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);
+ }
+}
diff --git a/lib/revokeroleform.php b/lib/revokeroleform.php
new file mode 100644
index 000000000..ec24b9910
--- /dev/null
+++ b/lib/revokeroleform.php
@@ -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);
+ }
+}
diff --git a/lib/right.php b/lib/right.php
index 4e9c5a918..deb451fde 100644
--- a/lib/right.php
+++ b/lib/right.php
@@ -58,5 +58,7 @@ class Right
const EMAILONSUBSCRIBE = 'emailonsubscribe';
const EMAILONFAVE = 'emailonfave';
const MAKEGROUPADMIN = 'makegroupadmin';
+ const GRANTROLE = 'grantrole';
+ const REVOKEROLE = 'revokerole';
}
diff --git a/lib/router.php b/lib/router.php
index 7e8e22a7d..706120e0b 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -98,6 +98,7 @@ class Router
'groupblock', 'groupunblock',
'sandbox', 'unsandbox',
'silence', 'unsilence',
+ 'grantrole', 'revokerole',
'repeat',
'deleteuser',
'geocode',
@@ -650,6 +651,7 @@ class Router
$m->connect('admin/paths', array('action' => 'pathsadminpanel'));
$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',
array('action' => 'getfile'),
diff --git a/lib/statusnet.php b/lib/statusnet.php
index 7c4df84b4..eba9ab9b8 100644
--- a/lib/statusnet.php
+++ b/lib/statusnet.php
@@ -354,10 +354,10 @@ class StatusNet
class NoConfigException extends Exception
{
- public $config_files;
+ public $configFiles;
- function __construct($msg, $config_files) {
+ function __construct($msg, $configFiles) {
parent::__construct($msg);
- $this->config_files = $config_files;
+ $this->configFiles = $configFiles;
}
}
diff --git a/lib/userprofile.php b/lib/userprofile.php
index 43dfd05be..8464c2446 100644
--- a/lib/userprofile.php
+++ b/lib/userprofile.php
@@ -346,6 +346,16 @@ class UserProfile extends Widget
$this->out->elementEnd('ul');
$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()
{
$url = common_local_url('remotesubscribe',
diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php
index cc7e75976..ad4f61389 100644
--- a/plugins/OStatus/OStatusPlugin.php
+++ b/plugins/OStatus/OStatusPlugin.php
@@ -44,7 +44,9 @@ class OStatusPlugin extends Plugin
$m->connect('.well-known/host-meta',
array('action' => 'hostmeta'));
$m->connect('main/xrd',
- array('action' => 'xrd'));
+ array('action' => 'userxrd'));
+ $m->connect('main/ownerxrd',
+ array('action' => 'ownerxrd'));
$m->connect('main/ostatus',
array('action' => 'ostatusinit'));
$m->connect('main/ostatus?nickname=:nickname',
@@ -111,7 +113,7 @@ class OStatusPlugin extends Plugin
{
if ($action instanceof ShowstreamAction) {
$acct = 'acct:'. $action->profile->nickname .'@'. common_config('site', 'server');
- $url = common_local_url('xrd');
+ $url = common_local_url('userxrd');
$url.= '?uri='. $acct;
header('Link: <'.$url.'>; rel="'. Discovery::LRDD_REL.'"; type="application/xrd+xml"');
diff --git a/plugins/OStatus/actions/hostmeta.php b/plugins/OStatus/actions/hostmeta.php
index 3d00b98ae..6d35ada6c 100644
--- a/plugins/OStatus/actions/hostmeta.php
+++ b/plugins/OStatus/actions/hostmeta.php
@@ -32,7 +32,7 @@ class HostMetaAction extends Action
parent::handle();
$domain = common_config('site', 'server');
- $url = common_local_url('xrd');
+ $url = common_local_url('userxrd');
$url.= '?uri={uri}';
$xrd = new XRD();
diff --git a/plugins/OStatus/actions/ostatusgroup.php b/plugins/OStatus/actions/ostatusgroup.php
index 4fcd0eb39..f325ba053 100644
--- a/plugins/OStatus/actions/ostatusgroup.php
+++ b/plugins/OStatus/actions/ostatusgroup.php
@@ -72,9 +72,9 @@ class OStatusGroupAction extends OStatusSubAction
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->input('profile',
- _m('Group profile URL'),
+ _m('Join group'),
$this->profile_uri,
- _m('Enter the profile URL of a group on another StatusNet site'));
+ _m("OStatus group's address, like http://example.net/group/nickname"));
$this->elementEnd('li');
$this->elementEnd('ul');
diff --git a/plugins/OStatus/actions/ostatussub.php b/plugins/OStatus/actions/ostatussub.php
index 542f7e20c..65dee2392 100644
--- a/plugins/OStatus/actions/ostatussub.php
+++ b/plugins/OStatus/actions/ostatussub.php
@@ -62,9 +62,9 @@ class OStatusSubAction extends Action
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->input('profile',
- _m('Address or profile URL'),
+ _m('Subscribe to'),
$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('ul');
@@ -244,25 +244,33 @@ class OStatusSubAction extends Action
} else if (Validate::uri($this->profile_uri)) {
$this->oprofile = Ostatus_profile::ensureProfile($this->profile_uri);
} 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 true;
} 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) {
- $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) {
- $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) {
- $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) {
- $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) {
- $this->error = _m('Not a recognized feed type.');
- } catch (FeedSubException $e) {
+ $this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
+ common_debug('Not a recognized feed type.', __FILE__);
+ } catch (Exception $e) {
// 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;
@@ -315,7 +323,6 @@ class OStatusSubAction extends Action
if ($this->pullRemoteProfile()) {
$this->validateRemoteProfile();
}
-
return true;
}
@@ -391,7 +398,7 @@ class OStatusSubAction extends Action
function title()
{
// TRANS: Page title for OStatus remote subscription form
- return _m('Authorize subscription');
+ return _m('Confirm');
}
/**
diff --git a/plugins/OStatus/actions/ownerxrd.php b/plugins/OStatus/actions/ownerxrd.php
new file mode 100644
index 000000000..9c141d8c7
--- /dev/null
+++ b/plugins/OStatus/actions/ownerxrd.php
@@ -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;
+ }
+}
diff --git a/plugins/OStatus/actions/userxrd.php b/plugins/OStatus/actions/userxrd.php
new file mode 100644
index 000000000..414de9364
--- /dev/null
+++ b/plugins/OStatus/actions/userxrd.php
@@ -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;
+ }
+}
diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php
index 7ab031aa5..fcca1a252 100644
--- a/plugins/OStatus/classes/Ostatus_profile.php
+++ b/plugins/OStatus/classes/Ostatus_profile.php
@@ -1267,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)
{
// First, try the cache
@@ -1275,7 +1280,8 @@ class Ostatus_profile extends Memcached_DataObject
if ($uri !== false) {
if (is_null($uri)) {
- return null;
+ // Negative cache entry
+ throw new Exception('Not a valid webfinger address.');
}
$oprofile = Ostatus_profile::staticGet('uri', $uri);
if (!empty($oprofile)) {
@@ -1299,20 +1305,24 @@ class Ostatus_profile extends Memcached_DataObject
try {
$result = $disco->lookup($addr);
} 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);
- return null;
+ throw new Exception('Not a valid webfinger address.');
}
+ $hints = array('webfinger' => $addr);
+
foreach ($result->links as $link) {
switch ($link['rel']) {
case Discovery::PROFILEPAGE:
- $profileUrl = $link['href'];
+ $hints['profileurl'] = $profileUrl = $link['href'];
break;
case Salmon::NS_REPLIES:
- $salmonEndpoint = $link['href'];
+ $hints['salmon'] = $salmonEndpoint = $link['href'];
break;
case Discovery::UPDATESFROM:
- $feedUrl = $link['href'];
+ $hints['feedurl'] = $feedUrl = $link['href'];
break;
case Discovery::HCARD:
$hcardUrl = $link['href'];
@@ -1323,11 +1333,6 @@ class Ostatus_profile extends Memcached_DataObject
}
}
- $hints = array('webfinger' => $addr,
- 'profileurl' => $profileUrl,
- 'feedurl' => $feedUrl,
- 'salmon' => $salmonEndpoint);
-
if (isset($hcardUrl)) {
$hcardHints = self::slurpHcard($hcardUrl);
// Note: Webfinger > hcard
@@ -1410,7 +1415,7 @@ class Ostatus_profile extends Memcached_DataObject
return $oprofile;
}
- return null;
+ throw new Exception("Couldn't find a valid profile for '$addr'");
}
function saveHTMLFile($title, $rendered)
diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php
index 230d81ba1..fb8c57c71 100644
--- a/plugins/OStatus/lib/magicenvelope.php
+++ b/plugins/OStatus/lib/magicenvelope.php
@@ -156,18 +156,32 @@ class MagicEnvelope
public function verify($env)
{
if ($env['alg'] != 'RSA-SHA256') {
+ common_log(LOG_DEBUG, "Salmon error: bad algorithm");
return false;
}
if ($env['encoding'] != MagicEnvelope::ENCODING) {
+ common_log(LOG_DEBUG, "Salmon error: bad encoding");
return false;
}
$text = base64_decode($env['data']);
$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']);
}
diff --git a/plugins/OStatus/lib/ostatusqueuehandler.php b/plugins/OStatus/lib/ostatusqueuehandler.php
index 6ca31c485..d1e58f1d6 100644
--- a/plugins/OStatus/lib/ostatusqueuehandler.php
+++ b/plugins/OStatus/lib/ostatusqueuehandler.php
@@ -164,46 +164,21 @@ class OStatusQueueHandler extends QueueHandler
*/
function userFeedForNotice()
{
- // @fixme this feels VERY hacky...
- // should probably be a cleaner way to do it
-
- 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);
+ $atom = new AtomUserNoticeFeed($this->user);
+ $atom->addEntryFromNotice($this->notice);
+ $feed = $atom->getString();
+
return $feed;
}
function groupFeedForNotice($group_id)
{
- // @fixme this feels VERY hacky...
- // should probably be a cleaner way to do it
-
- ob_start();
- $api = new ApiTimelineGroupAction();
- $args = array('id' => $group_id,
- '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);
+ $group = User_group::staticGet('id', $group_id);
+
+ $atom = new AtomGroupNoticeFeed($group);
+ $atom->addEntryFromNotice($this->notice);
+ $feed = $atom->getString();
+
return $feed;
}
diff --git a/plugins/OStatus/lib/xrd.php b/plugins/OStatus/lib/xrd.php
index f00e1f809..aa13ef024 100644
--- a/plugins/OStatus/lib/xrd.php
+++ b/plugins/OStatus/lib/xrd.php
@@ -57,6 +57,9 @@ class XRD
throw new Exception("Invalid XML");
}
$xrd_element = $dom->getElementsByTagName('XRD')->item(0);
+ if (!$xrd_element) {
+ throw new Exception("Invalid XML, missing XRD root");
+ }
// Check for host-meta host
$host = $xrd_element->getElementsByTagName('Host')->item(0);
diff --git a/plugins/OStatus/actions/xrd.php b/plugins/OStatus/lib/xrdaction.php
index f574b60ee..6881292ad 100644
--- a/plugins/OStatus/actions/xrd.php
+++ b/plugins/OStatus/lib/xrdaction.php
@@ -28,32 +28,24 @@ class XrdAction extends Action
{
public $uri;
+
+ public $user;
- function prepare($args)
- {
- parent::prepare($args);
-
- $this->uri = $this->trimmed('uri');
-
- return true;
- }
-
+ public $xrd;
+
function handle()
{
- $acct = Discovery::normalize($this->uri);
-
- $xrd = new XRD();
+ $nick = $this->user->nickname;
- 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;
+ if (empty($this->xrd)) {
+ $xrd = new XRD();
+ } else {
+ $xrd = $this->xrd;
}
- $xrd->subject = $this->uri;
+ if (empty($xrd->subject)) {
+ $xrd->subject = Discovery::normalize($this->uri);
+ }
$xrd->alias[] = common_profile_url($nick);
$xrd->links[] = array('rel' => Discovery::PROFILEPAGE,
'type' => 'text/html',
diff --git a/plugins/OStatus/theme/base/css/ostatus.css b/plugins/OStatus/theme/base/css/ostatus.css
index ac668623d..f7d9853cf 100644
--- a/plugins/OStatus/theme/base/css/ostatus.css
+++ b/plugins/OStatus/theme/base/css/ostatus.css
@@ -52,7 +52,8 @@ margin-bottom:0;
width:405px;
}
-.aside #entity_subscriptions .more {
+.aside #entity_subscriptions .more,
+.aside #entity_groups .more {
float:left;
}
diff --git a/tests/UserFeedParseTest.php b/tests/UserFeedParseTest.php
new file mode 100644
index 000000000..b3f9a6417
--- /dev/null
+++ b/tests/UserFeedParseTest.php
@@ -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;
diff --git a/theme/base/css/display.css b/theme/base/css/display.css
index b8dd561cc..01d5dd134 100644
--- a/theme/base/css/display.css
+++ b/theme/base/css/display.css
@@ -771,10 +771,12 @@ display:none;
text-align:center;
}
-.entity_moderation {
+.entity_moderation,
+.entity_role {
position:relative;
}
-.entity_moderation p {
+.entity_moderation p,
+.entity_role p {
border-radius:4px;
-moz-border-radius:4px;
-webkit-border-radius:4px;
@@ -782,13 +784,14 @@ font-weight:bold;
padding-bottom:2px;
margin-bottom:7px;
}
-.entity_moderation ul {
+.entity_moderation ul,
+.entity_role ul {
display:none;
}
-.entity_moderation:hover ul {
+.entity_moderation:hover ul,
+.entity_role:hover ul {
display:block;
-min-width:21%;
-width:100%;
+width:110%;
padding:11px;
position:absolute;
top:-1px;
diff --git a/theme/base/images/icons/README b/theme/base/images/icons/README
index ea582149f..f701959ca 100644
--- a/theme/base/images/icons/README
+++ b/theme/base/images/icons/README
@@ -45,6 +45,11 @@
White pin with green background
White underscore 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
diff --git a/theme/base/images/icons/icons-01.gif b/theme/base/images/icons/icons-01.gif
index be884ff48..bf0f1230e 100644
--- a/theme/base/images/icons/icons-01.gif
+++ b/theme/base/images/icons/icons-01.gif
Binary files differ
diff --git a/theme/default/css/display.css b/theme/default/css/display.css
index 8ae2b4014..be341813a 100644
--- a/theme/default/css/display.css
+++ b/theme/default/css/display.css
@@ -49,6 +49,7 @@ box-shadow:3px 3px 7px rgba(194, 194, 194, 0.3);
.pagination .nav_next a,
.form_settings fieldset fieldset,
.entity_moderation:hover ul,
+.entity_role:hover ul,
.dialogbox {
border-color:#DDDDDD;
}
@@ -67,6 +68,7 @@ input.submit,
.entity_actions a,
.entity_actions input,
.entity_moderation p,
+.entity_role p,
button {
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,
.entity_actions a,
.entity_actions input,
-.entity_moderation p {
+.entity_moderation p,
+.entity_role p {
color:#002FA7;
}
@@ -190,6 +193,9 @@ button.close,
.entity_sandbox input.submit,
.entity_silence input.submit,
.entity_delete input.submit,
+.entity_role p,
+.entity_role_administrator input.submit,
+.entity_role_moderator input.submit,
.notice-options .repeated,
.form_notice label[for=notice_data-geo],
button.minimize,
@@ -229,6 +235,7 @@ border-color:transparent;
#site_nav_local_views .current a,
.entity_send-a-message .form_notice,
.entity_moderation:hover ul,
+.entity_role:hover ul,
.dialogbox {
background-color:#FFFFFF;
}
@@ -319,6 +326,7 @@ background-position: 5px -852px;
}
.entity_send-a-message .form_notice,
.entity_moderation:hover ul,
+.entity_role:hover ul,
.dialogbox {
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 {
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 {
background-position: 5px -1973px;
}
diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css
index 737e3a103..db85408eb 100644
--- a/theme/identica/css/display.css
+++ b/theme/identica/css/display.css
@@ -49,6 +49,7 @@ box-shadow:3px 3px 7px rgba(194, 194, 194, 0.3);
.pagination .nav_next a,
.form_settings fieldset fieldset,
.entity_moderation:hover ul,
+.entity_role:hover ul,
.dialogbox {
border-color:#DDDDDD;
}
@@ -67,6 +68,7 @@ input.submit,
.entity_actions a,
.entity_actions input,
.entity_moderation p,
+.entity_role p,
button {
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,
.entity_actions a,
.entity_actions input,
-.entity_moderation p {
+.entity_moderation p,
+.entity_role p {
color:#002FA7;
}
@@ -191,6 +194,9 @@ button.close,
.entity_sandbox input.submit,
.entity_silence input.submit,
.entity_delete input.submit,
+.entity_role p,
+.entity_role_administrator input.submit,
+.entity_role_moderator input.submit,
.notice-options .repeated,
.form_notice label[for=notice_data-geo],
button.minimize,
@@ -230,6 +236,7 @@ border-color:transparent;
#site_nav_local_views .current a,
.entity_send-a-message .form_notice,
.entity_moderation:hover ul,
+.entity_role:hover ul,
.dialogbox {
background-color:#FFFFFF;
}
@@ -319,6 +326,7 @@ background-position: 5px -852px;
}
.entity_send-a-message .form_notice,
.entity_moderation:hover ul,
+.entity_role:hover ul,
.dialogbox {
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 {
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 {
background-position: 5px -1973px;
}