From 0e6b80ded340e2011d436671e65e4a6ba0975d66 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 27 May 2015 20:21:05 +0200 Subject: [PATCH 01/40] more debugging info on failed schema.php runSqlSet --- lib/schema.php | 1 + 1 file changed, 1 insertion(+) 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); } } From cd0b70dbc1b4276e4c70d2c1711ac358aa34314a Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 27 May 2015 21:31:29 +0200 Subject: [PATCH 02/40] upgrade fix for file URLs longer than 191 chars --- classes/File.php | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/classes/File.php b/classes/File.php index 1e296242b7..0018a03e32 100644 --- a/classes/File.php +++ b/classes/File.php @@ -610,6 +610,37 @@ 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 "\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 ( From c31d6608a8d66ec5bf0ad27ef59730209ad40166 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 27 May 2015 21:54:51 +0200 Subject: [PATCH 03/40] remove _all_ file URLs not just the duplicates --- classes/File.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/classes/File.php b/classes/File.php index 0018a03e32..9a8f9eaf53 100644 --- a/classes/File.php +++ b/classes/File.php @@ -639,6 +639,8 @@ class File extends Managed_DataObject 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, From f926e27a652fa2a82241b2bbe2216b505d900465 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 27 May 2015 22:37:20 +0200 Subject: [PATCH 04/40] urlhash will _be_ NULL on update, so NOT NULL won't work --- classes/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/File.php b/classes/File.php index 9a8f9eaf53..594506449a 100644 --- a/classes/File.php +++ b/classes/File.php @@ -648,7 +648,7 @@ class File extends Managed_DataObject $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 ( From 3294d704a44203eb891d4b6485452fd16976ec2e Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 30 May 2015 15:41:04 +0200 Subject: [PATCH 05/40] scripts/nukefile.php for blasting crap from the server Deletes notices and the locally stored file based on File id, as you may want to just get rid of shit sometimes. --- classes/File.php | 9 +++++ classes/File_to_post.php | 18 ++++++++++ scripts/nukefile.php | 78 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100755 scripts/nukefile.php diff --git a/classes/File.php b/classes/File.php index 594506449a..1cb7eab6b8 100644 --- a/classes/File.php +++ b/classes/File.php @@ -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) diff --git a/classes/File_to_post.php b/classes/File_to_post.php index 4c751ae4f3..b3c44d4a22 100644 --- a/classes/File_to_post.php +++ b/classes/File_to_post.php @@ -85,6 +85,24 @@ class File_to_post extends Managed_DataObject } } + static function getNoticeIDsByFile(File $file) + { + $f2p = new File_to_post(); + + $f2p->selectAdd(); + $f2p->selectAdd('post_id'); + + $f2p->file_id = $file->id; + + $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); 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"; From b4b8cb57b326b6a668a090da060d72c30a20e967 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 30 May 2015 16:40:00 +0200 Subject: [PATCH 06/40] slugify console.php prompt name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit since PHP thought it was fun to crash on Quitter EspaƱa and I couldn't be bothered messing with readline --- classes/Profile.php | 5 +++++ scripts/console.php | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/classes/Profile.php b/classes/Profile.php index 6eb09782b1..47e144032d 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -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/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 = ''; } From 731d283159d865068fa19ede3119d72b418d4265 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 30 May 2015 23:18:17 +0200 Subject: [PATCH 07/40] Password recovery logic cleaned up --- actions/emailsettings.php | 1 + classes/User.php | 65 +++++++++++++++++++-------------------- 2 files changed, 32 insertions(+), 34 deletions(-) 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/classes/User.php b/classes/User.php index f543a75528..6a12bb6642 100644 --- a/classes/User.php +++ b/classes/User.php @@ -853,57 +853,55 @@ 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. + // TODO: Fake sending email (since we don't want to reveal which addresses exist or not) + if (!$user instanceof 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 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 or e-mail address 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 +910,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. From 82f9b6908cc12b7f46fac2f7e0471febdb789187 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 30 May 2015 23:29:16 +0200 Subject: [PATCH 08/40] Fake recovery by email address, to hide registrants on the site --- actions/recoverpassword.php | 14 ++++++++++---- classes/User.php | 10 +++++++--- lib/default.php | 1 + 3 files changed, 18 insertions(+), 7 deletions(-) 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/User.php b/classes/User.php index 6a12bb6642..3efaa5e721 100644 --- a/classes/User.php +++ b/classes/User.php @@ -873,16 +873,20 @@ class User extends Managed_DataObject } // No luck finding anyone by that email address. - // TODO: Fake sending email (since we don't want to reveal which addresses exist or not) if (!$user instanceof User) { - // TRANS: Information on password recovery form if no known username or e-mail address was specified. + 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 { // 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 or e-mail address was specified. + // TRANS: Information on password recovery form if no known username was specified. throw new ClientException(_('No user with that nickname exists here.')); } } 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, From a2ddcc124f76cea47c9fac7f8465515170675442 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 2 Jun 2015 10:54:37 +0200 Subject: [PATCH 09/40] No redirect follow on HEAD request (bump to PHP5.5 minimum requirement) We're using the try-catch-finally clause where "finally" wasn't introduced until PHP 5.5, so our minimum requirement for GNU social is bumped to that. --- INSTALL | 2 +- lib/httpclient.php | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) 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/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 } /** From 2cebbead7552c6b93ea549c8ce37820b4e3ab689 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 2 Jun 2015 12:58:30 +0200 Subject: [PATCH 10/40] Accidentally presented Atom feed as ActivityStreams JSON --- actions/conversation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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)'))); } } From 2096c18e57dbf502be3a9d88439bd769e87c179f Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 2 Jun 2015 13:13:55 +0200 Subject: [PATCH 11/40] use array_key_exists instead of empty to test array key --- classes/Notice.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 38e31cb274..ed2686a383 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1634,8 +1634,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; } From e728e2aa8199b16911a98189c7f08d3b82270aad Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 2 Jun 2015 13:17:51 +0200 Subject: [PATCH 12/40] typing, added typing to some common_* calls in util.php --- lib/util.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/util.php b/lib/util.php index dbc036c461..cafe209a74 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,7 +695,7 @@ 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); From 6b9a8b7b199991f4f26f3460fda85f8f59adbbd3 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 2 Jun 2015 13:45:22 +0200 Subject: [PATCH 13/40] Reuse code from our classes, don't write own algorithms --- lib/util.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/util.php b/lib/util.php index cafe209a74..395fadfbd7 100644 --- a/lib/util.php +++ b/lib/util.php @@ -697,11 +697,8 @@ function common_linkify_mention(array $mention) */ 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(); From c84b21008ed546e9c474cdb503d9b493004c7cb0 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 2 Jun 2015 13:45:49 +0200 Subject: [PATCH 14/40] Notice->getParent code reuse ...not entirely sure whether to allow getParent calls on Notice objects which have not been created, but we'll leave that in for now... --- classes/Notice.php | 9 +++----- lib/noparentnoticeexception.php | 41 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 lib/noparentnoticeexception.php diff --git a/classes/Notice.php b/classes/Notice.php index ed2686a383..4d57c5a8db 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -2762,13 +2762,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/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)); + } +} From 0726dcd06c122083b1977462b69fe91ac19290fc Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 2 Jun 2015 13:50:52 +0200 Subject: [PATCH 15/40] Start using NoParentNoticeException more widely --- classes/Notice.php | 6 +++--- lib/apiaction.php | 2 +- lib/implugin.php | 2 +- lib/util.php | 4 ++-- plugins/Xmpp/XmppPlugin.php | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 4d57c5a8db..49c6801ae7 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1311,7 +1311,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 +1617,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! } @@ -1852,7 +1852,7 @@ class Notice extends Managed_DataObject $reply = $this->getParent(); $ctx->replyToID = $reply->getUri(); $ctx->replyToUrl = $reply->getUrl(); - } catch (Exception $e) { + } catch (NoParentNoticeException $e) { // This is not a reply to something } 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/implugin.php b/lib/implugin.php index 5b0f3dbe09..98fba19911 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; } diff --git a/lib/util.php b/lib/util.php index 395fadfbd7..f29d9559b9 100644 --- a/lib/util.php +++ b/lib/util.php @@ -725,8 +725,8 @@ function common_find_mentions($text, Notice $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/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)) { From 7e388e697d6088fbc705ca523fc454b4392b64ac Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 2 Jun 2015 13:55:13 +0200 Subject: [PATCH 16/40] fallback to local URL if reply->getUrl() is missing Remote Activity notices generally don't have a proper HTTP URL associated. --- classes/Notice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/Notice.php b/classes/Notice.php index 49c6801ae7..500ea31c97 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1851,7 +1851,7 @@ class Notice extends Managed_DataObject try { $reply = $this->getParent(); $ctx->replyToID = $reply->getUri(); - $ctx->replyToUrl = $reply->getUrl(); + $ctx->replyToUrl = $reply->getUrl(true); // true for fallback to local URL, less messy } catch (NoParentNoticeException $e) { // This is not a reply to something } From 2bf0ec719da0a420d6378b687756df87f88596f5 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 2 Jun 2015 14:17:59 +0200 Subject: [PATCH 17/40] initialize command interpretation result to false --- lib/commandinterpreter.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/commandinterpreter.php b/lib/commandinterpreter.php index d2b744e93d..f6c7518fbd 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) { From c5da7306bdc71beab2866038480ab6aa430a02ff Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 2 Jun 2015 14:25:45 +0200 Subject: [PATCH 18/40] return logic tidied up for command interpretation --- lib/commandinterpreter.php | 2 -- lib/implugin.php | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/commandinterpreter.php b/lib/commandinterpreter.php index f6c7518fbd..c546cf0fca 100644 --- a/lib/commandinterpreter.php +++ b/lib/commandinterpreter.php @@ -298,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/implugin.php b/lib/implugin.php index 98fba19911..2da4fa961a 100644 --- a/lib/implugin.php +++ b/lib/implugin.php @@ -402,9 +402,8 @@ abstract class ImPlugin extends Plugin $chan = new IMChannel($this); $cmd->execute($chan); return true; - } else { - return false; } + return false; } /** From fd121f371a2943e582a4ba504c84c3054779f7e9 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 3 Jun 2015 22:43:51 +0200 Subject: [PATCH 19/40] case insensitive indexing for content and nick/fullnames (search) This broke when changing the database to utf8mb4 instead of utf8, since utf8_general_ci wasn't accepted and the engine fell back to utf8mb4_bin. Now we're back in case insensitive search business! --- classes/Notice.php | 2 +- classes/Profile.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 500ea31c97..b8af0fac6d 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'), diff --git a/classes/Profile.php b/classes/Profile.php index 47e144032d..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'), From 2b2ebfc254d5ddc770836dfe2fea0be68dbcb35f Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 3 Jun 2015 22:57:58 +0200 Subject: [PATCH 20/40] bump version to 1.2.0-alpha1 since we fixed the search issue and updated jquery --- js/extlib/jquery.js | 13 +++++++++---- lib/framework.php | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) 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/framework.php b/lib/framework.php index 4ec8b083e0..5e66e1ae6f 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); From 551c69ed56c4ea36e325df9b7f6204dadbee8d39 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 00:50:08 +0200 Subject: [PATCH 21/40] Extend Networkpublic and Public actions from SitestreamAction --- actions/networkpublic.php | 6 +- actions/public.php | 165 ++++++---------------------------- lib/sitestreamaction.php | 185 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+), 144 deletions(-) create mode 100644 lib/sitestreamaction.php diff --git a/actions/networkpublic.php b/actions/networkpublic.php index 7baa313bee..6f1124a9d8 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() { @@ -30,9 +30,7 @@ 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(); + parent::extraHead(); } function showSections() diff --git a/actions/public.php b/actions/public.php index 06ee75b8d1..7cae3cfd15 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 * @@ -45,52 +41,8 @@ define('MAX_PUBLIC_PAGE', 100); * @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()) { @@ -137,80 +89,6 @@ class PublicAction extends ManagedAction } } - /** - * 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 +117,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/lib/sitestreamaction.php b/lib/sitestreamaction.php new file mode 100644 index 0000000000..aa1c2e1b0a --- /dev/null +++ b/lib/sitestreamaction.php @@ -0,0 +1,185 @@ +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(); + $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 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'); + } +} From 26631bf9e613a20157e23ae6310b301b1aaf0445 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 00:53:35 +0200 Subject: [PATCH 22/40] Show network public feed link in PublicGroupNav --- lib/publicgroupnav.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/publicgroupnav.php b/lib/publicgroupnav.php index 69347bc0d4..1c9d013293 100644 --- a/lib/publicgroupnav.php +++ b/lib/publicgroupnav.php @@ -64,6 +64,12 @@ class PublicGroupNav extends Menu // TRANS: Menu item title in search group navigation panel. _('Public timeline'), $this->actionName == 'public', 'nav_timeline_public'); } + if (!$this->scoped instanceof Profile && common_config('public', 'localonly')) { + // 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'), From bdd15cfe6340df9fd387e817352bea5f16655d0a Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 00:56:46 +0200 Subject: [PATCH 23/40] don't double the output for some PublicAction->extraHead --- actions/public.php | 13 ------------- lib/sitestreamaction.php | 3 --- 2 files changed, 16 deletions(-) diff --git a/actions/public.php b/actions/public.php index 7cae3cfd15..175e0f1e70 100644 --- a/actions/public.php +++ b/actions/public.php @@ -74,19 +74,6 @@ class PublicAction extends SitestreamAction 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'))); - } } function showSections() diff --git a/lib/sitestreamaction.php b/lib/sitestreamaction.php index aa1c2e1b0a..d462c499a5 100644 --- a/lib/sitestreamaction.php +++ b/lib/sitestreamaction.php @@ -72,9 +72,6 @@ class SitestreamAction 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 From a4739b010795fc9a7f32cf1e85366453aa379206 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 01:08:03 +0200 Subject: [PATCH 24/40] Move some last OpenID stuff into the OpenID plugin (XRDS) --- actions/networkpublic.php | 5 ----- actions/public.php | 8 -------- plugins/OpenID/OpenIDPlugin.php | 11 ++++++++--- {actions => plugins/OpenID/actions}/publicxrds.php | 12 ++++-------- {lib => plugins/OpenID/lib}/xrdsoutputter.php | 6 +----- 5 files changed, 13 insertions(+), 29 deletions(-) rename {actions => plugins/OpenID/actions}/publicxrds.php (90%) rename {lib => plugins/OpenID/lib}/xrdsoutputter.php (96%) diff --git a/actions/networkpublic.php b/actions/networkpublic.php index 6f1124a9d8..41c4e37e3c 100644 --- a/actions/networkpublic.php +++ b/actions/networkpublic.php @@ -28,11 +28,6 @@ class NetworkpublicAction extends SitestreamAction } } - function extraHead() - { - parent::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 175e0f1e70..000f82cb93 100644 --- a/actions/public.php +++ b/actions/public.php @@ -39,7 +39,6 @@ if (!defined('GNUSOCIAL')) { exit(1); } * @link http://status.net/ * * @see PublicrssAction - * @see PublicxrdsAction */ class PublicAction extends SitestreamAction { @@ -69,13 +68,6 @@ class PublicAction extends SitestreamAction } } - function extraHead() - { - parent::extraHead(); - $this->element('meta', array('http-equiv' => 'X-XRDS-Location', - 'content' => common_local_url('publicxrds'))); - } - function showSections() { // Show invite button, as long as site isn't closed, and diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php index 0d093f2868..cf820c3ab6 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', @@ -184,7 +184,7 @@ class OpenIDPlugin extends Plugin * * @return boolean hook return */ - function onEndUserXRDS($action, &$xrdsOutputter) + function onEndUserXRDS(Action $action, &$xrdsOutputter) { $xrdsOutputter->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)', 'xml:id' => 'openid', @@ -415,7 +415,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 +427,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 90% rename from actions/publicxrds.php rename to plugins/OpenID/actions/publicxrds.php index aac6f423cf..5b099872f4 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 @@ -70,9 +67,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 +77,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 From 94492357650bbd2ab9edfaa17c1ae4165fb585aa Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 01:14:26 +0200 Subject: [PATCH 25/40] Remove some clutter from OMB plugin --- plugins/OpenID/OpenIDPlugin.php | 31 --------------------------- plugins/OpenID/actions/publicxrds.php | 2 -- 2 files changed, 33 deletions(-) diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php index cf820c3ab6..3ba2f4e5ab 100644 --- a/plugins/OpenID/OpenIDPlugin.php +++ b/plugins/OpenID/OpenIDPlugin.php @@ -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 $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. * diff --git a/plugins/OpenID/actions/publicxrds.php b/plugins/OpenID/actions/publicxrds.php index 5b099872f4..25801e7861 100644 --- a/plugins/OpenID/actions/publicxrds.php +++ b/plugins/OpenID/actions/publicxrds.php @@ -45,8 +45,6 @@ require_once __DIR__.'/../openid.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 { From 5358fb3cce663c4903fdc35a07b731d45c3ab279 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 17:02:45 +0200 Subject: [PATCH 26/40] Use the same cache string in all places for file:notice-ids --- classes/File.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classes/File.php b/classes/File.php index 1cb7eab6b8..76c00dc1f8 100644 --- a/classes/File.php +++ b/classes/File.php @@ -510,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); } From fe6498e7c875f5b386a9c7d2cc1fba5677daae09 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 17:36:11 +0200 Subject: [PATCH 27/40] Send objects instead of integers to File_to_post::processNew --- classes/File.php | 10 ++++----- classes/File_to_post.php | 23 ++++++++++----------- classes/Notice.php | 12 ++++------- lib/mediafile.php | 2 +- plugins/OStatus/classes/Ostatus_profile.php | 4 ++-- plugins/TwitterBridge/lib/twitterimport.php | 4 ++-- 6 files changed, 25 insertions(+), 30 deletions(-) diff --git a/classes/File.php b/classes/File.php index 76c00dc1f8..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; } diff --git a/classes/File_to_post.php b/classes/File_to_post.php index b3c44d4a22..7f2aca3362 100644 --- a/classes/File_to_post.php +++ b/classes/File_to_post.php @@ -58,30 +58,29 @@ class File_to_post extends Managed_DataObject ); } - function processNew($file_id, $notice_id) { + function processNew(File $file, Notice $notice) { static $seen = array(); + + $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])) { $f2p = File_to_post::pkeyGet(array('post_id' => $notice_id, 'file_id' => $file_id)); - if (empty($f2p)) { + if (!$f2p instanceof File_to_post) { $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; } } diff --git a/classes/Notice.php b/classes/Notice.php index b8af0fac6d..f9d80c1289 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1109,7 +1109,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 +1126,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 +1134,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? } 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/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/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 } From 5718f812d9f1df8ecdac97bbcf6b47778f0f2e3b Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 17:39:23 +0200 Subject: [PATCH 28/40] Network public publicgroupnav link would always show --- lib/publicgroupnav.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/publicgroupnav.php b/lib/publicgroupnav.php index 1c9d013293..9101a7f3b6 100644 --- a/lib/publicgroupnav.php +++ b/lib/publicgroupnav.php @@ -64,7 +64,7 @@ class PublicGroupNav extends Menu // TRANS: Menu item title in search group navigation panel. _('Public timeline'), $this->actionName == 'public', 'nav_timeline_public'); } - if (!$this->scoped instanceof Profile && common_config('public', 'localonly')) { + if (!$this->action->getScoped() instanceof Profile && common_config('public', 'localonly')) { // 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. From 20145092ce004332a2b5d1f09936e5599b995762 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 18:54:09 +0200 Subject: [PATCH 29/40] Publish OAuth data in host-meta --- plugins/WebFinger/WebFingerPlugin.php | 9 +++++++++ 1 file changed, 9 insertions(+) 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')); } /** From ca19a5cd6d9a9a369e6615503aef54432ebbfbc7 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 21:51:56 +0200 Subject: [PATCH 30/40] Easier pkeyCols call to get primary key columns --- classes/Managed_DataObject.php | 5 +++++ classes/Memcached_DataObject.php | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/classes/Managed_DataObject.php b/classes/Managed_DataObject.php index b324984b7f..925d6d3ae4 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 * 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(); From ebdd792b6f98a69e936fc91c1a73298c77d67e9c Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 22:17:40 +0200 Subject: [PATCH 31/40] getByPK (primary key) lookup for Managed_DataObject instances --- classes/Managed_DataObject.php | 48 +++++++++++++++++++ classes/Notice.php | 12 +---- plugins/ActivityVerb/actions/activityverb.php | 2 +- plugins/Share/SharePlugin.php | 2 +- 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/classes/Managed_DataObject.php b/classes/Managed_DataObject.php index 925d6d3ae4..b8c78680a0 100644 --- a/classes/Managed_DataObject.php +++ b/classes/Managed_DataObject.php @@ -309,6 +309,54 @@ 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(); + var_dump($classname); + + $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/Notice.php b/classes/Notice.php index f9d80c1289..9246c26919 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -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. */ @@ -2761,7 +2751,7 @@ class Notice extends Managed_DataObject if (empty($this->reply_to)) { throw new NoParentNoticeException($this); } - return self::getById($this->reply_to); + return self::getByID($this->reply_to); } /** 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/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'), From d1afc7812413493a4b4d0d788c87150bad35b74d Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 22:22:49 +0200 Subject: [PATCH 32/40] Modernize File_to_post to use Managed_DataObject functions --- classes/File_to_post.php | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/classes/File_to_post.php b/classes/File_to_post.php index 7f2aca3362..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 @@ -68,10 +66,10 @@ class File_to_post extends Managed_DataObject } 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 (!$f2p instanceof File_to_post) { + 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; @@ -91,7 +89,7 @@ class File_to_post extends Managed_DataObject $f2p->selectAdd(); $f2p->selectAdd('post_id'); - $f2p->file_id = $file->id; + $f2p->file_id = $file->getID(); $ids = array(); @@ -104,10 +102,13 @@ class File_to_post extends Managed_DataObject 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); } } From f9698598c6aa74d5f5e060989871f8e90d310adf Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 22:26:31 +0200 Subject: [PATCH 33/40] Modernize Profile_prefs to use Managed_DataObject functions --- classes/Profile_prefs.php | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) 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; From fc9de94cbdeec6da6ae977260b100e79f9fad93a Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 22:27:53 +0200 Subject: [PATCH 34/40] Modernize Queue_item to use Managed_DataObject functions --- classes/Queue_item.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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; From 63251fb9d0b1dfa6dd4f3375e6ca52b366586c83 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 22:29:40 +0200 Subject: [PATCH 35/40] Modernize File_thumbnail to use Managed_DataObject functions --- classes/File_thumbnail.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) 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); } } From cafab14f2bdbd05c9047d53cc094ec483ee23e81 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 22:33:36 +0200 Subject: [PATCH 36/40] Modernize File_redirection to use Managed_DataObject functions --- classes/File_redirection.php | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) 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); From 50f0235654f14a77164e28b41f7a16c47209bdc5 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 4 Jun 2015 22:34:28 +0200 Subject: [PATCH 37/40] Oops, don't forget to remove var_dump(...) --- classes/Managed_DataObject.php | 1 - 1 file changed, 1 deletion(-) diff --git a/classes/Managed_DataObject.php b/classes/Managed_DataObject.php index b8c78680a0..a69a957bcc 100644 --- a/classes/Managed_DataObject.php +++ b/classes/Managed_DataObject.php @@ -325,7 +325,6 @@ abstract class Managed_DataObject extends Memcached_DataObject static function getByPK(array $vals) { $classname = get_called_class(); - var_dump($classname); $pkey = static::pkeyCols(); if (is_null($pkey)) { From b24d711f552854d88f2df2120672bdaf173335fc Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 5 Jun 2015 14:01:03 +0200 Subject: [PATCH 38/40] Network wide feed link would NEVER show! --- lib/publicgroupnav.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/publicgroupnav.php b/lib/publicgroupnav.php index 9101a7f3b6..620a61ddd9 100644 --- a/lib/publicgroupnav.php +++ b/lib/publicgroupnav.php @@ -64,7 +64,8 @@ class PublicGroupNav extends Menu // TRANS: Menu item title in search group navigation panel. _('Public timeline'), $this->actionName == 'public', 'nav_timeline_public'); } - if (!$this->action->getScoped() instanceof Profile && common_config('public', 'localonly')) { + 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. From dee4ca37abb380f9f17dceaa5ba25a96dc105a8e Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 5 Jun 2015 14:07:03 +0200 Subject: [PATCH 39/40] GROUPS_PER_MINILIST was undefined in profileaction.php --- lib/framework.php | 3 +++ lib/groupminilist.php | 2 -- lib/profileminilist.php | 2 -- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/framework.php b/lib/framework.php index 5e66e1ae6f..da43297d10 100644 --- a/lib/framework.php +++ b/lib/framework.php @@ -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/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 * From 94f5247f91cc9b6ace60d632b91e329c68113c9b Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 5 Jun 2015 15:34:14 +0200 Subject: [PATCH 40/40] $this->client_ip was not always set in AntiBrutePlugin onEndCheckPassword --- plugins/AntiBrute/AntiBrutePlugin.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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: