]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch 'master' of gitorious.org:statusnet/mainline into testing
authorBrion Vibber <brion@pobox.com>
Sun, 11 Apr 2010 00:54:37 +0000 (17:54 -0700)
committerBrion Vibber <brion@pobox.com>
Sun, 11 Apr 2010 00:54:37 +0000 (17:54 -0700)
75 files changed:
README
actions/all.php
actions/apiaccountupdatedeliverydevice.php
actions/apigroupcreate.php
actions/apigroupismember.php
actions/apigroupjoin.php
actions/apigroupleave.php
actions/apigroupmembership.php
actions/apigroupshow.php
actions/apistatusesupdate.php
actions/apitimelinegroup.php
actions/apitimelineretweetedbyme.php
actions/avatarsettings.php
actions/block.php
actions/bookmarklet.php
actions/confirmaddress.php
actions/deleteuser.php
actions/designadminpanel.php
actions/disfavor.php
actions/favor.php
actions/finishremotesubscribe.php
actions/grantrole.php
actions/groupblock.php
actions/groupmembers.php
actions/imsettings.php
actions/invite.php
actions/makeadmin.php
actions/microsummary.php
actions/oauthconnectionssettings.php
actions/oembed.php
actions/pathsadminpanel.php
actions/peopletag.php
actions/postnotice.php
actions/public.php
actions/register.php
actions/remotesubscribe.php
actions/repeat.php
actions/replies.php
actions/revokerole.php
actions/sandbox.php
actions/showfavorites.php
actions/shownotice.php
actions/silence.php
actions/siteadminpanel.php
actions/sitenoticeadminpanel.php
actions/snapshotadminpanel.php
actions/subscribers.php
actions/tag.php
actions/unsandbox.php
actions/unsilence.php
actions/unsubscribe.php
actions/userauthorization.php
actions/userrss.php
classes/Memcached_DataObject.php
classes/Notice.php
extlib/Net/IDNA.php [new file with mode: 0644]
extlib/Net/IDNA/php5.php [new file with mode: 0644]
install.php
lib/default.php
lib/httpclient.php
lib/installer.php [new file with mode: 0644]
lib/jabber.php
lib/mail.php
lib/noticelist.php
lib/profileformaction.php
lib/redirectingaction.php [new file with mode: 0644]
lib/stompqueuemanager.php
lib/util.php
plugins/DirectionDetector/DirectionDetectorPlugin.php [new file with mode: 0644]
plugins/Facebook/FBConnectAuth.php
plugins/OpenID/finishopenidlogin.php
plugins/RSSCloud/RSSCloudPlugin.php
plugins/TwitterBridge/twitterauthorization.php
scripts/install_cli.php [new file with mode: 0755]
tests/JidValidateTest.php [new file with mode: 0644]

diff --git a/README b/README
index c687cb240a57d616da317f1cbfc016fd0a460ec4..1e244c448296ba95c62f622be7f7dc235b6c8530 100644 (file)
--- a/README
+++ b/README
@@ -942,6 +942,26 @@ stomp_password: password for connecting to the stomp server; defaults
     to null.
 
 stomp_persistent: keep items across queue server restart, if enabled.
+    Under ActiveMQ, the server configuration determines if and how
+    persistent storage is actually saved.
+
+    If using a message queue server other than ActiveMQ, you may
+    need to disable this if it does not support persistence.
+
+stomp_transactions: use transactions to aid in error detection.
+    A broken transaction will be seen quickly, allowing a message
+    to be redelivered immediately if a daemon crashes.
+
+    If using a message queue server other than ActiveMQ, you may
+    need to disable this if it does not support transactions.
+
+stomp_acks: send acknowledgements to aid in flow control.
+    An acknowledgement of successful processing tells the server
+    we're ready for more and can help keep things moving smoothly.
+
+    This should *not* be turned off when running with ActiveMQ, but
+    if using another message queue server that does not support
+    acknowledgements you might need to disable this.
 
 softlimit: an absolute or relative "soft memory limit"; daemons will
     restart themselves gracefully when they find they've hit
@@ -970,6 +990,12 @@ max_retries: for stomp, drop messages after N failed attempts to process.
 dead_letter_dir: for stomp, optional directory to dump data on failed
     queue processing events after discarding them.
 
+stomp_no_transactions: for stomp, the server does not support transactions,
+    so do not try to user them. This is needed for http://www.morbidq.com/.
+
+stomp_no_acks: for stomp, the server does not support acknowledgements.
+    so do not try to user them. This is needed for http://www.morbidq.com/.
+
 license
 -------
 
index 8c22e6f5f076337b5829a791129369beaef2b9db..a977fce95425f41383e5e209fb4a7145613cc234 100644 (file)
@@ -61,7 +61,7 @@ class AllAction extends ProfileAction
 
         if ($this->page > 1 && $this->notice->N == 0) {
             // TRANS: Server error when page not found (404)
-            $this->serverError(_('No such page'), $code = 404);
+            $this->serverError(_('No such page.'), $code = 404);
         }
 
         return true;
index 684906fe9019eb75604c733a3e16ff7e4d842218..05d19c22dea8d63ab5e3ee849daafe88d1df8f19 100644 (file)
@@ -103,7 +103,7 @@ class ApiAccountUpdateDeliveryDeviceAction extends ApiAuthAction
             $this->clientError(
                 _(
                     'You must specify a parameter named ' .
-                    '\'device\' with a value of one of: sms, im, none'
+                    '\'device\' with a value of one of: sms, im, none.'
                 )
             );
             return;
index 145806356c77d2b74224635ed19cc854e92d9629..3eb3ae5fcce4cd003b4c96c491bd7d47687d1e15 100644 (file)
@@ -263,7 +263,7 @@ class ApiGroupCreateAction extends ApiAuthAction
 
             if (!$valid) {
                 $this->clientError(
-                    sprintf(_('Invalid alias: "%s"'), $alias),
+                    sprintf(_('Invalid alias: "%s".'), $alias),
                     403,
                     $this->format
                 );
index 97f8435614e9da39662c67155f0fd1fba1daf3d6..f51c747dfbed9c1bd7303956988710a81ddf587b 100644 (file)
@@ -92,7 +92,7 @@ class ApiGroupIsMemberAction extends ApiBareAuthAction
         }
 
         if (empty($this->group)) {
-            $this->clientError(_('Group not found!'), 404, $this->format);
+            $this->clientError(_('Group not found.'), 404, $this->format);
             return false;
         }
 
index 374cf83df068c77506d025d7c66e3506fff69cf3..28df72fa9ab759f2102d5d3c3f45f3a7fc2b7333 100644 (file)
@@ -101,7 +101,7 @@ class ApiGroupJoinAction extends ApiAuthAction
         }
 
         if (empty($this->group)) {
-            $this->clientError(_('Group not found!'), 404, $this->format);
+            $this->clientError(_('Group not found.'), 404, $this->format);
             return false;
         }
 
index 9848ece0530cb133937e993e49f98e6eccc20e9d..f6e52b26e86548945e1ee3b0db003c3c09dd37fb 100644 (file)
@@ -101,7 +101,7 @@ class ApiGroupLeaveAction extends ApiAuthAction
         }
 
         if (empty($this->group)) {
-            $this->clientError(_('Group not found!'), 404, $this->format);
+            $this->clientError(_('Group not found.'), 404, $this->format);
             return false;
         }
 
index 9f72b527cfd7a43601fcf84de764cf330bc82e63..c97b27fac425cd7e7f383e359d7738cec37a430d 100644 (file)
@@ -88,7 +88,7 @@ class ApiGroupMembershipAction extends ApiPrivateAuthAction
         parent::handle($args);
 
         if (empty($this->group)) {
-            $this->clientError(_('Group not found!'), 404, $this->format);
+            $this->clientError(_('Group not found.'), 404, $this->format);
             return false;
         }
 
index 5745a81f4172e6572694bba3b12ccb1ea638f8ed..8e471689a8ebdb86fd8f04f102ca57468528d7b4 100644 (file)
@@ -79,7 +79,7 @@ class ApiGroupShowAction extends ApiPrivateAuthAction
                 common_redirect(common_local_url('ApiGroupShow', $args), 301);
             } else {
                 $this->clientError(
-                    _('Group not found!'),
+                    _('Group not found.'),
                     404,
                     $this->format
                 );
index 1956c85863d747173b4a15c8cc30e10f9caefe43..d4ef6b550d974743f26858c945bf576bda05eb24 100644 (file)
@@ -199,7 +199,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction
                     $reply_to = $this->in_reply_to_status_id;
                 } else {
                     $this->clientError(
-                        _('Not found'),
+                        _('Not found.'),
                         $code = 404,
                         $this->format
                     );
index da816c40a9063141a8b567270ab0872e1e82898b..56d1de094c5da37a12445f107151e66ced41af08 100644 (file)
@@ -88,7 +88,7 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
         parent::handle($args);
 
         if (empty($this->group)) {
-            $this->clientError(_('Group not found!'), 404, $this->format);
+            $this->clientError(_('Group not found.'), 404, $this->format);
             return false;
         }
 
index 564e98619a8321f1f12f56de57786aa45e09a3bb..af05623cdf484144ab5ef3a58bd41c2209ba3bdb 100644 (file)
@@ -69,7 +69,7 @@ class ApiTimelineRetweetedByMeAction extends ApiAuthAction
     {
         parent::prepare($args);
 
-        $this->serverError('Unimplemented', 503);
+        $this->serverError('Unimplemented.', 503);
 
         return false;
     }
index d4ea11cb7e9deb03b3857e4abff3ee4608d5cd76..52dc2e42496aa1b8c27ef47bc232b76a6dcf279d 100644 (file)
@@ -103,7 +103,7 @@ class AvatarsettingsAction extends AccountSettingsAction
 
         if (!$profile) {
             common_log_db_error($user, 'SELECT', __FILE__);
-            $this->serverError(_('User without matching profile'));
+            $this->serverError(_('User without matching profile.'));
             return;
         }
 
@@ -182,7 +182,7 @@ class AvatarsettingsAction extends AccountSettingsAction
 
         if (!$profile) {
             common_log_db_error($user, 'SELECT', __FILE__);
-            $this->serverError(_('User without matching profile'));
+            $this->serverError(_('User without matching profile.'));
             return;
         }
 
index fe4ec00881e6565a503bcaac1dc82d601789aede..7f609c253b99b2d270f65c2779f784ebdf0a2d83 100644 (file)
@@ -66,7 +66,7 @@ class BlockAction extends ProfileFormAction
         assert(!empty($cur)); // checked by parent
 
         if ($cur->hasBlocked($this->profile)) {
-            $this->clientError(_("You already blocked that user."));
+            $this->clientError(_('You already blocked that user.'));
             return false;
         }
 
index 0603a745612a07df289f0bead249b87f3887e8ad..041c2e89475267931108b23e520c93cba543722e 100644 (file)
@@ -47,7 +47,8 @@ class BookmarkletAction extends NewnoticeAction
 {
     function showTitle()
     {
-        $this->element('title', null, _('Post to ').common_config('site', 'name'));
+        // TRANS: Title for mini-posting window loaded from bookmarklet.
+        $this->element('title', null, sprintf(_('Post to %s'), common_config('site', 'name')));
     }
 
     function showHeader()
index cc8351d8dcc0309ae593b77e120688fb571602c9..dc17499f551161b6f1f936daa862d7f158cd3ba5 100644 (file)
@@ -87,7 +87,7 @@ class ConfirmaddressAction extends Action
         }
         $type = $confirm->address_type;
         if (!in_array($type, array('email', 'jabber', 'sms'))) {
-            $this->serverError(sprintf(_('Unrecognized address type %s'), $type));
+            $this->serverError(sprintf(_('Unrecognized address type %s.'), $type));
             return;
         }
         if ($cur->$type == $confirm->address) {
index 4e6b27395389406f627cec8bed330ebfe62b274f..42ef4b9f513de031b77f05acaeb2908c2cd96f75 100644 (file)
@@ -64,14 +64,14 @@ class DeleteuserAction extends ProfileFormAction
         assert(!empty($cur)); // checked by parent
 
         if (!$cur->hasRight(Right::DELETEUSER)) {
-            $this->clientError(_("You cannot delete users."));
+            $this->clientError(_('You cannot delete users.'));
             return false;
         }
 
         $this->user = User::staticGet('id', $this->profile->id);
 
         if (empty($this->user)) {
-            $this->clientError(_("You can only delete local users."));
+            $this->clientError(_('You can only delete local users.'));
             return false;
         }
 
index 30e8bde1a4e5d344ffa221a770183720cbfda204..41d917e3ca2a961256d648244d089fdcc82bca52 100644 (file)
@@ -272,11 +272,11 @@ class DesignadminpanelAction extends AdminPanelAction
     {
         if (!empty($values['logo']) &&
             !Validate::uri($values['logo'], array('allowed_schemes' => array('http', 'https')))) {
-            $this->clientError(_("Invalid logo URL."));
+            $this->clientError(_('Invalid logo URL.'));
         }
 
         if (!in_array($values['theme'], Theme::listAvailable())) {
-            $this->clientError(sprintf(_("Theme not available: %s"), $values['theme']));
+            $this->clientError(sprintf(_("Theme not available: %s."), $values['theme']));
         }
     }
 
index 6269f1bd25aa56022bc5cc076b8dcd0899d00ae0..3ccdd69af25f49a243561c61dfe25e4603cf46eb 100644 (file)
@@ -71,7 +71,7 @@ class DisfavorAction extends Action
         $notice = Notice::staticGet($id);
         $token  = $this->trimmed('token-'.$notice->id);
         if (!$token || $token != common_session_token()) {
-            $this->clientError(_("There was a problem with your session token. Try again, please."));
+            $this->clientError(_('There was a problem with your session token. Try again, please.'));
             return;
         }
         $fave            = new Fave();
index afca9768ae0c4c69c57065101f21f8b6dab6b8ea..475912fd0b1722ba924a842f85bf9288af36e1ad 100644 (file)
@@ -72,7 +72,7 @@ class FavorAction extends Action
         $notice = Notice::staticGet($id);
         $token  = $this->trimmed('token-'.$notice->id);
         if (!$token || $token != common_session_token()) {
-            $this->clientError(_("There was a problem with your session token. Try again, please."));
+            $this->clientError(_('There was a problem with your session token. Try again, please.'));
             return;
         }
         if ($user->hasFave($notice)) {
index deee70f360625400b88f3c4047023597f31d7f12..ac51ddec3fbc825a2ad4f824d11d480406b7a239 100644 (file)
@@ -135,7 +135,7 @@ class FinishremotesubscribeAction extends Action
                             $service->getServiceURI(OMB_ENDPOINT_UPDATEPROFILE);
 
         if (!$remote->update($orig_remote)) {
-                $this->serverError(_('Error updating remote profile'));
+                $this->serverError(_('Error updating remote profile.'));
                 return;
         }
 
index cd6bd4d79ae41d6ae68524801d4a0d7dde30ec0e..b8b23d02e91d8c70f8494bfb5c7b20c3bf70948d 100644 (file)
@@ -59,11 +59,11 @@ class GrantRoleAction extends ProfileFormAction
         
         $this->role = $this->arg('role');
         if (!Profile_role::isValid($this->role)) {
-            $this->clientError(_("Invalid role."));
+            $this->clientError(_('Invalid role.'));
             return false;
         }
         if (!Profile_role::isSettable($this->role)) {
-            $this->clientError(_("This role is reserved and cannot be set."));
+            $this->clientError(_('This role is reserved and cannot be set.'));
             return false;
         }
 
@@ -72,14 +72,14 @@ class GrantRoleAction extends ProfileFormAction
         assert(!empty($cur)); // checked by parent
 
         if (!$cur->hasRight(Right::GRANTROLE)) {
-            $this->clientError(_("You cannot grant user roles on this site."));
+            $this->clientError(_('You cannot grant user roles on this site.'));
             return false;
         }
 
         assert(!empty($this->profile)); // checked by parent
 
         if ($this->profile->hasRole($this->role)) {
-            $this->clientError(_("User already has this role."));
+            $this->clientError(_('User already has this role.'));
             return false;
         }
 
index 88d7634a28d89078047032e2d3e2af24e132fe4f..fc95c0e66963f06e4d7b22a57af94df7dcb7f89e 100644 (file)
@@ -41,7 +41,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
  * @link     http://status.net/
  */
 
-class GroupblockAction extends Action
+class GroupblockAction extends RedirectingAction
 {
     var $profile = null;
     var $group = null;
@@ -117,9 +117,7 @@ class GroupblockAction extends Action
         parent::handle($args);
         if ($_SERVER['REQUEST_METHOD'] == 'POST') {
             if ($this->arg('no')) {
-                common_redirect(common_local_url('groupmembers',
-                                                 array('nickname' => $this->group->nickname)),
-                                303);
+                $this->returnToArgs();
             } elseif ($this->arg('yes')) {
                 $this->blockProfile();
             } elseif ($this->arg('blockto')) {
@@ -196,23 +194,20 @@ class GroupblockAction extends Action
             $this->serverError(_("Database error blocking user from group."));
             return false;
         }
+        
+        $this->returnToArgs();
+    }
 
-        // Now, gotta figure where we go back to
-        foreach ($this->args as $k => $v) {
-            if ($k == 'returnto-action') {
-                $action = $v;
-            } elseif (substr($k, 0, 9) == 'returnto-') {
-                $args[substr($k, 9)] = $v;
-            }
-        }
-
-        if ($action) {
-            common_redirect(common_local_url($action, $args), 303);
-        } else {
-            common_redirect(common_local_url('groupmembers',
-                                             array('nickname' => $this->group->nickname)),
-                            303);
-        }
+    /**
+     * If we reached this form without returnto arguments, default to
+     * the top of the group's member list.
+     * 
+     * @return string URL
+     */
+    function defaultReturnTo()
+    {
+        return common_local_url('groupmembers',
+                                array('nickname' => $this->group->nickname));
     }
 
     function showScripts()
index e72ef371acad7fe36fc67e1d9c96eb2c1d3b16fd..54f1d8dcda748e8fb112c6042e9d4a14ac1f0ca7 100644 (file)
@@ -205,8 +205,7 @@ class GroupMemberListItem extends ProfileListItem
             !$this->profile->isAdmin($this->group)) {
             $this->out->elementStart('li', 'entity_make_admin');
             $maf = new MakeAdminForm($this->out, $this->profile, $this->group,
-                                     array('action' => 'groupmembers',
-                                           'nickname' => $this->group->nickname));
+                                     $this->returnToArgs());
             $maf->show();
             $this->out->elementEnd('li');
         }
@@ -220,8 +219,7 @@ class GroupMemberListItem extends ProfileListItem
         if (!empty($user) && $user->id != $this->profile->id && $user->isAdmin($this->group)) {
             $this->out->elementStart('li', 'entity_block');
             $bf = new GroupBlockForm($this->out, $this->profile, $this->group,
-                                array('action' => 'groupmembers',
-                                      'nickname' => $this->group->nickname));
+                                     $this->returnToArgs());
             $bf->show();
             $this->out->elementEnd('li');
         }
@@ -248,6 +246,23 @@ class GroupMemberListItem extends ProfileListItem
 
         return $aAttrs;
     }
+
+    /**
+     * Fetch necessary return-to arguments for the profile forms
+     * to return to this list when they're done.
+     * 
+     * @return array
+     */
+    protected function returnToArgs()
+    {
+        $args = array('action' => 'groupmembers',
+                      'nickname' => $this->group->nickname);
+        $page = $this->out->arg('page');
+        if ($page) {
+            $args['param-page'] = $page;
+        }
+        return $args;
+    }
 }
 
 /**
index af4915843d5f799fab73d4075795d65aa23dc648..c3360fb12a5b16483428f118d10a9699b703e3d0 100644 (file)
@@ -292,7 +292,7 @@ class ImsettingsAction extends ConnectSettingsAction
             $this->showForm(_('Cannot normalize that Jabber ID'));
             return;
         }
-        if (!jabber_valid_base_jid($jabber)) {
+        if (!jabber_valid_base_jid($jabber, common_config('email', 'domain_check'))) {
             $this->showForm(_('Not a valid Jabber ID'));
             return;
         } else if ($user->jabber == $jabber) {
index 54b2de62ac910e3792f902495c54a4554e81a79c..5dac048b061e6990922ddd28e84a76430e5ed599 100644 (file)
@@ -38,7 +38,7 @@ class InviteAction extends CurrentUserDesignAction
         if (!common_config('invite', 'enabled')) {
             $this->clientError(_('Invites have been disabled.'));
         } else if (!common_logged_in()) {
-            $this->clientError(sprintf(_('You must be logged in to invite other users to use %s'),
+            $this->clientError(sprintf(_('You must be logged in to invite other users to use %s.'),
                                         common_config('site', 'name')));
             return;
         } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
index f19348648d8700f50b0ef8955419dbd7a0b24ecf..9ccb442308b2910b0fd815dd80fd03bfb9aa4629 100644 (file)
@@ -41,7 +41,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
  * @link     http://status.net/
  */
 
-class MakeadminAction extends Action
+class MakeadminAction extends RedirectingAction
 {
     var $profile = null;
     var $group = null;
@@ -148,20 +148,19 @@ class MakeadminAction extends Action
                                $this->group->getBestName());
         }
 
-        foreach ($this->args as $k => $v) {
-            if ($k == 'returnto-action') {
-                $action = $v;
-            } else if (substr($k, 0, 9) == 'returnto-') {
-                $args[substr($k, 9)] = $v;
-            }
-        }
+        $this->returnToArgs();
+    }
 
-        if ($action) {
-            common_redirect(common_local_url($action, $args), 303);
-        } else {
-            common_redirect(common_local_url('groupmembers',
-                                             array('nickname' => $this->group->nickname)),
-                            303);
-        }
+    /**
+     * If we reached this form without returnto arguments, default to
+     * the top of the group's member list.
+     * 
+     * @return string URL
+     */
+    function defaultReturnTo()
+    {
+        return common_local_url('groupmembers',
+                                array('nickname' => $this->group->nickname));
     }
+
 }
index 5c761e8bb6f06acdba886a5350528fd58f71550a..d145dc3bc7b0bc724ba7ae087148131e4415589d 100644 (file)
@@ -66,7 +66,7 @@ class MicrosummaryAction extends Action
         $notice = $user->getCurrentNotice();
         
         if (!$notice) {
-            $this->clientError(_('No current status'), 404);
+            $this->clientError(_('No current status.'), 404);
         }
         
         header('Content-Type: text/plain');
index f125f4c63101f222b740da552ad0390be8377ecd..8a206d7101372f3dd69dd9c6af9e92ec203b2051 100644 (file)
@@ -183,7 +183,7 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction
 
         if (!$result) {
             common_log_db_error($orig, 'DELETE', __FILE__);
-            $this->clientError(_('Unable to revoke access for app: ' . $app->id));
+            $this->clientError(sprintf(_('Unable to revoke access for app: %s.'), $app->id));
             return false;
         }
 
@@ -195,7 +195,7 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction
 
     function showEmptyListMessage()
     {
-        $message = sprintf(_('You have not authorized any applications to use your account.'));
+        $message = _('You have not authorized any applications to use your account.');
 
         $this->elementStart('div', 'guide');
         $this->raw(common_markup_to_html($message));
index e287b6ae2a9563972ea2dac524bf379a8e789adb..1503aa9c2b93718f182686fbca8710714449c559 100644 (file)
@@ -60,7 +60,7 @@ class OembedAction extends Action
             $proxy_args = $r->map($path);
 
             if (!$proxy_args) {
-                $this->serverError(_("$path not found"), 404);
+                $this->serverError(_("$path not found."), 404);
             }
             $oembed=array();
             $oembed['version']='1.0';
@@ -72,11 +72,11 @@ class OembedAction extends Action
                     $id = $proxy_args['notice'];
                     $notice = Notice::staticGet($id);
                     if(empty($notice)){
-                        $this->serverError(_("notice $id not found"), 404);
+                        $this->serverError(_("Notice $id not found."), 404);
                     }
                     $profile = $notice->getProfile();
                     if (empty($profile)) {
-                        $this->serverError(_('Notice has no profile'), 500);
+                        $this->serverError(_('Notice has no profile.'), 500);
                     }
                     if (!empty($profile->fullname)) {
                         $authorname = $profile->fullname . ' (' . $profile->nickname . ')';
@@ -95,7 +95,7 @@ class OembedAction extends Action
                     $id = $proxy_args['attachment'];
                     $attachment = File::staticGet($id);
                     if(empty($attachment)){
-                        $this->serverError(_("attachment $id not found"), 404);
+                        $this->serverError(_("Attachment $id not found."), 404);
                     }
                     if(empty($attachment->filename) && $file_oembed = File_oembed::staticGet('file_id', $attachment->id)){
                         // Proxy the existing oembed information
@@ -123,7 +123,7 @@ class OembedAction extends Action
                     if($attachment->title) $oembed['title']=$attachment->title;
                     break;
                 default:
-                    $this->serverError(_("$path not supported for oembed requests"), 501);
+                    $this->serverError(_("$path not supported for oembed requests."), 501);
             }
             switch($args['format']){
                 case 'xml':
@@ -154,10 +154,12 @@ class OembedAction extends Action
                     $this->end_document('json');
                     break;
                 default:
-                    $this->serverError(_('content type ' . $apidata['content-type'] . ' not supported'), 501);
+                    // TRANS: Error message displaying attachments. %s is a raw MIME type (eg 'image/png')
+                    $this->serverError(sprintf(_('Content type %s not supported.'), $apidata['content-type']), 501);
             }
         }else{
-            $this->serverError(_('Only ' . common_root_url() . ' urls over plain http please'), 404);
+            // TRANS: Error message displaying attachments. %s is the site's base URL.
+            $this->serverError(sprintf(_('Only %s URLs over plain HTTP please.'), common_root_url()), 404);
         }
     }
 
index 9155a7e42856ae19b61b405973c5de83aa3b3d5e..7ff3c2583a0c44f7868017256a3d8a7cb1ead954 100644 (file)
@@ -154,19 +154,19 @@ class PathsadminpanelAction extends AdminPanelAction
         // Validate theme dir
 
         if (!empty($values['theme']['dir']) && !is_readable($values['theme']['dir'])) {
-            $this->clientError(sprintf(_("Theme directory not readable: %s"), $values['theme']['dir']));
+            $this->clientError(sprintf(_("Theme directory not readable: %s."), $values['theme']['dir']));
         }
 
         // Validate avatar dir
 
         if (empty($values['avatar']['dir']) || !is_writable($values['avatar']['dir'])) {
-            $this->clientError(sprintf(_("Avatar directory not writable: %s"), $values['avatar']['dir']));
+            $this->clientError(sprintf(_("Avatar directory not writable: %s."), $values['avatar']['dir']));
         }
 
         // Validate background dir
 
         if (empty($values['background']['dir']) || !is_writable($values['background']['dir'])) {
-            $this->clientError(sprintf(_("Background directory not writable: %s"), $values['background']['dir']));
+            $this->clientError(sprintf(_("Background directory not writable: %s."), $values['background']['dir']));
         }
 
         // Validate locales dir
@@ -174,13 +174,13 @@ class PathsadminpanelAction extends AdminPanelAction
         // XXX: What else do we need to validate for lacales path here? --Z
 
         if (!empty($values['site']['locale_path']) && !is_readable($values['site']['locale_path'])) {
-            $this->clientError(sprintf(_("Locales directory not readable: %s"), $values['site']['locale_path']));
+            $this->clientError(sprintf(_("Locales directory not readable: %s."), $values['site']['locale_path']));
         }
 
         // Validate SSL setup
 
         if (mb_strlen($values['site']['sslserver']) > 255) {
-            $this->clientError(_("Invalid SSL server. The maximum length is 255 characters."));
+            $this->clientError(_('Invalid SSL server. The maximum length is 255 characters.'));
         }
     }
 
index 32652f75518f1a34ba548e574ccc062bfe88c5a5..7287cfbf995aa0bf08d29df44cde7627ff59f87b 100644 (file)
@@ -65,7 +65,7 @@ class PeopletagAction extends Action
         $this->tag = $this->trimmed('tag');
 
         if (!common_valid_profile_tag($this->tag)) {
-            $this->clientError(sprintf(_('Not a valid people tag: %s'),
+            $this->clientError(sprintf(_('Not a valid people tag: %s.'),
                 $this->tag));
             return;
         }
index b2f6f1bb95debe9a57e441bbdc07a96c8a37447e..694c7808d9930722c5d2e054a59b0387201c4ae2 100644 (file)
@@ -92,7 +92,7 @@ class PostnoticeAction extends Action
     {
         $content = common_shorten_links($_POST['omb_notice_content']);
         if (Notice::contentTooLong($content)) {
-            $this->clientError(_('Invalid notice content'), 400);
+            $this->clientError(_('Invalid notice content.'), 400);
             return false;
         }
         $license      = $_POST['omb_notice_license'];
index 0b3b5fde846fd23b2cbb4aedcce78e25f71363a8..5fc547feaf632613a04d11a647c51b5afdd49588 100644 (file)
@@ -80,7 +80,7 @@ class PublicAction extends Action
         $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
 
         if ($this->page > MAX_PUBLIC_PAGE) {
-            $this->clientError(sprintf(_("Beyond the page limit (%s)"), MAX_PUBLIC_PAGE));
+            $this->clientError(sprintf(_("Beyond the page limit (%s)."), MAX_PUBLIC_PAGE));
         }
 
         common_set_returnto($this->selfUrl());
@@ -95,7 +95,7 @@ class PublicAction extends Action
 
         if($this->page > 1 && $this->notice->N == 0){
             // TRANS: Server error when page not found (404)
-            $this->serverError(_('No such page'),$code=404);
+            $this->serverError(_('No such page.'),$code=404);
         }
 
         return true;
index ccab76cf0193af51e161624f054e8b05681292c6..da8d0a0bbcdd83ce26ca2757d504fde0e7f89bd0 100644 (file)
@@ -491,11 +491,15 @@ class RegisterAction extends Action
             $this->elementStart('li');
             $this->element('input', $attrs);
             $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license'));
-            $this->text(_('My text and files are available under '));
-            $this->element('a', array('href' => common_config('license', 'url')),
-                           common_config('license', 'title'), _("Creative Commons Attribution 3.0"));
-            $this->text(_(' except this private data: password, '.
-                          'email address, IM address, and phone number.'));
+            $message = _('My text and files are available under %s ' .
+                         'except this private data: password, ' .
+                         'email address, IM address, and phone number.');
+            $link = '<a href="' .
+                    htmlspecialchars(common_config('license', 'url')) .
+                    '">' .
+                    htmlspecialchars(common_config('license', 'title')) .
+                    '</a>';
+            $this->raw(sprintf(htmlspecialchars($message), $link));
             $this->elementEnd('label');
             $this->elementEnd('li');
         }
index c723d53a1c3170d92be92d35966f6f57336aad80..9fc235e743a8deba34f2321e05b3254412f62dda 100644 (file)
@@ -188,7 +188,7 @@ class RemotesubscribeAction extends Action
         $profile = $user->getProfile();
         if (!$profile) {
             common_log_db_error($user, 'SELECT', __FILE__);
-            $this->serverError(_('User without matching profile'));
+            $this->serverError(_('User without matching profile.'));
             return;
         }
 
index e112496bc120dbab109235077b5faa94260764a8..893cae4ffd8f2feae1b802b6011b8b239bdae875 100644 (file)
@@ -54,21 +54,21 @@ class RepeatAction extends Action
         $this->user = common_current_user();
 
         if (empty($this->user)) {
-            $this->clientError(_("Only logged-in users can repeat notices."));
+            $this->clientError(_('Only logged-in users can repeat notices.'));
             return false;
         }
 
         $id = $this->trimmed('notice');
 
         if (empty($id)) {
-            $this->clientError(_("No notice specified."));
+            $this->clientError(_('No notice specified.'));
             return false;
         }
 
         $this->notice = Notice::staticGet('id', $id);
 
         if (empty($this->notice)) {
-            $this->clientError(_("No notice specified."));
+            $this->clientError(_('No notice specified.'));
             return false;
         }
 
@@ -80,14 +80,14 @@ class RepeatAction extends Action
         $token  = $this->trimmed('token-'.$id);
 
         if (empty($token) || $token != common_session_token()) {
-            $this->clientError(_("There was a problem with your session token. Try again, please."));
+            $this->clientError(_('There was a problem with your session token. Try again, please.'));
             return false;
         }
 
         $profile = $this->user->getProfile();
 
         if ($profile->hasRepeated($id)) {
-            $this->clientError(_("You already repeated that notice."));
+            $this->clientError(_('You already repeated that notice.'));
             return false;
         }
 
index 4ff1b7a8d20e7e8549d095ba838cfe82760b4760..608f71d6e0f4a011d1156e1ceaaa815c2a5a7018 100644 (file)
@@ -90,7 +90,7 @@ class RepliesAction extends OwnerDesignAction
 
         if($this->page > 1 && $this->notice->N == 0){
             // TRANS: Server error when page not found (404)
-            $this->serverError(_('No such page'),$code=404);
+            $this->serverError(_('No such page.'),$code=404);
         }
 
         return true;
index b78c1c25a4f2f8a04926dff64565cfe07fd9895d..c67b70fdafd5e22a3487464426e68ac3fdbb4419 100644 (file)
@@ -59,11 +59,11 @@ class RevokeRoleAction extends ProfileFormAction
         
         $this->role = $this->arg('role');
         if (!Profile_role::isValid($this->role)) {
-            $this->clientError(_("Invalid role."));
+            $this->clientError(_('Invalid role.'));
             return false;
         }
         if (!Profile_role::isSettable($this->role)) {
-            $this->clientError(_("This role is reserved and cannot be set."));
+            $this->clientError(_('This role is reserved and cannot be set.'));
             return false;
         }
 
@@ -72,7 +72,7 @@ class RevokeRoleAction extends ProfileFormAction
         assert(!empty($cur)); // checked by parent
 
         if (!$cur->hasRight(Right::REVOKEROLE)) {
-            $this->clientError(_("You cannot revoke user roles on this site."));
+            $this->clientError(_('You cannot revoke user roles on this site.'));
             return false;
         }
 
index 5b034ff07819da35c5ce1707318aa3b9687ebb77..d1ef4c86b2858cd302f384343571853db231b9ce 100644 (file)
@@ -62,14 +62,14 @@ class SandboxAction extends ProfileFormAction
         assert(!empty($cur)); // checked by parent
 
         if (!$cur->hasRight(Right::SANDBOXUSER)) {
-            $this->clientError(_("You cannot sandbox users on this site."));
+            $this->clientError(_('You cannot sandbox users on this site.'));
             return false;
         }
 
         assert(!empty($this->profile)); // checked by parent
 
         if ($this->profile->isSandboxed()) {
-            $this->clientError(_("User is already sandboxed."));
+            $this->clientError(_('User is already sandboxed.'));
             return false;
         }
 
index 5b85de6835d704c610a0dfc0e20b24f606569da7..4d776ef04cec23fce077c9e58b6c849a34757a89 100644 (file)
@@ -135,7 +135,7 @@ class ShowfavoritesAction extends OwnerDesignAction
 
         if($this->page > 1 && $this->notice->N == 0){
             // TRANS: Server error when page not found (404)
-            $this->serverError(_('No such page'),$code=404);
+            $this->serverError(_('No such page.'),$code=404);
         }
 
         return true;
index a23027f7c5f6858f308008c647c9e21fe576691e..7be9618f864f7bc99f9e7199d4b9bf2c82101fa0 100644 (file)
@@ -97,7 +97,7 @@ class ShownoticeAction extends OwnerDesignAction
         $this->profile = $this->notice->getProfile();
 
         if (empty($this->profile)) {
-            $this->serverError(_('Notice has no profile'), 500);
+            $this->serverError(_('Notice has no profile.'), 500);
             return false;
         }
 
index 206e5ba878fcf797f30f51acef55b9d8e1d0dce5..09cc480d9ebbcb274dfe741ae822a84caabb8cf4 100644 (file)
@@ -62,14 +62,14 @@ class SilenceAction extends ProfileFormAction
         assert(!empty($cur)); // checked by parent
 
         if (!$cur->hasRight(Right::SILENCEUSER)) {
-            $this->clientError(_("You cannot silence users on this site."));
+            $this->clientError(_('You cannot silence users on this site.'));
             return false;
         }
 
         assert(!empty($this->profile)); // checked by parent
 
         if ($this->profile->isSilenced()) {
-            $this->clientError(_("User is already silenced."));
+            $this->clientError(_('User is already silenced.'));
             return false;
         }
 
index e5482987fb02cf3a57fdc44713139936d99b34ad..4238b3e85a3a98636dfadb4eeb3ee6ff24528752 100644 (file)
@@ -130,7 +130,7 @@ class SiteadminpanelAction extends AdminPanelAction
         // Validate site name
 
         if (empty($values['site']['name'])) {
-            $this->clientError(_("Site name must have non-zero length."));
+            $this->clientError(_('Site name must have non-zero length.'));
         }
 
         // Validate email
@@ -168,7 +168,7 @@ class SiteadminpanelAction extends AdminPanelAction
         // Validate dupe limit
 
         if (!Validate::number($values['site']['dupelimit'], array('min' => 1))) {
-            $this->clientError(_("Dupe limit must 1 or more seconds."));
+            $this->clientError(_("Dupe limit must be one or more seconds."));
         }
 
     }
index a68cc699ca5cb52cb529724e3c7637f23fc04659..bdcaa235573eb6e49e67b3ba88a8e614dc9d275a 100644 (file)
@@ -110,7 +110,7 @@ class SitenoticeadminpanelAction extends AdminPanelAction
 
         if (mb_strlen($siteNotice) > 255)  {
             $this->clientError(
-                _('Max length for the site-wide notice is 255 chars')
+                _('Max length for the site-wide notice is 255 chars.')
             );
         }
 
index a0c2315bc19abd16e867b8cd8ff456c525b91115..df6b168dc8e0cfb77a8330aa34ca87affcd74864 100644 (file)
@@ -124,13 +124,13 @@ class SnapshotadminpanelAction extends AdminPanelAction
         // Validate snapshot run value
 
         if (!in_array($values['snapshot']['run'], array('web', 'cron', 'never'))) {
-            $this->clientError(_("Invalid snapshot run value."));
+            $this->clientError(_('Invalid snapshot run value.'));
         }
 
         // Validate snapshot frequency value
 
         if (!Validate::number($values['snapshot']['frequency'])) {
-            $this->clientError(_("Snapshot frequency must be a number."));
+            $this->clientError(_('Snapshot frequency must be a number.'));
         }
 
         // Validate report URL
@@ -141,7 +141,7 @@ class SnapshotadminpanelAction extends AdminPanelAction
                 array('allowed_schemes' => array('http', 'https')
             )
         )) {
-            $this->clientError(_("Invalid snapshot report URL."));
+            $this->clientError(_('Invalid snapshot report URL.'));
         }
     }
 }
