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";