code:
<?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/>.
 */

if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }

function ping_broadcast_notice($notice) {
    if ($notice->is_local != Notice::LOCAL_PUBLIC && $notice->is_local != Notice::LOCAL_NONPUBLIC) {
        return true;
    }

    // Array of servers, URL => type
    $notify = common_config('ping', 'notify');
    try {
        $profile = $notice->getProfile();
    } catch (Exception $e) {
        // @todo: distinguish the 'broken notice/profile' case from more general
        //        transitory errors.
        common_log(LOG_ERR, "Exception getting notice profile: " . $e->getMessage());
        return true;
    }
    $tags = ping_notice_tags($notice);

    foreach ($notify as $notify_url => $type) {
        switch ($type) {
         case 'xmlrpc':
         case 'extended':
            $req = xmlrpc_encode_request('weblogUpdates.ping',
                                         array($profile->nickname, # site name
                                               common_local_url('showstream',
                                                                array('nickname' => $profile->nickname)),
                                               common_local_url('shownotice',
                                                                array('notice' => $notice->id)),
                                               common_local_url('userrss',
                                                                array('nickname' => $profile->nickname)),
                                               $tags));

            $request = HTTPClient::start();
            $request->setConfig('connect_timeout', common_config('ping', 'timeout'));
            $request->setConfig('timeout', common_config('ping', 'timeout'));
            try {
                $httpResponse = $request->post($notify_url, array('Content-Type: text/xml'), $req);
            } catch (Exception $e) {
                common_log(LOG_ERR,
                           "Exception pinging $notify_url: " . $e->getMessage());
                break;
            }

            if (!$httpResponse || mb_strlen($httpResponse->getBody()) == 0) {
                common_log(LOG_WARNING,
                           "XML-RPC empty results for ping ($notify_url, $notice->id) ");
                break;
            }

            $response = xmlrpc_decode($httpResponse->getBody());

            if (is_array($response) && xmlrpc_is_fault($response)) {
                common_log(LOG_WARNING,
                           "XML-RPC error for ping ($notify_url, $notice->id) ".
                           "$response[faultString] ($response[faultCode])");
            } else {
                common_log(LOG_INFO,
                           "Ping success for $notify_url $notice->id");
            }
            break;
         case 'get':
         case 'post':
            $args = array('name' => $profile->nickname,
                          'url' => common_local_url('showstream',
                                                    array('nickname' => $profile->nickname)),
                          'changesURL' => common_local_url('userrss',
                                                           array('nickname' => $profile->nickname)));

            $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();

            if ($type === 'get') {
                $result = $fetcher->get($notify_url . '?' . http_build_query($args),
                                        array('User-Agent: ' . HTTPClient::userAgent()));
            } else {
                $result = $fetcher->post($notify_url,
                                         http_build_query($args),
                                         array('User-Agent: ' . HTTPClient::userAgent()));
            }
            if ($result->status != '200') {
                common_log(LOG_WARNING,
                           "Ping error for '$notify_url' ($notice->id): ".
                           "$result->body");
            } else {
                common_log(LOG_INFO,
                           "Ping success for '$notify_url' ($notice->id): ".
                           "'$result->body'");
            }
            break;
         default:
            common_log(LOG_WARNING, 'Unknown notify type for ' . $notify_url . ': ' . $type);
        }
    }

    return true;
}

