Merge branch 'nightly' of git.gnu.io:gnu/gnu-social into nightly

This commit is contained in:
abjectio 2015-06-05 20:57:28 +02:00
commit c00b2ecc4b
44 changed files with 648 additions and 398 deletions

View File

@ -26,7 +26,7 @@ PHP modules
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

View File

@ -128,7 +128,7 @@ class ConversationAction extends ManagedAction
'format' => 'atom')),
// TRANS: Title for link to notice feed.
// TRANS: %s is a user nickname.
_('Conversation feed (Activity Streams JSON)')));
_('Conversation feed (Atom)')));
}
}

View File

@ -410,6 +410,7 @@ class EmailsettingsAction extends SettingsAction
$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));

View File

@ -2,7 +2,7 @@
if (!defined('GNUSOCIAL')) { exit(1); }
class NetworkpublicAction extends PublicAction
class NetworkpublicAction extends SitestreamAction
{
protected function streamPrepare()
{
@ -28,13 +28,6 @@ class NetworkpublicAction extends PublicAction
}
}
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

View File

@ -29,10 +29,6 @@
if (!defined('GNUSOCIAL')) { exit(1); }
// Farther than any human will go
define('MAX_PUBLIC_PAGE', 100);
/**
* Action for displaying the public stream
*
@ -43,54 +39,9 @@ define('MAX_PUBLIC_PAGE', 100);
* @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()) {
@ -117,100 +68,6 @@ class PublicAction extends ManagedAction
}
}
function extraHead()
{
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
$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 showSections()
{
// Show invite button, as long as site isn't closed, and
@ -239,23 +96,30 @@ class PublicAction extends ManagedAction
$feat->show();
}
function showAnonymousMessage()
/**
* Output <head> elements for RSS and Atom feeds
*
* @return void
*/
function getFeeds()
{
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');
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)')));
}
}

View File

@ -272,10 +272,16 @@ class RecoverpasswordAction extends Action
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;

View File

@ -116,14 +116,14 @@ class File extends Managed_DataObject
*
* @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');
}
@ -181,7 +181,7 @@ class File extends Managed_DataObject
//
// 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);
}
@ -193,8 +193,8 @@ class File extends Managed_DataObject
}
}
if (!empty($notice_id)) {
File_to_post::processNew($file->id, $notice_id);
if ($notice instanceof Notice) {
File_to_post::processNew($file, $notice);
}
return $file;
}
@ -249,6 +249,15 @@ class File extends Managed_DataObject
return true;
}
public function getFilename()
{
if (!self::validFilename($this->filename)) {
// TRANS: Client exception thrown if a file upload does not have a valid name.
throw new ClientException(_("Invalid filename."));
}
return $this->filename;
}
// where should the file go?
static function filename(Profile $profile, $origname, $mimetype)
@ -501,9 +510,9 @@ class File extends Managed_DataObject
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);
}
@ -610,12 +619,45 @@ class File extends Managed_DataObject
return;
}
echo "\nFound old $table table, upgrading it to contain 'urlhash' field...";
$file = new File();
$file->query(sprintf('SELECT id, LEFT(url, 191) AS shortenedurl, COUNT(*) AS c FROM %1$s WHERE LENGTH(url)>191 GROUP BY shortenedurl HAVING c > 1', $schema->quoteIdentifier($table)));
print "\nFound {$file->N} URLs with too long entries in file table\n";
while ($file->fetch()) {
// We've got a URL that is too long for our future file table
// so we'll cut it. We could save the original URL, but there is
// no guarantee it is complete anyway since the previous max was 255 chars.
$dupfile = new File();
// First we find file entries that would be duplicates of this when shortened
// ... and we'll just throw the dupes out the window for now! It's already so borken.
$dupfile->query(sprintf('SELECT * FROM file WHERE LEFT(url, 191) = "%1$s"', $file->shortenedurl));
// Leave one of the URLs in the database by using ->find(true) (fetches first entry)
if ($dupfile->find(true)) {
print "\nShortening url entry for $table id: {$file->id} [";
$orig = clone($dupfile);
$dupfile->url = $file->shortenedurl; // make sure it's only 191 chars from now on
$dupfile->update($orig);
print "\nDeleting duplicate entries of too long URL on $table id: {$file->id} [";
// only start deleting with this fetch.
while($dupfile->fetch()) {
print ".";
$dupfile->delete();
}
print "]\n";
} else {
print "\nWarning! URL suddenly disappeared from database: {$file->url}\n";
}
}
echo "...and now all the non-duplicates which are longer than 191 characters...\n";
$file->query('UPDATE file SET url=LEFT(url, 191) WHERE LENGTH(url)>191');
echo "\n...now running hacky pre-schemaupdate change for $table:";
// We have to create a urlhash that is _not_ the primary key,
// transfer data and THEN run checkSchema
$schemadef['fields']['urlhash'] = array (
'type' => 'varchar',
'length' => 64,
'not null' => true,
'not null' => false, // this is because when adding column, all entries will _be_ NULL!
'description' => 'sha256 of destination URL (url field)',
);
$schemadef['fields']['url'] = array (

View File

@ -59,12 +59,7 @@ class File_redirection extends Managed_DataObject
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) {
@ -261,7 +256,7 @@ class File_redirection extends Managed_DataObject
// 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);
@ -269,7 +264,7 @@ class File_redirection extends Managed_DataObject
// 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);
@ -281,7 +276,7 @@ class File_redirection extends Managed_DataObject
// SSL sites with cert issues.
return null;
}
$file_id = $file->id;
$file_id = $file->getID();
}
}
$file_redir = File_redirection::getKV('url', $short_url);

