/** * Make a new subscription * * @param Profile $subscriber party to receive new notices * @param Profile $other party sending notices; publisher * @param bool $force pass Subscription::FORCE to override local subscription approval * * @return mixed Subscription or Subscription_queue: new subscription info */ static function start(Profile $subscriber, Profile $other, $force = false) { if (!$subscriber->hasRight(Right::SUBSCRIBE)) { // TRANS: Exception thrown when trying to subscribe while being banned from subscribing. throw new Exception(_('You have been banned from subscribing.')); } if (self::exists($subscriber, $other)) { // TRANS: Exception thrown when trying to subscribe while already subscribed. throw new AlreadyFulfilledException(_('Already subscribed!')); } if ($other->hasBlocked($subscriber)) { // TRANS: Exception thrown when trying to subscribe to a user who has blocked the subscribing user. throw new Exception(_('User has blocked you.')); } if (Event::handle('StartSubscribe', array($subscriber, $other))) { // 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(); } catch (AlreadyFulfilledException $e) { $sub = Subscription_queue::getSubQueue($subscriber, $other); } } else { $otherUser = User::getKV('id', $other->id); $sub = self::saveNew($subscriber, $other); $sub->notify(); self::blow('user:notices_with_friends:%d', $subscriber->id); self::blow('subscription:by-subscriber:' . $subscriber->id); self::blow('subscription:by-subscribed:' . $other->id); $subscriber->blowSubscriptionCount(); $other->blowSubscriberCount(); if ($otherUser instanceof User && $otherUser->autosubscribe && !self::exists($other, $subscriber) && !$subscriber->hasBlocked($other)) { try { self::start($other, $subscriber); } catch (AlreadyFulfilledException $e) { // This shouldn't happen due to !self::exists above common_debug('Tried to autosubscribe a user to its new subscriber.'); } catch (Exception $e) { common_log(LOG_ERR, "Exception during autosubscribe of {$other->nickname} to profile {$subscriber->id}: {$e->getMessage()}"); } } } if ($sub instanceof Subscription) { // i.e. not Subscription_queue Event::handle('EndSubscribe', array($subscriber, $other)); } } return $sub; }
/** * Notify a user that one of their notices has been chosen as a 'fave' * * @param User $rcpt The user whose notice was faved * @param Profile $sender The user who faved the notice * @param Notice $notice The notice that was faved * * @return void */ function mail_notify_fave(User $rcpt, Profile $sender, Notice $notice) { if (!$rcpt->receivesEmailNotifications() || !$rcpt->getConfigPref('email', 'notify_fave')) { return; } // This test is actually "if the sender is sandboxed" if (!$sender->hasRight(Right::EMAILONFAVE)) { return; } if ($rcpt->hasBlocked($sender)) { // If the author has blocked us, don't spam them with a notification. return; } // We need the global mail.php for various mail related functions below. require_once INSTALLDIR . '/lib/mail.php'; $bestname = $sender->getBestName(); common_switch_locale($rcpt->language); // TRANS: Subject for favorite notification e-mail. // TRANS: %1$s is the adding user's long name, %2$s is the adding user's nickname. $subject = sprintf(_('%1$s (@%2$s) added your notice as a favorite'), $bestname, $sender->getNickname()); // TRANS: Body for favorite notification e-mail. // TRANS: %1$s is the adding user's long name, $2$s is the date the notice was created, // TRANS: %3$s is a URL to the faved notice, %4$s is the faved notice text, // TRANS: %5$s is a URL to all faves of the adding user, %6$s is the StatusNet sitename, // TRANS: %7$s is the adding user's nickname. $body = sprintf(_("%1\$s (@%7\$s) just added your notice from %2\$s" . " as one of their favorites.\n\n" . "The URL of your notice is:\n\n" . "%3\$s\n\n" . "The text of your notice is:\n\n" . "%4\$s\n\n" . "You can see the list of %1\$s's favorites here:\n\n" . "%5\$s"), $bestname, common_exact_date($notice->created), common_local_url('shownotice', array('notice' => $notice->id)), $notice->content, common_local_url('showfavorites', array('nickname' => $sender->getNickname())), common_config('site', 'name'), $sender->getNickname()) . mail_footer_block(); $headers = _mail_prepare_headers('fave', $rcpt->getNickname(), $sender->getNickname()); common_switch_locale(); mail_to_user($rcpt, $subject, $body, $headers); }
/** * notify a user of subscription by a profile (remote or local) * * This function checks to see if the listenee has an email * address and wants subscription notices. * * @param User $listenee user who's being subscribed to * @param Profile $other profile of person who's listening * * @return void */ function mail_subscribe_notify_profile($listenee, $other) { if ($other->hasRight(Right::EMAILONSUBSCRIBE) && $listenee->email && $listenee->emailnotifysub) { $profile = $listenee->getProfile(); $name = $profile->getBestName(); $long_name = $other->fullname ? $other->fullname . ' (' . $other->nickname . ')' : $other->nickname; $recipients = $listenee->email; // use the recipient's localization common_switch_locale($listenee->language); $headers = _mail_prepare_headers('subscribe', $listenee->nickname, $other->nickname); $headers['From'] = mail_notify_from(); $headers['To'] = $name . ' <' . $listenee->email . '>'; // TRANS: Subject of new-subscriber notification e-mail. // TRANS: %1$s is the subscribing user's nickname, %2$s is the StatusNet sitename. $headers['Subject'] = sprintf(_('%1$s is now listening to ' . 'your notices on %2$s.'), $other->getBestName(), common_config('site', 'name')); // TRANS: This is a paragraph in a new-subscriber e-mail. // TRANS: %s is a URL where the subscriber can be reported as abusive. $blocklink = sprintf(_("If you believe this account is being used abusively, " . "you can block them from your subscribers list and " . "report as spam to site administrators at %s"), common_local_url('block', array('profileid' => $other->id))); // TRANS: Main body of new-subscriber notification e-mail. // TRANS: %1$s is the subscriber's long name, %2$s is the StatusNet sitename, // TRANS: %3$s is the subscriber's profile URL, %4$s is the subscriber's location (or empty) // TRANS: %5$s is the subscriber's homepage URL (or empty), %6%s is the subscriber's bio (or empty) // TRANS: %7$s is a link to the addressed user's e-mail settings. $body = sprintf(_('%1$s is now listening to your notices on %2$s.' . "\n\n" . "\t" . '%3$s' . "\n\n" . '%4$s' . '%5$s' . '%6$s' . "\n" . 'Faithfully yours,' . "\n" . '%2$s.' . "\n\n" . "----\n" . "Change your email address or " . "notification options at " . '%7$s' . "\n"), $long_name, common_config('site', 'name'), $other->profileurl, $other->location ? sprintf(_("Location: %s"), $other->location) . "\n" : '', $other->homepage ? sprintf(_("Homepage: %s"), $other->homepage) . "\n" : '', ($other->bio ? sprintf(_("Bio: %s"), $other->bio) . "\n" : '') . "\n\n" . $blocklink . "\n", common_local_url('emailsettings')); // reset localization common_switch_locale(); mail_send($recipients, $headers, $body); } }
static function saveActivity(Activity $act, Profile $actor, array $options = array()) { // First check if we're going to let this Activity through from the specific actor if (!$actor->hasRight(Right::NEWNOTICE)) { common_log(LOG_WARNING, "Attempted post from user disallowed to post: " . $actor->getNickname()); // TRANS: Client exception thrown when a user tries to post while being banned. throw new ClientException(_m('You are banned from posting notices on this site.'), 403); } if (common_config('throttle', 'enabled') && !self::checkEditThrottle($actor->id)) { common_log(LOG_WARNING, 'Excessive posting by profile #' . $actor->id . '; throttled.'); // TRANS: Client exception thrown when a user tries to post too many notices in a given time frame. throw new ClientException(_m('Too many notices too fast; take a breather ' . 'and post again in a few minutes.')); } // Get ActivityObject properties $actobj = null; if (!empty($act->id)) { // implied object $options['uri'] = $act->id; $options['url'] = $act->link; } else { $actobj = count($act->objects) == 1 ? $act->objects[0] : null; if (!is_null($actobj) && !empty($actobj->id)) { $options['uri'] = $actobj->id; if ($actobj->link) { $options['url'] = $actobj->link; } elseif (preg_match('!^https?://!', $actobj->id)) { $options['url'] = $actobj->id; } } } $defaults = array('groups' => array(), 'is_local' => $actor->isLocal() ? self::LOCAL_PUBLIC : self::REMOTE, 'mentions' => array(), 'reply_to' => null, 'repeat_of' => null, 'scope' => null, 'source' => 'unknown', 'tags' => array(), 'uri' => null, 'url' => null, 'urls' => array(), 'distribute' => true); // options will have default values when nothing has been supplied $options = array_merge($defaults, $options); foreach (array_keys($defaults) as $key) { // Only convert the keynames we specify ourselves from 'defaults' array into variables ${$key} = $options[$key]; } extract($options, EXTR_SKIP); // dupe check $stored = new Notice(); if (!empty($uri) && !ActivityUtils::compareVerbs($act->verb, array(ActivityVerb::DELETE))) { $stored->uri = $uri; if ($stored->find()) { common_debug('cannot create duplicate Notice URI: ' . $stored->uri); // I _assume_ saving a Notice with a colliding URI means we're really trying to // save the same notice again... throw new AlreadyFulfilledException('Notice URI already exists'); } } $autosource = common_config('public', 'autosource'); // Sandboxed are non-false, but not 1, either if (!$actor->hasRight(Right::PUBLICNOTICE) || $source && $autosource && in_array($source, $autosource)) { // FIXME: ...what about remote nonpublic? Hmmm. That is, if we sandbox remote profiles... $stored->is_local = Notice::LOCAL_NONPUBLIC; } else { $stored->is_local = intval($is_local); } if (!$stored->isLocal()) { // Only do these checks for non-local notices. Local notices will generate these values later. if (!common_valid_http_url($url)) { common_debug('Bad notice URL: [' . $url . '], URI: [' . $uri . ']. Cannot link back to original! This is normal for shared notices etc.'); } if (empty($uri)) { throw new ServerException('No URI for remote notice. Cannot accept that.'); } } $stored->profile_id = $actor->id; $stored->source = $source; $stored->uri = $uri; $stored->url = $url; $stored->verb = $act->verb; // Notice content. We trust local users to provide HTML we like, but of course not remote users. // FIXME: What about local users importing feeds? Mirror functions must filter out bad HTML first... $content = $act->content ?: $act->summary; if (is_null($content) && !is_null($actobj)) { $content = $actobj->content ?: $actobj->summary; } $stored->rendered = $actor->isLocal() ? $content : common_purify($content); // yeah, just don't use getRendered() here since it's not inserted yet ;) $stored->content = common_strip_html($stored->rendered); // Maybe a missing act-time should be fatal if the actor is not local? if (!empty($act->time)) { $stored->created = common_sql_date($act->time); } else { $stored->created = common_sql_now(); } $reply = null; if ($act->context instanceof ActivityContext && !empty($act->context->replyToID)) { $reply = self::getKV('uri', $act->context->replyToID); } if (!$reply instanceof Notice && $act->target instanceof ActivityObject) { $reply = self::getKV('uri', $act->target->id); } if ($reply instanceof Notice) { if (!$reply->inScope($actor)) { // TRANS: Client error displayed when trying to reply to a notice a the target has no access to. // TRANS: %1$s is a user nickname, %2$d is a notice ID (number). throw new ClientException(sprintf(_m('%1$s has no right to reply to notice %2$d.'), $actor->getNickname(), $reply->id), 403); } $stored->reply_to = $reply->id; $stored->conversation = $reply->conversation; // If the original is private to a group, and notice has no group specified, // make it to the same group(s) if (empty($groups) && $reply->scope & Notice::GROUP_SCOPE) { $replyGroups = $reply->getGroups(); foreach ($replyGroups as $group) { if ($actor->isMember($group)) { $groups[] = $group->id; } } } if (is_null($scope)) { $scope = $reply->scope; } } else { // If we don't know the reply, we might know the conversation! // This will happen if a known remote user replies to an // unknown remote user - within a known conversation. if (empty($stored->conversation) and !empty($act->context->conversation)) { $conv = Conversation::getKV('uri', $act->context->conversation); if ($conv instanceof Conversation) { common_debug('Conversation stitched together from (probably) a reply activity to unknown remote user. Activity creation time (' . $stored->created . ') should maybe be compared to conversation creation time (' . $conv->created . ').'); } else { // Conversation entry with specified URI was not found, so we must create it. common_debug('Conversation URI not found, so we will create it with the URI given in the context of the activity: ' . $act->context->conversation); // The insert in Conversation::create throws exception on failure $conv = Conversation::create($act->context->conversation, $stored->created); } $stored->conversation = $conv->getID(); unset($conv); } } // If it's not part of a conversation, it's the beginning of a new conversation. if (empty($stored->conversation)) { $conv = Conversation::create(); $stored->conversation = $conv->getID(); unset($conv); } $notloc = null; if ($act->context instanceof ActivityContext) { if ($act->context->location instanceof Location) { $notloc = Notice_location::fromLocation($act->context->location); } } else { $act->context = new ActivityContext(); } $stored->scope = self::figureOutScope($actor, $groups, $scope); foreach ($act->categories as $cat) { if ($cat->term) { $term = common_canonical_tag($cat->term); if (!empty($term)) { $tags[] = $term; } } } foreach ($act->enclosures as $href) { // @todo FIXME: Save these locally or....? $urls[] = $href; } if (ActivityUtils::compareVerbs($stored->verb, array(ActivityVerb::POST))) { if (empty($act->objects[0]->type)) { // Default type for the post verb is 'note', but we know it's // a 'comment' if it is in reply to something. $stored->object_type = empty($stored->reply_to) ? ActivityObject::NOTE : ActivityObject::COMMENT; } else { //TODO: Is it safe to always return a relative URI? The // JSON version of ActivityStreams always use it, so we // should definitely be able to handle it... $stored->object_type = ActivityUtils::resolveUri($act->objects[0]->type, true); } } if (Event::handle('StartNoticeSave', array(&$stored))) { // XXX: some of these functions write to the DB try { $result = $stored->insert(); // throws exception on error if ($notloc instanceof Notice_location) { $notloc->notice_id = $stored->getID(); $notloc->insert(); } $orig = clone $stored; // for updating later in this try clause $object = null; Event::handle('StoreActivityObject', array($act, $stored, $options, &$object)); if (empty($object)) { throw new ServerException('Unsuccessful call to StoreActivityObject ' . $stored->getUri() . ': ' . $act->asString()); } // If something changed in the Notice during StoreActivityObject $stored->update($orig); } catch (Exception $e) { if (empty($stored->id)) { common_debug('Failed to save stored object entry in database (' . $e->getMessage() . ')'); } else { common_debug('Failed to store activity object in database (' . $e->getMessage() . '), deleting notice id ' . $stored->id); $stored->delete(); } throw $e; } } if (!$stored instanceof Notice) { throw new ServerException('StartNoticeSave did not give back a Notice'); } // Only save 'attention' and metadata stuff (URLs, tags...) stuff if // the activityverb is a POST (since stuff like repeat, favorite etc. // reasonably handle notifications themselves. if (ActivityUtils::compareVerbs($stored->verb, array(ActivityVerb::POST))) { if (!empty($tags)) { $stored->saveKnownTags($tags); } else { $stored->saveTags(); } // Note: groups may save tags, so must be run after tags are saved // to avoid errors on duplicates. $stored->saveAttentions($act->context->attention); if (!empty($urls)) { $stored->saveKnownUrls($urls); } else { $stored->saveUrls(); } } if ($distribute) { // Prepare inbox delivery, may be queued to background. $stored->distribute(); } return $stored; }
protected function getActivityForm(ManagedAction $action, $verb, Notice $target, Profile $scoped) { if (!$scoped instanceof Profile || !($scoped->sameAs($target->getProfile()) || $scoped->hasRight(Right::DELETEOTHERSNOTICE))) { throw new AuthorizationException(_('You are not allowed to delete other user\'s notices')); } return DeletenoticeForm($action, array('notice' => $target)); }
/** * notify a user of subscription by a profile (remote or local) * * This function checks to see if the listenee has an email * address and wants subscription notices. * * @param User $listenee user who's being subscribed to * @param Profile $other profile of person who's listening * * @return void */ function mail_subscribe_notify_profile($listenee, $other) { if ($other->hasRight(Right::EMAILONSUBSCRIBE) && $listenee->email && $listenee->emailnotifysub) { $profile = $listenee->getProfile(); $name = $profile->getBestName(); $long_name = $other->fullname ? $other->fullname . ' (' . $other->nickname . ')' : $other->nickname; $recipients = $listenee->email; // use the recipient's localization common_switch_locale($listenee->language); $headers = _mail_prepare_headers('subscribe', $listenee->nickname, $other->nickname); $headers['From'] = mail_notify_from(); $headers['To'] = $name . ' <' . $listenee->email . '>'; // TRANS: Subject of new-subscriber notification e-mail. // TRANS: %1$s is the subscribing user's nickname, %2$s is the StatusNet sitename. $headers['Subject'] = sprintf(_('%1$s is now following you on %2$s.'), $other->getBestName(), common_config('site', 'name')); // TRANS: Main body of new-subscriber notification e-mail. // TRANS: %1$s is the subscriber's long name, %2$s is the StatusNet sitename. $body = sprintf(_('%1$s is now following you on %2$s.'), $long_name, common_config('site', 'name')) . mail_profile_block($other) . mail_footer_block(); // reset localization common_switch_locale(); mail_send($recipients, $headers, $body); } }
/** * notify a user of subscription by a profile (remote or local) * * This function checks to see if the listenee has an email * address and wants subscription notices. * * @param User $listenee user who's being subscribed to * @param Profile $other profile of person who's listening * * @return void */ function mail_subscribe_notify_profile($listenee, $other) { if ($other->hasRight(Right::EMAILONSUBSCRIBE) && $listenee->email && $listenee->emailnotifysub) { // use the recipient's localization common_init_locale($listenee->language); $profile = $listenee->getProfile(); $name = $profile->getBestName(); $long_name = $other->fullname ? $other->fullname . ' (' . $other->nickname . ')' : $other->nickname; $recipients = $listenee->email; $headers = _mail_prepare_headers('subscribe', $listenee->nickname, $other->nickname); $headers['From'] = mail_notify_from(); $headers['To'] = $name . ' <' . $listenee->email . '>'; // TRANS: Subject of new-subscriber notification e-mail $headers['Subject'] = sprintf(_('%1$s is now listening to ' . 'your notices on %2$s.'), $other->getBestName(), common_config('site', 'name')); // TRANS: Main body of new-subscriber notification e-mail $body = sprintf(_('%1$s is now listening to your notices on %2$s.' . "\n\n" . "\t" . '%3$s' . "\n\n" . '%4$s' . '%5$s' . '%6$s' . "\n" . 'Faithfully yours,' . "\n" . '%7$s.' . "\n\n" . "----\n" . "Change your email address or " . "notification options at " . '%8$s' . "\n"), $long_name, common_config('site', 'name'), $other->profileurl, $other->location ? sprintf(_("Location: %s"), $other->location) . "\n" : '', $other->homepage ? sprintf(_("Homepage: %s"), $other->homepage) . "\n" : '', $other->bio ? sprintf(_("Bio: %s"), $other->bio) . "\n\n" : '', common_config('site', 'name'), common_local_url('emailsettings')); // reset localization common_init_locale(); mail_send($recipients, $headers, $body); } }
static function saveActivity(Activity $act, Profile $actor, array $options = array()) { // First check if we're going to let this Activity through from the specific actor if (!$actor->hasRight(Right::NEWNOTICE)) { common_log(LOG_WARNING, "Attempted post from user disallowed to post: " . $actor->getNickname()); // TRANS: Client exception thrown when a user tries to post while being banned. throw new ClientException(_m('You are banned from posting notices on this site.'), 403); } if (common_config('throttle', 'enabled') && !self::checkEditThrottle($actor->id)) { common_log(LOG_WARNING, 'Excessive posting by profile #' . $actor->id . '; throttled.'); // TRANS: Client exception thrown when a user tries to post too many notices in a given time frame. throw new ClientException(_m('Too many notices too fast; take a breather ' . 'and post again in a few minutes.')); } // Get ActivityObject properties if (!empty($act->id)) { // implied object $options['uri'] = $act->id; $options['url'] = $act->link; } else { $actobj = count($act->objects) == 1 ? $act->objects[0] : null; if (!is_null($actobj) && !empty($actobj->id)) { $options['uri'] = $actobj->id; if ($actobj->link) { $options['url'] = $actobj->link; } elseif (preg_match('!^https?://!', $actobj->id)) { $options['url'] = $actobj->id; } } } $defaults = array('groups' => array(), 'is_local' => self::LOCAL_PUBLIC, 'mentions' => array(), 'reply_to' => null, 'repeat_of' => null, 'scope' => null, 'source' => 'unknown', 'tags' => array(), 'uri' => null, 'url' => null, 'urls' => array(), 'distribute' => true); // options will have default values when nothing has been supplied $options = array_merge($defaults, $options); foreach (array_keys($defaults) as $key) { // Only convert the keynames we specify ourselves from 'defaults' array into variables ${$key} = $options[$key]; } extract($options, EXTR_SKIP); $stored = new Notice(); if (!empty($uri)) { $stored->uri = $uri; if ($stored->find()) { common_debug('cannot create duplicate Notice URI: ' . $stored->uri); throw new Exception('Notice URI already exists'); } } $stored->profile_id = $actor->id; $stored->source = $source; $stored->uri = $uri; $stored->url = $url; $stored->verb = $act->verb; // Use the local user's shortening preferences, if applicable. $stored->rendered = $actor->isLocal() ? $actor->shortenLinks($act->content) : $act->content; $stored->content = common_strip_html($stored->rendered); $autosource = common_config('public', 'autosource'); // Sandboxed are non-false, but not 1, either if (!$actor->hasRight(Right::PUBLICNOTICE) || $source && $autosource && in_array($source, $autosource)) { $stored->is_local = Notice::LOCAL_NONPUBLIC; } // Maybe a missing act-time should be fatal if the actor is not local? if (!empty($act->time)) { $stored->created = common_sql_date($act->time); } else { $stored->created = common_sql_now(); } $reply = null; if ($act->context instanceof ActivityContext && !empty($act->context->replyToID)) { $reply = self::getKV('uri', $act->context->replyToID); } if (!$reply instanceof Notice && $act->target instanceof ActivityObject) { $reply = self::getKV('uri', $act->target->id); } if ($reply instanceof Notice) { if (!$reply->inScope($actor)) { // TRANS: Client error displayed when trying to reply to a notice a the target has no access to. // TRANS: %1$s is a user nickname, %2$d is a notice ID (number). throw new ClientException(sprintf(_m('%1$s has no right to reply to notice %2$d.'), $actor->getNickname(), $reply->id), 403); } $stored->reply_to = $reply->id; $stored->conversation = $reply->conversation; // If the original is private to a group, and notice has no group specified, // make it to the same group(s) if (empty($groups) && $reply->scope & Notice::GROUP_SCOPE) { $groups = array(); $replyGroups = $reply->getGroups(); foreach ($replyGroups as $group) { if ($actor->isMember($group)) { $groups[] = $group->id; } } } if (is_null($scope)) { $scope = $reply->scope; } } if ($act->context instanceof ActivityContext) { $location = $act->context->location; if ($location) { $stored->lat = $location->lat; $stored->lon = $location->lon; if ($location->location_id) { $stored->location_ns = $location->location_ns; $stored->location_id = $location->location_id; } } } else { $act->context = new ActivityContext(); } $stored->scope = self::figureOutScope($actor, $groups, $scope); foreach ($act->categories as $cat) { if ($cat->term) { $term = common_canonical_tag($cat->term); if (!empty($term)) { $tags[] = $term; } } } foreach ($act->enclosures as $href) { // @todo FIXME: Save these locally or....? $urls[] = $href; } if (Event::handle('StartNoticeSave', array(&$stored))) { // XXX: some of these functions write to the DB try { $stored->insert(); // throws exception on error $orig = clone $stored; // for updating later in this try clause $object = null; Event::handle('StoreActivityObject', array($act, $stored, $options, &$object)); if (empty($object)) { throw new ServerException('Unsuccessful call to StoreActivityObject ' . $stored->uri . ': ' . $act->asString()); } // If it's not part of a conversation, it's // the beginning of a new conversation. if (empty($stored->conversation)) { // $act->context->conversation will be null if it was not provided $conv = Conversation::create($stored, $act->context->conversation); $stored->conversation = $conv->id; } $stored->update($orig); } catch (Exception $e) { if (empty($stored->id)) { common_debug('Failed to save stored object entry in database (' . $e->getMessage() . ')'); } else { common_debug('Failed to store activity object in database (' . $e->getMessage() . '), deleting notice id ' . $stored->id); $stored->delete(); } throw $e; } } if (!$stored instanceof Notice) { throw new ServerException('StartNoticeSave did not give back a Notice'); } // Save per-notice metadata... $mentions = array(); $groups = array(); // This event lets plugins filter out non-local recipients (attentions we don't care about) // Used primarily for OStatus (and if we don't federate, all attentions would be local anyway) Event::handle('GetLocalAttentions', array($actor, $act->context->attention, &$mentions, &$groups)); if (!empty($mentions)) { $stored->saveKnownReplies($mentions); } else { $stored->saveReplies(); } if (!empty($tags)) { $stored->saveKnownTags($tags); } else { $stored->saveTags(); } // Note: groups may save tags, so must be run after tags are saved // to avoid errors on duplicates. // Note: groups should always be set. $stored->saveKnownGroups($groups); if (!empty($urls)) { $stored->saveKnownUrls($urls); } else { $stored->saveUrls(); } if ($distribute) { // Prepare inbox delivery, may be queued to background. $stored->distribute(); } return $stored; }