diff --git a/INSTALL b/INSTALL
index aad21756fe..90fa84923b 100644
--- a/INSTALL
+++ b/INSTALL
@@ -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
diff --git a/actions/conversation.php b/actions/conversation.php
index 3b6f48c853..5a6e4b5c7a 100644
--- a/actions/conversation.php
+++ b/actions/conversation.php
@@ -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)')));
}
}
diff --git a/actions/emailsettings.php b/actions/emailsettings.php
index dfdbe1bad0..a0f111c0d5 100644
--- a/actions/emailsettings.php
+++ b/actions/emailsettings.php
@@ -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));
diff --git a/actions/networkpublic.php b/actions/networkpublic.php
index 7baa313bee..41c4e37e3c 100644
--- a/actions/networkpublic.php
+++ b/actions/networkpublic.php
@@ -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
diff --git a/actions/public.php b/actions/public.php
index 06ee75b8d1..000f82cb93 100644
--- a/actions/public.php
+++ b/actions/public.php
@@ -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
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 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)')));
}
}
diff --git a/actions/recoverpassword.php b/actions/recoverpassword.php
index 4839a036c0..060ba83510 100644
--- a/actions/recoverpassword.php
+++ b/actions/recoverpassword.php
@@ -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;
diff --git a/classes/File.php b/classes/File.php
index 1e296242b7..d4abbfddee 100644
--- a/classes/File.php
+++ b/classes/File.php
@@ -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 (
diff --git a/classes/File_redirection.php b/classes/File_redirection.php
index 12619b0394..ea9db8e891 100644
--- a/classes/File_redirection.php
+++ b/classes/File_redirection.php
@@ -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);
diff --git a/classes/File_thumbnail.php b/classes/File_thumbnail.php
index a2e633249f..fb2515f9f5 100644
--- a/classes/File_thumbnail.php
+++ b/classes/File_thumbnail.php
@@ -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);
}
}
diff --git a/classes/File_to_post.php b/classes/File_to_post.php
index 4c751ae4f3..e06e34aa46 100644
--- a/classes/File_to_post.php
+++ b/classes/File_to_post.php
@@ -17,9 +17,7 @@
* along with this program. If not, see .
*/
-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);
}
}
diff --git a/classes/Managed_DataObject.php b/classes/Managed_DataObject.php
index b324984b7f..a69a957bcc 100644
--- a/classes/Managed_DataObject.php
+++ b/classes/Managed_DataObject.php
@@ -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
*
diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php
index 3f1945205a..91b986891c 100644
--- a/classes/Memcached_DataObject.php
+++ b/classes/Memcached_DataObject.php
@@ -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();
diff --git a/classes/Notice.php b/classes/Notice.php
index 38e31cb274..9246c26919 100644
--- a/classes/Notice.php
+++ b/classes/Notice.php
@@ -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);
}
/**
diff --git a/classes/Profile.php b/classes/Profile.php
index 6eb09782b1..b5ba00caa9 100644
--- a/classes/Profile.php
+++ b/classes/Profile.php
@@ -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);
}
diff --git a/classes/Profile_prefs.php b/classes/Profile_prefs.php
index 27034390f8..72a707cae8 100644
--- a/classes/Profile_prefs.php
+++ b/classes/Profile_prefs.php
@@ -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;
diff --git a/classes/Queue_item.php b/classes/Queue_item.php
index 0d6fd56af2..3a7d05adef 100644
--- a/classes/Queue_item.php
+++ b/classes/Queue_item.php
@@ -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;
diff --git a/classes/User.php b/classes/User.php
index f543a75528..3efaa5e721 100644
--- a/classes/User.php
+++ b/classes/User.php
@@ -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.
diff --git a/js/extlib/jquery.js b/js/extlib/jquery.js
index 79d631ff46..eed17778c6 100644
--- a/js/extlib/jquery.js
+++ b/js/extlib/jquery.js
@@ -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 ) ) {
diff --git a/lib/apiaction.php b/lib/apiaction.php
index 0eea08bed6..724447f120 100755
--- a/lib/apiaction.php
+++ b/lib/apiaction.php
@@ -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;
diff --git a/lib/commandinterpreter.php b/lib/commandinterpreter.php
index d2b744e93d..c546cf0fca 100644
--- a/lib/commandinterpreter.php
+++ b/lib/commandinterpreter.php
@@ -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));
diff --git a/lib/default.php b/lib/default.php
index 6369fbddc6..0ec9fc4e14 100644
--- a/lib/default.php
+++ b/lib/default.php
@@ -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,
diff --git a/lib/framework.php b/lib/framework.php
index 4ec8b083e0..da43297d10 100644
--- a/lib/framework.php
+++ b/lib/framework.php
@@ -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);
diff --git a/lib/groupminilist.php b/lib/groupminilist.php
index ca7f8775c6..8212b81b17 100644
--- a/lib/groupminilist.php
+++ b/lib/groupminilist.php
@@ -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
*
diff --git a/lib/httpclient.php b/lib/httpclient.php
index 6016f89314..865fc9029e 100644
--- a/lib/httpclient.php
+++ b/lib/httpclient.php
@@ -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
}
/**
diff --git a/lib/implugin.php b/lib/implugin.php
index 5b0f3dbe09..2da4fa961a 100644
--- a/lib/implugin.php
+++ b/lib/implugin.php
@@ -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;
}
/**
diff --git a/lib/mediafile.php b/lib/mediafile.php
index 546239ed7d..2b8f324df2 100644
--- a/lib/mediafile.php
+++ b/lib/mediafile.php
@@ -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()
diff --git a/lib/noparentnoticeexception.php b/lib/noparentnoticeexception.php
new file mode 100644
index 0000000000..fea179c409
--- /dev/null
+++ b/lib/noparentnoticeexception.php
@@ -0,0 +1,41 @@
+.
+ *
+ * @category Exception
+ * @package GNUsocial
+ * @author Mikael Nordfeldth
+ * @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));
+ }
+}
diff --git a/lib/profileminilist.php b/lib/profileminilist.php
index 4f47487220..d2997d12de 100644
--- a/lib/profileminilist.php
+++ b/lib/profileminilist.php
@@ -29,8 +29,6 @@
if (!defined('GNUSOCIAL')) { exit(1); }
-define('PROFILES_PER_MINILIST', 8);
-
/**
* Widget to show a list of profiles, good for sidebar
*
diff --git a/lib/publicgroupnav.php b/lib/publicgroupnav.php
index 69347bc0d4..620a61ddd9 100644
--- a/lib/publicgroupnav.php
+++ b/lib/publicgroupnav.php
@@ -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'),
diff --git a/lib/schema.php b/lib/schema.php
index 0421bcb810..f536f01645 100644
--- a/lib/schema.php
+++ b/lib/schema.php
@@ -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);
}
}
diff --git a/lib/sitestreamaction.php b/lib/sitestreamaction.php
new file mode 100644
index 0000000000..d462c499a5
--- /dev/null
+++ b/lib/sitestreamaction.php
@@ -0,0 +1,182 @@
+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 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');
+ }
+}
diff --git a/lib/util.php b/lib/util.php
index dbc036c461..f29d9559b9 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -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());
}
diff --git a/plugins/ActivityVerb/actions/activityverb.php b/plugins/ActivityVerb/actions/activityverb.php
index 0abfacd645..45bb18be46 100644
--- a/plugins/ActivityVerb/actions/activityverb.php
+++ b/plugins/ActivityVerb/actions/activityverb.php
@@ -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).
diff --git a/plugins/AntiBrute/AntiBrutePlugin.php b/plugins/AntiBrute/AntiBrutePlugin.php
index 365937fedf..625180d23d 100755
--- a/plugins/AntiBrute/AntiBrutePlugin.php
+++ b/plugins/AntiBrute/AntiBrutePlugin.php
@@ -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:
diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php
index 07c9d1c182..4d1b95e2b7 100644
--- a/plugins/OStatus/classes/Ostatus_profile.php
+++ b/plugins/OStatus/classes/Ostatus_profile.php
@@ -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) {
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
index 0d093f2868..3ba2f4e5ab 100644
--- a/plugins/OpenID/OpenIDPlugin.php
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -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;
}
diff --git a/actions/publicxrds.php b/plugins/OpenID/actions/publicxrds.php
similarity index 88%
rename from actions/publicxrds.php
rename to plugins/OpenID/actions/publicxrds.php
index aac6f423cf..25801e7861 100644
--- a/actions/publicxrds.php
+++ b/plugins/OpenID/actions/publicxrds.php
@@ -30,12 +30,9 @@
* along with this program. If not, see .
*/
-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();
}
}
-
diff --git a/lib/xrdsoutputter.php b/plugins/OpenID/lib/xrdsoutputter.php
similarity index 96%
rename from lib/xrdsoutputter.php
rename to plugins/OpenID/lib/xrdsoutputter.php
index 95dc73300a..9841d9e871 100644
--- a/lib/xrdsoutputter.php
+++ b/plugins/OpenID/lib/xrdsoutputter.php
@@ -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
diff --git a/plugins/Share/SharePlugin.php b/plugins/Share/SharePlugin.php
index b5643c1d09..c337efbaec 100644
--- a/plugins/Share/SharePlugin.php
+++ b/plugins/Share/SharePlugin.php
@@ -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'),
diff --git a/plugins/TwitterBridge/lib/twitterimport.php b/plugins/TwitterBridge/lib/twitterimport.php
index 5258bfc2c9..45b7547ce2 100644
--- a/plugins/TwitterBridge/lib/twitterimport.php
+++ b/plugins/TwitterBridge/lib/twitterimport.php
@@ -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
}
diff --git a/plugins/WebFinger/WebFingerPlugin.php b/plugins/WebFinger/WebFingerPlugin.php
index e5759e886c..6f8ec9397d 100644
--- a/plugins/WebFinger/WebFingerPlugin.php
+++ b/plugins/WebFinger/WebFingerPlugin.php
@@ -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'));
}
/**
diff --git a/plugins/Xmpp/XmppPlugin.php b/plugins/Xmpp/XmppPlugin.php
index 2974e8b2ab..d95ffcf0d6 100644
--- a/plugins/Xmpp/XmppPlugin.php
+++ b/plugins/Xmpp/XmppPlugin.php
@@ -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)) {
diff --git a/scripts/console.php b/scripts/console.php
index c260ffaa00..692cedf8d1 100755
--- a/scripts/console.php
+++ b/scripts/console.php
@@ -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 = '';
}
diff --git a/scripts/nukefile.php b/scripts/nukefile.php
new file mode 100755
index 0000000000..1381676483
--- /dev/null
+++ b/scripts/nukefile.php
@@ -0,0 +1,78 @@
+#!/usr/bin/env php
+.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+
+$shortoptions = 'i::yv';
+$longoptions = array('id=', 'yes', 'verbose');
+
+$helptext = <<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";