<?php
/**
 * Phergie (http://phergie.org)
 *
 * @link http://github.com/phergie/phergie-irc-parser for the canonical source repository
 * @copyright Copyright (c) 2008-2014 Phergie Development Team (http://phergie.org)
 * @license http://phergie.org/license Simplified BSD License
 * @package Phergie\Irc
 */

namespace Phergie\Irc;

/**
 * Canonical implementation of ParserInterface.
 *
 * @category Phergie
 * @package Phergie\Irc
 */
class Parser implements ParserInterface
{
    /**
     * Regular expression to match a single IRC message
     *
     * @var string
     */
    protected $message;

    /**
     * Regular expression to parse target information from the first parameter
     * of a single IRC message
     *
     * @var string
     */
    protected $target;

    /**
     * Regular expression to parse channel names
     *
     * @var string
     */
    protected $channel;

    /**
     * Regular expression to parse CTCP commands
     *
     * @var string
     * @link http://irchelp.org/irchelp/rfc/ctcpspec.html
     */
    protected $ctcp;

    /**
     * Mapping of CTCP commands to corresponding regular expressions for
     * parsing parameter values for those commands
     *
     * @var array
     */
    protected $ctcpParams;

    /**
     * Mapping of IRC commands to corresponding regular expressions for
     * parsing parameter values for those commands
     *
     * @var array
     */
    protected $params;

    /**
     * Mapping of numeric replies to their descriptive codes
     *
     * @var array
     */
    protected $replies = array(
        '401' => 'ERR_NOSUCHNICK',
        '402' => 'ERR_NOSUCHSERVER',
        '403' => 'ERR_NOSUCHCHANNEL',
        '404' => 'ERR_CANNOTSENDTOCHAN',
        '405' => 'ERR_TOOMANYCHANNELS',
        '406' => 'ERR_WASNOSUCHNICK',
        '407' => 'ERR_TOOMANYTARGETS',
        '409' => 'ERR_NOORIGIN',
        '411' => 'ERR_NORECIPIENT',
        '412' => 'ERR_NOTEXTTOSEND',
        '413' => 'ERR_NOTOPLEVEL',
        '414' => 'ERR_WILDTOPLEVEL',
        '421' => 'ERR_UNKNOWNCOMMAND',
        '422' => 'ERR_NOMOTD',
        '423' => 'ERR_NOADMININFO',
        '424' => 'ERR_FILEERROR',
        '431' => 'ERR_NONICKNAMEGIVEN',
        '432' => 'ERR_ERRONEUSNICKNAME',
        '433' => 'ERR_NICKNAMEINUSE',
        '436' => 'ERR_NICKCOLLISION',
        '441' => 'ERR_USERNOTINCHANNEL',
        '442' => 'ERR_NOTONCHANNEL',
        '443' => 'ERR_USERONCHANNEL',
        '444' => 'ERR_NOLOGIN',
        '445' => 'ERR_SUMMONDISABLED',
        '446' => 'ERR_USERSDISABLED',
        '451' => 'ERR_NOTREGISTERED',
        '461' => 'ERR_NEEDMOREPARAMS',
        '462' => 'ERR_ALREADYREGISTRED',
        '463' => 'ERR_NOPERMFORHOST',
        '464' => 'ERR_PASSWDMISMATCH',
        '465' => 'ERR_YOUREBANNEDCREEP',
        '467' => 'ERR_KEYSET',
        '471' => 'ERR_CHANNELISFULL',
        '472' => 'ERR_UNKNOWNMODE',
        '473' => 'ERR_INVITEONLYCHAN',
        '474' => 'ERR_BANNEDFROMCHAN',
        '475' => 'ERR_BADCHANNELKEY',
        '481' => 'ERR_NOPRIVILEGES',
        '482' => 'ERR_CHANOPRIVSNEEDED',
        '483' => 'ERR_CANTKILLSERVER',
        '491' => 'ERR_NOOPERHOST',
        '501' => 'ERR_UMODEUNKNOWNFLAG',
        '502' => 'ERR_USERSDONTMATCH',
        '300' => 'RPL_NONE',
        '302' => 'RPL_USERHOST',
        '303' => 'RPL_ISON',
        '301' => 'RPL_AWAY',
        '305' => 'RPL_UNAWAY',
        '306' => 'RPL_NOWAWAY',
        '311' => 'RPL_WHOISUSER',
        '312' => 'RPL_WHOISSERVER',
        '313' => 'RPL_WHOISOPERATOR',
        '317' => 'RPL_WHOISIDLE',
        '318' => 'RPL_ENDOFWHOIS',
        '319' => 'RPL_WHOISCHANNELS',
        '314' => 'RPL_WHOWASUSER',
        '369' => 'RPL_ENDOFWHOWAS',
        '321' => 'RPL_LISTSTART',
        '322' => 'RPL_LIST',
        '323' => 'RPL_LISTEND',
        '324' => 'RPL_CHANNELMODEIS',
        '331' => 'RPL_NOTOPIC',
        '332' => 'RPL_TOPIC',
        '341' => 'RPL_INVITING',
        '342' => 'RPL_SUMMONING',
        '351' => 'RPL_VERSION',
        '352' => 'RPL_WHOREPLY',
        '315' => 'RPL_ENDOFWHO',
        '353' => 'RPL_NAMREPLY',
        '366' => 'RPL_ENDOFNAMES',
        '364' => 'RPL_LINKS',
        '365' => 'RPL_ENDOFLINKS',
        '367' => 'RPL_BANLIST',
        '368' => 'RPL_ENDOFBANLIST',
        '371' => 'RPL_INFO',
        '374' => 'RPL_ENDOFINFO',
        '375' => 'RPL_MOTDSTART',
        '372' => 'RPL_MOTD',
        '376' => 'RPL_ENDOFMOTD',
        '381' => 'RPL_YOUREOPER',
        '382' => 'RPL_REHASHING',
        '391' => 'RPL_TIME',
        '392' => 'RPL_USERSSTART',
        '393' => 'RPL_USERS',
        '394' => 'RPL_ENDOFUSERS',
        '395' => 'RPL_NOUSERS',
        '200' => 'RPL_TRACELINK',
        '201' => 'RPL_TRACECONNECTING',
        '202' => 'RPL_TRACEHANDSHAKE',
        '203' => 'RPL_TRACEUNKNOWN',
        '204' => 'RPL_TRACEOPERATOR',
        '205' => 'RPL_TRACEUSER',
        '206' => 'RPL_TRACESERVER',
        '208' => 'RPL_TRACENEWTYPE',
        '261' => 'RPL_TRACELOG',
        '211' => 'RPL_STATSLINKINFO',
        '212' => 'RPL_STATSCOMMANDS',
        '213' => 'RPL_STATSCLINE',
        '214' => 'RPL_STATSNLINE',
        '215' => 'RPL_STATSILINE',
        '216' => 'RPL_STATSKLINE',
        '218' => 'RPL_STATSYLINE',
        '219' => 'RPL_ENDOFSTATS',
        '241' => 'RPL_STATSLLINE',
        '242' => 'RPL_STATSUPTIME',
        '243' => 'RPL_STATSOLINE',
        '244' => 'RPL_STATSHLINE',
        '221' => 'RPL_UMODEIS',
        '251' => 'RPL_LUSERCLIENT',
        '252' => 'RPL_LUSEROP',
        '253' => 'RPL_LUSERUNKNOWN',
        '254' => 'RPL_LUSERCHANNELS',
        '255' => 'RPL_LUSERME',
        '256' => 'RPL_ADMINME',
        '257' => 'RPL_ADMINLOC1',
        '258' => 'RPL_ADMINLOC2',
        '259' => 'RPL_ADMINEMAIL',
        '005' => 'RPL_ISUPPORT',
    );

    /**
     * Constructs regular expressions used to parse messages.
     */
    public function __construct()
    {
        $crlf = "\r\n";
        $letter = 'a-zA-Z';
        $number = '0-9';
        $special = preg_quote('[]\`_^{|}~');
        $null = '\\x00';
        $command = "(?P<command>[$letter]+|[$number]{3})";
        $middle = "(?: [^ $null$crlf:][^ $null$crlf]*)";
        // ? provides for relaxed parsing of messages without trailing parameters properly demarcated
        $trailing = "(?: :?[^$null$crlf]*)";
        $params = "(?P<params>$trailing?|(?:$middle{0,14}$trailing))";
        $host = '[^ ]+';
        $nick = "(?:[\\*$letter$special][$letter$number$special-]*)";
        $user = "(?:[^ $null$crlf@]+)";
        $prefix = "(?:(?:(?P<nick>$nick)(?:!(?P<user>$user))?(?:@(?P<host>$host))?)|(?P<servername>$host))";
        $message = "(?P<prefix>:$prefix )?$command$params$crlf";
        $this->message = "/^$message\$/SU";

        $chstring = "[^ \a$null,$crlf]+";
        $channel = $this->channel = "(?:[#&]$chstring)";
        $mask = "(?:[#$]$chstring)";
        $to = "(?:$channel|(?:$user@$host)|$nick|$mask)";
        $target = "(?:$to(?:,$to)*)";
        $this->target = "/^$target$/S";

        $this->params = array(
            'PASS'     => "/^(?:(?P<password>$trailing))$/",
            'NICK'     => "/^(?:(?P<nickname>$middle|$trailing)(?P<hopcount>$trailing)?)$/",
            'USER'     => "/^(?:(?P<username>$middle)(?P<hostname>$middle)(?P<servername>$middle)(?P<realname>$trailing))$/",
            'SERVER'   => "/^(?:(?P<servername>$middle)(?P<hopcount>$middle)(?P<info>$trailing))$/",
            'OPER'     => "/^(?:(?P<user>$middle)(?P<password>$trailing))$/",
            'QUIT'     => "/^(?:(?P<message>$trailing)?)$/",
            'SQUIT'    => "/^(?:(?P<server>$middle)(?P<comment>$trailing))$/",
            'JOIN'     => "/^(?:(?P<channels>$middle|$trailing)(?P<keys>$trailing)?)$/",
            'PART'     => "/^(?:(?P<channels>$middle|$trailing)(?P<message>$trailing)?)$/",
            'MODE'     => "/^(?:(?P<target>$middle|$trailing)(?P<mode>$middle|$trailing)?(?P<params>$trailing)?)$/",
            'TOPIC'    => "/^(?:(?P<channel>$middle|$trailing)(?P<topic>$trailing)?)$/",
            'NAMES'    => "/^(?:(?P<channels>$trailing))$/",
            'LIST'     => "/^(?:(?:(?P<channels>$trailing)|$middle)?(?P<server>$trailing)?)$/",
            'INVITE'   => "/^(?:(?P<nickname>$middle)(?P<channel>$trailing))$/",
            'KICK'     => "/^(?:(?P<channel>$middle)(?P<user>$middle|$trailing)(?P<comment>$trailing)?)$/",
            'VERSION'  => "/^(?:(?P<server>$trailing)?)$/",
            'STATS'    => "/^(?:(?P<query>$middle|$trailing)(?P<server>$trailing)?)$/",
            'LINKS'    => "/^(?:(?P<remoteserver>$middle)?(?P<servermask>$trailing)?)$/",
            'TIME'     => "/^(?:(?P<server>$trailing)?)$/",
            'CONNECT'  => "/^(?:(?P<targetserver>$middle|$trailing)(?P<port>$middle|$trailing)?(?P<remoteserver>$trailing)?)$/",
            'TRACE'    => "/^(?:(?P<server>$trailing)?)$/",
            'ADMIN'    => "/^(?:(?P<server>$trailing)?)$/",
            'INFO'     => "/^(?:(?P<server>$trailing)?)$/",
            'PRIVMSG'  => "/^(?:(?P<receivers>$middle)(?P<text>$trailing))$/S",
            'NOTICE'   => "/^(?:(?P<nickname>$middle)(?P<text>$trailing))$/S",
            'WHO'      => "/^(?:(?P<name>$middle|$trailing)(?P<o>$trailing)?)$/",
            'WHOIS'    => "/^(?:(?P<server>$middle)?(?P<nickmasks>$trailing))$/",
            'WHOWAS'   => "/^(?:(?P<nickname>$middle|$trailing)(?P<count>$middle|$trailing)?(?P<server>$trailing)?)$/",
            'KILL'     => "/^(?:(?P<nickname>$middle)(?P<comment>$trailing))$/",
            'PING'     => "/^(?:(?P<server1>$middle|$trailing)(?P<server2>$trailing)?)$/",
            'PONG'     => "/^(?:(?P<daemon>$middle|$trailing)(?P<daemon2>$trailing)?)$/",
            'ERROR'    => "/^(?:(?P<message>$trailing))$/",
            'AWAY'     => "/^(?:(?P<message>$trailing)?)$/",
            'REHASH'   => "/^$/",
            'RESTART'  => "/^$/",
            'SUMMON'   => "/^(?:(?P<user>$middle|$trailing)(?P<server>$trailing)?)$/",
            'USERS'    => "/^(?:(?P<server>$trailing)?)$/",
            'WALLOPS'  => "/^(?:(?P<text>$trailing))$/",
            'USERHOST' => "/^(?:(?P<nickname1>$middle|$trailing)(?P<nickname2>$middle|$trailing)?(?P<nickname3>$middle|$trailing)?(?P<nickname4>$middle|$trailing)?(?P<nickname5>$trailing)?)$/",
            'ISON'     => "/^(?:(?P<nicknames>(?:$middle )*$trailing))$/",
            'PROTOCTL' => "/^(?:()(?P<proto>$trailing))$/",
        );

        $xdelim = "\001";
        $middle = "(?:[^:]+)";
        $trailing = "(?:[^$xdelim]+)";
        $this->ctcp = "/^$xdelim(?P<command>[^ ]+)(?P<params> [^$xdelim]+)?$xdelim$/S";
        $this->ctcpParams = array(
            'FINGER'     => "/^(?::(?P<user>$trailing))?$/",
            'VERSION'    => "/^(?:(?P<name>$middle):(?P<version>$middle):(?P<environment>$trailing))?$/",
            'SOURCE'     => "/^(?:(?P<host>$middle):(?P<directories>$middle):(?P<files>$trailing))?$/",
            'USERINFO'   => "/^(?::(?P<user>$trailing))?$/",
            'CLIENTINFO' => "/^(?::(?P<client>$trailing))?$/",
            'ERRMSG'     => "/^(?:(?P<query>.+)(?: :(?P<message>$trailing))?)$/U",
            'PING'       => "/^(?:(?P<timestamp>$trailing))$/",
            'TIME'       => "/^(?::(?P<time>$trailing))$/",
            'ACTION'       => "/^(?::(?P<action>$trailing))$/",
        );
    }