View File

@ -82,9 +82,9 @@ class File_thumbnail extends Managed_DataObject
* 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;
}
@ -167,11 +167,6 @@ class File_thumbnail extends Managed_DataObject
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);
}
}

View File

@ -17,9 +17,7 @@
* 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
@ -58,39 +56,59 @@ class File_to_post extends Managed_DataObject
);
}
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;
}
}
static function getNoticeIDsByFile(File $file)
{
$f2p = new File_to_post();
$f2p->selectAdd();
$f2p->selectAdd('post_id');
$f2p->file_id = $file->getID();
$ids = array();
if (!$f2p->find()) {
throw new NoResultException($f2p);
}
return $f2p->fetchAll('post_id');
}
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);
}
}

View File

@ -64,6 +64,11 @@ abstract class Managed_DataObject extends Memcached_DataObject
return parent::pkeyGetClass(get_called_class(), $kv);
}
static function pkeyCols()
{
return parent::pkeyColsClass(get_called_class());
}
/**
* Get multiple items from the database by key
*
@ -304,6 +309,53 @@ abstract class Managed_DataObject extends Memcached_DataObject
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
*

View File

@ -34,7 +34,7 @@ class Memcached_DataObject extends Safe_DataObject
{
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');
@ -246,7 +246,7 @@ class Memcached_DataObject extends Safe_DataObject
return $query;
}
static function pkeyCols($cls)
static function pkeyColsClass($cls)
{
$i = new $cls;
$types = $i->keyTypes();
@ -279,7 +279,7 @@ class Memcached_DataObject extends Safe_DataObject
$pkeyMap = array_fill_keys($keyVals, array());
$result = array_fill_keys($keyVals, array());
$pkeyCols = self::pkeyCols($cls);
$pkeyCols = static::pkeyCols();
$toFetch = array();
$allPkeys = array();

View File

@ -84,7 +84,7 @@ class Notice extends Managed_DataObject
'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'),
@ -313,16 +313,6 @@ class Notice extends Managed_DataObject
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.
*/
@ -1109,7 +1099,7 @@ class Notice extends Managed_DataObject
*/
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);
}
}
@ -1126,11 +1116,7 @@ class Notice extends Managed_DataObject
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);
}
}
}
@ -1138,9 +1124,9 @@ class Notice extends Managed_DataObject
/**
* @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?
}
@ -1311,7 +1297,7 @@ class Notice extends Managed_DataObject
$last = $parent;
continue;
}
} catch (Exception $e) {
} catch (NoParentNoticeException $e) {
// Latest notice has no parent
}
// No parent, or parent out of scope
@ -1617,7 +1603,7 @@ class Notice extends Managed_DataObject
$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!
}
@ -1634,8 +1620,7 @@ class Notice extends Managed_DataObject
foreach ($mention['mentioned'] as $mentioned) {
// skip if they're already covered
if (!empty($replied[$mentioned->id])) {
if (array_key_exists($mentioned->id, $replied)) {
continue;
}
@ -1852,8 +1837,8 @@ class Notice extends Managed_DataObject
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
}
@ -2763,13 +2748,10 @@ class Notice extends Managed_DataObject
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);
}
/**

View File

@ -48,12 +48,12 @@ class Profile extends Managed_DataObject
'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'),
@ -880,6 +880,11 @@ class Profile extends Managed_DataObject
$inst->delete();
}
$localuser = User::getKV('id', $this->id);
if ($localuser instanceof User) {
$localuser->delete();
}
return parent::delete($useWhere);
}

View File

@ -62,11 +62,11 @@ class Profile_prefs extends Managed_DataObject
{
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)) {
@ -85,7 +85,7 @@ class Profile_prefs extends Managed_DataObject
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();
}
@ -101,15 +101,9 @@ class Profile_prefs extends Managed_DataObject
}
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) {
@ -164,7 +158,7 @@ class Profile_prefs extends Managed_DataObject
}
$pref = new Profile_prefs();
$pref->profile_id = $profile->id;
$pref->profile_id = $profile->getID();
$pref->namespace = $namespace;
$pref->topic = $topic;
$pref->data = $data;

View File

@ -63,7 +63,7 @@ class Queue_item extends Managed_DataObject
// 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();
@ -85,7 +85,7 @@ class Queue_item extends Managed_DataObject
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;

View File

@ -853,57 +853,59 @@ class User extends Managed_DataObject
static function recoverPassword($nore)
{
$user = User::getKV('email', common_canonical_email($nore));
// $confirm_email will be used as a fallback if our user doesn't have a confirmed email
$confirm_email = null;
if (!$user) {
try {
$user = User::getKV('nickname', common_canonical_nickname($nore));
} catch (NicknameException $e) {
// invalid
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
@ -912,13 +914,12 @@ class User extends Managed_DataObject
$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.

13
js/extlib/jquery.js vendored
View File

@ -1,5 +1,5 @@
/*!
* jQuery JavaScript Library v2.1.3
* jQuery JavaScript Library v2.1.4
* http://jquery.com/
*
* Includes Sizzle.js
@ -9,7 +9,7 @@
* Released under the MIT license
* http://jquery.org/license
*
* Date: 2014-12-18T15:11Z
* Date: 2015-04-28T16:01Z
*/
(function( global, factory ) {
@ -67,7 +67,7 @@ var
// 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 ) {
@ -531,7 +531,12 @@ jQuery.each("Boolean Number String Function Array Date RegExp Object Error".spli
});
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 ) ) {

View File

@ -328,7 +328,7 @@ class ApiAction extends Action
// 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;

View File

@ -36,6 +36,7 @@ class CommandInterpreter
// StatusNet
$cmd = strtolower($cmd);
$result = false;
if (Event::handle('StartInterpretCommand', array($cmd, $arg, $user, &$result))) {
switch($cmd) {
@ -297,8 +298,6 @@ class CommandInterpreter
$result = new TrackingCommand($user);
}
break;
default:
$result = false;
}
Event::handle('EndInterpretCommand', array($cmd, $arg, $user, &$result));

View File

@ -48,6 +48,7 @@ $default =
'languages' => get_all_languages(),
'email' =>
array_key_exists('SERVER_ADMIN', $_SERVER) ? $_SERVER['SERVER_ADMIN'] : null,
'fakeaddressrecovery' => true,
'broughtby' => null,
'timezone' => 'UTC',
'broughtbyurl' => null,

View File

@ -23,7 +23,7 @@ define('GNUSOCIAL_ENGINE', 'GNU social');
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);
@ -38,6 +38,9 @@ define('PROFILES_PER_PAGE', 20);
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);

