Merge branch '1.0.x' into schema-x

This commit is contained in:
Brion Vibber 2010-10-18 15:26:20 -07:00
commit f1bfbece06
18 changed files with 492 additions and 198 deletions

32
README
View File

@ -683,8 +683,9 @@ instructions; read to the end first before trying them.
6. Move your StatusNet directory to a backup spot, like "statusnet.bak". 6. Move your StatusNet directory to a backup spot, like "statusnet.bak".
7. Unpack your StatusNet 0.9.5 tarball and move it to "statusnet" or 7. Unpack your StatusNet 0.9.5 tarball and move it to "statusnet" or
wherever your code used to be. wherever your code used to be.
8. Copy the config.php file and avatar directory from your old 8. Copy the config.php file and the contents of the avatar/, background/,
directory to your new directory. file/, and local/ subdirectories from your old directory to your new
directory.
9. Copy htaccess.sample to .htaccess in the new directory. Change the 9. Copy htaccess.sample to .htaccess in the new directory. Change the
RewriteBase to use the correct path. RewriteBase to use the correct path.
10. Rebuild the database. (You can safely skip this step and go to #12 10. Rebuild the database. (You can safely skip this step and go to #12
@ -1499,6 +1500,33 @@ disallow: Array of (virtual) directories to disallow. Default is 'main',
'search', 'message', 'settings', 'admin'. Ignored when site 'search', 'message', 'settings', 'admin'. Ignored when site
is private, in which case the entire site ('/') is disallowed. is private, in which case the entire site ('/') is disallowed.
api
---
Options for the Twitter-like API.
realm: HTTP Basic Auth realm (see http://tools.ietf.org/html/rfc2617
for details). Some third-party tools like ping.fm want this to be
'Identi.ca API', so set it to that if you want to. default = null,
meaning 'something based on the site name'.
nofollow
--------
We optionally put 'rel="nofollow"' on some links in some pages. The
following configuration settings let you fine-tune how or when things
are nofollowed. See http://en.wikipedia.org/wiki/Nofollow for more
information on what 'nofollow' means.
subscribers: whether to nofollow links to subscribers on the profile
and personal pages. Default is true.
members: links to members on the group page. Default true.
peopletag: links to people listed in the peopletag page. Default true.
external: external links in notices. One of three values: 'sometimes',
'always', 'never'. If 'sometimes', then external links are not
nofollowed on profile, notice, and favorites page. Default is
'sometimes'.
url url
--- ---

View File

@ -74,6 +74,7 @@ class ApiDirectMessageAction extends ApiAuthAction
$this->user = $this->auth_user; $this->user = $this->auth_user;
if (empty($this->user)) { if (empty($this->user)) {
// TRANS: Client error given when a user was not found (404).
$this->clientError(_('No such user.'), 404, $this->format); $this->clientError(_('No such user.'), 404, $this->format);
return; return;
} }
@ -86,10 +87,12 @@ class ApiDirectMessageAction extends ApiAuthAction
// Action was called by /api/direct_messages/sent.format // Action was called by /api/direct_messages/sent.format
$this->title = sprintf( $this->title = sprintf(
// TRANS: %s is a user nickname.
_("Direct messages from %s"), _("Direct messages from %s"),
$this->user->nickname $this->user->nickname
); );
$this->subtitle = sprintf( $this->subtitle = sprintf(
// TRANS: %s is a user nickname.
_("All the direct messages sent from %s"), _("All the direct messages sent from %s"),
$this->user->nickname $this->user->nickname
); );
@ -98,10 +101,12 @@ class ApiDirectMessageAction extends ApiAuthAction
$this->id = "tag:$taguribase:SentDirectMessages:" . $this->user->id; $this->id = "tag:$taguribase:SentDirectMessages:" . $this->user->id;
} else { } else {
$this->title = sprintf( $this->title = sprintf(
// TRANS: %s is a user nickname.
_("Direct messages to %s"), _("Direct messages to %s"),
$this->user->nickname $this->user->nickname
); );
$this->subtitle = sprintf( $this->subtitle = sprintf(
// TRANS: %s is a user nickname.
_("All the direct messages sent to %s"), _("All the direct messages sent to %s"),
$this->user->nickname $this->user->nickname
); );
@ -153,6 +158,7 @@ class ApiDirectMessageAction extends ApiAuthAction
$this->showJsonDirectMessages(); $this->showJsonDirectMessages();
break; break;
default: default:
// TRANS: Client error given when an API method was not found (404).
$this->clientError(_('API method not found.'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);
break; break;
} }

View File

