From 3bb42d117027ebf610481ca3b0733854e0127e56 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 3 Mar 2010 19:00:02 +0000 Subject: Use poster's subscribed groups to disambiguate group linking when a remote group and a local group exist with the same name. (If you're a member of two groups with the same name though, there's not a defined winner.) --- classes/Profile.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'classes/Profile.php') diff --git a/classes/Profile.php b/classes/Profile.php index 470ef3320..9c2fa7a0c 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -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) { $avatar = $this->getAvatar($size); -- cgit v1.2.3-54-g00ecf From 4a2511139eaafcbe93a2e720e0c6f170ecb00d77 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 3 Mar 2010 15:43:49 -0800 Subject: Initial user role controls on profile pages, for owner to add/remove administrator and moderator options. Buttons need to be themed. --- actions/grantrole.php | 99 ++++++++++++++++++++++++++++++++++++++++++++++++ actions/revokerole.php | 99 ++++++++++++++++++++++++++++++++++++++++++++++++ classes/Profile.php | 4 ++ classes/Profile_role.php | 17 +++++++++ lib/grantroleform.php | 93 +++++++++++++++++++++++++++++++++++++++++++++ lib/revokeroleform.php | 93 +++++++++++++++++++++++++++++++++++++++++++++ lib/right.php | 2 + lib/router.php | 1 + lib/userprofile.php | 26 +++++++++++++ 9 files changed, 434 insertions(+) create mode 100644 actions/grantrole.php create mode 100644 actions/revokerole.php create mode 100644 lib/grantroleform.php create mode 100644 lib/revokeroleform.php (limited to 'classes/Profile.php') 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 @@ +. + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @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 + * @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 @@ +. + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @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 + * @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/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/lib/grantroleform.php b/lib/grantroleform.php new file mode 100644 index 000000000..b5f952746 --- /dev/null +++ b/lib/grantroleform.php @@ -0,0 +1,93 @@ +. + * + * @category Form + * @package StatusNet + * @author Evan Prodromou , Brion Vibber + * @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 + * @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 @@ +. + * + * @category Form + * @package StatusNet + * @author Evan Prodromou , Brion Vibber + * @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 + * @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..15f88c959 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', 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', -- cgit v1.2.3-54-g00ecf From ce92bc71431ec878c391b1ac6e16fd59cafd50b1 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 11 Mar 2010 11:01:01 -0800 Subject: Drop timestamp cutoff parameter from User::getCurrentNotice() and Profile::getCurrentNotice(). It's not currently used, and won't be efficient when we update the notice.profile_id_idx index to optimize for our id-based sorting when pulling user post lists for profile pages, feeds etc. --- classes/Profile.php | 12 +++++++----- classes/User.php | 9 +++++++-- 2 files changed, 14 insertions(+), 7 deletions(-) (limited to 'classes/Profile.php') diff --git a/classes/Profile.php b/classes/Profile.php index 0322c9358..91f6e4692 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -147,14 +147,16 @@ class Profile extends Memcached_DataObject return ($this->fullname) ? $this->fullname : $this->nickname; } - # Get latest notice on or before date; default now - function getCurrentNotice($dt=null) + /** + * Get the most recent notice posted by this user, if any. + * + * @return mixed Notice or null + */ + function getCurrentNotice() { $notice = new Notice(); $notice->profile_id = $this->id; - if ($dt) { - $notice->whereAdd('created < "' . $dt . '"'); - } + // @fixme change this to sort on notice.id only when indexes are updated $notice->orderBy('created DESC, notice.id DESC'); $notice->limit(1); if ($notice->find(true)) { diff --git a/classes/User.php b/classes/User.php index aa9fbf948..330da039b 100644 --- a/classes/User.php +++ b/classes/User.php @@ -132,13 +132,18 @@ class User extends Memcached_DataObject return !in_array($nickname, $blacklist); } - function getCurrentNotice($dt=null) + /** + * Get the most recent notice posted by this user, if any. + * + * @return mixed Notice or null + */ + function getCurrentNotice() { $profile = $this->getProfile(); if (!$profile) { return null; } - return $profile->getCurrentNotice($dt); + return $profile->getCurrentNotice(); } function getCarrier() -- cgit v1.2.3-54-g00ecf From 441e52718e4db4eb45bd5c76c5af446496f56f96 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 15 Mar 2010 15:08:16 -0700 Subject: Background deletion of user accounts. Notices are deleted in chunks, then the user itself when they're all gone. While deletion is in progress, the account is locked with the 'deleted' role, which disables all actions with rights control. Todo: * Pretty up the notice on the profile page about the pending delete. Show status? * Possibly more thorough account disabling, such as disallowing all use for login and access. * Improve error recovery; worst case is that an account gets left locked in 'deleted' state but the queue jobs have gotten dropped out. This would leave the username in use and any undeleted notices in place. --- actions/deleteuser.php | 10 ++++- classes/Profile.php | 3 ++ classes/Profile_role.php | 1 + lib/deluserqueuehandler.php | 95 +++++++++++++++++++++++++++++++++++++++++++++ lib/queuemanager.php | 3 ++ lib/userprofile.php | 11 ++++++ 6 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 lib/deluserqueuehandler.php (limited to 'classes/Profile.php') diff --git a/actions/deleteuser.php b/actions/deleteuser.php index c4f84fad2..4e6b27395 100644 --- a/actions/deleteuser.php +++ b/actions/deleteuser.php @@ -162,7 +162,15 @@ class DeleteuserAction extends ProfileFormAction function handlePost() { if (Event::handle('StartDeleteUser', array($this, $this->user))) { - $this->user->delete(); + // Mark the account as deleted and shove low-level deletion tasks + // to background queues. Removing a lot of posts can take a while... + if (!$this->user->hasRole(Profile_role::DELETED)) { + $this->user->grantRole(Profile_role::DELETED); + } + + $qm = QueueManager::get(); + $qm->enqueue($this->user, 'deluser'); + Event::handle('EndDeleteUser', array($this, $this->user)); } } diff --git a/classes/Profile.php b/classes/Profile.php index 91f6e4692..eded1ff71 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -732,6 +732,9 @@ class Profile extends Memcached_DataObject function hasRight($right) { $result = false; + if ($this->hasRole(Profile_role::DELETED)) { + return false; + } if (Event::handle('UserRightsCheck', array($this, $right, &$result))) { switch ($right) { diff --git a/classes/Profile_role.php b/classes/Profile_role.php index d0a0b31f0..e7aa1f0f0 100644 --- a/classes/Profile_role.php +++ b/classes/Profile_role.php @@ -53,6 +53,7 @@ class Profile_role extends Memcached_DataObject const ADMINISTRATOR = 'administrator'; const SANDBOXED = 'sandboxed'; const SILENCED = 'silenced'; + const DELETED = 'deleted'; // Pending final deletion of notices... public static function isValid($role) { diff --git a/lib/deluserqueuehandler.php b/lib/deluserqueuehandler.php new file mode 100644 index 000000000..4a1233a5e --- /dev/null +++ b/lib/deluserqueuehandler.php @@ -0,0 +1,95 @@ +. + */ + +/** + * Background job to delete prolific users without disrupting front-end too much. + * + * Up to 50 messages are deleted on each run through; when all messages are gone, + * the actual account is deleted. + * + * @package QueueHandler + * @maintainer Brion Vibber + */ + +class DelUserQueueHandler extends QueueHandler +{ + const DELETION_WINDOW = 50; + + public function transport() + { + return 'deluser'; + } + + public function handle($user) + { + if (!($user instanceof User)) { + common_log(LOG_ERR, "Got a bogus user, not deleting"); + return true; + } + + $user = User::staticGet('id', $user->id); + if (!$user) { + common_log(LOG_INFO, "User {$user->nickname} was deleted before we got here."); + return true; + } + + if (!$user->hasRole(Profile_role::DELETED)) { + common_log(LOG_INFO, "User {$user->nickname} is not pending deletion; aborting."); + return true; + } + + $notice = $this->getNextBatch($user); + if ($notice->N) { + common_log(LOG_INFO, "Deleting next {$notice->N} notices by {$user->nickname}"); + while ($notice->fetch()) { + $del = clone($notice); + $del->delete(); + } + + // @todo improve reliability in case we died during the above deletions + // with a fatal error. If the job is lost, we should perform some kind + // of garbage collection later. + + // Queue up the next batch. + $qm = QueueManager::get(); + $qm->enqueue($user, 'deluser'); + } else { + // Out of notices? Let's finish deleting this guy! + $user->delete(); + common_log(LOG_INFO, "User $user->id $user->nickname deleted."); + return true; + } + + return true; + } + + /** + * Fetch the next self::DELETION_WINDOW messages for this user. + * @return Notice + */ + protected function getNextBatch(User $user) + { + $notice = new Notice(); + $notice->profile_id = $user->id; + $notice->limit(self::DELETION_WINDOW); + $notice->find(); + return $notice; + } + +} diff --git a/lib/queuemanager.php b/lib/queuemanager.php index 87bd356aa..0829c8a8b 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -264,6 +264,9 @@ abstract class QueueManager extends IoManager $this->connect('sms', 'SmsQueueHandler'); } + // Background user management tasks... + $this->connect('deluser', 'DelUserQueueHandler'); + // Broadcasting profile updates to OMB remote subscribers $this->connect('profile', 'ProfileQueueHandler'); diff --git a/lib/userprofile.php b/lib/userprofile.php index 8464c2446..2c3b1ea45 100644 --- a/lib/userprofile.php +++ b/lib/userprofile.php @@ -228,6 +228,17 @@ class UserProfile extends Widget function showEntityActions() { + if ($this->profile->hasRole(Profile_role::DELETED)) { + $this->out->elementStart('div', 'entity_actions'); + $this->out->element('h2', null, _('User actions')); + $this->out->elementStart('ul'); + $this->out->elementStart('p', array('class' => 'profile_deleted')); + $this->out->text(_('User deletion in progress...')); + $this->out->elementEnd('p'); + $this->out->elementEnd('ul'); + $this->out->elementEnd('div'); + return; + } if (Event::handle('StartProfilePageActionsSection', array(&$this->out, $this->profile))) { $cur = common_current_user(); -- cgit v1.2.3-54-g00ecf From c1c7feedbd11cc291a0fb68ea8c4d322eb8bf538 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 31 Mar 2010 15:02:19 -0400 Subject: do complete unsubscribe process when deleting a user --- classes/Profile.php | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) (limited to 'classes/Profile.php') diff --git a/classes/Profile.php b/classes/Profile.php index eded1ff71..5de35c191 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -577,11 +577,41 @@ class Profile extends Memcached_DataObject { $sub = new Subscription(); $sub->subscriber = $this->id; - $sub->delete(); + + $sub->find(); + + while ($sub->fetch()) { + $other = Profile::staticGet('id', $sub->subscribed); + if (empty($other)) { + continue; + } + if ($other->id == $this->id) { + continue; + } + Subscription::cancel($this, $other); + } $subd = new Subscription(); $subd->subscribed = $this->id; - $subd->delete(); + $subd->find(); + + while ($subd->fetch()) { + $other = Profile::staticGet('id', $subd->subscriber); + if (empty($other)) { + continue; + } + if ($other->id == $this->id) { + continue; + } + Subscription::cancel($other, $this); + } + + $self = new Subscription(); + + $self->subscriber = $this->id; + $self->subscribed = $this->id; + + $self->delete(); } function _deleteMessages() -- cgit v1.2.3-54-g00ecf From f1c01f9ead4a5f4da7c6964f0998831fa31f8a16 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 1 Apr 2010 10:15:40 -0700 Subject: Temporary hack until notice_profile_id_idx is updated to (profile_id, id) instead of (profile_id, created, id). It's been falling back to PRIMARY instead, which is really very inefficient for a profile that hasn't posted in a few months. Even though forcing the index will cause a filesort, it's usually going to be better. Even for large profiles it seems much faster than the badly-indexed query. --- classes/Profile.php | 63 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 16 deletions(-) (limited to 'classes/Profile.php') diff --git a/classes/Profile.php b/classes/Profile.php index 5de35c191..54f557ea7 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -225,31 +225,62 @@ class Profile extends Memcached_DataObject { $notice = new Notice(); - $notice->profile_id = $this->id; + // Temporary hack until notice_profile_id_idx is updated + // to (profile_id, id) instead of (profile_id, created, id). + // It's been falling back to PRIMARY instead, which is really + // very inefficient for a profile that hasn't posted in a few + // months. Even though forcing the index will cause a filesort, + // it's usually going to be better. + if (common_config('db', 'type') == 'mysql') { + $index = ''; + $query = + "select id from notice force index (notice_profile_id_idx) ". + "where profile_id=" . $notice->escape($this->id); + + if ($since_id != 0) { + $query .= " and id > $since_id"; + } - $notice->selectAdd(); - $notice->selectAdd('id'); + if ($max_id != 0) { + $query .= " and id < $max_id"; + } - if ($since_id != 0) { - $notice->whereAdd('id > ' . $since_id); - } + $query .= ' order by id DESC'; - if ($max_id != 0) { - $notice->whereAdd('id <= ' . $max_id); - } + if (!is_null($offset)) { + $query .= " LIMIT $limit OFFSET $offset"; + } + + $notice->query($query); + } else { + $index = ''; - $notice->orderBy('id DESC'); + $notice->profile_id = $this->id; - if (!is_null($offset)) { - $notice->limit($offset, $limit); + $notice->selectAdd(); + $notice->selectAdd('id'); + + if ($since_id != 0) { + $notice->whereAdd('id > ' . $since_id); + } + + if ($max_id != 0) { + $notice->whereAdd('id <= ' . $max_id); + } + + $notice->orderBy('id DESC'); + + if (!is_null($offset)) { + $notice->limit($offset, $limit); + } + + $notice->find(); } $ids = array(); - if ($notice->find()) { - while ($notice->fetch()) { - $ids[] = $notice->id; - } + while ($notice->fetch()) { + $ids[] = $notice->id; } return $ids; -- cgit v1.2.3-54-g00ecf From 1eec7f779fc85b530907ea31deceadb2a30d7614 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 22 Jun 2010 16:28:06 -0700 Subject: - Add profile_info tag to Atom author - Normalize xmlns:statusnet links in the API --- classes/Notice.php | 4 ++-- classes/Profile.php | 10 +++++++++- lib/apiaction.php | 2 ++ lib/atomnoticefeed.php | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) (limited to 'classes/Profile.php') diff --git a/classes/Notice.php b/classes/Notice.php index f8eda5777..c752e35a7 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1190,7 +1190,7 @@ class Notice extends Memcached_DataObject 'xmlns:media' => 'http://purl.org/syndication/atommedia', 'xmlns:poco' => 'http://portablecontacts.net/spec/1.0', 'xmlns:ostatus' => 'http://ostatus.org/schema/1.0', - 'xmlns:statusnet' => 'http://status.net/ont/'); + 'xmlns:statusnet' => 'http://status.net/schema/api/1/'); } else { $attrs = array(); } @@ -1225,7 +1225,7 @@ class Notice extends Memcached_DataObject $xs->element('title', null, common_xml_safe_str($this->content)); if ($author) { - $xs->raw($profile->asAtomAuthor()); + $xs->raw($profile->asAtomAuthor($cur)); $xs->raw($profile->asActivityActor()); } diff --git a/classes/Profile.php b/classes/Profile.php index 54f557ea7..a303469e9 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -849,15 +849,23 @@ class Profile extends Memcached_DataObject * * Assumes that Atom has been previously set up as the base namespace. * + * @param Profile $cur the current authenticated user + * * @return string */ - function asAtomAuthor() + function asAtomAuthor($cur = null) { $xs = new XMLStringer(true); $xs->elementStart('author'); $xs->element('name', null, $this->nickname); $xs->element('uri', null, $this->getUri()); + if ($cur != null) { + $attrs = Array(); + $attrs['following'] = $cur->isSubscribed($this) ? 'true' : 'false'; + $attrs['blocking'] = $cur->hasBlocked($this) ? 'true' : 'false'; + $xs->element('statusnet:profile_info', $attrs, null); + } $xs->elementEnd('author'); return $xs->getString(); diff --git a/lib/apiaction.php b/lib/apiaction.php index 7cc473d51..226481778 100644 --- a/lib/apiaction.php +++ b/lib/apiaction.php @@ -208,11 +208,13 @@ class ApiAction extends Action // Is the requesting user following this user? $twitter_user['following'] = false; + $twitter_user['statusnet:blocking'] = false; $twitter_user['notifications'] = false; if (isset($this->auth_user)) { $twitter_user['following'] = $this->auth_user->isSubscribed($profile); + $twitter_user['statusnet:blocking'] = $this->auth_user->hasBlocked($profile); // Notifications on? $sub = Subscription::pkeyGet(array('subscriber' => diff --git a/lib/atomnoticefeed.php b/lib/atomnoticefeed.php index ef44de4b6..6ed803ce4 100644 --- a/lib/atomnoticefeed.php +++ b/lib/atomnoticefeed.php @@ -95,7 +95,7 @@ class AtomNoticeFeed extends Atom10Feed $this->addNamespace( 'statusnet', - 'http://status.net/ont/' + 'http://status.net/schema/api/1/' ); } -- cgit v1.2.3-54-g00ecf