    /**
     * Strips leading space and colon from a given parameter string.
     *
     * @param string $param
     * @return string
     */
    protected function strip($param)
    {
        return preg_replace('/^ :?/', '', $param);
    }

    /**
     * Removes elements with numeric keys from an array.
     *
     * @param array $array
     * @return array
     */
    protected function removeIntegerKeys(array $array)
    {
        foreach (array_keys($array) as $key) {
            if (is_int($key)) {
                unset($array[$key]);
            }
        }
        return $array;
    }

    /**
     * Implements ParserInterface::parse().
     *
     * @param string $message Data stream containing the message to parse
     * @return array|null Associative array containing parsed data if the
     *         message is successfully parsed, null otherwise
     * @see \Phergie\Irc\ParserInterface::parse()
     */
    public function parse($message)
    {
        // See if we have a full line in our buffer.
        if (($eol = strpos($message, "\r\n")) === false) {
            return null;
        }

        $eol += 2; // strlen("\r\n")
        $buf = (strlen($message) > $eol) ? substr($message, $eol) : '';
        $message = substr($message, 0, $eol);

        // Strip out invalid characters
        $message = preg_replace("/\\0|(?:(?<!\r)\n)|(?:\r(?!\n))/", '', $message);

        // Parse the message, or bail if the parser deems the line to be invalid.
        if (!preg_match($this->message, $message, $parsed)) {
            $parsed = array('invalid' => $message);
            if (strlen($buf)) {
                $parsed['tail'] = $buf;
            }
            return $parsed;
        }

        // Parse out the first full message and prefix if present
        $parsed['message'] = $parsed[0];
        if (isset($parsed['prefix'])) {
            $parsed['prefix'] = rtrim($parsed['prefix']);
        }

        // Parse command parameters
        $command = strtoupper($parsed['command']);
        if (isset($this->params[$command]) && !empty($parsed['params'])) {
            preg_match($this->params[$command], $parsed['params'], $params);
            $params = array_map(array($this, 'strip'), $params);

            // Parse targets if present
            if (isset($params[1]) && preg_match($this->target, $params[1], $targets)) {
                $parsed['targets'] = explode(',', $this->strip($targets[0]));
            }

            switch ($command) {
                // Handle MODE-specific parameters
                case 'MODE':
                    if (preg_match('/^' . $this->channel . '$/', $params['target'])) {
                        $params['channel'] = $params['target'];

                        /* Assign the value of $params['params'] to a named parameter if
                         * only one channel mode is being set.
                         * This functionality is DEPRECATED, and will not occur if more than one
                         * channel mode is being set. Use $params['params'] instead. */
                        if (isset($params['mode']) && strlen($params['mode']) == 2 && isset($params['params'])) {
                            switch ($params['mode']{1}) {
                                case 'l':
                                    $params['limit'] = $params['params'];
                                    break;
                                case 'b':
                                    $params['banmask'] = $params['params'];
                                    break;
                                case 'k':
                                    $params['key'] = $params['params'];
                                    break;
                                default:
                                    $params['user'] = $params['params'];
                                    break;
                            }
                        }
                    } else {
                        $params['user'] = $params['target'];
                    }
                    unset($params['target']);
                    break;
                // Handle CTCP messages
                case 'PRIVMSG':
                case 'NOTICE':
                    if ($params && preg_match($this->ctcp, end($params), $ctcp)) {
                        $parsed['ctcp'] = $this->removeIntegerKeys($ctcp);
                        if (isset($this->ctcpParams[$parsed['ctcp']['command']])
                            && !empty($parsed['ctcp']['params'])) {
                            $ctcpParams = ltrim($parsed['ctcp']['params']);
                            preg_match($this->ctcpParams[$parsed['ctcp']['command']], $ctcpParams, $ctcpParsed);
                            $parsed['ctcp']['params'] = array_merge(
                                $this->removeIntegerKeys($ctcpParsed),
                                array('all' => $ctcpParams)
                            );
                        }
                    }
                    break;
            }

            // Clean up and store the processed parameters
            $params = array_merge(array('all' => ltrim($parsed['params'])), array_filter($params, 'strlen'));
            $params = $this->removeIntegerKeys($params);
            $parsed['params'] = $params;
        } elseif (ctype_digit($command)) {
            if (isset($this->replies[$command])) {
                $parsed['code'] = $this->replies[$command];
            } else {
                $parsed['code'] = $command;
            }
            if (!empty($parsed['params'])) {
                // Slice off the target from the front of the reply
                $temp = explode(' ', ltrim($parsed['params']), 2);
                $parsed['target'] = array_shift($temp);
                if ($parsed['params'] = (!empty($temp)) ? (' ' . array_shift($temp)) : '') {
                    $all = ltrim($parsed['params']);
                    if (strpos($parsed['params'], ' :') !== false) {
                        list($head, $tail) = explode(' :', $parsed['params'], 2);
                    } else {
                        $head = $parsed['params'];
                        $tail = '';
                    }
                    $params = explode(' ', $head);
                    $params[] = $tail;
                    if ($params = array_filter($params, 'strlen')) {
                        $parsed['params'] = array_combine(range(1, count($params)), $params);
                        if (strlen($tail)) {
                            $parsed['params']['iterable'] = array_slice($params, 0, -1);
                            $parsed['params']['tail'] = $tail;
                        } else {
                            $parsed['params']['iterable'] = array_values($params);
                        }
                        $parsed['params']['all'] = $all;
                    }
                }
            }
        }

        // Store the remainder of the original data stream
        $parsed['tail'] = $buf;

        // Clean up and return the response
        $parsed = $this->removeIntegerKeys($parsed);
        $parsed = array_filter($parsed);
        return $parsed;
    }

    /**
     * Implements ParserInterface::parseAll().
     *
     * @param string $message String containing the message to parse
     * @return array Enumerated array of associative arrays each containing
     *         parsed data for a single message if any messages are
     *         successfully parsed, an empty array otherwise
     * @see \Phergie\Irc\ParserInterface::parseAll()
     */
    public function parseAll($message)
    {
        $tail = $message;
        $messages = array();
        do {
            $parsed = $this->parse($tail);
            if ($parsed === null) {
                break;
            }
            $messages[] = $parsed;
            $tail = empty($parsed['tail']) ? null : $parsed['tail'];
        } while ($tail !== null);
        return $messages;
    }

    /**
     * Implements ParserInterface::consume().
     *
     * @param string $message String containing the message to parse
     * @return array|null Associative array containing parsed data if a
     *         message is successfully parsed, null otherwise
     * @see \Phergie\Irc\ParserInterface::consume()
     */
    public function consume(&$message)
    {
        if (($parsed = $this->parse($message)) === null) {
            return null;
        }

        $message = empty($parsed['tail']) ? '' : $parsed['tail'];
        return $parsed;
    }

    /**
     * Implements ParserInterface::consumeAll().
     *
     * @param string $message String containing the message to parse
     * @return array Enumerated array of associative arrays each containing
     *         parsed data for a single message if any messages are
     *         successfully parsed, an empty array otherwise
     * @see \Phergie\Irc\ParserInterface::consumeAll()
     */
    public function consumeAll(&$message)
    {
        $parsed = $this->parseAll($message);
        if ($parsed) {
            $last = end($parsed);
            $message = empty($last['tail']) ? '' : $last['tail'];
        }
        return $parsed;
    }
}


<?php

namespace BootPress\SQLite;

class Fts
{
    /** @var object A BootPress\SQLite\Component instance. */
    private $db;

    /** @var bool Whether or not the SQL scalar function has been created. */
    private $rank;

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

