* @link http://status.net/
*/
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
-
-require_once(INSTALLDIR.'/lib/profilelist.php');
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* List of group members
{
parent::prepare($args);
- if ($this->scoped->id != $this->target->id) {
+ if (!$this->target->sameAs($this->scoped)) {
// TRANS: Client error displayed when trying to approve group applicants without being a group administrator.
- $this->clientError(_('You may only approve your own pending subscriptions.'));
+ throw new ClientException(_('You may only approve your own pending subscriptions.'));
}
return true;
}
$cnt = 0;
- $members = $this->target->getRequests($offset, $limit);
-
- if ($members) {
- // @fixme change!
- $member_list = new SubQueueList($members, $this);
- $cnt = $member_list->show();
+ try {
+ $subqueue = $this->target->getRequests($offset, $limit);
+ } catch (NoResultException $e) {
+ // TRANS: If no pending subscription requests are found
+ $this->element('div', null, _m('You have no pending subscription requests.'));
+ return;
}
- $members->free();
+ $list = new SubQueueList($subqueue, $this);
+ $cnt = $list->show();
+
+ $subqueue->free();
$this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE,
$this->page, 'subqueue',
array('nickname' => $this->target->getNickname())); // urgh
}
}
-
-class SubQueueList extends ProfileList
-{
- function newListItem(Profile $profile)
- {
- return new SubQueueListItem($profile, $this->action);
- }
-}
-
-class SubQueueListItem extends ProfileListItem
-{
- function showActions()
- {
- $this->startActions();
- if (Event::handle('StartProfileListItemActionElements', array($this))) {
- $this->showApproveButtons();
- Event::handle('EndProfileListItemActionElements', array($this));
- }
- $this->endActions();
- }
-
- function showApproveButtons()
- {
- $this->out->elementStart('li', 'entity_approval');
- $form = new ApproveSubForm($this->out, $this->profile);
- $form->show();
- $this->out->elementEnd('li');
- }
-}
static function getByID($id)
{
if (empty($id)) {
- throw new ServerException('Empty ID on lookup');
+ throw new EmptyIdException(get_called_class());
}
// getByPK throws exception if id is null
// or if the class does not have a single 'id' column as primary key
'source' => array('type' => 'varchar', 'length' => 32, 'description' => 'source of comment, like "web", "im", or "clientname"'),
'conversation' => array('type' => 'int', 'description' => 'id of root notice in this conversation'),
'repeat_of' => array('type' => 'int', 'description' => 'notice this is a repeat of'),
- 'object_type' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams object type', 'default' => 'http://activitystrea.ms/schema/1.0/note'),
+ 'object_type' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams object type', 'default' => null),
'verb' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams verb', 'default' => 'http://activitystrea.ms/schema/1.0/post'),
'scope' => array('type' => 'int',
'description' => 'bit map for distribution scope; 0 = everywhere; 1 = this server only; 2 = addressees; 4 = followers; null = default'),
*
* @return mixed Notice or null
*/
- function getCurrentNotice()
+ function getCurrentNotice(Profile $scoped=null)
{
- $notice = $this->getNotices(0, 1);
+ try {
+ $notice = $this->getNotices(0, 1, 0, 0, $scoped);
- if ($notice->fetch()) {
- if ($notice instanceof ArrayWrapper) {
- // hack for things trying to work with single notices
- return $notice->_items[0];
+ if ($notice->fetch()) {
+ if ($notice instanceof ArrayWrapper) {
+ // hack for things trying to work with single notices
+ // ...but this shouldn't happen anymore I think. Keeping it for safety...
+ return $notice->_items[0];
+ }
+ return $notice;
}
- return $notice;
+ } catch (PrivateStreamException $e) {
+ // Maybe we should let this through if it's handled well upstream
+ return null;
}
return null;
*/
function getRequests($offset=0, $limit=null)
{
- $qry =
- 'SELECT profile.* ' .
- 'FROM profile JOIN subscription_queue '.
- 'ON profile.id = subscription_queue.subscriber ' .
- 'WHERE subscription_queue.subscribed = %d ' .
- 'ORDER BY subscription_queue.created DESC ';
-
- if ($limit != null) {
- if (common_config('db','type') == 'pgsql') {
- $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
- } else {
- $qry .= ' LIMIT ' . $offset . ', ' . $limit;
- }
+ // FIXME: mysql only
+ $subqueue = new Profile();
+ $subqueue->joinAdd(array('id', 'subscription_queue:subscriber'));
+ $subqueue->whereAdd(sprintf('subscription_queue.subscribed = %d', $this->getID()));
+ $subqueue->limit($offset, $limit);
+ $subqueue->orderBy('subscription_queue.created', 'DESC');
+ if (!$subqueue->find()) {
+ throw new NoResultException($subqueue);
}
-
- $members = new Profile();
-
- $members->query(sprintf($qry, $this->id));
- return $members;
+ return $subqueue;
}
function subscriptionCount()
return Subscription::exists($this, $other);
}
+ function readableBy(Profile $other=null)
+ {
+ // If it's not a private stream, it's readable by anyone
+ if (!$this->isPrivateStream()) {
+ return true;
+ }
+
+ // If it's a private stream, $other must be a subscriber to $this
+ return is_null($other) ? false : $other->isSubscribed($this);
+ }
+
+ function requiresSubscriptionApproval(Profile $other=null)
+ {
+ if (!$this->isLocal()) {
+ // We don't know for remote users, and we'll always be able to send
+ // the request. Whether it'll work immediately or require moderation
+ // can be determined in another function.
+ return false;
+ }
+
+ // Assume that profiles _we_ subscribe to are permitted. Could be made configurable.
+ if (!is_null($other) && $this->isSubscribed($other)) {
+ return false;
+ }
+
+ // If the local user either has a private stream (implies the following)
+ // or user has a moderation policy for new subscriptions, return true.
+ return $this->getUser()->private_stream || $this->getUser()->subscribe_policy === User::SUBSCRIBE_POLICY_MODERATE;
+ }
+
/**
* Check if a pending subscription request is outstanding for this...
*
function _deleteSubscriptions()
{
$sub = new Subscription();
- $sub->subscriber = $this->id;
-
+ $sub->subscriber = $this->getID();
$sub->find();
while ($sub->fetch()) {
- $other = Profile::getKV('id', $sub->subscribed);
- if (empty($other)) {
- continue;
- }
- if ($other->id == $this->id) {
- continue;
+ try {
+ $other = $sub->getSubscribed();
+ if (!$other->sameAs($this)) {
+ Subscription::cancel($this, $other);
+ }
+ } catch (NoResultException $e) {
+ // Profile not found
+ common_log(LOG_INFO, 'Subscribed profile id=='.$sub->subscribed.' not found when deleting profile id=='.$this->getID().', ignoring...');
+ } catch (ServerException $e) {
+ // Subscription cancel failed
+ common_log(LOG_INFO, 'Subscribed profile id=='.$other->getID().' could not be reached for unsubscription notice when deleting profile id=='.$this->getID().', ignoring...');
}
- Subscription::cancel($this, $other);
}
- $subd = new Subscription();
- $subd->subscribed = $this->id;
- $subd->find();
+ $sub = new Subscription();
+ $sub->subscribed = $this->getID();
+ $sub->find();
- while ($subd->fetch()) {
- $other = Profile::getKV('id', $subd->subscriber);
- if (empty($other)) {
- continue;
- }
- if ($other->id == $this->id) {
- continue;
+ while ($sub->fetch()) {
+ try {
+ $other = $sub->getSubscriber();
+ common_log(LOG_INFO, 'Subscriber profile id=='.$sub->subscribed.' not found when deleting profile id=='.$this->getID().', ignoring...');
+ if (!$other->sameAs($this)) {
+ Subscription::cancel($other, $this);
+ }
+ } catch (NoResultException $e) {
+ // Profile not found
+ common_log(LOG_INFO, 'Subscribed profile id=='.$sub->subscribed.' not found when deleting profile id=='.$this->getID().', ignoring...');
+ } catch (ServerException $e) {
+ // Subscription cancel failed
+ common_log(LOG_INFO, 'Subscriber profile id=='.$other->getID().' could not be reached for unsubscription notice when deleting profile id=='.$this->getID().', ignoring...');
}
- Subscription::cancel($other, $this);
}
+ // Finally delete self-subscription
$self = new Subscription();
-
- $self->subscriber = $this->id;
- $self->subscribed = $this->id;
-
+ $self->subscriber = $this->getID();
+ $self->subscribed = $this->getID();
$self->delete();
}
}
if (Event::handle('StartSubscribe', array($subscriber, $other))) {
- $otherUser = User::getKV('id', $other->id);
- if ($otherUser instanceof User && $otherUser->subscribe_policy == User::SUBSCRIBE_POLICY_MODERATE && !$force) {
+ // unless subscription is forced, the user policy for subscription approvals is tested
+ if (!$force && $other->requiresSubscriptionApproval($subscriber)) {
try {
$sub = Subscription_queue::saveNew($subscriber, $other);
$sub->notify();
$sub = Subscription_queue::getSubQueue($subscriber, $other);
}
} else {
+ $otherUser = User::getKV('id', $other->id);
$sub = self::saveNew($subscriber, $other);
$sub->notify();
}
static function findLocalObject(array $uris, $type=ActivityObject::NOTE) {
- $object = null;
- // TODO: Extend this in plugins etc.
- if (Event::handle('StartFindLocalActivityObject', array($uris, $type, &$object))) {
+ $obj_class = null;
+ // TODO: Extend this in plugins etc. and describe in EVENTS.txt
+ if (Event::handle('StartFindLocalActivityObject', array($uris, $type, &$obj_class))) {
switch (self::resolveUri($type)) {
case ActivityObject::PERSON:
// GROUP will also be here in due time...
- $object = new Profile();
+ $obj_class = 'Profile';
break;
default:
- $object = new Notice();
+ $obj_class = 'Notice';
}
}
- foreach (array_unique($uris) as $uri) {
+ $object = null;
+ $uris = array_unique($uris);
+ foreach ($uris as $uri) {
try {
// the exception thrown will cancel before reaching $object
- $object = call_user_func(array($object, 'fromUri'), $uri);
+ $object = call_user_func("{$obj_class}::fromUri", $uri);
break;
- } catch (Exception $e) {
- common_debug('Could not find local activity object from uri: '.$uri);
+ } catch (UnknownUriException $e) {
+ common_debug('Could not find local activity object from uri: '.$e->object_uri);
}
}
- if (!empty($object)) {
- Event::handle('EndFindLocalActivityObject', array($object->getUri(), $type, $object));
- } else {
- throw new ServerException('Could not find any activityobject stored locally with given URI');
+ if (!$object instanceof Managed_DataObject) {
+ throw new ServerException('Could not find any activityobject stored locally with given URIs: '.var_export($uris,true));
}
+ Event::handle('EndFindLocalActivityObject', array($object->getUri(), $object->getObjectType(), $object));
return $object;
}
break;
default:
if (strncmp($element, 'statusnet_', 10) == 0) {
+ if ($element === 'statusnet_in_groups' && is_array($value)) {
+ // QVITTERFIX because it would cause an array to be sent as $value
+ // THIS IS UNDOCUMENTED AND SHOULD NEVER BE RELIED UPON (qvitter uses json output)
+ $value = json_encode($value);
+ }
$this->element('statusnet:'.substr($element, 10), null, $value);
} else {
$this->element($element, null, $value);
/**
* StatusNet, the distributed open-source microblogging tool
*
- * Form for leaving a group
+ * Form for approving or reject a pending subscription request
*
* PHP version 5
*
* @link http://status.net/
*/
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
-
-require_once INSTALLDIR.'/lib/form.php';
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
- * Form for leaving a group
+ * Form for approving or reject a pending subscription request
*
* @category Form
* @package StatusNet
function formActions()
{
// TRANS: Submit button text to accept a subscription request on approve sub form.
- $this->out->submit('approve', _m('BUTTON','Accept'));
+ $this->out->submit($this->id().'-approve', _m('BUTTON','Accept'), 'submit approve', 'approve');
// TRANS: Submit button text to reject a subscription request on approve sub form.
- $this->out->submit('cancel', _m('BUTTON','Reject'));
+ $this->out->submit($this->id().'-cancel', _m('BUTTON','Reject'), 'submit cancel', 'cancel');
}
}
--- /dev/null
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for an exception when a database lookup returns no results
+ *
+ * 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 Exception
+ * @package GNUsocial
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ * @copyright 2013 Free Software Foundation, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link http://www.gnu.org/software/social/
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class EmptyIdException extends ServerException
+{
+ public function __construct($called_class)
+ {
+ parent::__construct(sprintf(_('Empty ID value was given to query for a "%s" object'), $called_class));
+ }
+}
define('GNUSOCIAL_ENGINE_URL', 'https://www.gnu.org/software/social/');
define('GNUSOCIAL_BASE_VERSION', '1.2.0');
-define('GNUSOCIAL_LIFECYCLE', 'beta2'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
+define('GNUSOCIAL_LIFECYCLE', 'beta3'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
define('GNUSOCIAL_VERSION', GNUSOCIAL_BASE_VERSION . '-' . GNUSOCIAL_LIFECYCLE);
'en-gb' => array('q' => 1, 'lang' => 'en_GB', 'name' => 'English (British)', 'direction' => 'ltr'),
'eo' => array('q' => 0.8, 'lang' => 'eo', 'name' => 'Esperanto', 'direction' => 'ltr'),
'fi' => array('q' => 1, 'lang' => 'fi', 'name' => 'Finnish', 'direction' => 'ltr'),
- 'fr-fr' => array('q' => 1, 'lang' => 'fr', 'name' => 'French', 'direction' => 'ltr'),
+ 'fr' => array('q' => 1, 'lang' => 'fr', 'name' => 'French', 'direction' => 'ltr'),
+ 'fr-fr' => array('q' => 1, 'lang' => 'fr', 'name' => 'French (France)', 'direction' => 'ltr'),
'fur' => array('q' => 0.8, 'lang' => 'fur', 'name' => 'Friulian', 'direction' => 'ltr'),
'gl' => array('q' => 0.8, 'lang' => 'gl', 'name' => 'Galician', 'direction' => 'ltr'),
'ka' => array('q' => 0.8, 'lang' => 'ka', 'name' => 'Georgian', 'direction' => 'ltr'),
$this->showParent();
} catch (NoParentNoticeException $e) {
// no parent notice
+ } catch (InvalidUrlException $e) {
+ // parent had an invalid URL so we can't show it
}
if ($this->addressees) { $this->showAddressees(); }
$this->elementEnd('div');
$this->doStreamPreparation();
// fetch the actual stream stuff
- $stream = $this->getStream();
- $this->notice = $stream->getNotices(($this->page-1) * NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+ try {
+ $stream = $this->getStream();
+ $this->notice = $stream->getNotices(($this->page-1) * NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+ } catch (PrivateStreamException $e) {
+ $this->notice = new Notice();
+ $this->notice->whereAdd('FALSE');
+ }
if ($this->page > 1 && $this->notice->N == 0) {
// TRANS: Client error when page not found (404).
--- /dev/null
+<?php
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+/**
+ * An exception for private streams
+ *
+ * @category Exception
+ * @package GNUsocial
+ * @author Mikael Nordfeldth <mmn@hethane.se>
+ * @copyright 2016 Free Software Foundation, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+class PrivateStreamException extends AuthorizationException
+{
+ var $owner = null; // owner of the private stream
+ var $reader = null; // reader, may be null if not logged in
+
+ public function __construct(Profile $owner, Profile $reader=null)
+ {
+ $this->owner = $owner;
+ $this->reader = $reader;
+
+ // TRANS: Message when a private stream attemps to be read by unauthorized third party.
+ $msg = sprintf(_m('This stream is protected and only authorized subscribers may see its contents.'));
+
+ // If $reader is a profile, authentication has been made but still not accepted (403),
+ // otherwise authentication may give access to this resource (401).
+ parent::__construct($msg, ($reader instanceof Profile ? 403 : 401));
+ }
+}
/** Action object using us. */
var $action = null;
- function __construct($profile, $action=null)
+ function __construct($profile, HTMLOutputter $action=null)
{
parent::__construct($action);
return $cnt;
}
- function newListItem(Profile $profile)
+ function newListItem(Profile $target)
{
- return new ProfileListItem($profile, $this->action);
+ return new ProfileListItem($target, $this->action);
}
function maxProfiles()
function startItem()
{
$this->out->elementStart('li', array('class' => 'profile',
- 'id' => 'profile-' . $this->profile->id));
+ 'id' => 'profile-' . $this->getTarget()->getID()));
}
function showProfile()
$this->out->elementStart('ul', 'entities users xoxo');
}
- function newListItem(Profile $profile)
+ function newListItem(Profile $target)
{
- return new ProfileMiniListItem($profile, $this->action);
+ return new ProfileMiniListItem($target, $this->action);
}
function maxProfiles()
function getNotices($offset, $limit, $since_id=null, $max_id=null)
{
if ($this->impossibleStream()) {
- return new ArrayWrapper(array());
+ throw new PrivateStreamException($this->streamProfile, $this->userProfile);
} else {
return parent::getNotices($offset, $limit, $since_id, $max_id);
}
function impossibleStream()
{
- $user = User::getKV('id', $this->streamProfile->id);
-
- // If it's a private stream, and no user or not a subscriber
-
- if (!empty($user) && $user->private_stream &&
- (empty($this->userProfile) || !$this->userProfile->isSubscribed($this->streamProfile))) {
+ if (!$this->streamProfile->readableBy($this->userProfile)) {
+ // cannot read because it's a private stream and either noone's logged in or they are not subscribers
return true;
}
--- /dev/null
+<?php
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class SubQueueList extends ProfileList
+{
+ public function newListItem(Profile $target)
+ {
+ return new SubQueueListItem($target, $this->action);
+ }
+}
--- /dev/null
+<?php
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class SubQueueListItem extends ProfileListItem
+{
+ public function showActions()
+ {
+ $this->startActions();
+ if (Event::handle('StartProfileListItemActionElements', array($this))) {
+ $this->showApproveButtons();
+ Event::handle('EndProfileListItemActionElements', array($this));
+ }
+ $this->endActions();
+ }
+
+ public function showApproveButtons()
+ {
+ $this->out->elementStart('li', 'entity_approval');
+ $form = new ApproveSubForm($this->out, $this->profile);
+ $form->show();
+ $this->out->elementEnd('li');
+ }
+}
'uri' => $uri,
'verb' => ActivityVerb::UNFAVORITE,
'object_type' => (($notice->verb == ActivityVerb::POST) ?
- $notice->object_type : ActivityObject::ACTIVITY)));
+ $notice->object_type : null)));
return true;
}
* @link http://status.net/
*/
-if (!defined('STATUSNET')) {
- // This check helps protect against security problems;
- // your code file can't be executed directly from the web.
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Event plugin
}
function types() {
- return array(Happening::OBJECT_TYPE,
+ return array(Happening::OBJECT_TYPE);
+ }
+
+ function verbs() {
+ return array(ActivityVerb::POST,
RSVP::POSITIVE,
RSVP::NEGATIVE,
RSVP::POSSIBLE);
$url = $url_object->item(0)->nodeValue;
}
- $notice = null;
-
switch ($activity->verb) {
case ActivityVerb::POST:
// FIXME: get startTime, endTime, location and URL
break;
case RSVP::POSITIVE:
case RSVP::NEGATIVE:
+ case RSVP::POSSIBLE:
+ return Notice::saveActivity($activity, $actor, $options);
+ break;
+ default:
+ // TRANS: Exception thrown when event plugin comes across a undefined verb.
+ throw new Exception(_m('Unknown verb for events.'));
+ }
+ }
+
+ protected function saveObjectFromActivity(Activity $activity, Notice $stored, array $options=array())
+ {
+ $happeningObj = $activity->objects[0];
+
+ switch ($activity->verb) {
+ case RSVP::POSITIVE:
+ case RSVP::NEGATIVE:
case RSVP::POSSIBLE:
$happening = Happening::getKV('uri', $happeningObj->id);
if (empty($happening)) {
// TRANS: Exception thrown when trying to RSVP for an unknown event.
throw new Exception(_m('RSVP for unknown event.'));
}
- $notice = RSVP::saveNew($actor, $happening, $activity->verb, $options);
+ $object = RSVP::saveNewFromNotice($stored, $happening, $activity->verb);
+ // Our data model expects this
+ $stored->object_type = $activity->verb;
+ return $object;
break;
default:
- // TRANS: Exception thrown when event plugin comes across a undefined verb.
- throw new Exception(_m('Unknown verb for events.'));
+ common_log(LOG_ERR, 'Unknown verb for events.');
+ return NULL;
}
-
- return $notice;
}
/**
function saveNew(Profile $profile, $event, $verb, array $options = array())
{
- if (array_key_exists('uri', $options)) {
- $other = RSVP::getKV('uri', $options['uri']);
- if (!empty($other)) {
- // TRANS: Client exception thrown when trying to save an already existing RSVP ("please respond").
- throw new ClientException(_m('RSVP already exists.'));
- }
+ $eventNotice = $event->getNotice();
+ $options = array_merge(array('source' => 'web'), $options);
+
+ $act = new Activity();
+ $act->type = ActivityObject::ACTIVITY;
+ $act->verb = $verb;
+ $act->time = $options['created'] ? strtotime($options['created']) : time();
+ $act->title = _m("RSVP");
+ $act->actor = $profile->asActivityObject();
+ $act->target = $eventNotice->asActivityObject();
+ $act->objects = array(clone($act->target));
+ $act->content = RSVP::toHTML($profile, $event, self::codeFor($verb));
+
+ $act->id = common_local_url('showrsvp', array('id' => UUID::gen()));
+ $act->link = $act->id;
+
+ $saved = Notice::saveActivity($act, $profile, $options);
+
+ return $saved;
+ }
+
+ function saveNewFromNotice($notice, $event, $verb)
+ {
+ $other = RSVP::getKV('uri', $notice->uri);
+ if (!empty($other)) {
+ // TRANS: Client exception thrown when trying to save an already existing RSVP ("please respond").
+ throw new ClientException(_m('RSVP already exists.'));
}
+ $profile = $notice->getProfile();
+
try {
$other = RSVP::getByKeys( [ 'profile_id' => $profile->getID(),
'event_uri' => $event->getUri(),
$rsvp = new RSVP();
- $rsvp->id = UUID::gen();
- $rsvp->profile_id = $profile->getID();
- $rsvp->event_uri = $event->getUri();
- $rsvp->response = self::codeFor($verb);
-
- if (array_key_exists('created', $options)) {
- $rsvp->created = $options['created'];
- } else {
- $rsvp->created = common_sql_now();
- }
-
- if (array_key_exists('uri', $options)) {
- $rsvp->uri = $options['uri'];
- } else {
- $rsvp->uri = common_local_url('showrsvp',
- array('id' => $rsvp->id));
- }
+ preg_match('/\/([^\/]+)\/*/', $notice->uri, $match);
+ $rsvp->id = $match[1] ? $match[1] : UUID::gen();
+ $rsvp->profile_id = $profile->id;
+ $rsvp->event_id = $event->id;
+ $rsvp->response = self::codeFor($verb);
+ $rsvp->created = $notice->created;
+ $rsvp->uri = $notice->uri;
$rsvp->insert();
self::blow('rsvp:for-event:%s', $event->getUri());
- // XXX: come up with something sexier
-
- $content = $rsvp->asString();
-
- $rendered = $rsvp->asHTML();
-
- $options = array_merge(array('object_type' => $verb),
- $options);
-
- if (!array_key_exists('uri', $options)) {
- $options['uri'] = $rsvp->uri;
- }
-
- $eventNotice = $event->getNotice();
-
- if (!empty($eventNotice)) {
- $options['reply_to'] = $eventNotice->getID();
- }
-
- $saved = Notice::saveNew($profile->getID(),
- $content,
- array_key_exists('source', $options) ?
- $options['source'] : 'web',
- $options);
-
- return $saved;
+ return $rsvp;
}
function codeFor($verb)
{
assert($this->isMyActivity($act));
- // If empty, we should've created it ourselves on our node.
- if (!isset($options['created'])) {
- $options['created'] = !empty($act->time) ? common_sql_date($act->time) : common_sql_now();
- }
-
// We must have an objects[0] here because in isMyActivity we require the count to be == 1
$actobj = $act->objects[0];
$object = Fave::saveActivityObject($actobj, $stored);
- $stored->object_type = $object->getObjectType();
return $object;
}
public function delete($useWhere=false)
{
- $profile = Profile::getKV('id', $this->user_id);
- $notice = Notice::getKV('id', $this->notice_id);
-
$result = null;
- if (Event::handle('StartDisfavorNotice', array($profile, $notice, &$result))) {
+ try {
+ $profile = $this->getActor();
+ $notice = $this->getTarget();
- $result = parent::delete($useWhere);
+ if (Event::handle('StartDisfavorNotice', array($profile, $notice, &$result))) {
- self::blowCacheForProfileId($this->user_id);
- self::blowCacheForNoticeId($this->notice_id);
- self::blow('popular');
+ $result = parent::delete($useWhere);
- if ($result) {
- Event::handle('EndDisfavorNotice', array($profile, $notice));
+ if ($result !== false) {
+ Event::handle('EndDisfavorNotice', array($profile, $notice));
+ }
}
+
+ } catch (NoResultException $e) {
+ // In case there's some inconsistency where the profile or notice was deleted without losing the fave db entry
+ common_log(LOG_INFO, '"'.get_class($e->obj).'" with id=='.var_export($e->obj->id, true).' object not found when deleting favorite, ignoring...');
+ } catch (EmptyIdException $e) {
+ // Some buggy instances of GNU social have had favroites with notice id==0 stored in the database
+ common_log(LOG_INFO, '"'.get_class($e->obj).'"object had empty id deleting favorite, ignoring...');
+ }
+
+ // If we catch an exception above, then $result===null because parent::delete only returns an int>=0 or boolean false
+ if (is_null($result)) {
+ // Delete it without the event, as something is wrong and we don't want it anyway.
+ $result = parent::delete($useWhere);
}
+ // Err, apparently we can reference $this->user_id after parent::delete,
+ // I guess it's safe because this is the order it was before!
+ self::blowCacheForProfileId($this->user_id);
+ self::blowCacheForNoticeId($this->notice_id);
+ self::blow('popular');
+
return $result;
}
public function getTarget()
{
- // throws exception on failure
- $target = new Notice();
- $target->id = $this->notice_id;
- if (!$target->find(true)) {
- throw new NoResultException($target);
- }
-
- return $target;
+ return Notice::getByID($this->notice_id);
}
public function getTargetObject()
$hasFN = ($fullname !== '') ? 'nickname' : 'fn nickname entity_nickname';
$this->elementStart('a', array('href' => $profile,
'class' => 'url '.$hasFN));
- $this->raw($nickname);
+ $this->text($nickname);
$this->elementEnd('a');
if (!is_null($fullname)) {
$this->elementStart('div', 'fn entity_fn');
- $this->raw($fullname);
+ $this->text($fullname);
$this->elementEnd('div');
}
if (!is_null($location)) {
$this->elementStart('div', 'label entity_location');
- $this->raw($location);
+ $this->text($location);
$this->elementEnd('div');
}
if (!is_null($homepage)) {
$this->elementStart('a', array('href' => $homepage,
'class' => 'url entity_url'));
- $this->raw($homepage);
+ $this->text($homepage);
$this->elementEnd('a');
}
if (!is_null($note)) {
$this->elementStart('div', 'note entity_note');
- $this->raw($note);
+ $this->text($note);
$this->elementEnd('div');
}
$this->elementEnd('div');
// Notice::saveActivity it will update the Notice object.
$stored->repeat_of = $sharedNotice->getID();
$stored->conversation = $sharedNotice->conversation;
- $stored->object_type = ActivityUtils::resolveUri(ActivityObject::ACTIVITY, true);
// We don't have to save a repeat in a separate table, we can
// find repeats by just looking at the notice.repeat_of field.
margin-bottom: 20px;
}
+ul.profiles.groups {
+ list-style-type:none;
+}
+
.profile_list .h-card .u-photo {
margin-right: 4px;
}
-.profile_list .h-card .p-nickname {
- display: block;
-}
table.profile_list tbody tr:nth-child(2n+1) {
background-color: #fafafa !important;
}
.entity_profile .p-nickname {
- font-size:1.4em;
+ font-weight: bold;
}
.entity_profile .p-name {
- font-size: 1.2em;
clear: left;
}
+.entity_profile .label {
+ display: block;
+}
+
.entity_profile .p-name:before {
content: "(";
font-weight:normal;
padding: 4px 4px 4px 26px;
}
-.entity_actions a, .entity_actions p, .entity_actions .entity_subscribe input, .entity_actions .entity_block input, .entity_actions .entity_moderation input, .entity_actions .entity_role input, .entity_actions .entity_nudge input, .entity_actions .entity_delete input, .entity_actions input.submit {
+.entity_actions a, .entity_actions p, .entity_actions .entity_approval input, .entity_actions .entity_subscribe input, .entity_actions .entity_block input, .entity_actions .entity_moderation input, .entity_actions .entity_role input, .entity_actions .entity_nudge input, .entity_actions .entity_delete input, .entity_actions input.submit {
background-color: #ccc !important;
border: none;
}
padding: 2px 4px 4px 28px;
}
-.entity_actions a:hover, .entity_actions p:hover, .entity_actions .entity_subscribe input:hover, .entity_actions .entity_block input:hover, .entity_actions .entity_moderation input:hover, .entity_actions .entity_role input:hover, .entity_actions .entity_nudge input:hover, .entity_actions .entity_delete input:hover, .entity_actions input.submit:hover {
+.entity_actions a:hover, .entity_actions p:hover, .entity_actions, .entity_subscribe input:hover, .entity_actions .entity_subscribe input:hover, .entity_actions .entity_block input:hover, .entity_actions .entity_moderation input:hover, .entity_actions .entity_role input:hover, .entity_actions .entity_nudge input:hover, .entity_actions .entity_delete input:hover, .entity_actions input.submit:hover {
background-color: #f2f2f2 !important;
}
}
.profile .entity_profile {
-margin-bottom:0;
+margin-bottom:10px;
min-height:60px;
}
.entity_clear input.submit,
.entity_flag input.submit,
.entity_flag p,
+.entity_approval input.submit,
.entity_subscribe input.submit,
#realtime_play,
#realtime_pause,
.entity_flag p {
background-position: 5px -2105px;
}
+.entity_approval input.approve,
.entity_subscribe input.accept {
background-position: 5px -2171px;
}
+.entity_approval input.cancel,
.entity_subscribe input.reject {
background-position: 5px -2237px;
}
font-size: 1.4em;
}
-.profile_list .label {
- display: block;
- margin-left: 59px !important;
-}
-
.profile_list .note {
font-size: 0.88em;
line-height: 1.36em;
margin-right: 10px;
}
-.profile .entity_profile .p-name,
-.profile .entity_profile .p-locality,
-.profile .entity_profile .role,
-.profile .entity_profile > span,
-.profile .entity_profile .u-url[rel~="contact"] {
+
+/* these apply to both profiles and groups */
+.entity_profile .p-name,
+.entity_profile .p-locality,
+.entity_profile .role,
+.entity_profile > span,
+.entity_profile .u-url[rel~="contact"] {
display: inline;
- margin-left: 0;
- font-size:0.88em;
+ font-size:1.0em;
color:#9197a3;
}
-.entity_profile .p-name:before,
+.entity_profile .p-nickname {
+ color:#666 !important;
+ display: block !important;
+}
+
+.entity_profile .label {
+ display: inline !important;
+}
+
.entity_profile .p-name:after {
content: "";
}
.profile .entity_profile .u-url {
- font-size:0.88em;
+ font-size:1.0em;
+}
+
+/* clear parethesis from base theme */
+.entity_profile .p-name:before,
+.entity_profile .p-name:after {
+ content: "";
}
.entity_profile .role:before {
content: ")";
}
+.entity_profile .label:before,
.profile .entity_profile .p-locality:before {
content:" ยท ";
}