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. 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/actions/emailsettings.php b/actions/emailsettings.php index 0c2033d821..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); @@ -340,6 +338,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.')); } 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 * 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/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/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) { diff --git a/lib/util.php b/lib/util.php index a32c35395e..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; } /** @@ -870,12 +890,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})))\]?(?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/Directory/actions/groupdirectory.php b/plugins/Directory/actions/groupdirectory.php index 5e532fc214..32e5ce50b4 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,10 +35,11 @@ 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/ */ -class GroupdirectoryAction extends Action +class GroupdirectoryAction extends ManagedAction { /** * The page we're on @@ -138,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,23 +143,6 @@ class GroupdirectoryAction 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(); } /** @@ -303,74 +273,61 @@ class GroupdirectoryAction extends Action { $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')); - if (isset($this->q)) { + $order = false; - $order = 'user_group.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'; - } - } - - $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(); + if (!empty($this->q)) { + $wheres = array('nickname', 'fullname', 'homepage', 'description', 'location'); + foreach ($wheres as $where) { + // 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 = 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; } @@ -379,17 +336,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; } } 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'; } 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 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 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";