    /**
     * Create an SQLite FTS4 virtual table for fulltext searching.
     *
     * @param string $table    The database table name.
     * @param mixed  $fields   An ``array($field, ...)`` of names to create, or just a string (eg. '**search**') if there is only one field name.
     * @param string $tokenize Either '**simple**', or '**porter**' (the default).
     *
     * @return bool Either ``false`` if the table has already been created, or ``true`` if the table has been created anew.
     *
     * @example
     *
     * ```php
     * if ($db->created) {
     *
     *     $db->fts->create('results', 'search');
     *
     *     // You can insert, update, and query an FTS table the same as any other.
     *     if ($stmt = $db->insert('results', array('docid', 'search'))) {
     *         $db->insert($stmt, array(100, 'Fisherman never die, they just get reel tired.'));
     *         $db->insert($stmt, array(101, 'If wishes were fishes, we\'d have a fish fry.'));
     *         $db->insert($stmt, array(102, 'Women want me, fish fear me.'));
     *         $db->insert($stmt, array(103, 'Good things come to those who bait.'));
     *         $db->insert($stmt, array(104, 'A reel expert can tackle anything.'));
     *     }
     *
     * }
     * ```
     */
    public function create($table, array $fields, $tokenize = 'porter')
    {
        $fields = implode(', ', $fields);
        $query = "CREATE VIRTUAL TABLE {$table} USING fts4({$fields}, tokenize={$tokenize})";
        $executed = $this->db->info('tables', $table);
        if ($query == $executed) {
            return false; // the table has already been created
        }
        if (!is_null($executed)) {
            $this->db->exec('DROP TABLE '.$table);
        }
        $this->db->exec($query);
        $this->db->info('tables', $table, $query); // add or update
        return true; // the table has been created anew
    }

    /**
     * Get the total number of search results.
     *
     * @param string $table  The database table name.
     * @param string $search The search term(s) to '**MATCH**'.
     * @param string $where  An additional string of restrictions you would like to place. If you don't include '**WHERE**' we will add it for you. If you are combining tables to deliver results then put your '**INNER JOIN ... WHERE**' clause here, and prefix the search **$table** and fields with '**s.**' eg. ``INNER JOIN my_table AS my ON s.docid = my.id WHERE my.field = ...``
     *
     * @return int The total count.
     *
     * @example
     *
     * ```php
     * echo $db->fts->count('results', 'fish'); // 2
     * ```
     */
    public function count($table, $search, $where = '')
    {
        if (empty($where)) {
            $where = 'WHERE';
        } else {
            $where = (stripos($where, 'WHERE') === false) ? "WHERE {$where} AND" : "{$where} AND";
        }

        return $this->db->value("SELECT COUNT(*) FROM {$table} AS s {$where} s.{$table} MATCH ?", $search);
    }

    /**
     * Queries an FTS **$table** for the relevant **$search** word(s) found within.
     *
     * @param string $table   The database table name.
     * @param string $search  The search term(s) to '**MATCH**'.
     * @param mixed  $limit   If you are not paginating results and only want the top whatever, then this is an integer. Otherwise it is an SQL ' LIMIT offset, length' clause.
     * @param string $where   An additional string of restrictions you would like to place. If you don't include '**WHERE**' we will add it for you. If you are combining tables to deliver results then put your '**INNER JOIN ... WHERE**' clause here, and prefix the search **$table** and fields with '**s.**' eg. ``INNER JOIN my_table AS my ON s.docid = my.id WHERE my.field = ...``
     * @param array  $fields  An ``array('s.field', ...)`` of additional fields you would like to include in the search results. Remember to specify the table prefixes if needed.
     * @param array  $weights An array of importance that you would like to place on the **$table** fields searched in whatever order you placed them originally. The default weights are 1 for each field, meaning they are all of equal importance. If you want to make one field more relevant than another, then make this an ``array($weight, ...)`` of importance to place on each corresponding **$table** field. Even if you place an importance of 0 on a field it will still be included among the search results, it will just have a lower rank (possibly 0). All of this assumes you have more than one field in your **$table**, otherwise this will make no difference whatsoever.
     *
     * @return array An associative array of results.
     *
     * @example
     *
     * ```php
     * var_export($db->fts->search('results', 'fish'));
     * array(
     *     array(
     *         'docid' => 101,
     *         'snippet' => "If wishes were <b>fishes</b>, we'd have a <b>fish</b> fry.",
     *         'offsets' => '0 0 15 6 0 0 35 4',
     *         'rank' => 1.333,
     *     ),
     *     array(
     *         'docid' => 102,
     *         'snippet' => 'Women want me, <b>fish</b> fear me.',
     *         'offsets' => '0 0 15 4',
     *         'rank' => .666,
     *     ),
     * );
     * ```
     */
    public function search($table, $search, $limit = '', $where = '', array $fields = array(), array $weights = array())
    {
        if (is_null($this->rank)) {
            $this->rank = $this->db->connection()->createFunction('rank', array(&$this, 'rank'), 2);
        }
        if (!empty($where)) {
            $where = (stripos($where, 'WHERE') === false) ? "WHERE {$where} AND" : "{$where} AND";
        } else {
            $where = 'WHERE';
        }
        $fields = (!empty($fields)) ? implode(', ', $fields).',' : '';
        $weights = "'".implode(',', $weights)."'"; // we pass this along to our rank function
        #-- Join, Order, Values --#
        $join = '';
        $order = 'rank';
        $values = array($search);
        if (!empty($limit)) {
            if (is_numeric($limit)) {
                $offset = 0;
                $length = $limit;
            } else {
                $limit = explode(',', preg_replace('/[^0-9,]/', '', $limit));
                $offset = (isset($limit[0])) ? (int) $limit[0] : 0;
                $length = (isset($limit[1])) ? (int) $limit[1] : 10;
            }
            $join = implode("\n", array(
                'JOIN (',
                "  SELECT s.docid, rank(matchinfo(s.{$table}), {$weights}) AS rank",
                "  FROM {$table} AS s {$where} s.{$table} MATCH ?",
                "  ORDER BY rank DESC LIMIT {$length} OFFSET {$offset}",
                ') AS ranktable USING (docid)',
            ));
            $order = 'ranktable.rank';
            $values[] = $search; // add one more to the MATCH
        }
        #-- Query --#
        $results = array();
        if ($stmt = $this->db->query(array(
            "SELECT s.docid, {$fields}",
            "  snippet(s.{$table}, '', '', '...', -1, 50) AS snippet,",
            "  offsets(s.{$table}) AS offsets,",
            "  rank(matchinfo(s.{$table}), {$weights}) AS rank",
            "FROM {$table} AS s {$join} {$where} s.{$table} MATCH ?",
            "ORDER BY {$order} DESC",
        ), $values, 'assoc')) {
            while ($row = $this->db->fetch($stmt)) {
                $results[] = $row;
            }
            $this->db->close($stmt);
        }

        return $results;
    }

    /**
     * Get the words that made your **$search** relevant for **$docid**.
     *
     * @param string $table  The database table name.
     * @param string $search The search term(s) to '**MATCH**'.
     * @param mixed  $docid  The **$table** row's docid.
     *
     * @return array The unique words found which made the **$search** relevant.
     *
     * @example
     *
     * ```php
     * echo implode(', ', $db->fts->words('results', 'fish', 101)); // fishes, fish
     * ```
     */
    public function words($table, $search, $docid)
    {
        $words = array();
        $search = $this->search($table, $search, 1, 's.docid = '.$docid);
        if (empty($search)) {
            return $words;
        }
        $row = array_shift($search);
        $fields = $this->db->row("SELECT * FROM {$table} WHERE docid = ? LIMIT 1", $row['docid'], 'assoc');

        return $this->offset(array_merge($fields, $row), array_keys($fields));
    }

    /**
     * Sorts through the **$row['offsets']** integers, and retrieves the words they reference.
     *
     * @param array $row    An associative array of each **$fields** value, including an '**offsets**' key.
     * @param array $fields An array of field names in the same order as they are found in the database search table.
     *
     * @return array The words that made this row relevant.
     *
     * @example
     *
     * ```php
     * print_r($db->fts->offset(array(
     *     'search' => "If wishes were fishes, we'd have a fish fry.",
     *     'offsets' => '0 0 15 6 0 0 35 4',
     * ), array('search'))); // array('fishes', 'fish');
     * ```
     *
     * @link https://www.sqlite.org/fts3.html#offsets
     */
    public function offset(array $row, array $fields)
    {
        $words = array();
        $search = array();
        foreach ($fields as $value) {
            $search[] = (isset($row[$value])) ? $row[$value] : '';
        }
        $offsets = explode(' ', $row['offsets']);
        $combine = array();
        for ($i = 0; $i < (count($offsets) / 4); ++$i) {
            list($column, $term, $byte, $size) = array_slice($offsets, $i * 4, 4);
            $word = strtolower(substr($search[$column], $byte, $size));
            if ($combine == array($column, $term, $byte)) {
                $word = array_pop($words).' '.$word;
            }
            $words[] = $word;
            $combine = array($column, $term + 1, $byte + $size + 1); // same column, next term, one space away
        }
        $words = array_unique($words);
        rsort($words);

        return $words;
    }

    /**
     * Ranks search results in order of relevance.  Used internally, and only made public because it has to be.
     *
     * @param string $info
     * @param string $weights
     *
     * @return float A relevancy rank.  A larger value indicates a more relevant result.
     *
     * @link https://www.sqlite.org/fts3.html#appendix_a
     */
    public function rank($info, $weights)
    {
        if (!empty($weights)) {
            $weights = explode(',', $weights);
        }
        $score = (float) 0.0; // the value to return
        $isize = 4; // the amount of string we need to collect for each integer
        $phrases = (int) ord(substr($info, 0, $isize));
        $columns = (int) ord(substr($info, $isize, $isize));
        $string = $phrases.' '.$columns.' ';
        for ($p = 0; $p < $phrases; ++$p) {
            $term = substr($info, (2 + $p * $columns * 3) * $isize); // the start of $info for current phrase
            for ($c = 0; $c < $columns; ++$c) {
                $here = (float) ord(substr($term, (3 * $c * $isize), 1)); // total occurrences in this row and column
                $total = (float) ord(substr($term, (3 * $c + 1) * $isize, 1)); // total occurrences for all rows in this column
                $rows = (float) ord(substr($term, (3 * $c + 2) * $isize, 1)); // total rows with at least one occurence in this column
                $relevance = (!empty($total)) ? ($rows / $total) * $here : 0;
                $weight = (isset($weights[$c])) ? (float) $weights[$c] : 1;
                $score += $relevance * $weight;
                $string .= $here.$total.$rows.' ('.round($relevance, 2).'*'.$weight.') ';
            }
        }
        // return $string . '- ' . $score; // to debug
        return $score;
    }
}


<?php

/*
 * This file is part of the slince/upload package.
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Slince\Upload;

use Slince\Upload\Constraint\ConstraintInterface;
use Slince\Upload\Exception\ConstraintException;
use Symfony\Component\HttpFoundation\File\UploadedFile;

final class Validator
{
    /**
     * Array of constraints
     *
     * @var ConstraintInterface[]
     */
    protected $constraints = [];

    /**
     * Add a constraint
     * @param ConstraintInterface $constraint
     */
    public function addConstraint(ConstraintInterface $constraint)
    {
        $this->constraints[] = $constraint;
    }

    /**
     * Validate the file
     *
     * @param UploadedFile $file
     * @return true
     * @throws ConstraintException
     */
    public function validate(UploadedFile $file)
    {
        foreach ($this->constraints as $constraint) {
            if (!$constraint->validate($file)) {
                throw new ConstraintException($constraint, $file);
            }
        }
        return true;
    }
}


<?php

