From c31c2d10b972b9efc750fcec28acdd90da81a4cd Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 17 Feb 2015 20:11:35 +0100 Subject: [PATCH 01/17] PHP>=5.4.0 lets us use Transliterator, tags now asciified! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For example: #REVOLUCIÓN becomes #revolucion instead of #revolución --- lib/util.php | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/util.php b/lib/util.php index a32c35395e..b81aa5ef35 100644 --- a/lib/util.php +++ b/lib/util.php @@ -1116,6 +1116,20 @@ function common_xml_safe_str($str) return preg_replace('/[\p{Cc}\p{Cs}]/u', '*', $str); } +function common_slugify($str) +{ + $str = transliterator_transliterate( + 'Any-Latin;' . // any charset to latin compatible + 'NFD;' . // decompose + '[:Nonspacing Mark:] Remove;' . // remove nonspacing marks (accents etc.) + 'NFC;' . // composite again + '[:Punctuation:] Remove;' . // remove punctuation (.,¿? etc.) + 'Lower();' . // turn into lowercase + 'Latin-ASCII;', // get ASCII equivalents (ð to d for example) + $str); + return preg_replace('/[^\pL\pN]/', '', $str); +} + function common_tag_link($tag) { $canonical = common_canonical_tag($tag); @@ -1139,11 +1153,9 @@ function common_tag_link($tag) function common_canonical_tag($tag) { - // only alphanum - $tag = preg_replace('/[^\pL\pN]/u', '', $tag); - $tag = mb_convert_case($tag, MB_CASE_LOWER, "UTF-8"); - $tag = substr($tag, 0, 64); - return $tag; + $tag = common_slugify($tag); + $tag = substr($tag, 0, 64); + return $tag; } function common_valid_profile_tag($str) From 7ba7f4319952a38570c36dc083e4c83f01447cb1 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 17 Feb 2015 20:54:32 +0100 Subject: [PATCH 02/17] Don't linkify bare domains by default It's too farfetched to assume any text.com in a notice is an HTTP URL. For example stuff like pasting from log entries, with domain.com:1234 where 1234 is a _PID_ or something, not a port number for http://... --- lib/default.php | 3 +++ lib/util.php | 15 +++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/default.php b/lib/default.php index 6ca61f191b..b1c6d5ea33 100644 --- a/lib/default.php +++ b/lib/default.php @@ -207,6 +207,9 @@ $default = 'newuser' => array('default' => null, 'welcome' => null), + 'linkify' => array( + 'bare_domains' => false, // convert domain.com to domain.com ? + ), 'attachments' => array('server' => null, 'dir' => INSTALLDIR . '/file/', diff --git a/lib/util.php b/lib/util.php index b81aa5ef35..eb3750ff5f 100644 --- a/lib/util.php +++ b/lib/util.php @@ -870,12 +870,15 @@ function common_replace_urls_callback($text, $callback, $arg = null) { '|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'. //IPv4 '|(?:'. //IPv6 '\[?(?:(?:(?:[0-9A-Fa-f]{1,4}:){7}(?:(?:[0-9A-Fa-f]{1,4})|:))|(?:(?:[0-9A-Fa-f]{1,4}:){6}(?::|(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})|(?::[0-9A-Fa-f]{1,4})))|(?:(?:[0-9A-Fa-f]{1,4}:){5}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){4}(?::[0-9A-Fa-f]{1,4}){0,1}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){3}(?::[0-9A-Fa-f]{1,4}){0,2}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){2}(?::[0-9A-Fa-f]{1,4}){0,3}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:)(?::[0-9A-Fa-f]{1,4}){0,4}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?::(?::[0-9A-Fa-f]{1,4}){0,5}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})))\]?(? Date: Tue, 17 Feb 2015 21:31:35 +0100 Subject: [PATCH 03/17] forgot primary key column to updateWithKeys in SalmonAction --- plugins/OStatus/lib/salmonaction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/OStatus/lib/salmonaction.php b/plugins/OStatus/lib/salmonaction.php index 365f2c829c..2954d8038c 100644 --- a/plugins/OStatus/lib/salmonaction.php +++ b/plugins/OStatus/lib/salmonaction.php @@ -249,7 +249,7 @@ class SalmonAction extends Action $orig = clone($oprofile); $oprofile->uri = $e->object_uri; common_debug('URIFIX Updating Ostatus_profile URI for '.$aliased_uri.' to '.$oprofile->uri); - $oprofile->updateWithKeys($orig); + $oprofile->updateWithKeys($orig, 'uri'); // 'uri' is the primary key column unset($orig); $this->oprofile = $oprofile; break; // don't iterate through aliases anymore From 3dce6d9f6a1a95c42cdc1762140528323c71b08f Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 18 Feb 2015 00:10:31 +0100 Subject: [PATCH 04/17] Implement a common_purify for htmLawed and more We're removing unicode formatting characters as well, such as RTL marks. For more info on why we're because extra cautious (but may accept the characters in later versions) you can read: https://blog.malwarebytes.org/online-security/2014/01/the-rtlo-method/ --- lib/util.php | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/lib/util.php b/lib/util.php index eb3750ff5f..9a70d8d44e 100644 --- a/lib/util.php +++ b/lib/util.php @@ -576,6 +576,25 @@ function common_canonical_email($email) return $email; } +function common_purify($html) +{ + require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php'; + + $config = array('safe' => 1, + 'deny_attribute' => 'id,style,on*'); + + $html = common_remove_unicode_formatting($html); + + return htmLawed($html, $config); +} + +function common_remove_unicode_formatting($text) +{ + // Strip Unicode text formatting/direction codes + // this is pretty dangerous for visualisation of text and can be used for mischief + return preg_replace('/[\\x{200b}-\\x{200f}\\x{202a}-\\x{202e}]/u', '', $text); +} + /** * Partial notice markup rendering step: build links to !group references. * @@ -585,9 +604,9 @@ function common_canonical_email($email) */ function common_render_content($text, Notice $notice) { - $r = common_render_text($text); - $r = common_linkify_mentions($r, $notice); - return $r; + $text = common_render_text($text); + $text = common_linkify_mentions($text, $notice); + return $text; } /** @@ -829,14 +848,15 @@ function common_find_mentions_raw($text) function common_render_text($text) { - $r = nl2br(htmlspecialchars($text)); + $text = common_remove_unicode_formatting($text); + $text = nl2br(htmlspecialchars($text)); - $r = preg_replace('/[\x{0}-\x{8}\x{b}-\x{c}\x{e}-\x{19}]/', '', $r); - $r = common_replace_urls_callback($r, 'common_linkify'); - $r = preg_replace_callback('/(^|\"\;|\'|\(|\[|\{|\s+)#([\pL\pN_\-\.]{1,64})/u', - function ($m) { return "{$m[1]}#".common_tag_link($m[2]); }, $r); + $text = preg_replace('/[\x{0}-\x{8}\x{b}-\x{c}\x{e}-\x{19}]/', '', $text); + $text = common_replace_urls_callback($text, 'common_linkify'); + $text = preg_replace_callback('/(^|\"\;|\'|\(|\[|\{|\s+)#([\pL\pN_\-\.]{1,64})/u', + function ($m) { return "{$m[1]}#".common_tag_link($m[2]); }, $text); // XXX: machine tags - return $r; + return $text; } /** From 0deaf6c50c0a02dd307b797729adbaf2a973db07 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 18 Feb 2015 00:14:28 +0100 Subject: [PATCH 05/17] use common_purify to purify HTML, one function to rule them all --- actions/apitimelineuser.php | 11 +---------- lib/activityimporter.php | 13 +------------ plugins/Blog/classes/Blog_entry.php | 18 ++---------------- plugins/OStatus/classes/Ostatus_profile.php | 15 ++------------- 4 files changed, 6 insertions(+), 51 deletions(-) diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php index 26c960fa04..abc7fd6a96 100644 --- a/actions/apitimelineuser.php +++ b/actions/apitimelineuser.php @@ -405,7 +405,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction // Get (safe!) HTML and text versions of the content - $rendered = $this->purify($sourceContent); + $rendered = common_purify($sourceContent); $content = common_strip_html($rendered); $shortened = $this->auth_user->shortenLinks($content); @@ -504,13 +504,4 @@ class ApiTimelineUserAction extends ApiBareAuthAction return $saved; } - - function purify($content) - { - require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php'; - - $config = array('safe' => 1, - 'deny_attribute' => 'id,style,on*'); - return htmLawed($content, $config); - } } diff --git a/lib/activityimporter.php b/lib/activityimporter.php index 4e13419ae7..5bef4cfb07 100644 --- a/lib/activityimporter.php +++ b/lib/activityimporter.php @@ -213,7 +213,7 @@ class ActivityImporter extends QueueHandler // Get (safe!) HTML and text versions of the content - $rendered = $this->purify($sourceContent); + $rendered = common_purify($sourceContent); $content = common_strip_html($rendered); $shortened = $user->shortenLinks($content); @@ -338,15 +338,4 @@ class ActivityImporter extends QueueHandler return array($groups, $replies); } - - - function purify($content) - { - require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php'; - - $config = array('safe' => 1, - 'deny_attribute' => 'id,style,on*'); - - return htmLawed($content, $config); - } } diff --git a/plugins/Blog/classes/Blog_entry.php b/plugins/Blog/classes/Blog_entry.php index 1f585dce4f..6b82a0fdd1 100644 --- a/plugins/Blog/classes/Blog_entry.php +++ b/plugins/Blog/classes/Blog_entry.php @@ -117,10 +117,10 @@ class Blog_entry extends Managed_DataObject $be->id = (string) new UUID(); $be->profile_id = $profile->id; $be->title = $title; // Note: not HTML-protected - $be->content = self::purify($content); + $be->content = common_purify($content); if (array_key_exists('summary', $options)) { - $be->summary = self::purify($options['summary']); + $be->summary = common_purify($options['summary']); } else { // Already purified $be->summary = self::summarize($be->content); @@ -241,18 +241,4 @@ class Blog_entry extends Managed_DataObject return $obj; } - - /** - * Clean up input HTML - */ - static function purify($html) - { - require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php'; - - $config = array('safe' => 1, - 'deny_attribute' => 'id,style,on*'); - $pure = htmLawed($html, $config); - - return $pure; - } } diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 79098c6404..4be4e5112f 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -621,7 +621,7 @@ class Ostatus_profile extends Managed_DataObject // Get (safe!) HTML and text versions of the content - $rendered = $this->purify($sourceContent); + $rendered = common_purify($sourceContent); $content = common_strip_html($rendered); $shortened = common_shorten_links($content); @@ -788,7 +788,7 @@ class Ostatus_profile extends Managed_DataObject // Get (safe!) HTML and text versions of the content - $rendered = $this->purify($sourceContent); + $rendered = common_purify($sourceContent); $content = common_strip_html($rendered); $shortened = common_shorten_links($content); @@ -914,17 +914,6 @@ class Ostatus_profile extends Managed_DataObject return $saved; } - /** - * Clean up HTML - */ - protected function purify($html) - { - require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php'; - $config = array('safe' => 1, - 'deny_attribute' => 'id,style,on*'); - return htmLawed($html, $config); - } - /** * Filters a list of recipient ID URIs to just those for local delivery. * @param Profile local profile of sender From 8fbdb4b9ac7f7ff9da8c7152a7ec1fad1116260e Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 18 Feb 2015 00:28:45 +0100 Subject: [PATCH 06/17] New dependency: php5-intl Internationalization support is required for transliteration, which is currently used when creating the slugs for hashtags and such. It is a much more stable and efficient solution than having an unmaintainable list of unciode characters in an array... --- INSTALL | 1 + 1 file changed, 1 insertion(+) diff --git a/INSTALL b/INSTALL index 0483320a78..fff63681be 100644 --- a/INSTALL +++ b/INSTALL @@ -41,6 +41,7 @@ functional setup of GNU Social: - php5-curl Fetching files by HTTP. - php5-gd Image manipulation (scaling). - php5-gmp For Salmon signatures (part of OStatus). +- php5-intl Internationalization support (transliteration et al). - php5-json For WebFinger lookups and more. - php5-mysqlnd The native driver for PHP5 MariaDB connections. If you use MySQL, 'mysql' or 'mysqli' may work. From 66df043c19a195abcc14405631fb279304a4d525 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 18 Feb 2015 00:47:00 +0100 Subject: [PATCH 07/17] Add php5-intl dependency to Installer class --- lib/installer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/installer.php b/lib/installer.php index cea7d29ec7..1fcd0961c5 100644 --- a/lib/installer.php +++ b/lib/installer.php @@ -101,7 +101,7 @@ abstract class Installer $pass = false; } - $reqs = array('gd', 'curl', 'json', + $reqs = array('gd', 'curl', 'intl', 'json', 'xmlwriter', 'mbstring', 'xml', 'dom', 'simplexml'); foreach ($reqs as $req) { From b039d960f50dc0307e85604009ba4b10717a1d75 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 18 Feb 2015 11:08:11 +0100 Subject: [PATCH 08/17] Make group search in Directory use PEAR::DB No more direct SQL queries please. --- plugins/Directory/actions/groupdirectory.php | 31 +++++++++----------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/plugins/Directory/actions/groupdirectory.php b/plugins/Directory/actions/groupdirectory.php index 5e532fc214..496f035570 100644 --- a/plugins/Directory/actions/groupdirectory.php +++ b/plugins/Directory/actions/groupdirectory.php @@ -27,12 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET')) -{ - exit(1); -} - -require_once INSTALLDIR . '/lib/publicgroupnav.php'; +if (!defined('GNUSOCIAL')) { exit(1); } /** * Group directory @@ -40,6 +35,7 @@ require_once INSTALLDIR . '/lib/publicgroupnav.php'; * @category Directory * @package StatusNet * @author Zach Copley + * @author Mikael Nordfeldth * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ @@ -308,6 +304,14 @@ class GroupdirectoryAction extends Action if (isset($this->q)) { + // Disable this to get global group searches + $group->joinAdd(array('id', 'local_group:group_id')); + + $wheres = array('nickname', 'fullname', 'homepage', 'description', 'location'); + foreach ($wheres as $where) { + $group->whereAdd("LOWER({$group->__table}.{$where}) LIKE LOWER('%".$group->escape($this->q)."%')", 'OR'); + } + $order = 'user_group.created ASC'; if ($this->sort == 'nickname') { @@ -322,17 +326,8 @@ class GroupdirectoryAction extends Action } } - $sql = <<< GROUP_QUERY_END -SELECT user_group.* -FROM user_group -JOIN local_group ON user_group.id = local_group.group_id -ORDER BY %s -LIMIT %d, %d -GROUP_QUERY_END; - - $cnt = 0; - $group->query(sprintf($sql, $order, $limit, $offset)); - $group->find(); + $group->orderBy($order); + $group->limit($offset, $limit); } else { // User is browsing via AlphaNav @@ -371,6 +366,8 @@ GROUP_QUERY_END; $group->query($sql); } + $group->find(); + return $group; } From 52e0ce8b064735a44a8d0297cc5ced01e7c3db0b Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 18 Feb 2015 11:13:10 +0100 Subject: [PATCH 09/17] Directory group search code cleanup --- plugins/Directory/actions/groupdirectory.php | 22 ++++++++------------ 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/plugins/Directory/actions/groupdirectory.php b/plugins/Directory/actions/groupdirectory.php index 496f035570..1abf4144b1 100644 --- a/plugins/Directory/actions/groupdirectory.php +++ b/plugins/Directory/actions/groupdirectory.php @@ -302,7 +302,7 @@ class GroupdirectoryAction extends Action $offset = ($this->page-1) * PROFILES_PER_PAGE; $limit = PROFILES_PER_PAGE + 1; - if (isset($this->q)) { + if (!empty($this->q)) { // Disable this to get global group searches $group->joinAdd(array('id', 'local_group:group_id')); @@ -312,19 +312,15 @@ class GroupdirectoryAction extends Action $group->whereAdd("LOWER({$group->__table}.{$where}) LIKE LOWER('%".$group->escape($this->q)."%')", 'OR'); } - $order = 'user_group.created ASC'; + $order = "{$group->__table}.created ASC"; - if ($this->sort == 'nickname') { - if ($this->reverse) { - $order = 'user_group.nickname DESC'; - } else { - $order = 'user_group.nickname ASC'; - } - } else { - if ($this->reverse) { - $order = 'user_group.created DESC'; - } - } + if ($this->sort == 'nickname') { + $order = $this->reverse + ? "{$group->__table}.nickname DESC" + : "{$group->__table}.nickname ASC"; + } elseif ($this->reverse) { + $order = "{$group->__table}.created DESC"; + } $group->orderBy($order); $group->limit($offset, $limit); From a254f38a238179a88b08149e5dc44052cf4dd399 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 18 Feb 2015 11:15:30 +0100 Subject: [PATCH 10/17] Turn GroupdirectoryAction into a ManagedAction --- plugins/Directory/actions/groupdirectory.php | 28 ++------------------ 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/plugins/Directory/actions/groupdirectory.php b/plugins/Directory/actions/groupdirectory.php index 1abf4144b1..053516b793 100644 --- a/plugins/Directory/actions/groupdirectory.php +++ b/plugins/Directory/actions/groupdirectory.php @@ -39,7 +39,7 @@ if (!defined('GNUSOCIAL')) { exit(1); } * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ -class GroupdirectoryAction extends Action +class GroupdirectoryAction extends ManagedAction { /** * The page we're on @@ -134,17 +134,8 @@ class GroupdirectoryAction extends Action return true; } - /** - * Take arguments for running - * - * @param array $args $_REQUEST args - * - * @return boolean success flag - */ - function prepare($args) + protected function doPreparation() { - parent::prepare($args); - $this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1; $this->filter = $this->arg('filter', 'all'); $this->reverse = $this->boolean('reverse'); @@ -156,21 +147,6 @@ class GroupdirectoryAction extends Action return true; } - /** - * Handle request - * - * Shows the page - * - * @param array $args $_REQUEST args; handled in prepare() - * - * @return void - */ - function handle($args) - { - parent::handle($args); - $this->showPage(); - } - /** * Show the page notice * From 94e19e1ac6bdd0332b45e95e802d9009aa122465 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 18 Feb 2015 12:15:46 +0100 Subject: [PATCH 11/17] escapedTableName for SQL query easy access --- classes/Managed_DataObject.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/classes/Managed_DataObject.php b/classes/Managed_DataObject.php index a628b8bee3..dc4352270d 100644 --- a/classes/Managed_DataObject.php +++ b/classes/Managed_DataObject.php @@ -299,6 +299,11 @@ abstract class Managed_DataObject extends Memcached_DataObject return $ckeys; } + public function escapedTableName() + { + return common_database_tablename($this->tableName()); + } + /** * Returns an ID, checked that it is set and reasonably valid * From e64ac4c418ffc0a0145fd292ddc2abd65fc79988 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 18 Feb 2015 12:17:26 +0100 Subject: [PATCH 12/17] GroupdirectoryAction now has no direct SQL queries also various fixes uppers --- plugins/Directory/actions/groupdirectory.php | 85 +++++++++----------- 1 file changed, 36 insertions(+), 49 deletions(-) diff --git a/plugins/Directory/actions/groupdirectory.php b/plugins/Directory/actions/groupdirectory.php index 053516b793..f6b20d0cf5 100644 --- a/plugins/Directory/actions/groupdirectory.php +++ b/plugins/Directory/actions/groupdirectory.php @@ -275,69 +275,59 @@ class GroupdirectoryAction extends ManagedAction { $group = new User_group(); - $offset = ($this->page-1) * PROFILES_PER_PAGE; - $limit = PROFILES_PER_PAGE + 1; + // Disable this to get global group searches + $group->joinAdd(array('id', 'local_group:group_id')); + + $order = false; if (!empty($this->q)) { - - // Disable this to get global group searches - $group->joinAdd(array('id', 'local_group:group_id')); - $wheres = array('nickname', 'fullname', 'homepage', 'description', 'location'); foreach ($wheres as $where) { - $group->whereAdd("LOWER({$group->__table}.{$where}) LIKE LOWER('%".$group->escape($this->q)."%')", 'OR'); + // Double % because of sprintf + $group->whereAdd(sprintf('LOWER(%1$s.%2$s) LIKE LOWER("%%%3$s%%")', + $group->escapedTableName(), $where, + $group->escape($this->q)), + 'OR'); } - $order = "{$group->__table}.created ASC"; - - if ($this->sort == 'nickname') { - $order = $this->reverse - ? "{$group->__table}.nickname DESC" - : "{$group->__table}.nickname ASC"; - } elseif ($this->reverse) { - $order = "{$group->__table}.created DESC"; - } - - $group->orderBy($order); - $group->limit($offset, $limit); - + $order = sprintf('%1$s.%2$s %3$s', + $group->escapedTableName(), + $this->getSortKey('created'), + $this->reverse ? 'DESC' : 'ASC'); } else { // User is browsing via AlphaNav - $sort = $this->getSortKey(); - $sql = <<< GROUP_QUERY_END -SELECT user_group.* -FROM user_group -JOIN local_group ON user_group.id = local_group.group_id -GROUP_QUERY_END; - - switch($this->filter) - { + switch($this->filter) { case 'all': // NOOP break; case '0-9': - $sql .= - ' AND LEFT(user_group.nickname, 1) BETWEEN \'0\' AND \'9\''; + $group->whereAdd(sprintf('LEFT(%1$s.%2$s, 1) BETWEEN %3$s AND %4$s', + $group->escapedTableName(), + 'nickname', + $group->_quote("0"), + $group->_quote("9"))); break; default: - $sql .= sprintf( - ' AND LEFT(LOWER(user_group.nickname), 1) = \'%s\'', - $this->filter - ); + $group->whereAdd(sprintf('LEFT(LOWER(%1$s.%2$s), 1) = %3$s', + $group->escapedTableName(), + 'nickname', + $group->_quote($this->filter))); } - $sql .= sprintf( - ' ORDER BY user_group.%s %s, user_group.nickname ASC LIMIT %d, %d', - $sort, - $this->reverse ? 'DESC' : 'ASC', - $offset, - $limit - ); - - $group->query($sql); + $order = sprintf('%1$s.%2$s %3$s, %1$s.%4$s ASC', + $group->escapedTableName(), + $this->getSortKey('nickname'), + $this->reverse ? 'DESC' : 'ASC', + 'nickname'); } + $offset = ($this->page-1) * PROFILES_PER_PAGE; + $limit = PROFILES_PER_PAGE + 1; + + $group->orderBy($order); + $group->limit($offset, $limit); + $group->find(); return $group; @@ -348,17 +338,14 @@ GROUP_QUERY_END; * * @return string a column name for sorting */ - function getSortKey() + function getSortKey($def='created') { switch ($this->sort) { case 'nickname': - return $this->sort; - break; case 'created': return $this->sort; - break; default: - return 'nickname'; + return $def; } } From d445e0c8773e3ca74c3515ee8f54327a9363a24c Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 18 Feb 2015 13:18:32 +0100 Subject: [PATCH 13/17] No need for return true --- plugins/Directory/actions/groupdirectory.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/Directory/actions/groupdirectory.php b/plugins/Directory/actions/groupdirectory.php index f6b20d0cf5..32e5ce50b4 100644 --- a/plugins/Directory/actions/groupdirectory.php +++ b/plugins/Directory/actions/groupdirectory.php @@ -143,8 +143,6 @@ class GroupdirectoryAction extends ManagedAction $this->sort = $this->arg('sort', 'nickname'); common_set_returnto($this->selfUrl()); - - return true; } /** From 4ad7e8f459961bebeea06b0bb636499650ca1043 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 18 Feb 2015 13:37:06 +0100 Subject: [PATCH 14/17] UserdirectoryAction now ManagedAction and better SQL --- plugins/Directory/actions/userdirectory.php | 83 +++++++-------------- 1 file changed, 26 insertions(+), 57 deletions(-) diff --git a/plugins/Directory/actions/userdirectory.php b/plugins/Directory/actions/userdirectory.php index 77ffb5206b..f178408e8b 100644 --- a/plugins/Directory/actions/userdirectory.php +++ b/plugins/Directory/actions/userdirectory.php @@ -27,12 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET')) -{ - exit(1); -} - -require_once INSTALLDIR . '/lib/publicgroupnav.php'; +if (!defined('GNUSOCIAL')) { exit(1); } /** * User directory @@ -43,7 +38,7 @@ require_once INSTALLDIR . '/lib/publicgroupnav.php'; * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ -class UserdirectoryAction extends Action +class UserdirectoryAction extends ManagedAction { /** * The page we're on @@ -137,17 +132,8 @@ class UserdirectoryAction extends Action return true; } - /** - * Take arguments for running - * - * @param array $args $_REQUEST args - * - * @return boolean success flag - */ - function prepare($args) + protected function doPreparation() { - parent::prepare($args); - $this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1; $this->filter = $this->arg('filter', 'all'); $this->reverse = $this->boolean('reverse'); @@ -155,23 +141,6 @@ class UserdirectoryAction extends Action $this->sort = $this->arg('sort', 'nickname'); common_set_returnto($this->selfUrl()); - - return true; - } - - /** - * Handle request - * - * Shows the page - * - * @param array $args $_REQUEST args; handled in prepare() - * - * @return void - */ - function handle($args) - { - parent::handle($args); - $this->showPage(); } /** @@ -291,10 +260,13 @@ class UserdirectoryAction extends Action { $profile = new Profile(); + // Comment this out or disable to get global profile searches + $profile->joinAdd(array('id', 'user:id')); + $offset = ($this->page - 1) * PROFILES_PER_PAGE; $limit = PROFILES_PER_PAGE + 1; - if (isset($this->q)) { + if (!empty($this->q)) { // User is searching via query $search_engine = $profile->getSearchEngine('profile'); @@ -319,34 +291,34 @@ class UserdirectoryAction extends Action $profile->find(); } else { // User is browsing via AlphaNav - $sort = $this->getSortKey(); - $sql = 'SELECT profile.* FROM profile, user WHERE profile.id = user.id'; - switch($this->filter) - { + switch ($this->filter) { case 'all': // NOOP break; case '0-9': - $sql .= - ' AND LEFT(profile.nickname, 1) BETWEEN \'0\' AND \'9\''; + $profile->whereAdd(sprintf('LEFT(%1$s.%2$s, 1) BETWEEN %3$s AND %4$s', + $profile->escapedTableName(), + 'nickname', + $profile->_quote("0"), + $profile->_quote("9"))); break; default: - $sql .= sprintf( - ' AND LEFT(LOWER(profile.nickname), 1) = \'%s\'', - $this->filter - ); + $profile->whereAdd(sprintf('LEFT(LOWER(%1$s.%2$s), 1) = %3$s', + $profile->escapedTableName(), + 'nickname', + $profile->_quote($this->filter))); } - $sql .= sprintf( - ' ORDER BY profile.%s %s, profile.nickname ASC LIMIT %d, %d', - $sort, - $this->reverse ? 'DESC' : 'ASC', - $offset, - $limit - ); + $order = sprintf('%1$s.%2$s %3$s, %1$s.%4$s ASC', + $profile->escapedTableName(), + $this->getSortKey('nickname'), + $this->reverse ? 'DESC' : 'ASC', + 'nickname'); + $profile->orderBy($order); + $profile->limit($offset, $limit); - $profile->query($sql); + $profile->find(); } return $profile; @@ -357,15 +329,12 @@ class UserdirectoryAction extends Action * * @return string a column name for sorting */ - function getSortKey() + function getSortKey($def='nickname') { switch ($this->sort) { case 'nickname': - return $this->sort; - break; case 'created': return $this->sort; - break; default: return 'nickname'; } From 0acf3e0e30dbe9b127f5e307619e7ee72eb4e9cc Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 18 Feb 2015 14:01:35 +0100 Subject: [PATCH 15/17] Profile table cleaning script. --- scripts/clean_profiles.php | 59 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 scripts/clean_profiles.php diff --git a/scripts/clean_profiles.php b/scripts/clean_profiles.php new file mode 100755 index 0000000000..470d6c1b91 --- /dev/null +++ b/scripts/clean_profiles.php @@ -0,0 +1,59 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'y'; +$longoptions = array('yes'); + +$helptext = <<query('SELECT * FROM profile WHERE ' . + 'NOT (SELECT COUNT(*) FROM notice WHERE profile_id=profile.id) ' . + 'AND NOT (SELECT COUNT(*) FROM user WHERE user.id=profile.id) ' . + 'AND NOT (SELECT COUNT(*) FROM user_group WHERE user_group.profile_id=profile.id) ' . + 'AND NOT (SELECT COUNT(*) FROM subscription WHERE subscriber=profile.id OR subscribed=profile.id) '); +while ($profile->fetch()) { + echo ' '.$profile->getID().':'.$profile->getNickname(); + $profile->delete(); +} +print "\nDONE.\n"; From 042cb1604a45fbce0878c113bfc997d899e276d0 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 19 Feb 2015 17:42:47 +0100 Subject: [PATCH 16/17] ROLLBACK if query failed in email settings savePreferences --- actions/emailsettings.php | 1 + 1 file changed, 1 insertion(+) diff --git a/actions/emailsettings.php b/actions/emailsettings.php index 0c2033d821..96ada2c826 100644 --- a/actions/emailsettings.php +++ b/actions/emailsettings.php @@ -340,6 +340,7 @@ class EmailsettingsAction extends SettingsAction if ($result === false) { common_log_db_error($user, 'UPDATE', __FILE__); + $user->query('ROLLBACK'); // TRANS: Server error thrown on database error updating e-mail preferences. $this->serverError(_('Could not update user.')); } From df2cc09362404237af52f8b992ed48bd7e926de0 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 19 Feb 2015 17:46:37 +0100 Subject: [PATCH 17/17] $this->scoped->getUser() instead of common_current_user(); --- actions/emailsettings.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/actions/emailsettings.php b/actions/emailsettings.php index 96ada2c826..47c6fe54e5 100644 --- a/actions/emailsettings.php +++ b/actions/emailsettings.php @@ -313,7 +313,7 @@ class EmailsettingsAction extends SettingsAction */ function savePreferences() { - $user = common_current_user(); + $user = $this->scoped->getUser(); if (Event::handle('StartEmailSaveForm', array($this, $this->scoped))) { $emailnotifysub = $this->boolean('emailnotifysub'); @@ -323,8 +323,6 @@ class EmailsettingsAction extends SettingsAction $emailmicroid = $this->boolean('emailmicroid'); $emailpost = $this->boolean('emailpost'); - assert(!is_null($user)); // should already be checked - $user->query('BEGIN'); $original = clone($user);