View File

@ -33,8 +33,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
require_once INSTALLDIR.'/lib/grouplist.php';
define('GROUPS_PER_MINILIST', 8);
/**
* Widget to show a list of groups, good for sidebar
*

View File

@ -103,7 +103,7 @@ class GNUsocial_HTTPResponse extends HTTP_Request2_Response
*
* 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
@ -205,12 +205,28 @@ class HTTPClient extends HTTP_Request2
/**
* 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
}
/**

View File

@ -380,7 +380,7 @@ abstract class ImPlugin extends Plugin
$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;
}
@ -402,9 +402,8 @@ abstract class ImPlugin extends Plugin
$chan = new IMChannel($this);
$cmd->execute($chan);
return true;
} else {
return false;
}
return false;
}
/**

View File

@ -61,7 +61,7 @@ class MediaFile
public function attachToNotice(Notice $notice)
{
File_to_post::processNew($this->fileRecord->id, $notice->id);
File_to_post::processNew($this->fileRecord, $notice);
}
public function getPath()

View File

@ -0,0 +1,41 @@
<?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));
}
}

View File

@ -29,8 +29,6 @@
if (!defined('GNUSOCIAL')) { exit(1); }
define('PROFILES_PER_MINILIST', 8);
/**
* Widget to show a list of profiles, good for sidebar
*

View File

@ -64,6 +64,13 @@ class PublicGroupNav extends Menu
// 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'),

View File

@ -535,6 +535,7 @@ class Schema
$res = $this->conn->query($sql);
if ($_PEAR->isError($res)) {
common_debug('PEAR exception on query: '.$sql);
PEAR_ErrorToPEAR_Exception($res);
}
}

182
lib/sitestreamaction.php Normal file
View File

@ -0,0 +1,182 @@
<?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');
}
}

View File

@ -628,7 +628,7 @@ function common_render_content($text, Notice $notice)
* @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);
@ -655,7 +655,7 @@ function common_linkify_mentions($text, $notice)
return $text;
}
function common_linkify_mention($mention)
function common_linkify_mention(array $mention)
{
$output = null;
@ -695,13 +695,10 @@ function common_linkify_mention($mention)
*
* @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();
@ -728,8 +725,8 @@ function common_find_mentions($text, $notice)
}
} 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());
}

View File

@ -50,7 +50,7 @@ class ActivityverbAction extends ManagedAction
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).

View File

@ -9,6 +9,13 @@ class AntiBrutePlugin extends Plugin {
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)) {
@ -22,9 +29,6 @@ class AntiBrutePlugin extends Plugin {
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:

View File

@ -691,8 +691,8 @@ class Ostatus_profile extends Managed_DataObject
$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) {

View File

@ -154,7 +154,7 @@ class OpenIDPlugin extends Plugin
*
* @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',
@ -174,37 +174,6 @@ class OpenIDPlugin extends Plugin
$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.
*
@ -415,7 +384,7 @@ class OpenIDPlugin extends Plugin
*
* @return void
*/
function onEndShowHeadElements($action)
function onEndShowHeadElements(Action $action)
{
if ($action instanceof ShowstreamAction) {
$action->element('link', array('rel' => 'openid2.provider',
@ -427,6 +396,11 @@ class OpenIDPlugin extends Plugin
$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;
}

View File

@ -30,12 +30,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
if (!defined('GNUSOCIAL')) { exit(1); }
require_once INSTALLDIR.'/plugins/OpenID/openid.php';
require_once INSTALLDIR.'/lib/xrdsoutputter.php';
require_once __DIR__.'/../openid.php';
/**
* Public XRDS
@ -48,8 +45,6 @@ require_once INSTALLDIR.'/lib/xrdsoutputter.php';
* @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
{
@ -70,9 +65,9 @@ class PublicxrdsAction extends Action
*
* @return nothing
*/
function handle($args)
protected function handle()
{
parent::handle($args);
parent::handle();
$xrdsOutputter = new XRDSOutputter();
$xrdsOutputter->startXRDS();
Event::handle('StartPublicXRDS', array($this,&$xrdsOutputter));
@ -80,4 +75,3 @@ class PublicxrdsAction extends Action
$xrdsOutputter->endXRDS();
}
}

View File

@ -28,11 +28,7 @@
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/xmloutputter.php';
if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Low-level generator for XRDS XML

View File

@ -161,7 +161,7 @@ class SharePlugin extends ActivityVerbHandlerPlugin
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'),

View File

@ -564,13 +564,13 @@ class TwitterImport
* @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
}

View File

@ -31,6 +31,10 @@ if (!defined('GNUSOCIAL')) { exit(1); }
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()
@ -127,6 +131,11 @@ class WebFingerPlugin extends Plugin
$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'));
}
/**

View File

@ -354,7 +354,7 @@ class XmppPlugin extends ImPlugin
$xs->text(": ");
} catch (InvalidUrlException $e) {
$xs->text(sprintf(' => %s', $orig_profile->nickname));
} catch (Exception $e) {
} catch (NoParentNoticeException $e) {
$xs->text(": ");
}
if (!empty($notice->rendered)) {

View File

@ -113,7 +113,7 @@ function readline_emulation($prompt)
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";
@ -128,8 +128,8 @@ function console_help()
}
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 = '';
}

78
scripts/nukefile.php Executable file
View File

@ -0,0 +1,78 @@
#!/usr/bin/env php
<?php
/*
* StatusNet - a 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/>.
*/
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
$shortoptions = 'i::yv';
$longoptions = array('id=', 'yes', 'verbose');
$helptext = <<<END_OF_HELP
nukefile.php [options]
deletes a file and related notices from the database
-i --id ID of the file
-v --verbose Be verbose (print the contents of the notices deleted).
END_OF_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc';
if (have_option('i', 'id')) {
$id = get_option_value('i', 'id');
$file = File::getKV('id', $id);
if (!$file instanceof File) {
print "Can't find file with ID $id\n";
exit(1);
}
} else {
print "You must provide a file ID.\n";
exit(1);
}
$verbose = have_option('v', 'verbose');
if (!have_option('y', 'yes')) {
try {
$filename = $file->getFilename();
} catch (Exception $e) {
$filename = '(remote file or no filename)';
}
print "About to PERMANENTLY delete file ($filename) ({$file->id}). Are you sure? [y/N] ";
$response = fgets(STDIN);
if (strtolower(trim($response)) != 'y') {
print "Aborting.\n";
exit(0);
}
}
print "Finding notices...\n";
try {
$ids = File_to_post::getNoticeIDsByFile($file);
$notice = Notice::multiGet('id', $ids);
while ($notice->fetch()) {
print "Deleting notice {$notice->id}".($verbose ? ": $notice->content\n" : "\n");
$notice->delete();
}
} catch (NoResultException $e) {
print "No notices found with this File attached.\n";
}
print "Deleting File object together with possibly locally stored copy.\n";
$file->delete();
print "DONE.\n";