/*
 * This file is part of the slince/upload package.
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Slince\Upload\Filesystem;

use League\Flysystem\FileExistsException;
use League\Flysystem\Filesystem;
use Symfony\Component\HttpFoundation\File\UploadedFile;

class Flysystem implements FilesystemInterface
{
    /**
     * @var Filesystem
     */
    protected $filesystem;

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

    /**
     * {@inheritdoc}
     */
    public function upload($key, UploadedFile $file, $overwrite = false)
    {
        try {
            $this->uploadToFlysystem($key, $file);
        } catch (FileExistsException $exception) {
            if (!$overwrite) {
                throw new \RuntimeException(sprintf('The file with key "%s" is exists.', $key));
            }
            $this->filesystem->delete($key);
            $this->uploadToFlysystem($key, $file);
        }
        @unlink($file->getPathname()); //remove old
        return true;
    }

    /**
     * @param string $key
     * @param UploadedFile $file
     * @throws FileExistsException
     */
    protected function uploadToFlysystem($key, UploadedFile $file)
    {
        if (!$this->filesystem->writeStream($key, fopen($file->getPathname(), 'r'))) {
            throw new \RuntimeException('Failed to upload to flysystem');
        }
    }
}


<?php
/**
 * @package     Search
 * @author      Axel Etcheverry <axel@etcheverry.biz>
 * @copyright   Copyright (c) 2014 Axel Etcheverry (https://twitter.com/euskadi31)
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * @namespace
 */
namespace Search\Engine;

use RuntimeException;
use PDO;
use Search\Engine\SphinxQL\Response;
use Search\SearchEvents;
use Search\Event;

class SphinxQL extends AbstractEngine implements SearchInterface, IndexerInterface
{
    /**
     *
     * @var \PDO
     */
    protected $pdo;

    /**
     * @var string
     */
    protected $host;

    /**
     * @var integer
     */
    protected $port;

    /**
     * @var array
     */
    protected $filters = array();

    /**
     * @var array
     */
    protected $ranges = array();

    /**
     * @var array
     */
    protected $options = array();

    /**
     * @var string
     */
    protected $order_by;

    /**
     * @var string
     */
    protected $order;

    /**
     * @var array
     */
    protected $fields = array();

    /**
     * @var integer
     */
    protected $distance;

    /**
     * @var string
     */
    protected $distance_field = '_distance';

    /**
     * @var integer
     */
    protected $limit;

    /**
     *
     * @param string  $host
     * @param integer $port
     */
    public function __construct($host = "127.0.0.1", $port = 9306)
    {
        $this->host = $host;
        $this->port = $port;
    }

    /**
     * Set PDO object
     *
     * @param PDO $pdo
     */
    public function setPdo(PDO $pdo)
    {
        $this->pdo = $pdo;
        $this->pdo->setAttribute(
            PDO::ATTR_DEFAULT_FETCH_MODE,
            PDO::FETCH_ASSOC
        );

        return $this;
    }

    /**
     * Get PDO object
     *
     * @return PDO
     */
    public function getPdo()
    {
        if (empty($this->pdo)) {
            $this->pdo = new PDO(sprintf("mysql:host=%s;port=%d", $this->host, $this->port));
            $this->pdo->setAttribute(
                PDO::ATTR_DEFAULT_FETCH_MODE,
                PDO::FETCH_ASSOC
            );
        }

        return $this->pdo;
    }

    /**
     * Set the name of distance field
     *
     * @param string $name
     */
    public function setDistanceField($name)
    {
        $this->distance_field = $name;

        return $this;
    }

    /**
     * Get the name of distance field
     *
     * @return string
     */
    public function getDistanceField()
    {
        return $this->distance_field;
    }

    /**
     * Set limit
     *
     * @param integer $limit
     */
    public function setLimit($limit)
    {
        $this->limit = (int) $limit;

        return $this;
    }

    /**
     * Get limit
     *
     * @return integer
     */
    public function getLimit()
    {
        return (int) $this->limit;
    }

    /**
     * Set filter
     *
     * @param string $key
     * @param mixed $value
     */
    public function setFilter($key, $value)
    {
        $this->filters[$key] = (array)$value;

        return $this;
    }

    public function setFilterRange($key, $min, $max)
    {
        $this->ranges[$key] = array(
            'min' => $min,
            'max' => $max
        );

        return $this;
    }

    public function setGeoFilter($key_lat, $key_long, $latitude, $longitude, $distance)
    {
        $this->fields[] = sprintf(
            'GEODIST(%F, %F, %s, %s, {in=degrees, out=meters}) AS %s',
            $latitude,
            $longitude,
            $key_lat,
            $key_long,
            $this->distance_field
        );

        $this->distance = (int)$distance;

        return $this;
    }

    /**
     * Add option
     *
     * @param string $key
     * @param string $value
     */
    public function addOption($key, $value)
    {
        $this->options[$key] = $value;

        return $this;
    }

    /**
     * Set order by
     *
     * @param string $order_by
     */
    public function setOrderBy($order_by, $order = null)
    {
        $this->order_by = $order_by;

        $this->order = $order;

        return $this;
    }

    /**
     *
     * @param  string $type INSERT or REPLACE
     * @param  string $name index name
     * @param  array  $data
     * @return boolean
     */
    protected function _into($type, $name, array $data)
    {
        $sql = sprintf(
            '%s INTO %s (%s) VALUES (:%s)',
            strtoupper($type),
            $name,
            implode(', ', array_keys($data)),
            implode(', :', array_keys($data))
        );

        $parameters = array();

        foreach ($data as $key => $value) {
            $parameters[':' . trim($key, ':')] = $value;
        }

        $query = $this->getPdo()->prepare($sql);

        return $query->execute($parameters);
    }

    /**
     *
     * @param  string $name index name
     * @param  array  $data
     * @return boolean
     */
    public function insert($name, array $data)
    {
        $this->getEventDispatcher()->dispatch(
            SearchEvents::INSERT,
            new Event\InsertEvent($name, $data)
        );
        return $this->_into('INSERT', $name, $data);
    }

    /**
     *
     * @param  string $name index name
     * @param  array  $data
     * @return int
     */
    public function update($name, array $data)
    {
        $this->getEventDispatcher()->dispatch(
            SearchEvents::UPDATE,
            new Event\UpdateEvent($name, $data)
        );
        return $this->_into('REPLACE', $name, $data);
    }

    /**
     *
     * @param  string $name index name
     * @param  integer|array  $id
     * @return boolean
     */
    public function delete($name, $id)
    {
        $this->getEventDispatcher()->dispatch(
            SearchEvents::DELETE,
            new Event\DeleteEvent($name, $id)
        );

        $primary = 'id';

        if (is_array($id)) {
            $primary = key($id);
            $id = current($id);
        }

        $sql = sprintf('DELETE FROM %s WHERE %s = %d', $name, $primary, $id);

        $query = $this->getPdo()->prepare($sql);

        return $query->execute();
    }

    public function getCacheKey($sql, array $parameters)
    {
        foreach ($parameters as $key => $value) {
            $sql = str_replace($key, $value, $sql);
        }

        return hash('sha1', mb_strtolower($sql));
    }

    /**
     *
     * @param  string $sql  the SphinxQL string
     * @param  array  $data
     * @return PDOStatement
     */
    public function fetch($sql, array $data)
    {
        $parameters = array();

        foreach ($data as $key => $value) {
            $parameters[':' . trim($key, ':')] = $value;
        }

        $hash = $this->getCacheKey($sql, $parameters);

        $is_cached = true;

        if (!$response = $this->getCache()->fetch($hash)) {

            $query = $this->getPdo()->prepare($sql);
            $query->execute($parameters);

            $response = $query->fetchAll();

            $this->getCache()->save($hash, $response, $this->getCacheLife());

            $is_cached = false;
        }

        $this->getEventDispatcher()->dispatch(
            SearchEvents::REQUEST,
            new Event\RequestEvent($sql, $is_cached)
        );

        return $response;
    }

    /**
     *
     * @param  string $term
     * @param  string $index
     * @param  array  $fields
     * @return \Search\Engine\SphinxQL\Response
     */
    public function search($term, $index, array $fields = array('*'))
    {
        if (!empty($this->fields)) {
            $fields = array_merge($fields, $this->fields);
        }

        $sql = sprintf(
            'SELECT %s FROM %s WHERE MATCH(:term)',
            implode(', ', $fields),
            implode(', ', (array)$index)
        );

        if (!empty($this->filters)) {
            $parts = array();

            foreach ($this->filters as $key => $value) {
                if (count($value) > 1) {
                    $parts[] = sprintf('%s IN(%s)', $key, implode(', ', $value));
                } else {
                    $parts[] = sprintf('%s = %d', $key, (int)$value[0]);
                }
            }

            $sql .= ' AND ' . implode(' AND ', $parts);
        }

        if (!empty($this->ranges)) {
            $parts = array();

            foreach ($this->ranges as $key => $value) {

                $parts[] = sprintf('(%s BETWEEN %d AND %d)', $key, $value['min'], $value['max']);
            }

            $sql .= ' AND ' . implode(' AND ', $parts);
        }

        if (!is_null($this->distance)) {
            $sql .= sprintf(' AND %s < %d', $this->distance_field, $this->distance);
            $this->order_by = $this->distance_field;
            $this->order = 'ASC';
        }

        if (!empty($this->order_by)) {
            $sql .= ' ORDER BY ' . $this->order_by;

            if (!empty($this->order)) {
                $sql .= ' ' . $this->order;
            }
        }

        if (!empty($this->limit)) {
            $sql .= sprintf(' LIMIT %d', (int) $this->limit);
        }

        if (!empty($this->options)) {
            $parts = array();

            foreach ($this->options as $key => $value) {
                $parts[] = sprintf("%s = %s", $key, $value);
            }

            $sql .= ' OPTION ' . implode(', ', $parts);
        }

        $start = microtime(true);
        $response = $this->fetch($sql, array(
            'term' => $term
        ));
        $end = microtime(true);

        $response = new Response($response);

        $this->getEventDispatcher()->dispatch(
            SearchEvents::RESPONSE,
            new Event\ResponseEvent($index, $term, $response, ($end - $start))
        );

        return $response;
    }
}


<?php

/*
 * This file is part of the slince/upload package.
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Slince\Upload;

use Slince\Upload\Constraint\ConstraintInterface;
use Slince\Upload\Constraint\ExtensionConstraint;
use Slince\Upload\Constraint\MimeTypeConstraint;
use Slince\Upload\Constraint\SizeConstraint;
use Slince\Upload\Filesystem\FilesystemInterface;
use Slince\Upload\Filesystem\Local;
use Slince\Upload\Naming\GenericNamer;
use Slince\Upload\Naming\NamerInterface;
use Slince\Upload\Naming\ClosureNamer;

final class UploadHandlerBuilder
{
    /**
     * @var boolean
     */
    protected $overwrite;

    /**
     * @var NamerInterface
     */
    protected $namer;

    /**
     * @var FilesystemInterface
     */
    protected $filesystem;

    /**
     * @var ConstraintInterface[]
     */
    protected $constraints = [];

    /**
     * Sets overwrite mode.
     *
     * @param boolean $overwrite
     * @return $this
     */
    public function overwrite($overwrite = true)
    {
        $this->overwrite = $overwrite;
        return $this;
    }

    /**
     * Set allowed mime types.
     *
     * @param array|string $mimeTypes
     * @return $this
     */
    public function allowMimeTypes($mimeTypes)
    {
        if (!is_array($mimeTypes)) {
            $mimeTypes = [$mimeTypes];
        }
        $this->constraints[] = new MimeTypeConstraint($mimeTypes);
        return $this;
    }

    /**
     * Set size range
     *
     * @param string|int|null $from
     * @param string|int|null $to
     * @return $this
     */
    public function sizeBetween($from, $to)
    {
        $this->constraints[] = new SizeConstraint($from, $to);
        return $this;
    }

    /**
     * Set allowed extensions
     *
     * @param array|string $extensions
     * @return $this
     */
    public function allowExtensions($extensions)
    {
        if (!is_array($extensions)) {
            $extensions = [$extensions];
        }
        $this->constraints[] = new ExtensionConstraint($extensions);
        return $this;
    }

    /**
     * Sets namer.
     *
     * @param \Closure|NamerInterface $namer
     * @return $this
     */
    public function naming($namer)
    {
        if ($namer instanceof \Closure) {
            $namer = new ClosureNamer($namer);
        }
        $this->namer = $namer;
        return $this;
    }

    /**
     * Set filesystem.
     *
     * @param FilesystemInterface $filesystem
     * @return $this
     */
    public function setFilesystem(FilesystemInterface $filesystem)
    {
        $this->filesystem = $filesystem;
        return $this;
    }

    /**
     * Sets save path
     *
     * @param string $path
     * @return $this
     */
    public function saveTo($path)
    {
        return $this->setFilesystem(new Local($path));
    }

    /**
     * Add a constraint
     *
     * @param ConstraintInterface $constraint
     * @return $this
     */
    public function addConstraint(ConstraintInterface $constraint)
    {
        $this->constraints[] = $constraint;
        return $this;
    }

    /**
     * Make upload handler
     *
     * @return UploadHandler
     */
    public function getHandler()
    {
        if ($this->namer === null) {
            $this->namer = new GenericNamer();
        }
        if ($this->filesystem === null) {
            throw new \LogicException(sprintf('You should set a filesystem for the builder.'));
        }
        $handler = new UploadHandler($this->filesystem, $this->namer, $this->overwrite);
        $validator = $handler->getValidator();
        foreach ($this->constraints as $constraint) {
            $validator->addConstraint($constraint);
        }
        return $handler;
    }
}


