public function onEndPersonalGroupNav(Menu $menu, Profile $target, Profile $scoped = null) { $menu->out->menuItem(common_local_url('showfavorites', array('nickname' => $target->getNickname())), _m('MENU', 'Favorites'), sprintf(_('%s\'s favorite notices'), $target->getBestName()), $scoped instanceof Profile && $target->id === $scoped->id && $menu->actionName == 'showfavorites', 'nav_timeline_favorites'); }
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; }
*/ define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); $shortoptions = 'y'; $longoptions = array('yes'); $helptext = <<<END_OF_HELP clean_profiles.php [options] Deletes all profile table entries where the profile does not occur in the notice table, is not a group and is not a local user. Very MySQL specific I think. WARNING: This has not been tested thoroughly. Maybe we've missed a table to compare somewhere. -y --yes do not wait for confirmation END_OF_HELP; require_once INSTALLDIR . '/scripts/commandline.inc'; if (!have_option('y', 'yes')) { print "About to delete profiles that we think are useless to save. Are you sure? [y/N] "; $response = fgets(STDIN); if (strtolower(trim($response)) != 'y') { print "Aborting.\n"; exit(0); } } print "Deleting"; $profile = new Profile(); $profile->query('SELECT * FROM profile WHERE ' . 'NOT (SELECT COUNT(*) FROM notice WHERE profile_id=profile.id) ' . 'AND NOT (SELECT COUNT(*) FROM user WHERE user.id=profile.id) ' . 'AND NOT (SELECT COUNT(*) FROM user_group WHERE user_group.profile_id=profile.id) ' . 'AND NOT (SELECT COUNT(*) FROM subscription WHERE subscriber=profile.id OR subscribed=profile.id) '); while ($profile->fetch()) { echo ' ' . $profile->getID() . ':' . $profile->getNickname(); $profile->delete(); } print "\nDONE.\n";
function relationshipDetailsArray(Profile $source, Profile $target) { $details = array(); $details['screen_name'] = $source->getNickname(); $details['followed_by'] = $target->isSubscribed($source); try { $sub = Subscription::getSubscription($source, $target); $details['following'] = true; $details['notifications_enabled'] = $sub->jabber || $sub->sms; } catch (NoResultException $e) { $details['following'] = false; $details['notifications_enabled'] = false; } $details['blocking'] = $source->hasBlocked($target); $details['id'] = intval($source->id); return $details; }
function onEndPersonalGroupNav(Menu $menu, Profile $target, Profile $scoped = null) { $menu->menuItem(common_local_url('events', array('nickname' => $target->getNickname())), _m('Happenings'), _m('A list of your events'), false, 'nav_timeline_events'); return true; }
public function onEndPersonalGroupNav(Menu $menu, Profile $target, Profile $scoped = null) { if ($scoped instanceof Profile && $scoped->id == $target->id && !common_config('singleuser', 'enabled')) { $menu->out->menuItem(common_local_url('inbox', array('nickname' => $target->getNickname())), _m('MENU', 'Messages'), _('Your incoming messages'), $scoped->id === $target->id && $menu->actionName == 'inbox'); } }
static function filename(Profile $profile, $origname, $mimetype) { $ext = self::guessMimeExtension($mimetype); // Normalize and make the original filename more URL friendly. $origname = basename($origname, ".{$ext}"); if (class_exists('Normalizer')) { // http://php.net/manual/en/class.normalizer.php // http://www.unicode.org/reports/tr15/ $origname = Normalizer::normalize($origname, Normalizer::FORM_KC); } $origname = preg_replace('/[^A-Za-z0-9\\.\\_]/', '_', $origname); $nickname = $profile->getNickname(); $datestamp = strftime('%Y%m%d', time()); do { // generate new random strings until we don't run into a filename collision. $random = strtolower(common_confirmation_code(16)); $filename = "{$nickname}-{$datestamp}-{$origname}-{$random}.{$ext}"; } while (file_exists(self::path($filename))); return $filename; }
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; }
/** * Send a confirmation code to a user * * @param string $screenname screenname sending to * @param string $code the confirmation code * @param Profile $target For whom the code is valid for * * @return boolean success value */ function sendConfirmationCode($screenname, $code, Profile $target) { // TRANS: Body text for confirmation code e-mail. // TRANS: %1$s is a user nickname, %2$s is the StatusNet sitename, // TRANS: %3$s is the display name of an IM plugin. $body = sprintf(_('User "%1$s" on %2$s has said that your %3$s screenname belongs to them. ' . 'If that is true, you can confirm by clicking on this URL: ' . '%4$s' . ' . (If you cannot click it, copy-and-paste it into the ' . 'address bar of your browser). If that user is not you, ' . 'or if you did not request this confirmation, just ignore this message.'), $target->getNickname(), common_config('site', 'name'), $this->getDisplayName(), common_local_url('confirmaddress', null, array('code' => $code))); return $this->sendMessage($screenname, $body); }
public function onStartChangePassword(Profile $target, $oldpassword, $newpassword) { if (!$this->checkPassword($target->getNickname(), $oldpassword)) { // if we ARE in overwrite mode, test password with common_check_user if (!$this->overwrite || !common_check_user($target->getNickname(), $oldpassword)) { // either we're not in overwrite mode, or the password was incorrect return !$this->authoritative; } // oldpassword was apparently ok } $changed = $this->changePassword($target->getNickname(), $oldpassword, $newpassword); return !$changed && empty($this->authoritative); }