function ping_notice_tags($notice) {
    $tag = new Notice_tag();
    $tag->notice_id = $notice->id;
    $tags = array();
    if ($tag->find()) {
        while ($tag->fetch()) {
            $tags[] = $tag->tag;
        }
        $tag->free();
        unset($tag);
        return implode('|', $tags);
    }
    return NULL;
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * i/o manager to watch for a dead parent process
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  QueueManager
 * @package   StatusNet
 * @author    Brion Vibber <brion@status.net>
 * @copyright 2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

class ProcessManager extends IoManager
{
    protected $socket;

    public static function get()
    {
        throw new Exception("Must pass ProcessManager per-instance");
    }

    public function __construct($socket)
    {
        $this->socket = $socket;
    }

    /**
     * Tell the i/o queue master if and how we can handle multi-site
     * processes.
     *
     * Return one of:
     *   IoManager::SINGLE_ONLY
     *   IoManager::INSTANCE_PER_SITE
     *   IoManager::INSTANCE_PER_PROCESS
     */
    public static function multiSite()
    {
        return IoManager::INSTANCE_PER_PROCESS;
    }

    /**
     * We won't get any input on it, but if it's broken we'll
     * know something's gone horribly awry.
     *
     * @return array of resources
     */
    function getSockets()
    {
        return array($this->socket);
    }

    /**
     * See if the parent died and request a shutdown...
     *
     * @param resource $socket
     * @return boolean success
     */
    function handleInput($socket)
    {
        if (feof($socket)) {
            common_log(LOG_INFO, "Parent process exited; shutting down child.");
            $this->master->requestShutdown();
        }
        return true;
    }
}

repo_name: suraj/sloth

license: mit


code:
<?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.
     *
     * Note: this was named returnToArgs() up through 0.9.2, which
     * caused problems because there's an Action::returnToArgs()
     * already which does something different.
     *
     * @return void
     */
    function returnToPrevious()
    {
        // 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->defaultReturnTo();
        }
        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()
    {
        // TRANS: Client error displayed when return-to was defined without a target.
        $this->clientError(_('No return-to arguments.'));
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Class for user rights
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  Authorization
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET') && !defined('LACONICA')) {
    exit(1);
}

/**
 * class for rights
 *
 * Mostly for holding the rights constants
 *
 * @category Authorization
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */

class Right
{
    const DELETEOTHERSNOTICE = 'deleteothersnotice';
    const CONFIGURESITE      = 'configuresite';
    const DELETEUSER         = 'deleteuser';
    const SILENCEUSER        = 'silenceuser';
    const SANDBOXUSER        = 'sandboxuser';
    const NEWNOTICE          = 'newnotice';
    const PUBLICNOTICE       = 'publicnotice';
    const NEWMESSAGE         = 'newmessage';
    const SUBSCRIBE          = 'subscribe';
    const EMAILONREPLY       = 'emailonreply';
    const EMAILONSUBSCRIBE   = 'emailonsubscribe';
    const EMAILONFAVE        = 'emailonfave';
    const MAKEGROUPADMIN     = 'makegroupadmin';
    const GRANTROLE          = 'grantrole';
    const REVOKEROLE         = 'revokerole';
    const DELETEGROUP        = 'deletegroup';
    const BACKUPACCOUNT      = 'backupaccount';
    const RESTOREACCOUNT     = 'restoreaccount';
    const DELETEACCOUNT      = 'deleteaccount';
    const MOVEACCOUNT        = 'moveaccount';
    const CREATEGROUP        = 'creategroup';
    const WEBLOGIN           = 'weblogin';
    const API                = 'api';
    const REVIEWSPAM         = 'reviewspam';
    const TRAINSPAM          = 'trainspam';
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * URL routing utilities
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  URL
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

if (!defined('GNUSOCIAL')) { exit(1); }

/**
 * URL Router
 *
 * Cheap wrapper around Net_URL_Mapper
 *
 * @category URL
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class Router
{
    var $m = null;
    static $inst = null;

    const REGEX_TAG = '[^\/]+'; // [\pL\pN_\-\.]{1,64} better if we can do unicode regexes

    static function get()
    {
        if (!Router::$inst) {
            Router::$inst = new Router();
        }
        return Router::$inst;
    }

    /**
     * Clear the global singleton instance for this class.
     * Needed to ensure reset when switching site configurations.
     */
    static function clear()
    {
        Router::$inst = null;
    }

    function __construct()
    {
        if (empty($this->m)) {
            $this->m = $this->initialize();
        }
    }

    /**
     * Create a unique hashkey for the router.
     *
     * The router's url map can change based on the version of the software
     * you're running and the plugins that are enabled. To avoid having bad routes
     * get stuck in the cache, the key includes a list of plugins and the software
     * version.
     *
    * There can still be problems with a) differences in versions of the plugins and
     * b) people running code between official versions, but these tend to be more
     * sophisticated users who can grok what's going on and clear their caches.
     *
     * @return string cache key string that should uniquely identify a router
     */

    static function cacheKey()
    {
        $parts = array('router');

        // Many router paths depend on this setting.
        if (common_config('singleuser', 'enabled')) {
            $parts[] = '1user';
        } else {
            $parts[] = 'multi';
        }

        return Cache::codeKey(implode(':', $parts));
    }

    function initialize()
    {
        $m = new URLMapper();

        if (Event::handle('StartInitializeRouter', [&$m])) {

            // top of the menu hierarchy, sometimes "Home"
            $m->connect('', ['action' => 'top']);

            // public endpoints

            $m->connect('robots.txt', ['action' => 'robotstxt']);

            $m->connect('opensearch/people',
                        ['action' => 'opensearch',
                         'type' => 'people']);

            $m->connect('opensearch/notice',
                        ['action' => 'opensearch',
                         'type' => 'notice']);

            // docs

            $m->connect('doc/:title', ['action' => 'doc']);

            $m->connect('main/otp/:user_id/:token',
                        ['action' => 'otp'],
                        ['user_id' => '[0-9]+',
                         'token' => '.+']);

            // these take a code; before the main part

            foreach (['register', 'confirmaddress', 'recoverpassword'] as $c) {
                $m->connect('main/'.$c.'/:code', ['action' => $c]);
            }

            // Also need a block variant accepting ID on URL for mail links
            $m->connect('main/block/:profileid',
                        ['action' => 'block'],
                        ['profileid' => '[0-9]+']);

            $m->connect('main/sup/:seconds',
                        ['action' => 'sup'],
                        ['seconds' => '[0-9]+']);

            // main stuff is repetitive

            $main = ['login', 'logout', 'register', 'subscribe',
                     'unsubscribe', 'cancelsubscription', 'approvesub',
                     'confirmaddress', 'recoverpassword',
                     'invite', 'sup',
                     'block', 'unblock', 'subedit',
                     'groupblock', 'groupunblock',
                     'sandbox', 'unsandbox',
                     'silence', 'unsilence',
                     'grantrole', 'revokerole',
                     'deleteuser',
                     'geocode',
                     'version',
                     'backupaccount',
                     'deleteaccount',
                     'restoreaccount',
                     'top',
                     'public'];

            foreach ($main as $a) {
                $m->connect('main/'.$a, ['action' => $a]);
            }

            $m->connect('main/all', ['action' => 'networkpublic']);

            $m->connect('main/tagprofile/:id',
                        ['action' => 'tagprofile'],
                        ['id' => '[0-9]+']);

            $m->connect('main/tagprofile', ['action' => 'tagprofile']);

            $m->connect('main/xrds',
                        ['action' => 'publicxrds']);

            // settings

            foreach (['profile', 'avatar', 'password', 'im', 'oauthconnections',
                           'oauthapps', 'email', 'sms', 'url'] as $s) {
                $m->connect('settings/'.$s, ['action' => $s.'settings']);
            }

            if (common_config('oldschool', 'enabled')) {
                $m->connect('settings/oldschool', ['action' => 'oldschoolsettings']);
            }

            $m->connect('settings/oauthapps/show/:id',
                        ['action' => 'showapplication'],
                        ['id' => '[0-9]+']);

            $m->connect('settings/oauthapps/new',
                        ['action' => 'newapplication']);

            $m->connect('settings/oauthapps/edit/:id',
                        ['action' => 'editapplication'],
                        ['id' => '[0-9]+']);

            $m->connect('settings/oauthapps/delete/:id',
                        ['action' => 'deleteapplication'],
                        ['id' => '[0-9]+']);

            // search

            foreach (['group', 'people', 'notice'] as $s) {
                $m->connect('search/'.$s.'?q=:q',
                            ['action' => $s.'search'],
                            ['q' => '.+']);
                $m->connect('search/'.$s, ['action' => $s.'search']);
            }

            // The second of these is needed to make the link work correctly
            // when inserted into the page. The first is needed to match the
            // route on the way in. Seems to be another Net_URL_Mapper bug to me.
            $m->connect('search/notice/rss?q=:q',
                        ['action' => 'noticesearchrss'],
                        ['q' => '.+']);
            $m->connect('search/notice/rss', ['action' => 'noticesearchrss']);

            foreach (['' => 'attachment',
                      '/view' => 'attachment_view',
                      '/download' => 'attachment_download',
                      '/thumbnail' => 'attachment_thumbnail'] as $postfix => $action) {
                foreach (['filehash' => '[A-Za-z0-9._-]{64}',
                          'attachment' => '[0-9]+'] as $type => $match) {
                    $m->connect("attachment/:{$type}{$postfix}",
                                ['action' => $action],
                                [$type => $match]);
                }
            }

            $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto',
                        ['action' => 'newnotice'],
                        ['replyto' => Nickname::DISPLAY_FMT,
                         'inreplyto' => '[0-9]+']);

            $m->connect('notice/new?replyto=:replyto',
                        ['action' => 'newnotice'],
                        ['replyto' => Nickname::DISPLAY_FMT]);

            $m->connect('notice/new', ['action' => 'newnotice']);

            $m->connect('notice/:notice',
                        ['action' => 'shownotice'],
                        ['notice' => '[0-9]+']);

            $m->connect('notice/:notice/delete',
                        ['action' => 'deletenotice'],
                        ['notice' => '[0-9]+']);

            // conversation

            $m->connect('conversation/:id',
                        ['action' => 'conversation'],
                        ['id' => '[0-9]+']);

            $m->connect('user/:id',
                        ['action' => 'userbyid'],
                        ['id' => '[0-9]+']);

            $m->connect('tag/:tag/rss',
                        ['action' => 'tagrss'],
                        ['tag' => self::REGEX_TAG]);
            $m->connect('tag/:tag',
                        ['action' => 'tag'],
                        ['tag' => self::REGEX_TAG]);

            // groups

            $m->connect('group/new', ['action' => 'newgroup']);

            foreach (['edit', 'join', 'leave', 'delete', 'cancel', 'approve'] as $v) {
                $m->connect('group/:nickname/'.$v,
                            ['action' => $v.'group'],
                            ['nickname' => Nickname::DISPLAY_FMT]);
                $m->connect('group/:id/id/'.$v,
                            ['action' => $v.'group'],
                            ['id' => '[0-9]+']);
            }

            foreach (['members', 'logo', 'rss'] as $n) {
                $m->connect('group/:nickname/'.$n,
                            ['action' => 'group'.$n],
                            ['nickname' => Nickname::DISPLAY_FMT]);
            }

            $m->connect('group/:nickname/foaf',
                        ['action' => 'foafgroup'],
                        ['nickname' => Nickname::DISPLAY_FMT]);

            $m->connect('group/:nickname/blocked',
                        ['action' => 'blockedfromgroup'],
                        ['nickname' => Nickname::DISPLAY_FMT]);

            $m->connect('group/:nickname/makeadmin',
                        ['action' => 'makeadmin'],
                        ['nickname' => Nickname::DISPLAY_FMT]);

            $m->connect('group/:nickname/members/pending',
                        ['action' => 'groupqueue'],
                        ['nickname' => Nickname::DISPLAY_FMT]);

            $m->connect('group/:id/id',
                        ['action' => 'groupbyid'],
                        ['id' => '[0-9]+']);

            $m->connect('group/:nickname',
                        ['action' => 'showgroup'],
                        ['nickname' => Nickname::DISPLAY_FMT]);

            $m->connect('group/:nickname/',
                        ['action' => 'showgroup'],
                        ['nickname' => Nickname::DISPLAY_FMT]);

            $m->connect('group/', ['action' => 'groups']);
            $m->connect('group', ['action' => 'groups']);
            $m->connect('groups/', ['action' => 'groups']);
            $m->connect('groups', ['action' => 'groups']);

            // Twitter-compatible API

            // statuses API

            $m->connect('api',
                        ['action' => 'Redirect',
                         'nextAction' => 'doc',
                         'args' => ['title' => 'api']]);

            $m->connect('api/statuses/public_timeline.:format',
                        ['action' => 'ApiTimelinePublic'],
                        ['format' => '(xml|json|rss|atom|as)']);

            // this is not part of the Twitter API. Also may require authentication depending on server config!
            $m->connect('api/statuses/networkpublic_timeline.:format',
                        ['action' => 'ApiTimelineNetworkPublic'],
                        ['format' => '(xml|json|rss|atom|as)']);

            $m->connect('api/statuses/friends_timeline/:id.:format',
                        ['action' => 'ApiTimelineFriends'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json|rss|atom|as)']);

            $m->connect('api/statuses/friends_timeline.:format',
                        ['action' => 'ApiTimelineFriends'],
                        ['format' => '(xml|json|rss|atom|as)']);

            $m->connect('api/statuses/home_timeline/:id.:format',
                        ['action' => 'ApiTimelineHome'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json|rss|atom|as)']);

            $m->connect('api/statuses/home_timeline.:format',
                        ['action' => 'ApiTimelineHome'],
                        ['format' => '(xml|json|rss|atom|as)']);

            $m->connect('api/statuses/user_timeline/:id.:format',
                        ['action' => 'ApiTimelineUser'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json|rss|atom|as)']);

            $m->connect('api/statuses/user_timeline.:format',
                        ['action' => 'ApiTimelineUser'],
                        ['format' => '(xml|json|rss|atom|as)']);

            $m->connect('api/statuses/mentions/:id.:format',
                        ['action' => 'ApiTimelineMentions'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json|rss|atom|as)']);

            $m->connect('api/statuses/mentions.:format',
                        ['action' => 'ApiTimelineMentions'],
                        ['format' => '(xml|json|rss|atom|as)']);

            $m->connect('api/statuses/replies/:id.:format',
                        ['action' => 'ApiTimelineMentions'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json|rss|atom|as)']);

            $m->connect('api/statuses/replies.:format',
                        ['action' => 'ApiTimelineMentions'],
                        ['format' => '(xml|json|rss|atom|as)']);

            $m->connect('api/statuses/mentions_timeline/:id.:format',
                        ['action' => 'ApiTimelineMentions'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json|rss|atom|as)']);

            $m->connect('api/statuses/mentions_timeline.:format',
                        ['action' => 'ApiTimelineMentions'],
                        ['format' => '(xml|json|rss|atom|as)']);

            $m->connect('api/statuses/friends/:id.:format',
                        ['action' => 'ApiUserFriends'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json)']);

            $m->connect('api/statuses/friends.:format',
                        ['action' => 'ApiUserFriends'],
                        ['format' => '(xml|json)']);

            $m->connect('api/statuses/followers/:id.:format',
                        ['action' => 'ApiUserFollowers'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json)']);

            $m->connect('api/statuses/followers.:format',
                        ['action' => 'ApiUserFollowers'],
                         ['format' => '(xml|json)']);

            $m->connect('api/statuses/show/:id.:format',
                        ['action' => 'ApiStatusesShow'],
                        ['id' => '[0-9]+',
                         'format' => '(xml|json|atom)']);

            $m->connect('api/statuses/show.:format',
                        ['action' => 'ApiStatusesShow'],
                        ['format' => '(xml|json|atom)']);

            $m->connect('api/statuses/update.:format',
                        ['action' => 'ApiStatusesUpdate'],
                        ['format' => '(xml|json|atom)']);

            $m->connect('api/statuses/destroy/:id.:format',
                        ['action' => 'ApiStatusesDestroy'],
                        ['id' => '[0-9]+',
                         'format' => '(xml|json)']);

            $m->connect('api/statuses/destroy.:format',
                        ['action' => 'ApiStatusesDestroy'],
                        ['format' => '(xml|json)']);

            // START qvitter API additions

            $m->connect('api/attachment/:id.:format',
                        ['action' => 'ApiAttachment'],
                        ['id' => '[0-9]+',
                         'format' => '(xml|json)']);

            $m->connect('api/checkhub.:format',
                        ['action' => 'ApiCheckHub'],
                        ['format' => '(xml|json)']);

            $m->connect('api/externalprofile/show.:format',
                        ['action' => 'ApiExternalProfileShow'],
                        ['format' => '(xml|json)']);

            $m->connect('api/statusnet/groups/admins/:id.:format',
                        ['action' => 'ApiGroupAdmins'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json)']);

            $m->connect('api/account/update_link_color.:format',
                        ['action' => 'ApiAccountUpdateLinkColor'],
                        ['format' => '(xml|json)']);

            $m->connect('api/account/update_background_color.:format',
                        ['action' => 'ApiAccountUpdateBackgroundColor'],
                        ['format' => '(xml|json)']);

            $m->connect('api/account/register.:format',
                        ['action' => 'ApiAccountRegister'],
                        ['format' => '(xml|json)']);

            $m->connect('api/check_nickname.:format',
                        ['action' => 'ApiCheckNickname'],
                        ['format' => '(xml|json)']);

            // END qvitter API additions

            // users

            $m->connect('api/users/show/:id.:format',
                        ['action' => 'ApiUserShow'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json)']);

            $m->connect('api/users/show.:format',
                        ['action' => 'ApiUserShow'],
                        ['format' => '(xml|json)']);

            $m->connect('api/users/profile_image/:screen_name.:format',
                        ['action' => 'ApiUserProfileImage'],
                        ['screen_name' => Nickname::DISPLAY_FMT,
                         'format' => '(xml|json)']);

            // friendships

            $m->connect('api/friendships/show.:format',
                        ['action' => 'ApiFriendshipsShow'],
                        ['format' => '(xml|json)']);

            $m->connect('api/friendships/exists.:format',
                        ['action' => 'ApiFriendshipsExists'],
                        ['format' => '(xml|json)']);

            $m->connect('api/friendships/create/:id.:format',
                        ['action' => 'ApiFriendshipsCreate'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json)']);

            $m->connect('api/friendships/create.:format',
                        ['action' => 'ApiFriendshipsCreate'],
                        ['format' => '(xml|json)']);

            $m->connect('api/friendships/destroy/:id.:format',
                        ['action' => 'ApiFriendshipsDestroy'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json)']);

            $m->connect('api/friendships/destroy.:format',
                        ['action' => 'ApiFriendshipsDestroy'],
                        ['format' => '(xml|json)']);

            // Social graph

            $m->connect('api/friends/ids/:id.:format',
                        ['action' => 'ApiUserFriends',
                         'ids_only' => true],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json)']);

            $m->connect('api/followers/ids/:id.:format',
                        ['action' => 'ApiUserFollowers',
                         'ids_only' => true],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json)']);

            $m->connect('api/friends/ids.:format',
                        ['action' => 'ApiUserFriends',
                         'ids_only' => true],
                        ['format' => '(xml|json)']);

            $m->connect('api/followers/ids.:format',
                        ['action' => 'ApiUserFollowers',
                         'ids_only' => true],
                        ['format' => '(xml|json)']);

            // account

            $m->connect('api/account/verify_credentials.:format',
                        ['action' => 'ApiAccountVerifyCredentials'],
                        ['format' => '(xml|json)']);

            $m->connect('api/account/update_profile.:format',
                        ['action' => 'ApiAccountUpdateProfile'],
                        ['format' => '(xml|json)']);

            $m->connect('api/account/update_profile_image.:format',
                        ['action' => 'ApiAccountUpdateProfileImage'],
                        ['format' => '(xml|json)']);

            $m->connect('api/account/update_delivery_device.:format',
                        ['action' => 'ApiAccountUpdateDeliveryDevice'],
                        ['format' => '(xml|json)']);

            // special case where verify_credentials is called w/out a format

            $m->connect('api/account/verify_credentials',
                        ['action' => 'ApiAccountVerifyCredentials']);

            $m->connect('api/account/rate_limit_status.:format',
                        ['action' => 'ApiAccountRateLimitStatus'],
                        ['format' => '(xml|json)']);

            // blocks

            $m->connect('api/blocks/create/:id.:format',
                        ['action' => 'ApiBlockCreate'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json)']);

            $m->connect('api/blocks/create.:format',
                        ['action' => 'ApiBlockCreate'],
                        ['format' => '(xml|json)']);

            $m->connect('api/blocks/destroy/:id.:format',
                        ['action' => 'ApiBlockDestroy'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json)']);

            $m->connect('api/blocks/destroy.:format',
                        ['action' => 'ApiBlockDestroy'],
                        ['format' => '(xml|json)']);

            // help

            $m->connect('api/help/test.:format',
                        ['action' => 'ApiHelpTest'],
                        ['format' => '(xml|json)']);

            // statusnet

            $m->connect('api/statusnet/version.:format',
                        ['action' => 'ApiGNUsocialVersion'],
                        ['format' => '(xml|json)']);

            $m->connect('api/statusnet/config.:format',
                        ['action' => 'ApiGNUsocialConfig'],
                        ['format' => '(xml|json)']);

            // For our current software name, we provide "gnusocial" base action

            $m->connect('api/gnusocial/version.:format',
                        ['action' => 'ApiGNUsocialVersion'],
                        ['format' => '(xml|json)']);

            $m->connect('api/gnusocial/config.:format',
                        ['action' => 'ApiGNUsocialConfig'],
                        ['format' => '(xml|json)']);

            // Groups and tags are newer than 0.8.1 so no backward-compatibility
            // necessary

            // Groups
            //'list' has to be handled differently, as php will not allow a method to be named 'list'

            $m->connect('api/statusnet/groups/timeline/:id.:format',
                        ['action' => 'ApiTimelineGroup'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json|rss|atom|as)']);

            $m->connect('api/statusnet/groups/show/:id.:format',
                        ['action' => 'ApiGroupShow'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json)']);

            $m->connect('api/statusnet/groups/show.:format',
                        ['action' => 'ApiGroupShow'],
                        ['format' => '(xml|json)']);

            $m->connect('api/statusnet/groups/join/:id.:format',
                        ['action' => 'ApiGroupJoin'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json)']);

            $m->connect('api/statusnet/groups/join.:format',
                        ['action' => 'ApiGroupJoin'],
                        ['format' => '(xml|json)']);

            $m->connect('api/statusnet/groups/leave/:id.:format',
                        ['action' => 'ApiGroupLeave'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json)']);

            $m->connect('api/statusnet/groups/leave.:format',
                        ['action' => 'ApiGroupLeave'],
                        ['format' => '(xml|json)']);

            $m->connect('api/statusnet/groups/is_member.:format',
                        ['action' => 'ApiGroupIsMember'],
                        ['format' => '(xml|json)']);

            $m->connect('api/statusnet/groups/list/:id.:format',
                        ['action' => 'ApiGroupList'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json|rss|atom)']);

            $m->connect('api/statusnet/groups/list.:format',
                        ['action' => 'ApiGroupList'],
                        ['format' => '(xml|json|rss|atom)']);

            $m->connect('api/statusnet/groups/list_all.:format',
                        ['action' => 'ApiGroupListAll'],
                        ['format' => '(xml|json|rss|atom)']);

            $m->connect('api/statusnet/groups/membership/:id.:format',
                        ['action' => 'ApiGroupMembership'],
                        ['id' => Nickname::INPUT_FMT,
                         'format' => '(xml|json)']);

            $m->connect('api/statusnet/groups/membership.:format',
                        ['action' => 'ApiGroupMembership'],
                        ['format' => '(xml|json)']);

            $m->connect('api/statusnet/groups/create.:format',
                        ['action' => 'ApiGroupCreate'],
                        ['format' => '(xml|json)']);

            $m->connect('api/statusnet/groups/update/:id.:format',
                        ['action' => 'ApiGroupProfileUpdate'],
                        ['id' => '[a-zA-Z0-9]+',
                         'format' => '(xml|json)']);

            $m->connect('api/statusnet/conversation/:id.:format',
                        ['action' => 'apiconversation'],
                        ['id' => '[0-9]+',
                         'format' => '(xml|json|rss|atom|as)']);

            // Lists (people tags)
            $m->connect('api/lists/list.:format',
                        ['action' => 'ApiListSubscriptions'],
                        ['format' => '(xml|json)']);

            $m->connect('api/lists/memberships.:format',
                        ['action' => 'ApiListMemberships'],
                        ['format' => '(xml|json)']);

            $m->connect('api/:user/lists/memberships.:format',
                        ['action' => 'ApiListMemberships'],
                        ['user' => '[a-zA-Z0-9]+',
                         'format' => '(xml|json)']);

            $m->connect('api/lists/subscriptions.:format',
                        ['action' => 'ApiListSubscriptions'],
                        ['format' => '(xml|json)']);

            $m->connect('api/:user/lists/subscriptions.:format',
                        ['action' => 'ApiListSubscriptions'],
                        ['user' => '[a-zA-Z0-9]+',
                         'format' => '(xml|json)']);

            $m->connect('api/lists.:format',
                        ['action' => 'ApiLists'],
                        ['format' => '(xml|json)']);

            $m->connect('api/:user/lists/:id.:format',
                        ['action' => 'ApiList'],
                        ['user' => '[a-zA-Z0-9]+',
                         'id' => '[a-zA-Z0-9]+',
                         'format' => '(xml|json)']);

            $m->connect('api/:user/lists.:format',
                        ['action' => 'ApiLists'],
                        ['user' => '[a-zA-Z0-9]+',
                         'format' => '(xml|json)']);

            $m->connect('api/:user/lists/:id/statuses.:format',
                        ['action' => 'ApiTimelineList'],
                        ['user' => '[a-zA-Z0-9]+',
                         'id' => '[a-zA-Z0-9]+',
                         'format' => '(xml|json|rss|atom)']);

            $m->connect('api/:user/:list_id/members/:id.:format',
                        ['action' => 'ApiListMember'],
                        ['user' => '[a-zA-Z0-9]+',
                         'list_id' => '[a-zA-Z0-9]+',
                         'id' => '[a-zA-Z0-9]+',
                         'format' => '(xml|json)']);

            $m->connect('api/:user/:list_id/members.:format',
                        ['action' => 'ApiListMembers'],
                        ['user' => '[a-zA-Z0-9]+',
                        'list_id' => '[a-zA-Z0-9]+',
                        'format' => '(xml|json)']);

            $m->connect('api/:user/:list_id/subscribers/:id.:format',
                        ['action' => 'ApiListSubscriber'],
                        ['user' => '[a-zA-Z0-9]+',
                         'list_id' => '[a-zA-Z0-9]+',
                         'id' => '[a-zA-Z0-9]+',
                         'format' => '(xml|json)']);

            $m->connect('api/:user/:list_id/subscribers.:format',
                        ['action' => 'ApiListSubscribers'],
                        ['user' => '[a-zA-Z0-9]+',
                         'list_id' => '[a-zA-Z0-9]+',
                         'format' => '(xml|json)']);

            // Tags
            $m->connect('api/statusnet/tags/timeline/:tag.:format',
                        ['action' => 'ApiTimelineTag'],
                        ['tag'    => self::REGEX_TAG,
                         'format' => '(xml|json|rss|atom|as)']);

            // media related
            $m->connect('api/statusnet/media/upload',
                        ['action' => 'ApiMediaUpload']);

            $m->connect('api/statuses/update_with_media.json',
                        ['action' => 'ApiMediaUpload']);

            // Twitter Media upload API v1.1
            $m->connect('api/media/upload.:format',
                        ['action' => 'ApiMediaUpload'],
                        ['format' => '(xml|json)']);

            // search
            $m->connect('api/search.atom', ['action' => 'ApiSearchAtom']);
            $m->connect('api/search.json', ['action' => 'ApiSearchJSON']);
            $m->connect('api/trends.json', ['action' => 'ApiTrends']);

            $m->connect('api/oauth/request_token',
                        ['action' => 'ApiOAuthRequestToken']);

            $m->connect('api/oauth/access_token',
                        ['action' => 'ApiOAuthAccessToken']);

            $m->connect('api/oauth/authorize',
                        ['action' => 'ApiOAuthAuthorize']);

            // Admin

            $m->connect('panel/site', ['action' => 'siteadminpanel']);
            $m->connect('panel/user', ['action' => 'useradminpanel']);
            $m->connect('panel/access', ['action' => 'accessadminpanel']);
            $m->connect('panel/paths', ['action' => 'pathsadminpanel']);
            $m->connect('panel/sessions', ['action' => 'sessionsadminpanel']);
            $m->connect('panel/sitenotice', ['action' => 'sitenoticeadminpanel']);
            $m->connect('panel/license', ['action' => 'licenseadminpanel']);

            $m->connect('panel/plugins', ['action' => 'pluginsadminpanel']);
            $m->connect('panel/plugins/enable/:plugin',
                        ['action' => 'pluginenable'],
                        ['plugin' => '[A-Za-z0-9_]+']);
            $m->connect('panel/plugins/disable/:plugin',
                        ['action' => 'plugindisable'],
                        ['plugin' => '[A-Za-z0-9_]+']);
            $m->connect('panel/plugins/delete/:plugin',
                ['action' => 'plugindelete'],
                ['plugin' => '[A-Za-z0-9_]+']);
            $m->connect('panel/plugins/install',
                        ['action' => 'plugininstall']);

            // Common people-tag stuff

            $m->connect('peopletag/:tag',
                        ['action' => 'peopletag'],
                        ['tag'    => self::REGEX_TAG]);

            $m->connect('selftag/:tag',
                        ['action' => 'selftag'],
                        ['tag'    => self::REGEX_TAG]);

            $m->connect('main/addpeopletag', ['action' => 'addpeopletag']);

            $m->connect('main/removepeopletag', ['action' => 'removepeopletag']);

            $m->connect('main/profilecompletion', ['action' => 'profilecompletion']);

            $m->connect('main/peopletagautocomplete', ['action' => 'peopletagautocomplete']);

            // In the "root"

            if (common_config('singleuser', 'enabled')) {

                $nickname = User::singleUserNickname();

                foreach (['subscriptions', 'subscribers', 'all', 'foaf', 'replies'] as $a) {
                    $m->connect($a,
                                ['action' => $a,
                                 'nickname' => $nickname]);
                }

                foreach (['subscriptions', 'subscribers'] as $a) {
                    $m->connect($a.'/:tag',
                                ['action' => $a,
                                 'nickname' => $nickname],
                                ['tag' => self::REGEX_TAG]);
                }

                $m->connect('subscribers/pending',
                            ['action' => 'subqueue',
                             'nickname' => $nickname]);

                foreach (['rss', 'groups'] as $a) {
                    $m->connect($a,
                                ['action' => 'user'.$a,
                                 'nickname' => $nickname]);
                }

                foreach (['all', 'replies'] as $a) {
                    $m->connect($a.'/rss',
                                ['action' => $a.'rss',
                                 'nickname' => $nickname]);
                }

                $m->connect('avatar',
                            ['action' => 'avatarbynickname',
                             'nickname' => $nickname]);

                $m->connect('avatar/:size',
                            ['action' => 'avatarbynickname',
                             'nickname' => $nickname],
                            ['size' => '(|original|\d+)']);

                $m->connect('tag/:tag/rss',
                            ['action' => 'userrss',
                             'nickname' => $nickname],
                            ['tag' => self::REGEX_TAG]);

                $m->connect('tag/:tag',
                            ['action' => 'showstream',
                             'nickname' => $nickname],
                            ['tag' => self::REGEX_TAG]);

                $m->connect('rsd.xml',
                            ['action' => 'rsd',
                             'nickname' => $nickname]);

                // peopletags

                $m->connect('peopletags',
                            ['action' => 'peopletagsbyuser']);

                $m->connect('peopletags/private',
                            ['action' => 'peopletagsbyuser',
                             'private' => 1]);

                $m->connect('peopletags/public',
                            ['action' => 'peopletagsbyuser',
                             'public' => 1]);

                $m->connect('othertags',
                            ['action' => 'peopletagsforuser']);

                $m->connect('peopletagsubscriptions',
                            ['action' => 'peopletagsubscriptions']);

                $m->connect('all/:tag/subscribers',
                            ['action' => 'peopletagsubscribers'],
                            ['tag' => self::REGEX_TAG]);

                $m->connect('all/:tag/tagged',
                            ['action' => 'peopletagged'],
                            ['tag' => self::REGEX_TAG]);

                $m->connect('all/:tag/edit',
                            ['action' => 'editpeopletag'],
                            ['tag' => self::REGEX_TAG]);

                foreach (['subscribe', 'unsubscribe'] as $v) {
                    $m->connect('peopletag/:id/'.$v,
                                ['action' => $v.'peopletag'],
                                ['id' => '[0-9]{1,64}']);
                }

                $m->connect('user/:tagger_id/profiletag/:id/id',
                            ['action' => 'profiletagbyid'],
                            ['tagger_id' => '[0-9]+',
                             'id' => '[0-9]+']);

                $m->connect('all/:tag',
                            ['action' => 'showprofiletag',
                             'tagger' => $nickname],
                            ['tag' => self::REGEX_TAG]);

                foreach (['subscriptions', 'subscribers'] as $a) {
                    $m->connect($a.'/:tag',
                                ['action' => $a],
                                ['tag' => self::REGEX_TAG]);
                }
            }

            $m->connect('rss', ['action' => 'publicrss']);
            $m->connect('featuredrss', ['action' => 'featuredrss']);
            $m->connect('featured/', ['action' => 'featured']);
            $m->connect('featured', ['action' => 'featured']);
            $m->connect('rsd.xml', ['action' => 'rsd']);

            foreach (['subscriptions', 'subscribers',
                           'nudge', 'all', 'foaf', 'replies',
                           'inbox', 'outbox'] as $a) {
                $m->connect(':nickname/'.$a,
                            ['action' => $a],
                            ['nickname' => Nickname::DISPLAY_FMT]);
            }

            $m->connect(':nickname/subscribers/pending',
                        ['action' => 'subqueue'],
                        ['nickname' => Nickname::DISPLAY_FMT]);

            // some targeted RSS 1.0 actions (extends TargetedRss10Action)
            foreach (['all', 'replies'] as $a) {
                $m->connect(':nickname/'.$a.'/rss',
                            ['action' => $a.'rss'],
                            ['nickname' => Nickname::DISPLAY_FMT]);
            }

            // people tags

            $m->connect(':nickname/peopletags',
                        ['action' => 'peopletagsbyuser'],
                        ['nickname' => Nickname::DISPLAY_FMT]);

            $m->connect(':nickname/peopletags/private',
                        ['action' => 'peopletagsbyuser',
                         'private' => 1],
                        ['nickname' => Nickname::DISPLAY_FMT]);

            $m->connect(':nickname/peopletags/public',
                        ['action' => 'peopletagsbyuser',
                         'public' => 1],
                        ['nickname' => Nickname::DISPLAY_FMT]);

            $m->connect(':nickname/othertags',
                        ['action' => 'peopletagsforuser'],
                        ['nickname' => Nickname::DISPLAY_FMT]);

            $m->connect(':nickname/peopletagsubscriptions',
                        ['action' => 'peopletagsubscriptions'],
                        ['nickname' => Nickname::DISPLAY_FMT]);

            $m->connect(':tagger/all/:tag/subscribers',
                        ['action' => 'peopletagsubscribers'],
                        ['tagger' => Nickname::DISPLAY_FMT,
                         'tag' => self::REGEX_TAG]);

            $m->connect(':tagger/all/:tag/tagged',
                        ['action' => 'peopletagged'],
                        ['tagger' => Nickname::DISPLAY_FMT,
                         'tag' => self::REGEX_TAG]);

            $m->connect(':tagger/all/:tag/edit',
                        ['action' => 'editpeopletag'],
                        ['tagger' => Nickname::DISPLAY_FMT,
                         'tag' => self::REGEX_TAG]);

            foreach (['subscribe', 'unsubscribe'] as $v) {
                $m->connect('peopletag/:id/'.$v,
                            ['action' => $v.'peopletag'],
                            ['id' => '[0-9]{1,64}']);
            }

            $m->connect('user/:tagger_id/profiletag/:id/id',
                        ['action' => 'profiletagbyid'],
                        ['tagger_id' => '[0-9]+',
                         'id' => '[0-9]+']);

            $m->connect(':nickname/all/:tag',
                        ['action' => 'showprofiletag'],
                        ['nickname' => Nickname::DISPLAY_FMT,
                         'tag' => self::REGEX_TAG]);

            foreach (['subscriptions', 'subscribers'] as $a) {
                $m->connect(':nickname/'.$a.'/:tag',
                            ['action' => $a],
                            ['tag' => self::REGEX_TAG,
                             'nickname' => Nickname::DISPLAY_FMT]);
            }

            foreach (['rss', 'groups'] as $a) {
                $m->connect(':nickname/'.$a,
                            ['action' => 'user'.$a],
                            ['nickname' => Nickname::DISPLAY_FMT]);
            }

            $m->connect('avatar/:file',
                        ['action' => 'avatar'],
                        ['file' => '.*']);

            $m->connect(':nickname/avatar',
                        ['action' => 'avatarbynickname'],
                        ['nickname' => Nickname::DISPLAY_FMT]);

            $m->connect(':nickname/avatar/:size',
                        ['action' => 'avatarbynickname'],
                        ['size' => '(|original|\d+)',
                         'nickname' => Nickname::DISPLAY_FMT]);

            $m->connect(':nickname/tag/:tag/rss',
                        ['action' => 'userrss'],
                        ['nickname' => Nickname::DISPLAY_FMT,
                         'tag' => self::REGEX_TAG]);

            $m->connect(':nickname/tag/:tag',
                        ['action' => 'showstream'],
                        ['nickname' => Nickname::DISPLAY_FMT,
                         'tag' => self::REGEX_TAG]);

            $m->connect(':nickname/rsd.xml',
                        ['action' => 'rsd'],
                        ['nickname' => Nickname::DISPLAY_FMT]);

            $m->connect(':nickname',
                        ['action' => 'showstream'],
                        ['nickname' => Nickname::DISPLAY_FMT]);

            $m->connect(':nickname/',
                        ['action' => 'showstream'],
                        ['nickname' => Nickname::DISPLAY_FMT]);

            // AtomPub API

            $m->connect('api/statusnet/app/service/:id.xml',
                        ['action' => 'ApiAtomService'],
                        ['id' => Nickname::DISPLAY_FMT]);

            $m->connect('api/statusnet/app/service.xml',
                        ['action' => 'ApiAtomService']);

            $m->connect('api/statusnet/app/subscriptions/:subscriber/:subscribed.atom',
                        ['action' => 'AtomPubShowSubscription'],
                        ['subscriber' => '[0-9]+',
                         'subscribed' => '[0-9]+']);

            $m->connect('api/statusnet/app/subscriptions/:subscriber.atom',
                        ['action' => 'AtomPubSubscriptionFeed'],
                        ['subscriber' => '[0-9]+']);

            $m->connect('api/statusnet/app/memberships/:profile/:group.atom',
                        ['action' => 'AtomPubShowMembership'],
                        ['profile' => '[0-9]+',
                         'group' => '[0-9]+']);

            $m->connect('api/statusnet/app/memberships/:profile.atom',
                        ['action' => 'AtomPubMembershipFeed'],
                        ['profile' => '[0-9]+']);

            // URL shortening

            $m->connect('url/:id',
                        ['action' => 'redirecturl'],
                        ['id' => '[0-9]+']);

            // user stuff

            Event::handle('RouterInitialized', [$m]);
        }

        return $m;
    }

    function map($path)
    {
        try {
            return $this->m->match($path);
        } catch (NoRouteMapException $e) {
            common_debug($e->getMessage());
            // TRANS: Client error on action trying to visit a non-existing page.
            throw new ClientException(_('Page not found.'), 404);
        }
    }

    function build($action, $args=null, $params=null, $fragment=null)
    {
        $action_arg = array('action' => $action);

        if ($args) {
            $args = array_merge($action_arg, $args);
        } else {
            $args = $action_arg;
        }

        $url = $this->m->generate($args, $params, $fragment);
        // Due to a bug in the Net_URL_Mapper code, the returned URL may
        // contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
        // repair that here rather than modifying the upstream code...

        $qpos = strpos($url, '?');
        if ($qpos !== false) {
            $url = substr($url, 0, $qpos+1) .
                str_replace('?', '&', substr($url, $qpos+1));

            // @fixme this is a hacky workaround for http_build_query in the
            // lower-level code and bad configs that set the default separator
            // to & instead of &. Encoded &s in parameters will not be
            // affected.
            $url = substr($url, 0, $qpos+1) .
                str_replace('&', '&', substr($url, $qpos+1));

        }

        return $url;
    }
}


code:
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social.  If not, see <http://www.gnu.org/licenses/>.

defined('GNUSOCIAL') || die();

/**
 * Base class for daemon that can launch one or more processing threads,
 * respawning them if they exit.
 *
 * This is mainly intended for indefinite workloads such as monitoring
 * a queue or maintaining an IM channel.
 *
 * Child classes should implement the
 *
 * We can then pass individual items through the QueueHandler subclasses
 * they belong to. We additionally can handle queues for multiple sites.
 *
 * @package QueueHandler
 * @author Brion Vibber <brion@status.net>
 */
abstract class SpawningDaemon extends Daemon
{
    protected $threads = 1;

    const EXIT_OK = 0;
    const EXIT_ERR = 1;
    const EXIT_SHUTDOWN = 100;
    const EXIT_RESTART = 101;

    public function __construct($id = null, $daemonize = true, $threads = 1)
    {
        parent::__construct($daemonize);

        if ($id) {
            $this->set_id($id);
        }
        $this->threads = $threads;
    }

    /**
     * Perform some actual work!
     *
     * @return int exit code; use self::EXIT_SHUTDOWN to request not to respawn.
     */
    abstract public function runThread();

    /**
     * Spawn one or more background processes and let them start running.
     * Each individual process will execute whatever's in the runThread()
     * method, which should be overridden.
     *
     * Child processes will be automatically respawned when they exit.
     *
     * @todo possibly allow for not respawning on "normal" exits...
     *       though ParallelizingDaemon is probably better for workloads
     *       that have forseeable endpoints.
     */
    public function run()
    {
        $this->initPipes();

        $children = [];
        for ($i = 1; $i <= $this->threads; $i++) {
            $pid = pcntl_fork();
            if ($pid < 0) {
                $this->log(LOG_ERR, "Couldn't fork for thread $i; aborting\n");
                exit(1);
            } elseif ($pid === 0) {
                $this->initAndRunChild($i);
            } else {
                $this->log(LOG_INFO, "Spawned thread $i as pid $pid");
                $children[$i] = $pid;
            }
            sleep(common_config('queue', 'spawndelay'));
        }

        $this->log(LOG_INFO, "Waiting for children to complete.");
        while (count($children) > 0) {
            $status = null;
            $pid = pcntl_wait($status);
            if ($pid > 0) {
                $i = array_search($pid, $children);
                if ($i === false) {
                    $this->log(LOG_ERR, "Ignoring exit of unrecognized child pid $pid");
                    continue;
                }
                if (pcntl_wifexited($status)) {
                    $exitCode = pcntl_wexitstatus($status);
                    $info = "status $exitCode";
                } elseif (pcntl_wifsignaled($status)) {
                    $exitCode = self::EXIT_ERR;
                    $signal = pcntl_wtermsig($status);
                    $info = "signal $signal";
                }
                unset($children[$i]);

                if ($this->shouldRespawn($exitCode)) {
                    $this->log(LOG_INFO, "Thread $i pid $pid exited with $info; respawing.");

                    $pid = pcntl_fork();
                    if ($pid < 0) {
                        $this->log(LOG_ERR, "Couldn't fork to respawn thread $i; aborting thread.\n");
                    } elseif ($pid === 0) {
                        $this->initAndRunChild($i);
                    } else {
                        $this->log(LOG_INFO, "Respawned thread $i as pid $pid");
                        $children[$i] = $pid;
                    }
                    sleep(common_config('queue', 'spawndelay'));
                } else {
                    $this->log(LOG_INFO, "Thread $i pid $pid exited with status $exitCode; closing out thread.");
                }
            }
        }
        $this->log(LOG_INFO, "All child processes complete.");
        return true;
    }

    /**
     * Create an IPC socket pair which child processes can use to detect
     * if the parent process has been killed.
     */
    public function initPipes()
    {
        $sockets = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
        if ($sockets) {
            $this->parentWriter = $sockets[0];
            $this->parentReader = $sockets[1];
        } else {
            $this->log(LOG_ERR, "Couldn't create inter-process sockets");
            exit(1);
        }
    }

    /**
     * Build an IOManager that simply ensures that we have a connection
     * to the parent process open. If it breaks, the child process will
     * die.
     *
     * @return ProcessManager
     */
    public function processManager()
    {
        return new ProcessManager($this->parentReader);
    }

    /**
     * Determine whether to respawn an exited subprocess based on its exit code.
     * Otherwise we'll respawn all exits by default.
     *
     * @param int $exitCode
     * @return boolean true to respawn
     */
    protected function shouldRespawn($exitCode)
    {
        if ($exitCode == self::EXIT_SHUTDOWN) {
            // Thread requested a clean shutdown.
            return false;
        } else {
            // Otherwise we should always respawn!
            return true;
        }
    }

    /**
     * Initialize things for a fresh thread, call runThread(), and
     * exit at completion with appropriate return value.
     */
    protected function initAndRunChild($thread)
    {
        // Close the writer end of our parent<->children pipe.
        fclose($this->parentWriter);
        $this->set_id($this->get_id() . "." . $thread);
        $this->resetDb();
        $exitCode = $this->runThread();
        exit($exitCode);
    }

    public function log($level, $msg)
    {
        common_log($level, get_class($this) . ' ('. $this->get_id() .'): '.$msg);
    }

    public function name()
    {
        return strtolower(get_class($this).'.'.$this->get_id());
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Utility for creating new tag: URIs
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  URI
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    exit(1);
}

/**
 * Mint tag: URIs
 *
 * tag: URIs are unique identifiers according to http://tools.ietf.org/html/rfc4151.
 *
 * We use them for creating URIs for things that can't be HTTP retrieved.
 *
 * @category URI
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
 * @link     http://status.net/
 */

class TagURI
{
    /**
     * Return the base part of a tag URI
     *
     * Note: use mint() instead.
     *
     * @return string Tag URI base to use
     */

    static function base()
    {
        $base = common_config('integration', 'taguri');

        if (empty($base)) {

            $base = common_config('site', 'server').','.date('Y-m-d');

            $pathPart = trim(common_config('site', 'path'), '/');

            if (!empty($pathPart)) {
                $base .= ':'.str_replace('/', ':', $pathPart);
            }
        }

        return $base;
    }

    /**
     * Make a new tag URI
     *
     * Builds the proper base and creates all the parts
     *
     * @return string minted URI
     */

    static function mint()
    {
        $base = self::base();

        $args = func_get_args();

        $format = array_shift($args);

        $extra = vsprintf($format, $args);

        return 'tag:'.$base.':'.$extra;
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Utilities for theme files and paths
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  Paths
 * @package   StatusNet
 * @author    Brion Vibber <brion@status.net>
 * @copyright 2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET') && !defined('LACONICA')) {
    exit(1);
}

/**
 * Encapsulation of the validation-and-save process when dealing with
 * a user-uploaded StatusNet theme archive...
 *
 * @todo extract theme metadata from css/display.css
 * @todo allow saving multiple themes
 */
class ThemeUploader
{
    protected $sourceFile;
    protected $isUpload;
    private $prevErrorReporting;

    public function __construct($filename)
    {
        if (!class_exists('ZipArchive')) {
            // TRANS: Exception thrown when a compressed theme is uploaded while no support present in PHP configuration.
            throw new Exception(_('This server cannot handle theme uploads without ZIP support.'));
        }
        $this->sourceFile = $filename;
    }

    public static function fromUpload($name)
    {
        if (!isset($_FILES[$name]['error'])) {
            // TRANS: Server exception thrown when uploading a theme fails.
            throw new ServerException(_('The theme file is missing or the upload failed.'));
        }
        if ($_FILES[$name]['error'] != UPLOAD_ERR_OK) {
            // TRANS: Server exception thrown when uploading a theme fails.
            throw new ServerException(_('The theme file is missing or the upload failed.'));
        }
        return new ThemeUploader($_FILES[$name]['tmp_name']);
    }

    /**
     * @param string $destDir
     * @throws Exception on bogus files
     */
    public function extract($destDir)
    {
        $zip = $this->openArchive();

        // First pass: validate but don't save anything to disk.
        // Any errors will trip an exception.
        $this->traverseArchive($zip);

        // Second pass: now that we know we're good, actually extract!
        $tmpDir = $destDir . '.tmp' . getmypid();
        $this->traverseArchive($zip, $tmpDir);

        $zip->close();

        if (file_exists($destDir)) {
            $killDir = $tmpDir . '.old';
            $this->quiet();
            $ok = rename($destDir, $killDir);
            $this->loud();
            if (!$ok) {
                common_log(LOG_ERR, "Could not move old custom theme from $destDir to $killDir");
                // TRANS: Server exception thrown when saving an uploaded theme after decompressing it fails.
                throw new ServerException(_('Failed saving theme.'));
            }
        } else {
            $killDir = false;
        }

        $this->quiet();
        $ok = rename($tmpDir, $destDir);
        $this->loud();
        if (!$ok) {
            common_log(LOG_ERR, "Could not move saved theme from $tmpDir to $destDir");
            // TRANS: Server exception thrown when saving an uploaded theme after decompressing it fails.
            throw new ServerException(_('Failed saving theme.'));
        }

        if ($killDir) {
            $this->recursiveRmdir($killDir);
        }
    }

    /**
     *
     */
    protected function traverseArchive($zip, $outdir=false)
    {
        $sizeLimit = 2 * 1024 * 1024; // 2 megabyte space limit?
        $blockSize = 4096; // estimated; any entry probably takes this much space

        $totalSize = 0;
        $hasMain = false;
        $commonBaseDir = false;

        for ($i = 0; $i < $zip->numFiles; $i++) {
            $data = $zip->statIndex($i);
            $name = str_replace('\\', '/', $data['name']);

            if (substr($name, -1) == '/') {
                // A raw directory... skip!
                continue;
            }

            // Is this a safe or skippable file?
            $path = pathinfo($name);
            if ($this->skippable($path['filename'], $path['extension'])) {
                // Documentation and such... booooring
                continue;
            } else {
                $this->validateFile($path['filename'], $path['extension']);
            }

            // Check the directory structure...
            $dirs = explode('/', $path['dirname']);
            $baseDir = array_shift($dirs);
            if ($commonBaseDir === false) {
                $commonBaseDir = $baseDir;
            } else {
                if ($commonBaseDir != $baseDir) {
                    // TRANS: Server exception thrown when an uploaded theme has an incorrect structure.
                    throw new ClientException(_('Invalid theme: Bad directory structure.'));
                }
            }

            foreach ($dirs as $dir) {
                $this->validateFileOrFolder($dir);
            }

            $fullPath = $dirs;
            $fullPath[] = $path['basename'];
            $localFile = implode('/', $fullPath);
            if ($localFile == 'css/display.css') {
                $hasMain = true;
            }

            $size = $data['size'];
            $estSize = $blockSize * max(1, intval(ceil($size / $blockSize)));
            $totalSize += $estSize;
            if ($totalSize > $sizeLimit) {
                // TRANS: Client exception thrown when an uploaded theme is larger than the limit.
                // TRANS: %d is the number of bytes of the uncompressed theme.
                $msg = sprintf(_m('Uploaded theme is too large; must be less than %d byte uncompressed.',
                                  'Uploaded theme is too large; must be less than %d bytes uncompressed.',
                                  $sizeLimit),
                               $sizeLimit);
                throw new ClientException($msg);
            }

            if ($outdir) {
                $this->extractFile($zip, $data['name'], "$outdir/$localFile");
            }
        }

        if (!$hasMain) {
            // TRANS: Server exception thrown when an uploaded theme is incomplete.
            throw new ClientException(_('Invalid theme archive: ' .
                                        "Missing file css/display.css"));
        }
    }

    /**
     * @fixme Probably most unrecognized files should just be skipped...
     */
    protected function skippable($filename, $ext)
    {
        $skip = array('txt', 'html', 'rtf', 'doc', 'docx', 'odt', 'xcf');
        if (strtolower($filename) == 'readme') {
            return true;
        }
        if (in_array(strtolower($ext), $skip)) {
            return true;
        }
        if ($filename == '' || substr($filename, 0, 1) == '.') {
            // Skip Unix-style hidden files
            return true;
        }
        if ($filename == '__MACOSX') {
            // Skip awful metadata files Mac OS X slips in for you.
            // Thanks Apple!
            return true;
        }
        return false;
    }

    protected function validateFile($filename, $ext)
    {
        $this->validateFileOrFolder($filename);
        $this->validateExtension($filename, $ext);
        // @fixme validate content
    }

    protected function validateFileOrFolder($name)
    {
        if (!preg_match('/^[a-z0-9_\.-]+$/i', $name)) {
            common_log(LOG_ERR, "Bad theme filename: $name");
            // TRANS: Server exception thrown when an uploaded theme has an incorrect file or folder name.
            $msg = _("Theme contains invalid file or folder name. " .
                     'Stick with ASCII letters, digits, underscore, and minus sign.');
            throw new ClientException($msg);
        }
        if (preg_match('/\.(php|cgi|asp|aspx|js|vb)\w/i', $name)) {
            common_log(LOG_ERR, "Unsafe theme filename: $name");
            // TRANS: Server exception thrown when an uploaded theme contains files with unsafe file extensions.
            $msg = _('Theme contains unsafe file extension names; may be unsafe.');
            throw new ClientException($msg);
        }
        return true;
    }

    protected function validateExtension($base, $ext)
    {
        $allowed = array('css', // CSS may need validation
                         'png', 'gif', 'jpg', 'jpeg',
                         'svg', // SVG images/fonts may need validation
                         'ttf', 'eot', 'woff');
        if (!in_array(strtolower($ext), $allowed)) {
            if ($ext == 'ini' && $base == 'theme') {
                // theme.ini exception
                return true;
            }
            // TRANS: Server exception thrown when an uploaded theme contains a file type that is not allowed.
            // TRANS: %s is the file type that is not allowed.
            $msg = sprintf(_('Theme contains file of type ".%s", which is not allowed.'),
                           $ext);
            throw new ClientException($msg);
        }
        return true;
    }

    /**
     * @return ZipArchive
     */
    protected function openArchive()
    {
        $zip = new ZipArchive;
        $ok = $zip->open($this->sourceFile);
        if ($ok !== true) {
            common_log(LOG_ERR, "Error opening theme zip archive: " .
                                "{$this->sourceFile} code: {$ok}");
            // TRANS: Server exception thrown when an uploaded compressed theme cannot be opened.
            throw new Exception(_('Error opening theme archive.'));
        }
        return $zip;
    }

    /**
     * @param ZipArchive $zip
     * @param string $from original path inside ZIP archive
     * @param string $to final destination path in filesystem
     */
    protected function extractFile($zip, $from, $to)
    {
        $dir = dirname($to);
        if (!file_exists($dir)) {
            $this->quiet();
            $ok = mkdir($dir, 0755, true);
            $this->loud();
            if (!$ok) {
                common_log(LOG_ERR, "Failed to mkdir $dir while uploading theme");
                // TRANS: Server exception thrown when an uploaded theme cannot be saved during extraction.
                throw new ServerException(_('Failed saving theme.'));
            }
        } else if (!is_dir($dir)) {
            common_log(LOG_ERR, "Output directory $dir not a directory while uploading theme");
            // TRANS: Server exception thrown when an uploaded theme cannot be saved during extraction.
            throw new ServerException(_('Failed saving theme.'));
        }

        // ZipArchive::extractTo would be easier, but won't let us alter
        // the directory structure.
        $in = $zip->getStream($from);
        if (!$in) {
            common_log(LOG_ERR, "Couldn't open archived file $from while uploading theme");
            // TRANS: Server exception thrown when an uploaded theme cannot be saved during extraction.
            throw new ServerException(_('Failed saving theme.'));
        }
        $this->quiet();
        $out = fopen($to, "wb");
        $this->loud();
        if (!$out) {
            common_log(LOG_ERR, "Couldn't open output file $to while uploading theme");
            // TRANS: Server exception thrown when an uploaded theme cannot be saved during extraction.
            throw new ServerException(_('Failed saving theme.'));
        }
        while (!feof($in)) {
            $buffer = fread($in, 65536);
            fwrite($out, $buffer);
        }
        fclose($in);
        fclose($out);
    }

    private function quiet()
    {
        $this->prevErrorReporting = error_reporting();
        error_reporting($this->prevErrorReporting & ~E_WARNING);
    }

    private function loud()
    {
        error_reporting($this->prevErrorReporting);
    }

    private function recursiveRmdir($dir)
    {
        $list = dir($dir);
        while (($file = $list->read()) !== false) {
            if ($file == '.' || $file == '..') {
                continue;
            }
            $full = "$dir/$file";
            if (is_dir($full)) {
                $this->recursiveRmdir($full);
            } else {
                unlink($full);
            }
        }
        $list->close();
        rmdir($dir);
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * URL mapper
 *
 * PHP version 5
 *
 * 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  URL
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    // This check helps protect against security problems;
    // your code file can't be executed directly from the web.
    exit(1);
}

/**
 * URL mapper
 *
 * Converts a path into a set of parameters, and vice versa
 *
 * We used to use Net_URL_Mapper, so there's a wrapper class at Router, q.v.
 *
 * NUM's vagaries are the main reason we have weirdnesses here.
 *
 * @category  URL
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

class URLMapper
{
    const ACTION = 'action';

    protected $statics = [];
    protected $variables = [];
    protected $reverse_dynamics = [];
    protected $reverse_statics = [];
    protected $allpaths = [];

    /**
     * Route creation.
     * $acceptHeaders should be set to true when, for whatever reason,
     * a path is being re-connected. The $headers list is still optional,
     * in this case, given that being empty means "accept everything".
     *
     * @author Evan Prodromou <evan@status.net>
     * @author Bruno Casteleiro <brunoccast@fc.up.pt>
     * @param  string $path route path
     * @param  array $args route action and, if needed, action settings
     * @param  array $paramPatterns regex patterns for path's parameters
     * @param  bool $acceptHeaders whether a path is being re-connected
     * @param  array $headers headers that should be set for route creation
     * @throws Exception If can't connect
     * @return void
     */
    public function connect(string $path, array $args, array $paramPatterns = [], bool $acceptHeaders = false, array $headers = [])
    {
        if (!array_key_exists(self::ACTION, $args)) {
            throw new Exception(sprintf("Can't connect %s; path has no action.", $path));
        }

        $should = true;
        if ($acceptHeaders) {
            // even if it shouldn't be used as a route, we still want
            // to store some information to allow common_local_url
            // to generate urls
            $should = empty($headers) || self::should($headers);
        }

        $this->allpaths[] = $path;

        $action = $args[self::ACTION];

        $paramNames = $this->getParamNames($path);

        if (empty($paramNames)) {
            $this->statics[$path] = $args;
            if (array_key_exists($action, $this->reverse_statics)) {
                $this->reverse_statics[$action][] = [$args, $path];
            } else {
                $this->reverse_statics[$action] = [[$args, $path]];
            }
        } else {
            // fix for the code that still make improper use of this function's params
            foreach ($paramNames as $name) {
                if (!array_key_exists($name, $paramPatterns) &&
                    array_key_exists($name, $args)) {
                    $paramPatterns[$name] = $args[$name];
                    unset($args[$name]);
                }
            }

            // $variables is used for path matching, so we can't store invalid routes
            if ($should) {
                $regex = self::makeRegex($path, $paramPatterns);
                if (isset($this->variables[$regex]) || !$acceptHeaders) {
                    $this->variables[$regex] = [$args, $paramNames];
                } else {
                    // URLs that differ only in the attribute names will generate
                    // different regexes, so in order to avoid the wrong one (oldest)
                    // to be matched first, fresh regexes are stored at the front
                    $this->variables = [$regex => [$args, $paramNames]] + $this->variables;
                }
            }

            $format = $this->makeFormat($path);

            if (array_key_exists($action, $this->reverse_dynamics)) {
                $this->reverse_dynamics[$action][] = [$args, $format, $paramNames];
            } else {
                $this->reverse_dynamics[$action] = [[$args, $format, $paramNames]];
            }
        }
    }

    public function match($path)
    {
        if (array_key_exists($path, $this->statics)) {
            return $this->statics[$path];
        }

        foreach ($this->variables as $regex => $pattern) {
            list($args, $paramNames) = $pattern;
            if (preg_match($regex, $path, $match)) {
                $results = $args;
                foreach ($paramNames as $name) {
                    $results[$name] = $match[$name];
                }
                return $results;
            }
        }

        throw new NoRouteMapException($path);
    }

    public function generate($args, $qstring, $fragment)
    {
        if (!array_key_exists(self::ACTION, $args)) {
            throw new Exception("Every path needs an action.");
        }

        $action = $args[self::ACTION];

        if (!array_key_exists($action, $this->reverse_dynamics) && !array_key_exists($action, $this->reverse_statics)) {
            throw new Exception(sprintf('No candidate paths for action "%s"', $action));
        }

        if (array_key_exists($action, $this->reverse_dynamics)){
            $candidates = $this->reverse_dynamics[$action];
            foreach ($candidates as $candidate) {
                list($tryArgs, $format, $paramNames) = $candidate;

                foreach ($tryArgs as $key => $value) {
                    if (!array_key_exists($key, $args) || $args[$key] != $value) {
                        // next candidate
                        continue 2;
                    }
                }

                // success
                $toFormat = [];

                foreach ($paramNames as $name) {
                    if (!array_key_exists($name, $args)) {
                        // next candidate
                        continue 2;
                    }
                    $toFormat[] = $args[$name];
                }

                $path = vsprintf($format, $toFormat);

                if (!empty($qstring)) {
                    $formatted = http_build_query($qstring);
                    $path .= '?' . $formatted;
                }

                return $path;
            }
        }

        if (array_key_exists($action, $this->reverse_statics)) {
            $candidates = $this->reverse_statics[$action];
            foreach ($candidates as $candidate) {
                list($tryArgs, $tryPath) = $candidate;

                foreach ($tryArgs as $key => $value) {
                    if (!array_key_exists($key, $args) || $args[$key] != $value) {
                        // next candidate
                        continue 2;
                    }
                }

                // success
                $path = $tryPath;

                if (!empty($qstring)) {
                    $formatted = http_build_query($qstring);
                    $path .= '?' . $formatted;
                }

                return $path;
            }
        }

        // failure; some reporting twiddles

        unset($args['action']);

        if (empty($args)) {
            throw new Exception(sprintf('No matches for action "%s"', $action));
        }

        $argstring = '';

        foreach ($args as $key => $value) {
            $argstring .= "$key=$value ";
        }

        throw new Exception(sprintf('No matches for action "%s" with arguments "%s"', $action, $argstring));
    }

    protected function getParamNames($path)
    {
        preg_match_all('/:(?P<name>\w+)/', $path, $match);
        return $match['name'];
    }

    public static function makeRegex($path, $paramPatterns)
    {
        $pr = new PatternReplacer($paramPatterns);

        $regex = preg_replace_callback('/:(\w+)/',
                                       [$pr, 'toPattern'],
                                       $path);

        $regex = '#^' . str_replace('#', '\#', $regex) . '$#';

        return $regex;
    }

    protected function makeFormat($path)
    {
        $format = preg_replace('/(:\w+)/', '%s', $path);

        return $format;
    }

    public function getPaths()
    {
        return array_unique($this->allpaths);
    }

    /**
     * Determines whether the route should or not be overwrited.
     * If ACCEPT header isn't set, false will be returned.
     *
     * @author Diogo Cordeiro <diogo@fc.up.pt>
     * @param array $headers accept-headers that should be set to
     * mark the route for overwrite. This array must be associative
     * and contain the headers in the value-set.
     * @return bool true if should overwrite, false otherwise
     */
    public static function should(array $headers): bool
    {
        if (!isset($_SERVER['HTTP_ACCEPT'])) {
            return false;
        }

        $acceptHeader = new AcceptHeader($_SERVER['HTTP_ACCEPT']);
        foreach ($acceptHeader as $ah) {
            if (isset($headers[$ah['raw']])) {
                return true;
            }
        }

        return false;
    }
}

class PatternReplacer
{
    private $patterns;

    public function __construct($patterns)
    {
        $this->patterns = $patterns;
    }

    public function toPattern($matches)
    {
        // trim out the :
        $name = $matches[1];
        if (array_key_exists($name, $this->patterns)) {
            $pattern = $this->patterns[$name];
        } else {
            // ???
            $pattern = '\w+';
        }
        return '(?P<'.$name.'>'.$pattern.')';
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2010, StatusNet, Inc.
 *
 * UUID generation
 *
 * PHP version 5
 *
 * 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  UUID
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    // This check helps protect against security problems;
    // your code file can't be executed directly from the web.
    exit(1);
}

/**
 * UUID generation
 *
 * @category  General
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

class UUID
{
    const REGEX = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}';
    protected $str = null;

    /**
     * Constructor for a UUID
     *
     * Uses gen() to create a new UUID
     */

    function __construct()
    {
        $this->str = self::gen();
    }

    /**
     * For serializing to a string
     *
     * @return string version of self
     */

    function __toString()
    {
        return $this->str;
    }

    /**
     * For serializing to a string
     *
     * @return string version of self
     */

    function getString()
    {
        return $this->str;
    }

    /**
     * Generate a new UUID
     *
     * @return 36-char v4 (random-ish) UUID
     */

    static function gen()
    {
        return sprintf('%s-%s-%04x-%04x-%s',
                       // 32 bits for "time_low"
                       common_random_hexstr(4),
                       // 16 bits for "time_mid"
                       common_random_hexstr(2),
                       // 16 bits for "time_hi_and_version",
                       // four most significant bits holds version number 4
                       (hexdec(common_random_hexstr(2)) & 0x0fff) | 0x4000,
                       // 16 bits, 8 bits for "clk_seq_hi_res",
                       // 8 bits for "clk_seq_low",
                       // two most significant bits holds zero and one
                       // for variant DCE1.1
                       (hexdec(common_random_hexstr(2)) & 0x3fff) | 0x8000,
                       // 48 bits for "node"
                       common_random_hexstr(6));
    }
}