<?php

/*
 * This file is part of the slince/upload package.
 *
 * (c) Slince <taosikai@yeah.net>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Slince\Upload\Constraint;

use Symfony\Component\HttpFoundation\File\UploadedFile;

class SizeConstraint implements ConstraintInterface
{
    /**
     * Maximum file size
     * @var int
     */
    protected $maxSize;

    /**
     * Minimum file size
     * @var int
     */
    protected $minSize;

    /**
     * Limit file size (use "B", "K", M", or "G")
     * @param int|string|null $minSize
     * @param int|string|null $maxSize
     */
    public function __construct($minSize = null, $maxSize = null)
    {
        if ($minSize !== null) {
            $this->minSize = static::humanReadableToBytes($minSize);
        }
        if ($maxSize !== null) {
            $this->maxSize = static::humanReadableToBytes($maxSize);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function validate(UploadedFile $file)
    {
        $size = method_exists($file, 'getClientSize') ?
            $file->getClientSize() : $file->getSize();

        return ($this->maxSize === null || $size <= $this->maxSize)
            && ($this->minSize === null || $size >= $this->minSize);
    }

    /**
     * {@inheritdoc}
     */
    public function getErrorMessage(UploadedFile $file)
    {
        $message = null;
        if ($this->minSize && $this->maxSize) {
            $message = sprintf('File size should between %s and %s', $this->minSize, $this->maxSize);
        } elseif ($this->minSize === null) {
            $message = sprintf('File size should less than %s', $this->maxSize);
        } elseif ($this->maxSize === null) {
            $message = sprintf('File size should greater than %s', $this->minSize);
        }
        return $message;
    }

    /**
     * Convert human readable file size (e.g. "10K" or "3M") into bytes
     * @link https://github.com/brandonsavage/Upload/blob/master/src/Upload/File.php#L446
     * @param  string $input
     * @return int
     */
    public static function humanReadableToBytes($input)
    {
        $number = (int)$input;
        $units = array(
            'b' => 1,
            'k' => 1024,
            'm' => 1048576,
            'g' => 1073741824
        );
        $unit = strtolower(substr($input, -1));
        if (isset($units[$unit])) {
            $number = $number * $units[$unit];
        }
        return $number;
    }
}


<?php

namespace ParameterParser;

use Closure;
use Bramus\Ansi\ControlSequences\EscapeSequences\Enums\SGR;

class ParameterCluster
{
    /**
     * The prefixes and the closures associated therewith.
     *
     * @var array
     */
    public $prefixes = [];

    /**
     * The default closure to fall back on when no prefix is found
     * for the argument being parsed.
     *
     * @var Closure
     */
    public $default = null;

    /**
     * Construct the Parameter Cluster and assign a default value to the
     * fall back closure.
     */
    public function __construct()
    {
        $this->default = function ($parameter) {
            return -1;
        };
    }

    /**
     * Add a parameter closure.
     *
     * @param ParameterClosure $closure
     *
     * @return ParameterCluster
     */
    public function add(ParameterClosure $closure)
    {
        $this->prefixes[$closure->prefix][$closure->parameterName] = $closure;

        return $this;
    }

    /**
     * Remove a parameter closure based on prefix and parameter name.
     *
     * @param  string $prefix
     * @param  string $parameterName
     *
     * @return ParameterCluster
     */
    public function remove($prefix, $parameterName)
    {
        unset($this->prefixes[$prefix][$parameterName]);

        return $this;
    }

    /**
     * Add an array of parameters.
     *
     * @param string $prefix
     * @param array  $parameters
     *
     * @return ParameterCluster
     */
    public function addMany($parameters)
    {
        foreach ($parameters as $parameter) {
            $this->prefixes[$parameter->prefix][
                $parameter->parameterName
            ] = $parameter;
        }

        return $this;
    }

    /**
     * Set the default fall back closure.
     *
     * @param Closure $closure
     *
     * @return ParameterCluster
     */
    public function setDefault(Closure $closure)
    {
        $this->default = $closure;

        return $this;
    }

    /**
     * Retrieves the full usage of the ParameterCluster as a string.
     *
     * @param string $showRequiredFirst
     * @param string $customBinary
     * @param string $customScript
     *
     * @return string
     */
    public function getUsage(
        $showRequiredFirst = true,
        $customBinary = null,
        $customScript = null
    ) {
        $fullUsage = '';

        if ($customBinary == null) {
            $fullUsage = 'php ';
        } else {
            $fullUsage = $customBinary.' ';
        }

        if ($customScript == null) {
            $fullUsage .= basename($_SERVER['SCRIPT_NAME']).' ';
        } else {
            $fullUsage .= $customScript.' ';
        }

        foreach ($this->prefixes as $prefix => $parameters) {
            if ($showRequiredFirst) {
                usort($parameters, function ($p1, $p2) {
                    if ($p1->required && $p2->required) {
                        return 0;
                    }

                    if ($p1->required && ! $p2->required) {
                        return -1;
                    }

                    if ($p2->required && ! $p1->required) {
                        return 1;
                    }
                });
            }

            foreach ($parameters as $parameter) {
                if ($parameter->parent == null) {
                    $fullUsage .= $parameter->getUsage().' ';
                }
            }
        }

        return $fullUsage;
    }

    /**
     * Print the full usage along with each commands
     * individual usage and descripition.
     *
     * @param string $applicationName
     * @param string $description
     * @param string $applicationVersion
     * @param bool   $showRequiredFirst
     * @param string $customBinary
     * @param string $customScript
     * @param int    $columnPadding
     * @param array  $fullUsageStyle
     */
    public function printFullUsage(
        $applicationName,
        $description = null,
        $applicationVersion = null,
        $showRequiredFirst = true,
        $customBinary = null,
        $customScript = null,
        $columnPadding = 5,
        $usageStyle = null
    ) {
        // Create Ansi Instance
        $ansi = new \Bramus\Ansi\Ansi();

        $ansi->color([SGR::COLOR_FG_BLUE_BRIGHT])
             ->bold()
             ->text(PHP_EOL.$applicationName)
             ->noStyle();

        $ansi->text(
            ($applicationVersion !== null ? ' '.$applicationVersion : '').PHP_EOL
        );
        echo PHP_EOL;

        if ($description != null) {
            $ansi->color([SGR::COLOR_FG_BLUE_BRIGHT])
                 ->bold()
                 ->text('Description:')
                 ->noStyle();
            echo PHP_EOL.PHP_EOL."\t".$description.PHP_EOL.PHP_EOL;
        }

        $ansi->color([SGR::COLOR_FG_BLUE_BRIGHT])
                 ->bold()
                 ->text('Usage:')
                 ->noStyle();
        echo PHP_EOL.PHP_EOL."\t".
             $this->getUsage(
                 $showRequiredFirst,
                 $customBinary,
                 $customScript
             ).PHP_EOL;
        echo PHP_EOL;

        $parameterCount = 0;
        $values = $usageStyle == null ? FullUsageStyle::all() : $usageStyle;

        foreach ($this->prefixes as $prefix => $parameters) {
            foreach ($parameters as $parameter) {
                if ($parameter->parent === null) {
                    $parameterCount += 1;
                    foreach ($values as $mappedValueName => $mappedValue) {
                        $nVal = $mappedValue['fetch']($parameter);
                        $nValSize = strlen($nVal);
                        if (
                            $nValSize +
                            $columnPadding
                            > $values[$mappedValueName]['longest']
                        ) {
                            $values[$mappedValueName]['longest'] =
                                $nValSize +
                                $columnPadding;
                        }

                        $values[$mappedValueName]['values'][] = $nVal;
                    }
                }
            }
        }

        $ansi->color([SGR::COLOR_FG_BLUE_BRIGHT])
                 ->bold()
                 ->text('Parameters:')
                 ->noStyle();
        echo PHP_EOL.PHP_EOL;

        $headerFormat = "\t";
        $columnNames = [];

        $parameterFormat = '';
        $parameterValues = [];

        foreach ($values as $mappedValueName => $mappedValue) {
            $headerFormat .= '%-'.$mappedValue['longest'].'s ';
            $columnNames[] = ucwords($mappedValueName);
        }

        for ($i = 0; $i < $parameterCount; $i++) {
            $newFormat = "\t";
            foreach ($values as $mappedValueName => $mappedValue) {
                $newFormat .= '%-'.$mappedValue['longest'].'s ';
                $parameterValues[] = $mappedValue['values'][$i];
            }
            $newFormat .= PHP_EOL;
            $parameterFormat .= $newFormat;
        }

        vprintf($headerFormat.PHP_EOL, $columnNames);
        vprintf($parameterFormat.PHP_EOL, $parameterValues);
    }
}


<?php
/**
 * This file is part of the Autarky package.
 *
 * (c) Andreas Lutro <anlutro@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Autarky\Errors;

use Autarky\Container\ClassResolverInterface;

/**
 * Class for resolving error handlers.
 *
 * @internal
 */
class HandlerResolver
{
	/**
	 * @var ClassResolverInterface
	 */
	protected $container;

	/**
	 * @param ClassResolverInterface $container
	 */
	public function __construct(ClassResolverInterface $container)
	{
		$this->container = $container;
	}

	/**
	 * Resolve a handler.
	 *
	 * @param  mixed $handler
	 *
	 * @return ErrorHandlerInterface|callable
	 */
	public function resolve($handler)
	{
		if (!is_string($handler)) {
			return $handler;
		}

		$handler = $this->container->resolve($handler);

		if (!is_callable($handler) && !$handler instanceof ErrorHandlerInterface) {
			$type = is_object($handler) ? get_class($handler) : gettype($handler);
			throw new \UnexpectedValueException("Resolved error handler is not a valid handler - must be callable or an instance of Autarky\Errors\ErrorHandlerInterface, $type given");
		}

		return $handler;
	}
}


<?php
/**
 * This file is part of the Autarky package.
 *
 * (c) Andreas Lutro <anlutro@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Autarky\Errors;

use Exception;
use ErrorException;
use ReflectionFunction;
use ReflectionMethod;
use SplDoublyLinkedList;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;

/**
 * Manager that can handle exceptions as well as keep track of multiple other
 * exception handlers.
 */
class ErrorHandlerManager implements ErrorHandlerManagerInterface
{
	/**
	 * @var \Autarky\Errors\HandlerResolver
	 */
	protected $resolver;

	/**
	 * @var \SplDoublyLinkedList
	 */
	protected $handlers;

	/**
	 * @var ErrorHandlerInterface|null
	 */
	protected $defaultHandler;

	/**
	 * Re-throw exceptions rather than handling them.
	 *
	 * @var boolean
	 */
	protected $rethrow = false;

	/**
	 * @param HandlerResolver           $resolver
	 */
	public function __construct(HandlerResolver $resolver)
	{
		$this->resolver = $resolver;
		$this->handlers = new SplDoublyLinkedList;
	}

	/**
	 * {@inheritdoc}
	 */
	public function setRethrow($rethrow)
	{
		if ($rethrow !== null) {
			$this->rethrow = (bool) $rethrow;
		} else if (PHP_SAPI === 'cli') {
			$this->rethrow = true;
		} else {
			$this->rethrow = false;
		}
	}

	/**
	 * {@inheritdoc}
	 */
	public function appendHandler($handler)
	{
		$this->checkHandler($handler);
		$this->handlers->push($handler);
	}

	/**
	 * {@inheritdoc}
	 */
	public function prependHandler($handler)
	{
		$this->checkHandler($handler);
		$this->handlers->unshift($handler);
	}

	protected function checkHandler($handler)
	{
		if (
			!$handler instanceof ErrorHandlerInterface
			&& !is_callable($handler)
			&& !is_string($handler)
		) {
			$type = is_object($handler) ? get_class($handler) : gettype($handler);
			throw new \InvalidArgumentException("Error handler must be callable, string or instance of Autarky\Errors\ErrorHandlerInterface, $type given");
		}
	}

	/**
	 * {@inheritdoc}
	 */
	public function setDefaultHandler(ErrorHandlerInterface $handler)
	{
		$this->defaultHandler = $handler;
	}

	/**
	 * {@inheritdoc}
	 */
	public function register()
	{
		set_error_handler([$this, 'handleError']);

		if (!$this->rethrow) {
			set_exception_handler([$this, 'handleUncaught']);
			register_shutdown_function([$this, 'handleShutdown']);
		} else {
			register_shutdown_function([$this, 'throwFatalErrorException']);
		}
	}

	/**
	 * {@inheritdoc}
	 */
	public function handle(Exception $exception)
	{
		if ($this->rethrow) throw $exception;

		foreach ($this->handlers as $index => $handler) {
			try {
				if (is_string($handler)) {
					$handler = $this->resolver->resolve($handler);
					$this->handlers->offsetSet($index, $handler);
				} else if (is_array($handler) && is_string($handler[0])) {
					$handler[0] = $this->resolver->resolve($handler[0]);
					$this->handlers->offsetSet($index, $handler);
				}

				if (!$this->matchesTypehint($handler, $exception)) {
					continue;
				}

				$result = $this->callHandler($handler, $exception);

				if ($result !== null) {
					return $this->makeResponse($result, $exception);
				}
			} catch (Exception $newException) {
				return $this->handle($newException);
			}
		}

		return $this->makeResponse($this->defaultHandler->handle($exception), $exception);
	}

	/**
	 * Transform an exception handler's response into a Response object.
	 *
	 * @param  mixed      $response
	 * @param  \Exception $exception
	 *
	 * @return \Symfony\Component\HttpFoundation\Response
	 */
	protected function makeResponse($response, Exception $exception)
	{
		if (!$response instanceof Response) {
			$response = new Response($response);
		}

		if (!$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) {
			if ($exception instanceof HttpExceptionInterface) {
				$response->setStatusCode($exception->getStatusCode());
				$response->headers->add($exception->getHeaders());
			} else {
				$response->setStatusCode(500);
			}
		}

		return $response;
	}

	/**
	 * Check if a handler's argument typehint matches an exception.
	 *
	 * @param  callable|ErrorHandlerInterface $handler
	 * @param  \Exception                     $exception
	 *
	 * @return bool
	 */
	protected function matchesTypehint($handler, Exception $exception)
	{
		if ($handler instanceof ErrorHandlerInterface) {
			return true;
		}

		if (is_array($handler)) {
			$reflection = (new ReflectionMethod($handler[0], $handler[1]));
		} else {
			$reflection = (new ReflectionFunction($handler));
		}

		$params = $reflection->getParameters();

		// if the handler takes no parameters it is considered global and should
		// handle every exception
		if (empty($params)) {
			return true;
		}

		$handlerHint = $params[0]
			->getClass();

		// likewise, if the first handler parameter has no typehint, consider it
		// a global handler that handles everything
		if (!$handlerHint) {
			return true;
		}

		return $handlerHint->isInstance($exception);
	}

	/**
	 * Call an exception handler.
	 *
	 * @param  mixed     $handler
	 * @param  Exception $exception
	 *
	 * @return mixed
	 */
	protected function callHandler($handler, Exception $exception)
	{
		if ($handler instanceof ErrorHandlerInterface) {
			return $handler->handle($exception);
		}

		return call_user_func($handler, $exception);
	}

	/**
	 * Handle an uncaught exception. Does the same as handle(), but also sends
	 * the response, as we can assume that the exception happened outside of
	 * HttpKernelInterface::handle.
	 *
	 * @param  \Exception $exception
	 *
	 * @return Response
	 *
	 * @throws Exception  If PHP_SAPI is 'cli'
	 */
	public function handleUncaught(Exception $exception)
	{
		if (PHP_SAPI === 'cli') {
			throw $exception;
		}

		return $this->handle($exception)
			->send();
	}

	/**
	 * Handle a PHP error.
	 *
	 * @param  int     $level
	 * @param  string  $message
	 * @param  string  $file
	 * @param  int     $line
	 * @param  array   $context
	 *
	 * @throws \ErrorException if the error level matches PHP's error reporting.
	 */
	public function handleError($level, $message, $file = '', $line = 0, $context = array())
	{
		if (error_reporting() & $level) {
			throw new ErrorException($message, 0, $level, $file, $line);
		}
	}

	/**
	 * Handle a PHP fatal error.
	 *
	 * @return void
	 */
	public function handleShutdown()
	{
		$exception = $this->makeFatalErrorException();

		if ($exception) {
			$this->handleUncaught($exception);
		}
	}

	/**
	 * Throw a FatalErrorException if an error has occured.
	 *
	 * @return void
	 *
	 * @throws \Symfony\Component\Debug\Exception\FatalErrorException
	 */
	public function throwFatalErrorException()
	{
		$exception = $this->makeFatalErrorException();

		if ($exception) throw $exception;
	}

	/**
	 * Create a FatalErrorException out of the information stored on the last
	 * PHP error.
	 *
	 * @return \Symfony\Component\Debug\Exception\FatalErrorException|null
	 */
	protected function makeFatalErrorException()
	{
		$error = error_get_last();

		if ($error !== null) {
			return new FatalErrorException($error['message'],
				$error['type'], 0, $error['file'], $error['line']);
		}

		return null;
	}
}


<?php
/**
 * This file is part of the Autarky package.
 *
 * (c) Andreas Lutro <anlutro@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Autarky\Console;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;

class RouteListCommand extends Command
{
	/**
	 * {@inheritdoc}
	 */
	public function configure()
	{
		$this->setName('route:list')
			->setDescription('Show a list of routes')
			->setHelp(<<<'EOS'
Shows a list of all routes registered, their name, controller, path and hooks.
EOS
);
	}

	/**
	 * {@inheritdoc}
	 */
	protected function execute(InputInterface $input, OutputInterface $output)
	{
		$table = new Table($output);
		$table->setHeaders(['Name', 'Methods', 'Path', 'Controller', 'Hooks']);

		$router = $this->app->getContainer()
			->resolve('Autarky\Routing\Router');

		/** @var \Autarky\Routing\Route $route */
		foreach ($router->getRoutes() as $route) {
			$methods = implode('|', $route->getMethods());
			$controller = $route->getController();

			if (is_array($controller)) {
				$controller = implode('::', $controller);
			} else if ($controller instanceof \Closure) {
				$controller = 'Closure';
			}

			$hooks = '';
			if ($before = $route->getBeforeHooks()) {
				$hooks .= 'Before: ' . implode(', ', $before);
			}
			if ($after = $route->getAfterHooks()) {
				if ($hooks !== '') {
					$hooks .= ' - ';
				}
				$hooks .= 'After: ' . implode(', ', $after);
			}

			$table->addRow([$route->getName(), $methods, $route->getPattern(), $controller, $hooks]);
		}

		$table->render();

		return 0;
	}
}