index 6f1520b3f48024959f9bfcecf66050a98ec1c86c..2845a498e991a0c7033f83fe002f09fd58e5376a 100644 (file)
@@ -157,9 +157,13 @@ class SubscribersListItem extends SubscriptionListItem
         $user = common_current_user();
 
         if (!empty($user) && $this->owner->id == $user->id) {
-            $bf = new BlockForm($this->out, $this->profile,
-                                array('action' => 'subscribers',
-                                      'nickname' => $this->owner->nickname));
+            $returnto = array('action' => 'subscribers',
+                              'nickname' => $this->owner->nickname);
+            $page = $this->out->arg('page');
+            if ($page) {
+                $returnto['param-page'] = $page;
+            }
+            $bf = new BlockForm($this->out, $this->profile, $returnto);
             $bf->show();
         }
     }
index ee9617b662cd1d4185a00c2c6f142e511ff94042..9532404041597f44e94fe50fce2ac9725c37fca3 100644 (file)
@@ -49,7 +49,7 @@ class TagAction extends Action
 
         if($this->page > 1 && $this->notice->N == 0){
             // TRANS: Server error when page not found (404)
-            $this->serverError(_('No such page'),$code=404);
+            $this->serverError(_('No such page.'),$code=404);
         }
 
         return true;
index 22f4d8e766e7f35f0802b3d61af02ad43016bac2..d50b5072eee2f78e01c56dd6fa10d266fb2b8cbb 100644 (file)
@@ -62,14 +62,14 @@ class UnsandboxAction extends ProfileFormAction
         assert(!empty($cur)); // checked by parent
 
         if (!$cur->hasRight(Right::SANDBOXUSER)) {
-            $this->clientError(_("You cannot sandbox users on this site."));
+            $this->clientError(_('You cannot sandbox users on this site.'));
             return false;
         }
 
         assert(!empty($this->profile)); // checked by parent
 
         if (!$this->profile->isSandboxed()) {
-            $this->clientError(_("User is not sandboxed."));
+            $this->clientError(_('User is not sandboxed.'));
             return false;
         }
 
index 9ff1b828b03e59f0db8dd02cbef567ba58247cf7..7d282c3661e02deb9f931d6d05fa59d55a19b2fb 100644 (file)
@@ -62,14 +62,14 @@ class UnsilenceAction extends ProfileFormAction
         assert(!empty($cur)); // checked by parent
 
         if (!$cur->hasRight(Right::SILENCEUSER)) {
-            $this->clientError(_("You cannot silence users on this site."));
+            $this->clientError(_('You cannot silence users on this site.'));
             return false;
         }
 
         assert(!empty($this->profile)); // checked by parent
 
         if (!$this->profile->isSilenced()) {
-            $this->clientError(_("User is not silenced."));
+            $this->clientError(_('User is not silenced.'));
             return false;
         }
 
index 6bb10d448b3e663ceb85b214f8a0db774e39b421..57ca15d687442b8cbff85de3c02dd1c7aaac353f 100644 (file)
@@ -74,7 +74,7 @@ class UnsubscribeAction extends Action
         $other_id = $this->arg('unsubscribeto');
 
         if (!$other_id) {
-            $this->clientError(_('No profile id in request.'));
+            $this->clientError(_('No profile ID in request.'));
             return;
         }
 
index 7f71c60dbe64441adc9515b1ab106362089ce897..e896ff96cab27dd3b6d68fb68fa3d796a15fe5bc 100644 (file)
@@ -69,7 +69,7 @@ class UserauthorizationAction extends Action
             $profile = $user->getProfile();
             if (!$profile) {
                 common_log_db_error($user, 'SELECT', __FILE__);
-                $this->serverError(_('User without matching profile'));
+                $this->serverError(_('User without matching profile.'));
                 return;
             }
 
index e03eb93566d3c96dae04c8e6526a2f5835362996..cf7d18ca88c8b9c9867ba4e1e5c90ed58b71c2b0 100644 (file)
@@ -103,7 +103,7 @@ class UserrssAction extends Rss10Action
         $profile = $user->getProfile();
         if (!$profile) {
             common_log_db_error($user, 'SELECT', __FILE__);
-            $this->serverError(_('User without matching profile'));
+            $this->serverError(_('User without matching profile.'));
             return null;
         }
         $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
index 920966b16dbb5e9862ce86d10505288f90670f75..0836c2019f82486232f196a977b25f0563318f5d 100644 (file)
@@ -330,6 +330,10 @@ class Memcached_DataObject extends Safe_DataObject
      */
     function _query($string)
     {
+        if (common_config('db', 'annotate_queries')) {
+            $string = $this->annotateQuery($string);
+        }
+
         $start = microtime(true);
         $result = parent::_query($string);
         $delta = microtime(true) - $start;
@@ -342,6 +346,70 @@ class Memcached_DataObject extends Safe_DataObject
         return $result;
     }
 
+    /**
+     * Find the first caller in the stack trace that's not a
+     * low-level database function and add a comment to the
+     * query string. This should then be visible in process lists
+     * and slow query logs, to help identify problem areas.
+     * 
+     * Also marks whether this was a web GET/POST or which daemon
+     * was running it.
+     *
+     * @param string $string SQL query string
+     * @return string SQL query string, with a comment in it
+     */
+    function annotateQuery($string)
+    {
+        $ignore = array('annotateQuery',
+                        '_query',
+                        'query',
+                        'get',
+                        'insert',
+                        'delete',
+                        'update',
+                        'find');
+        $ignoreStatic = array('staticGet',
+                              'pkeyGet',
+                              'cachedQuery');
+        $here = get_class($this); // if we get confused
+        $bt = debug_backtrace();
+
+        // Find the first caller that's not us?
+        foreach ($bt as $frame) {
+            $func = $frame['function'];
+            if (isset($frame['type']) && $frame['type'] == '::') {
+                if (in_array($func, $ignoreStatic)) {
+                    continue;
+                }
+                $here = $frame['class'] . '::' . $func;
+                break;
+            } else if (isset($frame['type']) && $frame['type'] == '->') {
+                if ($frame['object'] === $this && in_array($func, $ignore)) {
+                    continue;
+                }
+                if (in_array($func, $ignoreStatic)) {
+                    continue; // @fixme this shouldn't be needed?
+                }
+                $here = get_class($frame['object']) . '->' . $func;
+                break;
+            }
+            $here = $func;
+            break;
+        }
+
+        if (php_sapi_name() == 'cli') {
+            $context = basename($_SERVER['PHP_SELF']);
+        } else {
+            $context = $_SERVER['REQUEST_METHOD'];
+        }
+
+        // Slip the comment in after the first command,
+        // or DB_DataObject gets confused about handling inserts and such.
+        $parts = explode(' ', $string, 2);
+        $parts[0] .= " /* $context $here */";
+        return implode(' ', $parts);
+    }
+
     // Sanitize a query for logging
     // @fixme don't trim spaces in string literals
     function sanitizeQuery($string)
index b416e2ff28d28bca4b574b0c5cd70d5068f1eb2b..998e9c92bad0c29dbe9b12b0f0cfebfc475aeda1 100644 (file)
@@ -699,6 +699,27 @@ class Notice extends Memcached_DataObject
         return $ids;
     }
 