@ -49,7 +49,6 @@ require_once INSTALLDIR . '/lib/apiauth.php';
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
class ApiDirectMessageNewAction extends ApiAuthAction class ApiDirectMessageNewAction extends ApiAuthAction
{ {
var $other = null; var $other = null;
@ -63,7 +62,6 @@ class ApiDirectMessageNewAction extends ApiAuthAction
* @return boolean success flag * @return boolean success flag
* *
*/ */
function prepare($args) function prepare($args)
{ {
parent::prepare($args); parent::prepare($args);
@ -99,7 +97,6 @@ class ApiDirectMessageNewAction extends ApiAuthAction
* *
* @return void * @return void
*/ */
function handle($args) function handle($args)
{ {
parent::handle($args); parent::handle($args);
@ -116,6 +113,7 @@ class ApiDirectMessageNewAction extends ApiAuthAction
if (empty($this->content)) { if (empty($this->content)) {
$this->clientError( $this->clientError(
// TRANS: Client error (406).
_('No message text!'), _('No message text!'),
406, 406,
$this->format $this->format
@ -123,9 +121,10 @@ class ApiDirectMessageNewAction extends ApiAuthAction
} else { } else {
$content_shortened = common_shorten_links($this->content); $content_shortened = common_shorten_links($this->content);
if (Message::contentTooLong($content_shortened)) { if (Message::contentTooLong($content_shortened)) {
// TRANS: Client error displayed when message content is too long.
// TRANS: %d is the maximum number of characters for a message.
$this->clientError( $this->clientError(
sprintf( sprintf(_m('That\'s too long. Maximum message size is %d character.', 'That\'s too long. Maximum message size is %d characters.', Message::maxContent()),
_('That\'s too long. Max message size is %d chars.'),
Message::maxContent() Message::maxContent()
), ),
406, 406,
@ -136,10 +135,12 @@ class ApiDirectMessageNewAction extends ApiAuthAction
} }
if (empty($this->other)) { if (empty($this->other)) {
// TRANS: Client error displayed if a recipient user could not be found (403).
$this->clientError(_('Recipient user not found.'), 403, $this->format); $this->clientError(_('Recipient user not found.'), 403, $this->format);
return; return;
} else if (!$this->user->mutuallySubscribed($this->other)) { } else if (!$this->user->mutuallySubscribed($this->other)) {
$this->clientError( $this->clientError(
// TRANS: Client error displayed trying to direct message another user who's not a friend (403).
_('Can\'t send direct messages to users who aren\'t your friend.'), _('Can\'t send direct messages to users who aren\'t your friend.'),
403, 403,
$this->format $this->format
@ -149,10 +150,9 @@ class ApiDirectMessageNewAction extends ApiAuthAction
// Note: sending msgs to yourself is allowed by Twitter // Note: sending msgs to yourself is allowed by Twitter
$errmsg = 'Don\'t send a message to yourself; ' . // TRANS: Client error displayed trying to direct message self (403).
'just say it to yourself quietly instead.'; $this->clientError(_('Do not send a message to yourself; ' .
'just say it to yourself quietly instead.'), 403, $this->format);
$this->clientError(_($errmsg), 403, $this->format);
return; return;
} }
@ -176,6 +176,4 @@ class ApiDirectMessageNewAction extends ApiAuthAction
$this->showSingleJsondirectMessage($message); $this->showSingleJsondirectMessage($message);
} }
} }
} }

View File

@ -93,9 +93,10 @@ class PathsadminpanelAction extends AdminPanelAction
{ {
static $settings = array( static $settings = array(
'site' => array('path', 'locale_path', 'ssl', 'sslserver'), 'site' => array('path', 'locale_path', 'ssl', 'sslserver'),
'theme' => array('server', 'dir', 'path'), 'theme' => array('server', 'dir', 'path', 'sslserver', 'sslpath'),
'avatar' => array('server', 'dir', 'path'), 'avatar' => array('server', 'dir', 'path'),
'background' => array('server', 'dir', 'path') 'background' => array('server', 'dir', 'path', 'sslserver', 'sslpath'),
'attachments' => array('server', 'dir', 'path', 'sslserver', 'sslpath')
); );
// XXX: If we're only going to have one boolean on thi page we // XXX: If we're only going to have one boolean on thi page we
@ -243,7 +244,7 @@ class PathsAdminPanelForm extends AdminForm
$this->unli(); $this->unli();
$this->li(); $this->li();
$this->input('locale_path', _('Path to locales'), _('Directory path to locales'), 'site'); $this->input('locale_path', _('Locale Directory'), _('Directory path to locales'), 'site');
$this->unli(); $this->unli();
$this->li(); $this->li();
@ -261,15 +262,23 @@ class PathsAdminPanelForm extends AdminForm
$this->out->elementStart('ul', 'form_data'); $this->out->elementStart('ul', 'form_data');
$this->li(); $this->li();
$this->input('server', _('Theme server'), 'Server for themes', 'theme'); $this->input('server', _('Server'), _('Server for themes'), 'theme');
$this->unli(); $this->unli();
$this->li(); $this->li();
$this->input('path', _('Theme path'), 'Web path to themes', 'theme'); $this->input('path', _('Path'), _('Web path to themes'), 'theme');
$this->unli(); $this->unli();
$this->li(); $this->li();
$this->input('dir', _('Theme directory'), 'Directory where themes are located', 'theme'); $this->input('sslserver', _('SSL server'), _('SSL server for themes (default: SSL server)'), 'theme');
$this->unli();
$this->li();
$this->input('sslpath', _('SSL path'), _('SSL path to themes (default: /theme/)'), 'theme');
$this->unli();
$this->li();
$this->input('dir', _('Directory'), _('Directory where themes are located'), 'theme');
$this->unli(); $this->unli();
$this->out->elementEnd('ul'); $this->out->elementEnd('ul');
@ -302,15 +311,52 @@ class PathsAdminPanelForm extends AdminForm
$this->out->elementStart('ul', 'form_data'); $this->out->elementStart('ul', 'form_data');
$this->li(); $this->li();
$this->input('server', _('Background server'), 'Server for backgrounds', 'background'); $this->input('server', _('Server'), 'Server for backgrounds', 'background');
$this->unli(); $this->unli();
$this->li(); $this->li();
$this->input('path', _('Background path'), 'Web path to backgrounds', 'background'); $this->input('path', _('Path'), 'Web path to backgrounds', 'background');
$this->unli(); $this->unli();
$this->li(); $this->li();
$this->input('dir', _('Background directory'), 'Directory where backgrounds are located', 'background'); $this->input('sslserver', _('SSL server'), 'Server for backgrounds on SSL pages', 'background');
$this->unli();
$this->li();
$this->input('sslpath', _('SSL path'), 'Web path to backgrounds on SSL pages', 'background');
$this->unli();
$this->li();
$this->input('dir', _('Directory'), 'Directory where backgrounds are located', 'background');
$this->unli();
$this->out->elementEnd('ul');
$this->out->elementEnd('fieldset');
$this->out->elementStart('fieldset', array('id' =>
'settings_design_attachments-paths'));
$this->out->element('legend', null, _('Attachments'));
$this->out->elementStart('ul', 'form_data');
$this->li();
$this->input('server', _('Server'), 'Server for attachments', 'attachments');
$this->unli();
$this->li();
$this->input('path', _('Path'), 'Web path to attachments', 'attachments');
$this->unli();
$this->li();
$this->input('sslserver', _('SSL server'), 'Server for attachments on SSL pages', 'attachments');
$this->unli();
$this->li();
$this->input('sslpath', _('SSL path'), 'Web path to attachments on SSL pages', 'attachments');
$this->unli();
$this->li();
$this->input('dir', _('Directory'), 'Directory where attachments are located', 'attachments');
$this->unli(); $this->unli();
$this->out->elementEnd('ul'); $this->out->elementEnd('ul');
@ -320,12 +366,11 @@ class PathsAdminPanelForm extends AdminForm
$this->out->element('legend', null, _('SSL')); $this->out->element('legend', null, _('SSL'));
$this->out->elementStart('ul', 'form_data'); $this->out->elementStart('ul', 'form_data');
$this->li(); $this->li();
$ssl = array('never' => _('Never'), $ssl = array('never' => _('Never'),
'sometimes' => _('Sometimes'), 'sometimes' => _('Sometimes'),
'always' => _('Always')); 'always' => _('Always'));
common_debug("site ssl = " . $this->value('site', 'ssl'));
$this->out->dropdown('site-ssl', _('Use SSL'), $this->out->dropdown('site-ssl', _('Use SSL'),
$ssl, _('When to use SSL'), $ssl, _('When to use SSL'),
false, $this->value('ssl', 'site')); false, $this->value('ssl', 'site'));
@ -370,5 +415,4 @@ class PathsAdminPanelForm extends AdminForm
{ {
$this->out->input("$section-$setting", $title, $this->value($setting, $section), $instructions); $this->out->input("$section-$setting", $title, $this->value($setting, $section), $instructions);
} }
} }

View File

@ -227,7 +227,7 @@ class ShowfavoritesAction extends OwnerDesignAction
function showContent() function showContent()
{ {
$nl = new NoticeList($this->notice, $this); $nl = new FavoritesNoticeList($this->notice, $this);
$cnt = $nl->show(); $cnt = $nl->show();
if (0 == $cnt) { if (0 == $cnt) {
@ -244,3 +244,15 @@ class ShowfavoritesAction extends OwnerDesignAction
} }
} }
class FavoritesNoticeList extends NoticeList
{
function newListItem($notice)
{
return new FavoritesNoticeListItem($notice, $this->out);
}
}
// All handled by superclass
class FavoritesNoticeListItem extends DoFollowListItem
{
}

View File

@ -305,7 +305,7 @@ class ShownoticeAction extends OwnerDesignAction
} }
} }
class SingleNoticeItem extends NoticeListItem class SingleNoticeItem extends DoFollowListItem
{ {
/** /**
* recipe function for displaying a single notice. * recipe function for displaying a single notice.

View File

@ -269,7 +269,7 @@ class ProfileNoticeList extends NoticeList
} }
} }
class ProfileNoticeListItem extends NoticeListItem class ProfileNoticeListItem extends DoFollowListItem
{ {
function showAuthor() function showAuthor()
{ {

View File

@ -45,13 +45,61 @@ require INSTALLDIR . '/lib/installer.php';
* Helper class for building form * Helper class for building form
*/ */
class Posted { class Posted {
/**
* HTML-friendly escaped string for the POST param of given name, or empty.
* @param string $name
* @return string
*/
function value($name) function value($name)
{ {
if (isset($_POST[$name])) { return htmlspecialchars($this->string($name));
return htmlspecialchars(strval($_POST[$name]));
} else {
return '';
} }
/**
* The given POST parameter value, forced to a string.
* Missing value will give ''.
*
* @param string $name
* @return string
*/
function string($name)
{
return strval($this->raw($name));
}
/**
* The given POST parameter value, in its original form.
* Magic quotes are stripped, if provided.
* Missing value will give null.
*
* @param string $name
* @return mixed
*/
function raw($name)
{
if (isset($_POST[$name])) {
return $this->dequote($_POST[$name]);
} else {
return null;
}
}
/**
* If necessary, strip magic quotes from the given value.
*
* @param mixed $val
* @return mixed
*/
function dequote($val)
{
if (get_magic_quotes_gpc()) {
if (is_string($val)) {
return stripslashes($val);
} else if (is_array($val)) {
return array_map(array($this, 'dequote'), $val);
}
}
return $val;
} }
} }
@ -107,11 +155,7 @@ class WebInstaller extends Installer
global $dbModules; global $dbModules;
$post = new Posted(); $post = new Posted();
$dbRadios = ''; $dbRadios = '';
if (isset($_POST['dbtype'])) { $dbtype = $post->raw('dbtype');
$dbtype = $_POST['dbtype'];
} else {
$dbtype = null;
}
foreach (self::$dbModules as $type => $info) { foreach (self::$dbModules as $type => $info) {
if ($this->checkExtension($info['check_module'])) { if ($this->checkExtension($info['check_module'])) {
if ($dbtype == null || $dbtype == $type) { if ($dbtype == null || $dbtype == $type) {
@ -245,19 +289,20 @@ STR;
*/ */
function prepare() function prepare()
{ {
$this->host = $_POST['host']; $post = new Posted();
$this->dbtype = $_POST['dbtype']; $this->host = $post->string('host');
$this->database = $_POST['database']; $this->dbtype = $post->string('dbtype');
$this->username = $_POST['dbusername']; $this->database = $post->string('database');
$this->password = $_POST['dbpassword']; $this->username = $post->string('dbusername');
$this->sitename = $_POST['sitename']; $this->password = $post->string('dbpassword');
$this->fancy = !empty($_POST['fancy']); $this->sitename = $post->string('sitename');
$this->fancy = (bool)$post->string('fancy');
$this->adminNick = strtolower($_POST['admin_nickname']); $this->adminNick = strtolower($post->string('admin_nickname'));
$this->adminPass = $_POST['admin_password']; $this->adminPass = $post->string('admin_password');
$adminPass2 = $_POST['admin_password2']; $adminPass2 = $post->string('admin_password2');
$this->adminEmail = $_POST['admin_email']; $this->adminEmail = $post->string('admin_email');
$this->adminUpdates = $_POST['admin_updates']; $this->adminUpdates = $post->string('admin_updates');
$this->server = $_SERVER['HTTP_HOST']; $this->server = $_SERVER['HTTP_HOST'];
$this->path = substr(dirname($_SERVER['PHP_SELF']), 1); $this->path = substr(dirname($_SERVER['PHP_SELF']), 1);

View File

@ -319,7 +319,8 @@ $default =
'nofollow' => 'nofollow' =>
array('subscribers' => true, array('subscribers' => true,
'members' => true, 'members' => true,
'peopletag' => true), 'peopletag' => true,
'external' => 'sometimes'), // Options: 'sometimes', 'never', default = 'sometimes'
'url' => 'url' =>
array('shortener' => 'ur1.ca', array('shortener' => 'ur1.ca',
'maxlength' => 25, 'maxlength' => 25,

88
lib/dofollowlistitem.php Normal file
View File

@ -0,0 +1,88 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* widget for displaying a list of notices
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category UI
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
exit(1);
}
require_once INSTALLDIR.'/lib/noticelist.php';
/**
* StatusNet, the distributed open-source microblogging tool
*
* Widget superclass for notice list items that remove rel=nofollow
*
* When nofollow|external = 'sometimes', notices get rendered and saved
* with rel=nofollow for external links. We want to remove that relationship
* on some pages (profile, single notice, faves). This superclass for
* some noticelistitems will strip that bit of code out when showing
* notice content
*
* @category UI
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
* @link http://status.net/
*/
class DoFollowListItem extends NoticeListItem
{
/**
* show the content of the notice
*
* Trims out the rel=nofollow for external links
* if nofollow|external = 'sometimes'
*
* @return void
*/
function showContent()
{
// FIXME: URL, image, video, audio
$this->out->elementStart('p', array('class' => 'entry-content'));
if (!empty($this->notice->rendered)) {
$html = $this->notice->rendered;
} else {
$html = common_render_content($this->notice->content, $this->notice);
}
if (common_config('nofollow', 'external') == 'sometimes') {
// remove the nofollow part
// XXX: cache the results here
$html = preg_replace('/rel="(.*)nofollow ?/', 'rel="\1', $html);
}
$this->out->raw($html);
$this->out->elementEnd('p');
}
}

View File

@ -355,6 +355,30 @@ abstract class Installer
return $schema; return $schema;
} }
/**
* Return a parseable PHP literal for the given value.
* This will include quotes for strings, etc.
*
* @param mixed $val
* @return string
*/
function phpVal($val)
{
return var_export($val, true);
}
/**
* Return an array of parseable PHP literal for the given values.
* These will include quotes for strings, etc.
*
* @param mixed $val
* @return array
*/
function phpVals($map)
{
return array_map(array($this, 'phpVal'), $map);
}
/** /**
* Write a stock configuration file. * Write a stock configuration file.
* *
@ -364,24 +388,32 @@ abstract class Installer
*/ */
function writeConf() function writeConf()
{ {
$vals = $this->phpVals(array(
'sitename' => $this->sitename,
'server' => $this->server,
'path' => $this->path,
'db_database' => $this->db['database'],
'db_type' => $this->db['type'],
));
// assemble configuration file in a string // assemble configuration file in a string
$cfg = "<?php\n". $cfg = "<?php\n".
"if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n". "if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n".
// site name // site name
"\$config['site']['name'] = '{$this->sitename}';\n\n". "\$config['site']['name'] = {$vals['sitename']};\n\n".
// site location // site location
"\$config['site']['server'] = '{$this->server}';\n". "\$config['site']['server'] = {$vals['server']};\n".
"\$config['site']['path'] = '{$this->path}'; \n\n". "\$config['site']['path'] = {$vals['path']}; \n\n".
// checks if fancy URLs are enabled // checks if fancy URLs are enabled
($this->fancy ? "\$config['site']['fancy'] = true;\n\n":''). ($this->fancy ? "\$config['site']['fancy'] = true;\n\n":'').
// database // database
"\$config['db']['database'] = '{$this->db['database']}';\n\n". "\$config['db']['database'] = {$vals['db_database']};\n\n".
($this->db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":''). ($this->db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
"\$config['db']['type'] = '{$this->db['type']}';\n\n"; "\$config['db']['type'] = {$vals['db_type']};\n\n";
// Normalize line endings for Windows servers // Normalize line endings for Windows servers
$cfg = str_replace("\n", PHP_EOL, $cfg); $cfg = str_replace("\n", PHP_EOL, $cfg);

View File

@ -875,7 +875,8 @@ function common_linkify($url) {
$longurl = $url; $longurl = $url;
} }
} }
$attrs = array('href' => $canon, 'title' => $longurl, 'rel' => 'external');
$attrs = array('href' => $canon, 'title' => $longurl);
$is_attachment = false; $is_attachment = false;
$attachment_id = null; $attachment_id = null;
@ -911,6 +912,16 @@ function common_linkify($url) {
$attrs['id'] = "attachment-{$attachment_id}"; $attrs['id'] = "attachment-{$attachment_id}";
} }
// Whether to nofollow
$nf = common_config('nofollow', 'external');
if ($nf == 'never') {
$attrs['rel'] = 'external';
} else {
$attrs['rel'] = 'nofollow external';
}
return XMLStringer::estring('a', $attrs, $url); return XMLStringer::estring('a', $attrs, $url);
} }

View File

@ -81,6 +81,9 @@ class DirectionDetectorPlugin extends Plugin {
* @return boolean * @return boolean
*/ */
public static function startsWithRTLCharacter($str){ public static function startsWithRTLCharacter($str){
if (strlen($str) < 1) {
return false;
}
if( is_array($cc = self::utf8ToUnicode(mb_substr($str, 0, 1, 'utf-8'))) ) if( is_array($cc = self::utf8ToUnicode(mb_substr($str, 0, 1, 'utf-8'))) )
$cc = $cc[0]; $cc = $cc[0];
else else

View File

@ -75,9 +75,10 @@ END;
'<body><pre><LINK rel=alternate hRef=http://example.com/feed/rss type=application/rss+xml><fnork', '<body><pre><LINK rel=alternate hRef=http://example.com/feed/rss type=application/rss+xml><fnork',
'http://example.com/feed/rss'), 'http://example.com/feed/rss'),
// 'rel' attribute must be lowercase, alone per http://www.rssboard.org/rss-autodiscovery // 'rel' attribute must be lowercase, alone per http://www.rssboard.org/rss-autodiscovery
// but we're going to be liberal in what we receive.
array('http://example.com/tagsoup2', array('http://example.com/tagsoup2',
'<body><pre><LINK rel=" feeders alternate 467" hRef=http://example.com/feed/rss type=application/rss+xml><fnork', '<body><pre><LINK rel=" feeders alternate 467" hRef=http://example.com/feed/rss type=application/rss+xml><fnork',
false), 'http://example.com/feed/rss'),
array('http://example.com/tagsoup3', array('http://example.com/tagsoup3',
'<body><pre><LINK rel=ALTERNATE hRef=http://example.com/feed/rss type=application/rss+xml><fnork', '<body><pre><LINK rel=ALTERNATE hRef=http://example.com/feed/rss type=application/rss+xml><fnork',
false), false),
@ -87,18 +88,20 @@ END;
array('http://example.com/relative/link2', array('http://example.com/relative/link2',
'<html><link rel="alternate" href="../feed/rss" type="application/rss+xml">', '<html><link rel="alternate" href="../feed/rss" type="application/rss+xml">',
'http://example.com/feed/rss'), 'http://example.com/feed/rss'),
// This one can't resolve correctly; relative link is bogus.
array('http://example.com/relative/link3', array('http://example.com/relative/link3',
'<html><link rel="alternate" href="http:/feed/rss" type="application/rss+xml">', '<html><link rel="alternate" href="http:/feed/rss" type="application/rss+xml">',
'http://example.com/feed/rss'), 'http:/feed/rss'),
array('http://example.com/base/link1', array('http://example.com/base/link1',
'<html><link rel="alternate" href="/feed/rss" type="application/rss+xml"><base href="http://target.example.com/">', '<html><link rel="alternate" href="/feed/rss" type="application/rss+xml"><base href="http://target.example.com/">',
'http://target.example.com/feed/rss'), 'http://target.example.com/feed/rss'),
array('http://example.com/base/link2', array('http://example.com/base/link2',
'<html><link rel="alternate" href="feed/rss" type="application/rss+xml"><base href="http://target.example.com/">', '<html><link rel="alternate" href="feed/rss" type="application/rss+xml"><base href="http://target.example.com/">',
'http://target.example.com/feed/rss'), 'http://target.example.com/feed/rss'),
// This one can't resolve; relative link is bogus.
array('http://example.com/base/link3', array('http://example.com/base/link3',
'<html><link rel="alternate" href="http:/feed/rss" type="application/rss+xml"><base href="http://target.example.com/">', '<html><link rel="alternate" href="http:/feed/rss" type="application/rss+xml"><base href="http://target.example.com/">',
'http://target.example.com/feed/rss'), 'http:/feed/rss'),
// Trick question! There's a <base> but no href on it // Trick question! There's a <base> but no href on it
array('http://example.com/relative/fauxbase', array('http://example.com/relative/fauxbase',
'<html><link rel="alternate" href="../feed/rss" type="application/rss+xml"><base target="top">', '<html><link rel="alternate" href="../feed/rss" type="application/rss+xml"><base target="top">',

View File

@ -4,7 +4,7 @@ if (php_sapi_name() != 'cli') {
die('not for web'); die('not for web');
} }
define('TIMEOUT', 60); // ssslllloowwwww salmon if queues are off define('HTTP_TIMEOUT', 60); // ssslllloowwwww salmon if queues are off
define('INSTALLDIR', dirname(dirname(dirname(dirname(__FILE__))))); define('INSTALLDIR', dirname(dirname(dirname(dirname(__FILE__)))));
set_include_path(INSTALLDIR . '/extlib' . PATH_SEPARATOR . get_include_path()); set_include_path(INSTALLDIR . '/extlib' . PATH_SEPARATOR . get_include_path());
@ -63,14 +63,15 @@ class OStatusTester extends TestBase
/** /**
* @param string $a base URL of test site A (eg http://localhost/mublog) * @param string $a base URL of test site A (eg http://localhost/mublog)
* @param string $b base URL of test site B (eg http://localhost/mublog2) * @param string $b base URL of test site B (eg http://localhost/mublog2)
* @param int $timeout HTTP timeout value (needs to be long if Salmon is slow)
*/ */
function __construct($a, $b) { function __construct($a, $b, $timeout=60) {
$this->a = $a; $this->a = $a;
$this->b = $b; $this->b = $b;
$base = 'test' . mt_rand(1, 1000000); $base = 'test' . mt_rand(1, 1000000);
$this->pub = new SNTestClient($this->a, 'pub' . $base, 'pw-' . mt_rand(1, 1000000)); $this->pub = new SNTestClient($this->a, 'pub' . $base, 'pw-' . mt_rand(1, 1000000), $timeout);
$this->sub = new SNTestClient($this->b, 'sub' . $base, 'pw-' . mt_rand(1, 1000000)); $this->sub = new SNTestClient($this->b, 'sub' . $base, 'pw-' . mt_rand(1, 1000000), $timeout);
} }
function run() function run()
@ -166,11 +167,12 @@ class OStatusTester extends TestBase
class SNTestClient extends TestBase class SNTestClient extends TestBase
{ {
function __construct($base, $username, $password) function __construct($base, $username, $password, $timeout=60)
{ {
$this->basepath = $base; $this->basepath = $base;
$this->username = $username; $this->username = $username;
$this->password = $password; $this->password = $password;
$this->timeout = $timeout;
$this->fullname = ucfirst($username) . ' Smith'; $this->fullname = ucfirst($username) . ' Smith';
$this->homepage = 'http://example.org/' . $username; $this->homepage = 'http://example.org/' . $username;
@ -190,7 +192,7 @@ class SNTestClient extends TestBase
{ {
$url = $this->basepath . '/' . $path; $url = $this->basepath . '/' . $path;
$http = new HTTP_Request2($url, 'POST', array('timeout' => TIMEOUT)); $http = new HTTP_Request2($url, 'POST', array('timeout' => $this->timeout));
if ($auth) { if ($auth) {
$http->setAuth($this->username, $this->password, HTTP_Request2::AUTH_BASIC); $http->setAuth($this->username, $this->password, HTTP_Request2::AUTH_BASIC);
} }
@ -217,7 +219,7 @@ class SNTestClient extends TestBase
protected function web($path, $form, $params=array()) protected function web($path, $form, $params=array())
{ {
$url = $this->basepath . '/' . $path; $url = $this->basepath . '/' . $path;
$http = new HTTP_Request2($url, 'GET', array('timeout' => TIMEOUT)); $http = new HTTP_Request2($url, 'GET', array('timeout' => $this->timeout));
$response = $http->send(); $response = $http->send();
$dom = $this->checkWeb($url, 'GET', $response); $dom = $this->checkWeb($url, 'GET', $response);
@ -534,10 +536,29 @@ class SNTestClient extends TestBase
} }
$args = array_slice($_SERVER['argv'], 1); // @fixme switch to commandline.inc?
$timeout = HTTP_TIMEOUT;
$args = array();
$options = array();
foreach (array_slice($_SERVER['argv'], 1) as $arg) {
if (substr($arg, 0, 2) == '--') {
$bits = explode('=', substr($arg, 2), 2);
if (count($bits == 2)) {
list($key, $val) = $bits;
$options[$key] = $val;
} else {
list($key) = $bits;
$options[$key] = true;
}
} else {
$args[] = $arg;
}
}
if (count($args) < 2) { if (count($args) < 2) {
print <<<END_HELP print <<<END_HELP
remote-tests.php <url1> <url2> remote-tests.php [options] <url1> <url2>
--timeout=## change HTTP timeout from default {$timeout}s
url1: base URL of a StatusNet instance url1: base URL of a StatusNet instance
url2: base URL of another StatusNet instance url2: base URL of another StatusNet instance
@ -551,6 +572,9 @@ exit(1);
$a = $args[0]; $a = $args[0];
$b = $args[1]; $b = $args[1];
if (isset($options['timeout'])) {
$timeout = intval($options['timeout']);
}
$tester = new OStatusTester($a, $b); $tester = new OStatusTester($a, $b, $timeout);
$tester->run(); $tester->run();

View File

@ -40,7 +40,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
class OpenExternalLinkTargetPlugin extends Plugin class OpenExternalLinkTargetPlugin extends Plugin
{ {
function onEndShowScripts($action) function onEndShowScripts($action)
@ -57,7 +56,7 @@ class OpenExternalLinkTargetPlugin extends Plugin
'author' => 'Sarven Capadisli', 'author' => 'Sarven Capadisli',
'homepage' => 'http://status.net/wiki/Plugin:OpenExternalLinkTarget', 'homepage' => 'http://status.net/wiki/Plugin:OpenExternalLinkTarget',
'rawdescription' => 'rawdescription' =>
_m('Opens external links (e.g., with rel=external) on a new window or tab.')); _m('Opens external links (i.e. with rel=external) on a new window or tab.'));
return true; return true;
} }
} }

View File

@ -257,7 +257,7 @@ class OpenIDAdminPanelForm extends AdminForm
$this->out->checkbox( $this->out->checkbox(
'openidonly', _m('Enable OpenID-only mode'), 'openidonly', _m('Enable OpenID-only mode'),
(bool) $this->value('openidonly', 'site'), (bool) $this->value('openidonly', 'site'),
_m('Require all users to login via OpenID. WARNING: disables password authentication for all users!'), _m('Require all users to login via OpenID. Warning: disables password authentication for all users!'),
'true' 'true'
); );
$this->unli(); $this->unli();

View File

@ -29,73 +29,73 @@ class URLDetectionTest extends PHPUnit_Framework_TestCase
array('not a link :: no way', array('not a link :: no way',
'not a link :: no way'), 'not a link :: no way'),
array('link http://www.somesite.com/xyz/35637563@N00/52803365/ link', array('link http://www.somesite.com/xyz/35637563@N00/52803365/ link',
'link <a href="http://www.somesite.com/xyz/35637563@N00/52803365/" title="http://www.somesite.com/xyz/35637563@N00/52803365/" rel="external">http://www.somesite.com/xyz/35637563@N00/52803365/</a> link'), 'link <a href="http://www.somesite.com/xyz/35637563@N00/52803365/" title="http://www.somesite.com/xyz/35637563@N00/52803365/" rel="nofollow external">http://www.somesite.com/xyz/35637563@N00/52803365/</a> link'),
array('http://127.0.0.1', array('http://127.0.0.1',
'<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'), '<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="nofollow external">http://127.0.0.1</a>'),
array('127.0.0.1', array('127.0.0.1',
'<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="external">127.0.0.1</a>'), '<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="nofollow external">127.0.0.1</a>'),
array('127.0.0.1:99', array('127.0.0.1:99',
'<a href="http://127.0.0.1:99/" title="http://127.0.0.1:99/" rel="external">127.0.0.1:99</a>'), '<a href="http://127.0.0.1:99/" title="http://127.0.0.1:99/" rel="nofollow external">127.0.0.1:99</a>'),
array('127.0.0.1/Name:test.php', array('127.0.0.1/Name:test.php',
'<a href="http://127.0.0.1/Name:test.php" title="http://127.0.0.1/Name:test.php" rel="external">127.0.0.1/Name:test.php</a>'), '<a href="http://127.0.0.1/Name:test.php" title="http://127.0.0.1/Name:test.php" rel="nofollow external">127.0.0.1/Name:test.php</a>'),
array('127.0.0.1/~test', array('127.0.0.1/~test',
'<a href="http://127.0.0.1/~test" title="http://127.0.0.1/~test" rel="external">127.0.0.1/~test</a>'), '<a href="http://127.0.0.1/~test" title="http://127.0.0.1/~test" rel="nofollow external">127.0.0.1/~test</a>'),
array('127.0.0.1/+test', array('127.0.0.1/+test',
'<a href="http://127.0.0.1/+test" title="http://127.0.0.1/+test" rel="external">127.0.0.1/+test</a>'), '<a href="http://127.0.0.1/+test" title="http://127.0.0.1/+test" rel="nofollow external">127.0.0.1/+test</a>'),
array('127.0.0.1/$test', array('127.0.0.1/$test',
'<a href="http://127.0.0.1/$test" title="http://127.0.0.1/$test" rel="external">127.0.0.1/$test</a>'), '<a href="http://127.0.0.1/$test" title="http://127.0.0.1/$test" rel="nofollow external">127.0.0.1/$test</a>'),
array('127.0.0.1/\'test', array('127.0.0.1/\'test',
'<a href="http://127.0.0.1/\'test" title="http://127.0.0.1/\'test" rel="external">127.0.0.1/\'test</a>'), '<a href="http://127.0.0.1/\'test" title="http://127.0.0.1/\'test" rel="nofollow external">127.0.0.1/\'test</a>'),
array('127.0.0.1/"test', array('127.0.0.1/"test',
'<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="external">127.0.0.1/</a>&quot;test'), '<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="nofollow external">127.0.0.1/</a>&quot;test'),
array('127.0.0.1/test"test', array('127.0.0.1/test"test',
'<a href="http://127.0.0.1/test" title="http://127.0.0.1/test" rel="external">127.0.0.1/test</a>&quot;test'), '<a href="http://127.0.0.1/test" title="http://127.0.0.1/test" rel="nofollow external">127.0.0.1/test</a>&quot;test'),
array('127.0.0.1/-test', array('127.0.0.1/-test',
'<a href="http://127.0.0.1/-test" title="http://127.0.0.1/-test" rel="external">127.0.0.1/-test</a>'), '<a href="http://127.0.0.1/-test" title="http://127.0.0.1/-test" rel="nofollow external">127.0.0.1/-test</a>'),
array('127.0.0.1/_test', array('127.0.0.1/_test',
'<a href="http://127.0.0.1/_test" title="http://127.0.0.1/_test" rel="external">127.0.0.1/_test</a>'), '<a href="http://127.0.0.1/_test" title="http://127.0.0.1/_test" rel="nofollow external">127.0.0.1/_test</a>'),
array('127.0.0.1/!test', array('127.0.0.1/!test',
'<a href="http://127.0.0.1/!test" title="http://127.0.0.1/!test" rel="external">127.0.0.1/!test</a>'), '<a href="http://127.0.0.1/!test" title="http://127.0.0.1/!test" rel="nofollow external">127.0.0.1/!test</a>'),
array('127.0.0.1/*test', array('127.0.0.1/*test',
'<a href="http://127.0.0.1/*test" title="http://127.0.0.1/*test" rel="external">127.0.0.1/*test</a>'), '<a href="http://127.0.0.1/*test" title="http://127.0.0.1/*test" rel="nofollow external">127.0.0.1/*test</a>'),
array('127.0.0.1/test%20stuff', array('127.0.0.1/test%20stuff',
'<a href="http://127.0.0.1/test%20stuff" title="http://127.0.0.1/test%20stuff" rel="external">127.0.0.1/test%20stuff</a>'), '<a href="http://127.0.0.1/test%20stuff" title="http://127.0.0.1/test%20stuff" rel="nofollow external">127.0.0.1/test%20stuff</a>'),
array('http://[::1]:99/test.php', array('http://[::1]:99/test.php',
'<a href="http://[::1]:99/test.php" title="http://[::1]:99/test.php" rel="external">http://[::1]:99/test.php</a>'), '<a href="http://[::1]:99/test.php" title="http://[::1]:99/test.php" rel="nofollow external">http://[::1]:99/test.php</a>'),
array('http://::1/test.php', array('http://::1/test.php',
'<a href="http://::1/test.php" title="http://::1/test.php" rel="external">http://::1/test.php</a>'), '<a href="http://::1/test.php" title="http://::1/test.php" rel="nofollow external">http://::1/test.php</a>'),
array('http://::1', array('http://::1',
'<a href="http://::1/" title="http://::1/" rel="external">http://::1</a>'), '<a href="http://::1/" title="http://::1/" rel="nofollow external">http://::1</a>'),
array('2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php', array('2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php',
'<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php" title="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php" rel="external">2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php</a>'), '<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php" title="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php" rel="nofollow external">2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php</a>'),
array('[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php', array('[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php',
'<a href="http://[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php" title="http://[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php" rel="external">[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php</a>'), '<a href="http://[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php" title="http://[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php" rel="nofollow external">[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php</a>'),
array('2001:4978:1b5:0:21d:e0ff:fe66:59ab', array('2001:4978:1b5:0:21d:e0ff:fe66:59ab',
'<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/" title="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/" rel="external">2001:4978:1b5:0:21d:e0ff:fe66:59ab</a>'), '<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/" title="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/" rel="nofollow external">2001:4978:1b5:0:21d:e0ff:fe66:59ab</a>'),
array('http://127.0.0.1', array('http://127.0.0.1',
'<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'), '<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="nofollow external">http://127.0.0.1</a>'),
array('example.com', array('example.com',
'<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>'),
array('example.com', array('example.com',
'<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>'),
array('http://example.com', array('http://example.com',
'<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a>'),
array('http://example.com.', array('http://example.com.',
'<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>.'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a>.'),
array('/var/lib/example.so', array('/var/lib/example.so',
'/var/lib/example.so'), '/var/lib/example.so'),
array('example', array('example',
'example'), 'example'),
array('user@example.com', array('user@example.com',
'<a href="mailto:user@example.com" title="mailto:user@example.com" rel="external">user@example.com</a>'), '<a href="mailto:user@example.com" title="mailto:user@example.com" rel="nofollow external">user@example.com</a>'),
array('user_name+other@example.com', array('user_name+other@example.com',
'<a href="mailto:user_name+other@example.com" title="mailto:user_name+other@example.com" rel="external">user_name+other@example.com</a>'), '<a href="mailto:user_name+other@example.com" title="mailto:user_name+other@example.com" rel="nofollow external">user_name+other@example.com</a>'),
array('mailto:user@example.com', array('mailto:user@example.com',
'<a href="mailto:user@example.com" title="mailto:user@example.com" rel="external">mailto:user@example.com</a>'), '<a href="mailto:user@example.com" title="mailto:user@example.com" rel="nofollow external">mailto:user@example.com</a>'),
array('mailto:user@example.com?subject=test', array('mailto:user@example.com?subject=test',
'<a href="mailto:user@example.com?subject=test" title="mailto:user@example.com?subject=test" rel="external">mailto:user@example.com?subject=test</a>'), '<a href="mailto:user@example.com?subject=test" title="mailto:user@example.com?subject=test" rel="nofollow external">mailto:user@example.com?subject=test</a>'),
array('xmpp:user@example.com', array('xmpp:user@example.com',
'<a href="xmpp:user@example.com" title="xmpp:user@example.com" rel="external">xmpp:user@example.com</a>'), '<a href="xmpp:user@example.com" title="xmpp:user@example.com" rel="nofollow external">xmpp:user@example.com</a>'),
array('#example', array('#example',
'#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('example'))) . '" rel="tag">example</a></span>'), '#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('example'))) . '" rel="tag">example</a></span>'),
array('#example.com', array('#example.com',
@ -103,167 +103,167 @@ class URLDetectionTest extends PHPUnit_Framework_TestCase
array('#.net', array('#.net',
'#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('.net'))) . '" rel="tag">.net</a></span>'), '#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('.net'))) . '" rel="tag">.net</a></span>'),
array('http://example', array('http://example',
'<a href="http://example/" title="http://example/" rel="external">http://example</a>'), '<a href="http://example/" title="http://example/" rel="nofollow external">http://example</a>'),
array('http://3xampl3', array('http://3xampl3',
'<a href="http://3xampl3/" title="http://3xampl3/" rel="external">http://3xampl3</a>'), '<a href="http://3xampl3/" title="http://3xampl3/" rel="nofollow external">http://3xampl3</a>'),
array('http://example/', array('http://example/',
'<a href="http://example/" title="http://example/" rel="external">http://example/</a>'), '<a href="http://example/" title="http://example/" rel="nofollow external">http://example/</a>'),
array('http://example/path', array('http://example/path',
'<a href="http://example/path" title="http://example/path" rel="external">http://example/path</a>'), '<a href="http://example/path" title="http://example/path" rel="nofollow external">http://example/path</a>'),
array('http://example.com', array('http://example.com',
'<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a>'),
array('https://example.com', array('https://example.com',
'<a href="https://example.com/" title="https://example.com/" rel="external">https://example.com</a>'), '<a href="https://example.com/" title="https://example.com/" rel="nofollow external">https://example.com</a>'),
array('ftp://example.com', array('ftp://example.com',
'<a href="ftp://example.com/" title="ftp://example.com/" rel="external">ftp://example.com</a>'), '<a href="ftp://example.com/" title="ftp://example.com/" rel="nofollow external">ftp://example.com</a>'),
array('ftps://example.com', array('ftps://example.com',
'<a href="ftps://example.com/" title="ftps://example.com/" rel="external">ftps://example.com</a>'), '<a href="ftps://example.com/" title="ftps://example.com/" rel="nofollow external">ftps://example.com</a>'),
array('http://user@example.com', array('http://user@example.com',
'<a href="http://user@example.com/" title="http://user@example.com/" rel="external">http://user@example.com</a>'), '<a href="http://user@example.com/" title="http://user@example.com/" rel="nofollow external">http://user@example.com</a>'),
array('http://user:pass@example.com', array('http://user:pass@example.com',
'<a href="http://user:pass@example.com/" title="http://user:pass@example.com/" rel="external">http://user:pass@example.com</a>'), '<a href="http://user:pass@example.com/" title="http://user:pass@example.com/" rel="nofollow external">http://user:pass@example.com</a>'),
array('http://example.com:8080', array('http://example.com:8080',
'<a href="http://example.com:8080/" title="http://example.com:8080/" rel="external">http://example.com:8080</a>'), '<a href="http://example.com:8080/" title="http://example.com:8080/" rel="nofollow external">http://example.com:8080</a>'),
array('http://example.com:8080/test.php', array('http://example.com:8080/test.php',
'<a href="http://example.com:8080/test.php" title="http://example.com:8080/test.php" rel="external">http://example.com:8080/test.php</a>'), '<a href="http://example.com:8080/test.php" title="http://example.com:8080/test.php" rel="nofollow external">http://example.com:8080/test.php</a>'),
array('example.com:8080/test.php', array('example.com:8080/test.php',
'<a href="http://example.com:8080/test.php" title="http://example.com:8080/test.php" rel="external">example.com:8080/test.php</a>'), '<a href="http://example.com:8080/test.php" title="http://example.com:8080/test.php" rel="nofollow external">example.com:8080/test.php</a>'),
array('http://www.example.com', array('http://www.example.com',
'<a href="http://www.example.com/" title="http://www.example.com/" rel="external">http://www.example.com</a>'), '<a href="http://www.example.com/" title="http://www.example.com/" rel="nofollow external">http://www.example.com</a>'),
array('http://example.com/', array('http://example.com/',
'<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com/</a>'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com/</a>'),
array('http://example.com/path', array('http://example.com/path',
'<a href="http://example.com/path" title="http://example.com/path" rel="external">http://example.com/path</a>'), '<a href="http://example.com/path" title="http://example.com/path" rel="nofollow external">http://example.com/path</a>'),
array('http://example.com/path.html', array('http://example.com/path.html',
'<a href="http://example.com/path.html" title="http://example.com/path.html" rel="external">http://example.com/path.html</a>'), '<a href="http://example.com/path.html" title="http://example.com/path.html" rel="nofollow external">http://example.com/path.html</a>'),
array('http://example.com/path.html#fragment', array('http://example.com/path.html#fragment',
'<a href="http://example.com/path.html#fragment" title="http://example.com/path.html#fragment" rel="external">http://example.com/path.html#fragment</a>'), '<a href="http://example.com/path.html#fragment" title="http://example.com/path.html#fragment" rel="nofollow external">http://example.com/path.html#fragment</a>'),
array('http://example.com/path.php?foo=bar&bar=foo', array('http://example.com/path.php?foo=bar&bar=foo',
'<a href="http://example.com/path.php?foo=bar&amp;bar=foo" title="http://example.com/path.php?foo=bar&amp;bar=foo" rel="external">http://example.com/path.php?foo=bar&amp;bar=foo</a>'), '<a href="http://example.com/path.php?foo=bar&amp;bar=foo" title="http://example.com/path.php?foo=bar&amp;bar=foo" rel="nofollow external">http://example.com/path.php?foo=bar&amp;bar=foo</a>'),
array('http://example.com.', array('http://example.com.',
'<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>.'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a>.'),
array('http://müllärör.de', array('http://müllärör.de',
'<a href="http://m&#xFC;ll&#xE4;r&#xF6;r.de/" title="http://m&#xFC;ll&#xE4;r&#xF6;r.de/" rel="external">http://müllärör.de</a>'), '<a href="http://m&#xFC;ll&#xE4;r&#xF6;r.de/" title="http://m&#xFC;ll&#xE4;r&#xF6;r.de/" rel="nofollow external">http://müllärör.de</a>'),
array('http://ﺱﺲﺷ.com', array('http://ﺱﺲﺷ.com',
'<a href="http://&#xFEB1;&#xFEB2;&#xFEB7;.com/" title="http://&#xFEB1;&#xFEB2;&#xFEB7;.com/" rel="external">http://ﺱﺲﺷ.com</a>'), '<a href="http://&#xFEB1;&#xFEB2;&#xFEB7;.com/" title="http://&#xFEB1;&#xFEB2;&#xFEB7;.com/" rel="nofollow external">http://ﺱﺲﺷ.com</a>'),
array('http://сделаткартинки.com', array('http://сделаткартинки.com',
'<a href="http://&#x441;&#x434;&#x435;&#x43B;&#x430;&#x442;&#x43A;&#x430;&#x440;&#x442;&#x438;&#x43D;&#x43A;&#x438;.com/" title="http://&#x441;&#x434;&#x435;&#x43B;&#x430;&#x442;&#x43A;&#x430;&#x440;&#x442;&#x438;&#x43D;&#x43A;&#x438;.com/" rel="external">http://сделаткартинки.com</a>'), '<a href="http://&#x441;&#x434;&#x435;&#x43B;&#x430;&#x442;&#x43A;&#x430;&#x440;&#x442;&#x438;&#x43D;&#x43A;&#x438;.com/" title="http://&#x441;&#x434;&#x435;&#x43B;&#x430;&#x442;&#x43A;&#x430;&#x440;&#x442;&#x438;&#x43D;&#x43A;&#x438;.com/" rel="nofollow external">http://сделаткартинки.com</a>'),
array('http://tūdaliņ.lv', array('http://tūdaliņ.lv',
'<a href="http://t&#x16B;dali&#x146;.lv/" title="http://t&#x16B;dali&#x146;.lv/" rel="external">http://tūdaliņ.lv</a>'), '<a href="http://t&#x16B;dali&#x146;.lv/" title="http://t&#x16B;dali&#x146;.lv/" rel="nofollow external">http://tūdaliņ.lv</a>'),
array('http://brændendekærlighed.com', array('http://brændendekærlighed.com',
'<a href="http://br&#xE6;ndendek&#xE6;rlighed.com/" title="http://br&#xE6;ndendek&#xE6;rlighed.com/" rel="external">http://brændendekærlighed.com</a>'), '<a href="http://br&#xE6;ndendek&#xE6;rlighed.com/" title="http://br&#xE6;ndendek&#xE6;rlighed.com/" rel="nofollow external">http://brændendekærlighed.com</a>'),
array('http://あーるいん.com', array('http://あーるいん.com',
'<a href="http://&#x3042;&#x30FC;&#x308B;&#x3044;&#x3093;.com/" title="http://&#x3042;&#x30FC;&#x308B;&#x3044;&#x3093;.com/" rel="external">http://あーるいん.com</a>'), '<a href="http://&#x3042;&#x30FC;&#x308B;&#x3044;&#x3093;.com/" title="http://&#x3042;&#x30FC;&#x308B;&#x3044;&#x3093;.com/" rel="nofollow external">http://あーるいん.com</a>'),
array('http://예비교사.com', array('http://예비교사.com',
'<a href="http://&#xC608;&#xBE44;&#xAD50;&#xC0AC;.com/" title="http://&#xC608;&#xBE44;&#xAD50;&#xC0AC;.com/" rel="external">http://예비교사.com</a>'), '<a href="http://&#xC608;&#xBE44;&#xAD50;&#xC0AC;.com/" title="http://&#xC608;&#xBE44;&#xAD50;&#xC0AC;.com/" rel="nofollow external">http://예비교사.com</a>'),
array('http://example.com.', array('http://example.com.',
'<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>.'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a>.'),
array('http://example.com?', array('http://example.com?',
'<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>?'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a>?'),
array('http://example.com!', array('http://example.com!',
'<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>!'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a>!'),
array('http://example.com,', array('http://example.com,',
'<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>,'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a>,'),
array('http://example.com;', array('http://example.com;',
'<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>;'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a>;'),
array('http://example.com:', array('http://example.com:',
'<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>:'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a>:'),
array('\'http://example.com\'', array('\'http://example.com\'',
'\'<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>\''), '\'<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a>\''),
array('"http://example.com"', array('"http://example.com"',
'&quot;<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>&quot;'), '&quot;<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a>&quot;'),
array('"http://example.com/"', array('"http://example.com/"',
'&quot;<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com/</a>&quot;'), '&quot;<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com/</a>&quot;'),
array('http://example.com', array('http://example.com',
'<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a>'),
array('(http://example.com)', array('(http://example.com)',
'(<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>)'), '(<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a>)'),
array('[http://example.com]', array('[http://example.com]',
'[<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>]'), '[<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a>]'),
array('<http://example.com>', array('<http://example.com>',
'&lt;<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>&gt;'), '&lt;<a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a>&gt;'),
array('http://example.com/path/(foo)/bar', array('http://example.com/path/(foo)/bar',
'<a href="http://example.com/path/(foo)/bar" title="http://example.com/path/(foo)/bar" rel="external">http://example.com/path/(foo)/bar</a>'), '<a href="http://example.com/path/(foo)/bar" title="http://example.com/path/(foo)/bar" rel="nofollow external">http://example.com/path/(foo)/bar</a>'),
array('http://example.com/path/[foo]/bar', array('http://example.com/path/[foo]/bar',
'<a href="http://example.com/path/" title="http://example.com/path/" rel="external">http://example.com/path/</a>[foo]/bar'), '<a href="http://example.com/path/" title="http://example.com/path/" rel="nofollow external">http://example.com/path/</a>[foo]/bar'),
array('http://example.com/path/foo/(bar)', array('http://example.com/path/foo/(bar)',
'<a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>'), '<a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="nofollow external">http://example.com/path/foo/(bar)</a>'),
//Not a valid url - urls cannot contain unencoded square brackets //Not a valid url - urls cannot contain unencoded square brackets
array('http://example.com/path/foo/[bar]', array('http://example.com/path/foo/[bar]',
'<a href="http://example.com/path/foo/" title="http://example.com/path/foo/" rel="external">http://example.com/path/foo/</a>[bar]'), '<a href="http://example.com/path/foo/" title="http://example.com/path/foo/" rel="nofollow external">http://example.com/path/foo/</a>[bar]'),
array('Hey, check out my cool site http://example.com okay?', array('Hey, check out my cool site http://example.com okay?',
'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a> okay?'), 'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="nofollow external">http://example.com</a> okay?'),
array('What about parens (e.g. http://example.com/path/foo/(bar))?', array('What about parens (e.g. http://example.com/path/foo/(bar))?',
'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>)?'), 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="nofollow external">http://example.com/path/foo/(bar)</a>)?'),
array('What about parens (e.g. http://example.com/path/foo/(bar)?', array('What about parens (e.g. http://example.com/path/foo/(bar)?',
'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>?'), 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="nofollow external">http://example.com/path/foo/(bar)</a>?'),
array('What about parens (e.g. http://example.com/path/foo/(bar).)?', array('What about parens (e.g. http://example.com/path/foo/(bar).)?',
'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>.)?'), 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="nofollow external">http://example.com/path/foo/(bar)</a>.)?'),
//Not a valid url - urls cannot contain unencoded commas //Not a valid url - urls cannot contain unencoded commas
array('What about parens (e.g. http://example.com/path/(foo,bar)?', array('What about parens (e.g. http://example.com/path/(foo,bar)?',
'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" title="http://example.com/path/(foo,bar)" rel="external">http://example.com/path/(foo,bar)</a>?'), 'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" title="http://example.com/path/(foo,bar)" rel="nofollow external">http://example.com/path/(foo,bar)</a>?'),
array('Unbalanced too (e.g. http://example.com/path/((((foo)/bar)?', array('Unbalanced too (e.g. http://example.com/path/((((foo)/bar)?',
'Unbalanced too (e.g. <a href="http://example.com/path/((((foo)/bar)" title="http://example.com/path/((((foo)/bar)" rel="external">http://example.com/path/((((foo)/bar)</a>?'), 'Unbalanced too (e.g. <a href="http://example.com/path/((((foo)/bar)" title="http://example.com/path/((((foo)/bar)" rel="nofollow external">http://example.com/path/((((foo)/bar)</a>?'),
array('Unbalanced too (e.g. http://example.com/path/(foo))))/bar)?', array('Unbalanced too (e.g. http://example.com/path/(foo))))/bar)?',
'Unbalanced too (e.g. <a href="http://example.com/path/(foo))))/bar" title="http://example.com/path/(foo))))/bar" rel="external">http://example.com/path/(foo))))/bar</a>)?'), 'Unbalanced too (e.g. <a href="http://example.com/path/(foo))))/bar" title="http://example.com/path/(foo))))/bar" rel="nofollow external">http://example.com/path/(foo))))/bar</a>)?'),
array('Unbalanced too (e.g. http://example.com/path/foo/((((bar)?', array('Unbalanced too (e.g. http://example.com/path/foo/((((bar)?',
'Unbalanced too (e.g. <a href="http://example.com/path/foo/((((bar)" title="http://example.com/path/foo/((((bar)" rel="external">http://example.com/path/foo/((((bar)</a>?'), 'Unbalanced too (e.g. <a href="http://example.com/path/foo/((((bar)" title="http://example.com/path/foo/((((bar)" rel="nofollow external">http://example.com/path/foo/((((bar)</a>?'),
array('Unbalanced too (e.g. http://example.com/path/foo/(bar))))?', array('Unbalanced too (e.g. http://example.com/path/foo/(bar))))?',
'Unbalanced too (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>)))?'), 'Unbalanced too (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="nofollow external">http://example.com/path/foo/(bar)</a>)))?'),
array('example.com', array('example.com',
'<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>'),
array('example.org', array('example.org',
'<a href="http://example.org/" title="http://example.org/" rel="external">example.org</a>'), '<a href="http://example.org/" title="http://example.org/" rel="nofollow external">example.org</a>'),
array('example.co.uk', array('example.co.uk',
'<a href="http://example.co.uk/" title="http://example.co.uk/" rel="external">example.co.uk</a>'), '<a href="http://example.co.uk/" title="http://example.co.uk/" rel="nofollow external">example.co.uk</a>'),
array('www.example.co.uk', array('www.example.co.uk',
'<a href="http://www.example.co.uk/" title="http://www.example.co.uk/" rel="external">www.example.co.uk</a>'), '<a href="http://www.example.co.uk/" title="http://www.example.co.uk/" rel="nofollow external">www.example.co.uk</a>'),
array('farm1.images.example.co.uk', array('farm1.images.example.co.uk',
'<a href="http://farm1.images.example.co.uk/" title="http://farm1.images.example.co.uk/" rel="external">farm1.images.example.co.uk</a>'), '<a href="http://farm1.images.example.co.uk/" title="http://farm1.images.example.co.uk/" rel="nofollow external">farm1.images.example.co.uk</a>'),
array('example.museum', array('example.museum',
'<a href="http://example.museum/" title="http://example.museum/" rel="external">example.museum</a>'), '<a href="http://example.museum/" title="http://example.museum/" rel="nofollow external">example.museum</a>'),
array('example.travel', array('example.travel',
'<a href="http://example.travel/" title="http://example.travel/" rel="external">example.travel</a>'), '<a href="http://example.travel/" title="http://example.travel/" rel="nofollow external">example.travel</a>'),
array('example.com.', array('example.com.',
'<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>.'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>.'),
array('example.com?', array('example.com?',
'<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>?'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>?'),
array('example.com!', array('example.com!',
'<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>!'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>!'),
array('example.com,', array('example.com,',
'<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>,'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>,'),
array('example.com;', array('example.com;',
'<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>;'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>;'),
array('example.com:', array('example.com:',
'<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>:'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>:'),
array('\'example.com\'', array('\'example.com\'',
'\'<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>\''), '\'<a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>\''),
array('"example.com"', array('"example.com"',
'&quot;<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>&quot;'), '&quot;<a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>&quot;'),
array('example.com', array('example.com',
'<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>'), '<a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>'),
array('(example.com)', array('(example.com)',
'(<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>)'), '(<a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>)'),
array('[example.com]', array('[example.com]',
'[<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>]'), '[<a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>]'),
array('<example.com>', array('<example.com>',
'&lt;<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>&gt;'), '&lt;<a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>&gt;'),
array('Hey, check out my cool site example.com okay?', array('Hey, check out my cool site example.com okay?',
'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">example.com</a> okay?'), 'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a> okay?'),
array('Hey, check out my cool site example.com.I made it.', array('Hey, check out my cool site example.com.I made it.',
'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>.I made it.'), 'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>.I made it.'),
array('Hey, check out my cool site example.com.Funny thing...', array('Hey, check out my cool site example.com.Funny thing...',
'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>.Funny thing...'), 'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>.Funny thing...'),
array('Hey, check out my cool site example.com.You will love it.', array('Hey, check out my cool site example.com.You will love it.',
'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>.You will love it.'), 'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="nofollow external">example.com</a>.You will love it.'),
array('What about parens (e.g. example.com/path/foo/(bar))?', array('What about parens (e.g. example.com/path/foo/(bar))?',
'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>)?'), 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="nofollow external">example.com/path/foo/(bar)</a>)?'),
array('What about parens (e.g. example.com/path/foo/(bar)?', array('What about parens (e.g. example.com/path/foo/(bar)?',
'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>?'), 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="nofollow external">example.com/path/foo/(bar)</a>?'),
array('What about parens (e.g. example.com/path/foo/(bar).)?', array('What about parens (e.g. example.com/path/foo/(bar).)?',
'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>.)?'), 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="nofollow external">example.com/path/foo/(bar)</a>.)?'),
array('What about parens (e.g. example.com/path/(foo,bar)?', array('What about parens (e.g. example.com/path/(foo,bar)?',
'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" title="http://example.com/path/(foo,bar)" rel="external">example.com/path/(foo,bar)</a>?'), 'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" title="http://example.com/path/(foo,bar)" rel="nofollow external">example.com/path/(foo,bar)</a>?'),
array('file.ext', array('file.ext',
'file.ext'), 'file.ext'),
array('file.html', array('file.html',