<?php
/**
 * This file is part of the Autarky package.
 *
 * (c) Andreas Lutro <anlutro@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Autarky\Routing;

/**
 * Class that represents a single route in the application.
 */
class Route
{
	/**
	 * @var \Autarky\Routing\Router
	 */
	protected static $router;

	/**
	 * @var array
	 */
	protected $methods;

	/**
	 * @var string
	 */
	protected $pattern;

	/**
	 * @var callable
	 */
	protected $controller;

	/**
	 * @var array
	 */
	protected $options;

	/**
	 * @var null|string
	 */
	protected $name;

	/**
	 * @var array|null
	 */
	protected $params = null;

	/**
	 * @param array    $methods    HTTP methods allowed for this route
	 * @param string   $pattern
	 * @param callable $controller
	 * @param string   $name
	 * @param array    $options
	 */
	public function __construct(array $methods, $pattern, $controller, $name = null, array $options = [])
	{
		$this->methods = array_map('strtoupper', $methods);
		$this->pattern = $pattern;
		$this->name = $name;
		$this->controller = $controller;
		$this->options = $options;
	}

	/**
	 * Get the methods the route responds to.
	 *
	 * @return string[]
	 */
	public function getMethods()
	{
		return $this->methods;
	}

	/**
	 * Get the URI pattern the route should match against.
	 *
	 * @return string
	 */
	public function getPattern()
	{
		return $this->pattern;
	}

	/**
	 * Get the callable controller for the route.
	 *
	 * @return callable
	 */
	public function getController()
	{
		return $this->controller;
	}

	/**
	 * Get the route's name.
	 *
	 * @return string|null
	 */
	public function getName()
	{
		return $this->name;
	}

	/**
	 * Get the route options.
	 *
	 * @return array
	 */
	public function getOptions()
	{
		return $this->options;
	}

	/**
	 * Get a route option.
	 *
	 * @param  $option string
	 *
	 * @return mixed  Returns null if option not set.
	 */
	public function getOption($option)
	{
		return isset($this->options[$option]) ? $this->options[$option] : null;
	}

	/**
	 * Add a before hook.
	 *
	 * @param string $hook
	 */
	public function addBeforeHook($hook)
	{
		if (!isset($this->options['before'])) {
			$this->options['before'] = [];
		}
		$this->options['before'][] = $hook;
	}

	/**
	 * Add an after hook.
	 *
	 * @param string $hook
	 */
	public function addAfterHook($hook)
	{
		if (!isset($this->options['after'])) {
			$this->options['after'] = [];
		}
		$this->options['after'][] = $hook;
	}

