The following software packages are *required* for this software to
run correctly.
-- PHP 5.4+ For newer versions, some functions that are used may be
+- PHP 5.5+ For newer versions, some functions that are used may be
disabled by default, such as the pcntl_* family. See the
section on 'Queues and daemons' for more information.
- MariaDB 5+ GNU Social uses, by default, a MariaDB server for data
To get it, use the git version control tool
<http://git-scm.com/> like so:
- git clone git@gitorious.org:social/mainline.git
+ git clone git@git.gnu.io:gnu/gnu-social.git
In the current phase of development it is probably
recommended to use git as a means to stay up to date
* Following us on GNU social -- <https://quitter.se/gnusocial>
* GNU social has a bug tracker for any defects you may find, or ideas for
- making things better. <https://bugz.foocorp.net/>
-* Patches are welcome, preferrably to our repository on Gitorious. <https://gitorious.org/social/mainline>
+ making things better. <https://git.gnu.io/gnu/gnu-social/issues/>
+* Patches are welcome, preferrably to our repository on git.gnu.io. <https://git.gnu.io/gnu/gnu-social>
Credits
=======
* @link http://status.net
*/
-if (!defined('GNUSOCIAL') && !defined('STATUSNET')) { exit(1); }
+if (!defined('GNUSOCIAL')) { exit(1); }
-class AllAction extends ProfileAction
+class AllAction extends ShowstreamAction
{
var $notice;
- protected function profileActionPreparation()
+ protected function getStream()
{
if ($this->scoped instanceof Profile && $this->scoped->isLocal() && $this->scoped->getUser()->streamModeOnly()) {
$stream = new InboxNoticeStream($this->target, $this->scoped);
$stream = new ThreadingInboxNoticeStream($this->target, $this->scoped);
}
- $this->notice = $stream->getNotices(($this->page-1)*NOTICES_PER_PAGE,
- NOTICES_PER_PAGE + 1);
-
- if ($this->page > 1 && $this->notice->N == 0) {
- // TRANS: Client error when page not found (404).
- $this->clientError(_('No such page.'), 404);
- }
+ return $stream;
}
function title()
'format' => 'atom')),
// TRANS: Title for link to notice feed.
// TRANS: %s is a user nickname.
- _('Conversation feed (Activity Streams JSON)')));
+ _('Conversation feed (Atom)')));
}
}
$this->serverError(_('Could not insert confirmation code.'));
}
+ common_debug('Sending confirmation address for user '.$user->id.' to email '.$email);
mail_confirm_address($user, $confirm->code, $user->nickname, $email);
Event::handle('EndAddEmailAddress', array($user, $email));
if (!defined('GNUSOCIAL')) { exit(1); }
-class NetworkpublicAction extends PublicAction
+class NetworkpublicAction extends SitestreamAction
{
protected function streamPrepare()
{
}
}
- function extraHead()
- {
- // the PublicAction has some XRDS stuff that might be unique to the non-network public feed
- // FIXME: Solve this with a call that doesn't rely on parent:: and is unique for each class.
- ManagedAction::extraHead();
- }
-
function showSections()
{
// Show invite button, as long as site isn't closed, and
if (!defined('GNUSOCIAL')) { exit(1); }
-// Farther than any human will go
-
-define('MAX_PUBLIC_PAGE', 100);
-
/**
* Action for displaying the public stream
*
* @link http://status.net/
*
* @see PublicrssAction
- * @see PublicxrdsAction
*/
-class PublicAction extends ManagedAction
+class PublicAction extends SitestreamAction
{
- /**
- * page of the stream we're on; default = 1
- */
-
- var $page = null;
- var $notice;
-
- protected $stream = null;
-
- function isReadOnly($args)
- {
- return true;
- }
-
- protected function doPreparation()
- {
- $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
-
- if ($this->page > MAX_PUBLIC_PAGE) {
- // TRANS: Client error displayed when requesting a public timeline page beyond the page limit.
- // TRANS: %s is the page limit.
- $this->clientError(sprintf(_('Beyond the page limit (%s).'), MAX_PUBLIC_PAGE));
- }
-
- common_set_returnto($this->selfUrl());
-
- $this->streamPrepare();
-
- $this->notice = $this->stream->getNotices(($this->page-1)*NOTICES_PER_PAGE,
- NOTICES_PER_PAGE + 1);
-
- if (!$this->notice) {
- // TRANS: Server error displayed when a public timeline cannot be retrieved.
- $this->serverError(_('Could not retrieve public timeline.'));
- }
-
- if ($this->page > 1 && $this->notice->N == 0){
- // TRANS: Client error when page not found (404).
- $this->clientError(_('No such page.'), 404);
- }
-
- return true;
- }
-
protected function streamPrepare()
{
if ($this->scoped instanceof Profile && $this->scoped->isLocal() && $this->scoped->getUser()->streamModeOnly()) {
}
}
- function extraHead()
+ function showSections()
{
- parent::extraHead();
- $this->element('meta', array('http-equiv' => 'X-XRDS-Location',
- 'content' => common_local_url('publicxrds')));
-
- $rsd = common_local_url('rsd');
-
- // RSD, http://tales.phrasewise.com/rfc/rsd
+ // Show invite button, as long as site isn't closed, and
+ // we have a logged in user.
+ if (common_config('invite', 'enabled') && !common_config('site', 'closed') && common_logged_in()) {
+ if (!common_config('site', 'private')) {
+ $ibs = new InviteButtonSection(
+ $this,
+ // TRANS: Button text for inviting more users to the StatusNet instance.
+ // TRANS: Less business/enterprise-oriented language for public sites.
+ _m('BUTTON', 'Send invite')
+ );
+ } else {
+ $ibs = new InviteButtonSection($this);
+ }
+ $ibs->show();
+ }
- $this->element('link', array('rel' => 'EditURI',
- 'type' => 'application/rsd+xml',
- 'href' => $rsd));
+ $p = Profile::current();
- if ($this->page != 1) {
- $this->element('link', array('rel' => 'canonical',
- 'href' => common_local_url('public')));
+ if (!common_config('performance', 'high')) {
+ $cloud = new PublicTagCloudSection($this);
+ $cloud->show();
}
+ $feat = new FeaturedUsersSection($this);
+ $feat->show();
}
/**
// TRANS: Link description for public timeline feed.
_('Public Timeline Feed (Atom)')));
}
-
- function showEmptyList()
- {
- // TRANS: Text displayed for public feed when there are no public notices.
- $message = _('This is the public timeline for %%site.name%% but no one has posted anything yet.') . ' ';
-
- if (common_logged_in()) {
- // TRANS: Additional text displayed for public feed when there are no public notices for a logged in user.
- $message .= _('Be the first to post!');
- }
- else {
- if (! (common_config('site','closed') || common_config('site','inviteonly'))) {
- // TRANS: Additional text displayed for public feed when there are no public notices for a not logged in user.
- $message .= _('Why not [register an account](%%action.register%%) and be the first to post!');
- }
- }
-
- $this->elementStart('div', 'guide');
- $this->raw(common_markup_to_html($message));
- $this->elementEnd('div');
- }
-
- /**
- * Fill the content area
- *
- * Shows a list of the notices in the public stream, with some pagination
- * controls.
- *
- * @return void
- */
- function showContent()
- {
- if ($this->scoped instanceof Profile && $this->scoped->isLocal() && $this->scoped->getUser()->streamModeOnly()) {
- $nl = new PrimaryNoticeList($this->notice, $this, array('show_n'=>NOTICES_PER_PAGE));
- } else {
- $nl = new ThreadedNoticeList($this->notice, $this, $this->scoped);
- }
-
- $cnt = $nl->show();
-
- if ($cnt == 0) {
- $this->showEmptyList();
- }
-
- $this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE,
- $this->page, $this->action);
- }
-
- function showSections()
- {
- // Show invite button, as long as site isn't closed, and
- // we have a logged in user.
- if (common_config('invite', 'enabled') && !common_config('site', 'closed') && common_logged_in()) {
- if (!common_config('site', 'private')) {
- $ibs = new InviteButtonSection(
- $this,
- // TRANS: Button text for inviting more users to the StatusNet instance.
- // TRANS: Less business/enterprise-oriented language for public sites.
- _m('BUTTON', 'Send invite')
- );
- } else {
- $ibs = new InviteButtonSection($this);
- }
- $ibs->show();
- }
-
- $p = Profile::current();
-
- if (!common_config('performance', 'high')) {
- $cloud = new PublicTagCloudSection($this);
- $cloud->show();
- }
- $feat = new FeaturedUsersSection($this);
- $feat->show();
- }
-
- function showAnonymousMessage()
- {
- if (! (common_config('site','closed') || common_config('site','inviteonly'))) {
- // TRANS: Message for not logged in users at an invite-only site trying to view the public feed of notices.
- // TRANS: This message contains Markdown links. Please mind the formatting.
- $m = _('This is %%site.name%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
- 'based on the Free Software [StatusNet](http://status.net/) tool. ' .
- '[Join now](%%action.register%%) to share notices about yourself with friends, family, and colleagues! ' .
- '([Read more](%%doc.help%%))');
- } else {
- // TRANS: Message for not logged in users at a closed site trying to view the public feed of notices.
- // TRANS: This message contains Markdown links. Please mind the formatting.
- $m = _('This is %%site.name%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
- 'based on the Free Software [StatusNet](http://status.net/) tool.');
- }
- $this->elementStart('div', array('id' => 'anon_notice'));
- $this->raw(common_markup_to_html($m));
- $this->elementEnd('div');
- }
}
+++ /dev/null
-<?php
-/**
- * Public XRDS for OpenID
- *
- * PHP version 5
- *
- * @category Action
- * @package StatusNet
- * @author Evan Prodromou <evan@status.net>
- * @author Craig Andrews <candrews@integralblue.com>
- * @author Robin Millette <millette@status.net>
- * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
- * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link http://status.net/
- *
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, StatusNet, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
-
-require_once INSTALLDIR.'/plugins/OpenID/openid.php';
-require_once INSTALLDIR.'/lib/xrdsoutputter.php';
-
-/**
- * Public XRDS
- *
- * @category Action
- * @package StatusNet
- * @author Evan Prodromou <evan@status.net>
- * @author Robin Millette <millette@status.net>
- * @author Craig Andrews <candrews@integralblue.com>
- * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
- * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link http://status.net/
- *
- * @todo factor out similarities with XrdsAction
- */
-class PublicxrdsAction extends Action
-{
- /**
- * Is read only?
- *
- * @return boolean true
- */
- function isReadOnly($args)
- {
- return true;
- }
-
- /**
- * Class handler.
- *
- * @param array $args array of arguments
- *
- * @return nothing
- */
- function handle($args)
- {
- parent::handle($args);
- $xrdsOutputter = new XRDSOutputter();
- $xrdsOutputter->startXRDS();
- Event::handle('StartPublicXRDS', array($this,&$xrdsOutputter));
- Event::handle('EndPublicXRDS', array($this,&$xrdsOutputter));
- $xrdsOutputter->endXRDS();
- }
-}
-
try {
User::recoverPassword($nore);
$this->mode = 'sent';
- // TRANS: User notification after an e-mail with instructions was sent from the password recovery form.
- $this->msg = _('Instructions for recovering your password ' .
- 'have been sent to the email address registered to your ' .
- 'account.');
+ if (common_is_email($nore) && common_config('site', 'fakeaddressrecovery')) {
+ // TRANS: User notification when recovering password by giving email address,
+ // regardless if the mail was sent or not (to hide registered email status).
+ $this->msg = _('If the email address you provided was found in the database, a recovery mail with instructions has been sent there.');
+ } else {
+ // TRANS: User notification after an e-mail with instructions was sent from the password recovery form.
+ $this->msg = _('Instructions for recovering your password ' .
+ 'have been sent to the email address registered to your ' .
+ 'account.');
+ }
$this->success = true;
} catch (Exception $e) {
$this->success = false;
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
-class RepliesAction extends ManagedAction
+class RepliesAction extends ShowstreamAction
{
var $page = null;
var $notice;
- protected function doPreparation()
+ protected function getStream()
{
- $nickname = common_canonical_nickname($this->arg('nickname'));
-
- $this->user = User::getKV('nickname', $nickname);
-
- if (!$this->user instanceof User) {
- // TRANS: Client error displayed when trying to reply to a non-exsting user.
- $this->clientError(_('No such user.'));
- }
-
- $this->target = $this->user->getProfile();
-
- if (!$this->target instanceof Profile) {
- // TRANS: Error message displayed when referring to a user without a profile.
- $this->serverError(_('User has no profile.'));
- }
-
- $this->page = $this->int('page') ?: 1;
-
- common_set_returnto($this->selfUrl());
-
- $stream = new ReplyNoticeStream($this->target->getID(), $this->scoped);
-
- $this->notice = $stream->getNotices(($this->page-1) * NOTICES_PER_PAGE,
- NOTICES_PER_PAGE + 1);
-
- if ($this->page > 1 && $this->notice->N == 0) {
- // TRANS: Client error when page not found (404)
- $this->clientError(_('No such page.'), 404);
- }
+ return new ReplyNoticeStream($this->target->getID(), $this->scoped);
}
/**
{
var $notice;
+ protected function doPreparation()
+ {
+ // showstream requires a nickname
+ $nickname_arg = $this->arg('nickname');
+ $nickname = common_canonical_nickname($nickname_arg);
+
+ // Permanent redirect on non-canonical nickname
+
+ if ($nickname_arg != $nickname) {
+ $args = array('nickname' => $nickname);
+ if ($this->arg('page') && $this->arg('page') != 1) {
+ $args['page'] = $this->arg['page'];
+ }
+ common_redirect(common_local_url($this->getActionName(), $args), 301);
+ }
+ $this->user = User::getKV('nickname', $nickname);
+
+ if (!$this->user) {
+ $group = Local_group::getKV('nickname', $nickname);
+ if ($group instanceof Local_group) {
+ common_redirect($group->getProfile()->getUrl());
+ }
+ // TRANS: Client error displayed when calling a profile action without specifying a user.
+ $this->clientError(_('No such user.'), 404);
+ }
+
+ $this->target = $this->user->getProfile();
+ }
+
protected function profileActionPreparation()
+ {
+ $stream = $this->getStream();
+ $this->notice = $stream->getNotices(($this->page-1) * NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+
+ if ($this->page > 1 && $this->notice->N == 0) {
+ // TRANS: Client error when page not found (404).
+ $this->clientError(_('No such page.'), 404);
+ }
+ }
+
+ protected function getStream()
{
if (empty($this->tag)) {
$stream = new ProfileNoticeStream($this->target, $this->scoped);
$stream = new TaggedProfileNoticeStream($this->target, $this->tag, $this->scoped);
}
- $this->notice = $stream->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
-
- return true;
+ return $stream;
}
return array(new Feed(Feed::JSON,
common_local_url('ApiTimelineUser',
array(
- 'id' => $this->user->id,
+ 'id' => $this->target->getID(),
'format' => 'as')),
// TRANS: Title for link to notice feed.
// TRANS: %s is a user nickname.
new Feed(Feed::RSS2,
common_local_url('ApiTimelineUser',
array(
- 'id' => $this->user->id,
+ 'id' => $this->target->getID(),
'format' => 'rss')),
// TRANS: Title for link to notice feed.
// TRANS: %s is a user nickname.
new Feed(Feed::ATOM,
common_local_url('ApiTimelineUser',
array(
- 'id' => $this->user->id,
+ 'id' => $this->target->getID(),
'format' => 'atom')),
// TRANS: Title for link to notice feed.
// TRANS: %s is a user nickname.
function showEmptyListMessage()
{
// TRANS: First sentence of empty list message for a timeline. $1%s is a user nickname.
- $message = sprintf(_('This is the timeline for %1$s, but %1$s hasn\'t posted anything yet.'), $this->target->nickname) . ' ';
+ $message = sprintf(_('This is the timeline for %1$s, but %1$s hasn\'t posted anything yet.'), $this->target->getNickname()) . ' ';
- if (common_logged_in()) {
- $current_user = common_current_user();
- if ($this->user->id === $current_user->id) {
+ if ($this->scoped instanceof Profile) {
+ if ($this->target->getID() === $this->scoped->getID()) {
// TRANS: Second sentence of empty list message for a stream for the user themselves.
$message .= _('Seen anything interesting recently? You haven\'t posted any notices yet, now would be a good time to start :)');
} else {
// TRANS: Second sentence of empty list message for a non-self timeline. %1$s is a user nickname, %2$s is a part of a URL.
// TRANS: This message contains a Markdown link. Keep "](" together.
- $message .= sprintf(_('You can try to nudge %1$s or [post something to them](%%%%action.newnotice%%%%?status_textarea=%2$s).'), $this->target->nickname, '@' . $this->target->nickname);
+ $message .= sprintf(_('You can try to nudge %1$s or [post something to them](%%%%action.newnotice%%%%?status_textarea=%2$s).'), $this->target->getNickname(), '@' . $this->target->getNickname());
}
}
else {
// TRANS: Second sentence of empty message for anonymous users. %s is a user nickname.
// TRANS: This message contains a Markdown link. Keep "](" together.
- $message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to them.'), $this->target->nickname);
+ $message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to them.'), $this->target->getNickname());
}
$this->elementStart('div', 'guide');
$this->showEmptyListMessage();
}
- $args = array('nickname' => $this->target->nickname);
+ $args = array('nickname' => $this->target->getNickname());
if (!empty($this->tag))
{
$args['tag'] = $this->tag;
$m = sprintf(_('**%s** has an account on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
'based on the Free Software [StatusNet](http://status.net/) tool. ' .
'[Join now](%%%%action.register%%%%) to follow **%s**\'s notices and many more! ([Read more](%%%%doc.help%%%%))'),
- $this->target->nickname, $this->target->nickname);
+ $this->target->getNickname(), $this->target->getNickname());
} else {
// TRANS: Announcement for anonymous users showing a timeline if site registrations are closed or invite only.
// TRANS: This message contains a Markdown link. Keep "](" together.
$m = sprintf(_('**%s** has an account on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
'based on the Free Software [StatusNet](http://status.net/) tool.'),
- $this->target->nickname, $this->target->nickname);
+ $this->target->getNickname(), $this->target->getNickname());
}
$this->elementStart('div', array('id' => 'anon_notice'));
$this->raw(common_markup_to_html($m));
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* User by ID action class.
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*/
-class UserbyidAction extends Action
+class UserbyidAction extends ShowstreamAction
{
- /**
- * Is read only?
- *
- * @return boolean true
- */
- function isReadOnly($args)
+ protected function doPreparation()
{
- return true;
- }
+ // accessing by ID just requires an ID, not a nickname
+ $this->target = Profile::getByID($this->trimmed('id'));
- /**
- * Class handler.
- *
- * @param array $args array of arguments
- *
- * @return nothing
- */
- protected function handle()
- {
- parent::handle();
- $id = $this->trimmed('id');
- if (!$id) {
- // TRANS: Client error displayed trying to find a user by ID without providing an ID.
- $this->clientError(_('No ID.'));
+ // For local users when accessed by id number, redirect with
+ // the nickname as argument instead of id.
+ if ($this->target->isLocal()) {
+ // Support redirecting to FOAF rdf/xml if the agent prefers it...
+ // Internet Explorer doesn't specify "text/html" and does list "*/*"
+ // at least through version 8. We need to list text/html up front to
+ // ensure that only user-agents who specifically ask for RDF get it.
+ $page_prefs = 'text/html,application/xhtml+xml,application/rdf+xml,application/xml;q=0.3,text/xml;q=0.2';
+ $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : null;
+ $type = common_negotiate_type(common_accept_to_prefs($httpaccept),
+ common_accept_to_prefs($page_prefs));
+ $page = $type === 'application/rdf+xml' ? 'foaf' : 'showstream';
+ $url = common_local_url($page, array('nickname' => $this->target->getNickname()));
+ common_redirect($url, 303);
}
- $user = User::getKV($id);
- if (!$user) {
- // TRANS: Client error displayed trying to find a user by ID for a non-existing ID.
- $this->clientError(_('No such user.'));
- }
-
- // Support redirecting to FOAF rdf/xml if the agent prefers it...
- // Internet Explorer doesn't specify "text/html" and does list "*/*"
- // at least through version 8. We need to list text/html up front to
- // ensure that only user-agents who specifically ask for RDF get it.
- $page_prefs = 'text/html,application/xhtml+xml,application/rdf+xml,application/xml;q=0.3,text/xml;q=0.2';
- $httpaccept = isset($_SERVER['HTTP_ACCEPT'])
- ? $_SERVER['HTTP_ACCEPT'] : null;
- $type = common_negotiate_type(common_accept_to_prefs($httpaccept),
- common_accept_to_prefs($page_prefs));
- $page = $type == 'application/rdf+xml' ? 'foaf' : 'showstream';
- $url = common_local_url($page, array('nickname' => $user->nickname));
- common_redirect($url, 303);
}
}
*
* @fixme refactor this mess, it's gotten pretty scary.
* @param string $given_url the URL we're looking at
- * @param int $notice_id (optional)
+ * @param Notice $notice (optional)
* @param bool $followRedirects defaults to true
*
* @return mixed File on success, -1 on some errors
*
* @throws ServerException on failure
*/
- public static function processNew($given_url, $notice_id=null, $followRedirects=true) {
+ public static function processNew($given_url, Notice $notice=null, $followRedirects=true) {
if (empty($given_url)) {
throw new ServerException('No given URL to process');
}
//
// Seen in the wild with clojure.org, which redirects through
// wikispaces for auth and appends session data in the URL params.
- $file = self::processNew($redir_url, $notice_id, /*followRedirects*/false);
+ $file = self::processNew($redir_url, $notice, /*followRedirects*/false);
File_redirection::saveNew($redir_data, $file->id, $given_url);
}
}
}
- if (!empty($notice_id)) {
- File_to_post::processNew($file->id, $notice_id);
+ if ($notice instanceof Notice) {
+ File_to_post::processNew($file, $notice);
}
return $file;
}
function blowCache($last=false)
{
- self::blow('file:notice-ids:%s', $this->urlhash);
+ self::blow('file:notice-ids:%s', $this->id);
if ($last) {
- self::blow('file:notice-ids:%s;last', $this->urlhash);
+ self::blow('file:notice-ids:%s;last', $this->id);
}
self::blow('file:notice-count:%d', $this->id);
}
static public function getByUrl($url)
{
- $file = new File_redirection();
- $file->urlhash = File::hashurl($url);
- if (!$file->find(true)) {
- throw new NoResultException($file);
- }
- return $file;
+ return self::getByPK(array('urlhash' => File::hashurl($url)));
}
static function _commonHttp($url, $redirs) {
// store it
$file = File::getKV('url', $long_url);
if ($file instanceof File) {
- $file_id = $file->id;
+ $file_id = $file->getID();
} else {
// Check if the target URL is itself a redirect...
$redir_data = File_redirection::where($long_url);
// We haven't seen the target URL before.
// Save file and embedding data about it!
$file = File::saveNew($redir_data, $long_url);
- $file_id = $file->id;
+ $file_id = $file->getID();
} else if (is_string($redir_data)) {
// The file is a known redirect target.
$file = File::getKV('url', $redir_data);
// SSL sites with cert issues.
return null;
}
- $file_id = $file->id;
+ $file_id = $file->getID();
}
}
$file_redir = File_redirection::getKV('url', $short_url);
* Fetch an entry by using a File's id
*/
static function byFile(File $file) {
- $file_thumbnail = self::getKV('file_id', $file->id);
+ $file_thumbnail = self::getKV('file_id', $file->getID());
if (!$file_thumbnail instanceof File_thumbnail) {
- throw new ServerException(sprintf('No File_thumbnail entry for File id==%u', $file->id));
+ throw new ServerException(sprintf('No File_thumbnail entry for File id==%u', $file->getID()));
}
return $file_thumbnail;
}
public function getFile()
{
- $file = new File();
- $file->id = $this->file_id;
- if (!$file->find(true)) {
- throw new NoResultException($file);
- }
- return $file;
+ return File::getByID($this->file_id);
}
}
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
-
-require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Table Definition for file_to_post
);
}
- function processNew($file_id, $notice_id) {
+ function processNew(File $file, Notice $notice) {
static $seen = array();
- if (empty($seen[$notice_id]) || !in_array($file_id, $seen[$notice_id])) {
- $f2p = File_to_post::pkeyGet(array('post_id' => $notice_id,
- 'file_id' => $file_id));
- if (empty($f2p)) {
+ $file_id = $file->getID();
+ $notice_id = $notice->getID();
+ if (!array_key_exists($notice_id, $seen)) {
+ $seen[$notice_id] = array();
+ }
+
+ if (empty($seen[$notice_id]) || !in_array($file_id, $seen[$notice_id])) {
+ try {
+ $f2p = File_to_post::getByPK(array('post_id' => $notice_id,
+ 'file_id' => $file_id));
+ } catch (NoResultException $e) {
$f2p = new File_to_post;
$f2p->file_id = $file_id;
$f2p->post_id = $notice_id;
$f2p->insert();
- $f = File::getKV($file_id);
-
- if (!empty($f)) {
- $f->blowCache();
- }
+ $file->blowCache();
}
- if (empty($seen[$notice_id])) {
- $seen[$notice_id] = array($file_id);
- } else {
- $seen[$notice_id][] = $file_id;
- }
+ $seen[$notice_id][] = $file_id;
}
}
$f2p->selectAdd();
$f2p->selectAdd('post_id');
- $f2p->file_id = $file->id;
+ $f2p->file_id = $file->getID();
$ids = array();
function delete($useWhere=false)
{
- $f = File::getKV('id', $this->file_id);
- if ($f instanceof File) {
+ try {
+ $f = File::getByID($this->file_id);
$f->blowCache();
+ } catch (NoResultException $e) {
+ // ...alright, that's weird, but no File to delete anyway.
}
+
return parent::delete($useWhere);
}
}
return parent::pkeyGetClass(get_called_class(), $kv);
}
+ static function pkeyCols()
+ {
+ return parent::pkeyColsClass(get_called_class());
+ }
+
/**
* Get multiple items from the database by key
*
return common_database_tablename($this->tableName());
}
+ /**
+ * Returns an object by looking at the primary key column(s).
+ *
+ * Will require all primary key columns to be defined in an associative array
+ * and ignore any keys which are not part of the primary key.
+ *
+ * Will NOT accept NULL values as part of primary key.
+ *
+ * @param array $vals Must match all primary key columns for the dataobject.
+ *
+ * @return Managed_DataObject of the get_called_class() type
+ * @throws NoResultException if no object with that primary key
+ */
+ static function getByPK(array $vals)
+ {
+ $classname = get_called_class();
+
+ $pkey = static::pkeyCols();
+ if (is_null($pkey)) {
+ throw new ServerException("Failed to get primary key columns for class '{$classname}'");
+ }
+
+ $object = new $classname();
+ foreach ($pkey as $col) {
+ if (!array_key_exists($col, $vals)) {
+ throw new ServerException("Missing primary key column '{$col}'");
+ } elseif (is_null($vals[$col])) {
+ throw new ServerException("NULL values not allowed in getByPK for column '{$col}'");
+ }
+ $object->$col = $vals[$col];
+ }
+ if (!$object->find(true)) {
+ throw new NoResultException($object);
+ }
+ return $object;
+ }
+
+ static function getByID($id)
+ {
+ if (empty($id)) {
+ throw new ServerException('Empty ID on lookup');
+ }
+ // getByPK throws exception if id is null
+ // or if the class does not have a single 'id' column as primary key
+ return static::getByPK(array('id' => $id));
+ }
+
/**
* Returns an ID, checked that it is set and reasonably valid
*
{
if (is_null($v)) {
$v = $k;
- $keys = self::pkeyCols($cls);
+ $keys = static::pkeyCols();
if (count($keys) > 1) {
// FIXME: maybe call pkeyGetClass() ourselves?
throw new Exception('Use pkeyGetClass() for compound primary keys');
return $query;
}
- static function pkeyCols($cls)
+ static function pkeyColsClass($cls)
{
$i = new $cls;
$types = $i->keyTypes();
$pkeyMap = array_fill_keys($keyVals, array());
$result = array_fill_keys($keyVals, array());
- $pkeyCols = self::pkeyCols($cls);
+ $pkeyCols = static::pkeyCols();
$toFetch = array();
$allPkeys = array();
'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'),
'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'who made the update'),
'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universally unique identifier, usually a tag URI'),
- 'content' => array('type' => 'text', 'description' => 'update content', 'collate' => 'utf8_general_ci'),
+ 'content' => array('type' => 'text', 'description' => 'update content', 'collate' => 'utf8mb4_general_ci'),
'rendered' => array('type' => 'text', 'description' => 'HTML version of the content'),
'url' => array('type' => 'varchar', 'length' => 191, 'description' => 'URL of any attachment (image, video, bookmark, whatever)'),
'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
return $notice;
}
- public static function getById($id)
- {
- $notice = new Notice();
- $notice->id = $id;
- if (!$notice->find(true)) {
- throw new NoResultException($notice);
- }
- return $notice;
- }
-
/**
* Extract #hashtags from this notice's content and save them to the database.
*/
*/
function saveUrls() {
if (common_config('attachments', 'process_links')) {
- common_replace_urls_callback($this->content, array($this, 'saveUrl'), $this->id);
+ common_replace_urls_callback($this->content, array($this, 'saveUrl'), $this);
}
}
if (common_config('attachments', 'process_links')) {
// @fixme validation?
foreach (array_unique($urls) as $url) {
- try {
- File::processNew($url, $this->id);
- } catch (ServerException $e) {
- // Could not save URL. Log it?
- }
+ $this->saveUrl($url, $this);
}
}
}
/**
* @private callback
*/
- function saveUrl($url, $notice_id) {
+ function saveUrl($url, Notice $notice) {
try {
- File::processNew($url, $notice_id);
+ File::processNew($url, $notice);
} catch (ServerException $e) {
// Could not save URL. Log it?
}
$last = $parent;
continue;
}
- } catch (Exception $e) {
+ } catch (NoParentNoticeException $e) {
// Latest notice has no parent
}
// No parent, or parent out of scope
$this->saveReply($parentauthor->id);
$replied[$parentauthor->id] = 1;
self::blow('reply:stream:%d', $parentauthor->id);
- } catch (Exception $e) {
+ } catch (NoParentNoticeException $e) {
// Not a reply, since it has no parent!
}
foreach ($mention['mentioned'] as $mentioned) {
// skip if they're already covered
-
- if (!empty($replied[$mentioned->id])) {
+ if (array_key_exists($mentioned->id, $replied)) {
continue;
}
try {
$reply = $this->getParent();
$ctx->replyToID = $reply->getUri();
- $ctx->replyToUrl = $reply->getUrl();
- } catch (Exception $e) {
+ $ctx->replyToUrl = $reply->getUrl(true); // true for fallback to local URL, less messy
+ } catch (NoParentNoticeException $e) {
// This is not a reply to something
}
public function getParent()
{
- $parent = Notice::getKV('id', $this->reply_to);
-
- if (!$parent instanceof Notice) {
- throw new ServerException('Notice has no parent');
+ if (empty($this->reply_to)) {
+ throw new NoParentNoticeException($this);
}
-
- return $parent;
+ return self::getByID($this->reply_to);
}
/**
'description' => 'local and remote users have profiles',
'fields' => array(
'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'),
- 'nickname' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'nickname or username', 'collate' => 'utf8_general_ci'),
- 'fullname' => array('type' => 'varchar', 'length' => 191, 'description' => 'display name', 'collate' => 'utf8_general_ci'),
+ 'nickname' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'nickname or username', 'collate' => 'utf8mb4_general_ci'),
+ 'fullname' => array('type' => 'varchar', 'length' => 191, 'description' => 'display name', 'collate' => 'utf8mb4_general_ci'),
'profileurl' => array('type' => 'varchar', 'length' => 191, 'description' => 'URL, cached so we dont regenerate'),
- 'homepage' => array('type' => 'varchar', 'length' => 191, 'description' => 'identifying URL', 'collate' => 'utf8_general_ci'),
- 'bio' => array('type' => 'text', 'description' => 'descriptive biography', 'collate' => 'utf8_general_ci'),
- 'location' => array('type' => 'varchar', 'length' => 191, 'description' => 'physical location', 'collate' => 'utf8_general_ci'),
+ 'homepage' => array('type' => 'varchar', 'length' => 191, 'description' => 'identifying URL', 'collate' => 'utf8mb4_general_ci'),
+ 'bio' => array('type' => 'text', 'description' => 'descriptive biography', 'collate' => 'utf8mb4_general_ci'),
+ 'location' => array('type' => 'varchar', 'length' => 191, 'description' => 'physical location', 'collate' => 'utf8mb4_general_ci'),
'lat' => array('type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'latitude'),
'lon' => array('type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'longitude'),
'location_id' => array('type' => 'int', 'description' => 'location id if possible'),
$inst->delete();
}
+ $localuser = User::getKV('id', $this->id);
+ if ($localuser instanceof User) {
+ $localuser->delete();
+ }
+
return parent::delete($useWhere);
}
{
if (empty($topic)) {
$prefs = new Profile_prefs();
- $prefs->profile_id = $profile->id;
+ $prefs->profile_id = $profile->getID();
$prefs->namespace = $namespace;
$prefs->find();
} else {
- $prefs = self::pivotGet('profile_id', $profile->id, array('namespace'=>$namespace, 'topic'=>$topic));
+ $prefs = self::pivotGet('profile_id', $profile->getID(), array('namespace'=>$namespace, 'topic'=>$topic));
}
if (empty($prefs->N)) {
static function getAll(Profile $profile)
{
try {
- $prefs = self::listFind('profile_id', $profile->id);
+ $prefs = self::listFind('profile_id', $profile->getID());
} catch (NoResultException $e) {
return array();
}
}
static function getTopic(Profile $profile, $namespace, $topic) {
- $pref = new Profile_prefs;
- $pref->profile_id = $profile->id;
- $pref->namespace = $namespace;
- $pref->topic = $topic;
-
- if (!$pref->find(true)) {
- throw new NoResultException($pref);
- }
- return $pref;
+ return Profile_prefs::getByPK(array('profile_id' => $profile->getID(),
+ 'namespace' => $namespace,
+ 'topic' => $topic));
}
static function getData(Profile $profile, $namespace, $topic, $def=null) {
}
$pref = new Profile_prefs();
- $pref->profile_id = $profile->id;
+ $pref->profile_id = $profile->getID();
$pref->namespace = $namespace;
$pref->topic = $topic;
$pref->data = $data;
// XXX: potential race condition
// can we force it to only update if claimed is still null
// (or old)?
- common_log(LOG_INFO, 'claiming queue item id = ' . $qi->id .
+ common_log(LOG_INFO, 'claiming queue item id = ' . $qi->getID() .
' for transport ' . $qi->transport);
$orig = clone($qi);
$qi->claimed = common_sql_now();
function releaseClaim()
{
// DB_DataObject doesn't let us save nulls right now
- $sql = sprintf("UPDATE queue_item SET claimed=NULL WHERE id=%d", $this->id);
+ $sql = sprintf("UPDATE queue_item SET claimed=NULL WHERE id=%d", $this->getID());
$this->query($sql);
$this->claimed = null;
static function recoverPassword($nore)
{
- $user = User::getKV('email', common_canonical_email($nore));
-
- if (!$user) {
- try {
- $user = User::getKV('nickname', common_canonical_nickname($nore));
- } catch (NicknameException $e) {
- // invalid
+ // $confirm_email will be used as a fallback if our user doesn't have a confirmed email
+ $confirm_email = null;
+
+ if (common_is_email($nore)) {
+ $user = User::getKV('email', common_canonical_email($nore));
+
+ // See if it's an unconfirmed email address
+ if (!$user instanceof User) {
+ // Warning: it may actually be legit to have multiple folks
+ // who have claimed, but not yet confirmed, the same address.
+ // We'll only send to the first one that comes up.
+ $confirm_email = new Confirm_address();
+ $confirm_email->address = common_canonical_email($nore);
+ $confirm_email->address_type = 'email';
+ if ($confirm_email->find(true)) {
+ $user = User::getKV('id', $confirm_email->user_id);
+ }
}
- }
-
- // See if it's an unconfirmed email address
- if (!$user) {
- // Warning: it may actually be legit to have multiple folks
- // who have claimed, but not yet confirmed, the same address.
- // We'll only send to the first one that comes up.
- $confirm_email = new Confirm_address();
- $confirm_email->address = common_canonical_email($nore);
- $confirm_email->address_type = 'email';
- $confirm_email->find();
- if ($confirm_email->fetch()) {
- $user = User::getKV($confirm_email->user_id);
- } else {
- $confirm_email = null;
+ // No luck finding anyone by that email address.
+ if (!$user instanceof User) {
+ if (common_config('site', 'fakeaddressrecovery')) {
+ // Return without actually doing anything! We fake address recovery
+ // to avoid revealing which email addresses are registered with the site.
+ return;
+ }
+ // TRANS: Information on password recovery form if no known e-mail address was specified.
+ throw new ClientException(_('No user with that email address exists here.'));
}
} else {
- $confirm_email = null;
- }
-
- if (!$user) {
- // TRANS: Information on password recovery form if no known username or e-mail address was specified.
- throw new ClientException(_('No user with that email address or username.'));
- return;
+ // This might throw a NicknameException on bad nicknames
+ $user = User::getKV('nickname', common_canonical_nickname($nore));
+ if (!$user instanceof User) {
+ // TRANS: Information on password recovery form if no known username was specified.
+ throw new ClientException(_('No user with that nickname exists here.'));
+ }
}
// Try to get an unconfirmed email address if they used a user name
-
- if (!$user->email && !$confirm_email) {
+ if (empty($user->email) && $confirm_email === null) {
$confirm_email = new Confirm_address();
$confirm_email->user_id = $user->id;
$confirm_email->address_type = 'email';
$confirm_email->find();
if (!$confirm_email->fetch()) {
+ // Nothing found, so let's reset it to null
$confirm_email = null;
}
}
- if (!$user->email && !$confirm_email) {
+ if (empty($user->email) && !$confirm_email instanceof Confirm_address) {
// TRANS: Client error displayed on password recovery form if a user does not have a registered e-mail address.
throw new ClientException(_('No registered email address for that user.'));
- return;
}
// Success! We have a valid user and a confirmed or unconfirmed email address
$confirm->code = common_confirmation_code(128);
$confirm->address_type = 'recover';
$confirm->user_id = $user->id;
- $confirm->address = (!empty($user->email)) ? $user->email : $confirm_email->address;
+ $confirm->address = $user->email ?: $confirm_email->address;
if (!$confirm->insert()) {
common_log_db_error($confirm, 'INSERT', __FILE__);
// TRANS: Server error displayed if e-mail address confirmation fails in the database on the password recovery form.
throw new ServerException(_('Error saving address confirmation.'));
- return;
}
// @todo FIXME: needs i18n.
/*!
- * jQuery JavaScript Library v2.1.3
+ * jQuery JavaScript Library v2.1.4
* http://jquery.com/
*
* Includes Sizzle.js
* Released under the MIT license
* http://jquery.org/license
*
- * Date: 2014-12-18T15:11Z
+ * Date: 2015-04-28T16:01Z
*/
(function( global, factory ) {
// Use the correct document accordingly with window argument (sandbox)
document = window.document,
- version = "2.1.3",
+ version = "2.1.4",
// Define a local copy of jQuery
jQuery = function( selector, context ) {
});
function isArraylike( obj ) {
- var length = obj.length,
+
+ // Support: iOS 8.2 (not reproducible in simulator)
+ // `in` check used to prevent JIT error (gh-2145)
+ // hasOwn isn't used here due to false negatives
+ // regarding Nodelist length in IE
+ var length = "length" in obj && obj.length,
type = jQuery.type( obj );
if ( type === "function" || jQuery.isWindow( obj ) ) {
// different story for parenting.
$parent = $notice->getParent();
$in_reply_to = $parent->id;
- } catch (Exception $e) {
+ } catch (NoParentNoticeException $e) {
$in_reply_to = null;
}
$twitter_status['in_reply_to_status_id'] = $in_reply_to;
// StatusNet
$cmd = strtolower($cmd);
+ $result = false;
if (Event::handle('StartInterpretCommand', array($cmd, $arg, $user, &$result))) {
switch($cmd) {
$result = new TrackingCommand($user);
}
break;
- default:
- $result = false;
}
Event::handle('EndInterpretCommand', array($cmd, $arg, $user, &$result));
'languages' => get_all_languages(),
'email' =>
array_key_exists('SERVER_ADMIN', $_SERVER) ? $_SERVER['SERVER_ADMIN'] : null,
+ 'fakeaddressrecovery' => true,
'broughtby' => null,
'timezone' => 'UTC',
'broughtbyurl' => null,
define('GNUSOCIAL_ENGINE_URL', 'https://www.gnu.org/software/social/');
define('GNUSOCIAL_BASE_VERSION', '1.2.0');
-define('GNUSOCIAL_LIFECYCLE', 'dev'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
+define('GNUSOCIAL_LIFECYCLE', 'alpha1'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
define('GNUSOCIAL_VERSION', GNUSOCIAL_BASE_VERSION . '-' . GNUSOCIAL_LIFECYCLE);
define('MESSAGES_PER_PAGE', 20);
define('GROUPS_PER_PAGE', 20);
+define('GROUPS_PER_MINILIST', 8);
+define('PROFILES_PER_MINILIST', 8);
+
define('FOREIGN_NOTICE_SEND', 1);
define('FOREIGN_NOTICE_RECV', 2);
define('FOREIGN_NOTICE_SEND_REPLY', 4);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
-
-require_once INSTALLDIR.'/lib/profilelist.php';
+if (!defined('GNUSOCIAL')) { exit(1); }
// 10x8
parent::handle();
}
+ protected function doPreparation()
+ {
+ // showstream requires a nickname
+ $nickname_arg = $this->arg('nickname');
+ $nickname = common_canonical_nickname($nickname_arg);
+
+ // Permanent redirect on non-canonical nickname
+
+ if ($nickname_arg != $nickname) {
+ $args = array('nickname' => $nickname);
+ if ($this->arg('page') && $this->arg('page') != 1) {
+ $args['page'] = $this->arg['page'];
+ }
+ common_redirect(common_local_url($this->getActionName(), $args), 301);
+ }
+ $this->user = User::getKV('nickname', $nickname);
+
+ if (!$this->user) {
+ $group = Local_group::getKV('nickname', $nickname);
+ if ($group instanceof Local_group) {
+ common_redirect($group->getProfile()->getUrl());
+ }
+ // TRANS: Client error displayed when calling a profile action without specifying a user.
+ $this->clientError(_('No such user.'), 404);
+ }
+
+ $this->target = $this->user->getProfile();
+ }
+
function showContent()
{
$this->showTagsDropdown();
require_once INSTALLDIR.'/lib/grouplist.php';
-define('GROUPS_PER_MINILIST', 8);
-
/**
* Widget to show a list of groups, good for sidebar
*
*
* This extends the PEAR HTTP_Request2 package:
* - sends StatusNet-specific User-Agent header
- * - 'follow_redirects' config option, defaulting off
+ * - 'follow_redirects' config option, defaulting on
* - 'max_redirs' config option, defaulting to 10
* - extended response class adds getRedirectCount() and getUrl() methods
* - get() and post() convenience functions return body content directly
/**
* Convenience function to run a HEAD request.
*
+ * NOTE: Will probably turn into a GET request if you let it follow redirects!
+ * That option is only there to be flexible and may be removed in the future!
+ *
* @return GNUsocial_HTTPResponse
* @throws HTTP_Request2_Exception
*/
- public function head($url, $headers=array())
+ public function head($url, $headers=array(), $follow_redirects=false)
{
- return $this->doRequest($url, self::METHOD_HEAD, $headers);
+ // Save the configured value for follow_redirects
+ $old_follow = $this->config['follow_redirects'];
+ try {
+ // Temporarily (possibly) override the follow_redirects setting
+ $this->config['follow_redirects'] = $follow_redirects;
+ return $this->doRequest($url, self::METHOD_HEAD, $headers);
+ } catch (Exception $e) {
+ // Let the exception go on its merry way.
+ throw $e;
+ } finally {
+ // reset to the old value
+ $this->config['follow_redirects'] = $old_follow;
+ }
+ //we've either returned or thrown exception here
}
/**
$parent = $notice->getParent();
$orig_profile = $parent->getProfile();
$nicknames = sprintf('%1$s => %2$s', $profile->nickname, $orig_profile->nickname);
- } catch (Exception $e) {
+ } catch (NoParentNoticeException $e) {
$nicknames = $profile->nickname;
}
$chan = new IMChannel($this);
$cmd->execute($chan);
return true;
- } else {
- return false;
}
+ return false;
}
/**
$this->updateStatus("GNU social has been installed at $link");
$this->updateStatus(
- '<strong>DONE!</strong> You can visit your <a href="'.htmlspecialchars($link).'">new GNU social site</a> (log in as "'.htmlspecialchars($this->adminNick).'"). If this is your first GNU social install, make your experience the best possible by visiting our resource site to join the mailing list and <a href="http://gnu.io/resources/">good documentation</a>.'
+ '<strong>DONE!</strong> You can visit your <a href="'.htmlspecialchars($link).'">new GNU social site</a> (log in as "'.htmlspecialchars($this->adminNick).'"). If this is your first GNU social install, make your experience the best possible by visiting our resource site to join the <a href="https://gnu.io/social/resources/">mailing list or IRC.</a>. <a href="'.htmlspecialchars($link).'/doc/faq/">FAQ is found here</a>.'
);
return true;
public function attachToNotice(Notice $notice)
{
- File_to_post::processNew($this->fileRecord->id, $notice->id);
+ File_to_post::processNew($this->fileRecord, $notice);
}
public function getPath()
*/
const DISPLAY_FMT = '[0-9a-zA-Z_]{1,64}';
+ /**
+ * Simplified regex fragment for acceptable full WebFinger ID of a user
+ *
+ * We could probably use an email regex here, but mainly we are interested
+ * in matching it in our URLs, like https://social.example/user@example.com
+ */
+ const WEBFINGER_FMT = '[0-9a-zA-Z_]{1,64}\@[0-9a-zA-Z_-.]{3,255}';
+
/**
* Regex fragment for checking a canonical nickname.
*
--- /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 NoParentNoticeException extends ServerException
+{
+ public $notice; // The notice which has no parent
+
+ public function __construct(Notice $notice)
+ {
+ $this->notice = $notice;
+ parent::__construct(sprintf(_('No parent for notice with ID "%s".'), $this->notice->id));
+ }
+}
// @fixme this will fail for things installed in local/plugins
// ... but then so will web links so far.
$path = INSTALLDIR . "/plugins/$name/locale";
+ if (!file_exists($path)) {
+ $path = INSTALLDIR . "/local/plugins/$name/locale";
+ }
}
if (file_exists($path) && is_dir($path)) {
bindtextdomain($name, $path);
protected $target = null; // Profile that we're showing
- protected function doPreparation()
+ protected function prepare(array $args=array())
{
- try {
- $nickname_arg = $this->arg('nickname');
- $nickname = common_canonical_nickname($nickname_arg);
+ // this will call ->doPreparation() which lower classes can use
+ parent::prepare($args);
- // Permanent redirect on non-canonical nickname
-
- if ($nickname_arg != $nickname) {
- $args = array('nickname' => $nickname);
- if ($this->arg('page') && $this->arg('page') != 1) {
- $args['page'] = $this->arg['page'];
- }
- common_redirect(common_local_url($this->getActionName(), $args), 301);
- }
- $this->user = User::getKV('nickname', $nickname);
-
- if (!$this->user) {
- $group = Local_group::getKV('nickname', $nickname);
- if ($group instanceof Local_group) {
- common_redirect($group->getProfile()->getUrl());
- }
- // TRANS: Client error displayed when calling a profile action without specifying a user.
- $this->clientError(_('No such user.'), 404);
- }
-
- $this->target = $this->user->getProfile();
- } catch (NicknameException $e) {
- $id = (int)$this->arg('id');
- $this->target = Profile::getKV('id', $id);
-
- if (!$this->target instanceof Profile) {
- // TRANS: Error message displayed when referring to a user without a profile.
- $this->serverError(_m('Profile ID does not exist.'));
- }
-
- if ($this->target->isLocal()) {
- // For local users when accessed by id number, redirect to
- // the same action but using the nickname as argument.
- common_redirect(common_local_url($this->getActionName(),
- array('nickname'=>$user->getNickname())));
- }
- }
-
- if ($this->target->hasRole(Profile_role::SILENCED) &&
- (empty($this->scoped) || !$this->scoped->hasRight(Right::SILENCEUSER))) {
+ if ($this->target->hasRole(Profile_role::SILENCED)
+ && (!$this->scoped instanceof Profile || !$this->scoped->hasRight(Right::SILENCEUSER))) {
throw new ClientException(_('This profile has been silenced by site moderators'), 403);
}
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
common_set_returnto($this->selfUrl());
- return $this->profileActionPreparation();
+ // fetch the actual stream stuff
+ $this->profileActionPreparation();
+
+ return true;
}
protected function profileActionPreparation()
{
- // No-op by default.
+ // Nothing to do by default.
+ }
+
+ public function getTarget()
+ {
+ return $this->target;
}
function isReadOnly($args)
if (!defined('GNUSOCIAL')) { exit(1); }
-define('PROFILES_PER_MINILIST', 8);
-
/**
* Widget to show a list of profiles, good for sidebar
*
// TRANS: Menu item title in search group navigation panel.
_('Public timeline'), $this->actionName == 'public', 'nav_timeline_public');
}
+ if (!common_config('public', 'localonly') || $this->action->getScoped() instanceof Profile) {
+ // Allow network wide view if you're logged in
+ // TRANS: Menu item in search group navigation panel.
+ $this->out->menuItem(common_local_url('networkpublic'), _m('MENU','Network'),
+ // TRANS: Menu item title in search group navigation panel.
+ _('Network public timeline'), $this->actionName == 'networkpublic', 'nav_timeline_networkpublic');
+ }
// TRANS: Menu item in search group navigation panel.
$this->out->menuItem(common_local_url('groups'), _m('MENU','Groups'),
--- /dev/null
+<?php
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+// Farther than any human will go
+
+define('MAX_PUBLIC_PAGE', 100);
+
+class SitestreamAction extends ManagedAction
+{
+ /**
+ * page of the stream we're on; default = 1
+ */
+
+ var $page = null;
+ var $notice;
+
+ protected $stream = null;
+
+ function isReadOnly($args)
+ {
+ return true;
+ }
+
+ protected function doPreparation()
+ {
+ $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
+
+ if ($this->page > MAX_PUBLIC_PAGE) {
+ // TRANS: Client error displayed when requesting a public timeline page beyond the page limit.
+ // TRANS: %s is the page limit.
+ $this->clientError(sprintf(_('Beyond the page limit (%s).'), MAX_PUBLIC_PAGE));
+ }
+
+ common_set_returnto($this->selfUrl());
+
+ $this->streamPrepare();
+
+ $this->notice = $this->stream->getNotices(($this->page-1)*NOTICES_PER_PAGE,
+ NOTICES_PER_PAGE + 1);
+
+ if (!$this->notice) {
+ // TRANS: Server error displayed when a public timeline cannot be retrieved.
+ $this->serverError(_('Could not retrieve public timeline.'));
+ }
+
+ if ($this->page > 1 && $this->notice->N == 0){
+ // TRANS: Client error when page not found (404).
+ $this->clientError(_('No such page.'), 404);
+ }
+
+ return true;
+ }
+
+ /**
+ * Title of the page
+ *
+ * @return page title, including page number if over 1
+ */
+ function title()
+ {
+ if ($this->page > 1) {
+ // TRANS: Title for all public timeline pages but the first.
+ // TRANS: %d is the page number.
+ return sprintf(_('Public timeline, page %d'), $this->page);
+ } else {
+ // TRANS: Title for the first public timeline page.
+ return _('Public timeline');
+ }
+ }
+
+ function extraHead()
+ {
+ parent::extraHead();
+ $rsd = common_local_url('rsd');
+
+ // RSD, http://tales.phrasewise.com/rfc/rsd
+
+ $this->element('link', array('rel' => 'EditURI',
+ 'type' => 'application/rsd+xml',
+ 'href' => $rsd));
+
+ if ($this->page != 1) {
+ $this->element('link', array('rel' => 'canonical',
+ 'href' => common_local_url('public')));
+ }
+ }
+
+ /**
+ * Output <head> elements for RSS and Atom feeds
+ *
+ * @return void
+ */
+ function getFeeds()
+ {
+ return array(new Feed(Feed::JSON,
+ common_local_url('ApiTimelinePublic',
+ array('format' => 'as')),
+ // TRANS: Link description for public timeline feed.
+ _('Public Timeline Feed (Activity Streams JSON)')),
+ new Feed(Feed::RSS1, common_local_url('publicrss'),
+ // TRANS: Link description for public timeline feed.
+ _('Public Timeline Feed (RSS 1.0)')),
+ new Feed(Feed::RSS2,
+ common_local_url('ApiTimelinePublic',
+ array('format' => 'rss')),
+ // TRANS: Link description for public timeline feed.
+ _('Public Timeline Feed (RSS 2.0)')),
+ new Feed(Feed::ATOM,
+ common_local_url('ApiTimelinePublic',
+ array('format' => 'atom')),
+ // TRANS: Link description for public timeline feed.
+ _('Public Timeline Feed (Atom)')));
+ }
+
+ function showEmptyList()
+ {
+ // TRANS: Text displayed for public feed when there are no public notices.
+ $message = _('This is the public timeline for %%site.name%% but no one has posted anything yet.') . ' ';
+
+ if (common_logged_in()) {
+ // TRANS: Additional text displayed for public feed when there are no public notices for a logged in user.
+ $message .= _('Be the first to post!');
+ }
+ else {
+ if (! (common_config('site','closed') || common_config('site','inviteonly'))) {
+ // TRANS: Additional text displayed for public feed when there are no public notices for a not logged in user.
+ $message .= _('Why not [register an account](%%action.register%%) and be the first to post!');
+ }
+ }
+
+ $this->elementStart('div', 'guide');
+ $this->raw(common_markup_to_html($message));
+ $this->elementEnd('div');
+ }
+
+ /**
+ * Fill the content area
+ *
+ * Shows a list of the notices in the public stream, with some pagination
+ * controls.
+ *
+ * @return void
+ */
+ function showContent()
+ {
+ if ($this->scoped instanceof Profile && $this->scoped->isLocal() && $this->scoped->getUser()->streamModeOnly()) {
+ $nl = new PrimaryNoticeList($this->notice, $this, array('show_n'=>NOTICES_PER_PAGE));
+ } else {
+ $nl = new ThreadedNoticeList($this->notice, $this, $this->scoped);
+ }
+
+ $cnt = $nl->show();
+
+ if ($cnt == 0) {
+ $this->showEmptyList();
+ }
+
+ $this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE,
+ $this->page, $this->action);
+ }
+
+ function showAnonymousMessage()
+ {
+ if (! (common_config('site','closed') || common_config('site','inviteonly'))) {
+ // TRANS: Message for not logged in users at an invite-only site trying to view the public feed of notices.
+ // TRANS: This message contains Markdown links. Please mind the formatting.
+ $m = _('This is %%site.name%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
+ 'based on the Free Software [StatusNet](http://status.net/) tool. ' .
+ '[Join now](%%action.register%%) to share notices about yourself with friends, family, and colleagues! ' .
+ '([Read more](%%doc.help%%))');
+ } else {
+ // TRANS: Message for not logged in users at a closed site trying to view the public feed of notices.
+ // TRANS: This message contains Markdown links. Please mind the formatting.
+ $m = _('This is %%site.name%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
+ 'based on the Free Software [StatusNet](http://status.net/) tool.');
+ }
+ $this->elementStart('div', array('id' => 'anon_notice'));
+ $this->raw(common_markup_to_html($m));
+ $this->elementEnd('div');
+ }
+}
* @param Notice $notice in-progress or complete Notice object for context
* @return string partially-rendered HTML
*/
-function common_linkify_mentions($text, $notice)
+function common_linkify_mentions($text, Notice $notice)
{
$mentions = common_find_mentions($text, $notice);
return $text;
}
-function common_linkify_mention($mention)
+function common_linkify_mention(array $mention)
{
$output = null;
*
* @access private
*/
-function common_find_mentions($text, $notice)
+function common_find_mentions($text, Notice $notice)
{
- try {
- $sender = Profile::getKV('id', $notice->profile_id);
- } catch (NoProfileException $e) {
- return array();
- }
+ // The getProfile call throws NoProfileException on failure
+ $sender = $notice->getProfile();
$mentions = array();
}
} catch (NoProfileException $e) {
common_log(LOG_WARNING, sprintf('Notice %d author profile id %d does not exist', $origNotice->id, $origNotice->profile_id));
- } catch (ServerException $e) {
- // Probably just no parent. Should get a specific NoParentException
+ } catch (NoParentNoticeException $e) {
+ // This notice is not in reply to anything
} catch (Exception $e) {
common_log(LOG_WARNING, __METHOD__ . ' got exception ' . get_class($e) . ' : ' . $e->getMessage());
}
+++ /dev/null
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Low-level generator for HTML
- *
- * 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 Output
- * @package StatusNet
- * @author Craig Andrews <candrews@integralblue.com>
- * @copyright 2008 StatusNet, Inc.
- * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
- * @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') && !defined('LACONICA')) {
- exit(1);
-}
-
-require_once INSTALLDIR.'/lib/xmloutputter.php';
-
-/**
- * Low-level generator for XRDS XML
- *
- * @category Output
- * @package StatusNet
- * @author Craig Andrews <candrews@integralblue.com>
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://status.net/
- *
- * @see Action
- * @see XMLOutputter
- */
-class XRDSOutputter extends XMLOutputter
-{
- public function startXRDS()
- {
- header('Content-Type: application/xrds+xml');
- $this->startXML();
- $this->elementStart('XRDS', array('xmlns' => 'xri://$xrds'));
- }
-
- public function endXRDS()
- {
- $this->elementEnd('XRDS');
- $this->endXML();
- }
-
- /**
- * Show service.
- *
- * @param string $type XRDS type
- * @param string $uri URI
- * @param array $params type parameters, null by default
- * @param array $sigs type signatures, null by default
- * @param string $localId local ID, null by default
- *
- * @return void
- */
- function showXrdsService($type, $uri, $params=null, $sigs=null, $localId=null)
- {
- $this->elementStart('Service');
- if ($uri) {
- $this->element('URI', null, $uri);
- }
- $this->element('Type', null, $type);
- if ($params) {
- foreach ($params as $param) {
- $this->element('Type', null, $param);
- }
- }
- if ($sigs) {
- foreach ($sigs as $sig) {
- $this->element('Type', null, $sig);
- }
- }
- if ($localId) {
- $this->element('LocalID', null, $localId);
- }
- $this->elementEnd('Service');
- }
-}
throw new ServerException('A verb has not been specified.');
}
- $this->notice = Notice::getById($this->trimmed('id'));
+ $this->notice = Notice::getByID($this->trimmed('id'));
if (!$this->notice->inScope($this->scoped)) {
// TRANS: %1$s is a user nickname, %2$d is a notice ID (number).
const FAILED_LOGIN_IP_SECTION = 'failed_login_ip';
+ public function initialize()
+ {
+ // This probably needs some work. For example with IPv6 you can easily generate new IPs...
+ $client_ip = common_client_ip();
+ $this->client_ip = $client_ip[0] ?: $client_ip[1]; // [0] is proxy, [1] should be the real IP
+ }
+
public function onStartCheckPassword($nickname, $password, &$authenticatedUser)
{
if (common_is_email($nickname)) {
return true;
}
- // This probably needs some work. For example with IPv6 you can easily generate new IPs...
- $client_ip = common_client_ip();
- $this->client_ip = $client_ip[0] ?: $client_ip[1]; // [0] is proxy, [1] should be the real IP
$this->failed_attempts = (int)$this->unauthed_user->getPref(self::FAILED_LOGIN_IP_SECTION, $this->client_ip);
switch (true) {
case $this->failed_attempts >= 5:
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-if (!defined('STATUSNET')) {
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Some UI extras for now...
return true;
}
- /**
- * Add ModPlus-related paths to the router table
- *
- * Hook for RouterInitialized event.
- *
- * @param URLMapper $m URL mapper
- *
- * @return boolean hook return
- */
- public function onStartInitializeRouter(URLMapper $m)
- {
- $m->connect('user/remote/:id',
- array('action' => 'remoteprofile'),
- array('id' => '[\d]+'));
-
- return true;
- }
-
/**
* Add per-profile info popup menu for author on notice lists.
*
protected function showProfileOptions(HTMLOutputter $out, Profile $profile)
{
if (!$profile->isGroup() && !$profile->isLocal()) {
- $target = common_local_url('remoteprofile', array('id' => $profile->id));
+ $target = common_local_url('userbyid', array('id' => $profile->getID()));
// TRANS: Label for access to remote profile options.
$label = _m('Remote profile options...');
$out->elementStart('div', 'remote-profile-options');
+++ /dev/null
-<?php
-/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
-
-class RemoteProfileAction extends ShowstreamAction
-{
- function title()
- {
- $base = $this->target->getBestName();
- $host = parse_url($this->target->profileurl, PHP_URL_HOST);
- // TRANS: Remote profile action page title.
- // TRANS: %1$s is a username, %2$s is a hostname.
- return sprintf(_m('%1$s on %2$s'), $base, $host);
- }
-
- /**
- * Instead of showing notices, link to the original offsite profile.
- */
- function showNotices()
- {
- $url = $this->target->profileurl;
- $host = parse_url($url, PHP_URL_HOST);
- $markdown = sprintf(
- // TRANS: Message on remote profile page.
- // TRANS: This message contains Markdown links in the form [description](link).
- // TRANS: %1$s is a profile nickname, %2$s is a hostname, %3$s is a URL.
- _m('This remote profile is registered on another site; see [%1$s\'s original profile page on %2$s](%3$s).'),
- $this->target->nickname,
- $host,
- $url);
- $html = common_markup_to_html($markdown);
- $this->raw($html);
-
- if ($this->target->hasRole(Profile_role::SILENCED)) {
- // TRANS: Message on blocked remote profile page.
- $markdown = _m('Site moderators have silenced this profile, which prevents delivery of new messages to any users on this site.');
- $this->raw(common_markup_to_html($markdown));
- }else{
-
- $pnl = new NoticeList($this->notice, $this);
- $cnt = $pnl->show();
- if (0 == $cnt) {
- $this->showEmptyListMessage();
- }
-
- $args = array('id' => $this->target->id);
- if (!empty($this->tag))
- {
- $args['tag'] = $this->tag;
- }
- $this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page,
- 'remoteprofile', $args);
-
- }
- }
-
- function getFeeds()
- {
- // none
- }
-
- /**
- * Don't do various extra stuff, and also trim some things to avoid crawlers.
- */
- function extraHead()
- {
- $this->element('meta', array('name' => 'robots',
- 'content' => 'noindex,nofollow'));
- }
-
- function showLocalNav()
- {
- // skip
- }
-
- function showSections()
- {
- // skip
- }
-
- function showStatistics()
- {
- // skip
- }
-}
$options);
if ($saved instanceof Notice) {
Ostatus_source::saveNew($saved, $this, $method);
- if (!empty($attachment)) {
- File_to_post::processNew($attachment->id, $saved->id);
+ if ($attachment instanceof File) {
+ File_to_post::processNew($attachment, $saved);
}
}
} catch (Exception $e) {
*
* @return boolean hook return
*/
- function onEndPublicXRDS($action, &$xrdsOutputter)
+ function onEndPublicXRDS(Action $action, &$xrdsOutputter)
{
$xrdsOutputter->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
$xrdsOutputter->elementEnd('XRD');
}
- /**
- * User XRDS output hook
- *
- * Puts the bits of code needed to discover OpenID endpoints.
- *
- * @param Action $action Action being executed
- * @param XMLOutputter &$xrdsOutputter Output channel
- *
- * @return boolean hook return
- */
- function onEndUserXRDS($action, &$xrdsOutputter)
- {
- $xrdsOutputter->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
- 'xml:id' => 'openid',
- 'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
- 'version' => '2.0'));
- $xrdsOutputter->element('Type', null, 'xri://$xrds*simple');
-
- //consumer
- $xrdsOutputter->showXrdsService('http://specs.openid.net/auth/2.0/return_to',
- common_local_url('finishopenidlogin'));
-
- //provider
- $xrdsOutputter->showXrdsService('http://specs.openid.net/auth/2.0/signon',
- common_local_url('openidserver'),
- null,
- null,
- common_profile_url($action->user->nickname));
- $xrdsOutputter->elementEnd('XRD');
- }
-
/**
* If we're in OpenID-only mode, hide all the main menu except OpenID login.
*
*
* @return void
*/
- function onEndShowHeadElements($action)
+ function onEndShowHeadElements(Action $action)
{
if ($action instanceof ShowstreamAction) {
$action->element('link', array('rel' => 'openid2.provider',
$action->element('link', array('rel' => 'openid.delegate',
'href' => $action->profile->profileurl));
}
+
+ if ($action instanceof SitestreamAction) {
+ $action->element('meta', array('http-equiv' => 'X-XRDS-Location',
+ 'content' => common_local_url('publicxrds')));
+ }
return true;
}
--- /dev/null
+<?php
+/**
+ * Public XRDS for OpenID
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @author Robin Millette <millette@status.net>
+ * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+require_once __DIR__.'/../openid.php';
+
+/**
+ * Public XRDS
+ *
+ * @category Action
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @author Robin Millette <millette@status.net>
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ */
+class PublicxrdsAction extends Action
+{
+ /**
+ * Is read only?
+ *
+ * @return boolean true
+ */
+ function isReadOnly($args)
+ {
+ return true;
+ }
+
+ /**
+ * Class handler.
+ *
+ * @param array $args array of arguments
+ *
+ * @return nothing
+ */
+ protected function handle()
+ {
+ parent::handle();
+ $xrdsOutputter = new XRDSOutputter();
+ $xrdsOutputter->startXRDS();
+ Event::handle('StartPublicXRDS', array($this,&$xrdsOutputter));
+ Event::handle('EndPublicXRDS', array($this,&$xrdsOutputter));
+ $xrdsOutputter->endXRDS();
+ }
+}
--- /dev/null
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Low-level generator for HTML
+ *
+ * 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 Output
+ * @package StatusNet
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @copyright 2008 StatusNet, Inc.
+ * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
+ * @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('GNUSOCIAL')) { exit(1); }
+
+/**
+ * Low-level generator for XRDS XML
+ *
+ * @category Output
+ * @package StatusNet
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ *
+ * @see Action
+ * @see XMLOutputter
+ */
+class XRDSOutputter extends XMLOutputter
+{
+ public function startXRDS()
+ {
+ header('Content-Type: application/xrds+xml');
+ $this->startXML();
+ $this->elementStart('XRDS', array('xmlns' => 'xri://$xrds'));
+ }
+
+ public function endXRDS()
+ {
+ $this->elementEnd('XRDS');
+ $this->endXML();
+ }
+
+ /**
+ * Show service.
+ *
+ * @param string $type XRDS type
+ * @param string $uri URI
+ * @param array $params type parameters, null by default
+ * @param array $sigs type signatures, null by default
+ * @param string $localId local ID, null by default
+ *
+ * @return void
+ */
+ function showXrdsService($type, $uri, $params=null, $sigs=null, $localId=null)
+ {
+ $this->elementStart('Service');
+ if ($uri) {
+ $this->element('URI', null, $uri);
+ }
+ $this->element('Type', null, $type);
+ if ($params) {
+ foreach ($params as $param) {
+ $this->element('Type', null, $param);
+ }
+ }
+ if ($sigs) {
+ foreach ($sigs as $sig) {
+ $this->element('Type', null, $sig);
+ }
+ }
+ if ($localId) {
+ $this->element('LocalID', null, $localId);
+ }
+ $this->elementEnd('Service');
+ }
+}
public function extendActivity(Notice $stored, Activity $act, Profile $scoped=null)
{
// TODO: How to handle repeats of deleted notices?
- $target = Notice::getById($stored->repeat_of);
+ $target = Notice::getByID($stored->repeat_of);
// TRANS: A repeat activity's title. %1$s is repeater's nickname
// and %2$s is the repeated user's nickname.
$act->title = sprintf(_('%1$s repeated a notice by %2$s'),
* @param Notice $notice
* @param object $status
*/
- function saveStatusAttachments($notice, $status)
+ function saveStatusAttachments(Notice $notice, $status)
{
if (common_config('attachments', 'process_links')) {
if (!empty($status->entities) && !empty($status->entities->urls)) {
foreach ($status->entities->urls as $url) {
try {
- File::processNew($url->url, $notice->id);
+ File::processNew($url->url, $notice);
} catch (ServerException $e) {
// Could not process attached URL
}
class WebFingerPlugin extends Plugin
{
+ const OAUTH_ACCESS_TOKEN_REL = 'http://apinamespace.org/oauth/access_token';
+ const OAUTH_REQUEST_TOKEN_REL = 'http://apinamespace.org/oauth/request_token';
+ const OAUTH_AUTHORIZE_REL = 'http://apinamespace.org/oauth/authorize';
+
public $http_alias = false;
public function initialize()
$type,
true); // isTemplate
}
+
+ // OAuth connections
+ $links[] = new XML_XRD_Element_link(self::OAUTH_ACCESS_TOKEN_REL, common_local_url('ApiOAuthAccessToken'));
+ $links[] = new XML_XRD_Element_link(self::OAUTH_REQUEST_TOKEN_REL, common_local_url('ApiOAuthRequestToken'));
+ $links[] = new XML_XRD_Element_link(self::OAUTH_AUTHORIZE_REL, common_local_url('ApiOAuthAuthorize'));
}
/**
$xs->text(": ");
} catch (InvalidUrlException $e) {
$xs->text(sprintf(' => %s', $orig_profile->nickname));
- } catch (Exception $e) {
+ } catch (NoParentNoticeException $e) {
$xs->text(": ");
}
if (!empty($notice->rendered)) {
function console_help()
{
- print "Welcome to StatusNet's interactive PHP console!\n";
+ print "Welcome to GNU social's interactive PHP console!\n";
print "Type some PHP code and it'll execute...\n";
print "\n";
print "Hint: return a value of any type to output it via var_export():\n";
}
if (CONSOLE_INTERACTIVE) {
- print "StatusNet interactive PHP console... type ctrl+D or enter 'exit' to exit.\n";
- $prompt = common_config('site', 'name') . '> ';
+ print "GNU social interactive PHP console... type ctrl+D or enter 'exit' to exit.\n";
+ $prompt = common_slugify(common_config('site', 'name')) . '> ';
} else {
$prompt = '';
}
fieldset {border: none;}
legend {font-weight: bold; font-size: 1.2em;}
input, textarea, select, option {padding: 2px;}
-textarea {overflow:auto;}
+
+textarea {
+ overflow:auto;
+ resize: vertical;
+}
body {
background-color: #f2f2f2;
.notice-options {
margin-bottom: 7px;
- margin-top: 12px;
float: right;
position: relative;
}
.threaded-replies {
clear: both;
- float: left;
width: 458px;
margin-left: 55px;
margin-bottom: 10px;
/* old school conversation style */
#conversation .notices .notices {
- float: left;
background-color:rgba(200, 200, 200, 0.050);
margin-left: 15px;
width: auto;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
-a {color: #006c9b;}
-a:hover {color: #006c9b;text-decoration:underline;}
+a {color: #666;}
+a:hover {color: #a22430;text-decoration:underline;}
#aside_primary a {color: rgba(0,0,0,0.8);}
top: 6px;
width: 89px;
z-index: 1001;
- background-color:#0084B4;
+ background-color:#a22430;
}
address:hover {
}
#site_nav_global_primary a:hover {
- color: #ED7EFF;
- border-bottom:3px solid #ED7EFF;
+ color: #c33541;
+ border-bottom:3px solid #c33541;
text-decoration: none;
}
}
#input_form_nav li.current a {
- background: -moz-linear-gradient(top, #33bcef 0%, #019ad2 100%); /* FF3.6+ */
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#33bcef), color-stop(100%,#019ad2)); /* Chrome,Safari4+ */
- background: -webkit-linear-gradient(top, #33bcef 0%,#019ad2 100%); /* Chrome10+,Safari5.1+ */
- background: -o-linear-gradient(top, #33bcef 0%,#019ad2 100%); /* Opera 11.10+ */
- background: -ms-linear-gradient(top, #33bcef 0%,#019ad2 100%); /* IE10+ */
- background: linear-gradient(to bottom, #33bcef 0%,#019ad2 100%); /* W3C */
+ background: -moz-linear-gradient(top, #a22430 0%, #c33541 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#a22430), color-stop(100%,#c33541)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #a22430 0%,#c33541 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #a22430 0%,#c33541 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, #a22430 0%,#c33541 100%); /* IE10+ */
+ background: linear-gradient(to bottom, #a22430 0%,#c33541 100%); /* W3C */
background-repeat: repeat-x;
text-shadow: rgba(0, 0, 0, 0.25) 0px -1px 1px;
- border-color:#096eb3;
+ border-color:#c33541;
color:#fff;
}
#input_form_nav li:hover a {
- background: -moz-linear-gradient(top, #2daddc 0%, #0271bf 100%); /* FF3.6+ */
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#2daddc), color-stop(100%,#0271bf)); /* Chrome,Safari4+ */
- background: -webkit-linear-gradient(top, #2daddc 0%,#0271bf 100%); /* Chrome10+,Safari5.1+ */
- background: -o-linear-gradient(top, #2daddc 0%,#0271bf 100%); /* Opera 11.10+ */
- background: -ms-linear-gradient(top, #2daddc 0%,#0271bf 100%); /* IE10+ */
- background: linear-gradient(to bottom, #2daddc 0%,#0271bf 100%); /* W3C */
+ background: -moz-linear-gradient(top, #c33541 0%, #d44652 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#c33541), color-stop(100%,#d44652)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #c33541 0%,#d44652 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #c33541 0%,#d44652 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, #c33541 0%,#d44652 100%); /* IE10+ */
+ background: linear-gradient(to bottom, #c33541 0%,#d44652 100%); /* W3C */
background-repeat: repeat-x;
text-shadow: rgba(0, 0, 0, 0.25) 0px -1px 1px;
- border-color:#096eb3;
+ border-color:#c33541;
color:#fff;
}
}
.form_notice .error, .form_notice .success, .form_notice .notice-status {
- background-color: #33bcef;
+ background-color: #a22430;
border: 0 none;
border-radius: 0;
color: rgba(255, 255, 255, 0.9);
margin-top: 0;
}
-.form_notice_placeholder .placeholder,
-.form_notice textarea ,
-.input_form .form_settings li input,
-.input_form .form_settings li textarea,
+.threaded-replies {
+ margin-top: 4px;
+ float: none;
+}
+
+#conversation .notices .notices {
+ float: none;
+ margin-left: 55px;
+}
+
.threaded-replies .placeholder { /* TODO combine all these declarations */
border-radius: 4px;
box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2);
right: 10px !important;
}
+#realtime_actions {
+ padding-top: 0px !important;
+}
#realtime_actions button {
margin-right: 5px;
color: rgba(0, 0, 0, 0.3);
}
+.notice > footer {
+ margin-bottom: 5px;
+}
+
/* loading */
#infscr-loading {
border: 1px solid #CDD1DD;
}
-.profile_list .form_group_leave input.submit,
-.profile_list .form_user_unsubscribe input.submit {
+.form_user_unsubscribe input.submit {
background-position: 2px -1250px;
}
-.profile_list .form_group_join input.submit,
-.profile_list .form_user_subscribe input.submit {
+.form_user_subscribe input.submit {
background-position: 2px -1184px;
}
+.form_user_block input.submit {
+ background-position: 2px -920px;
+}
+
.profile_list .entity_actions input:hover {
width: auto;
padding-right: 10px;
font-weight: 700;
color: #FFFFFF;
line-height: 16px;
- background: -moz-linear-gradient(top, #33bcef 0%, #019ad2 100%); /* FF3.6+ */
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#33bcef), color-stop(100%,#019ad2)); /* Chrome,Safari4+ */
- background: -webkit-linear-gradient(top, #33bcef 0%,#019ad2 100%); /* Chrome10+,Safari5.1+ */
- background: -o-linear-gradient(top, #33bcef 0%,#019ad2 100%); /* Opera 11.10+ */
- background: -ms-linear-gradient(top, #33bcef 0%,#019ad2 100%); /* IE10+ */
- background: linear-gradient(to bottom, #33bcef 0%,#019ad2 100%); /* W3C */
+ background: -moz-linear-gradient(top, #a22430 0%, #c33541 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#a22430), color-stop(100%,#c33541)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #a22430 0%,#c33541 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #a22430 0%,#c33541 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, #a22430 0%,#c33541 100%); /* IE10+ */
+ background: linear-gradient(to bottom, #a22430 0%,#c33541 100%); /* W3C */
background-repeat: repeat-x;
padding-top: 5px;
padding-right: 10px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
- border-top-color: #057ED0;
- border-right-color: #057ED0;
- border-bottom-color: #057ED0;
- border-left-color: #057ED0;
+ border-top-color: #c33541;
+ border-right-color: #c33541;
+ border-bottom-color: #c33541;
+ border-left-color: #c33541;
border-top-style: solid;
border-right-style: solid;
border-bottom-style: solid;
.entity_tag .dialogbox input.submit_dialogbox:hover,
.form_repeat.dialogbox input.submit_dialogbox:hover
{
- background: -moz-linear-gradient(top, #2daddc 0%, #0271bf 100%); /* FF3.6+ */
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#2daddc), color-stop(100%,#0271bf)); /* Chrome,Safari4+ */
- background: -webkit-linear-gradient(top, #2daddc 0%,#0271bf 100%); /* Chrome10+,Safari5.1+ */
- background: -o-linear-gradient(top, #2daddc 0%,#0271bf 100%); /* Opera 11.10+ */
- background: -ms-linear-gradient(top, #2daddc 0%,#0271bf 100%); /* IE10+ */
- background: linear-gradient(to bottom, #2daddc 0%,#0271bf 100%); /* W3C */
+ background: -moz-linear-gradient(top, #c33541 0%, #d44652 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#c33541), color-stop(100%,#d44652)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #c33541 0%,#d44652 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #c33541 0%,#d44652 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, #c33541 0%,#d44652 100%); /* IE10+ */
+ background: linear-gradient(to bottom, #c33541 0%,#d44652 100%); /* W3C */
background-repeat: repeat-x;
- border-color:#096eb3;
+ border-color:#c33541;
color:#fff;
}