+    /**
+     * Is this notice part of an active conversation?
+     * 
+     * @return boolean true if other messages exist in the same
+     *                 conversation, false if this is the only one
+     */
+    function hasConversation()
+    {
+        if (!empty($this->conversation)) {
+            $conversation = Notice::conversationStream(
+                $this->conversation,
+                1,
+                1
+            );
+            if ($conversation->N > 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * @param $groups array of Group *objects*
      * @param $recipients array of profile *ids*
diff --git a/extlib/Net/IDNA.php b/extlib/Net/IDNA.php
new file mode 100644 (file)
index 0000000..987a37e
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+
+// {{{ license
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+//
+// +----------------------------------------------------------------------+
+// | This library is free software; you can redistribute it and/or modify |
+// | it under the terms of the GNU Lesser General Public License as       |
+// | published by the Free Software Foundation; either version 2.1 of the |
+// | License, or (at your option) any later version.                      |
+// |                                                                      |
+// | This library 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    |
+// | Lesser General Public License for more details.                      |
+// |                                                                      |
+// | You should have received a copy of the GNU Lesser General Public     |
+// | License along with this library; if not, write to the Free Software  |
+// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 |
+// | USA.                                                                 |
+// +----------------------------------------------------------------------+
+//
+
+// }}}
+
+
+/**
+ * Encode/decode Internationalized Domain Names.
+ * Factory class to get correct implementation either for php4 or php5.
+ *
+ * @author  Markus Nix <mnix@docuverse.de>
+ * @author  Matthias Sommerfeld <mso@phlylabs.de>
+ * @package Net
+ * @version $Id: IDNA.php 284681 2009-07-24 04:24:27Z clockwerx $
+ */
+
+class Net_IDNA
+{
+    // {{{ factory
+    /**
+     * Attempts to return a concrete IDNA instance for either php4 or php5.
+     *
+     * @param  array  $params   Set of paramaters
+     * @return object IDNA      The newly created concrete Log instance, or an
+     *                          false on an error.
+     * @access public
+     */
+    function getInstance($params = array())
+    {
+        $version   = explode( '.', phpversion() );
+        $handler   = ((int)$version[0] > 4) ? 'php5' : 'php4';
+        $class     = 'Net_IDNA_' . $handler;
+        $classfile = 'Net/IDNA/' . $handler . '.php';
+
+        /*
+         * Attempt to include our version of the named class, but don't treat
+         * a failure as fatal.  The caller may have already included their own
+         * version of the named class.
+         */
+        @include_once $classfile;
+
+        /* If the class exists, return a new instance of it. */
+        if (class_exists($class)) {
+            return new $class($params);
+        }
+
+        return false;
+    }
+    // }}}
+
+    // {{{ singleton
+    /**
+     * Attempts to return a concrete IDNA instance for either php4 or php5,
+     * only creating a new instance if no IDNA instance with the same
+     * parameters currently exists.
+     *
+     * @param  array  $params   Set of paramaters
+     * @return object IDNA      The newly created concrete Log instance, or an
+     *                          false on an error.
+     * @access public
+     */
+    function singleton($params = array())
+    {
+        static $instances;
+        if (!isset($instances)) {
+            $instances = array();
+        }
+
+        $signature = serialize($params);
+        if (!isset($instances[$signature])) {
+            $instances[$signature] = Net_IDNA::getInstance($params);
+        }
+
+        return $instances[$signature];
+    }
+    // }}}
+}
+
+?>
diff --git a/extlib/Net/IDNA/php5.php b/extlib/Net/IDNA/php5.php
new file mode 100644 (file)
index 0000000..d617721
--- /dev/null
@@ -0,0 +1,3269 @@
+<?php
+
+// {{{ license
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+//
+// +----------------------------------------------------------------------+
+// | This library is free software; you can redistribute it and/or modify |
+// | it under the terms of the GNU Lesser General Public License as       |
+// | published by the Free Software Foundation; either version 2.1 of the |
+// | License, or (at your option) any later version.                      |
+// |                                                                      |
+// | This library 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    |
+// | Lesser General Public License for more details.                      |
+// |                                                                      |
+// | You should have received a copy of the GNU Lesser General Public     |
+// | License along with this library; if not, write to the Free Software  |
+// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 |
+// | USA.                                                                 |
+// +----------------------------------------------------------------------+
+//
+
+// }}}
+
+
+/**
+ * Encode/decode Internationalized Domain Names.
+ *
+ * The class allows to convert internationalized domain names
+ * (see RFC 3490 for details) as they can be used with various registries worldwide
+ * to be translated between their original (localized) form and their encoded form
+ * as it will be used in the DNS (Domain Name System).
+ *
+ * The class provides two public methods, encode() and decode(), which do exactly
+ * what you would expect them to do. You are allowed to use complete domain names,
+ * simple strings and complete email addresses as well. That means, that you might
+ * use any of the following notations:
+ *
+ * - www.n�rgler.com
+ * - xn--nrgler-wxa
+ * - xn--brse-5qa.xn--knrz-1ra.info
+ *
+ * Unicode input might be given as either UTF-8 string, UCS-4 string or UCS-4
+ * array. Unicode output is available in the same formats.
+ * You can select your preferred format via {@link set_paramter()}.
+ *
+ * ACE input and output is always expected to be ASCII.
+ *
+ * @author  Markus Nix <mnix@docuverse.de>
+ * @author  Matthias Sommerfeld <mso@phlylabs.de>
+ * @author  Stefan Neufeind <pear.neufeind@speedpartner.de>
+ * @package Net
+ * @version $Id: php5.php 284682 2009-07-24 04:27:35Z clockwerx $
+ */
+
+class Net_IDNA_php5
+{
+    // {{{ npdata
+    /**
+     * These Unicode codepoints are
+     * mapped to nothing, See RFC3454 for details
+     *
+     * @static
+     * @var array
+     * @access private
+     */
+    private static $_np_map_nothing = array(
+        0xAD,
+        0x34F,
+        0x1806,
+        0x180B,
+        0x180C,
+        0x180D,
+        0x200B,
+        0x200C,
+        0x200D,
+        0x2060,
+        0xFE00,
+        0xFE01,
+        0xFE02,
+        0xFE03,
+        0xFE04,
+        0xFE05,
+        0xFE06,
+        0xFE07,
+        0xFE08,
+        0xFE09,
+        0xFE0A,
+        0xFE0B,
+        0xFE0C,
+        0xFE0D,
+        0xFE0E,
+        0xFE0F,
+        0xFEFF
+    );
+
+    /**
+     * Prohibited codepints
+     *
+     * @static
+     * @var array
+     * @access private
+     */
+    private static $_general_prohibited = array(
+        0,
+        1,
+        2,
+        3,
+        4,
+        5,
+        6,
+        7,
+        8,
+        9,
+        0xA,
+        0xB,
+        0xC,
+        0xD,
+        0xE,
+        0xF,
+        0x10,
+        0x11,
+        0x12,
+        0x13,
+        0x14,
+        0x15,
+        0x16,
+        0x17,
+        0x18,
+        0x19,
+        0x1A,
+        0x1B,
+        0x1C,
+        0x1D,
+        0x1E,
+        0x1F,
+        0x20,
+        0x21,
+        0x22,
+        0x23,
+        0x24,
+        0x25,
+        0x26,
+        0x27,
+        0x28,
+        0x29,
+        0x2A,
+        0x2B,
+        0x2C,
+        0x2F,
+        0x3B,
+        0x3C,
+        0x3D,
+        0x3E,
+        0x3F,
+        0x40,
+        0x5B,
+        0x5C,
+        0x5D,
+        0x5E,
+        0x5F,
+        0x60,
+        0x7B,
+        0x7C,
+        0x7D,
+        0x7E,
+        0x7F,
+        0x3002
+    );
+
+    /**
+     * Codepints prohibited by Nameprep
+     * @static
+     * @var array
+     * @access private
+     */
+    private static $_np_prohibit = array(
+        0xA0,
+        0x1680,
+        0x2000,
+        0x2001,
+        0x2002,
+        0x2003,
+        0x2004,
+        0x2005,
+        0x2006,
+        0x2007,
+        0x2008,
+        0x2009,
+        0x200A,
+        0x200B,
+        0x202F,
+        0x205F,
+        0x3000,
+        0x6DD,
+        0x70F,
+        0x180E,
+        0x200C,
+        0x200D,
+        0x2028,
+        0x2029,
+        0xFEFF,
+        0xFFF9,
+        0xFFFA,
+        0xFFFB,
+        0xFFFC,
+        0xFFFE,
+        0xFFFF,
+        0x1FFFE,
+        0x1FFFF,
+        0x2FFFE,
+        0x2FFFF,
+        0x3FFFE,
+        0x3FFFF,
+        0x4FFFE,
+        0x4FFFF,
+        0x5FFFE,
+        0x5FFFF,
+        0x6FFFE,
+        0x6FFFF,
+        0x7FFFE,
+        0x7FFFF,
+        0x8FFFE,
+        0x8FFFF,
+        0x9FFFE,
+        0x9FFFF,
+        0xAFFFE,
+        0xAFFFF,
+        0xBFFFE,
+        0xBFFFF,
+        0xCFFFE,
+        0xCFFFF,
+        0xDFFFE,
+        0xDFFFF,
+        0xEFFFE,
+        0xEFFFF,
+        0xFFFFE,
+        0xFFFFF,
+        0x10FFFE,
+        0x10FFFF,
+        0xFFF9,
+        0xFFFA,
+        0xFFFB,
+        0xFFFC,
+        0xFFFD,
+        0x340,
+        0x341,
+        0x200E,
+        0x200F,
+        0x202A,
+        0x202B,
+        0x202C,
+        0x202D,
+        0x202E,
+        0x206A,
+        0x206B,
+        0x206C,
+        0x206D,
+        0x206E,
+        0x206F,
+        0xE0001
+    );
+
+    /**
+     * Codepoint ranges prohibited by nameprep
+     *
+     * @static
+     * @var array
+     * @access private
+     */
+    private static $_np_prohibit_ranges = array(
+        array(0x80,     0x9F    ),
+        array(0x2060,   0x206F  ),
+        array(0x1D173,  0x1D17A ),
+        array(0xE000,   0xF8FF  ),
+        array(0xF0000,  0xFFFFD ),
+        array(0x100000, 0x10FFFD),
+        array(0xFDD0,   0xFDEF  ),
+        array(0xD800,   0xDFFF  ),
+        array(0x2FF0,   0x2FFB  ),
+        array(0xE0020,  0xE007F )
+    );
+
+    /**
+     * Replacement mappings (casemapping, replacement sequences, ...)
+     *
+     * @static
+     * @var array
+     * @access private
+     */
+    private static $_np_replacemaps = array(
+        0x41    => array(0x61),
+        0x42    => array(0x62),
+        0x43    => array(0x63),
+        0x44    => array(0x64),
+        0x45    => array(0x65),
+        0x46    => array(0x66),
+        0x47    => array(0x67),
+        0x48    => array(0x68),
+        0x49    => array(0x69),
+        0x4A    => array(0x6A),
+        0x4B    => array(0x6B),
+        0x4C    => array(0x6C),
+        0x4D    => array(0x6D),
+        0x4E    => array(0x6E),
+        0x4F    => array(0x6F),
+        0x50    => array(0x70),
+        0x51    => array(0x71),
+        0x52    => array(0x72),
+        0x53    => array(0x73),
+        0x54    => array(0x74),
+        0x55    => array(0x75),
+        0x56    => array(0x76),
+        0x57    => array(0x77),
+        0x58    => array(0x78),
+        0x59    => array(0x79),
+        0x5A    => array(0x7A),
+        0xB5    => array(0x3BC),
+        0xC0    => array(0xE0),
+        0xC1    => array(0xE1),
+        0xC2    => array(0xE2),
+        0xC3    => array(0xE3),
+        0xC4    => array(0xE4),
+        0xC5    => array(0xE5),
+        0xC6    => array(0xE6),
+        0xC7    => array(0xE7),
+        0xC8    => array(0xE8),
+        0xC9    => array(0xE9),
+        0xCA    => array(0xEA),
+        0xCB    => array(0xEB),
+        0xCC    => array(0xEC),
+        0xCD    => array(0xED),
+        0xCE    => array(0xEE),
+        0xCF    => array(0xEF),
+        0xD0    => array(0xF0),
+        0xD1    => array(0xF1),
+        0xD2    => array(0xF2),
+        0xD3    => array(0xF3),
+        0xD4    => array(0xF4),
+        0xD5    => array(0xF5),
+        0xD6    => array(0xF6),
+        0xD8    => array(0xF8),
+        0xD9    => array(0xF9),
+        0xDA    => array(0xFA),
+        0xDB    => array(0xFB),
+        0xDC    => array(0xFC),
+        0xDD    => array(0xFD),
+        0xDE    => array(0xFE),
+        0xDF    => array(0x73, 0x73),
+        0x100   => array(0x101),
+        0x102   => array(0x103),
+        0x104   => array(0x105),
+        0x106   => array(0x107),
+        0x108   => array(0x109),
+        0x10A   => array(0x10B),
+        0x10C   => array(0x10D),
+        0x10E   => array(0x10F),
+        0x110   => array(0x111),
+        0x112   => array(0x113),
+        0x114   => array(0x115),
+        0x116   => array(0x117),
+        0x118   => array(0x119),
+        0x11A   => array(0x11B),
+        0x11C   => array(0x11D),
+        0x11E   => array(0x11F),
+        0x120   => array(0x121),
+        0x122   => array(0x123),
+        0x124   => array(0x125),
+        0x126   => array(0x127),
+        0x128   => array(0x129),
+        0x12A   => array(0x12B),
+        0x12C   => array(0x12D),
+        0x12E   => array(0x12F),
+        0x130   => array(0x69, 0x307),
+        0x132   => array(0x133),
+        0x134   => array(0x135),
+        0x136   => array(0x137),
+        0x139   => array(0x13A),
+        0x13B   => array(0x13C),
+        0x13D   => array(0x13E),
+        0x13F   => array(0x140),
+        0x141   => array(0x142),
+        0x143   => array(0x144),
+        0x145   => array(0x146),
+        0x147   => array(0x148),
+        0x149   => array(0x2BC, 0x6E),
+        0x14A   => array(0x14B),
+        0x14C   => array(0x14D),
+        0x14E   => array(0x14F),
+        0x150   => array(0x151),
+        0x152   => array(0x153),
+        0x154   => array(0x155),
+        0x156   => array(0x157),
+        0x158   => array(0x159),
+        0x15A   => array(0x15B),
+        0x15C   => array(0x15D),
+        0x15E   => array(0x15F),
+        0x160   => array(0x161),
+        0x162   => array(0x163),
+        0x164   => array(0x165),
+        0x166   => array(0x167),
+        0x168   => array(0x169),
+        0x16A   => array(0x16B),
+        0x16C   => array(0x16D),
+        0x16E   => array(0x16F),
+        0x170   => array(0x171),
+        0x172   => array(0x173),
+        0x174   => array(0x175),
+        0x176   => array(0x177),
+        0x178   => array(0xFF),
+        0x179   => array(0x17A),
+        0x17B   => array(0x17C),
+        0x17D   => array(0x17E),
+        0x17F   => array(0x73),
+        0x181   => array(0x253),
+        0x182   => array(0x183),
+        0x184   => array(0x185),
+        0x186   => array(0x254),
+        0x187   => array(0x188),
+        0x189   => array(0x256),
+        0x18A   => array(0x257),
+        0x18B   => array(0x18C),
+        0x18E   => array(0x1DD),
+        0x18F   => array(0x259),
+        0x190   => array(0x25B),
+        0x191   => array(0x192),
+        0x193   => array(0x260),
+        0x194   => array(0x263),
+        0x196   => array(0x269),
+        0x197   => array(0x268),
+        0x198   => array(0x199),
+        0x19C   => array(0x26F),
+        0x19D   => array(0x272),
+        0x19F   => array(0x275),
+        0x1A0   => array(0x1A1),
+        0x1A2   => array(0x1A3),
+        0x1A4   => array(0x1A5),
+        0x1A6   => array(0x280),
+        0x1A7   => array(0x1A8),
+        0x1A9   => array(0x283),
+        0x1AC   => array(0x1AD),
+        0x1AE   => array(0x288),
+        0x1AF   => array(0x1B0),
+        0x1B1   => array(0x28A),
+        0x1B2   => array(0x28B),
+        0x1B3   => array(0x1B4),
+        0x1B5   => array(0x1B6),
+        0x1B7   => array(0x292),
+        0x1B8   => array(0x1B9),
+        0x1BC   => array(0x1BD),
+        0x1C4   => array(0x1C6),
+        0x1C5   => array(0x1C6),
+        0x1C7   => array(0x1C9),
+        0x1C8   => array(0x1C9),
+        0x1CA   => array(0x1CC),
+        0x1CB   => array(0x1CC),
+        0x1CD   => array(0x1CE),
+        0x1CF   => array(0x1D0),
+        0x1D1   => array(0x1D2),
+        0x1D3   => array(0x1D4),
+        0x1D5   => array(0x1D6),
+        0x1D7   => array(0x1D8),
+        0x1D9   => array(0x1DA),
+        0x1DB   => array(0x1DC),
+        0x1DE   => array(0x1DF),
+        0x1E0   => array(0x1E1),
+        0x1E2   => array(0x1E3),
+        0x1E4   => array(0x1E5),
+        0x1E6   => array(0x1E7),
+        0x1E8   => array(0x1E9),
+        0x1EA   => array(0x1EB),
+        0x1EC   => array(0x1ED),
+        0x1EE   => array(0x1EF),
+        0x1F0   => array(0x6A, 0x30C),
+        0x1F1   => array(0x1F3),
+        0x1F2   => array(0x1F3),
+        0x1F4   => array(0x1F5),
+        0x1F6   => array(0x195),
+        0x1F7   => array(0x1BF),
+        0x1F8   => array(0x1F9),
+        0x1FA   => array(0x1FB),
+        0x1FC   => array(0x1FD),
+        0x1FE   => array(0x1FF),
+        0x200   => array(0x201),
+        0x202   => array(0x203),
+        0x204   => array(0x205),
+        0x206   => array(0x207),
+        0x208   => array(0x209),
+        0x20A   => array(0x20B),
+        0x20C   => array(0x20D),
+        0x20E   => array(0x20F),
+        0x210   => array(0x211),
+        0x212   => array(0x213),
+        0x214   => array(0x215),
+        0x216   => array(0x217),
+        0x218   => array(0x219),
+        0x21A   => array(0x21B),
+        0x21C   => array(0x21D),
+        0x21E   => array(0x21F),
+        0x220   => array(0x19E),
+        0x222   => array(0x223),
+        0x224   => array(0x225),
+        0x226   => array(0x227),
+        0x228   => array(0x229),
+        0x22A   => array(0x22B),
+        0x22C   => array(0x22D),
+        0x22E   => array(0x22F),
+        0x230   => array(0x231),
+        0x232   => array(0x233),
+        0x345   => array(0x3B9),
+        0x37A   => array(0x20, 0x3B9),
+        0x386   => array(0x3AC),
+        0x388   => array(0x3AD),
+        0x389   => array(0x3AE),
+        0x38A   => array(0x3AF),
+        0x38C   => array(0x3CC),
+        0x38E   => array(0x3CD),
+        0x38F   => array(0x3CE),
+        0x390   => array(0x3B9, 0x308, 0x301),
+        0x391   => array(0x3B1),
+        0x392   => array(0x3B2),
+        0x393   => array(0x3B3),
+        0x394   => array(0x3B4),
+        0x395   => array(0x3B5),
+        0x396   => array(0x3B6),
+        0x397   => array(0x3B7),
+        0x398   => array(0x3B8),
+        0x399   => array(0x3B9),
+        0x39A   => array(0x3BA),
+        0x39B   => array(0x3BB),
+        0x39C   => array(0x3BC),
+        0x39D   => array(0x3BD),
+        0x39E   => array(0x3BE),
+        0x39F   => array(0x3BF),
+        0x3A0   => array(0x3C0),
+        0x3A1   => array(0x3C1),
+        0x3A3   => array(0x3C3),
+        0x3A4   => array(0x3C4),
+        0x3A5   => array(0x3C5),
+        0x3A6   => array(0x3C6),
+        0x3A7   => array(0x3C7),
+        0x3A8   => array(0x3C8),
+        0x3A9   => array(0x3C9),
+        0x3AA   => array(0x3CA),
+        0x3AB   => array(0x3CB),
+        0x3B0   => array(0x3C5, 0x308, 0x301),
+        0x3C2   => array(0x3C3),
+        0x3D0   => array(0x3B2),
+        0x3D1   => array(0x3B8),
+        0x3D2   => array(0x3C5),
+        0x3D3   => array(0x3CD),
+        0x3D4   => array(0x3CB),
+        0x3D5   => array(0x3C6),
+        0x3D6   => array(0x3C0),
+        0x3D8   => array(0x3D9),
+        0x3DA   => array(0x3DB),
+        0x3DC   => array(0x3DD),
+        0x3DE   => array(0x3DF),
+        0x3E0   => array(0x3E1),
+        0x3E2   => array(0x3E3),
+        0x3E4   => array(0x3E5),
+        0x3E6   => array(0x3E7),
+        0x3E8   => array(0x3E9),
+        0x3EA   => array(0x3EB),
+        0x3EC   => array(0x3ED),
+        0x3EE   => array(0x3EF),
+        0x3F0   => array(0x3BA),
+        0x3F1   => array(0x3C1),
+        0x3F2   => array(0x3C3),
+        0x3F4   => array(0x3B8),
+        0x3F5   => array(0x3B5),
+        0x400   => array(0x450),
+        0x401   => array(0x451),
+        0x402   => array(0x452),
+        0x403   => array(0x453),
+        0x404   => array(0x454),
+        0x405   => array(0x455),
+        0x406   => array(0x456),
+        0x407   => array(0x457),
+        0x408   => array(0x458),
+        0x409   => array(0x459),
+        0x40A   => array(0x45A),
+        0x40B   => array(0x45B),
+        0x40C   => array(0x45C),
+        0x40D   => array(0x45D),
+        0x40E   => array(0x45E),
+        0x40F   => array(0x45F),
+        0x410   => array(0x430),
+        0x411   => array(0x431),
+        0x412   => array(0x432),
+        0x413   => array(0x433),
+        0x414   => array(0x434),
+        0x415   => array(0x435),
+        0x416   => array(0x436),
+        0x417   => array(0x437),
+        0x418   => array(0x438),
+        0x419   => array(0x439),
+        0x41A   => array(0x43A),
+        0x41B   => array(0x43B),
+        0x41C   => array(0x43C),
+        0x41D   => array(0x43D),
+        0x41E   => array(0x43E),
+        0x41F   => array(0x43F),
+        0x420   => array(0x440),
+        0x421   => array(0x441),
+        0x422   => array(0x442),
+        0x423   => array(0x443),
+        0x424   => array(0x444),
+        0x425   => array(0x445),
+        0x426   => array(0x446),
+        0x427   => array(0x447),
+        0x428   => array(0x448),
+        0x429   => array(0x449),
+        0x42A   => array(0x44A),
+        0x42B   => array(0x44B),
+        0x42C   => array(0x44C),
+        0x42D   => array(0x44D),
+        0x42E   => array(0x44E),
+        0x42F   => array(0x44F),
+        0x460   => array(0x461),
+        0x462   => array(0x463),
+        0x464   => array(0x465),
+        0x466   => array(0x467),
+        0x468   => array(0x469),
+        0x46A   => array(0x46B),
+        0x46C   => array(0x46D),
+        0x46E   => array(0x46F),
+        0x470   => array(0x471),
+        0x472   => array(0x473),
+        0x474   => array(0x475),
+        0x476   => array(0x477),
+        0x478   => array(0x479),
+        0x47A   => array(0x47B),
+        0x47C   => array(0x47D),
+        0x47E   => array(0x47F),
+        0x480   => array(0x481),
+        0x48A   => array(0x48B),
+        0x48C   => array(0x48D),
+        0x48E   => array(0x48F),
+        0x490   => array(0x491),
+        0x492   => array(0x493),
+        0x494   => array(0x495),
+        0x496   => array(0x497),
+        0x498   => array(0x499),
+        0x49A   => array(0x49B),
+        0x49C   => array(0x49D),
+        0x49E   => array(0x49F),
+        0x4A0   => array(0x4A1),
+        0x4A2   => array(0x4A3),
+        0x4A4   => array(0x4A5),
+        0x4A6   => array(0x4A7),
+        0x4A8   => array(0x4A9),
+        0x4AA   => array(0x4AB),
+        0x4AC   => array(0x4AD),
+        0x4AE   => array(0x4AF),
+        0x4B0   => array(0x4B1),
+        0x4B2   => array(0x4B3),
+        0x4B4   => array(0x4B5),
+        0x4B6   => array(0x4B7),
+        0x4B8   => array(0x4B9),
+        0x4BA   => array(0x4BB),
+        0x4BC   => array(0x4BD),
+        0x4BE   => array(0x4BF),
+        0x4C1   => array(0x4C2),
+        0x4C3   => array(0x4C4),
+        0x4C5   => array(0x4C6),
+        0x4C7   => array(0x4C8),
+        0x4C9   => array(0x4CA),
+        0x4CB   => array(0x4CC),
+        0x4CD   => array(0x4CE),
+        0x4D0   => array(0x4D1),
+        0x4D2   => array(0x4D3),
+        0x4D4   => array(0x4D5),
+        0x4D6   => array(0x4D7),
+        0x4D8   => array(0x4D9),
+        0x4DA   => array(0x4DB),
+        0x4DC   => array(0x4DD),
+        0x4DE   => array(0x4DF),
+        0x4E0   => array(0x4E1),
+        0x4E2   => array(0x4E3),
+        0x4E4   => array(0x4E5),
+        0x4E6   => array(0x4E7),
+        0x4E8   => array(0x4E9),
+        0x4EA   => array(0x4EB),
+        0x4EC   => array(0x4ED),
+        0x4EE   => array(0x4EF),
+        0x4F0   => array(0x4F1),
+        0x4F2   => array(0x4F3),
+        0x4F4   => array(0x4F5),
+        0x4F8   => array(0x4F9),
+        0x500   => array(0x501),
+        0x502   => array(0x503),
+        0x504   => array(0x505),
+        0x506   => array(0x507),
+        0x508   => array(0x509),
+        0x50A   => array(0x50B),
+        0x50C   => array(0x50D),
+        0x50E   => array(0x50F),
+        0x531   => array(0x561),
+        0x532   => array(0x562),
+        0x533   => array(0x563),
+        0x534   => array(0x564),
+        0x535   => array(0x565),
+        0x536   => array(0x566),
+        0x537   => array(0x567),
+        0x538   => array(0x568),
+        0x539   => array(0x569),
+        0x53A   => array(0x56A),
+        0x53B   => array(0x56B),
+        0x53C   => array(0x56C),
+        0x53D   => array(0x56D),
+        0x53E   => array(0x56E),
+        0x53F   => array(0x56F),
+        0x540   => array(0x570),
+        0x541   => array(0x571),
+        0x542   => array(0x572),
+        0x543   => array(0x573),
+        0x544   => array(0x574),
+        0x545   => array(0x575),
+        0x546   => array(0x576),
+        0x547   => array(0x577),
+        0x548   => array(0x578),
+        0x549   => array(0x579),
+        0x54A   => array(0x57A),
+        0x54B   => array(0x57B),
+        0x54C   => array(0x57C),
+        0x54D   => array(0x57D),
+        0x54E   => array(0x57E),
+        0x54F   => array(0x57F),
+        0x550   => array(0x580),
+        0x551   => array(0x581),
+        0x552   => array(0x582),
+        0x553   => array(0x583),
+        0x554   => array(0x584),
+        0x555   => array(0x585),
+        0x556   => array(0x586),
+        0x587   => array(0x565, 0x582),
+        0x1E00  => array(0x1E01),
+        0x1E02  => array(0x1E03),
+        0x1E04  => array(0x1E05),
+        0x1E06  => array(0x1E07),
+        0x1E08  => array(0x1E09),
+        0x1E0A  => array(0x1E0B),
+        0x1E0C  => array(0x1E0D),
+        0x1E0E  => array(0x1E0F),
+        0x1E10  => array(0x1E11),
+        0x1E12  => array(0x1E13),
+        0x1E14  => array(0x1E15),
+        0x1E16  => array(0x1E17),
+        0x1E18  => array(0x1E19),
+        0x1E1A  => array(0x1E1B),
+        0x1E1C  => array(0x1E1D),
+        0x1E1E  => array(0x1E1F),
+        0x1E20  => array(0x1E21),
+        0x1E22  => array(0x1E23),
+        0x1E24  => array(0x1E25),
+        0x1E26  => array(0x1E27),
+        0x1E28  => array(0x1E29),
+        0x1E2A  => array(0x1E2B),
+        0x1E2C  => array(0x1E2D),
+        0x1E2E  => array(0x1E2F),
+        0x1E30  => array(0x1E31),
+        0x1E32  => array(0x1E33),
+        0x1E34  => array(0x1E35),
+        0x1E36  => array(0x1E37),
+        0x1E38  => array(0x1E39),
+        0x1E3A  => array(0x1E3B),
+        0x1E3C  => array(0x1E3D),
+        0x1E3E  => array(0x1E3F),
+        0x1E40  => array(0x1E41),
+        0x1E42  => array(0x1E43),
+        0x1E44  => array(0x1E45),
+        0x1E46  => array(0x1E47),
+        0x1E48  => array(0x1E49),
+        0x1E4A  => array(0x1E4B),
+        0x1E4C  => array(0x1E4D),
+        0x1E4E  => array(0x1E4F),
+        0x1E50  => array(0x1E51),
+        0x1E52  => array(0x1E53),
+        0x1E54  => array(0x1E55),
+        0x1E56  => array(0x1E57),
+        0x1E58  => array(0x1E59),
+        0x1E5A  => array(0x1E5B),
+        0x1E5C  => array(0x1E5D),
+        0x1E5E  => array(0x1E5F),
+        0x1E60  => array(0x1E61),
+        0x1E62  => array(0x1E63),
+        0x1E64  => array(0x1E65),
+        0x1E66  => array(0x1E67),
+        0x1E68  => array(0x1E69),
+        0x1E6A  => array(0x1E6B),
+        0x1E6C  => array(0x1E6D),
+        0x1E6E  => array(0x1E6F),
+        0x1E70  => array(0x1E71),
+        0x1E72  => array(0x1E73),
+        0x1E74  => array(0x1E75),
+        0x1E76  => array(0x1E77),
+        0x1E78  => array(0x1E79),
+        0x1E7A  => array(0x1E7B),
+        0x1E7C  => array(0x1E7D),
+        0x1E7E  => array(0x1E7F),
+        0x1E80  => array(0x1E81),
+        0x1E82  => array(0x1E83),
+        0x1E84  => array(0x1E85),
+        0x1E86  => array(0x1E87),
+        0x1E88  => array(0x1E89),
+        0x1E8A  => array(0x1E8B),
+        0x1E8C  => array(0x1E8D),
+        0x1E8E  => array(0x1E8F),
+        0x1E90  => array(0x1E91),
+        0x1E92  => array(0x1E93),
+        0x1E94  => array(0x1E95),
+        0x1E96  => array(0x68, 0x331),
+        0x1E97  => array(0x74, 0x308),
+        0x1E98  => array(0x77, 0x30A),
+        0x1E99  => array(0x79, 0x30A),
+        0x1E9A  => array(0x61, 0x2BE),
+        0x1E9B  => array(0x1E61),
+        0x1EA0  => array(0x1EA1),
+        0x1EA2  => array(0x1EA3),
+        0x1EA4  => array(0x1EA5),
+        0x1EA6  => array(0x1EA7),
+        0x1EA8  => array(0x1EA9),
+        0x1EAA  => array(0x1EAB),
+        0x1EAC  => array(0x1EAD),
+        0x1EAE  => array(0x1EAF),
+        0x1EB0  => array(0x1EB1),
+        0x1EB2  => array(0x1EB3),
+        0x1EB4  => array(0x1EB5),
+        0x1EB6  => array(0x1EB7),
+        0x1EB8  => array(0x1EB9),
+        0x1EBA  => array(0x1EBB),
+        0x1EBC  => array(0x1EBD),
+        0x1EBE  => array(0x1EBF),
+        0x1EC0  => array(0x1EC1),
+        0x1EC2  => array(0x1EC3),
+        0x1EC4  => array(0x1EC5),
+        0x1EC6  => array(0x1EC7),
+        0x1EC8  => array(0x1EC9),
+        0x1ECA  => array(0x1ECB),
+        0x1ECC  => array(0x1ECD),
+        0x1ECE  => array(0x1ECF),
+        0x1ED0  => array(0x1ED1),
+        0x1ED2  => array(0x1ED3),
+        0x1ED4  => array(0x1ED5),
+        0x1ED6  => array(0x1ED7),
+        0x1ED8  => array(0x1ED9),
+        0x1EDA  => array(0x1EDB),
+        0x1EDC  => array(0x1EDD),
+        0x1EDE  => array(0x1EDF),
+        0x1EE0  => array(0x1EE1),
+        0x1EE2  => array(0x1EE3),
+        0x1EE4  => array(0x1EE5),
+        0x1EE6  => array(0x1EE7),
+        0x1EE8  => array(0x1EE9),
+        0x1EEA  => array(0x1EEB),
+        0x1EEC  => array(0x1EED),
+        0x1EEE  => array(0x1EEF),
+        0x1EF0  => array(0x1EF1),
+        0x1EF2  => array(0x1EF3),
+        0x1EF4  => array(0x1EF5),
+        0x1EF6  => array(0x1EF7),
+        0x1EF8  => array(0x1EF9),
+        0x1F08  => array(0x1F00),
+        0x1F09  => array(0x1F01),
+        0x1F0A  => array(0x1F02),
+        0x1F0B  => array(0x1F03),
+        0x1F0C  => array(0x1F04),
+        0x1F0D  => array(0x1F05),
+        0x1F0E  => array(0x1F06),
+        0x1F0F  => array(0x1F07),
+        0x1F18  => array(0x1F10),
+        0x1F19  => array(0x1F11),
+        0x1F1A  => array(0x1F12),
+        0x1F1B  => array(0x1F13),
+        0x1F1C  => array(0x1F14),
+        0x1F1D  => array(0x1F15),
+        0x1F28  => array(0x1F20),
+        0x1F29  => array(0x1F21),
+        0x1F2A  => array(0x1F22),
+        0x1F2B  => array(0x1F23),
+        0x1F2C  => array(0x1F24),
+        0x1F2D  => array(0x1F25),
+        0x1F2E  => array(0x1F26),
+        0x1F2F  => array(0x1F27),
+        0x1F38  => array(0x1F30),
+        0x1F39  => array(0x1F31),
+        0x1F3A  => array(0x1F32),
+        0x1F3B  => array(0x1F33),
+        0x1F3C  => array(0x1F34),
+        0x1F3D  => array(0x1F35),
+        0x1F3E  => array(0x1F36),
+        0x1F3F  => array(0x1F37),
+        0x1F48  => array(0x1F40),
+        0x1F49  => array(0x1F41),
+        0x1F4A  => array(0x1F42),
+        0x1F4B  => array(0x1F43),
+        0x1F4C  => array(0x1F44),
+        0x1F4D  => array(0x1F45),
+        0x1F50  => array(0x3C5, 0x313),
+        0x1F52  => array(0x3C5, 0x313, 0x300),
+        0x1F54  => array(0x3C5, 0x313, 0x301),
+        0x1F56  => array(0x3C5, 0x313, 0x342),
+        0x1F59  => array(0x1F51),
+        0x1F5B  => array(0x1F53),
+        0x1F5D  => array(0x1F55),
+        0x1F5F  => array(0x1F57),
+        0x1F68  => array(0x1F60),
+        0x1F69  => array(0x1F61),
+        0x1F6A  => array(0x1F62),
+        0x1F6B  => array(0x1F63),
+        0x1F6C  => array(0x1F64),
+        0x1F6D  => array(0x1F65),
+        0x1F6E  => array(0x1F66),
+        0x1F6F  => array(0x1F67),
+        0x1F80  => array(0x1F00, 0x3B9),
+        0x1F81  => array(0x1F01, 0x3B9),
+        0x1F82  => array(0x1F02, 0x3B9),
+        0x1F83  => array(0x1F03, 0x3B9),
+        0x1F84  => array(0x1F04, 0x3B9),
+        0x1F85  => array(0x1F05, 0x3B9),
+        0x1F86  => array(0x1F06, 0x3B9),
+        0x1F87  => array(0x1F07, 0x3B9),
+        0x1F88  => array(0x1F00, 0x3B9),
+        0x1F89  => array(0x1F01, 0x3B9),
+        0x1F8A  => array(0x1F02, 0x3B9),
+        0x1F8B  => array(0x1F03, 0x3B9),
+        0x1F8C  => array(0x1F04, 0x3B9),
+        0x1F8D  => array(0x1F05, 0x3B9),
+        0x1F8E  => array(0x1F06, 0x3B9),
+        0x1F8F  => array(0x1F07, 0x3B9),
+        0x1F90  => array(0x1F20, 0x3B9),
+        0x1F91  => array(0x1F21, 0x3B9),
+        0x1F92  => array(0x1F22, 0x3B9),
+        0x1F93  => array(0x1F23, 0x3B9),
+        0x1F94  => array(0x1F24, 0x3B9),
+        0x1F95  => array(0x1F25, 0x3B9),
+        0x1F96  => array(0x1F26, 0x3B9),
+        0x1F97  => array(0x1F27, 0x3B9),
+        0x1F98  => array(0x1F20, 0x3B9),
+        0x1F99  => array(0x1F21, 0x3B9),
+        0x1F9A  => array(0x1F22, 0x3B9),
+        0x1F9B  => array(0x1F23, 0x3B9),
+        0x1F9C  => array(0x1F24, 0x3B9),
+        0x1F9D  => array(0x1F25, 0x3B9),
+        0x1F9E  => array(0x1F26, 0x3B9),
+        0x1F9F  => array(0x1F27, 0x3B9),
+        0x1FA0  => array(0x1F60, 0x3B9),
+        0x1FA1  => array(0x1F61, 0x3B9),
+        0x1FA2  => array(0x1F62, 0x3B9),
+        0x1FA3  => array(0x1F63, 0x3B9),
+        0x1FA4  => array(0x1F64, 0x3B9),
+        0x1FA5  => array(0x1F65, 0x3B9),
+        0x1FA6  => array(0x1F66, 0x3B9),
+        0x1FA7  => array(0x1F67, 0x3B9),
+        0x1FA8  => array(0x1F60, 0x3B9),
+        0x1FA9  => array(0x1F61, 0x3B9),
+        0x1FAA  => array(0x1F62, 0x3B9),
+        0x1FAB  => array(0x1F63, 0x3B9),
+        0x1FAC  => array(0x1F64, 0x3B9),
+        0x1FAD  => array(0x1F65, 0x3B9),
+        0x1FAE  => array(0x1F66, 0x3B9),
+        0x1FAF  => array(0x1F67, 0x3B9),
+        0x1FB2  => array(0x1F70, 0x3B9),
+        0x1FB3  => array(0x3B1, 0x3B9),
+        0x1FB4  => array(0x3AC, 0x3B9),
+        0x1FB6  => array(0x3B1, 0x342),
+        0x1FB7  => array(0x3B1, 0x342, 0x3B9),
+        0x1FB8  => array(0x1FB0),
+        0x1FB9  => array(0x1FB1),
+        0x1FBA  => array(0x1F70),
+        0x1FBB  => array(0x1F71),
+        0x1FBC  => array(0x3B1, 0x3B9),
+        0x1FBE  => array(0x3B9),
+        0x1FC2  => array(0x1F74, 0x3B9),
+        0x1FC3  => array(0x3B7, 0x3B9),
+        0x1FC4  => array(0x3AE, 0x3B9),
+        0x1FC6  => array(0x3B7, 0x342),
+        0x1FC7  => array(0x3B7, 0x342, 0x3B9),
+        0x1FC8  => array(0x1F72),
+        0x1FC9  => array(0x1F73),
+        0x1FCA  => array(0x1F74),
+        0x1FCB  => array(0x1F75),
+        0x1FCC  => array(0x3B7, 0x3B9),
+        0x1FD2  => array(0x3B9, 0x308, 0x300),
+        0x1FD3  => array(0x3B9, 0x308, 0x301),
+        0x1FD6  => array(0x3B9, 0x342),
+        0x1FD7  => array(0x3B9, 0x308, 0x342),
+        0x1FD8  => array(0x1FD0),
+        0x1FD9  => array(0x1FD1),
+        0x1FDA  => array(0x1F76),
+        0x1FDB  => array(0x1F77),
+        0x1FE2  => array(0x3C5, 0x308, 0x300),
+        0x1FE3  => array(0x3C5, 0x308, 0x301),
+        0x1FE4  => array(0x3C1, 0x313),
+        0x1FE6  => array(0x3C5, 0x342),
+        0x1FE7  => array(0x3C5, 0x308, 0x342),
+        0x1FE8  => array(0x1FE0),
+        0x1FE9  => array(0x1FE1),
+        0x1FEA  => array(0x1F7A),
+        0x1FEB  => array(0x1F7B),
+        0x1FEC  => array(0x1FE5),
+        0x1FF2  => array(0x1F7C, 0x3B9),
+        0x1FF3  => array(0x3C9, 0x3B9),
+        0x1FF4  => array(0x3CE, 0x3B9),
+        0x1FF6  => array(0x3C9, 0x342),
+        0x1FF7  => array(0x3C9, 0x342, 0x3B9),
+        0x1FF8  => array(0x1F78),
+        0x1FF9  => array(0x1F79),
+        0x1FFA  => array(0x1F7C),
+        0x1FFB  => array(0x1F7D),
+        0x1FFC  => array(0x3C9, 0x3B9),
+        0x20A8  => array(0x72, 0x73),
+        0x2102  => array(0x63),
+        0x2103  => array(0xB0, 0x63),
+        0x2107  => array(0x25B),
+        0x2109  => array(0xB0, 0x66),
+        0x210B  => array(0x68),
+        0x210C  => array(0x68),
+        0x210D  => array(0x68),
+        0x2110  => array(0x69),
+        0x2111  => array(0x69),
+        0x2112  => array(0x6C),
+        0x2115  => array(0x6E),
+        0x2116  => array(0x6E, 0x6F),
+        0x2119  => array(0x70),
+        0x211A  => array(0x71),
+        0x211B  => array(0x72),
+        0x211C  => array(0x72),
+        0x211D  => array(0x72),
+        0x2120  => array(0x73, 0x6D),
+        0x2121  => array(0x74, 0x65, 0x6C),
+        0x2122  => array(0x74, 0x6D),
+        0x2124  => array(0x7A),
+        0x2126  => array(0x3C9),
+        0x2128  => array(0x7A),
+        0x212A  => array(0x6B),
+        0x212B  => array(0xE5),
+        0x212C  => array(0x62),
+        0x212D  => array(0x63),
+        0x2130  => array(0x65),
+        0x2131  => array(0x66),
+        0x2133  => array(0x6D),
+        0x213E  => array(0x3B3),
+        0x213F  => array(0x3C0),
+        0x2145  => array(0x64),
+        0x2160  => array(0x2170),
+        0x2161  => array(0x2171),
+        0x2162  => array(0x2172),
+        0x2163  => array(0x2173),
+        0x2164  => array(0x2174),
+        0x2165  => array(0x2175),
+        0x2166  => array(0x2176),
+        0x2167  => array(0x2177),
+        0x2168  => array(0x2178),
+        0x2169  => array(0x2179),
+        0x216A  => array(0x217A),
+        0x216B  => array(0x217B),
+        0x216C  => array(0x217C),
+        0x216D  => array(0x217D),
+        0x216E  => array(0x217E),
+        0x216F  => array(0x217F),
+        0x24B6  => array(0x24D0),
+        0x24B7  => array(0x24D1),
+        0x24B8  => array(0x24D2),
+        0x24B9  => array(0x24D3),
+        0x24BA  => array(0x24D4),
+        0x24BB  => array(0x24D5),
+        0x24BC  => array(0x24D6),
+        0x24BD  => array(0x24D7),
+        0x24BE  => array(0x24D8),
+        0x24BF  => array(0x24D9),
+        0x24C0  => array(0x24DA),
+        0x24C1  => array(0x24DB),
+        0x24C2  => array(0x24DC),
+        0x24C3  => array(0x24DD),
+        0x24C4  => array(0x24DE),
+        0x24C5  => array(0x24DF),
+        0x24C6  => array(0x24E0),
+        0x24C7  => array(0x24E1),
+        0x24C8  => array(0x24E2),
+        0x24C9  => array(0x24E3),
+        0x24CA  => array(0x24E4),
+        0x24CB  => array(0x24E5),
+        0x24CC  => array(0x24E6),
+        0x24CD  => array(0x24E7),
+        0x24CE  => array(0x24E8),
+        0x24CF  => array(0x24E9),
+        0x3371  => array(0x68, 0x70, 0x61),
+        0x3373  => array(0x61, 0x75),
+        0x3375  => array(0x6F, 0x76),
+        0x3380  => array(0x70, 0x61),
+        0x3381  => array(0x6E, 0x61),
+        0x3382  => array(0x3BC, 0x61),
+        0x3383  => array(0x6D, 0x61),
+        0x3384  => array(0x6B, 0x61),
+        0x3385  => array(0x6B, 0x62),
+        0x3386  => array(0x6D, 0x62),
+        0x3387  => array(0x67, 0x62),
+        0x338A  => array(0x70, 0x66),
+        0x338B  => array(0x6E, 0x66),
+        0x338C  => array(0x3BC, 0x66),
+        0x3390  => array(0x68, 0x7A),
+        0x3391  => array(0x6B, 0x68, 0x7A),
+        0x3392  => array(0x6D, 0x68, 0x7A),
+        0x3393  => array(0x67, 0x68, 0x7A),
+        0x3394  => array(0x74, 0x68, 0x7A),
+        0x33A9  => array(0x70, 0x61),
+        0x33AA  => array(0x6B, 0x70, 0x61),
+        0x33AB  => array(0x6D, 0x70, 0x61),
+        0x33AC  => array(0x67, 0x70, 0x61),
+        0x33B4  => array(0x70, 0x76),
+        0x33B5  => array(0x6E, 0x76),
+        0x33B6  => array(0x3BC, 0x76),
+        0x33B7  => array(0x6D, 0x76),
+        0x33B8  => array(0x6B, 0x76),
+        0x33B9  => array(0x6D, 0x76),
+        0x33BA  => array(0x70, 0x77),
+        0x33BB  => array(0x6E, 0x77),
+        0x33BC  => array(0x3BC, 0x77),
+        0x33BD  => array(0x6D, 0x77),
+        0x33BE  => array(0x6B, 0x77),
+        0x33BF  => array(0x6D, 0x77),
+        0x33C0  => array(0x6B, 0x3C9),
+        0x33C1  => array(0x6D, 0x3C9), /*
+        0x33C2  => array(0x61, 0x2E, 0x6D, 0x2E), */
+        0x33C3  => array(0x62, 0x71),
+        0x33C6  => array(0x63, 0x2215, 0x6B, 0x67),
+        0x33C7  => array(0x63, 0x6F, 0x2E),
+        0x33C8  => array(0x64, 0x62),
+        0x33C9  => array(0x67, 0x79),
+        0x33CB  => array(0x68, 0x70),
+        0x33CD  => array(0x6B, 0x6B),
+        0x33CE  => array(0x6B, 0x6D),
+        0x33D7  => array(0x70, 0x68),
+        0x33D9  => array(0x70, 0x70, 0x6D),
+        0x33DA  => array(0x70, 0x72),
+        0x33DC  => array(0x73, 0x76),
+        0x33DD  => array(0x77, 0x62),
+        0xFB00  => array(0x66, 0x66),
+        0xFB01  => array(0x66, 0x69),
+        0xFB02  => array(0x66, 0x6C),
+        0xFB03  => array(0x66, 0x66, 0x69),
+        0xFB04  => array(0x66, 0x66, 0x6C),
+        0xFB05  => array(0x73, 0x74),
+        0xFB06  => array(0x73, 0x74),
+        0xFB13  => array(0x574, 0x576),
+        0xFB14  => array(0x574, 0x565),
+        0xFB15  => array(0x574, 0x56B),
+        0xFB16  => array(0x57E, 0x576),
+        0xFB17  => array(0x574, 0x56D),
+        0xFF21  => array(0xFF41),
+        0xFF22  => array(0xFF42),
+        0xFF23  => array(0xFF43),
+        0xFF24  => array(0xFF44),
+        0xFF25  => array(0xFF45),
+        0xFF26  => array(0xFF46),
+        0xFF27  => array(0xFF47),
+        0xFF28  => array(0xFF48),
+        0xFF29  => array(0xFF49),
+        0xFF2A  => array(0xFF4A),
+        0xFF2B  => array(0xFF4B),
+        0xFF2C  => array(0xFF4C),
+        0xFF2D  => array(0xFF4D),
+        0xFF2E  => array(0xFF4E),
+        0xFF2F  => array(0xFF4F),
+        0xFF30  => array(0xFF50),
+        0xFF31  => array(0xFF51),
+        0xFF32  => array(0xFF52),
+        0xFF33  => array(0xFF53),
+        0xFF34  => array(0xFF54),
+        0xFF35  => array(0xFF55),
+        0xFF36  => array(0xFF56),
+        0xFF37  => array(0xFF57),
+        0xFF38  => array(0xFF58),
+        0xFF39  => array(0xFF59),
+        0xFF3A  => array(0xFF5A),
+        0x10400 => array(0x10428),
+        0x10401 => array(0x10429),
+        0x10402 => array(0x1042A),
+        0x10403 => array(0x1042B),
+        0x10404 => array(0x1042C),
+        0x10405 => array(0x1042D),
+        0x10406 => array(0x1042E),
+        0x10407 => array(0x1042F),
+        0x10408 => array(0x10430),
+        0x10409 => array(0x10431),
+        0x1040A => array(0x10432),
+        0x1040B => array(0x10433),
+        0x1040C => array(0x10434),
+        0x1040D => array(0x10435),
+        0x1040E => array(0x10436),
+        0x1040F => array(0x10437),
+        0x10410 => array(0x10438),
+        0x10411 => array(0x10439),
+        0x10412 => array(0x1043A),
+        0x10413 => array(0x1043B),
+        0x10414 => array(0x1043C),
+        0x10415 => array(0x1043D),
+        0x10416 => array(0x1043E),
+        0x10417 => array(0x1043F),
+        0x10418 => array(0x10440),
+        0x10419 => array(0x10441),
+        0x1041A => array(0x10442),
+        0x1041B => array(0x10443),
+        0x1041C => array(0x10444),
+        0x1041D => array(0x10445),
+        0x1041E => array(0x10446),
+        0x1041F => array(0x10447),
+        0x10420 => array(0x10448),
+        0x10421 => array(0x10449),
+        0x10422 => array(0x1044A),
+        0x10423 => array(0x1044B),
+        0x10424 => array(0x1044C),
+        0x10425 => array(0x1044D),
+        0x1D400 => array(0x61),
+        0x1D401 => array(0x62),
+        0x1D402 => array(0x63),
+        0x1D403 => array(0x64),
+        0x1D404 => array(0x65),
+        0x1D405 => array(0x66),
+        0x1D406 => array(0x67),
+        0x1D407 => array(0x68),
+        0x1D408 => array(0x69),
+        0x1D409 => array(0x6A),
+        0x1D40A => array(0x6B),
+        0x1D40B => array(0x6C),
+        0x1D40C => array(0x6D),
+        0x1D40D => array(0x6E),
+        0x1D40E => array(0x6F),
+        0x1D40F => array(0x70),
+        0x1D410 => array(0x71),
+        0x1D411 => array(0x72),
+        0x1D412 => array(0x73),
+        0x1D413 => array(0x74),
+        0x1D414 => array(0x75),
+        0x1D415 => array(0x76),
+        0x1D416 => array(0x77),
+        0x1D417 => array(0x78),
+        0x1D418 => array(0x79),
+        0x1D419 => array(0x7A),
+        0x1D434 => array(0x61),
+        0x1D435 => array(0x62),
+        0x1D436 => array(0x63),
+        0x1D437 => array(0x64),
+        0x1D438 => array(0x65),
+        0x1D439 => array(0x66),
+        0x1D43A => array(0x67),
+        0x1D43B => array(0x68),
+        0x1D43C => array(0x69),
+        0x1D43D => array(0x6A),
+        0x1D43E => array(0x6B),
+        0x1D43F => array(0x6C),
+        0x1D440 => array(0x6D),
+        0x1D441 => array(0x6E),
+        0x1D442 => array(0x6F),
+        0x1D443 => array(0x70),
+        0x1D444 => array(0x71),
+        0x1D445 => array(0x72),
+        0x1D446 => array(0x73),
+        0x1D447 => array(0x74),
+        0x1D448 => array(0x75),
+        0x1D449 => array(0x76),
+        0x1D44A => array(0x77),
+        0x1D44B => array(0x78),
+        0x1D44C => array(0x79),
+        0x1D44D => array(0x7A),
+        0x1D468 => array(0x61),
+        0x1D469 => array(0x62),
+        0x1D46A => array(0x63),
+        0x1D46B => array(0x64),
+        0x1D46C => array(0x65),
+        0x1D46D => array(0x66),
+        0x1D46E => array(0x67),
+        0x1D46F => array(0x68),
+        0x1D470 => array(0x69),
+        0x1D471 => array(0x6A),
+        0x1D472 => array(0x6B),
+        0x1D473 => array(0x6C),
+        0x1D474 => array(0x6D),
+        0x1D475 => array(0x6E),
+        0x1D476 => array(0x6F),
+        0x1D477 => array(0x70),
+        0x1D478 => array(0x71),
+        0x1D479 => array(0x72),
+        0x1D47A => array(0x73),
+        0x1D47B => array(0x74),
+        0x1D47C => array(0x75),
+        0x1D47D => array(0x76),
+        0x1D47E => array(0x77),
+        0x1D47F => array(0x78),
+        0x1D480 => array(0x79),
+        0x1D481 => array(0x7A),
+        0x1D49C => array(0x61),
+        0x1D49E => array(0x63),
+        0x1D49F => array(0x64),
+        0x1D4A2 => array(0x67),
+        0x1D4A5 => array(0x6A),
+        0x1D4A6 => array(0x6B),
+        0x1D4A9 => array(0x6E),
+        0x1D4AA => array(0x6F),
+        0x1D4AB => array(0x70),
+        0x1D4AC => array(0x71),
+        0x1D4AE => array(0x73),
+        0x1D4AF => array(0x74),
+        0x1D4B0 => array(0x75),
+        0x1D4B1 => array(0x76),
+        0x1D4B2 => array(0x77),
+        0x1D4B3 => array(0x78),
+        0x1D4B4 => array(0x79),
+        0x1D4B5 => array(0x7A),
+        0x1D4D0 => array(0x61),
+        0x1D4D1 => array(0x62),
+        0x1D4D2 => array(0x63),
+        0x1D4D3 => array(0x64),
+        0x1D4D4 => array(0x65),
+        0x1D4D5 => array(0x66),
+        0x1D4D6 => array(0x67),
+        0x1D4D7 => array(0x68),
+        0x1D4D8 => array(0x69),
+        0x1D4D9 => array(0x6A),
+        0x1D4DA => array(0x6B),
+        0x1D4DB => array(0x6C),
+        0x1D4DC => array(0x6D),
+        0x1D4DD => array(0x6E),
+        0x1D4DE => array(0x6F),
+        0x1D4DF => array(0x70),
+        0x1D4E0 => array(0x71),
+        0x1D4E1 => array(0x72),
+        0x1D4E2 => array(0x73),
+        0x1D4E3 => array(0x74),
+        0x1D4E4 => array(0x75),
+        0x1D4E5 => array(0x76),
+        0x1D4E6 => array(0x77),
+        0x1D4E7 => array(0x78),
+        0x1D4E8 => array(0x79),
+        0x1D4E9 => array(0x7A),
+        0x1D504 => array(0x61),
+        0x1D505 => array(0x62),
+        0x1D507 => array(0x64),
+        0x1D508 => array(0x65),
+        0x1D509 => array(0x66),
+        0x1D50A => array(0x67),
+        0x1D50D => array(0x6A),
+        0x1D50E => array(0x6B),
+        0x1D50F => array(0x6C),
+        0x1D510 => array(0x6D),
+        0x1D511 => array(0x6E),
+        0x1D512 => array(0x6F),
+        0x1D513 => array(0x70),
+        0x1D514 => array(0x71),
+        0x1D516 => array(0x73),
+        0x1D517 => array(0x74),
+        0x1D518 => array(0x75),
+        0x1D519 => array(0x76),
+        0x1D51A => array(0x77),
+        0x1D51B => array(0x78),
+        0x1D51C => array(0x79),
+        0x1D538 => array(0x61),
+        0x1D539 => array(0x62),
+        0x1D53B => array(0x64),
+        0x1D53C => array(0x65),
+        0x1D53D => array(0x66),
+        0x1D53E => array(0x67),
+        0x1D540 => array(0x69),
+        0x1D541 => array(0x6A),
+        0x1D542 => array(0x6B),
+        0x1D543 => array(0x6C),
+        0x1D544 => array(0x6D),
+        0x1D546 => array(0x6F),
+        0x1D54A => array(0x73),
+        0x1D54B => array(0x74),
+        0x1D54C => array(0x75),
+        0x1D54D => array(0x76),
+        0x1D54E => array(0x77),
+        0x1D54F => array(0x78),
+        0x1D550 => array(0x79),
+        0x1D56C => array(0x61),
+        0x1D56D => array(0x62),
+        0x1D56E => array(0x63),
+        0x1D56F => array(0x64),
+        0x1D570 => array(0x65),
+        0x1D571 => array(0x66),
+        0x1D572 => array(0x67),
+        0x1D573 => array(0x68),
+        0x1D574 => array(0x69),
+        0x1D575 => array(0x6A),
+        0x1D576 => array(0x6B),
+        0x1D577 => array(0x6C),
+        0x1D578 => array(0x6D),
+        0x1D579 => array(0x6E),
+        0x1D57A => array(0x6F),
+        0x1D57B => array(0x70),
+        0x1D57C => array(0x71),
+        0x1D57D => array(0x72),
+        0x1D57E => array(0x73),
+        0x1D57F => array(0x74),
+        0x1D580 => array(0x75),
+        0x1D581 => array(0x76),
+        0x1D582 => array(0x77),
+        0x1D583 => array(0x78),
+        0x1D584 => array(0x79),
+        0x1D585 => array(0x7A),
+        0x1D5A0 => array(0x61),
+        0x1D5A1 => array(0x62),
+        0x1D5A2 => array(0x63),
+        0x1D5A3 => array(0x64),
+        0x1D5A4 => array(0x65),
+        0x1D5A5 => array(0x66),
+        0x1D5A6 => array(0x67),
+        0x1D5A7 => array(0x68),
+        0x1D5A8 => array(0x69),
+        0x1D5A9 => array(0x6A),
+        0x1D5AA => array(0x6B),
+        0x1D5AB => array(0x6C),
+        0x1D5AC => array(0x6D),
+        0x1D5AD => array(0x6E),
+        0x1D5AE => array(0x6F),
+        0x1D5AF => array(0x70),
+        0x1D5B0 => array(0x71),
+        0x1D5B1 => array(0x72),
+        0x1D5B2 => array(0x73),
+        0x1D5B3 => array(0x74),
+        0x1D5B4 => array(0x75),
+        0x1D5B5 => array(0x76),
+        0x1D5B6 => array(0x77),
+        0x1D5B7 => array(0x78),
+        0x1D5B8 => array(0x79),
+        0x1D5B9 => array(0x7A),
+        0x1D5D4 => array(0x61),
+        0x1D5D5 => array(0x62),
+        0x1D5D6 => array(0x63),
+        0x1D5D7 => array(0x64),
+        0x1D5D8 => array(0x65),
+        0x1D5D9 => array(0x66),
+        0x1D5DA => array(0x67),
+        0x1D5DB => array(0x68),
+        0x1D5DC => array(0x69),
+        0x1D5DD => array(0x6A),
+        0x1D5DE => array(0x6B),
+        0x1D5DF => array(0x6C),
+        0x1D5E0 => array(0x6D),
+        0x1D5E1 => array(0x6E),
+        0x1D5E2 => array(0x6F),
+        0x1D5E3 => array(0x70),
+        0x1D5E4 => array(0x71),
+        0x1D5E5 => array(0x72),
+        0x1D5E6 => array(0x73),
+        0x1D5E7 => array(0x74),
+        0x1D5E8 => array(0x75),
+        0x1D5E9 => array(0x76),
+        0x1D5EA => array(0x77),
+        0x1D5EB => array(0x78),
+        0x1D5EC => array(0x79),
+        0x1D5ED => array(0x7A),
+        0x1D608 => array(0x61),
+        0x1D609 => array(0x62),
+        0x1D60A => array(0x63),
+        0x1D60B => array(0x64),
+        0x1D60C => array(0x65),
+        0x1D60D => array(0x66),
+        0x1D60E => array(0x67),
+        0x1D60F => array(0x68),
+        0x1D610 => array(0x69),
+        0x1D611 => array(0x6A),
+        0x1D612 => array(0x6B),
+        0x1D613 => array(0x6C),
+        0x1D614 => array(0x6D),
+        0x1D615 => array(0x6E),
+        0x1D616 => array(0x6F),
+        0x1D617 => array(0x70),
+        0x1D618 => array(0x71),
+        0x1D619 => array(0x72),
+        0x1D61A => array(0x73),
+        0x1D61B => array(0x74),
+        0x1D61C => array(0x75),
+        0x1D61D => array(0x76),
+        0x1D61E => array(0x77),
+        0x1D61F => array(0x78),
+        0x1D620 => array(0x79),
+        0x1D621 => array(0x7A),
+        0x1D63C => array(0x61),
+        0x1D63D => array(0x62),
+        0x1D63E => array(0x63),
+        0x1D63F => array(0x64),
+        0x1D640 => array(0x65),
+        0x1D641 => array(0x66),
+        0x1D642 => array(0x67),
+        0x1D643 => array(0x68),
+        0x1D644 => array(0x69),
+        0x1D645 => array(0x6A),
+        0x1D646 => array(0x6B),
+        0x1D647 => array(0x6C),
+        0x1D648 => array(0x6D),
+        0x1D649 => array(0x6E),
+        0x1D64A => array(0x6F),
+        0x1D64B => array(0x70),
+        0x1D64C => array(0x71),
+        0x1D64D => array(0x72),
+        0x1D64E => array(0x73),
+        0x1D64F => array(0x74),
+        0x1D650 => array(0x75),
+        0x1D651 => array(0x76),
+        0x1D652 => array(0x77),
+        0x1D653 => array(0x78),
+        0x1D654 => array(0x79),
+        0x1D655 => array(0x7A),
+        0x1D670 => array(0x61),
+        0x1D671 => array(0x62),
+        0x1D672 => array(0x63),
+        0x1D673 => array(0x64),
+        0x1D674 => array(0x65),
+        0x1D675 => array(0x66),
+        0x1D676 => array(0x67),
+        0x1D677 => array(0x68),
+        0x1D678 => array(0x69),
+        0x1D679 => array(0x6A),
+        0x1D67A => array(0x6B),
+        0x1D67B => array(0x6C),
+        0x1D67C => array(0x6D),
+        0x1D67D => array(0x6E),
+        0x1D67E => array(0x6F),
+        0x1D67F => array(0x70),
+        0x1D680 => array(0x71),
+        0x1D681 => array(0x72),
+        0x1D682 => array(0x73),
+        0x1D683 => array(0x74),
+        0x1D684 => array(0x75),
+        0x1D685 => array(0x76),
+        0x1D686 => array(0x77),
+        0x1D687 => array(0x78),
+        0x1D688 => array(0x79),
+        0x1D689 => array(0x7A),
+        0x1D6A8 => array(0x3B1),
+        0x1D6A9 => array(0x3B2),
+        0x1D6AA => array(0x3B3),
+        0x1D6AB => array(0x3B4),
+        0x1D6AC => array(0x3B5),
+        0x1D6AD => array(0x3B6),
+        0x1D6AE => array(0x3B7),
+        0x1D6AF => array(0x3B8),
+        0x1D6B0 => array(0x3B9),
+        0x1D6B1 => array(0x3BA),
+        0x1D6B2 => array(0x3BB),
+        0x1D6B3 => array(0x3BC),
+        0x1D6B4 => array(0x3BD),
+        0x1D6B5 => array(0x3BE),
+        0x1D6B6 => array(0x3BF),
+        0x1D6B7 => array(0x3C0),
+        0x1D6B8 => array(0x3C1),
+        0x1D6B9 => array(0x3B8),
+        0x1D6BA => array(0x3C3),
+        0x1D6BB => array(0x3C4),
+        0x1D6BC => array(0x3C5),
+        0x1D6BD => array(0x3C6),
+        0x1D6BE => array(0x3C7),
+        0x1D6BF => array(0x3C8),
+        0x1D6C0 => array(0x3C9),
+        0x1D6D3 => array(0x3C3),
+        0x1D6E2 => array(0x3B1),
+        0x1D6E3 => array(0x3B2),
+        0x1D6E4 => array(0x3B3),
+        0x1D6E5 => array(0x3B4),
+        0x1D6E6 => array(0x3B5),
+        0x1D6E7 => array(0x3B6),
+        0x1D6E8 => array(0x3B7),
+        0x1D6E9 => array(0x3B8),
+        0x1D6EA => array(0x3B9),
+        0x1D6EB => array(0x3BA),
+        0x1D6EC => array(0x3BB),
+        0x1D6ED => array(0x3BC),
+        0x1D6EE => array(0x3BD),
+        0x1D6EF => array(0x3BE),
+        0x1D6F0 => array(0x3BF),
+        0x1D6F1 => array(0x3C0),
+        0x1D6F2 => array(0x3C1),
+        0x1D6F3 => array(0x3B8),
+        0x1D6F4 => array(0x3C3),
+        0x1D6F5 => array(0x3C4),
+        0x1D6F6 => array(0x3C5),
+        0x1D6F7 => array(0x3C6),
+        0x1D6F8 => array(0x3C7),
+        0x1D6F9 => array(0x3C8),
+        0x1D6FA => array(0x3C9),
+        0x1D70D => array(0x3C3),
+        0x1D71C => array(0x3B1),
+        0x1D71D => array(0x3B2),
+        0x1D71E => array(0x3B3),
+        0x1D71F => array(0x3B4),
+        0x1D720 => array(0x3B5),
+        0x1D721 => array(0x3B6),
+        0x1D722 => array(0x3B7),
+        0x1D723 => array(0x3B8),
+        0x1D724 => array(0x3B9),
+        0x1D725 => array(0x3BA),
+        0x1D726 => array(0x3BB),
+        0x1D727 => array(0x3BC),
+        0x1D728 => array(0x3BD),
+        0x1D729 => array(0x3BE),
+        0x1D72A => array(0x3BF),
+        0x1D72B => array(0x3C0),
+        0x1D72C => array(0x3C1),
+        0x1D72D => array(0x3B8),
+        0x1D72E => array(0x3C3),
+        0x1D72F => array(0x3C4),
+        0x1D730 => array(0x3C5),
+        0x1D731 => array(0x3C6),
+        0x1D732 => array(0x3C7),
+        0x1D733 => array(0x3C8),
+        0x1D734 => array(0x3C9),
+        0x1D747 => array(0x3C3),
+        0x1D756 => array(0x3B1),
+        0x1D757 => array(0x3B2),
+        0x1D758 => array(0x3B3),
+        0x1D759 => array(0x3B4),
+        0x1D75A => array(0x3B5),
+        0x1D75B => array(0x3B6),
+        0x1D75C => array(0x3B7),
+        0x1D75D => array(0x3B8),
+        0x1D75E => array(0x3B9),
+        0x1D75F => array(0x3BA),
+        0x1D760 => array(0x3BB),
+        0x1D761 => array(0x3BC),
+        0x1D762 => array(0x3BD),
+        0x1D763 => array(0x3BE),
+        0x1D764 => array(0x3BF),
+        0x1D765 => array(0x3C0),
+        0x1D766 => array(0x3C1),
+        0x1D767 => array(0x3B8),
+        0x1D768 => array(0x3C3),
+        0x1D769 => array(0x3C4),
+        0x1D76A => array(0x3C5),
+        0x1D76B => array(0x3C6),
+        0x1D76C => array(0x3C7),
+        0x1D76D => array(0x3C8),
+        0x1D76E => array(0x3C9),
+        0x1D781 => array(0x3C3),
+        0x1D790 => array(0x3B1),
+        0x1D791 => array(0x3B2),
+        0x1D792 => array(0x3B3),
+        0x1D793 => array(0x3B4),
+        0x1D794 => array(0x3B5),
+        0x1D795 => array(0x3B6),
+        0x1D796 => array(0x3B7),
+        0x1D797 => array(0x3B8),
+        0x1D798 => array(0x3B9),
+        0x1D799 => array(0x3BA),
+        0x1D79A => array(0x3BB),
+        0x1D79B => array(0x3BC),
+        0x1D79C => array(0x3BD),
+        0x1D79D => array(0x3BE),
+        0x1D79E => array(0x3BF),
+        0x1D79F => array(0x3C0),
+        0x1D7A0 => array(0x3C1),
+        0x1D7A1 => array(0x3B8),
+        0x1D7A2 => array(0x3C3),
+        0x1D7A3 => array(0x3C4),
+        0x1D7A4 => array(0x3C5),
+        0x1D7A5 => array(0x3C6),
+        0x1D7A6 => array(0x3C7),
+        0x1D7A7 => array(0x3C8),
+        0x1D7A8 => array(0x3C9),
+        0x1D7BB => array(0x3C3),
+        0x3F9   => array(0x3C3),
+        0x1D2C  => array(0x61),
+        0x1D2D  => array(0xE6),
+        0x1D2E  => array(0x62),
+        0x1D30  => array(0x64),
+        0x1D31  => array(0x65),
+        0x1D32  => array(0x1DD),
+        0x1D33  => array(0x67),
+        0x1D34  => array(0x68),
+        0x1D35  => array(0x69),
+        0x1D36  => array(0x6A),
+        0x1D37  => array(0x6B),
+        0x1D38  => array(0x6C),
+        0x1D39  => array(0x6D),
+        0x1D3A  => array(0x6E),
+        0x1D3C  => array(0x6F),
+        0x1D3D  => array(0x223),
+        0x1D3E  => array(0x70),
+        0x1D3F  => array(0x72),
+        0x1D40  => array(0x74),
+        0x1D41  => array(0x75),
+        0x1D42  => array(0x77),
+        0x213B  => array(0x66, 0x61, 0x78),
+        0x3250  => array(0x70, 0x74, 0x65),
+        0x32CC  => array(0x68, 0x67),
+        0x32CE  => array(0x65, 0x76),
+        0x32CF  => array(0x6C, 0x74, 0x64),
+        0x337A  => array(0x69, 0x75),
+        0x33DE  => array(0x76, 0x2215, 0x6D),
+        0x33DF  => array(0x61, 0x2215, 0x6D)
+    );
+
+    /**
+     * Normalization Combining Classes; Code Points not listed
+     * got Combining Class 0.
+     *
+     * @static
+     * @var array
+     * @access private
+     */
+    private static $_np_norm_combcls = array(
+        0x334   => 1,
+        0x335   => 1,
+        0x336   => 1,
+        0x337   => 1,
+        0x338   => 1,
+        0x93C   => 7,
+        0x9BC   => 7,
+        0xA3C   => 7,
+        0xABC   => 7,
+        0xB3C   => 7,
+        0xCBC   => 7,
+        0x1037  => 7,
+        0x3099  => 8,
+        0x309A  => 8,
+        0x94D   => 9,
+        0x9CD   => 9,
+        0xA4D   => 9,
+        0xACD   => 9,
+        0xB4D   => 9,
+        0xBCD   => 9,
+        0xC4D   => 9,
+        0xCCD   => 9,
+        0xD4D   => 9,
+        0xDCA   => 9,
+        0xE3A   => 9,
+        0xF84   => 9,
+        0x1039  => 9,
+        0x1714  => 9,
+        0x1734  => 9,
+        0x17D2  => 9,
+        0x5B0   => 10,
+        0x5B1   => 11,
+        0x5B2   => 12,
+        0x5B3   => 13,
+        0x5B4   => 14,
+        0x5B5   => 15,
+        0x5B6   => 16,
+        0x5B7   => 17,
+        0x5B8   => 18,
+        0x5B9   => 19,
+        0x5BB   => 20,
+        0x5Bc   => 21,
+        0x5BD   => 22,
+        0x5BF   => 23,
+        0x5C1   => 24,
+        0x5C2   => 25,
+        0xFB1E  => 26,
+        0x64B   => 27,
+        0x64C   => 28,
+        0x64D   => 29,
+        0x64E   => 30,
+        0x64F   => 31,
+        0x650   => 32,
+        0x651   => 33,
+        0x652   => 34,
+        0x670   => 35,
+        0x711   => 36,
+        0xC55   => 84,
+        0xC56   => 91,
+        0xE38   => 103,
+        0xE39   => 103,
+        0xE48   => 107,
+        0xE49   => 107,
+        0xE4A   => 107,
+        0xE4B   => 107,
+        0xEB8   => 118,
+        0xEB9   => 118,
+        0xEC8   => 122,
+        0xEC9   => 122,
+        0xECA   => 122,
+        0xECB   => 122,
+        0xF71   => 129,
+        0xF72   => 130,
+        0xF7A   => 130,
+        0xF7B   => 130,
+        0xF7C   => 130,
+        0xF7D   => 130,
+        0xF80   => 130,
+        0xF74   => 132,
+        0x321   => 202,
+        0x322   => 202,
+        0x327   => 202,
+        0x328   => 202,
+        0x31B   => 216,
+        0xF39   => 216,
+        0x1D165 => 216,
+        0x1D166 => 216,
+        0x1D16E => 216,
+        0x1D16F => 216,
+        0x1D170 => 216,
+        0x1D171 => 216,
+        0x1D172 => 216,
+        0x302A  => 218,
+        0x316   => 220,
+        0x317   => 220,
+        0x318   => 220,
+        0x319   => 220,
+        0x31C   => 220,
+        0x31D   => 220,
+        0x31E   => 220,
+        0x31F   => 220,
+        0x320   => 220,
+        0x323   => 220,
+        0x324   => 220,
+        0x325   => 220,
+        0x326   => 220,
+        0x329   => 220,
+        0x32A   => 220,
+        0x32B   => 220,
+        0x32C   => 220,
+        0x32D   => 220,
+        0x32E   => 220,
+        0x32F   => 220,
+        0x330   => 220,
+        0x331   => 220,
+        0x332   => 220,
+        0x333   => 220,
+        0x339   => 220,
+        0x33A   => 220,
+        0x33B   => 220,
+        0x33C   => 220,
+        0x347   => 220,
+        0x348   => 220,
+        0x349   => 220,
+        0x34D   => 220,
+        0x34E   => 220,
+        0x353   => 220,
+        0x354   => 220,
+        0x355   => 220,
+        0x356   => 220,
+        0x591   => 220,
+        0x596   => 220,
+        0x59B   => 220,
+        0x5A3   => 220,
+        0x5A4   => 220,
+        0x5A5   => 220,
+        0x5A6   => 220,
+        0x5A7   => 220,
+        0x5AA   => 220,
+        0x655   => 220,
+        0x656   => 220,
+        0x6E3   => 220,
+        0x6EA   => 220,
+        0x6ED   => 220,
+        0x731   => 220,
+        0x734   => 220,
+        0x737   => 220,
+        0x738   => 220,
+        0x739   => 220,
+        0x73B   => 220,
+        0x73C   => 220,
+        0x73E   => 220,
+        0x742   => 220,
+        0x744   => 220,
+        0x746   => 220,
+        0x748   => 220,
+        0x952   => 220,
+        0xF18   => 220,
+        0xF19   => 220,
+        0xF35   => 220,
+        0xF37   => 220,
+        0xFC6   => 220,
+        0x193B  => 220,
+        0x20E8  => 220,
+        0x1D17B => 220,
+        0x1D17C => 220,
+        0x1D17D => 220,
+        0x1D17E => 220,
+        0x1D17F => 220,
+        0x1D180 => 220,
+        0x1D181 => 220,
+        0x1D182 => 220,
+        0x1D18A => 220,
+        0x1D18B => 220,
+        0x59A   => 222,
+        0x5AD   => 222,
+        0x1929  => 222,
+        0x302D  => 222,
+        0x302E  => 224,
+        0x302F  => 224,
+        0x1D16D => 226,
+        0x5AE   => 228,
+        0x18A9  => 228,
+        0x302B  => 228,
+        0x300   => 230,
+        0x301   => 230,
+        0x302   => 230,
+        0x303   => 230,
+        0x304   => 230,
+        0x305   => 230,
+        0x306   => 230,
+        0x307   => 230,
+        0x308   => 230,
+        0x309   => 230,
+        0x30A   => 230,
+        0x30B   => 230,
+        0x30C   => 230,
+        0x30D   => 230,
+        0x30E   => 230,
+        0x30F   => 230,
+        0x310   => 230,
+        0x311   => 230,
+        0x312   => 230,
+        0x313   => 230,
+        0x314   => 230,
+        0x33D   => 230,
+        0x33E   => 230,
+        0x33F   => 230,
+        0x340   => 230,
+        0x341   => 230,
+        0x342   => 230,
+        0x343   => 230,
+        0x344   => 230,
+        0x346   => 230,
+        0x34A   => 230,
+        0x34B   => 230,
+        0x34C   => 230,
+        0x350   => 230,
+        0x351   => 230,
+        0x352   => 230,
+        0x357   => 230,
+        0x363   => 230,
+        0x364   => 230,
+        0x365   => 230,
+        0x366   => 230,
+        0x367   => 230,
+        0x368   => 230,
+        0x369   => 230,
+        0x36A   => 230,
+        0x36B   => 230,
+        0x36C   => 230,
+        0x36D   => 230,
+        0x36E   => 230,
+        0x36F   => 230,
+        0x483   => 230,
+        0x484   => 230,
+        0x485   => 230,
+        0x486   => 230,
+        0x592   => 230,
+        0x593   => 230,
+        0x594   => 230,
+        0x595   => 230,
+        0x597   => 230,
+        0x598   => 230,
+        0x599   => 230,
+        0x59C   => 230,
+        0x59D   => 230,
+        0x59E   => 230,
+        0x59F   => 230,
+        0x5A0   => 230,
+        0x5A1   => 230,
+        0x5A8   => 230,
+        0x5A9   => 230,
+        0x5AB   => 230,
+        0x5AC   => 230,
+        0x5AF   => 230,
+        0x5C4   => 230,
+        0x610   => 230,
+        0x611   => 230,
+        0x612   => 230,
+        0x613   => 230,
+        0x614   => 230,
+        0x615   => 230,
+        0x653   => 230,
+        0x654   => 230,
+        0x657   => 230,
+        0x658   => 230,
+        0x6D6   => 230,
+        0x6D7   => 230,
+        0x6D8   => 230,
+        0x6D9   => 230,
+        0x6DA   => 230,
+        0x6DB   => 230,
+        0x6DC   => 230,
+        0x6DF   => 230,
+        0x6E0   => 230,
+        0x6E1   => 230,
+        0x6E2   => 230,
+        0x6E4   => 230,
+        0x6E7   => 230,
+        0x6E8   => 230,
+        0x6EB   => 230,
+        0x6EC   => 230,
+        0x730   => 230,
+        0x732   => 230,
+        0x733   => 230,
+        0x735   => 230,
+        0x736   => 230,
+        0x73A   => 230,
+        0x73D   => 230,
+        0x73F   => 230,
+        0x740   => 230,
+        0x741   => 230,
+        0x743   => 230,
+        0x745   => 230,
+        0x747   => 230,
+        0x749   => 230,
+        0x74A   => 230,
+        0x951   => 230,
+        0x953   => 230,
+        0x954   => 230,
+        0xF82   => 230,
+        0xF83   => 230,
+        0xF86   => 230,
+        0xF87   => 230,
+        0x170D  => 230,
+        0x193A  => 230,
+        0x20D0  => 230,
+        0x20D1  => 230,
+        0x20D4  => 230,
+        0x20D5  => 230,
+        0x20D6  => 230,
+        0x20D7  => 230,
+        0x20DB  => 230,
+        0x20DC  => 230,
+        0x20E1  => 230,
+        0x20E7  => 230,
+        0x20E9  => 230,
+        0xFE20  => 230,
+        0xFE21  => 230,
+        0xFE22  => 230,
+        0xFE23  => 230,
+        0x1D185 => 230,
+        0x1D186 => 230,
+        0x1D187 => 230,
+        0x1D189 => 230,
+        0x1D188 => 230,
+        0x1D1AA => 230,
+        0x1D1AB => 230,
+        0x1D1AC => 230,
+        0x1D1AD => 230,
+        0x315   => 232,
+        0x31A   => 232,
+        0x302C  => 232,
+        0x35F   => 233,
+        0x362   => 233,
+        0x35D   => 234,
+        0x35E   => 234,
+        0x360   => 234,
+        0x361   => 234,
+        0x345   => 240
+    );
+    // }}}
+
+    // {{{ properties
+    /**
+     * @var string
+     * @access private
+     */
+    private $_punycode_prefix = 'xn--';
+
+    /**
+     * @access private
+     */
+    private $_invalid_ucs = 0x80000000;
+
+    /**
+     * @access private
+     */
+    private $_max_ucs = 0x10FFFF;
+
+    /**
+     * @var int
+     * @access private
+     */
+    private $_base = 36;
+
+    /**
+     * @var int
+     * @access private
+     */
+    private $_tmin = 1;
+
+    /**
+     * @var int
+     * @access private
+     */
+    private $_tmax = 26;
+
+    /**
+     * @var int
+     * @access private
+     */
+    private $_skew = 38;
+
+    /**
+     * @var int
+     * @access private
+     */
+    private $_damp = 700;
+
+    /**
+     * @var int
+     * @access private
+     */
+    private $_initial_bias = 72;
+
+    /**
+     * @var int
+     * @access private
+     */
+    private $_initial_n = 0x80;
+
+    /**
+     * @var int
+     * @access private
+     */
+    private $_slast;
+
+    /**
+     * @access private
+     */
+    private $_sbase = 0xAC00;
+
+    /**
+     * @access private
+     */
+    private $_lbase = 0x1100;
+
+    /**
+     * @access private
+     */
+    private $_vbase = 0x1161;
+
+    /**
+     * @access private
+     */
+    private $_tbase = 0x11a7;
+
+    /**
+     * @var int
+     * @access private
+     */
+    private $_lcount = 19;
+
+    /**
+     * @var int
+     * @access private
+     */
+    private $_vcount = 21;
+
+    /**
+     * @var int
+     * @access private
+     */
+    private $_tcount = 28;
+
+    /**
+     * vcount * tcount
+     *
+     * @var int
+     * @access private
+     */
+    private $_ncount = 588;
+
+    /**
+     * lcount * tcount * vcount
+     *
+     * @var int
+     * @access private
+     */
+    private $_scount = 11172;
+
+    /**
+     * Default encoding for encode()'s input and decode()'s output is UTF-8;
+     * Other possible encodings are ucs4_string and ucs4_array
+     * See {@link setParams()} for how to select these
+     *
+     * @var bool
+     * @access private
+     */
+    private $_api_encoding = 'utf8';
+
+    /**
+     * Overlong UTF-8 encodings are forbidden
+     *
+     * @var bool
+     * @access private
+     */
+    private $_allow_overlong = false;
+
+    /**
+     * Behave strict or not
+     *
+     * @var bool
+     * @access private
+     */
+    private $_strict_mode = false;
+
+    /**
+     * Cached value indicating whether or not mbstring function overloading is
+     * on for strlen
+     *
+     * This is cached for optimal performance.
+     *
+     * @var boolean
+     * @see Net_IDNA_php5::_byteLength()
+     */
+    private static $_mb_string_overload = null;
+    // }}}
+
+
+    // {{{ constructor
+    /**
+     * Constructor
+     *
+     * @param  array  $options
+     * @access public
+     * @see    setParams()
+     */
+    public function __construct($options = null)
+    {
+        $this->_slast = $this->_sbase + $this->_lcount * $this->_vcount * $this->_tcount;
+
+        if (is_array($options)) {
+            $this->setParams($options);
+        }
+
+        // populate mbstring overloading cache if not set
+        if (self::$_mb_string_overload === null) {
+            self::$_mb_string_overload = (extension_loaded('mbstring')
+                && (ini_get('mbstring.func_overload') & 0x02) === 0x02);
+        }
+    }
+    // }}}
+
+
+    /**
+     * Sets a new option value. Available options and values:
+     *
+     * [utf8 -     Use either UTF-8 or ISO-8859-1 as input (true for UTF-8, false
+     *             otherwise); The output is always UTF-8]
+     * [overlong - Unicode does not allow unnecessarily long encodings of chars,
+     *             to allow this, set this parameter to true, else to false;
+     *             default is false.]
+     * [strict -   true: strict mode, good for registration purposes - Causes errors
+     *             on failures; false: loose mode, ideal for "wildlife" applications
+     *             by silently ignoring errors and returning the original input instead]
+     *
+     * @param    mixed     $option      Parameter to set (string: single parameter; array of Parameter => Value pairs)
+     * @param    string    $value       Value to use (if parameter 1 is a string)
+     * @return   boolean                true on success, false otherwise
+     * @access   public
+     */
+    public function setParams($option, $value = false)
+    {
+        if (!is_array($option)) {
+            $option = array($option => $value);
+        }
+
+        foreach ($option as $k => $v) {
+            switch ($k) {
+            case 'encoding':
+                switch ($v) {
+                case 'utf8':
+                case 'ucs4_string':
+                case 'ucs4_array':
+                    $this->_api_encoding = $v;
+                    break;
+
+                default:
+                    throw new Exception('Set Parameter: Unknown parameter '.$v.' for option '.$k);
+                }
+
+                break;
+
+            case 'overlong':
+                $this->_allow_overlong = ($v) ? true : false;
+                break;
+
+            case 'strict':
+                $this->_strict_mode = ($v) ? true : false;
+                break;
+
+            default:
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Encode a given UTF-8 domain name.
+     *
+     * @param    string     $decoded     Domain name (UTF-8 or UCS-4)
+     * [@param    string     $encoding    Desired input encoding, see {@link set_parameter}]
+     * @return   string                  Encoded Domain name (ACE string)
+     * @return   mixed                   processed string
+     * @throws   Exception
+     * @access   public
+     */
+    public function encode($decoded, $one_time_encoding = false)
+    {
+        // Forcing conversion of input to UCS4 array
+        // If one time encoding is given, use this, else the objects property
+        switch (($one_time_encoding) ? $one_time_encoding : $this->_api_encoding) {
+        case 'utf8':
+            $decoded = $this->_utf8_to_ucs4($decoded);
+            break;
+        case 'ucs4_string':
+           $decoded = $this->_ucs4_string_to_ucs4($decoded);
+        case 'ucs4_array': // No break; before this line. Catch case, but do nothing
+           break;
+        default:
+            throw new Exception('Unsupported input format');
+        }
+
+        // No input, no output, what else did you expect?
+        if (empty($decoded)) return '';
+
+        // Anchors for iteration
+        $last_begin = 0;
+        // Output string
+        $output = '';
+
+        foreach ($decoded as $k => $v) {
+            // Make sure to use just the plain dot
+            switch($v) {
+            case 0x3002:
+            case 0xFF0E:
+            case 0xFF61:
+                $decoded[$k] = 0x2E;
+                // It's right, no break here
+                // The codepoints above have to be converted to dots anyway
+
+            // Stumbling across an anchoring character
+            case 0x2E:
+            case 0x2F:
+            case 0x3A:
+            case 0x3F:
+            case 0x40:
+                // Neither email addresses nor URLs allowed in strict mode
+                if ($this->_strict_mode) {
+                   throw new Exception('Neither email addresses nor URLs are allowed in strict mode.');
+                } else {
+                    // Skip first char
+                    if ($k) {
+                        $encoded = '';
+                        $encoded = $this->_encode(array_slice($decoded, $last_begin, (($k)-$last_begin)));
+                        if ($encoded) {
+                            $output .= $encoded;
+                        } else {
+                            $output .= $this->_ucs4_to_utf8(array_slice($decoded, $last_begin, (($k)-$last_begin)));
+                        }
+                        $output .= chr($decoded[$k]);
+                    }
+                    $last_begin = $k + 1;
+                }
+            }
+        }
+        // Catch the rest of the string
+        if ($last_begin) {
+            $inp_len = sizeof($decoded);
+            $encoded = '';
+            $encoded = $this->_encode(array_slice($decoded, $last_begin, (($inp_len)-$last_begin)));
+            if ($encoded) {
+                $output .= $encoded;
+            } else {
+                $output .= $this->_ucs4_to_utf8(array_slice($decoded, $last_begin, (($inp_len)-$last_begin)));
+            }
+            return $output;
+        } else {
+            if ($output = $this->_encode($decoded)) {
+                return $output;
+            } else {
+                return $this->_ucs4_to_utf8($decoded);
+            }
+        }
+    }
+
+    /**
+     * Decode a given ACE domain name.
+     *
+     * @param    string     $encoded     Domain name (ACE string)
+     * @param    string     $encoding    Desired output encoding, see {@link set_parameter}
+     * @return   string                  Decoded Domain name (UTF-8 or UCS-4)
+     * @throws   Exception
+     * @access   public
+     */
+    public function decode($input, $one_time_encoding = false)
+    {
+        // Optionally set
+        if ($one_time_encoding) {
+            switch ($one_time_encoding) {
+            case 'utf8':
+            case 'ucs4_string':
+            case 'ucs4_array':
+                break;
+            default:
+                throw new Exception('Unknown encoding '.$one_time_encoding);
+                return false;
+            }
+        }
+        // Make sure to drop any newline characters around
+        $input = trim($input);
+
+        // Negotiate input and try to determine, wether it is a plain string,
+        // an email address or something like a complete URL
+        if (strpos($input, '@')) { // Maybe it is an email address
+            // No no in strict mode
+            if ($this->_strict_mode) {
+                throw new Exception('Only simple domain name parts can be handled in strict mode');
+            }
+            list($email_pref, $input) = explode('@', $input, 2);
+            $arr = explode('.', $input);
+            foreach ($arr as $k => $v) {
+                $conv = $this->_decode($v);
+                if ($conv) $arr[$k] = $conv;
+            }
+            $return = $email_pref . '@' . join('.', $arr);
+        } elseif (preg_match('![:\./]!', $input)) { // Or a complete domain name (with or without paths / parameters)
+            // No no in strict mode
+            if ($this->_strict_mode) {
+                throw new Exception('Only simple domain name parts can be handled in strict mode');
+            }
+            $parsed = parse_url($input);
+            if (isset($parsed['host'])) {
+                $arr = explode('.', $parsed['host']);
+                foreach ($arr as $k => $v) {
+                    $conv = $this->_decode($v);
+                    if ($conv) $arr[$k] = $conv;
+                }
+                $parsed['host'] = join('.', $arr);
+                if (isset($parsed['scheme'])) {
+                    $parsed['scheme'] .= (strtolower($parsed['scheme']) == 'mailto') ? ':' : '://';
+                }
+                $return = join('', $parsed);
+            } else { // parse_url seems to have failed, try without it
+                $arr = explode('.', $input);
+                foreach ($arr as $k => $v) {
+                    $conv = $this->_decode($v);
+                    if ($conv) $arr[$k] = $conv;
+                }
+                $return = join('.', $arr);
+            }
+        } else { // Otherwise we consider it being a pure domain name string
+            $return = $this->_decode($input);
+        }
+        // The output is UTF-8 by default, other output formats need conversion here
+        // If one time encoding is given, use this, else the objects property
+        switch (($one_time_encoding) ? $one_time_encoding : $this->_api_encoding) {
+        case 'utf8':
+            return $return;
+            break;
+        case 'ucs4_string':
+           return $this->_ucs4_to_ucs4_string($this->_utf8_to_ucs4($return));
+           break;
+        case 'ucs4_array':
+            return $this->_utf8_to_ucs4($return);
+            break;
+        default:
+            throw new Exception('Unsupported output format');
+        }
+    }
+
+
+    // {{{ private
+    /**
+     * The actual encoding algorithm.
+     *
+     * @return   string
+     * @throws   Exception
+     * @access   private
+     */
+    private function _encode($decoded)
+    {
+        // We cannot encode a domain name containing the Punycode prefix
+        $extract = self::_byteLength($this->_punycode_prefix);
+        $check_pref = $this->_utf8_to_ucs4($this->_punycode_prefix);
+        $check_deco = array_slice($decoded, 0, $extract);
+
+        if ($check_pref == $check_deco) {
+            throw new Exception('This is already a punycode string');
+        }
+        // We will not try to encode strings consisting of basic code points only
+        $encodable = false;
+        foreach ($decoded as $k => $v) {
+            if ($v > 0x7a) {
+                $encodable = true;
+                break;
+            }
+        }
+        if (!$encodable) {
+            if ($this->_strict_mode) {
+                throw new Exception('The given string does not contain encodable chars');
+            } else {
+                return false;
+            }
+        }
+
+        // Do NAMEPREP
+        try {
+            $decoded = $this->_nameprep($decoded);
+        } catch (Exception $e) {
+            // hmm, serious - rethrow
+            throw $e;
+        }
+
+        $deco_len = count($decoded);
+
+        // Empty array
+        if (!$deco_len) {
+            return false;
+        }
+
+        // How many chars have been consumed
+        $codecount = 0;
+
+        // Start with the prefix; copy it to output
+        $encoded = $this->_punycode_prefix;
+
+        $encoded = '';
+        // Copy all basic code points to output
+        for ($i = 0; $i < $deco_len; ++$i) {
+            $test = $decoded[$i];
+            // Will match [0-9a-zA-Z-]
+            if ((0x2F < $test && $test < 0x40)
+                    || (0x40 < $test && $test < 0x5B)
+                    || (0x60 < $test && $test <= 0x7B)
+                    || (0x2D == $test)) {
+                $encoded .= chr($decoded[$i]);
+                $codecount++;
+            }
+        }
+
+        // All codepoints were basic ones
+        if ($codecount == $deco_len) {
+            return $encoded;
+        }
+
+        // Start with the prefix; copy it to output
+        $encoded = $this->_punycode_prefix . $encoded;
+
+        // If we have basic code points in output, add an hyphen to the end
+        if ($codecount) {
+            $encoded .= '-';
+        }
+
+        // Now find and encode all non-basic code points
+        $is_first  = true;
+        $cur_code  = $this->_initial_n;
+        $bias      = $this->_initial_bias;
+        $delta     = 0;
+
+        while ($codecount < $deco_len) {
+            // Find the smallest code point >= the current code point and
+            // remember the last ouccrence of it in the input
+            for ($i = 0, $next_code = $this->_max_ucs; $i < $deco_len; $i++) {
+                if ($decoded[$i] >= $cur_code && $decoded[$i] <= $next_code) {
+                    $next_code = $decoded[$i];
+                }
+            }
+
+            $delta += ($next_code - $cur_code) * ($codecount + 1);
+            $cur_code = $next_code;
+
+            // Scan input again and encode all characters whose code point is $cur_code
+            for ($i = 0; $i < $deco_len; $i++) {
+                if ($decoded[$i] < $cur_code) {
+                    $delta++;
+                } else if ($decoded[$i] == $cur_code) {
+                    for ($q = $delta, $k = $this->_base; 1; $k += $this->_base) {
+                        $t = ($k <= $bias)?
+                            $this->_tmin :
+                            (($k >= $bias + $this->_tmax)? $this->_tmax : $k - $bias);
+
+                        if ($q < $t) {
+                            break;
+                        }
+
+                        $encoded .= $this->_encodeDigit(ceil($t + (($q - $t) % ($this->_base - $t))));
+                        $q = ($q - $t) / ($this->_base - $t);
+                    }
+
+                    $encoded .= $this->_encodeDigit($q);
+                    $bias = $this->_adapt($delta, $codecount + 1, $is_first);
+                    $codecount++;
+                    $delta = 0;
+                    $is_first = false;
+                }
+            }
+
+            $delta++;
+            $cur_code++;
+        }
+
+        return $encoded;
+    }
+
+    /**
+     * The actual decoding algorithm.
+     *
+     * @return   string
+     * @throws   Exception
+     * @access   private
+     */
+    private function _decode($encoded)
+    {
+        // We do need to find the Punycode prefix
+        if (!preg_match('!^' . preg_quote($this->_punycode_prefix, '!') . '!', $encoded)) {
+            return false;
+        }
+
+        $encode_test = preg_replace('!^' . preg_quote($this->_punycode_prefix, '!') . '!', '', $encoded);
+
+        // If nothing left after removing the prefix, it is hopeless
+        if (!$encode_test) {
+            return false;
+        }
+
+        // Find last occurence of the delimiter
+        $delim_pos = strrpos($encoded, '-');
+
+        if ($delim_pos > self::_byteLength($this->_punycode_prefix)) {
+            for ($k = self::_byteLength($this->_punycode_prefix); $k < $delim_pos; ++$k) {
+                $decoded[] = ord($encoded{$k});
+            }
+        } else {
+            $decoded = array();
+        }
+
+        $deco_len = count($decoded);
+        $enco_len = self::_byteLength($encoded);
+
+        // Wandering through the strings; init
+        $is_first = true;
+        $bias     = $this->_initial_bias;
+        $idx      = 0;
+        $char     = $this->_initial_n;
+
+        for ($enco_idx = ($delim_pos)? ($delim_pos + 1) : 0; $enco_idx < $enco_len; ++$deco_len) {
+            for ($old_idx = $idx, $w = 1, $k = $this->_base; 1 ; $k += $this->_base) {
+                $digit = $this->_decodeDigit($encoded{$enco_idx++});
+                $idx += $digit * $w;
+
+                $t = ($k <= $bias) ?
+                    $this->_tmin :
+                    (($k >= $bias + $this->_tmax)? $this->_tmax : ($k - $bias));
+
+                if ($digit < $t) {
+                    break;
+                }
+
+                $w = (int)($w * ($this->_base - $t));
+            }
+
+            $bias      = $this->_adapt($idx - $old_idx, $deco_len + 1, $is_first);
+            $is_first  = false;
+            $char     += (int) ($idx / ($deco_len + 1));
+            $idx      %= ($deco_len + 1);
+
+            if ($deco_len > 0) {
+                // Make room for the decoded char
+                for ($i = $deco_len; $i > $idx; $i--) {
+                    $decoded[$i] = $decoded[($i - 1)];
+                }
+            }
+
+            $decoded[$idx++] = $char;
+        }
+
+        try {
+            return $this->_ucs4_to_utf8($decoded);
+        } catch (Exception $e) {
+            // rethrow
+            throw $e;
+        }
+    }
+
+    /**
+     * Adapt the bias according to the current code point and position.
+     *
+     * @access   private
+     */
+    private function _adapt($delta, $npoints, $is_first)
+    {
+        $delta = (int) ($is_first ? ($delta / $this->_damp) : ($delta / 2));
+        $delta += (int) ($delta / $npoints);
+
+        for ($k = 0; $delta > (($this->_base - $this->_tmin) * $this->_tmax) / 2; $k += $this->_base) {
+            $delta = (int) ($delta / ($this->_base - $this->_tmin));
+        }
+
+        return (int) ($k + ($this->_base - $this->_tmin + 1) * $delta / ($delta + $this->_skew));
+    }
+
+    /**
+     * Encoding a certain digit.
+     *
+     * @access   private
+     */
+    private function _encodeDigit($d)
+    {
+        return chr($d + 22 + 75 * ($d < 26));
+    }
+
+    /**
+     * Decode a certain digit.
+     *
+     * @access   private
+     */
+    private function _decodeDigit($cp)
+    {
+        $cp = ord($cp);
+        return ($cp - 48 < 10)? $cp - 22 : (($cp - 65 < 26)? $cp - 65 : (($cp - 97 < 26)? $cp - 97 : $this->_base));
+    }
+
+    /**
+     * Do Nameprep according to RFC3491 and RFC3454.
+     *
+     * @param    array      $input       Unicode Characters
+     * @return   string                  Unicode Characters, Nameprep'd
+     * @throws   Exception
+     * @access   private
+     */
+    private function _nameprep($input)
+    {
+        $output = array();
+
+        // Walking through the input array, performing the required steps on each of
+        // the input chars and putting the result into the output array
+        // While mapping required chars we apply the cannonical ordering
+
+        foreach ($input as $v) {
+            // Map to nothing == skip that code point
+            if (in_array($v, self::$_np_map_nothing)) {
+                continue;
+            }
+
+            // Try to find prohibited input
+            if (in_array($v, self::$_np_prohibit) || in_array($v, self::$_general_prohibited)) {
+                throw new Exception('NAMEPREP: Prohibited input U+' . sprintf('%08X', $v));
+            }
+
+            foreach (self::$_np_prohibit_ranges as $range) {
+                if ($range[0] <= $v && $v <= $range[1]) {
+                    throw new Exception('NAMEPREP: Prohibited input U+' . sprintf('%08X', $v));
+                }
+            }
+
+            // Hangul syllable decomposition
+            if (0xAC00 <= $v && $v <= 0xD7AF) {
+                foreach ($this->_hangulDecompose($v) as $out) {
+                    $output[] = $out;
+                }
+            } else if (isset(self::$_np_replacemaps[$v])) { // There's a decomposition mapping for that code point
+                foreach ($this->_applyCannonicalOrdering(self::$_np_replacemaps[$v]) as $out) {
+                    $output[] = $out;
+                }
+            } else {
+                $output[] = $v;
+            }
+        }
+
+        // Combine code points
+
+        $last_class   = 0;
+        $last_starter = 0;
+        $out_len      = count($output);
+
+        for ($i = 0; $i < $out_len; ++$i) {
+            $class = $this->_getCombiningClass($output[$i]);
+
+            if ((!$last_class || $last_class != $class) && $class) {
+                // Try to match
+                $seq_len = $i - $last_starter;
+                $out = $this->_combine(array_slice($output, $last_starter, $seq_len));
+
+                // On match: Replace the last starter with the composed character and remove
+                // the now redundant non-starter(s)
+                if ($out) {
+                    $output[$last_starter] = $out;
+
+                    if (count($out) != $seq_len) {
+                        for ($j = $i + 1; $j < $out_len; ++$j) {
+                            $output[$j - 1] = $output[$j];
+                        }
+
+                        unset($output[$out_len]);
+                    }
+
+                    // Rewind the for loop by one, since there can be more possible compositions
+                    $i--;
+                    $out_len--;
+                    $last_class = ($i == $last_starter)? 0 : $this->_getCombiningClass($output[$i - 1]);
+
+                    continue;
+                }
+            }
+
+            // The current class is 0
+            if (!$class) {
+                $last_starter = $i;
+            }
+
+            $last_class = $class;
+        }
+
+        return $output;
+    }
+
+    /**
+     * Decomposes a Hangul syllable
+     * (see http://www.unicode.org/unicode/reports/tr15/#Hangul).
+     *
+     * @param    integer    $char        32bit UCS4 code point
+     * @return   array                   Either Hangul Syllable decomposed or original 32bit
+     *                                   value as one value array
+     * @access   private
+     */
+    private function _hangulDecompose($char)
+    {
+        $sindex = $char - $this->_sbase;
+
+        if ($sindex < 0 || $sindex >= $this->_scount) {
+            return array($char);
+        }
+
+        $result   = array();
+        $T        = $this->_tbase + $sindex % $this->_tcount;
+        $result[] = (int)($this->_lbase +  $sindex / $this->_ncount);
+        $result[] = (int)($this->_vbase + ($sindex % $this->_ncount) / $this->_tcount);
+
+        if ($T != $this->_tbase) {
+            $result[] = $T;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Ccomposes a Hangul syllable
+     * (see http://www.unicode.org/unicode/reports/tr15/#Hangul).
+     *
+     * @param    array      $input       Decomposed UCS4 sequence
+     * @return   array                   UCS4 sequence with syllables composed
+     * @access   private
+     */
+    private function _hangulCompose($input)
+    {
+        $inp_len = count($input);
+
+        if (!$inp_len) {
+            return array();
+        }
+
+        $result   = array();
+        $last     = $input[0];
+        $result[] = $last; // copy first char from input to output
+
+        for ($i = 1; $i < $inp_len; ++$i) {
+            $char = $input[$i];
+
+            // Find out, wether two current characters from L and V
+            $lindex = $last - $this->_lbase;
+
+            if (0 <= $lindex && $lindex < $this->_lcount) {
+                $vindex = $char - $this->_vbase;
+
+                if (0 <= $vindex && $vindex < $this->_vcount) {
+                    // create syllable of form LV
+                    $last    = ($this->_sbase + ($lindex * $this->_vcount + $vindex) * $this->_tcount);
+                    $out_off = count($result) - 1;
+                    $result[$out_off] = $last; // reset last
+
+                    // discard char
+                    continue;
+                }
+            }
+
+            // Find out, wether two current characters are LV and T
+            $sindex = $last - $this->_sbase;
+
+            if (0 <= $sindex && $sindex < $this->_scount && ($sindex % $this->_tcount) == 0) {
+                $tindex = $char - $this->_tbase;
+
+                if (0 <= $tindex && $tindex <= $this->_tcount) {
+                    // create syllable of form LVT
+                    $last += $tindex;
+                    $out_off = count($result) - 1;
+                    $result[$out_off] = $last; // reset last
+
+                    // discard char
+                    continue;
+                }
+            }
+
+            // if neither case was true, just add the character
+            $last = $char;
+            $result[] = $char;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Returns the combining class of a certain wide char.
+     *
+     * @param    integer    $char        Wide char to check (32bit integer)
+     * @return   integer                 Combining class if found, else 0
+     * @access   private
+     */
+    private function _getCombiningClass($char)
+    {
+        return isset(self::$_np_norm_combcls[$char])? self::$_np_norm_combcls[$char] : 0;
+    }
+
+    /**
+     * Apllies the cannonical ordering of a decomposed UCS4 sequence.
+     *
+     * @param    array      $input       Decomposed UCS4 sequence
+     * @return   array                   Ordered USC4 sequence
+     * @access   private
+     */
+    private function _applyCannonicalOrdering($input)
+    {
+        $swap = true;
+        $size = count($input);
+
+        while ($swap) {
+            $swap = false;
+            $last = $this->_getCombiningClass($input[0]);
+
+            for ($i = 0; $i < $size - 1; ++$i) {
+                $next = $this->_getCombiningClass($input[$i + 1]);
+
+                if ($next != 0 && $last > $next) {
+                    // Move item leftward until it fits
+                    for ($j = $i + 1; $j > 0; --$j) {
+                        if ($this->_getCombiningClass($input[$j - 1]) <= $next) {
+                            break;
+                        }
+
+                        $t = $input[$j];
+                        $input[$j] = $input[$j - 1];
+                        $input[$j - 1] = $t;
+                        $swap = 1;
+                    }
+
+                    // Reentering the loop looking at the old character again
+                    $next = $last;
+                }
+
+                $last = $next;
+            }
+        }
+
+        return $input;
+    }
+
+    /**
+     * Do composition of a sequence of starter and non-starter.
+     *
+     * @param    array      $input       UCS4 Decomposed sequence
+     * @return   array                   Ordered USC4 sequence
+     * @access   private
+     */
+    private function _combine($input)
+    {
+        $inp_len = count($input);
+
+        // Is it a Hangul syllable?
+        if (1 != $inp_len) {
+            $hangul = $this->_hangulCompose($input);
+
+            // This place is probably wrong
+            if (count($hangul) != $inp_len) {
+                return $hangul;
+            }
+        }
+
+        foreach (self::$_np_replacemaps as $np_src => $np_target) {
+            if ($np_target[0] != $input[0]) {
+                continue;
+            }
+
+            if (count($np_target) != $inp_len) {
+                continue;
+            }
+
+            $hit = false;
+
+            foreach ($input as $k2 => $v2) {
+                if ($v2 == $np_target[$k2]) {
+                    $hit = true;
+                } else {
+                    $hit = false;
+                    break;
+                }
+            }
+
+            if ($hit) {
+                return $np_src;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * This converts an UTF-8 encoded string to its UCS-4 (array) representation
+     * By talking about UCS-4 we mean arrays of 32bit integers representing
+     * each of the "chars". This is due to PHP not being able to handle strings with
+     * bit depth different from 8. This applies to the reverse method _ucs4_to_utf8(), too.
+     * The following UTF-8 encodings are supported:
+     *
+     * bytes bits  representation
+     * 1        7  0xxxxxxx
+     * 2       11  110xxxxx 10xxxxxx
+     * 3       16  1110xxxx 10xxxxxx 10xxxxxx
+     * 4       21  11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+     * 5       26  111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+     * 6       31  1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+     *
+     * Each x represents a bit that can be used to store character data.
+     *
+     * @access   private
+     */
+    private function _utf8_to_ucs4($input)
+    {
+        $output = array();
+        $out_len = 0;
+        $inp_len = self::_byteLength($input, '8bit');
+        $mode = 'next';
+        $test = 'none';
+        for ($k = 0; $k < $inp_len; ++$k) {
+            $v = ord($input{$k}); // Extract byte from input string
+
+            if ($v < 128) { // We found an ASCII char - put into stirng as is
+                $output[$out_len] = $v;
+                ++$out_len;
+                if ('add' == $mode) {
+                    throw new Exception('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k);
+                    return false;
+                }
+                continue;
+            }
+            if ('next' == $mode) { // Try to find the next start byte; determine the width of the Unicode char
+                $start_byte = $v;
+                $mode = 'add';
+                $test = 'range';
+                if ($v >> 5 == 6) { // &110xxxxx 10xxxxx
+                    $next_byte = 0; // Tells, how many times subsequent bitmasks must rotate 6bits to the left
+                    $v = ($v - 192) << 6;
+                } elseif ($v >> 4 == 14) { // &1110xxxx 10xxxxxx 10xxxxxx
+                    $next_byte = 1;
+                    $v = ($v - 224) << 12;
+                } elseif ($v >> 3 == 30) { // &11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+                    $next_byte = 2;
+                    $v = ($v - 240) << 18;
+                } elseif ($v >> 2 == 62) { // &111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+                    $next_byte = 3;
+                    $v = ($v - 248) << 24;
+                } elseif ($v >> 1 == 126) { // &1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+                    $next_byte = 4;
+                    $v = ($v - 252) << 30;
+                } else {
+                    throw new Exception('This might be UTF-8, but I don\'t understand it at byte '.$k);
+                    return false;
+                }
+                if ('add' == $mode) {
+                    $output[$out_len] = (int) $v;
+                    ++$out_len;
+                    continue;
+                }
+            }
+            if ('add' == $mode) {
+                if (!$this->_allow_overlong && $test == 'range') {
+                    $test = 'none';
+                    if (($v < 0xA0 && $start_byte == 0xE0) || ($v < 0x90 && $start_byte == 0xF0) || ($v > 0x8F && $start_byte == 0xF4)) {
+                        throw new Exception('Bogus UTF-8 character detected (out of legal range) at byte '.$k);
+                        return false;
+                    }
+                }
+                if ($v >> 6 == 2) { // Bit mask must be 10xxxxxx
+                    $v = ($v - 128) << ($next_byte * 6);
+                    $output[($out_len - 1)] += $v;
+                    --$next_byte;
+                } else {
+                    throw new Exception('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k);
+                    return false;
+                }
+                if ($next_byte < 0) {
+                    $mode = 'next';
+                }
+            }
+        } // for
+        return $output;
+    }
+
+    /**
+     * Convert UCS-4 array into UTF-8 string.
+     *
+     * @throws   Exception
+     * @access   private
+     */
+    private function _ucs4_to_utf8($input)
+    {
+        $output = '';
+
+        foreach ($input as $v) {
+            // $v = ord($v);
+
+            if ($v < 128) {
+                // 7bit are transferred literally
+                $output .= chr($v);
+            } else if ($v < 1 << 11) {
+                // 2 bytes
+                $output .= chr(192 + ($v >> 6))
+                    . chr(128 + ($v & 63));
+            } else if ($v < 1 << 16) {
+                // 3 bytes
+                $output .= chr(224 + ($v >> 12))
+                    . chr(128 + (($v >> 6) & 63))
+                    . chr(128 + ($v & 63));
+            } else if ($v < 1 << 21) {
+                // 4 bytes
+                $output .= chr(240 + ($v >> 18))
+                    . chr(128 + (($v >> 12) & 63))
+                    . chr(128 + (($v >>  6) & 63))
+                    . chr(128 + ($v & 63));
+            } else if ($v < 1 << 26) {
+                // 5 bytes
+                $output .= chr(248 + ($v >> 24))
+                    . chr(128 + (($v >> 18) & 63))
+                    . chr(128 + (($v >> 12) & 63))
+                    . chr(128 + (($v >>  6) & 63))
+                    . chr(128 + ($v & 63));
+            } else if ($v < 1 << 31) {
+                // 6 bytes
+                $output .= chr(252 + ($v >> 30))
+                    . chr(128 + (($v >> 24) & 63))
+                    . chr(128 + (($v >> 18) & 63))
+                    . chr(128 + (($v >> 12) & 63))
+                    . chr(128 + (($v >>  6) & 63))
+                    . chr(128 + ($v & 63));
+            } else {
+                throw new Exception('Conversion from UCS-4 to UTF-8 failed: malformed input at byte ' . $k);
+            }
+        }
+
+        return $output;
+    }
+
+    /**
+     * Convert UCS-4 array into UCS-4 string
+     *
+     * @throws   Exception
+     * @access   private
+     */
+    private function _ucs4_to_ucs4_string($input)
+    {
+        $output = '';
+        // Take array values and split output to 4 bytes per value
+        // The bit mask is 255, which reads &11111111
+        foreach ($input as $v) {
+            $output .= ($v & (255 << 24) >> 24) . ($v & (255 << 16) >> 16) . ($v & (255 << 8) >> 8) . ($v & 255);
+        }
+        return $output;
+    }
+
+    /**
+     * Convert UCS-4 strin into UCS-4 garray
+     *
+     * @throws   Exception
+     * @access   private
+     */
+    private function _ucs4_string_to_ucs4($input)
+    {
+        $output = array();
+
+        $inp_len = self::_byteLength($input);
+        // Input length must be dividable by 4
+        if ($inp_len % 4) {
+            throw new Exception('Input UCS4 string is broken');
+            return false;
+        }
+
+        // Empty input - return empty output
+        if (!$inp_len) return $output;
+
+        for ($i = 0, $out_len = -1; $i < $inp_len; ++$i) {
+            // Increment output position every 4 input bytes
+            if (!$i % 4) {
+                $out_len++;
+                $output[$out_len] = 0;
+            }
+            $output[$out_len] += ord($input{$i}) << (8 * (3 - ($i % 4) ) );
+        }
+        return $output;
+    }
+
+    /**
+     * Echo hex representation of UCS4 sequence.
+     *
+     * @param    array      $input       UCS4 sequence
+     * @param    boolean    $include_bit Include bitmask in output
+     * @return   void
+     * @static
+     * @access   private
+     */
+    private static function _showHex($input, $include_bit = false)
+    {
+        foreach ($input as $k => $v) {
+            echo '[', $k, '] => ', sprintf('%X', $v);
+
+            if ($include_bit) {
+                echo ' (', Net_IDNA::_showBitmask($v), ')';
+            }
+
+            echo "\n";
+        }
+    }
+
+    /**
+     * Gives you a bit representation of given Byte (8 bits), Word (16 bits) or DWord (32 bits)
+     * Output width is automagically determined
+     *
+     * @static
+     * @access   private
+     */
+    private static function _showBitmask($octet)
+    {
+        if ($octet >= (1 << 16)) {
+            $w = 31;
+        } else if ($octet >= (1 << 8)) {
+            $w = 15;
+        } else {
+            $w = 7;
+        }
+
+        $return = '';
+
+        for ($i = $w; $i > -1; $i--) {
+            $return .= ($octet & (1 << $i))? 1 : '0';
+        }
+
+        return $return;
+    }
+
+    /**
+     * Gets the length of a string in bytes even if mbstring function
+     * overloading is turned on
+     *
+     * @param string $string the string for which to get the length.
+     *
+     * @return integer the length of the string in bytes.
+     *
+     * @see Net_IDNA_php5::$_mb_string_overload
+     */
+    private static function _byteLength($string)
+    {
+        if (self::$_mb_string_overload) {
+            return mb_strlen($string, '8bit');
+        }
+        return strlen((binary)$string);
+    }
+
+    // }}}}
+}
+
+?>
index 9a7e27fa2c9c50b310c7e5ae0abf2c936cccf976..08555d19b9515ea2773a774e3403a0287e74fbdd 100644 (file)
@@ -1,8 +1,7 @@
-
 <?php
 /**
  * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2009, StatusNet, Inc.
+ * Copyright (C) 2009-2010, StatusNet, Inc.
  *
  * 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
 
 define('INSTALLDIR', dirname(__FILE__));
 
-$external_libraries=array(
-    array(
-        'name'=>'gettext',
-        'url'=>'http://us.php.net/manual/en/book.gettext.php',
-        'check_function'=>'gettext'
-    ),
-    array(
-        'name'=>'PEAR',
-        'url'=>'http://pear.php.net/',
-        'deb'=>'php-pear',
-        'include'=>'PEAR.php',
-        'check_class'=>'PEAR'
-    ),
-    array(
-        'name'=>'DB',
-        'pear'=>'DB',
-        'url'=>'http://pear.php.net/package/DB',
-        'deb'=>'php-db',
-        'include'=>'DB/common.php',
-        'check_class'=>'DB_common'
-    ),
-    array(
-        'name'=>'DB_DataObject',
-        'pear'=>'DB_DataObject',
-        'url'=>'http://pear.php.net/package/DB_DataObject',
-        'include'=>'DB/DataObject.php',
-        'check_class'=>'DB_DataObject'
-    ),
-    array(
-        'name'=>'Console_Getopt',
-        'pear'=>'Console_Getopt',
-        'url'=>'http://pear.php.net/package/Console_Getopt',
-        'include'=>'Console/Getopt.php',
-        'check_class'=>'Console_Getopt'
-    ),
-    array(
-        'name'=>'Facebook API',
-        'url'=>'http://developers.facebook.com/',
-        'include'=>'facebook/facebook.php',
-        'check_class'=>'Facebook'
-    ),
-    array(
-        'name'=>'htmLawed',
-        'url'=>'http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed',
-        'include'=>'htmLawed/htmLawed.php',
-        'check_function'=>'htmLawed'
-    ),
-    array(
-        'name'=>'HTTP_Request',
-        'pear'=>'HTTP_Request',
-        'url'=>'http://pear.php.net/package/HTTP_Request',
-        'deb'=>'php-http-request',
-        'include'=>'HTTP/Request.php',
-        'check_class'=>'HTTP_Request'
-    ),
-    array(
-        'name'=>'HTTP_Request2',
-        'pear'=>'HTTP_Request2',
-        'url'=>'http://pear.php.net/package/HTTP_Request2',
-        'include'=>'HTTP/Request2.php',
-        'check_class'=>'HTTP_Request2'
-    ),
-    array(
-        'name'=>'Mail',
-        'pear'=>'Mail',
-        'url'=>'http://pear.php.net/package/Mail',
-        'deb'=>'php-mail',
-        'include'=>'Mail.php',
-        'check_class'=>'Mail'
-    ),
-    array(
-        'name'=>'Mail_mimeDecode',
-        'pear'=>'Mail_mimeDecode',
-        'url'=>'http://pear.php.net/package/Mail_mimeDecode',
-        'deb'=>'php-mail-mimedecode',
-        'include'=>'Mail/mimeDecode.php',
-        'check_class'=>'Mail_mimeDecode'
-    ),
-    array(
-        'name'=>'Mime_Type',
-        'pear'=>'Mime_Type',
-        'url'=>'http://pear.php.net/package/Mime_Type',
-        'include'=>'MIME/Type.php',
-        'check_class'=>'Mime_Type'
-    ),
-    array(
-        'name'=>'Net_URL_Mapper',
-        'pear'=>'Net_URL_Mapper',
-        'url'=>'http://pear.php.net/package/Net_URL_Mapper',
-        'include'=>'Net/URL/Mapper.php',
-        'check_class'=>'Net_URL_Mapper'
-    ),
-    array(
-        'name'=>'Net_LDAP2',
-        'pear'=>'Net_LDAP2',
-        'url'=>'http://pear.php.net/package/Net_LDAP2',
-        'deb'=>'php-net-ldap2',
-        'include'=>'Net/LDAP2.php',
-        'check_class'=>'Net_LDAP2'
-    ),
-    array(
-        'name'=>'Net_Socket',
-        'pear'=>'Net_Socket',
-        'url'=>'http://pear.php.net/package/Net_Socket',
-        'deb'=>'php-net-socket',
-        'include'=>'Net/Socket.php',
-        'check_class'=>'Net_Socket'
-    ),
-    array(
-        'name'=>'Net_SMTP',
-        'pear'=>'Net_SMTP',
-        'url'=>'http://pear.php.net/package/Net_SMTP',
-        'deb'=>'php-net-smtp',
-        'include'=>'Net/SMTP.php',
-        'check_class'=>'Net_SMTP'
-    ),
-    array(
-        'name'=>'Net_URL',
-        'pear'=>'Net_URL',
-        'url'=>'http://pear.php.net/package/Net_URL',
-        'deb'=>'php-net-url',
-        'include'=>'Net/URL.php',
-        'check_class'=>'Net_URL'
-    ),
-    array(
-        'name'=>'Net_URL2',
-        'pear'=>'Net_URL2',
-        'url'=>'http://pear.php.net/package/Net_URL2',
-        'include'=>'Net/URL2.php',
-        'check_class'=>'Net_URL2'
-    ),
-    array(
-        'name'=>'Services_oEmbed',
-        'pear'=>'Services_oEmbed',
-        'url'=>'http://pear.php.net/package/Services_oEmbed',
-        'include'=>'Services/oEmbed.php',
-        'check_class'=>'Services_oEmbed'
-    ),
-    array(
-        'name'=>'Stomp',
-        'url'=>'http://stomp.codehaus.org/PHP',
-        'include'=>'Stomp.php',
-        'check_class'=>'Stomp'
-    ),
-    array(
-        'name'=>'System_Command',
-        'pear'=>'System_Command',
-        'url'=>'http://pear.php.net/package/System_Command',
-        'include'=>'System/Command.php',
-        'check_class'=>'System_Command'
-    ),
-    array(
-        'name'=>'XMPPHP',
-        'url'=>'http://code.google.com/p/xmpphp',
-        'include'=>'XMPPHP/XMPP.php',
-        'check_class'=>'XMPPHP_XMPP'
-    ),
-    array(
-        'name'=>'PHP Markdown',
-        'url'=>'http://www.michelf.com/projects/php-markdown/',
-        'include'=>'markdown.php',
-        'check_class'=>'Markdown_Parser'
-    ),
-    array(
-        'name'=>'OAuth',
-        'url'=>'http://code.google.com/p/oauth-php',
-        'include'=>'OAuth.php',
-        'check_class'=>'OAuthRequest'
-    ),
-    array(
-        'name'=>'Validate',
-        'pear'=>'Validate',
-        'url'=>'http://pear.php.net/package/Validate',
-        'include'=>'Validate.php',
-        'check_class'=>'Validate'
-    )
-);
-$dbModules = array(
-    'mysql' => array(
-        'name' => 'MySQL',
-        'check_module' => 'mysql', // mysqli?
-        'installer' => 'mysql_db_installer',
-    ),
-    'pgsql' => array(
-        'name' => 'PostgreSQL',
-        'check_module' => 'pgsql',
-        'installer' => 'pgsql_db_installer',
-    ),
-);
+require INSTALLDIR . '/lib/installer.php';
 
 /**
- * the actual installation.
- * If call libraries are present, then install
- *
- * @return void
+ * Helper class for building form
  */
-function main()
-{
-    if (!checkPrereqs()) {
-        return;
-    }
-
-    if (!empty($_GET['checklibs'])) {
-        showLibs();
-    } else {
-        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
-            handlePost();
+class Posted {
+    function value($name)
+    {
+        if (isset($_POST[$name])) {
+            return htmlspecialchars(strval($_POST[$name]));
         } else {
-            showForm();
+            return '';
         }
     }
 }
 
 /**
- * checks if an external libary is present
- *
- * @param string $external_library Name of library
- *
- * @return boolean indicates if library present
- */
-function haveExternalLibrary($external_library)
-{
-    if (isset($external_library['include']) && !haveIncludeFile($external_library['include'])) {
-        return false;
-    }
-    if (isset($external_library['check_function']) && ! function_exists($external_library['check_function'])) {
-        return false;
-    }
-    if (isset($external_library['check_class']) && ! class_exists($external_library['check_class'])) {
-        return false;
-    }
-    return true;
-}
-
-// Attempt to include a PHP file and report if it worked, while
-// suppressing the annoying warning messages on failure.
-function haveIncludeFile($filename) {
-    $old = error_reporting(error_reporting() & ~E_WARNING);
-    $ok = include_once($filename);
-    error_reporting($old);
-    return $ok;
-}
-
-/**
- * Check if all is ready for installation
- *
- * @return void
+ * Web-based installer: provides a form and such.
  */
-function checkPrereqs()
+class WebInstaller extends Installer
 {
-    $pass = true;
-
-    if (file_exists(INSTALLDIR.'/config.php')) {
-         printf('<p class="error">Config file &quot;config.php&quot; already exists.</p>');
-        $pass = false;
-    }
-
-    if (version_compare(PHP_VERSION, '5.2.3', '<')) {
-        printf('<p class="error">Require PHP version 5.2.3 or greater.</p>');
-        $pass = false;
-    }
-
-    // Look for known library bugs
-    $str = "abcdefghijklmnopqrstuvwxyz";
-    $replaced = preg_replace('/[\p{Cc}\p{Cs}]/u', '*', $str);
-    if ($str != $replaced) {
-        printf('<p class="error">PHP is linked to a version of the PCRE library ' .
-               'that does not support Unicode properties. ' .
-               'If you are running Red Hat Enterprise Linux / ' .
-               'CentOS 5.4 or earlier, see <a href="' .
-               'http://status.net/wiki/Red_Hat_Enterprise_Linux#PCRE_library' .
-               '">our documentation page</a> on fixing this.</p>');
-        $pass = false;
-    }
-
-    $reqs = array('gd', 'curl',
-                  'xmlwriter', 'mbstring', 'xml', 'dom', 'simplexml');
-
-    foreach ($reqs as $req) {
-        if (!checkExtension($req)) {
-            printf('<p class="error">Cannot load required extension: <code>%s</code></p>', $req);
-            $pass = false;
-        }
-    }
-    // Make sure we have at least one database module available
-    global $dbModules;
-    $missingExtensions = array();
-    foreach ($dbModules as $type => $info) {
-        if (!checkExtension($info['check_module'])) {
-            $missingExtensions[] = $info['check_module'];
+    /**
+     * the actual installation.
+     * If call libraries are present, then install
+     *
+     * @return void
+     */
+    function main()
+    {
+        if (!$this->checkPrereqs()) {
+            $this->showForm();
+            return;
         }
-    }
-
-    if (count($missingExtensions) == count($dbModules)) {
-        $req = implode(', ', $missingExtensions);
-        printf('<p class="error">Cannot find mysql or pgsql extension. You need one or the other.');
-        $pass = false;
-    }
-
-    if (!is_writable(INSTALLDIR)) {
-        printf('<p class="error">Cannot write config file to: <code>%s</code></p>', INSTALLDIR);
-        printf('<p>On your server, try this command: <code>chmod a+w %s</code>', INSTALLDIR);
-        $pass = false;
-    }
 
-    // Check the subdirs used for file uploads
-    $fileSubdirs = array('avatar', 'background', 'file');
-    foreach ($fileSubdirs as $fileSubdir) {
-        $fileFullPath = INSTALLDIR."/$fileSubdir/";
-        if (!is_writable($fileFullPath)) {
-            printf('<p class="error">Cannot write to %s directory: <code>%s</code></p>', $fileSubdir, $fileFullPath);
-            printf('<p>On your server, try this command: <code>chmod a+w %s</code></p>', $fileFullPath);
-            $pass = false;
+        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+            $this->handlePost();
+        } else {
+            $this->showForm();
         }
     }
 
-    return $pass;
-}
-
-/**
- * Checks if a php extension is both installed and loaded
- *
- * @param string $name of extension to check
- *
- * @return boolean whether extension is installed and loaded
- */
-function checkExtension($name)
-{
-    if (extension_loaded($name)) {
-        return true;
-    } elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) {
-        // dl will throw a fatal error if it's disabled or we're in safe mode.
-        // More fun, it may not even exist under some SAPIs in 5.3.0 or later...
-        $soname = $name . '.' . PHP_SHLIB_SUFFIX;
-        if (PHP_SHLIB_SUFFIX == 'dll') {
-            $soname = "php_" . $soname;
+    /**
+     * Web implementation of warning output
+     */
+    function warning($message, $submessage='')
+    {
+        print "<p class=\"error\">$message</p>\n";
+        if ($submessage != '') {
+            print "<p>$submessage</p>\n";
         }
-        return @dl($soname);
-    } else {
-        return false;
     }
-}
 
-/**
- * Show list of libraries
- *
- * @return void
- */
-function showLibs()
-{
-    global $external_libraries;
-    $present_libraries=array();
-    $absent_libraries=array();
-    foreach ($external_libraries as $external_library) {
-        if (haveExternalLibrary($external_library)) {
-            $present_libraries[]=$external_library;
-        } else {
-            $absent_libraries[]=$external_library;
-        }
-    }
-    echo<<<E_O_T
-    <div class="instructions">
-        <p>StatusNet comes bundled with a number of libraries required for the application to work. However, it is best that you use PEAR or you distribution to manage
-        libraries instead, as they tend to provide security updates faster, and may offer improved performance.</p>
-        <p>On Debian based distributions, such as Ubuntu, use a package manager (such as &quot;aptitude&quot;, &quot;apt-get&quot;, and &quot;synaptic&quot;) to install the package listed.</p>
-        <p>On RPM based distributions, such as Red Hat, Fedora, CentOS, Scientific Linux, Yellow Dog Linux and Oracle Enterprise Linux, use a package manager (such as &quot;yum&quot;, &quot;apt-rpm&quot;, and &quot;up2date&quot;) to install the package listed.</p>
-        <p>On servers without a package manager (such as Windows), or if the library is not packaged for your distribution, you can use PHP's PEAR to install the library. Simply run &quot;pear install &lt;name&gt;&quot;.</p>
-    </div>
-    <h2>Absent Libraries</h2>
-    <ul id="absent_libraries">
-E_O_T;
-    foreach ($absent_libraries as $library) {
-        echo '<li>';
-        if (isset($library['url'])) {
-            echo '<a href="'.$library['url'].'">'.htmlentities($library['name']).'</a>';
-        } else {
-            echo htmlentities($library['name']);
-        }
-        echo '<ul>';
-        if (isset($library['deb'])) {
-            echo '<li class="deb package">deb: <a href="apt:' . urlencode($library['deb']) . '">' . htmlentities($library['deb']) . '</a></li>';
-        }
-        if (isset($library['rpm'])) {
-            echo '<li class="rpm package">rpm: ' . htmlentities($library['rpm']) . '</li>';
-        }
-        if (isset($library['pear'])) {
-            echo '<li class="pear package">pear: ' . htmlentities($library['pear']) . '</li>';
-        }
-        echo '</ul>';
-    }
-    echo<<<E_O_T
-    </ul>
-    <h2>Installed Libraries</h2>
-    <ul id="present_libraries">
-E_O_T;
-    foreach ($present_libraries as $library) {
-        echo '<li>';
-        if (isset($library['url'])) {
-            echo '<a href="'.$library['url'].'">'.htmlentities($library['name']).'</a>';
-        } else {
-            echo htmlentities($library['name']);
-        }
-        echo '</li>';
+    /**
+     * Web implementation of status output
+     */
+    function updateStatus($status, $error=false)
+    {
+        echo '<li' . ($error ? ' class="error"': '' ) . ">$status</li>";
     }
-    echo<<<E_O_T
-    </ul>
-E_O_T;
-}
 
-/**
- * Helper class for building form
- */
-class Posted {
-    function value($name)
+    /**
+     * Show the web form!
+     */
+    function showForm()
     {
-        if (isset($_POST[$name])) {
-            return htmlspecialchars(strval($_POST[$name]));
+        global $dbModules;
+        $post = new Posted();
+        $dbRadios = '';
+        if (isset($_POST['dbtype'])) {
+            $dbtype = $_POST['dbtype'];
         } else {
-            return '';
+            $dbtype = null;
         }
-    }
-}
-
-function showForm()
-{
-    global $dbModules;
-    $post = new Posted();
-    $dbRadios = '';
-    if (isset($_POST['dbtype'])) {
-        $dbtype = $_POST['dbtype'];
-    } else {
-        $dbtype = null;
-    }
-    foreach ($dbModules as $type => $info) {
-        if (checkExtension($info['check_module'])) {
-            if ($dbtype == null || $dbtype == $type) {
-                $checked = 'checked="checked" ';
-                $dbtype = $type; // if we didn't have one checked, hit the first
-            } else {
-                $checked = '';
+        foreach (self::$dbModules as $type => $info) {
+            if ($this->checkExtension($info['check_module'])) {
+                if ($dbtype == null || $dbtype == $type) {
+                    $checked = 'checked="checked" ';
+                    $dbtype = $type; // if we didn't have one checked, hit the first
+                } else {
+                    $checked = '';
+                }
+                $dbRadios .= "<input type=\"radio\" name=\"dbtype\" id=\"dbtype-$type\" value=\"$type\" $checked/> $info[name]<br />\n";
             }
-            $dbRadios .= "<input type=\"radio\" name=\"dbtype\" id=\"dbtype-$type\" value=\"$type\" $checked/> $info[name]<br />\n";
         }
-    }
 
-    echo<<<E_O_T
-        </ul>
-    </dd>
-</dl>
-<form method="post" action="install.php" class="form_settings" id="form_install">
-    <fieldset>
-        <fieldset id="settings_site">
-            <legend>Site settings</legend>
-            <ul class="form_data">
-                <li>
-                    <label for="sitename">Site name</label>
-                    <input type="text" id="sitename" name="sitename" value="{$post->value('sitename')}" />
-                    <p class="form_guide">The name of your site</p>
-                </li>
-                <li>
-                    <label for="fancy-enable">Fancy URLs</label>
-                    <input type="radio" name="fancy" id="fancy-enable" value="enable" checked='checked' /> enable<br />
-                    <input type="radio" name="fancy" id="fancy-disable" value="" /> disable<br />
-                    <p class="form_guide" id='fancy-form_guide'>Enable fancy (pretty) URLs. Auto-detection failed, it depends on Javascript.</p>
-                </li>
-            </ul>
+        echo<<<E_O_T
+    <form method="post" action="install.php" class="form_settings" id="form_install">
+        <fieldset>
+            <fieldset id="settings_site">
+                <legend>Site settings</legend>
+                <ul class="form_data">
+                    <li>
+                        <label for="sitename">Site name</label>
+                        <input type="text" id="sitename" name="sitename" value="{$post->value('sitename')}" />
+                        <p class="form_guide">The name of your site</p>
+                    </li>
+                    <li>
+                        <label for="fancy-enable">Fancy URLs</label>
+                        <input type="radio" name="fancy" id="fancy-enable" value="enable" checked='checked' /> enable<br />
+                        <input type="radio" name="fancy" id="fancy-disable" value="" /> disable<br />
+                        <p class="form_guide" id='fancy-form_guide'>Enable fancy (pretty) URLs. Auto-detection failed, it depends on Javascript.</p>
+                    </li>
+                </ul>
+            </fieldset>
+
+            <fieldset id="settings_db">
+                <legend>Database settings</legend>
+                <ul class="form_data">
+                    <li>
+                        <label for="host">Hostname</label>
+                        <input type="text" id="host" name="host" value="{$post->value('host')}" />
+                        <p class="form_guide">Database hostname</p>
+                    </li>
+                    <li>
+                        <label for="dbtype">Type</label>
+                        $dbRadios
+                        <p class="form_guide">Database type</p>
+                    </li>
+                    <li>
+                        <label for="database">Name</label>
+                        <input type="text" id="database" name="database" value="{$post->value('database')}" />
+                        <p class="form_guide">Database name</p>
+                    </li>
+                    <li>
+                        <label for="dbusername">DB username</label>
+                        <input type="text" id="dbusername" name="dbusername" value="{$post->value('dbusername')}" />
+                        <p class="form_guide">Database username</p>
+                    </li>
+                    <li>
+                        <label for="dbpassword">DB password</label>
+                        <input type="password" id="dbpassword" name="dbpassword" value="{$post->value('dbpassword')}" />
+                        <p class="form_guide">Database password (optional)</p>
+                    </li>
+                </ul>
+            </fieldset>
+
+            <fieldset id="settings_admin">
+                <legend>Administrator settings</legend>
+                <ul class="form_data">
+                    <li>
+                        <label for="admin_nickname">Administrator nickname</label>
+                        <input type="text" id="admin_nickname" name="admin_nickname" value="{$post->value('admin_nickname')}" />
+                        <p class="form_guide">Nickname for the initial StatusNet user (administrator)</p>
+                    </li>
+                    <li>
+                        <label for="admin_password">Administrator password</label>
+                        <input type="password" id="admin_password" name="admin_password" value="{$post->value('admin_password')}" />
+                        <p class="form_guide">Password for the initial StatusNet user (administrator)</p>
+                    </li>
+                    <li>
+                        <label for="admin_password2">Confirm password</label>
+                        <input type="password" id="admin_password2" name="admin_password2" value="{$post->value('admin_password2')}" />
+                    </li>
+                    <li>
+                        <label for="admin_email">Administrator e-mail</label>
+                        <input id="admin_email" name="admin_email" value="{$post->value('admin_email')}" />
+                        <p class="form_guide">Optional email address for the initial StatusNet user (administrator)</p>
+                    </li>
+                    <li>
+                        <label for="admin_updates">Subscribe to announcements</label>
+                        <input type="checkbox" id="admin_updates" name="admin_updates" value="true" checked="checked" />
+                        <p class="form_guide">Release and security feed from <a href="http://update.status.net/">update@status.net</a> (recommended)</p>
+                    </li>
+                </ul>
+            </fieldset>
+            <input type="submit" name="submit" class="submit" value="Submit" />
         </fieldset>
-
-        <fieldset id="settings_db">
-            <legend>Database settings</legend>
-            <ul class="form_data">
-                <li>
-                    <label for="host">Hostname</label>
-                    <input type="text" id="host" name="host" value="{$post->value('host')}" />
-                    <p class="form_guide">Database hostname</p>
-                </li>
-                <li>
-                    <label for="dbtype">Type</label>
-                    $dbRadios
-                    <p class="form_guide">Database type</p>
-                </li>
-                <li>
-                    <label for="database">Name</label>
-                    <input type="text" id="database" name="database" value="{$post->value('database')}" />
-                    <p class="form_guide">Database name</p>
-                </li>
-                <li>
-                    <label for="dbusername">DB username</label>
-                    <input type="text" id="dbusername" name="dbusername" value="{$post->value('dbusername')}" />
-                    <p class="form_guide">Database username</p>
-                </li>
-                <li>
-                    <label for="dbpassword">DB password</label>
-                    <input type="password" id="dbpassword" name="dbpassword" value="{$post->value('dbpassword')}" />
-                    <p class="form_guide">Database password (optional)</p>
-                </li>
-            </ul>
-        </fieldset>
-
-        <fieldset id="settings_admin">
-            <legend>Administrator settings</legend>
-            <ul class="form_data">
-                <li>
-                    <label for="admin_nickname">Administrator nickname</label>
-                    <input type="text" id="admin_nickname" name="admin_nickname" value="{$post->value('admin_nickname')}" />
-                    <p class="form_guide">Nickname for the initial StatusNet user (administrator)</p>
-                </li>
-                <li>
-                    <label for="admin_password">Administrator password</label>
-                    <input type="password" id="admin_password" name="admin_password" value="{$post->value('admin_password')}" />
-                    <p class="form_guide">Password for the initial StatusNet user (administrator)</p>
-                </li>
-                <li>
-                    <label for="admin_password2">Confirm password</label>
-                    <input type="password" id="admin_password2" name="admin_password2" value="{$post->value('admin_password2')}" />
-                </li>
-                <li>
-                    <label for="admin_email">Administrator e-mail</label>
-                    <input id="admin_email" name="admin_email" value="{$post->value('admin_email')}" />
-                    <p class="form_guide">Optional email address for the initial StatusNet user (administrator)</p>
-                </li>
-                <li>
-                    <label for="admin_updates">Subscribe to announcements</label>
-                    <input type="checkbox" id="admin_updates" name="admin_updates" value="true" checked="checked" />
-                    <p class="form_guide">Release and security feed from <a href="http://update.status.net/">update@status.net</a> (recommended)</p>
-                </li>
-            </ul>
-        </fieldset>
-        <input type="submit" name="submit" class="submit" value="Submit" />
-    </fieldset>
-</form>
+    </form>
 
 E_O_T;
-}
-
-function updateStatus($status, $error=false)
-{
-    echo '<li' . ($error ? ' class="error"': '' ) . ">$status</li>";
-}
-
-function handlePost()
-{
-    $host     = $_POST['host'];
-    $dbtype   = $_POST['dbtype'];
-    $database = $_POST['database'];
-    $username = $_POST['dbusername'];
-    $password = $_POST['dbpassword'];
-    $sitename = $_POST['sitename'];
-    $fancy    = !empty($_POST['fancy']);
-
-    $adminNick = strtolower($_POST['admin_nickname']);
-    $adminPass = $_POST['admin_password'];
-    $adminPass2 = $_POST['admin_password2'];
-    $adminEmail = $_POST['admin_email'];
-    $adminUpdates = $_POST['admin_updates'];
-
-    $server = $_SERVER['HTTP_HOST'];
-    $path = substr(dirname($_SERVER['PHP_SELF']), 1);
-
-    echo <<<STR
-    <dl class="system_notice">
-        <dt>Page notice</dt>
-        <dd>
-            <ul>
-STR;
-    $fail = false;
-
-    if (empty($host)) {
-        updateStatus("No hostname specified.", true);
-        $fail = true;
-    }
-
-    if (empty($database)) {
-        updateStatus("No database specified.", true);
-        $fail = true;
-    }
-
-    if (empty($username)) {
-        updateStatus("No username specified.", true);
-        $fail = true;
-    }
-
-    if (empty($sitename)) {
-        updateStatus("No sitename specified.", true);
-        $fail = true;
-    }
-
-    if (empty($adminNick)) {
-        updateStatus("No initial StatusNet user nickname specified.", true);
-        $fail = true;
-    }
-    if ($adminNick && !preg_match('/^[0-9a-z]{1,64}$/', $adminNick)) {
-        updateStatus('The user nickname "' . htmlspecialchars($adminNick) .
-                     '" is invalid; should be plain letters and numbers no longer than 64 characters.', true);
-        $fail = true;
-    }
-    // @fixme hardcoded list; should use User::allowed_nickname()
-    // if/when it's safe to have loaded the infrastructure here
-    $blacklist = array('main', 'admin', 'twitter', 'settings', 'rsd.xml', 'favorited', 'featured', 'favoritedrss', 'featuredrss', 'rss', 'getfile', 'api', 'groups', 'group', 'peopletag', 'tag', 'user', 'message', 'conversation', 'bookmarklet', 'notice', 'attachment', 'search', 'index.php', 'doc', 'opensearch', 'robots.txt', 'xd_receiver.html', 'facebook');
-    if (in_array($adminNick, $blacklist)) {
-        updateStatus('The user nickname "' . htmlspecialchars($adminNick) .
-                     '" is reserved.', true);
-        $fail = true;
-    }
-
-    if (empty($adminPass)) {
-        updateStatus("No initial StatusNet user password specified.", true);
-        $fail = true;
-    }
-    
-    if ($adminPass != $adminPass2) {
-        updateStatus("Administrator passwords do not match. Did you mistype?", true);
-        $fail = true;
-    }
-
-    if ($fail) {
-        showForm();
-        return;
-    }
-
-    global $dbModules;
-    $db = call_user_func($dbModules[$dbtype]['installer'], $host, $database, $username, $password);
-
-    if (!$db) {
-        // database connection failed, do not move on to create config file.
-        return false;
-    }
-
-    updateStatus("Writing config file...");
-    $res = writeConf($sitename, $server, $path, $fancy, $db);
-
-    if (!$res) {
-        updateStatus("Can't write config file.", true);
-        showForm();
-        return;
-    }
-
-    // Okay, cross fingers and try to register an initial user
-    if (registerInitialUser($adminNick, $adminPass, $adminEmail, $adminUpdates)) {
-        updateStatus(
-            "An initial user with the administrator role has been created."
-        );
-    } else {
-        updateStatus(
-            "Could not create initial StatusNet user (administrator).",
-            true
-        );
-        showForm();
-        return;
-    }
-
-    /*
-        TODO https needs to be considered
-    */
-    $link = "http://".$server.'/'.$path;
-
-    updateStatus("StatusNet has been installed at $link");
-    updateStatus(
-        "<strong>DONE!</strong> You can visit your <a href='$link'>new StatusNet site</a> (login as '$adminNick'). If this is your first StatusNet install, you may want to poke around our <a href='http://status.net/wiki/Getting_started'>Getting Started guide</a>."
-    );
-}
-
-function Pgsql_Db_installer($host, $database, $username, $password)
-{
-    $connstring = "dbname=$database host=$host user=$username";
-
-    //No password would mean trust authentication used.
-    if (!empty($password)) {
-        $connstring .= " password=$password";
-    }
-    updateStatus("Starting installation...");
-    updateStatus("Checking database...");
-    $conn = pg_connect($connstring);
-
-    if ($conn ===false) {
-        updateStatus("Failed to connect to database: $connstring");
-        showForm();
-        return false;
     }
 
-    //ensure database encoding is UTF8
-    $record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding'));
-    if ($record->server_encoding != 'UTF8') {
-        updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
-        showForm();
-        return false;
-    }
-
-    updateStatus("Running database script...");
-    //wrap in transaction;
-    pg_query($conn, 'BEGIN');
-    $res = runDbScript(INSTALLDIR.'/db/statusnet_pg.sql', $conn, 'pgsql');
-
-    if ($res === false) {
-        updateStatus("Can't run database script.", true);
-        showForm();
-        return false;
-    }
-    foreach (array('sms_carrier' => 'SMS carrier',
-                'notice_source' => 'notice source',
-                'foreign_services' => 'foreign service')
-          as $scr => $name) {
-        updateStatus(sprintf("Adding %s data to database...", $name));
-        $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn, 'pgsql');
-        if ($res === false) {
-            updateStatus(sprintf("Can't run %d script.", $name), true);
-            showForm();
-            return false;
+    /**
+     * Handle a POST submission... if we have valid input, start the install!
+     * Otherwise shows the form along with any error messages.
+     */
+    function handlePost()
+    {
+        echo <<<STR
+        <dl class="system_notice">
+            <dt>Page notice</dt>
+            <dd>
+                <ul>
+STR;
+        $this->validated = $this->prepare();
+        if ($this->validated) {
+            $this->doInstall();
         }
-    }
-    pg_query($conn, 'COMMIT');
-
-    if (empty($password)) {
-        $sqlUrl = "pgsql://$username@$host/$database";
-    } else {
-        $sqlUrl = "pgsql://$username:$password@$host/$database";
-    }
-
-    $db = array('type' => 'pgsql', 'database' => $sqlUrl);
-
-    return $db;
-}
-
-function Mysql_Db_installer($host, $database, $username, $password)
-{
-    updateStatus("Starting installation...");
-    updateStatus("Checking database...");
-
-    $conn = mysql_connect($host, $username, $password);
-    if (!$conn) {
-        updateStatus("Can't connect to server '$host' as '$username'.", true);
-        showForm();
-        return false;
-    }
-    updateStatus("Changing to database...");
-    $res = mysql_select_db($database, $conn);
-    if (!$res) {
-        updateStatus("Can't change to database.", true);
-        showForm();
-        return false;
-    }
-    updateStatus("Running database script...");
-    $res = runDbScript(INSTALLDIR.'/db/statusnet.sql', $conn);
-    if ($res === false) {
-        updateStatus("Can't run database script.", true);
-        showForm();
-        return false;
-    }
-    foreach (array('sms_carrier' => 'SMS carrier',
-                'notice_source' => 'notice source',
-                'foreign_services' => 'foreign service')
-          as $scr => $name) {
-        updateStatus(sprintf("Adding %s data to database...", $name));
-        $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn);
-        if ($res === false) {
-            updateStatus(sprintf("Can't run %d script.", $name), true);
-            showForm();
-            return false;
+        echo <<<STR
+            </ul>
+        </dd>
+    </dl>
+STR;
+        if (!$this->validated) {
+            $this->showForm();
         }
     }
 
-    $sqlUrl = "mysqli://$username:$password@$host/$database";
-    $db = array('type' => 'mysql', 'database' => $sqlUrl);
-    return $db;
-}
-
-function writeConf($sitename, $server, $path, $fancy, $db)
-{
-    // assemble configuration file in a string
-    $cfg =  "<?php\n".
-            "if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n".
-
-            // site name
-            "\$config['site']['name'] = '$sitename';\n\n".
-
-            // site location
-            "\$config['site']['server'] = '$server';\n".
-            "\$config['site']['path'] = '$path'; \n\n".
-
-            // checks if fancy URLs are enabled
-            ($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
-
-            // database
-            "\$config['db']['database'] = '{$db['database']}';\n\n".
-            ($db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
-            "\$config['db']['type'] = '{$db['type']}';\n\n";
-    // write configuration file out to install directory
-    $res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
-
-    return $res;
-}
-
-/**
- * Install schema into the database
- *
- * @param string $filename location of database schema file
- * @param dbconn $conn     connection to database
- * @param string $type     type of database, currently mysql or pgsql
- *
- * @return boolean - indicating success or failure
- */
-function runDbScript($filename, $conn, $type = 'mysqli')
-{
-    $sql = trim(file_get_contents($filename));
-    $stmts = explode(';', $sql);
-    foreach ($stmts as $stmt) {
-        $stmt = trim($stmt);
-        if (!mb_strlen($stmt)) {
-            continue;
-        }
-        // FIXME: use PEAR::DB or PDO instead of our own switch
-        switch ($type) {
-        case 'mysqli':
-            $res = mysql_query($stmt, $conn);
-            if ($res === false) {
-                $error = mysql_error();
-            }
-            break;
-        case 'pgsql':
-            $res = pg_query($conn, $stmt);
-            if ($res === false) {
-                $error = pg_last_error();
-            }
-            break;
-        default:
-            updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
-        }
-        if ($res === false) {
-            updateStatus("ERROR ($error) for SQL '$stmt'");
-            return $res;
+    /**
+     * Read and validate input data.
+     * May output side effects.
+     * 
+     * @return boolean success
+     */
+    function prepare()
+    {
+        $this->host     = $_POST['host'];
+        $this->dbtype   = $_POST['dbtype'];
+        $this->database = $_POST['database'];
+        $this->username = $_POST['dbusername'];
+        $this->password = $_POST['dbpassword'];
+        $this->sitename = $_POST['sitename'];
+        $this->fancy    = !empty($_POST['fancy']);
+
+        $this->adminNick    = strtolower($_POST['admin_nickname']);
+        $this->adminPass    = $_POST['admin_password'];
+        $adminPass2         = $_POST['admin_password2'];
+        $this->adminEmail   = $_POST['admin_email'];
+        $this->adminUpdates = $_POST['admin_updates'];
+
+        $this->server = $_SERVER['HTTP_HOST'];
+        $this->path = substr(dirname($_SERVER['PHP_SELF']), 1);
+
+        $fail = false;
+        if (!$this->validateDb()) {
+            $fail = true;
         }
-    }
-    return true;
-}
 
-function registerInitialUser($nickname, $password, $email, $adminUpdates)
-{
-    define('STATUSNET', true);
-    define('LACONICA', true); // compatibility
-
-    require_once INSTALLDIR . '/lib/common.php';
-
-    $data = array('nickname' => $nickname,
-                  'password' => $password,
-                  'fullname' => $nickname);
-    if ($email) {
-        $data['email'] = $email;
-    }
-    $user = User::register($data);
-
-    if (empty($user)) {
-        return false;
-    }
-
-    // give initial user carte blanche
-
-    $user->grantRole('owner');
-    $user->grantRole('moderator');
-    $user->grantRole('administrator');
-    
-    // Attempt to do a remote subscribe to update@status.net
-    // Will fail if instance is on a private network.
-
-    if (class_exists('Ostatus_profile') && $adminUpdates) {
-        try {
-            $oprofile = Ostatus_profile::ensureProfile('http://update.status.net/');
-            Subscription::start($user->getProfile(), $oprofile->localProfile());
-            updateStatus("Set up subscription to <a href='http://update.status.net/'>update@status.net</a>.");
-        } catch (Exception $e) {
-            updateStatus("Could not set up subscription to <a href='http://update.status.net/'>update@status.net</a>.");
+        if (!$this->validateAdmin()) {
+            $fail = true;
         }
+        
+        if ($this->adminPass != $adminPass2) {
+            $this->updateStatus("Administrator passwords do not match. Did you mistype?", true);
+            $fail = true;
+        }
+        
+        return !$fail;
     }
 
-    return true;
 }
 
 ?>
@@ -945,7 +310,10 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                 <div id="content">
                      <div id="content_inner">
                         <h1>Install StatusNet</h1>
-<?php main(); ?>
+<?php 
+$installer = new WebInstaller();
+$installer->main();
+?>
                    </div>
                 </div>
             </div>
index ffeab70b3a6626eda08b1c2051a9ded122ad43da..fa4ece10addbb1fdab09bc607c27b8c9b615efb5 100644 (file)
@@ -72,6 +72,7 @@ $default =
               'quote_identifiers' => false,
               'type' => 'mysql',
               'schemacheck' => 'runtime', // 'runtime' or 'script'
+              'annotate_queries' => false, // true to add caller comments to queries, eg /* POST Notice::saveNew */
               'log_queries' => false, // true to log all DB queries
               'log_slow_queries' => 0), // if set, log queries taking over N seconds
         'syslog' =>
@@ -87,6 +88,8 @@ $default =
               'stomp_username' => null,
               'stomp_password' => null,
               'stomp_persistent' => true, // keep items across queue server restart, if persistence is enabled
+              'stomp_transactions' => true, // use STOMP transactions to aid in detecting failures (supported by ActiveMQ, but not by all)
+              'stomp_acks' => true, // send acknowledgements after successful processing (supported by ActiveMQ, but not by all)
               'stomp_manual_failover' => true, // if multiple servers are listed, treat them as separate (enqueue on one randomly, listen on all)
               'monitor' => null, // URL to monitor ping endpoint (work in progress)
               'softlimit' => '90%', // total size or % of memory_limit at which to restart queue threads gracefully
index 64a51353c76e106cdfc10f79b90c6337a7b806b6..384626ae06348de59dffc70af3a1a82338ed91e4 100644 (file)
@@ -43,6 +43,9 @@ require_once 'HTTP/Request2/Response.php';
  *
  * This extends the HTTP_Request2_Response class with methods to get info
  * about any followed redirects.
+ * 
+ * Originally used the name 'HTTPResponse' to match earlier code, but
+ * this conflicts with a class in in the PECL HTTP extension.
  *
  * @category HTTP
  * @package StatusNet
@@ -51,7 +54,7 @@ require_once 'HTTP/Request2/Response.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 HTTPResponse extends HTTP_Request2_Response
+class StatusNet_HTTPResponse extends HTTP_Request2_Response
 {
     function __construct(HTTP_Request2_Response $response, $url, $redirects=0)
     {
@@ -146,7 +149,7 @@ class HTTPClient extends HTTP_Request2
     /**
      * Convenience function to run a GET request.
      *
-     * @return HTTPResponse
+     * @return StatusNet_HTTPResponse
      * @throws HTTP_Request2_Exception
      */
     public function get($url, $headers=array())
@@ -157,7 +160,7 @@ class HTTPClient extends HTTP_Request2
     /**
      * Convenience function to run a HEAD request.
      *
-     * @return HTTPResponse
+     * @return StatusNet_HTTPResponse
      * @throws HTTP_Request2_Exception
      */
     public function head($url, $headers=array())
@@ -171,7 +174,7 @@ class HTTPClient extends HTTP_Request2
      * @param string $url
      * @param array $headers optional associative array of HTTP headers
      * @param array $data optional associative array or blob of form data to submit
-     * @return HTTPResponse
+     * @return StatusNet_HTTPResponse
      * @throws HTTP_Request2_Exception
      */
     public function post($url, $headers=array(), $data=array())
@@ -183,7 +186,7 @@ class HTTPClient extends HTTP_Request2
     }
 
     /**
-     * @return HTTPResponse
+     * @return StatusNet_HTTPResponse
      * @throws HTTP_Request2_Exception
      */
     protected function doRequest($url, $method, $headers)
@@ -217,12 +220,12 @@ class HTTPClient extends HTTP_Request2
     }
 
     /**
-     * Actually performs the HTTP request and returns an HTTPResponse object
-     * with response body and header info.
+     * Actually performs the HTTP request and returns a
+     * StatusNet_HTTPResponse object with response body and header info.
      *
      * Wraps around parent send() to add logging and redirection processing.
      *
-     * @return HTTPResponse
+     * @return StatusNet_HTTPResponse
      * @throw HTTP_Request2_Exception
      */
     public function send()
@@ -265,6 +268,6 @@ class HTTPClient extends HTTP_Request2
             }
             break;
         } while ($maxRedirs);
-        return new HTTPResponse($response, $this->getUrl(), $redirs);
+        return new StatusNet_HTTPResponse($response, $this->getUrl(), $redirs);
     }
 }
diff --git a/lib/installer.php b/lib/installer.php
new file mode 100644 (file)
index 0000000..d0e46f9
--- /dev/null
@@ -0,0 +1,578 @@
+<?php
+
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009, StatusNet, Inc.
+ *
+ * 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 Installation
+ * @package  Installation
+ *
+ * @author   Adrian Lang <mail@adrianlang.de>
+ * @author   Brenda Wallace <shiny@cpan.org>
+ * @author   Brett Taylor <brett@webfroot.co.nz>
+ * @author   Brion Vibber <brion@pobox.com>
+ * @author   CiaranG <ciaran@ciarang.com>
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Eric Helgeson <helfire@Erics-MBP.local>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @author   Sarven Capadisli <csarven@status.net>
+ * @author   Tom Adams <tom@holizz.com>
+ * @author   Zach Copley <zach@status.net>
+ * @license  GNU Affero General Public License http://www.gnu.org/licenses/
+ * @version  0.9.x
+ * @link     http://status.net
+ */
+
+abstract class Installer
+{
+    /** Web site info */
+    public $sitename, $server, $path, $fancy;
+    /** DB info */
+    public $host, $dbname, $dbtype, $username, $password, $db;
+    /** Administrator info */
+    public $adminNick, $adminPass, $adminEmail, $adminUpdates;
+    /** Should we skip writing the configuration file? */
+    public $skipConfig = false;
+
+    public static $dbModules = array(
+        'mysql' => array(
+            'name' => 'MySQL',
+            'check_module' => 'mysql', // mysqli?
+            'installer' => 'mysql_db_installer',
+        ),
+        'pgsql' => array(
+            'name' => 'PostgreSQL',
+            'check_module' => 'pgsql',
+            'installer' => 'pgsql_db_installer',
+        ),
+    );
+
+    /**
+     * Attempt to include a PHP file and report if it worked, while
+     * suppressing the annoying warning messages on failure.
+     */
+    private function haveIncludeFile($filename) {
+        $old = error_reporting(error_reporting() & ~E_WARNING);
+        $ok = include_once($filename);
+        error_reporting($old);
+        return $ok;
+    }
+    
+    /**
+     * Check if all is ready for installation
+     *
+     * @return void
+     */
+    function checkPrereqs()
+    {
+        $pass = true;
+
+        if (file_exists(INSTALLDIR.'/config.php')) {
+            $this->warning('Config file "config.php" already exists.');
+            $pass = false;
+        }
+
+        if (version_compare(PHP_VERSION, '5.2.3', '<')) {
+            $errors[] = 'Require PHP version 5.2.3 or greater.';
+            $pass = false;
+        }
+
+        // Look for known library bugs
+        $str = "abcdefghijklmnopqrstuvwxyz";
+        $replaced = preg_replace('/[\p{Cc}\p{Cs}]/u', '*', $str);
+        if ($str != $replaced) {
+            $this->warning('PHP is linked to a version of the PCRE library ' .
+                           'that does not support Unicode properties. ' .
+                           'If you are running Red Hat Enterprise Linux / ' .
+                           'CentOS 5.4 or earlier, see <a href="' .
+                           'http://status.net/wiki/Red_Hat_Enterprise_Linux#PCRE_library' .
+                           '">our documentation page</a> on fixing this.');
+            $pass = false;
+        }
+
+        $reqs = array('gd', 'curl',
+                      'xmlwriter', 'mbstring', 'xml', 'dom', 'simplexml');
+
+        foreach ($reqs as $req) {
+            if (!$this->checkExtension($req)) {
+                $this->warning(sprintf('Cannot load required extension: <code>%s</code>', $req));
+                $pass = false;
+            }
+        }
+
+        // Make sure we have at least one database module available
+        $missingExtensions = array();
+        foreach (self::$dbModules as $type => $info) {
+            if (!$this->checkExtension($info['check_module'])) {
+                $missingExtensions[] = $info['check_module'];
+            }
+        }
+
+        if (count($missingExtensions) == count(self::$dbModules)) {
+            $req = implode(', ', $missingExtensions);
+            $this->warning(sprintf('Cannot find a database extension. You need at least one of %s.', $req));
+            $pass = false;
+        }
+
+        if (!is_writable(INSTALLDIR)) {
+            $this->warning(sprintf('Cannot write config file to: <code>%s</code></p>', INSTALLDIR),
+                           sprintf('On your server, try this command: <code>chmod a+w %s</code>', INSTALLDIR));
+            $pass = false;
+        }
+
+        // Check the subdirs used for file uploads
+        $fileSubdirs = array('avatar', 'background', 'file');
+        foreach ($fileSubdirs as $fileSubdir) {
+            $fileFullPath = INSTALLDIR."/$fileSubdir/";
+            if (!is_writable($fileFullPath)) {
+                $this->warning(sprintf('Cannot write to %s directory: <code>%s</code>', $fileSubdir, $fileFullPath),
+                               sprintf('On your server, try this command: <code>chmod a+w %s</code>', $fileFullPath));
+                $pass = false;
+            }
+        }
+
+        return $pass;
+    }
+
+    /**
+     * Checks if a php extension is both installed and loaded
+     *
+     * @param string $name of extension to check
+     *
+     * @return boolean whether extension is installed and loaded
+     */
+    function checkExtension($name)
+    {
+        if (extension_loaded($name)) {
+            return true;
+        } elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) {
+            // dl will throw a fatal error if it's disabled or we're in safe mode.
+            // More fun, it may not even exist under some SAPIs in 5.3.0 or later...
+            $soname = $name . '.' . PHP_SHLIB_SUFFIX;
+            if (PHP_SHLIB_SUFFIX == 'dll') {
+                $soname = "php_" . $soname;
+            }
+            return @dl($soname);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Basic validation on the database paramters
+     * Side effects: error output if not valid
+     * 
+     * @return boolean success
+     */
+    function validateDb()
+    {
+        $fail = false;
+
+        if (empty($this->host)) {
+            $this->updateStatus("No hostname specified.", true);
+            $fail = true;
+        }
+
+        if (empty($this->database)) {
+            $this->updateStatus("No database specified.", true);
+            $fail = true;
+        }
+
+        if (empty($this->username)) {
+            $this->updateStatus("No username specified.", true);
+            $fail = true;
+        }
+
+        if (empty($this->sitename)) {
+            $this->updateStatus("No sitename specified.", true);
+            $fail = true;
+        }
+
+        return !$fail;
+    }
+
+    /**
+     * Basic validation on the administrator user paramters
+     * Side effects: error output if not valid
+     * 
+     * @return boolean success
+     */
+    function validateAdmin()
+    {
+        $fail = false;
+
+        if (empty($this->adminNick)) {
+            $this->updateStatus("No initial StatusNet user nickname specified.", true);
+            $fail = true;
+        }
+        if ($this->adminNick && !preg_match('/^[0-9a-z]{1,64}$/', $this->adminNick)) {
+            $this->updateStatus('The user nickname "' . htmlspecialchars($this->adminNick) .
+                         '" is invalid; should be plain letters and numbers no longer than 64 characters.', true);
+            $fail = true;
+        }
+        // @fixme hardcoded list; should use User::allowed_nickname()
+        // if/when it's safe to have loaded the infrastructure here
+        $blacklist = array('main', 'admin', 'twitter', 'settings', 'rsd.xml', 'favorited', 'featured', 'favoritedrss', 'featuredrss', 'rss', 'getfile', 'api', 'groups', 'group', 'peopletag', 'tag', 'user', 'message', 'conversation', 'bookmarklet', 'notice', 'attachment', 'search', 'index.php', 'doc', 'opensearch', 'robots.txt', 'xd_receiver.html', 'facebook');
+        if (in_array($this->adminNick, $blacklist)) {
+            $this->updateStatus('The user nickname "' . htmlspecialchars($this->adminNick) .
+                         '" is reserved.', true);
+            $fail = true;
+        }
+
+        if (empty($this->adminPass)) {
+            $this->updateStatus("No initial StatusNet user password specified.", true);
+            $fail = true;
+        }
+
+        return !$fail;
+    }
+
+    /**
+     * Set up the database with the appropriate function for the selected type...
+     * Saves database info into $this->db.
+     * 
+     * @return mixed array of database connection params on success, false on failure
+     */
+    function setupDatabase()
+    {
+        if ($this->db) {
+            throw new Exception("Bad order of operations: DB already set up.");
+        }
+        $method = self::$dbModules[$this->dbtype]['installer'];
+        $db = call_user_func(array($this, $method),
+                             $this->host,
+                             $this->database,
+                             $this->username,
+                             $this->password);
+        $this->db = $db;
+        return $this->db;
+    }
+
+    /**
+     * Set up a database on PostgreSQL.
+     * Will output status updates during the operation.
+     * 
+     * @param string $host
+     * @param string $database
+     * @param string $username
+     * @param string $password
+     * @return mixed array of database connection params on success, false on failure
+     * 
+     * @fixme escape things in the connection string in case we have a funny pass etc
+     */
+    function Pgsql_Db_installer($host, $database, $username, $password)
+    {
+        $connstring = "dbname=$database host=$host user=$username";
+
+        //No password would mean trust authentication used.
+        if (!empty($password)) {
+            $connstring .= " password=$password";
+        }
+        $this->updateStatus("Starting installation...");
+        $this->updateStatus("Checking database...");
+        $conn = pg_connect($connstring);
+
+        if ($conn ===false) {
+            $this->updateStatus("Failed to connect to database: $connstring");
+            return false;
+        }
+
+        //ensure database encoding is UTF8
+        $record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding'));
+        if ($record->server_encoding != 'UTF8') {
+            $this->updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
+            return false;
+        }
+
+        $this->updateStatus("Running database script...");
+        //wrap in transaction;
+        pg_query($conn, 'BEGIN');
+        $res = $this->runDbScript('statusnet_pg.sql', $conn, 'pgsql');
+
+        if ($res === false) {
+            $this->updateStatus("Can't run database script.", true);
+            return false;
+        }
+        foreach (array('sms_carrier' => 'SMS carrier',
+                    'notice_source' => 'notice source',
+                    'foreign_services' => 'foreign service')
+              as $scr => $name) {
+            $this->updateStatus(sprintf("Adding %s data to database...", $name));
+            $res = $this->runDbScript($scr.'.sql', $conn, 'pgsql');
+            if ($res === false) {
+                $this->updateStatus(sprintf("Can't run %d script.", $name), true);
+                return false;
+            }
+        }
+        pg_query($conn, 'COMMIT');
+
+        if (empty($password)) {
+            $sqlUrl = "pgsql://$username@$host/$database";
+        } else {
+            $sqlUrl = "pgsql://$username:$password@$host/$database";
+        }
+
+        $db = array('type' => 'pgsql', 'database' => $sqlUrl);
+
+        return $db;
+    }
+
+    /**
+     * Set up a database on MySQL.
+     * Will output status updates during the operation.
+     * 
+     * @param string $host
+     * @param string $database
+     * @param string $username
+     * @param string $password
+     * @return mixed array of database connection params on success, false on failure
+     * 
+     * @fixme be consistent about using mysqli vs mysql!
+     * @fixme escape things in the connection string in case we have a funny pass etc
+     */
+    function Mysql_Db_installer($host, $database, $username, $password)
+    {
+        $this->updateStatus("Starting installation...");
+        $this->updateStatus("Checking database...");
+
+        $conn = mysql_connect($host, $username, $password);
+        if (!$conn) {
+            $this->updateStatus("Can't connect to server '$host' as '$username'.", true);
+            return false;
+        }
+        $this->updateStatus("Changing to database...");
+        $res = mysql_select_db($database, $conn);
+        if (!$res) {
+            $this->updateStatus("Can't change to database.", true);
+            return false;
+        }
+
+        $this->updateStatus("Running database script...");
+        $res = $this->runDbScript('statusnet.sql', $conn);
+        if ($res === false) {
+            $this->updateStatus("Can't run database script.", true);
+            return false;
+        }
+        foreach (array('sms_carrier' => 'SMS carrier',
+                    'notice_source' => 'notice source',
+                    'foreign_services' => 'foreign service')
+              as $scr => $name) {
+            $this->updateStatus(sprintf("Adding %s data to database...", $name));
+            $res = $this->runDbScript($scr.'.sql', $conn);
+            if ($res === false) {
+                $this->updateStatus(sprintf("Can't run %d script.", $name), true);
+                return false;
+            }
+        }
+
+        $sqlUrl = "mysqli://$username:$password@$host/$database";
+        $db = array('type' => 'mysql', 'database' => $sqlUrl);
+        return $db;
+    }
+
+    /**
+     * Write a stock configuration file.
+     *
+     * @return boolean success
+     * 
+     * @fixme escape variables in output in case we have funny chars, apostrophes etc
+     */
+    function writeConf()
+    {
+        // assemble configuration file in a string
+        $cfg =  "<?php\n".
+                "if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n".
+
+                // site name
+                "\$config['site']['name'] = '{$this->sitename}';\n\n".
+
+                // site location
+                "\$config['site']['server'] = '{$this->server}';\n".
+                "\$config['site']['path'] = '{$this->path}'; \n\n".
+
+                // checks if fancy URLs are enabled
+                ($this->fancy ? "\$config['site']['fancy'] = true;\n\n":'').
+
+                // database
+                "\$config['db']['database'] = '{$this->db['database']}';\n\n".
+                ($this->db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
+                "\$config['db']['type'] = '{$this->db['type']}';\n\n";
+        // write configuration file out to install directory
+        $res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
+
+        return $res;
+    }
+
+    /**
+     * Install schema into the database
+     *
+     * @param string $filename location of database schema file
+     * @param dbconn $conn     connection to database
+     * @param string $type     type of database, currently mysql or pgsql
+     *
+     * @return boolean - indicating success or failure
+     */
+    function runDbScript($filename, $conn, $type = 'mysqli')
+    {
+        $sql = trim(file_get_contents(INSTALLDIR . '/db/' . $filename));
+        $stmts = explode(';', $sql);
+        foreach ($stmts as $stmt) {
+            $stmt = trim($stmt);
+            if (!mb_strlen($stmt)) {
+                continue;
+            }
+            // FIXME: use PEAR::DB or PDO instead of our own switch
+            switch ($type) {
+            case 'mysqli':
+                $res = mysql_query($stmt, $conn);
+                if ($res === false) {
+                    $error = mysql_error();
+                }
+                break;
+            case 'pgsql':
+                $res = pg_query($conn, $stmt);
+                if ($res === false) {
+                    $error = pg_last_error();
+                }
+                break;
+            default:
+                $this->updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
+            }
+            if ($res === false) {
+                $this->updateStatus("ERROR ($error) for SQL '$stmt'");
+                return $res;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Create the initial admin user account.
+     * Side effect: may load portions of StatusNet framework.
+     * Side effect: outputs program info
+     */
+    function registerInitialUser()
+    {
+        define('STATUSNET', true);
+        define('LACONICA', true); // compatibility
+
+        require_once INSTALLDIR . '/lib/common.php';
+
+        $data = array('nickname' => $this->adminNick,
+                      'password' => $this->adminPass,
+                      'fullname' => $this->adminNick);
+        if ($this->adminEmail) {
+            $data['email'] = $this->adminEmail;
+        }
+        $user = User::register($data);
+
+        if (empty($user)) {
+            return false;
+        }
+
+        // give initial user carte blanche
+
+        $user->grantRole('owner');
+        $user->grantRole('moderator');
+        $user->grantRole('administrator');
+        
+        // Attempt to do a remote subscribe to update@status.net
+        // Will fail if instance is on a private network.
+
+        if ($this->adminUpdates && class_exists('Ostatus_profile')) {
+            try {
+                $oprofile = Ostatus_profile::ensureProfileURL('http://update.status.net/');
+                Subscription::start($user->getProfile(), $oprofile->localProfile());
+                $this->updateStatus("Set up subscription to <a href='http://update.status.net/'>update@status.net</a>.");
+            } catch (Exception $e) {
+                $this->updateStatus("Could not set up subscription to <a href='http://update.status.net/'>update@status.net</a>.", true);
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * The beef of the installer!
+     * Create database, config file, and admin user.
+     * 
+     * Prerequisites: validation of input data.
+     * 
+     * @return boolean success
+     */
+    function doInstall()
+    {
+        $this->db = $this->setupDatabase();
+
+        if (!$this->db) {
+            // database connection failed, do not move on to create config file.
+            return false;
+        }
+
+        if (!$this->skipConfig) {
+            $this->updateStatus("Writing config file...");
+            $res = $this->writeConf();
+
+            if (!$res) {
+                $this->updateStatus("Can't write config file.", true);
+                return false;
+            }
+        }
+
+        if (!empty($this->adminNick)) {
+            // Okay, cross fingers and try to register an initial user
+            if ($this->registerInitialUser()) {
+                $this->updateStatus(
+                    "An initial user with the administrator role has been created."
+                );
+            } else {
+                $this->updateStatus(
+                    "Could not create initial StatusNet user (administrator).",
+                    true
+                );
+                return false;
+            }
+        }
+
+        /*
+            TODO https needs to be considered
+        */
+        $link = "http://".$this->server.'/'.$this->path;
+
+        $this->updateStatus("StatusNet has been installed at $link");
+        $this->updateStatus(
+            "<strong>DONE!</strong> You can visit your <a href='$link'>new StatusNet site</a> (login as '$this->adminNick'). If this is your first StatusNet install, you may want to poke around our <a href='http://status.net/wiki/Getting_started'>Getting Started guide</a>."
+        );
+
+        return true;
+    }
+
+    /**
+     * Output a pre-install-time warning message
+     * @param string $message HTML ok, but should be plaintext-able
+     * @param string $submessage HTML ok, but should be plaintext-able
+     */
+    abstract function warning($message, $submessage='');
+
+    /**
+     * Output an install-time progress message
+     * @param string $message HTML ok, but should be plaintext-able
+     * @param boolean $error true if this should be marked as an error condition
+     */
+    abstract function updateStatus($status, $error=false);
+
+}
index db4e2e9a706a62f8bf905e4b34167f37416492f3..cdcfc44232b8f659d325bbedabbc665ae2510e70 100644 (file)
@@ -34,38 +34,197 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
 require_once 'XMPPHP/XMPP.php';
 
 /**
- * checks whether a string is a syntactically valid Jabber ID (JID)
+ * Splits a Jabber ID (JID) into node, domain, and resource portions.
+ * 
+ * Based on validation routine submitted by:
+ * @copyright 2009 Patrick Georgi <patrick@georgi-clan.de>
+ * @license Licensed under ISC-L, which is compatible with everything else that keeps the copyright notice intact. 
  *
  * @param string $jid string to check
  *
+ * @return array with "node", "domain", and "resource" indices
+ * @throws Exception if input is not valid
+ */
+
+function jabber_split_jid($jid)
+{
+    $chars = '';
+    /* the following definitions come from stringprep, Appendix C,
+       which is used in its entirety by nodeprop, Chapter 5, "Prohibited Output" */
+    /* C1.1 ASCII space characters */
+    $chars .= "\x{20}";
+    /* C1.2 Non-ASCII space characters */
+    $chars .= "\x{a0}\x{1680}\x{2000}-\x{200b}\x{202f}\x{205f}\x{3000a}";
+    /* C2.1 ASCII control characters */
+    $chars .= "\x{00}-\x{1f}\x{7f}";
+    /* C2.2 Non-ASCII control characters */
+    $chars .= "\x{80}-\x{9f}\x{6dd}\x{70f}\x{180e}\x{200c}\x{200d}\x{2028}\x{2029}\x{2060}-\x{2063}\x{206a}-\x{206f}\x{feff}\x{fff9}-\x{fffc}\x{1d173}-\x{1d17a}";
+    /* C3 - Private Use */
+    $chars .= "\x{e000}-\x{f8ff}\x{f0000}-\x{ffffd}\x{100000}-\x{10fffd}";
+    /* C4 - Non-character code points */
+    $chars .= "\x{fdd0}-\x{fdef}\x{fffe}\x{ffff}\x{1fffe}\x{1ffff}\x{2fffe}\x{2ffff}\x{3fffe}\x{3ffff}\x{4fffe}\x{4ffff}\x{5fffe}\x{5ffff}\x{6fffe}\x{6ffff}\x{7fffe}\x{7ffff}\x{8fffe}\x{8ffff}\x{9fffe}\x{9ffff}\x{afffe}\x{affff}\x{bfffe}\x{bffff}\x{cfffe}\x{cffff}\x{dfffe}\x{dffff}\x{efffe}\x{effff}\x{ffffe}\x{fffff}\x{10fffe}\x{10ffff}";
+    /* C5 - Surrogate codes */
+    $chars .= "\x{d800}-\x{dfff}";
+    /* C6 - Inappropriate for plain text */
+    $chars .= "\x{fff9}-\x{fffd}";
+    /* C7 - Inappropriate for canonical representation */
+    $chars .= "\x{2ff0}-\x{2ffb}";
+    /* C8 - Change display properties or are deprecated */
+    $chars .= "\x{340}\x{341}\x{200e}\x{200f}\x{202a}-\x{202e}\x{206a}-\x{206f}";
+    /* C9 - Tagging characters */
+    $chars .= "\x{e0001}\x{e0020}-\x{e007f}";
+
+    /* Nodeprep forbids some more characters */
+    $nodeprepchars = $chars;
+    $nodeprepchars .= "\x{22}\x{26}\x{27}\x{2f}\x{3a}\x{3c}\x{3e}\x{40}";
+
+    $parts = explode("/", $jid, 2);
+    if (count($parts) > 1) {
+        $resource = $parts[1];
+        if ($resource == '') {
+            // Warning: empty resource isn't legit.
+            // But if we're normalizing, we may as well take it...
+        }
+    } else {
+        $resource = null;
+    }
+
+    $node = explode("@", $parts[0]);
+    if ((count($node) > 2) || (count($node) == 0)) {
+        throw new Exception("Invalid JID: too many @s");
+    } else if (count($node) == 1) {
+        $domain = $node[0];
+        $node = null;
+    } else {
+        $domain = $node[1];
+        $node = $node[0];
+        if ($node == '') {
+            throw new Exception("Invalid JID: @ but no node");
+        }
+    }
+
+    // Length limits per http://xmpp.org/rfcs/rfc3920.html#addressing
+    if ($node !== null) {
+        if (strlen($node) > 1023) {
+            throw new Exception("Invalid JID: node too long.");
+        }
+        if (preg_match("/[".$nodeprepchars."]/u", $node)) {
+            throw new Exception("Invalid JID node '$node'");
+        }
+    }
+
+    if (strlen($domain) > 1023) {
+        throw new Exception("Invalid JID: domain too long.");
+    }
+    if (!common_valid_domain($domain)) {
+        throw new Exception("Invalid JID domain name '$domain'");
+    }
+
+    if ($resource !== null) {
+        if (strlen($resource) > 1023) {
+            throw new Exception("Invalid JID: resource too long.");
+        }
+        if (preg_match("/[".$chars."]/u", $resource)) {
+            throw new Exception("Invalid JID resource '$resource'");
+        }
+    }
+
+    return array('node' => is_null($node) ? null : mb_strtolower($node),
+                 'domain' => is_null($domain) ? null : mb_strtolower($domain),
+                 'resource' => $resource);
+}
+
+/**
+ * Checks whether a string is a syntactically valid Jabber ID (JID),
+ * either with or without a resource.
+ * 
+ * Note that a bare domain can be a valid JID.
+ * 
+ * @param string $jid string to check
+ * @param bool $check_domain whether we should validate that domain...
+ *
  * @return     boolean whether the string is a valid JID
  */
+function jabber_valid_full_jid($jid, $check_domain=false)
+{
+    try {
+        $parts = jabber_split_jid($jid);
+        if ($check_domain) {
+            if (!jabber_check_domain($parts['domain'])) {
+                return false;
+            }
+        }
+        return $parts['resource'] !== ''; // missing or present; empty ain't kosher
+    } catch (Exception $e) {
+        return false;
+    }
+}
 
-function jabber_valid_base_jid($jid)
+/**
+ * Checks whether a string is a syntactically valid base Jabber ID (JID).
+ * A base JID won't include a resource specifier on the end; since we
+ * take it off when reading input we can't really use them reliably
+ * to direct outgoing messages yet (sorry guys!)
+ * 
+ * Note that a bare domain can be a valid JID.
+ * 
+ * @param string $jid string to check
+ * @param bool $check_domain whether we should validate that domain...
+ *
+ * @return     boolean whether the string is a valid JID
+ */
+function jabber_valid_base_jid($jid, $check_domain=false)
 {
-    // Cheap but effective
-    return Validate::email($jid);
+    try {
+        $parts = jabber_split_jid($jid);
+        if ($check_domain) {
+            if (!jabber_check_domain($parts['domain'])) {
+                return false;
+            }
+        }
+        return ($parts['resource'] === null); // missing; empty ain't kosher
+    } catch (Exception $e) {
+        return false;
+    }
 }
 
 /**
- * normalizes a Jabber ID for comparison
+ * Normalizes a Jabber ID for comparison, dropping the resource component if any.
  *
  * @param string $jid JID to check
+ * @param bool $check_domain if true, reject if the domain isn't findable
  *
  * @return string an equivalent JID in normalized (lowercase) form
  */
 
 function jabber_normalize_jid($jid)
 {
-    if (preg_match("/(?:([^\@]+)\@)?([^\/]+)(?:\/(.*))?$/", $jid, $matches)) {
-        $node   = $matches[1];
-        $server = $matches[2];
-        return strtolower($node.'@'.$server);
-    } else {
+    try {
+        $parts = jabber_split_jid($jid);
+        if ($parts['node'] !== null) {
+            return $parts['node'] . '@' . $parts['domain'];
+        } else {
+            return $parts['domain'];
+        }
+    } catch (Exception $e) {
         return null;
     }
 }
 
+/**
+ * Check if this domain's got some legit DNS record
+ */
+function jabber_check_domain($domain)
+{
+    if (checkdnsrr("_xmpp-server._tcp." . $domain, "SRV")) {
+        return true;
+    }
+    if (checkdnsrr($domain, "ANY")) {
+        return true;
+    }
+    return false;
+}
+
 /**
  * the JID of the Jabber daemon for this StatusNet instance
  *
index 807b6a36339a8b7fabd4d4468e1f672f4e109c1b..c38d9f2f504b3b79e923a2c2100a74a2083feff3 100644 (file)
@@ -170,8 +170,10 @@ function mail_to_user(&$user, $subject, $body, $headers=array(), $address=null)
 
 function mail_confirm_address($user, $code, $nickname, $address)
 {
+    // TRANS: Subject for address confirmation email
     $subject = _('Email address confirmation');
 
+    // TRANS: Body for address confirmation email.
     $body = sprintf(_("Hey, %s.\n\n".
                       "Someone just entered this email address on %s.\n\n" .
                       "If it was you, and you want to confirm your entry, ".
@@ -237,11 +239,13 @@ function mail_subscribe_notify_profile($listenee, $other)
         $headers = _mail_prepare_headers('subscribe', $listenee->nickname, $other->nickname);
         $headers['From']    = mail_notify_from();
         $headers['To']      = $name . ' <' . $listenee->email . '>';
+        // TRANS: Subject of new-subscriber notification e-mail
         $headers['Subject'] = sprintf(_('%1$s is now listening to '.
                                         'your notices on %2$s.'),
                                       $other->getBestName(),
                                       common_config('site', 'name'));
 
+        // TRANS: Main body of new-subscriber notification e-mail
         $body = sprintf(_('%1$s is now listening to your notices on %2$s.'."\n\n".
                           "\t".'%3$s'."\n\n".
                           '%4$s'.
@@ -255,10 +259,13 @@ function mail_subscribe_notify_profile($listenee, $other)
                         common_config('site', 'name'),
                         $other->profileurl,
                         ($other->location) ?
+                        // TRANS: Profile info line in new-subscriber notification e-mail
                         sprintf(_("Location: %s"), $other->location) . "\n" : '',
                         ($other->homepage) ?
+                        // TRANS: Profile info line in new-subscriber notification e-mail
                         sprintf(_("Homepage: %s"), $other->homepage) . "\n" : '',
                         ($other->bio) ?
+                        // TRANS: Profile info line in new-subscriber notification e-mail
                         sprintf(_("Bio: %s"), $other->bio) . "\n\n" : '',
                         common_config('site', 'name'),
                         common_local_url('emailsettings'));
@@ -287,9 +294,11 @@ function mail_new_incoming_notify($user)
 
     $headers['From']    = $user->incomingemail;
     $headers['To']      = $name . ' <' . $user->email . '>';
+    // TRANS: Subject of notification mail for new posting email address
     $headers['Subject'] = sprintf(_('New email address for posting to %s'),
                                   common_config('site', 'name'));
 
+    // TRANS: Body of notification mail for new posting email address
     $body = sprintf(_("You have a new posting address on %1\$s.\n\n".
                       "Send email to %2\$s to post new messages.\n\n".
                       "More email instructions at %3\$s.\n\n".
@@ -414,6 +423,7 @@ function mail_send_sms_notice_address($notice, $smsemail, $incomingemail)
 
     $headers['From']    = ($incomingemail) ? $incomingemail : mail_notify_from();
     $headers['To']      = $to;
+    // TRANS: Subject line for SMS-by-email notification messages
     $headers['Subject'] = sprintf(_('%s status'),
                                   $other->getBestName());
 
@@ -440,11 +450,11 @@ function mail_confirm_sms($code, $nickname, $address)
 
     $headers['From']    = mail_notify_from();
     $headers['To']      = $nickname . ' <' . $address . '>';
+    // TRANS: Subject line for SMS-by-email address confirmation message
     $headers['Subject'] = _('SMS confirmation');
 
-    // FIXME: I18N
-
-    $body  = "$nickname: confirm you own this phone number with this code:";
+    // TRANS: Main body heading for SMS-by-email address confirmation message
+    $body  = sprintf(_("%s: confirm you own this phone number with this code:"), $nickname);
     $body .= "\n\n";
     $body .= $code;
     $body .= "\n\n";
@@ -464,10 +474,12 @@ function mail_confirm_sms($code, $nickname, $address)
 function mail_notify_nudge($from, $to)
 {
     common_init_locale($to->language);
+    // TRANS: Subject for 'nudge' notification email
     $subject = sprintf(_('You\'ve been nudged by %s'), $from->nickname);
 
     $from_profile = $from->getProfile();
 
+    // TRANS: Body for 'nudge' notification email
     $body = sprintf(_("%1\$s (%2\$s) is wondering what you are up to ".
                       "these days and is inviting you to post some news.\n\n".
                       "So let's hear from you :)\n\n".
@@ -514,10 +526,12 @@ function mail_notify_message($message, $from=null, $to=null)
     }
 
     common_init_locale($to->language);
+    // TRANS: Subject for direct-message notification email
     $subject = sprintf(_('New private message from %s'), $from->nickname);
 
     $from_profile = $from->getProfile();
 
+    // TRANS: Body for direct-message notification email
     $body = sprintf(_("%1\$s (%2\$s) sent you a private message:\n\n".
                       "------------------------------------------------------\n".
                       "%3\$s\n".
@@ -565,8 +579,10 @@ function mail_notify_fave($other, $user, $notice)
 
     common_init_locale($other->language);
 
+    // TRANS: Subject for favorite notification email
     $subject = sprintf(_('%s (@%s) added your notice as a favorite'), $bestname, $user->nickname);
 
+    // TRANS: Body for favorite notification email
     $body = sprintf(_("%1\$s (@%7\$s) just added your notice from %2\$s".
                       " as one of their favorites.\n\n" .
                       "The URL of your notice is:\n\n" .
@@ -622,24 +638,25 @@ function mail_notify_attn($user, $notice)
 
     common_init_locale($user->language);
 
-        if ($notice->conversation != $notice->id) {
-                $conversationEmailText = "The full conversation can be read here:\n\n".
-                                                                 "\t%5\$s\n\n ";
-                $conversationUrl            = common_local_url('conversation',
-                                 array('id' => $notice->conversation)).'#notice-'.$notice->id;
-        } else {
-                $conversationEmailText = "%5\$s";
-                $conversationUrl = null;
-        }
+    if ($notice->hasConversation()) {
+        $conversationUrl = common_local_url('conversation',
+                         array('id' => $notice->conversation)).'#notice-'.$notice->id;
+        // TRANS: Line in @-reply notification e-mail. %s is conversation URL.
+        $conversationEmailText = sprintf(_("The full conversation can be read here:\n\n".
+                                           "\t%s"), $conversationUrl) . "\n\n";
+    } else {
+        $conversationEmailText = '';
+    }
 
     $subject = sprintf(_('%s (@%s) sent a notice to your attention'), $bestname, $sender->nickname);
 
+        // TRANS: Body of @-reply notification e-mail.
         $body = sprintf(_("%1\$s (@%9\$s) just sent a notice to your attention (an '@-reply') on %2\$s.\n\n".
                       "The notice is here:\n\n".
                       "\t%3\$s\n\n" .
                       "It reads:\n\n".
                       "\t%4\$s\n\n" .
-                      $conversationEmailText .
+                      "%5\$s" .
                       "You can reply back here:\n\n".
                       "\t%6\$s\n\n" .
                       "The list of all @-replies for you here:\n\n" .
@@ -652,7 +669,7 @@ function mail_notify_attn($user, $notice)
                     common_local_url('shownotice',
                                      array('notice' => $notice->id)),//%3
                     $notice->content,//%4
-                                        $conversationUrl,//%5
+                    $conversationEmailText,//%5
                     common_local_url('newnotice',
                                      array('replyto' => $sender->nickname, 'inreplyto' => $notice->id)),//%6
                     common_local_url('replies',
index 83c8de9f6e0ac3b573846ea92016858d8cd3e187..4f997a3286a78b339644c8a7dd72f32fd31e07ed 100644 (file)
@@ -543,18 +543,7 @@ class NoticeListItem extends Widget
 
     function showContext()
     {
-        $hasConversation = false;
-        if (!empty($this->notice->conversation)) {
-            $conversation = Notice::conversationStream(
-                $this->notice->conversation,
-                1,
-                1
-            );
-            if ($conversation->N > 0) {
-                $hasConversation = true;
-            }
-        }
-        if ($hasConversation) {
+        if ($this->notice->hasConversation()) {
             $conv = Conversation::staticGet(
                 'id',
                 $this->notice->conversation
index 8a934666e3adbc8678979acf087e614fe1c54674..0ffafe5fb8d1279aeea4828b4da54c38869dae7a 100644 (file)
@@ -41,7 +41,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
  * @link     http://status.net/
  */
 
-class ProfileFormAction extends Action
+class ProfileFormAction extends RedirectingAction
 {
     var $profile = null;
 
@@ -101,29 +101,6 @@ class ProfileFormAction extends Action
         }
     }
 
-    /**
-     * Return to the calling page based on hidden arguments
-     *
-     * @return void
-     */
-
-    function returnToArgs()
-    {
-        foreach ($this->args as $k => $v) {
-            if ($k == 'returnto-action') {
-                $action = $v;
-            } else if (substr($k, 0, 9) == 'returnto-') {
-                $args[substr($k, 9)] = $v;
-            }
-        }
-
-        if ($action) {
-            common_redirect(common_local_url($action, $args), 303);
-        } else {
-            $this->clientError(_("No return-to arguments."));
-        }
-    }
-
     /**
      * handle a POST request
      *
diff --git a/lib/redirectingaction.php b/lib/redirectingaction.php
new file mode 100644 (file)
index 0000000..f115852
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+/**
+ * Superclass for actions that redirect to a given return-to page on completion.
+ *
+ * PHP version 5
+ *
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009-2010, StatusNet, Inc.
+ *
+ * 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 Action
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Brion Vibber <brion@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * Superclass for actions that redirect to a given return-to page on completion.
+ *
+ * @category Action
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Brion Vibber <brion@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://status.net/
+ */
+
+
+class RedirectingAction extends Action
+{
+
+    /**
+     * Redirect browser to the page our hidden parameters requested,
+     * or if none given, to the url given by $this->defaultReturnTo().
+     * 
+     * To be called only after successful processing.
+     * 
+     * @fixme rename this -- it obscures Action::returnToArgs() which
+     * returns a list of arguments, and is a bit confusing.
+     * 
+     * @return void
+     */
+    function returnToArgs()
+    {
+        // Now, gotta figure where we go back to
+        $action = false;
+        $args = array();
+        $params = array();
+        foreach ($this->args as $k => $v) {
+            if ($k == 'returnto-action') {
+                $action = $v;
+            } else if (substr($k, 0, 15) == 'returnto-param-') {
+                $params[substr($k, 15)] = $v;
+            } elseif (substr($k, 0, 9) == 'returnto-') {
+                $args[substr($k, 9)] = $v;
+            }
+        }
+
+        if ($action) {
+            common_redirect(common_local_url($action, $args, $params), 303);
+        } else {
+            $url = $this->defaultReturnToUrl();
+        }
+        common_redirect($url, 303);
+    }
+
+    /**
+     * If we reached this form without returnto arguments, where should
+     * we go? May be overridden by subclasses to a reasonable destination
+     * for that action; default implementation throws an exception.
+     * 
+     * @return string URL
+     */
+    function defaultReturnTo()
+    {
+        $this->clientError(_("No return-to arguments."));
+    }
+}
index 9af8b2f4826b24714f5f557af56d2a560681adcd..5d5c7ccfbd9cfd81b6450bd2cfd082ab8e10c803 100644 (file)
@@ -39,7 +39,8 @@ class StompQueueManager extends QueueManager
     protected $base;
     protected $control;
 
-    protected $useTransactions = true;
+    protected $useTransactions;
+    protected $useAcks;
 
     protected $sites = array();
     protected $subscriptions = array();
@@ -59,11 +60,13 @@ class StompQueueManager extends QueueManager
         } else {
             $this->servers = array($server);
         }
-        $this->username = common_config('queue', 'stomp_username');
-        $this->password = common_config('queue', 'stomp_password');
-        $this->base     = common_config('queue', 'queue_basename');
-        $this->control  = common_config('queue', 'control_channel');
-        $this->breakout = common_config('queue', 'breakout');
+        $this->username        = common_config('queue', 'stomp_username');
+        $this->password        = common_config('queue', 'stomp_password');
+        $this->base            = common_config('queue', 'queue_basename');
+        $this->control         = common_config('queue', 'control_channel');
+        $this->breakout        = common_config('queue', 'breakout');
+        $this->useTransactions = common_config('queue', 'stomp_transactions');
+        $this->useAcks         = common_config('queue', 'stomp_acks');
     }
 
     /**
@@ -703,13 +706,15 @@ class StompQueueManager extends QueueManager
 
     protected function ack($idx, $frame)
     {
-        if ($this->useTransactions) {
-            if (empty($this->transaction[$idx])) {
-                throw new Exception("Tried to ack but not in a transaction");
+        if ($this->useAcks) {
+            if ($this->useTransactions) {
+                if (empty($this->transaction[$idx])) {
+                    throw new Exception("Tried to ack but not in a transaction");
+                }
+                $this->cons[$idx]->ack($frame, $this->transaction[$idx]);
+            } else {
+                $this->cons[$idx]->ack($frame);
             }
-            $this->cons[$idx]->ack($frame, $this->transaction[$idx]);
-        } else {
-            $this->cons[$idx]->ack($frame);
         }
     }
 
index e37df63484645feb9aa8dc540d1d4b1346823e50..6905df839a3f6a3ba84306053420a6dbf317350d 100644 (file)
@@ -1279,12 +1279,38 @@ function common_mtrand($bytes)
     return $enc;
 }
 
+/**
+ * Record the given URL as the return destination for a future
+ * form submission, to be read by common_get_returnto().
+ * 
+ * @param string $url
+ * 
+ * @fixme as a session-global setting, this can allow multiple forms
+ * to conflict and overwrite each others' returnto destinations if
+ * the user has multiple tabs or windows open.
+ * 
+ * Should refactor to index with a token or otherwise only pass the
+ * data along its intended path.
+ */
 function common_set_returnto($url)
 {
     common_ensure_session();
     $_SESSION['returnto'] = $url;
 }
 
+/**
+ * Fetch a return-destination URL previously recorded by
+ * common_set_returnto().
+ * 
+ * @return mixed URL string or null
+ * 
+ * @fixme as a session-global setting, this can allow multiple forms
+ * to conflict and overwrite each others' returnto destinations if
+ * the user has multiple tabs or windows open.
+ * 
+ * Should refactor to index with a token or otherwise only pass the
+ * data along its intended path.
+ */
 function common_get_returnto()
 {
     common_ensure_session();
@@ -1404,6 +1430,55 @@ function common_valid_tag($tag)
     return false;
 }
 
+/**
+ * Determine if given domain or address literal is valid
+ * eg for use in JIDs and URLs. Does not check if the domain
+ * exists!
+ * 
+ * @param string $domain
+ * @return boolean valid or not
+ */
+function common_valid_domain($domain)
+{
+    $octet = "(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])";
+    $ipv4 = "(?:$octet(?:\.$octet){3})";
+    if (preg_match("/^$ipv4$/u", $domain)) return true;
+
+    $group = "(?:[0-9a-f]{1,4})";
+    $ipv6 = "(?:\[($group(?::$group){0,7})?(::)?($group(?::$group){0,7})?\])"; // http://tools.ietf.org/html/rfc3513#section-2.2
+
+    if (preg_match("/^$ipv6$/ui", $domain, $matches)) {
+        $before = explode(":", $matches[1]);
+        $zeroes = $matches[2];
+        $after = explode(":", $matches[3]);
+        if ($zeroes) {
+            $min = 0;
+            $max = 7;
+        } else {
+            $min = 1;
+            $max = 8;
+        }
+        $explicit = count($before) + count($after);
+        if ($explicit < $min || $explicit > $max) {
+            return false;
+        }
+        return true;
+    }
+
+    try {
+        require_once "Net/IDNA.php";
+        $idn = Net_IDNA::getInstance();
+        $domain = $idn->encode($domain);
+    } catch (Exception $e) {
+        return false;
+    }
+
+    $subdomain = "(?:[a-z0-9][a-z0-9-]*)"; // @fixme
+    $fqdn = "(?:$subdomain(?:\.$subdomain)*\.?)";
+
+    return preg_match("/^$fqdn$/ui", $domain);
+}
+
 /* Following functions are copied from MediaWiki GlobalFunctions.php
  * and written by Evan Prodromou. */
 
diff --git a/plugins/DirectionDetector/DirectionDetectorPlugin.php b/plugins/DirectionDetector/DirectionDetectorPlugin.php
new file mode 100644 (file)
index 0000000..303e60d
--- /dev/null
@@ -0,0 +1,230 @@
+<?php
+
+/**
+ * DirectionDetector plugin, detects notices with RTL content & sets RTL
+ * style for them.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.    If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category     Plugin
+ * @package      StatusNet
+ * @author       Behrooz shabani (everplays) - <behrooz@rock.com>
+ * @copyright    2009-2010 Behrooz shabani
+ * @license      http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ *
+ */
+
+if (!defined('STATUSNET')) {
+               exit(1);
+}
+
+define('DIRECTIONDETECTORPLUGIN_VERSION', '0.1.1');
+
+class DirectionDetectorPlugin extends Plugin {
+       /**
+        * SN plugin API, here we will make changes on rendered column
+        *
+        * @param object $notice notice is going to be saved
+        */
+       public function onStartNoticeSave(&$notice){
+               if(self::isRTL($notice->content))
+                       $notice->rendered = '<span class="rtl">'.$notice->rendered.'</span>';
+               return true;
+       }
+
+       /**
+        * SN plugin API, here we will add css needed for modifiyed rendered
+        *
+        * @param 
+        */
+       public function onEndShowStatusNetStyles(&$xml){
+               $xml->element('style', array('type' => 'text/css'), 'span.rtl {display:block;direction:rtl;text-align:right;float:right;width:490px;} .notice .author {float:left}');
+       }
+       /**
+        * checks that passed string is a RTL language or not
+        *
+        * @param string $str string to be checked
+        */
+       public static function isRTL($str){
+               self::getClearText($str);
+               if( is_array($cc = self::utf8ToUnicode(mb_substr($str, 0, 1, 'utf-8'))) )
+                       $cc = $cc[0];
+               else
+                       return false;
+               if($cc>=1536 && $cc<=1791) // arabic, persian, urdu, kurdish, ...
+                       return true;
+               if($cc>=65136 && $cc<=65279) // arabic peresent 2
+                       return true;
+               if($cc>=64336 && $cc<=65023) // arabic peresent 1
+                       return true;
+               if($cc>=1424 && $cc<=1535) // hebrew
+                       return true;
+               if($cc>=64256 && $cc<=64335) // hebrew peresent
+                       return true;
+               if($cc>=1792 && $cc<=1871) // Syriac
+                       return true;
+               if($cc>=1920 && $cc<=1983) // Thaana
+                       return true;
+               if($cc>=1984 && $cc<=2047) // NKo
+                       return true;
+               if($cc>=11568 && $cc<=11647) // Tifinagh
+                       return true;
+               return false;
+       }
+
+       /**
+        * clears text from replys, tags, groups, reteets & whitespaces
+        *
+        * @param string &$str string to be cleared
+        */
+       private static function getClearText(&$str){
+               $str = preg_replace('/@[^ ]+|![^ ]+|#[^ ]+/u', '', $str); // reply, tag, group
+               $str = preg_replace('/^RT[: ]{1}| RT | RT: |^RD[: ]{1}| RD | RD: |[♺♻:]/u', '', $str); // redent, retweet
+               $str = preg_replace("/[ \r\t\n]+/", ' ', trim($str)); // remove spaces
+       }
+
+       /**
+        * Takes an UTF-8 string and returns an array of ints representing the 
+        * Unicode characters. Astral planes are supported ie. the ints in the
+        * output can be > 0xFFFF. O$ccurrances of the BOM are ignored. Surrogates
+        * are not allowed. ### modified ### returns first character code
+        *
+        * Returns false if the input string isn't a valid UTF-8 octet sequence.
+        */
+       private static function utf8ToUnicode(&$str){
+               $mState = 0;       // cached expected number of octets after the current octet
+                                  // until the beginning of the next UTF8 character sequence
+               $mUcs4  = 0;     // cached Unicode character
+               $mBytes = 1;       // cached expected number of octets in the current sequence
+               $out = array();
+               $len = strlen($str);
+
+               for($i = 0; $i < $len; $i++) {
+                       $in = ord($str{$i});
+                       if (0 == $mState) {
+                               // When mState is zero we expect either a US-ASCII character or a
+                               // multi-octet sequence.
+                               if (0 == (0x80 & ($in))) {
+                                       // US-ASCII, pass straight through.
+                                       $out[] = $in;
+                                       $mBytes = 1;
+                               } elseif (0xC0 == (0xE0 & ($in))) {
+                                       // First octet of 2 octet sequence
+                                       $mUcs4 = ($in);
+                                       $mUcs4 = ($mUcs4 & 0x1F) << 6;
+                                       $mState = 1;
+                                       $mBytes = 2;
+                               } elseif (0xE0 == (0xF0 & ($in))) {
+                                       // First octet of 3 octet sequence
+                                       $mUcs4 = ($in);
+                                       $mUcs4 = ($mUcs4 & 0x0F) << 12;
+                                       $mState = 2;
+                                       $mBytes = 3;
+                               } elseif (0xF0 == (0xF8 & ($in))) {
+                                       // First octet of 4 octet sequence
+                                       $mUcs4 = ($in);
+                                       $mUcs4 = ($mUcs4 & 0x07) << 18;
+                                       $mState = 3;
+                                       $mBytes = 4;
+                               } elseif (0xF8 == (0xFC & ($in))) {
+                                       /* First octet of 5 octet sequence.
+                                        *
+                                        * This is illegal because the encoded codepoint must be either
+                                        * (a) not the shortest form or
+                                        * (b) outside the Unicode range of 0-0x10FFFF.
+                                        * Rather than trying to resynchronize, we will carry on until the end
+                                        * of the sequence and let the later error handling code catch it.
+                                        */
+                                       $mUcs4 = ($in);
+                                       $mUcs4 = ($mUcs4 & 0x03) << 24;
+                                       $mState = 4;
+                                       $mBytes = 5;
+                               } elseif (0xFC == (0xFE & ($in))) {
+                                       // First octet of 6 octet sequence, see comments for 5 octet sequence.
+                                       $mUcs4 = ($in);
+                                       $mUcs4 = ($mUcs4 & 1) << 30;
+                                       $mState = 5;
+                                       $mBytes = 6;
+                               } else {
+                                       /* Current octet is neither in the US-ASCII range nor a legal first
+                                        * octet of a multi-octet sequence.
+                                        */
+                                       return false;
+                               }
+                       } else {
+                               // When mState is non-zero, we expect a continuation of the multi-octet
+                               // sequence
+                               if (0x80 == (0xC0 & ($in))) {
+                                       // Legal continuation.
+                                       $shift = ($mState - 1) * 6;
+                                       $tmp = $in;
+                                       $tmp = ($tmp & 0x0000003F) << $shift;
+                                       $mUcs4 |= $tmp;
+                                       if (0 == --$mState) {
+                                               /* End of the multi-octet sequence. mUcs4 now contains the final
+                                                * Unicode codepoint to be output
+                                                *
+                                                * Check for illegal sequences and codepoints.
+                                                */
+                                               // From Unicode 3.1, non-shortest form is illegal
+                                               if      (
+                                                               ((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
+                                                               ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
+                                                               ((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
+                                                               (4 < $mBytes) ||
+                                                               // From Unicode 3.2, surrogate characters are illegal
+                                                               (($mUcs4 & 0xFFFFF800) == 0xD800) ||
+                                                               // Codepoints outside the Unicode range are illegal
+                                                               ($mUcs4 > 0x10FFFF)
+                                                       ){
+                                                       return false;
+                                               }
+                                               if (0xFEFF != $mUcs4) {
+                                                       $out[] = $mUcs4;
+                                               }
+                                               //initialize UTF8 cache
+                                               $mState = 0;
+                                               $mUcs4  = 0;
+                                               $mBytes = 1;
+                                       }
+                               } else {
+                                       /* ((0xC0 & (*in) != 0x80) && (mState != 0))
+                                        * 
+                                        * Incomplete multi-octet sequence.
+                                        */
+                                       return false;
+                               }
+                       }
+               }
+               return $out;
+       }
+
+       /**
+        * plugin details
+        */
+       function onPluginVersion(&$versions){
+               $versions[] = array(
+                       'name' => 'Direction detector',
+                       'version' => DIRECTIONDETECTORPLUGIN_VERSION,
+                       'author' => 'behrooz shabani',
+                       'rawdescription' => _m('shows notices with right-to-left content in correct direction.')
+               );
+               return true;
+       }
+}
+
+/*
+// Example:
+var_dump(DirectionDetectorPlugin::isRTL('RT @everplays ♺: دادگاه به دليل عدم حضور وکلای متهمان بنا بر اصل ١٣٥ قانون اساسی غير قانونی است')); // true
+*/
index 51bfc3865700c323921cbfac49eecd6f26c4ef59..8eba7fc13569bf9266e5f62dc73942a391479f83 100644 (file)
@@ -138,6 +138,11 @@ class FBConnectauthAction extends Action
         parent::showPage();
     }
 
+    /**
+     * @fixme much of this duplicates core code, which is very fragile.
+     * Should probably be replaced with an extensible mini version of
+     * the core registration form.
+     */
     function showContent()
     {
         if (!empty($this->message_text)) {
@@ -159,10 +164,15 @@ class FBConnectauthAction extends Action
                                       'name' => 'license',
                                       'value' => 'true'));
         $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license'));
-        $this->text(_m('My text and files are available under '));
-        $this->element('a', array('href' => common_config('license', 'url')),
-                       common_config('license', 'title'));
-        $this->text(_m(' except this private data: password, email address, IM address, phone number.'));
+        $message = _('My text and files are available under %s ' .
+                     'except this private data: password, ' .
+                     'email address, IM address, and phone number.');
+        $link = '<a href="' .
+                htmlspecialchars(common_config('license', 'url')) .
+                '">' .
+                htmlspecialchars(common_config('license', 'title')) .
+                '</a>';
+        $this->raw(sprintf(htmlspecialchars($message), $link));
         $this->elementEnd('label');
         $this->elementEnd('li');
         $this->elementEnd('ul');
index f3a48330063f1a93aac04835785b01c66f520f3c..32b092a0bdefa896b38d18ef35c57eb552cd4c3c 100644 (file)
@@ -79,6 +79,11 @@ class FinishopenidloginAction extends Action
         $this->showPage();
     }
 
+    /**
+     * @fixme much of this duplicates core code, which is very fragile.
+     * Should probably be replaced with an extensible mini version of
+     * the core registration form.
+     */
     function showContent()
     {
         if (!empty($this->message_text)) {
@@ -110,10 +115,15 @@ class FinishopenidloginAction extends Action
                                       'value' => 'true'));
         $this->elementStart('label', array('for' => 'license',
                                           'class' => 'checkbox'));
-        $this->text(_m('My text and files are available under '));
-        $this->element('a', array('href' => common_config('license', 'url')),
-                       common_config('license', 'title'));
-        $this->text(_m(' except this private data: password, email address, IM address, phone number.'));
+        $message = _('My text and files are available under %s ' .
+                     'except this private data: password, ' .
+                     'email address, IM address, and phone number.');
+        $link = '<a href="' .
+                htmlspecialchars(common_config('license', 'url')) .
+                '">' .
+                htmlspecialchars(common_config('license', 'title')) .
+                '</a>';
+        $this->raw(sprintf(htmlspecialchars($message), $link));
         $this->elementEnd('label');
         $this->elementEnd('li');
         $this->elementEnd('ul');
index 9f444c8bba0ca5482363ca612b242ec6eabbe43a..001106aceceb4606b2a4e3a741bb90351720b704 100644 (file)
@@ -105,7 +105,7 @@ class RSSCloudPlugin extends Plugin
      * @return boolean hook return
      */
 
-    function onRouterInitialized(&$m)
+    function onRouterInitialized($m)
     {
         $m->connect('/main/rsscloud/request_notify',
                     array('action' => 'RSSCloudRequestNotify'));
index bc004cb955f6082308823a764d4f5487d410a793..7a896e1687a86b6ce736563286f5ae545ef36d4a 100644 (file)
@@ -332,6 +332,11 @@ class TwitterauthorizationAction extends Action
         parent::showPage();
     }
 
+    /**
+     * @fixme much of this duplicates core code, which is very fragile.
+     * Should probably be replaced with an extensible mini version of
+     * the core registration form.
+     */
     function showContent()
     {
         if (!empty($this->message_text)) {
@@ -353,10 +358,15 @@ class TwitterauthorizationAction extends Action
                                       'name' => 'license',
                                       'value' => 'true'));
         $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license'));
-        $this->text(_('My text and files are available under '));
-        $this->element('a', array('href' => common_config('license', 'url')),
-                       common_config('license', 'title'));
-        $this->text(_(' except this private data: password, email address, IM address, phone number.'));
+        $message = _('My text and files are available under %s ' .
+                     'except this private data: password, ' .
+                     'email address, IM address, and phone number.');
+        $link = '<a href="' .
+                htmlspecialchars(common_config('license', 'url')) .
+                '">' .
+                htmlspecialchars(common_config('license', 'title')) .
+                '</a>';
+        $this->raw(sprintf(htmlspecialchars($message), $link));
         $this->elementEnd('label');
         $this->elementEnd('li');
         $this->elementEnd('ul');
diff --git a/scripts/install_cli.php b/scripts/install_cli.php
new file mode 100755 (executable)
index 0000000..61fbe18
--- /dev/null
@@ -0,0 +1,217 @@
+#!/usr/bin/env php
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * 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 Installation
+ * @package  Installation
+ *
+ * @author   Brion Vibber <brion@status.net>
+ * @license  GNU Affero General Public License http://www.gnu.org/licenses/
+ * @version  0.9.x
+ * @link     http://status.net
+ */
+
+if (php_sapi_name() !== 'cli') {
+    exit(1);
+}
+
+define('INSTALLDIR', dirname(dirname(__FILE__)));
+set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/extlib');
+
+require_once INSTALLDIR . '/lib/installer.php';
+require_once 'Console/Getopt.php';
+
+class CliInstaller extends Installer
+{
+    public $verbose = true;
+
+    /**
+     * Go for it!
+     * @return boolean success
+     */
+    function main()
+    {
+        if (!$this->checkPrereqs()) {
+            return false;
+        }
+        if ($this->prepare()) {
+            return $this->handle();
+       } else {
+            $this->showHelp();
+            return false;
+        }
+    }
+
+    /**
+     * Get our input parameters...
+     * @return boolean success
+     */
+    function prepare()
+    {
+        $shortoptions = 'qvh';
+        $longoptions = array('quiet', 'verbose', 'help', 'skip-config');
+        $map = array(
+            '-s'         => 'server',
+            '--server'   => 'server',
+            '-p'         => 'path',
+            '--path'     => 'path',
+            '--sitename' => 'sitename',
+            '--fancy'    => 'fancy',
+
+            '--dbtype'   => 'dbtype',
+            '--host'     => 'host',
+            '--database' => 'database',
+            '--username' => 'username',
+            '--password' => 'password',
+
+            '--admin-nick' => 'adminNick',
+            '--admin-pass' => 'adminPass',
+            '--admin-email' => 'adminEmail',
+            '--admin-updates' => 'adminUpdates'
+        );
+        foreach ($map as $arg => $target) {
+            if (substr($arg, 0, 2) == '--') {
+                $longoptions[] = substr($arg, 2) . '=';
+            } else {
+                $shortoptions .= substr($arg, 1) . ':';
+            }
+        }
+
+        $parser = new Console_Getopt();
+        $result = $parser->getopt($_SERVER['argv'], $shortoptions, $longoptions);
+        if (PEAR::isError($result)) {
+            $this->warning($result->getMessage());
+            return false;
+        }
+        list($options, $args) = $result;
+
+        // defaults
+        $this->dbtype = 'mysql';
+        $this->adminUpdates = true;
+        $this->verbose = true;
+
+        foreach ($options as $option) {
+            $arg = $option[0];
+            if (isset($map[$arg])) {
+                $var = $map[$arg];
+                $this->$var = $option[1];
+                if ($var == 'adminUpdates' || $arg == '--fancy') {
+                    $this->$var = ($option[1] != 'false') && ($option[1] != 'no');
+                }
+            } else if ($arg == '--skip-config') {
+                $this->skipConfig = true;
+            } else if ($arg == 'q' || $arg == '--quiet') {
+                $this->verbose = false;
+            } else if ($arg == 'v' || $arg == '--verbose') {
+                $this->verbose = true;
+            } else if ($arg == 'h' || $arg == '--help') {
+                // will go back to show help
+                return false;
+            }
+        }
+
+        $fail = false;
+        if (empty($this->server)) {
+            $this->updateStatus("You must specify a web server for the site.", true);
+            // path is optional though
+            $fail = true;
+        }
+
+        if (!$this->validateDb()) {
+            $fail = true;
+        }
+
+        if (!$this->validateAdmin()) {
+            $fail = true;
+        }
+
+        return !$fail;
+    }
+
+    function handle()
+    {
+        return $this->doInstall();
+    }
+
+    function showHelp()
+    {
+        echo <<<END_HELP
+install_cli.php - StatusNet command-line installer
+
+    -s --server=<name>   Use <name> as server name (required)
+    -p --path=<path>     Use <path> as path name
+       --sitename        User-friendly site name (required)
+       --fancy           Whether to use fancy URLs (default no)
+
+       --dbtype          'mysql' (default) or 'pgsql'
+       --host            Database hostname (required)
+       --database        Database/schema name (required)
+       --username        Database username (required)
+       --password        Database password (required)
+
+       --admin-nick      Administrator nickname (required)
+       --admin-pass      Initial password for admin user (required)
+       --admin-email     Initial email address for admin user
+       --admin-updates   'yes' (default) or 'no', whether to subscribe
+                         admin to update@status.net (default yes)
+       
+       --skip-config     Don't write a config.php -- use with caution,
+                         requires a global configuration file.
+
+      General options:
+
+    -q --quiet           Quiet (little output)
+    -v --verbose         Verbose (lots of output)
+    -h --help            Show this message and quit.
+
+END_HELP;
+    }
+
+    function warning($message, $submessage='')
+    {
+        print $this->html2text($message) . "\n";
+        if ($submessage != '') {
+            print "  " . $this->html2text($submessage) . "\n";
+        }
+        print "\n";
+    }
+
+    function updateStatus($status, $error=false)
+    {
+        if ($this->verbose || $error) {
+            if ($error) {
+                print "ERROR: ";
+            }
+            print $this->html2text($status);
+            print "\n";
+        }
+    }
+
+    private function html2text($html)
+    {
+        // break out any links for text legibility
+        $breakout = preg_replace('/<a[^>+]\bhref="(.*)"[^>]*>(.*)<\/a>/',
+                                 '\2 &lt;\1&gt;',
+                                 $html);
+        return html_entity_decode(strip_tags($breakout));
+    }
+}
+
+$installer = new CliInstaller();
+$ok = $installer->main();
+exit($ok ? 0 : 1);
diff --git a/tests/JidValidateTest.php b/tests/JidValidateTest.php
new file mode 100644 (file)
index 0000000..9f59011
--- /dev/null
@@ -0,0 +1,146 @@
+<?php
+
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+    print "This script must be run from the command line\n";
+    exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('STATUSNET', true);
+define('LACONICA', true);
+
+mb_internal_encoding('UTF-8'); // @fixme this probably belongs in common.php?
+
+require_once INSTALLDIR . '/lib/common.php';
+require_once INSTALLDIR . '/lib/jabber.php';
+
+class JidValidateTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * @dataProvider validationCases
+     *
+     */
+    public function testValidate($jid, $validFull, $validBase)
+    {
+        $this->assertEquals($validFull, jabber_valid_full_jid($jid), "validating as full or base JID");
+
+        $this->assertEquals($validBase, jabber_valid_base_jid($jid), "validating as base JID only");
+    }
+
+    /**
+     * @dataProvider normalizationCases
+     *
+     */
+    public function testNormalize($jid, $expected)
+    {
+        $this->assertEquals($expected, jabber_normalize_jid($jid));
+    }
+
+    /**
+     * @dataProvider domainCheckCases()
+     */
+    public function testDomainCheck($domain, $expected, $note)
+    {
+        $this->assertEquals($expected, jabber_check_domain($domain), $note);
+    }
+
+    static public function validationCases()
+    {
+        $long1023 = "long1023" . str_repeat('x', 1023 - 8);
+        $long1024 = "long1024" . str_repeat('x', 1024 - 8);
+        return array(
+            // Our own test cases for standard things & those mentioned in bug reports
+            // (jid, valid_full, valid_base)
+            array('user@example.com', true, true),
+            array('user@example.com/resource', true, false),
+            array('user with spaces@example.com', false, false), // not kosher
+
+            array('user.@example.com', true, true), // "common in intranets"
+            array('example.com', true, true),
+            array('example.com/resource', true, false),
+            array('jabchat', true, true),
+            
+            array("$long1023@$long1023/$long1023", true, false), // max 1023 "bytes" per portion per spec. Do they really mean bytes though?
+            array("$long1024@$long1023/$long1023", false, false),
+            array("$long1023@$long1024/$long1023", false, false),
+            array("$long1023@$long1023/$long1024", false, false),
+
+            // Borrowed from test_jabber_jutil.c in libpurple
+            array("gmail.com", true, true),
+            array("gmail.com/Test", true, false),
+            array("gmail.com/Test@", true, false),
+            array("gmail.com/@", true, false),
+            array("gmail.com/Test@alkjaweflkj", true, false),
+            array("mark.doliner@gmail.com", true, true),
+            array("mark.doliner@gmail.com/Test12345", true, false),
+            array("mark.doliner@gmail.com/Test@12345", true, false),
+            array("mark.doliner@gmail.com/Te/st@12@//345", true, false),
+            array("わいど@conference.jabber.org", true, true),
+            array("まりるーむ@conference.jabber.org", true, true),
+            array("mark.doliner@gmail.com/まりるーむ", true, false),
+            array("mark.doliner@gmail/stuff.org", true, false),
+            array("stuart@nödåtXäYZ.se", true, true),
+            array("stuart@nödåtXäYZ.se/まりるーむ", true, false),
+            array("mark.doliner@わいど.org", true, true),
+            array("nick@まつ.おおかみ.net", true, true),
+            array("paul@10.0.42.230/s", true, false),
+            array("paul@[::1]", true, true), /* IPv6 */
+            array("paul@[2001:470:1f05:d58::2]", true, true),
+            array("paul@[2001:470:1f05:d58::2]/foo", true, false),
+            array("pa=ul@10.0.42.230", true, true),
+            array("pa,ul@10.0.42.230", true, true),
+
+            array("@gmail.com", false, false),
+            array("@@gmail.com", false, false),
+            array("mark.doliner@@gmail.com/Test12345", false, false),
+            array("mark@doliner@gmail.com/Test12345", false, false),
+            array("@gmail.com/Test@12345", false, false),
+            array("/Test@12345", false, false),
+            array("mark.doliner@", false, false),
+            array("mark.doliner/", false, false),
+            array("mark.doliner@gmail_stuff.org", false, false),
+            array("mark.doliner@gmail[stuff.org", false, false),
+            array("mark.doliner@gmail\\stuff.org", false, false),
+            array("paul@[::1]124", false, false),
+            array("paul@2[::1]124/as", false, false),
+            array("paul@まつ.おおかみ/\x01", false, false),
+
+            /*
+             * RFC 3454 Section 6 reads, in part,
+             * "If a string contains any RandALCat character, the
+             *  string MUST NOT contain any LCat character."
+             * The character is U+066D (ARABIC FIVE POINTED STAR).
+             */
+            // Leaving this one commented out for the moment
+            // as it shouldn't hurt anything for our purposes.
+            //array("foo@example.com/٭simplexe٭", false, false)
+        );
+    }
+    
+    static public function normalizationCases()
+    {
+        return array(
+            // Borrowed from test_jabber_jutil.c in libpurple
+            array('PaUL@DaRkRain42.org', 'paul@darkrain42.org'),
+            array('PaUL@DaRkRain42.org/', 'paul@darkrain42.org'),
+            array('PaUL@DaRkRain42.org/resource', 'paul@darkrain42.org'),
+
+            // Also adapted from libpurple tests...
+            array('Ф@darkrain42.org', 'ф@darkrain42.org'),
+            array('paul@Өarkrain.org', 'paul@өarkrain.org'),
+        );
+    }
+
+    static public function domainCheckCases()
+    {
+        return array(
+            array('gmail.com', true, 'known SRV record'),
+            array('jabber.org', true, 'known SRV record'),
+            array('status.net', true, 'known SRV record'),
+            array('status.leuksman.com', true, 'known no SRV record but valid domain'),
+        );
+    }
+
+
+}
+