	/**
	 * Add a before or after hook.
	 *
	 * @param string  $when   "before" or "after"
	 * @param string  $hook
	 */
	public function addHook($when, $hook)
	{
		$this->{'add'.ucfirst($when).'Hook'}($hook);
	}

	/**
	 * Get the route's before hooks.
	 *
	 * @return string[]
	 */
	public function getBeforeHooks()
	{
		return isset($this->options['before']) ? $this->options['before'] : [];
	}

	/**
	 * Get the route's after hooks.
	 *
	 * @return string[]
	 */
	public function getAfterHooks()
	{
		return isset($this->options['after']) ? $this->options['after'] : [];
	}

	/**
	 * When a match against the route has been confirmed, extract the parameters
	 * from the URI and pass them as an associative array to this method.
	 *
	 * @param array $params
	 */
	public function setParams(array $params)
	{
		$this->params = $params;
	}

	/**
	 * Get the parameters. Can only be called on a route that has been matched
	 * against an URI (i.e. setParams has been called)
	 *
	 * @return array
	 *
	 * @throws \BadMethodCallException If route has not been matched yet
	 */
	public function getParams()
	{
		if ($this->params === null) {
			throw new \BadMethodCallException("Cannot get params from a route that has not been matched yet");
		}

		return $this->params;
	}

	/**
	 * Set the router the route objects use.
	 *
	 * Somewhat of a hack to make var_export caching work.
	 *
	 * @param \Autarky\Routing\RouterInterface $router
	 */
	public static function setRouter(RouterInterface $router)
	{
		static::$router = $router;
	}

	/**
	 * Re-build a Route object from data that has been var_export-ed.
	 *
	 * @param  array $data
	 *
	 * @return static
	 */
	public static function __set_state($data)
	{
		$route = new static($data['methods'], $data['pattern'],
			$data['controller'], $data['name'], $data['options']);
		if (static::$router !== null) {
			static::$router->addCachedRoute($route);
		}
		return $route;
	}
}


<?php
/**
 * This file is part of the Autarky package.
 *
 * (c) Andreas Lutro <anlutro@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Autarky;

use Closure;
use SplPriorityQueue;
use SplDoublyLinkedList;
use Stack\Builder as StackBuilder;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\HttpKernelInterface;

use Autarky\Config\ConfigInterface;
use Autarky\Console\Application as ConsoleApplication;
use Autarky\Container\ContainerInterface;
use Autarky\Errors\ErrorHandlerManagerInterface;
use Autarky\Http\Kernel;
use Autarky\Providers\ProviderInterface;
use Autarky\Providers\DependantProviderInterface;
use Autarky\Providers\ConsoleProviderInterface;
use Autarky\Providers\AbstractProvider;

/**
 * The main application of the framework.
 */
class Application implements HttpKernelInterface
{
	/**
	 * The framework version.
	 */
	const VERSION = '0.10.1';

	/**
	 * The application's service providers.
	 *
	 * @var array
	 */
	protected $providers = [];

	/**
	 * @var \SplPriorityQueue
	 */
	protected $middlewares;

	/**
	 * @var Kernel
	 */
	protected $kernel;

	/**
	 * @var \Stack\Builder
	 */
	protected $stack;

	/**
	 * @var \Autarky\Config\ConfigInterface
	 */
	protected $config;

	/**
	 * @var \Autarky\Container\ContainerInterface
	 */
	protected $container;

	/**
	 * @var \Autarky\Errors\ErrorHandlerManagerInterface
	 */
	protected $errorHandler;

	/**
	 * @var ConsoleApplication
	 */
	protected $console;

	/**
	 * @var \Closure|string
	 */
	protected $environment;

	/**
	 * @var boolean
	 */
	protected $booting = false;

	/**
	 * @var boolean
	 */
	protected $booted = false;

	/**
	 * @var \SplDoublyLinkedList
	 */
	protected $configurators;

	/**
	 * @var \Symfony\Component\HttpFoundation\RequestStack
	 */
	protected $requests;

	/**
	 * Construct a new application instance.
	 *
	 * @param \Closure|string   $environment
	 * @param ProviderInterface[] $providers
	 */
	public function __construct($environment, array $providers)
	{
		$this->middlewares = new SplPriorityQueue;
		$this->configurators = new SplDoublyLinkedList;
		$this->requests = new RequestStack;
		$this->setEnvironment($environment);

		foreach ($providers as $provider) {
			$class = is_object($provider) ? get_class($provider) : (string) $provider;
			$this->providers[$class] = $provider;
		}
	}

	/**
	 * Push a configurator on top of the stack. The configurators will be
	 * executed when the application is booted. If the application is already
	 * booted, the configurator will be executed at once.
	 *
	 * @param  callable|string|ConfiguratorInterface $configurator
	 *
	 * @return void
	 */
	public function config($configurator)
	{
		if ($this->booted) {
			$this->invokeConfigurator($configurator);
		} else {
			$this->configurators->push($configurator);
		}
	}

	/**
	 * Invoke a single configurator.
	 *
	 * @param  callable|string|ConfiguratorInterface $configurator
	 *
	 * @return void
	 */
	protected function invokeConfigurator($configurator)
	{
		if (is_callable($configurator)) {
			call_user_func($configurator, $this);
			return;
		}

		if (is_string($configurator)) {
			$configurator = $this->container->resolve($configurator);
		}

		if ($configurator instanceof ConfiguratorInterface) {
			$configurator->configure();
		} else {
			throw new \UnexpectedValueException('Invalid configurator');
		}
	}

	/**
	 * Set the environment of the application. Has to be called before boot().
	 *
	 * @param string $environment
	 */
	public function setEnvironment($environment)
	{
		if ($this->booting) {
			throw new \RuntimeException('Cannot set environment after application has booted');
		}

		if ($environment instanceof Closure) {
			$environment = call_user_func($environment);
		}

		if (!is_string($environment)) {
			throw new \InvalidArgumentException('Environment must be a string');
		}

		$this->environment = $environment;
	}

	/**
	 * Get the current environment.
	 *
	 * @return string
	 */
	public function getEnvironment()
	{
		if (!$this->booting) {
			throw new \RuntimeException('Environment has not yet been resolved');
		}

		return $this->environment;
	}

	/**
	 * Get the application's providers.
	 *
	 * @return string[] array of provider class names
	 */
	public function getProviders()
	{
		return array_keys($this->providers);
	}

	/**
	 * Set the application's error handler.
	 *
	 * @param ErrorHandlerManagerInterface $errorHandler
	 */
	public function setErrorHandler(ErrorHandlerManagerInterface $errorHandler)
	{
		$this->errorHandler = $errorHandler;
	}

	/**
	 * Get the application's error handler.
	 *
	 * @return ErrorHandlerManagerInterface
	 */
	public function getErrorHandler()
	{
		return $this->errorHandler;
	}

	/**
	 * Set the application's container.
	 *
	 * @param \Autarky\Container\ContainerInterface $container
	 */
	public function setContainer(ContainerInterface $container)
	{
		$this->container = $container;
		$container->instance('Autarky\Application', $this);
		$container->instance('Symfony\Component\HttpFoundation\RequestStack', $this->requests);
	}

	/**
	 * Get the application's container.
	 *
	 * @return \Autarky\Container\Container|ContainerInterface
	 */
	public function getContainer()
	{
		return $this->container;
	}

	/**
	 * Set the application's config store.
	 *
	 * @param \Autarky\Config\ConfigInterface $config
	 */
	public function setConfig(ConfigInterface $config)
	{
		$this->config = $config;
	}

	/**
	 * Get the application's config store.
	 *
	 * @return \Autarky\Config\ConfigInterface
	 */
	public function getConfig()
	{
		return $this->config;
	}

	/**
	 * Get the application's router.
	 *
	 * @return \Autarky\Routing\RouterInterface
	 */
	public function getRouter()
	{
		return $this->container->resolve('Autarky\Routing\RouterInterface');
	}

	/**
	 * Get the application's request stack.
	 *
	 * @return \Symfony\Component\HttpFoundation\RequestStack
	 */
	public function getRequestStack()
	{
		return $this->requests;
	}

	/**
	 * Add a middleware to the application.
	 *
	 * @param \Closure|string|array $middleware
	 * @param int                   $priority
	 */
	public function addMiddleware($middleware, $priority = null)
	{
		$this->middlewares->insert($middleware, (int) $priority);
	}

	/**
	 * Boot a console application.
	 *
	 * @return \Symfony\Component\Console\Application
	 */
	public function bootConsole()
	{
		$this->console = new ConsoleApplication('Autarky', static::VERSION);

		$this->console->setAutarkyApplication($this);

		$this->boot();

		return $this->console;
	}

	/**
	 * Boot the application.
	 *
	 * @return void
	 */
	public function boot()
	{
		if ($this->booted) return;

		$this->booting = true;

		$this->registerProviders();

		foreach ($this->configurators as $configurator) {
			$this->invokeConfigurator($configurator);
		}

		$this->resolveStack();

		$this->booted = true;
	}

	/**
	 * Register all of the application's service providers.
	 *
	 * @return void
	 */
	protected function registerProviders()
	{
		$dependants = [];

		foreach ($this->providers as $provider) {
			if (is_string($provider)) {
				$provider = new $provider();
			}

			$this->registerProvider($provider);

			if ($provider instanceof DependantProviderInterface) {
				$dependants[] = $provider;
			}
		}

		foreach ($dependants as $dependant) {
			$this->checkProviderDependencies($dependant);
		}
	}

	/**
	 * Register a single service provider.
	 *
	 * @param  ProviderInterface $provider
	 *
	 * @return void
	 */
	protected function registerProvider(ProviderInterface $provider)
	{
		if ($provider instanceof AbstractProvider) {
			$provider->setApplication($this);
		}

		$provider->register();

		if ($this->console && $provider instanceof ConsoleProviderInterface) {
			$provider->registerConsole($this->console);
		}
	}

	protected function checkProviderDependencies(DependantProviderInterface $provider)
	{
		$errors = [];

		foreach ($provider->getClassDependencies() as $class) {
			if (!class_exists($class)) {
				$errors[] = "Class must exist: $class";
			}
		}

		foreach ($provider->getContainerDependencies() as $class) {
			if (!$this->container->isBound($class)) {
				$errors[] = "Class must be bound to the container: $class";
			}
		}

		foreach ($provider->getProviderDependencies() as $class) {
			if (!isset($this->providers[$class])) {
				$errors[] = "Provider must be loaded: $class";
			}
		}

		if ($errors) {
			$providerClass = get_class($provider);
			$message = "Errors while registering provider: $providerClass";

			if (count($errors) > 1) {
				$message .= "\n".implode("\n", $errors);
			} else {
				$message .= " - {$errors[0]}";
			}

			throw new Providers\ProviderException($message, $errors);
		}
	}

	/**
	 * Resolve the stack builder.
	 *
	 * @return \Stack\Builder
	 */
	protected function resolveStack()
	{
		if ($this->stack !== null) {
			return $this->stack;
		}

		$this->stack = new StackBuilder;

		foreach ($this->middlewares as $middleware) {
			call_user_func_array([$this->stack, 'push'], (array) $middleware);
		}

		return $this->stack;
	}

	/**
	 * Resolve the HTTP kernel.
	 *
	 * @return HttpKernelInterface
	 */
	protected function resolveKernel()
	{
		if ($this->kernel !== null) {
			return $this->kernel;
		}

		$class = 'Symfony\Component\EventDispatcher\EventDispatcherInterface';
		$eventDispatcher = $this->container->isBound($class) ?
			$this->container->resolve($class) : null;

		$kernel = new Kernel(
			$this->getRouter(),
			$this->requests,
			$this->errorHandler,
			$eventDispatcher
		);

		return $this->kernel = $this->resolveStack()
			->resolve($kernel);
	}

	/**
	 * Run the application.
	 *
	 * @param  \Symfony\Component\HttpFoundation\Request $request
	 * @param  bool $send
	 *
	 * @return \Symfony\Component\HttpFoundation\Response
	 */
	public function run(Request $request = null, $send = true)
	{
		if ($request === null) {
			$request = Request::createFromGlobals();
		}

		$response = $this->handle($request);

		return $send ? $response->send() : $response;
	}

	/**
	 * {@inheritdoc}
	 */
	public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $throw = false)
	{
		$this->boot();

		return $this->resolveKernel()
			->handle($request, $type, $throw);
	}

	/**
	 * @see \Autarky\Container\ContainerInterface::resolve()
	 */
	public function resolve()
	{
		return call_user_func_array([$this->container, 'resolve'], func_get_args());
	}

	/**
	 * @see \Autarky\Container\ContainerInterface::define()
	 */
	public function define()
	{
		return call_user_func_array([$this->container, 'define'], func_get_args());
	}

	/**
	 * @see \Autarky\Container\ContainerInterface::share()
	 */
	public function share()
	{
		return call_user_func_array([$this->container, 'share'], func_get_args());
	}

	/**
	 * @see \Autarky\Container\ContainerInterface::alias()
	 */
	public function alias()
	{
		return call_user_func_array([$this->container, 'alias'], func_get_args());
	}

	/**
	 * @see \Autarky\Container\ContainerInterface::params()
	 */
	public function params()
	{
		return call_user_func_array([$this->container, 'params'], func_get_args());
	}

	/**
	 * @see \Autarky\Container\ContainerInterface::invoke()
	 */
	public function invoke()
	{
		return call_user_func_array([$this->container, 'invoke'], func_get_args());
	}

	/**
	 * @see \Autarky\Routing\RouterInterface::addRoute()
	 */
	public function route()
	{
		return call_user_func_array([$this->getRouter(), 'addRoute'], func_get_args());
	}

	/**
	 * @see \Autarky\Routing\RouterInterface::mount()
	 */
	public function mount()
	{
		return call_user_func_array([$this->getRouter(), 'mount'], func_get_args());
	}
}


<?php

/**
 * Moo_EditableFieldPageTypeList is an object representing page types dropdown field created by CMS admin.
 *
 * @author  Mohamed Alsharaf <mohamed.alsharaf@gmail.com>
 *
 * @package editablefield
 */
class Moo_EditableFieldPageTypeList extends Moo_EditableField
{
    private static $singular_name = 'Page Type List Field';
    private static $plural_name   = 'Page Type List Fields';

    /**
     * List of allowed custom settings fields.
     *
     * @var array
     */
    protected $customSettingsFields = [
        'PageTypeName',
    ];

    /**
     * Get extra configuration fields.
     *
     * @return array
     */
    public function getFieldConfiguration()
    {
        $pageType = ($this->getSetting('PageTypeName')) ? $this->getSetting('PageTypeName') : 0;
        // Get all subclasses of Page class
        $allTypes = ClassInfo::subclassesFor('Page');

        // Create sorted array with keys and values are class names
        $types = [];
        foreach ($allTypes as $type) {
            $types[(string) $type] = Page::create_from_string($type)->singular_name();
        }
        asort($types);

        return [
            new DropdownField(
                $this->getSettingName('PageTypeName'),
                _t('Moo_EditableFieldPageTypeList.PAGETYPENAME', 'Page Type Name'),
                $types,
                $pageType
            ),
        ];
    }

    protected function initFormField()
    {
        if ($this->getSetting('PageTypeName')) {
            $pages = Page::get($this->getSetting('PageTypeName'))->map('ID', 'Title');

            return new DropdownField($this->Name, $this->Title, $pages);
        }

        return false;
    }

    public function getIcon()
    {
        return 'editablefield/images/formfields/editablefielddropdown.png';
    }
}


<?php
/**
 * This file is part of the Autarky package.
 *
 * (c) Andreas Lutro <anlutro@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Autarky\Database;

use Autarky\Container\ContainerInterface;
use Autarky\Container\Factory\Definition;
use Autarky\Providers\AbstractProvider;

/**
 * Simple service provider that binds a shared PDO instance onto the container,
 * using settings found in the database config file.
 */
class DatabaseProvider extends AbstractProvider
{
	/**
	 * {@inheritdoc}
	 */
	public function register()
	{
		$dic = $this->app->getContainer();

		$dic->share('Autarky\Database\PDOInstantiator');
		$dic->alias('Autarky\Database\PDOInstantiator',
			'Autarky\Database\PDOInstantiatorInterface');

		$dic->share('Autarky\Database\ConnectionFactory');
		$dic->alias('Autarky\Database\ConnectionFactory',
			'Autarky\Database\ConnectionFactoryInterface');

		$dic->define('Autarky\Database\ConnectionManager', function(ContainerInterface $dic) {
			return new ConnectionManager(
				$this->app->getConfig(),
				$dic->resolve('Autarky\Database\ConnectionFactoryInterface')
			);
		});
		$dic->share('Autarky\Database\ConnectionManager');

		$factory = new Definition(['Autarky\Database\ConnectionManager', 'getPdo']);
		$factory->addScalarArgument('$connection', 'string', false, null);
		$dic->define('PDO', $factory);
	}
}


<?php
/**
 * This file is part of the Autarky package.
 *
 * (c) Andreas Lutro <anlutro@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Autarky\Errors;

use Autarky\Providers\AbstractProvider;

/**
 * Provides error handling.
 */
class ErrorHandlerProvider extends AbstractProvider
{
	/**
	 * @var boolean  Whether or not the error handler should register itself as
	 * handler of native PHP errors as well as application exceptions.
	 */
	protected $register;

	/**
	 * @var ErrorHandlerManager
	 */
	protected $manager;

	/**
	 * @param boolean $register Whether or not the error handler should register
	 * itself as handler of native PHP errors as well as application exceptions.
	 */
	public function __construct($register = true)
	{
		$this->register = (bool) $register;
	}

	/**
	 * {@inheritdoc}
	 */
	public function register()
	{
		$dic = $this->app->getContainer();
		$debug = $this->app->getConfig()->get('app.debug');

		$this->manager = new ErrorHandlerManager(
			new HandlerResolver($dic)
		);

		$this->manager->setDefaultHandler(new DefaultErrorHandler($debug));

		$this->app->setErrorHandler($this->manager);

		if ($this->register) {
			$this->manager->register();
		}

		$dic->instance('Autarky\Errors\ErrorHandlerManager', $this->manager);
		$dic->alias('Autarky\Errors\ErrorHandlerManager', 'Autarky\Errors\ErrorHandlerManagerInterface');

		$this->app->config([$this, 'configureErrorHandler']);
	}

	public function configureErrorHandler()
	{
		$errorHandlers = $this->app->getConfig()->get('app.error_handlers', []);

		foreach ($errorHandlers as $errorHandler) {
			$this->manager->appendHandler($errorHandler);
		}
	}
}


<?php
/**
 * This file is part of the Autarky package.
 *
 * (c) Andreas Lutro <anlutro@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Autarky\Logging;

use InvalidArgumentException;
use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;

/**
 * Manager of logging channels.
 */
class ChannelManager extends AbstractLogger implements LoggerInterface
{
	/**
	 * The name of the default channel to use.
	 *
	 * @var string
	 */
	protected $defaultChannel;

	/**
	 * The defined channels.
	 *
	 * @var LoggerInterface[]
	 */
	protected $channels = [];

	/**
	 * The deferred channel callbacks.
	 *
	 * @var callable[]
	 */
	protected $deferredChannels = [];

	/**
	 * Constructor.
	 *
	 * @param string $defaultChannel
	 */
	public function __construct($defaultChannel = 'default')
	{
		$this->defaultChannel = $defaultChannel;
	}

	/**
	 * Change the default channel.
	 *
	 * @param string $channel
	 */
	public function setDefaultChannel($channel)
	{
		$this->defaultChannel = $channel;
	}

	/**
	 * Get the name of the default channel.
	 *
	 * @return string
	 */
	public function getDefaultChannelName()
	{
		return $this->defaultChannel;
	}

	/**
	 * Set a channel instance.
	 *
	 * @param string          $channel
	 * @param LoggerInterface $logger
	 *
	 * @throws InvalidArgumentException If the channel is already defined.
	 */
	public function setChannel($channel, LoggerInterface $logger)
	{
		if (isset($this->channels[$channel])) {
			throw new InvalidArgumentException("Channel $channel is already defined");
		}

		$this->channels[$channel] = $logger;
	}

	/**
	 * Define a deferred channel. The callback will be invoked and the return
	 * value passed to setChannel. The return value must implement
	 * Psr\Log\LoggerInterface.
	 *
	 * @param string   $channel
	 * @param callable $callback Callback that takes no arguments
	 *
	 * @throws InvalidArgumentException If the channel is already defined.
	 */
	public function setDeferredChannel($channel, callable $callback)
	{
		if (isset($this->channels[$channel])) {
			throw new InvalidArgumentException("Channel $channel is already defined");
		}

		$this->deferredChannels[$channel] = $callback;
	}

	/**
	 * Get a specific channel.
	 *
	 * @param  string $channel  Optional - if none, use default channel
	 *
	 * @return \Psr\Log\LoggerInterface
	 *
	 * @throws InvalidArgumentException If the channel is not defined.
	 */
	public function getChannel($channel = null)
	{
		$channel = $channel ?: $this->defaultChannel;

		if (isset($this->deferredChannels[$channel])) {
			$this->setChannel($channel, $this->deferredChannels[$channel]());
			unset($this->deferredChannels[$channel]);
		}

		if (isset($this->channels[$channel])) {
			return $this->channels[$channel];
		}

		throw new InvalidArgumentException("Undefined channel: $channel");
	}

	/**
	 * {@inheritdoc}
	 */
	public function log($level, $message, array $context = array())
	{
		$this->getChannel($this->defaultChannel)
			->log($level, $message, $context);
	}
}


<?php

/**
 * Moo_EditableFieldNumeric is an object representing numeric field created by CMS admin.
 *
 * @package editablefield
 *
 * @author  Mohamed Alsharaf <mohamed.alsharaf@gmail.com>
 */
class Moo_EditableFieldNumeric extends Moo_EditableField
{
    private static $singular_name   = 'Numeric Field';
    private static $plural_name     = 'Numeric Fields';

    /**
     * List of allowed custom settings fields.
     *
     * @var array
     */
    protected $customSettingsFields = [
        'MinValue', 'MaxValue',
    ];

    protected function initFormField()
    {
        $field = new NumericField($this->Name, $this->Title);
        $field->addExtraClass('number');

        return $field;
    }

    /**
     * Get extra validation fields.
     *
     * @return array
     */
    public function getFieldValidationOptions()
    {
        $min = ($this->getSetting('MinValue')) ? $this->getSetting('MinValue') : '';
        $max = ($this->getSetting('MaxValue')) ? $this->getSetting('MaxValue') : '';

        return [
            new NumericField($this->getSettingName('MinValue'), _t('Moo_EditableField.MINVALUE', 'Min Value'), $min),
            new NumericField($this->getSettingName('MaxValue'), _t('Moo_EditableField.MAXVALUE', 'Max Value'), $max),
        ];
    }
}