<?php

namespace Sonic;

/**
 * Sonic
 *
 * @package    Sonic
 * @author     Hassan Khan <contact@hassankhan.me>
 * @link       https://github.com/hassankhan/Sonic
 * @license    MIT
 * @since      0.2
 */
class Sonic
{
    /**
     * Current application version
     */
    const VERSION = '0.7';

    /**
     * Container object to store all dependencies
     *
     * @var \Pimple
     */
    public $app;

    /**
     * A singleton instance of this class, provided as a static property
     *
     * @var \Sonic\Sonic
     * @static
     */
    protected static $instance;

    /**
     * Sonic constructor
     *
     * @param array $settings
     */
    public function __construct(array $settings = array())
    {
        $this->app = new \Pimple();

        // Get local reference to container
        $app = $this->app;

        // Set ROOT_DIR in here, rather than as a constant
        $app['ROOT_DIR'] = realpath(getcwd()) . '/';

        $app['request'] = function () {
            return \Symfony\Component\HttpFoundation\Request::createFromGlobals();
        };

        $app['response'] = function () {
            return new \Symfony\Component\HttpFoundation\Response(
                'Content',
                \Symfony\Component\HttpFoundation\Response::HTTP_OK,
                array('content-type' => 'text/html; charset=utf-8')
            );
        };

        $app['router'] = function ($app) {
            return new Routing\Router($app['request'], $app['response']);
        };

        $app['content_plugin'] = function () {
            $plugin = new Flysystem\Plugin\Markdown();
            $plugin->setParser(new \Michelf\MarkdownExtra);
            return $plugin;
        };

        $app['plugin_plugin'] = function () {
            return new Flysystem\Plugin\Plugin();
        };

        $app['filesystem'] = function ($app) {
            $filesystem = new \League\Flysystem\Filesystem(
                new \League\Flysystem\Adapter\Local($app['ROOT_DIR'])
            );
            $filesystem->addPlugin($app['content_plugin']);
            $filesystem->addPlugin($app['plugin_plugin']);
            return $filesystem;
        };

        $app['helper'] = function ($app) {
            return new Helper($app);
        };

        $app['twig_extension'] = function() {
            return new Extension\Twig;
        };

        $app['twig'] = function ($app) {
            $twig = new \Twig_Environment(
                new \Twig_Loader_Filesystem($app['ROOT_DIR'] . $app['settings']['sonic.templates_dir']),
                $app['settings']['twig']
            );
            $twig->addExtension($app['twig_extension']);
            return $twig;
        };

        // If settings array is empty, then get a default one and validate it
        $settings = array_merge($this->app['helper']->default_config(), $settings);
        $this->app['helper']->validate_config($settings);

        // Set app settings now
        $this->app['settings'] = $settings;
        // If plugins ARE indeed enabled, initialise the plugin loader
        // and load the fuckers
        if ($this->app['settings']['sonic.plugins_enabled'] === TRUE) {
            $this->load_plugins();
            $this->run_hooks('after_plugins_load');
        }

        // Run application hooks and reload application settings
        $this->run_hooks('before_config_load', array(&$settings));
        $app['settings'] = $settings;

        // Add basic routes to router and run application hooks
        $this->run_hooks('before_router_setup');
        $this->setup_router();
        $this->run_hooks('after_router_setup');

        // Set default instance to this one
        if (is_null(static::instance())) {
            static::$instance = $this;
        }
    }

    /**
     * Executes router and returns true for a successful route,
     *
     * @return bool|null
     */
    public function run()
    {
        $this->run_hooks('before_response_send');
        return $this->app['router']->run();
        // $this->run_hooks('after_response_send');
    }

    /**
     * Runs all hooks registered to the specified hook name
     *
     * @param  string  $hook_id
     * @param  string  $args
     * @return boolean
     */
    public function run_hooks($hook_id, $args = array())
    {
        // If plugins are disabled, do not run
        if ($this->app['settings']['sonic.plugins_enabled'] === FALSE) {
            return FALSE;
        }

        // Run hooks associated with that event
        foreach ($this->app['plugins'] as $plugin_id => $plugin) {
            if (is_callable(array($plugin, $hook_id))) {
                call_user_func_array(array($plugin, $hook_id), $args);
            }
        }
        return TRUE;
    }

    /**
     * Loads all plugins from the 'plugins' directory
     *
     * @return
     */
    protected function load_plugins()
    {
        if ($this->app['settings']['sonic.plugins_enabled'] === TRUE) {

            // Load plugins from 'plugins' folder
            $file_list    = $this->app['filesystem']->listContents($this->app['settings']['sonic.plugins_dir']);

            $plugin_files = array_filter($file_list, function ($file) {
                return preg_match('#^([A-Z]+\w+)Plugin.php$#', $file['basename']) === 1
                    ? TRUE
                    : FALSE;
            });

            foreach ($plugin_files as $plugin_file) {
                $plugin = $this->app['filesystem']->include($plugin_file['path']);
                $plugin->set_app($this);
                $plugins[$plugin_file['filename']] = $plugin;
            }

            $this->app['plugins'] = $plugins;
        }
    }

    /**
     * Does the initial setup for the router. This entails creating the routes
     * for the application, mainly
     *
     * @return
     */
    protected function setup_router()
    {
        // Only because you can't use $this->app in the callback
        $app       = $this->app;
        $file_list = $app['filesystem']->listContents($app['settings']['sonic.content_dir'], true);

        $files     = array_filter($file_list, function ($file) {
            return isset($file['extension']) && $file['extension'] === 'md'
                ? TRUE
                : FALSE;
        });

        // Add each as a route
        foreach ($files as $file) {

            // Get filename without extension
            $file_path = str_replace(
                $app['settings']['sonic.content_dir'],
                '',
                $file['dirname'] . '/' . $file['filename']
            );

            $route = '/' . trim(str_replace('/index', '/', $file_path), '/');

            $this->app['router']->route(new Routes\DefaultRoute($route));
        }
    }

    /**
     * Retrieves current instance, if one exists, otherwise returns null
     *
     * @return \Sonic\Sonic|null
     * @static
     */
    public static function instance()
    {
        if (isset(static::$instance) === FALSE) {
            Sonic::kill();
            return null;
        }

        return static::$instance;
    }

    /**
     * Annoying method to help with tests. It probably kills the app too
     *
     * @return
     */
    public static function kill()
    {
        static::$instance = NULL;
    }

}


<?php namespace im\Primitive\Container;

use Closure;
use \Iterator;
use \BadMethodCallException;
use \InvalidArgumentException;
use \RecursiveIteratorIterator;

use JWT;
use im\Primitive\Int\Int;
use im\Primitive\String\String;
use im\Primitive\Object\Object;
use im\Primitive\Support\Arr;
use im\Primitive\Support\Str;
use im\Primitive\Support\Abstracts\ComplexType;
use im\Primitive\Support\Traits\StringCheckerTrait;
use im\Primitive\Support\Contracts\BooleanContract;
use im\Primitive\Support\Contracts\IntegerContract;
use im\Primitive\Support\Contracts\StringContract;
use im\Primitive\Support\Contracts\ContainerContract;
use im\Primitive\Support\Iterators\RecursiveContainerIterator;
use im\Primitive\Support\Exceptions\NotIsFileException;
use im\Primitive\Support\Exceptions\OffsetNotExistsException;
use im\Primitive\Container\Exceptions\ContainerException;
use im\Primitive\Container\Exceptions\BadLengthException;
use im\Primitive\Container\Exceptions\EmptyContainerException;

/**
 * Class Container
 *
 * @package im\Primitive\Container
 * @author Igor Krimerman <i.m.krimerman@gmail.com>
 */
class Container extends ComplexType implements ContainerContract {

    use StringCheckerTrait;

    /**
     * Storing main items
     *
     * @var array
     */
    protected $items;

    /**
     * Constructor
     * Container can be constructed from array, json, serialized, Container or file that contains json or serialized
     *
     * @param array|string|ContainerContract|StringContract $from
     * @throws BadMethodCallException
     * @throws ContainerException
     */
    public function __construct($from = [])
    {
        if ($this->isArrayable($from))
        {
            $this->fromArray($this->retrieveValue($from));
        }
        elseif ($this->isStringable($from))
        {
            $this->fromString($this->getStringable($from));
        }
        else
        {
            throw new InvalidArgumentException('Bad constructor argument, expected string, array or Container');
        }
    }

    /**
     * Magic get method. For support of object style get.
     * First we try to find method with $item name and call it. Note that
     * you can only call methods without parameters. If no such method than
     * we try to find such key with dot notation.
     * Supports dot notation.
     *
     * @param mixed $item
     * @throws OffsetNotExistsException
     * @return mixed
     */
    public function __get($item)
    {
        if (method_exists($this, $item)) return $this->{$item}();

        if ($this->has($item)) return $this->get($item);

        throw new OffsetNotExistsException('Container item: ' . $item . ' not exists');
    }

    /**
     * Magic set method. For support of object style set.
     * Set $key with $value. Supports dot notation.
     *
     * @param mixed $key
     * @param mixed $value
     */
    public function __set($key, $value)
    {
        $this->set($key, $value);
    }

    /**
     * {@inheritdoc}
     */
    public function length()
    {
        return $this->measure();
    }

    /**
     * Getter. Supports dot notation.
     *
     * @param mixed $key
     * @param mixed $default
     * @return mixed
     */
    public function get($key, $default = null)
    {
        return Arr::get($this->items, $this->getKey($key), $default);
    }

    /**
     * Setter. Supports dot notation.
     *
     * @param mixed $key
     * @param mixed $value
     * @return $this
     */
    public function set($key, $value)
    {
        Arr::set($this->items, $this->getKey($key), $value);

        return $this;
    }

    /**
     * Push value to specified key
     *
     * @param mixed $key
     * @param mixed $value
     * @return $this
     */
    public function pushToKey($key, $value)
    {
        if ( ! $this->has($key))
        {
            $this->set($key, []);
        }

        $keyValue = $this->get($key);

        if ($keyValue instanceof static)
        {
            $keyValue->push($value);
        }
        elseif (is_array($keyValue))
        {
            $keyValue[] = $value;
        }
        else
        {
            $this->set($key, [])->pushToKey($key, $keyValue);

            return $this;
        }

        $this->set($key, $keyValue);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function value()
    {
        return $this->items;
    }

    /**
     * Push item in a Container.
     *
     * @param mixed $item
     * @return $this
     */
    public function push($item)
    {
        $this->items[] = $item;

        return $this;
    }

    /**
     * Remove last item from Container and return it.
     *
     * @return mixed
     */
    public function pop()
    {
        return array_pop($this->items);
    }

    /**
     * Add item to the first index of Container.
     *
     * @param mixed $item
     * @return $this
     */
    public function prepend($item)
    {
        array_unshift($this->items, $item);

        return $this;
    }

    /**
     * Remove first item from Container and return it.
     *
     * @return mixed
     */
    public function shift()
    {
        return array_shift($this->items);
    }

    /**
     * Search for specified value, return key on success, otherwise false.
     * If value is nested deeper than 1 level dot notation key will be returned.
     * You can specify strict search with 2 argument.
     *
     * @param mixed $value
     * @param bool|BooleanContract $strict
     * @return mixed
     */
    public function search($value, $strict = false)
    {
        return Arr::search($this->items, $this->getSearchable($value, $value), $this->getBoolable($strict));
    }

    /**
     * Check if Container has specified key.
     *
     * @param mixed $key
     * @return bool
     */
    public function has($key)
    {
        return Arr::has($this->items, $this->getKey($key));
    }

    /**
     * Checks if Container has specified value.
     *
     * @param mixed $value
     * @param null|bool|BooleanContract $strict
     * @return bool
     */
    public function hasValue($value, $strict = false)
    {
        return ! is_null(
            Arr::search($this->items, $this->getSearchable($value, $value), $this->getBoolable($strict))
        );
    }

    /**
     * Key an associative array by a field.
     *
     * @param  string|StringContract  $keyBy
     * @return static
     */
    public function keysByField($keyBy)
    {
        $byField = [];

        $keyBy = $this->getStringable($keyBy);

        foreach ($this->items as $item)
        {
            $key = _data_get($item, $keyBy);

            $byField[$key] = $item;
        }

        return new static($byField);
    }

    /**
     * Return first Container key.
     *
     * @throws ContainerException
     * @throws EmptyContainerException
     * @return mixed
     */
    public function firstKey()
    {
        if ($this->isEmpty()) throw new EmptyContainerException('Empty Container');

        return first_key($this->items);
    }

    /**
     * Return last Container key.
     *
     * @throws ContainerException
     * @throws EmptyContainerException
     * @return mixed
     */
    public function lastKey()
    {
        if ($this->isEmpty()) throw new EmptyContainerException('Empty Container');

        return last_key($this->items);
    }

    /**
     * Return first Container value.
     *
     * @return mixed
     * @throws \im\Primitive\Container\Exceptions\EmptyContainerException
     */
    public function first()
    {
        if ($this->isEmpty()) throw new EmptyContainerException('Empty Container');

        return first($this->items);
    }

    /**
     * Return first value that passes truth test.
     *
     * @param callable $function
     * @return mixed
     * @throws \im\Primitive\Container\Exceptions\EmptyContainerException
     */
    public function firstWhere(callable $function)
    {
        if ($this->isEmpty()) throw new EmptyContainerException('Empty Container');

        return Arr::first($this->items, $function);
    }

    /**
     * Return last value.
     *
     * @return mixed
     * @throws EmptyContainerException
     */
    public function last()
    {
        if ($this->isEmpty()) throw new EmptyContainerException('Empty Container');

        return last($this->items);
    }

    /**
     * Return last value that passes truth test.
     *
     * @param callable $function
     * @return mixed
     * @throws \im\Primitive\Container\Exceptions\EmptyContainerException
     */
    public function lastWhere(callable $function)
    {
        if ($this->isEmpty()) throw new EmptyContainerException('Empty Container');

        return Arr::last($this->items, $function);
    }

    /**
     * Unique Container items.
     *
     * @param bool|BooleanContract $recursive
     * @return $this
     */
    public function unique($recursive = false)
    {
        if ($this->getBoolable($recursive))
        {
            return new static($this->uniqueRecursive($this->items));
        }

        return new static(array_unique($this->items));
    }

    /**
     * Return Container keys.
     *
     * @return Container
     */
    public function keys()
    {
        return new static(array_keys($this->items));
    }

    /**
     * Return Container values
     *
     * @return Container
     */
    public function values()
    {
        return new static(array_values($this->items));
    }

    /**
     * Returns keys and values divided in new Container
     * with indexes 'keys' for keys and 'values' for values.
     *
     * @return Container
     */
    public function divide()
    {
        return new static(Arr::divide($this->items));
    }

    /**
     * Return items only with numeric keys.
     *
     * @return static
     */
    public function numericKeys()
    {
        $keys = new static;

        foreach ($this->items as $key => $value)
        {
            if (is_numeric($key)) $keys->set($key, $value);
        }

        return $keys;
    }

    /**
     * Return items only with not numeric keys.
     *
     * @return static
     */
    public function notNumericKeys()
    {
        $keys = new static;

        foreach ($this->items as $key => $value)
        {
            if ( ! is_numeric($key)) $keys->set($key, $value);
        }

        return $keys;
    }

    /**
     * Shuffle Container items.
     *
     * @return $this
     */
    public function shuffle()
    {
        shuffle($this->items);

        return $this;
    }

    /**
     * Flatten a multi-dimensional associative array with dots.
     *
     * @return static
     */
    public function dot()
    {
        return new static(Arr::dot($this->items));
    }

    /**
     * Return joined Container items.
     *
     * @param string|StringContract $glue
     * @return \im\Primitive\String\String
     */
    public function join($glue = '')
    {
        foreach ($copy = Arr::flatten($this->items) as $key => $object)
        {
            $copy[$key] = $this->getStringable($object);
        }

        return string(implode($this->getStringable($glue, ''), $copy));
    }

    /**
     * Concatenate values of a given key as a string.
     *
     * @param mixed $key
     * @param null|mixed $glue
     * @return \im\Primitive\String\String
     */
    public function joinByKey($key, $glue = null)
    {
        return string(implode($glue, $this->lists($this->getKey($key))->all()));
    }

    /**
     * Get new Container with the values of a given key.
     * 1 Argument is key to make value from.
     * 2 Argument is key from the same Arrayable which will be the key.
     *
     * @param  mixed $valueByKey
     * @param  mixed $key
     * @return static
     */
    public function lists($valueByKey, $key = null)
    {
        return new static(Arr::pluck($this->items, $this->getKey($valueByKey), $this->getKey($key)));
    }

    /**
     * Return split Container items into chunks wrapped with new Container.
     *
     * @param int|IntegerContract  $size
     * @param bool|BooleanContract $preserveKeys
     * @return \im\Primitive\Container\Container
     * @throws \im\Primitive\Container\Exceptions\BadLengthException
     */
    public function chunk($size = 2, $preserveKeys = false)
    {
        $size = $this->getIntegerable($size);

        if ( ! is_integer($size) || $size > $this->length())
        {
            throw new BadLengthException('Chunk size is larger than container length');
        }

        $chunks = new static;

        foreach (array_chunk($this->items, $size, $this->getBoolable($preserveKeys)) as $value)
        {
            $chunks->push(new static($value));
        }

        return $chunks;
    }

    /**
     * Combine values from $array.
     * You can specify what to combine 'keys' or 'values' with the second argument.
     *
     * @param mixed $array
     * @param string|StringContract $what
     * @return static
     * @throws BadMethodCallException
     * @throws BadLengthException
     */
    public function combine($array, $what = 'keys')
    {
        $array = $this->retrieveValue($array);

        if (count($array) !== $this->length())
        {
            throw new BadLengthException('Container length should match array length.');
        }

        switch ($this->getStringable($what))
        {
            case 'keys':
                return new static(array_combine($array, $this->values()->all()));
            case 'values':
                return new static(array_combine($this->keys()->all(), $array));
            default:
                throw new BadMethodCallException('Argument 2 must be string or Stringable (keys or values)');
        }
    }

    /**
     * Return filtered Container.
     * You can specify recursive filter with the second argument.
     *
     * @param callable $function
     * @param bool|BooleanContract $recursive
     * @return Container
     */
    public function filter(callable $function, $recursive = false)
    {
        if ( ! $this->getBoolable($recursive))
        {
            return new static(array_filter($this->items, $function));
        }

        return new static($this->filterRecursive($function, $this->items));
    }

    /**
     * Flip keys with values.
     *
     * @return $this
     * @throws \im\Primitive\Container\Exceptions\ContainerException
     */
    public function flip()
    {
        if ($this->isMulti())
        {
            throw new ContainerException('Can\'t flip in multi-dimensional array.');
        }

        return new static(array_flip($this->items));
    }

    /**
     * Traverse Container items.
     *
     * @param callable $function
     * @return $this
     */
    public function each(callable $function)
    {
        array_map($function, $this->items);

        return $this;
    }

    /**
     * Run a map on each Container item.
     *
     * @param callable $function
     * @return $this
     */
    public function map(callable $function)
    {
        return new static(array_map($function, $this->items, $this->keys()->all()));
    }

    /**
     * Transform each item with the callback.
     *
     * @param callable $function
     * @return $this
     */
    public function transform(callable $function)
    {
        $this->items = array_map($function, $this->items);

        return $this;
    }

    /**
     * Apply a user function to every Container item.
     * You can specify recursive walk with the second argument.
     *
     * @param callable $callback
     * @param bool|BooleanContract $recursive
     * @param null|mixed $userdata
     * @return $this
     */
    public function walk(callable $callback, $recursive = false, $userdata = null)
    {
        $function = $this->getBoolable($recursive) ? 'array_walk_recursive' : 'array_walk';

        $function($this->items, $callback, $userdata);

        return $this;
    }

    /**
     * Merge Arrayable with Container.
     *
     * @param  mixed $items
     * @throws BadMethodCallException
     * @return $this
     */
    public function merge($items)
    {
        if ( ! $this->isArrayable($items))
        {
            throw new BadMethodCallException('Argument 1 must be array, Container or implement Arrayable interface');
        }

        return new static(array_merge($this->items, $this->retrieveValue($items)));
    }

    /**
     * Merge Arrayable with specified key in Container.
     *
     * @param mixed $items
     * @param mixed $key
     * @param null $default
     * @return $this
     * @throws InvalidArgumentException
     */
    public function mergeWithKey($items, $key, $default = null)
    {
        $key = $this->getKey($key);

        if ( ! $this->has($key)) throw new InvalidArgumentException('Key: '.$key.' not exists');

        $get = $this->get($key, $default);

        $value = array_merge($get, $this->retrieveValue($items));

        return $this->copy()->set($key, $value);
    }

    /**
     * Increase Container to the specified length with a value.
     *
     * @param int|IntegerContract $increaseSize
     * @param null|mixed $value
     * @return $this
     */
    public function increase($increaseSize = 1, $value = null)
    {
        $this->items = array_pad($this->items, $this->length() + $this->getIntegerable($increaseSize), $value);

        return $this;
    }

    /**
     * Return pseudo-random index from Container.
     *
     * @param int|IntegerContract $quantity
     * @throws BadMethodCallException
     * @return mixed
     */
    public function randomKey($quantity = 1)
    {
        $quantity = $this->getIntegerable($quantity);

        if ($this->isNotEmpty() && $this->length() >= $quantity && $quantity > 0)
        {
            $random = array_rand($this->items, $quantity);

            return is_array($random) ? new static($random) : $random;
        }

        throw new BadMethodCallException("1 Argument should be between 1 and the number of elements in the Container, got: {$quantity}");
    }

    /**
     * Return pseudo-random item from Container.
     *
     * @param int|IntegerContract $quantity
     * @return array
     */
    public function random($quantity = 1)
    {
        $quantity = $this->getIntegerable($quantity);

        $result = new static;

        while($quantity--)
        {
            $result->push($this->items[$this->randomKey()]);
        }

        return $result->length() === 1 ? $result->first() : $result;
    }

    /**
     * Cut a slice of a Container.
     * You can return result or assign to Container with the forth argument.
     *
     * @param int|IntegerContract $offset
     * @param null|int|IntegerContract $length
     * @param bool|BooleanContract $preserveKeys
     * @param bool|BooleanContract $set
     * @return static|$this
     */
    public function cut($offset, $length = null, $preserveKeys = false, $set = false)
    {
        $result = array_slice(
            $this->items,
            $this->getIntegerable($offset),
            $this->getIntegerable($length),
            $this->getBoolable($preserveKeys)
        );

        if ($this->getBoolable($set))
        {
            $this->items = empty($result) ? [] : $result;

            return $this;
        }

        return new static($result);
    }

    /**
     * Create new Container of all elements that do not pass a given truth test.
     *
     * @param callable|string|StringContract $callback
     * @return static
     */
    public function reject($callback)
    {
        if (is_callable($callback))
        {
            return $this->copy()->filter(function($item) use ($callback)
            {
                return ! $callback($item);
            });
        }

        $callback = $this->getStringable($callback);

        return $this->copy()->filter(function($item) use ($callback)
        {
            return $item != $callback;
        });
    }

    /**
     * Reduce items to one value.
     *
     * @param callable $callback
     * @param null|mixed $initial
     * @return mixed
     */
    public function reduce(callable $callback, $initial = null)
    {
        return array_reduce($this->items, $callback, $initial);
    }

    /**
     * Encrypt Container items to JWT Token.
     *
     * @param string|StringContract $key
     * @param int|IntegerContract $expires
     * @return string
     */
    public function encrypt($key, $expires)
    {
        $payload = [
            'exp' => $this->getIntegerable($expires),
            'container' => $this->toJson()
        ];

        return JWT::encode($payload, $this->getStringable($key));
    }

    /**
     * Return base64 representation of Container.
     *
     * @return string
     */
    public function base64()
    {
        return base64_encode($this->toJson());
    }

    /**
     * Remove key from Container. Supports dot notation.
     *
     * @param mixed $key
     * @return $this
     */
    public function forget($key)
    {
        Arr::forget($this->items, $this->getStringable($key));

        return $this;
    }

    /**
     * Reset Container to empty array.
     *
     * @return $this
     */
    public function reset()
    {
        $this->items = [];

        return $this;
    }

    /**
     * Reverse Container items.
     *
     * @param bool|BooleanContract $preserveKeys
     * @return $this
     */
    public function reverse($preserveKeys = true)
    {
        $this->items = array_reverse($this->items, $this->getBoolable($preserveKeys));

        return $this;
    }

    /**
     * Return all items from Container. Alias for value.
     *
     * @return array
     */
    public function all()
    {
        return $this->value();
    }

    /**
     * Create copy of Container.
     *
     * @return Container
     */
    public function copy()
    {
        return clone $this;
    }

    /**
     * Group an associative array by a field or callback value.
     *
     * @param  \Closure|string|StringContract  $groupBy
     * @return static
     */
    public function groupBy($groupBy)
    {
        if ($this->isStringable($groupBy, true)) $groupBy = $this->getStringable($groupBy);

        $results = [];

        foreach ($this->items as $key => $value)
        {
            $results[$this->getGroupByKey($groupBy, $key, $value)][] = $value;
        }

        return new static($results);
    }

    /**
     * Return copy of Container except given keys.
     *
     * @param mixed $keys
     * @return static
     */
    public function except($keys)
    {
        return new static(Arr::except($this->items, $this->retrieveValue($keys)));
    }

    /**
     * Return copy of Container except given index.
     *
     * @param int|IntegerContract $nth
     * @throws OffsetNotExistsException
     * @return Container
     */
    public function exceptIndex($nth)
    {
        $nth = $this->getIntegerable($nth);

        if ($this->isEmpty() || $nth >= $this->length())
        {
            throw new OffsetNotExistsException('Offset: '. $nth .' not exist');
        }

        return $this->copy()->forget($this->keys()->get($nth));
    }

    /**
     * Return rest items after given index.
     *
     * @param int|IntegerContract $index
     * @return \im\Primitive\Container\Container
     * @throws \im\Primitive\Container\Exceptions\BadLengthException
     * @throws \im\Primitive\Container\Exceptions\ContainerException
     * @throws \im\Primitive\Support\Exceptions\OffsetNotExistsException
     */
    public function restAfterIndex($index)
    {
        $index = $this->getIntegerable($index);

        if ( ! is_numeric($index))
        {
            throw new BadMethodCallException('Argument 1: ' . $index . ' is not numeric');
        }

        $length = $this->length();

        if ($length <= $index)
        {
            throw new OffsetNotExistsException('Offset: '. $index .' not exists');
        }

        $index++;

        $keys = $this->keys()->cut($index, $length - 1)->flip();

        $values = $this->values()->cut($index, $length - 1)->all();

        return $keys->combine($values, 'values');
    }

    /**
     * Return rest items after given key.
     *
     * @param mixed $key
     * @return \im\Primitive\Container\Container
     * @throws \im\Primitive\Container\Exceptions\ContainerException
     * @throws \im\Primitive\Support\Exceptions\OffsetNotExistsException
     */
    public function restAfterKey($key)
    {
        $key = $this->getKey($key);

        if ( ! array_key_exists($key, $this->items))
        {
            throw new OffsetNotExistsException('Key: '. $key .' not exists');
        }

        $index = $this->keys()->flip()->get($key);

        return $this->restAfterIndex($index);
    }

    /**
     * Flatten Container items.
     *
     * @return $this
     */
    public function flatten()
    {
        return new static(Arr::flatten($this->items));
    }

    /**
     * Calculate difference of Container and given Arrayable.
     *
     * @param mixed $items
     * @return static
     */
    public function difference($items)
    {
        if ($this->isArrayable($items))
        {
            return new static(array_diff_key($this->items, $this->retrieveValue($items)));
        }

        throw new BadMethodCallException('Argument 1 should be array, Container or implement ArrayableContract');
    }

    /**
     * Get gathered column of a nested array element.
     *
     * @param mixed $key
     * @return static
     */
    public function column($key)
    {
        return new static(Arr::fetch($this->items, $this->getKey($key)));
    }

    /**
     * Remove all not true items from Container (null, '', false, 0, []).
     * You can specify second argument to make it recursive.
     *
     * @param bool|BooleanContract $recursive
     * @param \Closure|string|StringContract|null $function
     * @return $this
     */
    public function truly($recursive = false, $function = null)
    {
        $recursive = $this->getBoolable($recursive);

        $function = $this->getSearchable($function, null);

        if (is_null($function) || ! $function instanceof Closure)
        {
            $function = function ($item) {return ! empty($item);};
        }

        return new static($this->filter($function, $recursive));
    }

    /**
     * Take all items recursively by key.
     *
     * @param mixed $key
     * @return static
     */
    public function take($key)
    {
        $take = [];

        $key = $this->getKey($key);

        $this->walk(function ($_value_, $_key_) use ($key, & $take)
        {
            if ($_key_ == $key) $take[] = $_value_;

        }, true);

        return new static($take);
    }

    /**
     * Get a value from a Container, and remove it.
     *
     * @param mixed $key
     * @return mixed
     * @throws OffsetNotExistsException
     */
    public function pull($key)
    {
        $key = $this->getKey($key);

        if ( ! $this->has($key))
        {
            throw new OffsetNotExistsException("Key: {$key} not exists");
        }

        return Arr::pull($this->items, $key);
    }

    /**
     * Recursively remove values by key.
     *
     * @param mixed $key
     * @return static
     */
    public function without($key)
    {
        return new static($this->forgetRecursive($this->getKey($key), $this->items));
    }

    /**
     * Return intersection with Arrayable.
     * You can specify second argument to with additional index check.
     *
     * @param mixed $array
     * @param bool|BooleanContract $assoc
     * @return Container
     */
    public function intersect($array, $assoc = false)
    {
        $function = $this->getBoolable($assoc) ? 'array_intersect_assoc' : 'array_intersect';

        return new static($function($this->items, $this->retrieveValue($array)));
    }

    /**
     * Return intersection by keys with Arrayable.
     *
     * @param mixed $array
     * @return Container
     */
    public function intersectKey($array)
    {
        return new static(array_intersect_key($this->items, $this->retrieveValue($array)));
    }

    /**
     * Return user sorted Container.
     *
     * @param \Closure $function
     * @return static
     */
    public function sort(Closure $function)
    {
        $copy = $this->all();

        usort($copy, $function);

        return new static($copy);
    }

    /**
     * Reset keys to numeric.
     *
     * @return $this
     */
    public function resetKeys()
    {
        $this->items = array_values($this->items);

        return $this;
    }

    /**
     * Return sum of numeric values.
     *
     * @return \im\Primitive\Int\Int
     */
    public function sum()
    {
        return new Int(array_sum($this->flatten()->filter('is_numeric')->all()));
    }

    /**
     * Find all items by key or key value pairs.
     * You can specify second parameter to preserve keys reset.
     *
     * @param mixed $condition
     * @param bool|BooleanContract $preserveKeys
     * @throws ContainerException
     * @throws EmptyContainerException
     * @return static
     */
    public function where($condition, $preserveKeys = true)
    {
        $condition = $this->retrieveValue($condition);

        if (empty($condition)) return $this;

        return new static($this->whereCondition($condition, $this->getBoolable($preserveKeys)));
    }

    /**
     * Check if Container items is associative.
     *
     * @return bool
     */
    public function isAssoc()
    {
        return $this->keys()->filter('is_int')->length() !== $this->length();
    }

    /**
     * Check if Container items is not associative.
     *
     * @return bool
     */
    public function isNotAssoc()
    {
        return ! $this->isAssoc();
    }

    /**
     * Check if Container is multi-dimensional.
     *
     * @return bool
     */
    public function isMulti()
    {
        return $this->values()->filter('is_scalar')->length() !== $this->length();
    }

    /**
     * Check if Container is not multi-dimensional.
     *
     * @return bool
     */
    public function isNotMulti()
    {
        return ! $this->isMulti();
    }

    /**
     * Check if Container is empty.
     *
     * @return bool
     */
    public function isEmpty()
    {
        return ! (bool) $this->length();
    }

    /**
     * Check if Container is not empty.
     *
     * @return bool
     */
    public function isNotEmpty()
    {
        return (bool) $this->length();
    }

    /**
     * Return Int Type representation of Container.
     *
     * @return \im\Primitive\Int\Int
     */
    public function toInt()
    {
        return $this->sum();
    }

    /**
     * Return Bool Type representation of Container.
     *
     * @return \im\Primitive\Bool\Bool
     */
    public function toBool()
    {
        return $this->toInt()->toBool();
    }

    /**
     * Return Float Type representation of Container.
     *
     * @return \im\Primitive\Float\Float
     */
    public function toFloat()
    {
        return $this->toInt()->toFloat();
    }

    /**
     * Return String Type representation of Container.
     *
     * @param int $options
     *
     * @return \im\Primitive\String\String
     */
    public function toString($options = 0)
    {
        return new String($this->toJson($options));
    }

    /**
     * Return Object Type representation of Container.
     *
     * @return \im\Primitive\Object\Object
     */
    public function toObject()
    {
        return new Object($this->value());
    }

    /**
     * Construct from Json.
     *
     * @param $json
     * @return $this
     */
    public function fromJson($json)
    {
        $json = $this->getStringable($json);

        if ($this->isJson($json))
        {
            $this->initialize(json_decode($json, true));
        }

        return $this;
    }

    /**
     * Construct from file.
     * Contents can be json or serialized array.
     *
     * @param string|StringContract $file
     * @throws ContainerException
     * @throws NotIsFileException
     * @return $this
     */
    public function fromFile($file)
    {
        $file = $this->getStringable($file);

        if ( ! $this->isFile($file)) throw new NotIsFileException('Not is file: ' . $file);

        $content = file_get_contents($file);

        if ($this->isJson($content))
        {
            return $this->initialize(json_decode($content, true));
        }
        elseif ($this->isSerialized($content))
        {
            return $this->initialize(unserialize($content));
        }

        throw new ContainerException('Can\'t convert file to Container');
    }

    /**
     * Construct from serialized.
     *
     * @param string|StringContract $content
     * @return $this
     */
    public function fromSerialized($content)
    {
        $content = $this->getStringable($content);

        if ($this->isSerialized($content))
        {
            return $this->initialize(unserialize($content));
        }

        throw new BadMethodCallException('Expected serialized, got: ' . $content);
    }

    /**
     * Construct from Encrypted Container.
     *
     * @param string|StringContract $encrypted
     * @param string|StringContract $key
     * @return \im\Primitive\Container\Container
     */
    public function fromEncrypted($encrypted, $key)
    {
        $encrypted = $this->getStringable($encrypted);

        $key = $this->getStringable($key);

        if ($this->isEncryptedContainer($encrypted, $key))
        {
            $data = JWT::decode($encrypted, $key);

            return $this->fromJson($data->container);
        }

        throw new BadMethodCallException('Expected encrypted Container, got: ' . $encrypted);
    }

    /**
     * Construct from string.
     *
     * @param string|StringContract $string
     * @return $this
     * @throws \im\Primitive\Container\Exceptions\ContainerException
     * @throws \im\Primitive\Support\Exceptions\NotIsFileException
     */
    protected function fromString($string)
    {
        if ($this->isFile($string))
        {
            return $this->fromFile($string);
        }
        elseif ($this->isJson($string))
        {
            return $this->initialize(json_decode($string, true));
        }
        elseif ($this->isSerialized($string))
        {
            return $this->initialize(unserialize($string));
        }

        throw new BadMethodCallException('Argument 1 should be valid json, serialized or file with json or serialized data');
    }

    /**
     * Return json representation of Container.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->toJson();
    }

    /**
     * Magic call method.
     * Calls native php array functions, where with key parsed or combine.
     *
     * @param $method
     * @param $parameters
     *
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        $method = string($method);

        if ($method->startsWith('array_'))
        {
            return call_user_func_array($method(), array_merge([$this->items], $parameters));
        }

        if ($method->startsWith('where'))
        {
            $key = $method->cut(Str::length('where'), $method->length())->lower();

            return $this->where([(string) $key => $parameters[0]]);
        }

        if ($method->startsWith('combine'))
        {
            $what = $method->removeLeft('combine')->lower()->value();

            return $this->combine($parameters[0], $what);
        }

        throw new BadMethodCallException('Can\'t find method: '.$method.' in class'. __CLASS__);
    }

    /**
     *  Destructor
     */
    public function __destruct()
    {
        unset($this->items);
    }

    /**
     * Measure Container length.
     *
     * @param int $default
     * @return int
     */
    protected function measure($default = 0)
    {
        if ( ! is_array($this->items)) return $default;

        return count($this->items);
    }

    /**
     * Find where condition is array.
     *
     * @param array $conditions
     * @param bool $preserveKeys
     * @return array
     * @throws EmptyContainerException
     */
    protected function whereCondition(array $conditions, $preserveKeys)
    {
        $key = first_key($conditions);  $value = array_shift($conditions);

        $where = $this->whereRecursive($this->items, $key, $value, $preserveKeys);

        foreach ($conditions as $key => $value)
        {
            $where = $this->whereRecursive($where, $key, $value, $preserveKeys);
        }

        return $where;
    }

    /**
     * Recursively traversing tree.
     *
     * @param $array
     * @param $key
     * @param $value
     * @param $preventKeys
     * @return array
     */
    protected function whereRecursive($array, $key, $value = null, $preventKeys = false)
    {
        $outputArray = [];

        $iterator = new RecursiveIteratorIterator(
            new RecursiveContainerIterator($array),
            RecursiveIteratorIterator::SELF_FIRST
        );

        foreach ($iterator as $sub)
        {
            $subIterator = $iterator->getSubIterator();

            if (isset($subIterator[$key]) && (( ! is_null($value) && $subIterator[$key] == $value ) || is_null($value)))
            {
                $outputArray += $this->iteratorToArray($iterator, $subIterator, $outputArray, $preventKeys);
            }
        }

        return $outputArray;
    }

    /**
     * Convert iterator to array.
     *
     * @param RecursiveIteratorIterator $iterator
     * @param Iterator $subIterator
     * @param array $outputArray
     * @param $preventKeys
     * @return array
     */
    protected function iteratorToArray(RecursiveIteratorIterator $iterator, Iterator $subIterator, array $outputArray, $preventKeys = false)
    {
        if ($preventKeys === false)
        {
            $key = $iterator->getSubIterator($iterator->getDepth() - 1)->key();

            $outputArray[$key] = iterator_to_array($subIterator);
        }
        else
        {
            $outputArray[] = iterator_to_array($subIterator);
        }

        return $outputArray;
    }

    /**
     * Get the "group by" key value.
     *
     * @param  \Closure|string  $groupBy
     * @param  string  $key
     * @param  mixed  $value
     * @return string
     */
    protected function getGroupByKey($groupBy, $key, $value)
    {
        if ( ! is_string($groupBy) && $groupBy instanceof Closure)
        {
            return $groupBy($value, $key);
        }

        return _data_get($value, $groupBy);
    }

    /**
     * Recursive filter.
     *
     * @param \Closure $function
     * @param          $items
     * @return array
     */
    protected function filterRecursive(Closure $function, $items)
    {
        if ( ! $this->isArrayable($items)) return $items;

        foreach ($items as $key => $item)
        {
            $items[$key] = $this->filterRecursive($function, $this->retrieveValue($item));
        }

        return array_filter($items, $function);
    }

    /**
     * Recursive unset.
     *
     * @param $forgetKey
     * @param mixed $items
     * @return mixed
     */
    protected function forgetRecursive($forgetKey, $items)
    {
        Arr::forget($items, $forgetKey);

        foreach ($items as $key => $item)
        {
            if ($this->isArrayable($item))
            {
                $items[$key] = $this->forgetRecursive($forgetKey, $this->retrieveValue($item));
            }
        }

        return $items;
    }

    /**
     * Unique items recursively.
     *
     * @param $items
     * @return array
     */
    protected function uniqueRecursive($items)
    {
        foreach ($items as $key => $item)
        {
            if ($this->isArrayable($item))
            {
                $this->items[$key] = $this->uniqueRecursive($this->retrieveValue($item));
            }
        }

        return array_unique($this->items);
    }

    /**
     * {@inheritdoc}
     */
    protected function initialize($array)
    {
        $this->reset();

        foreach ((array) $array as $key => $value)
        {
            $this->set($key, $value);
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    protected function retrieveValue($value)
    {
        return $this->getArrayable($value, $this->getDefault());
    }

    /**
     * {@inheritdoc}
     */
    protected function getDefault()
    {
        return [];
    }

    /**
     * Retrieve proper key.
     *
     * @param mixed $key
     * @param null|mixed $default
     * @return int|string
     */
    protected function getKey($key, $default = null)
    {
        return ($this->isIntegerable($key, true)) ? $this->getIntegerable($key, $default)
                                                  : $this->getStringable($key, $default);
    }

    /*
    |--------------------------------------------------------------------------
    | JsonSerializable
    |--------------------------------------------------------------------------
    */
    /**
     * (PHP 5 >= 5.4.0)
     * Specify data which should be serialized to JSON
     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
     * @return mixed data which can be serialized by json_encode,
     * which is a value of any type other than a resource.
     */
    public function jsonSerialize()
    {
        return $this->filter(function($value)
        {
            return ! $value instanceof Closure;

        })->all();
    }

    /*
    |--------------------------------------------------------------------------
    | ArrayAccess
    |--------------------------------------------------------------------------
    */
    /**
     * (PHP 5 >= 5.0.0)
     * Offset to set
     * @link http://php.net/manual/en/arrayaccess.offsetset.php
     * @param mixed $offset The offset to assign the value to.
     * @param mixed $value The value to set.
     * @return void
     */
    public function offsetSet($offset, $value)
    {
        if (is_null($offset))
        {
            $this->items[] = $value;
        }

        $this->set($offset, $value);
    }
}


<?php

namespace Sonic\Routes;

/**
 * ListRoute
 *
 * This route displays a list of posts
 *
 * @package    Sonic
 * @subpackage Route
 * @author     Hassan Khan <contact@hassankhan.me>
 * @link       https://github.com/hassankhan/Sonic
 * @license    MIT
 * @since      0.7
 */
abstract class ListRouteAbstract extends \Sonic\Routing\Route
{

    abstract public function posts();

    protected $sonic;

    /**
     * Initializes the route by creating a regex pattern from the provided URL,
     * and assigns the callable function for this route
     *
     * @param string $url
     * @codeCoverageIgnore
     */
    public function __construct($url = '')
    {
        // Get reference to Sonic application
        $this->sonic = \Sonic\Sonic::instance();
        parent::__construct($url, array($this, 'build_route'));
    }

    /**
     * Builds and returns the rendered HTML
     *
     * @return string
     */
    public function build_route($param = '')
    {
        // Get filenames of content and create array to hold posts
        $posts = $this->posts($param);

        // Load in any extra stuffs
        $this->sonic->app['extra'] = isset($this->sonic->app['extra']) === TRUE ? $this->sonic->app['extra'] : array();

        // Render template with Twig
        return $this->sonic->app['twig']->render($this->sonic->app['settings']['sonic.default_list_template'], array('contents' => $posts));
    }

    /**
     * Gets file excerpts from all files
     *
     * @param  array $files
     * @return array
     */
    public function excerpts($files)
    {
        // Create array to hold posts
        $posts = array();

        // Iterate through files and get excerpts for all of them
        foreach ($files as $file) {
            $file_contents = $this->sonic->app['filesystem']->parse($file);

            if (
                $file_contents['meta']['title'] !== 'Quote'
                &&
                $this->sonic->app['settings']['site.excerpt_newline_limit'] !== 0
            ) {
                $contents  = $this->sonic->app['helper']->get_excerpt(
                    $file_contents['contents'],
                    $this->sonic->app['settings']['site.excerpt_newline_limit']
                );

                $file_contents['contents'] = $contents;
            }
            $posts[str_replace('content/', '', $file)] = $file_contents;
        }

        return $posts;
    }

}


<?php

namespace Sonic\Flysystem\Plugin;

/**
 * Plugin
 *
 * @package    Sonic
 * @subpackage Flysystem
 * @author     Hassan Khan <contact@hassankhan.me>
 * @link       https://github.com/hassankhan/Sonic
 * @license    MIT
 * @since      0.7
 */
class Plugin implements \League\Flysystem\PluginInterface
{

    /**
     * Filesystem object
     *
     * @var \League\Flysystem\FilesystemInterface
     */
    protected $filesystem;

    /**
     * Sets the filesystem for this plugin
     *
     * @param LeagueFlysystemFilesystemInterface $filesystem
     * @codeCoverageIgnore
     */
    public function setFilesystem(\League\Flysystem\FilesystemInterface $filesystem)
    {
        $this->filesystem = $filesystem;
    }

    /**
     * Returns plugin's method name
     *
     * @return string
     */
    public function getMethod()
    {
        return 'include';
    }

    /**
     * Plugin handler
     *
     * @param  string $path
     * @return \Sonic\PluginInterface
     */
    public function handle($path = '')
    {
        // Try and read the file
        $file = \League\Flysystem\Util::pathinfo($path);

        // Include plugin file
        include_once(getcwd() . '/' . $file['path']);

        // Check class implements correct interface
        $parents = class_parents($file['filename']);
        if (isset($parents['Sonic\PluginAbstract']) === FALSE) {
            throw new \UnexpectedValueException('Plugin does not implement Sonic\PluginAbstract');
        }

        return new $file['filename'];
    }

}


<?php
/**
 * Spiral Framework.
 *
 * @license   MIT
 * @author    Anton Titov (Wolfy-J)
 */
declare(strict_types=1);

namespace Spiral\Encrypter;

use Defuse\Crypto\Crypto;
use Defuse\Crypto\Exception\CryptoException;
use Defuse\Crypto\Exception\EnvironmentIsBrokenException;
use Defuse\Crypto\Key;
use Spiral\Core\Container\InjectableInterface;
use Spiral\Encrypter\Exception\DecryptException;
use Spiral\Encrypter\Exception\EncrypterException;
use Spiral\Encrypter\Exception\EncryptException;

/**
 * Default implementation of spiral encrypter. Facade at top of defuse/php-encryption
 *
 * @see https://github.com/defuse/php-encryption
 */
final class Encrypter implements EncrypterInterface, InjectableInterface
{
    const INJECTOR = EncrypterFactory::class;

    /** @var Key */
    private $key = null;

    /**
     * @param string $key Loads a Key from its encoded form (ANSI).
     */
    public function __construct(string $key)
    {
        try {
            $this->key = Key::loadFromAsciiSafeString($key);
        } catch (CryptoException $e) {
            throw new EncrypterException($e->getMessage(), $e->getCode(), $e);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function withKey(string $key): EncrypterInterface
    {
        $encrypter = clone $this;
        try {
            $encrypter->key = Key::loadFromAsciiSafeString($key);
        } catch (CryptoException $e) {
            throw new EncrypterException($e->getMessage(), $e->getCode(), $e);
        }

        return $encrypter;
    }

    /**
     * {@inheritdoc}
     */
    public function getKey(): string
    {
        try {
            return $this->key->saveToAsciiSafeString();
        } catch (EnvironmentIsBrokenException $e) {
            throw new EncrypterException($e->getMessage(), $e->getCode(), $e);
        }
    }

    /**
     * {@inheritdoc}
     *
     * Data encoded using json_encode method, only supported formats are allowed!
     */
    public function encrypt($data): string
    {
        $packed = json_encode($data);

        try {
            return base64_encode(Crypto::Encrypt($packed, $this->key));
        } catch (\Throwable $e) {
            throw new EncryptException($e->getMessage(), $e->getCode(), $e);
        }
    }

    /**
     * {@inheritdoc}
     *
     * json_decode with assoc flag set to true
     */
    public function decrypt(string $payload)
    {
        try {
            $result = Crypto::Decrypt(
                base64_decode($payload),
                $this->key
            );

            return json_decode($result, true);
        } catch (\Throwable $e) {
            throw new DecryptException($e->getMessage(), $e->getCode(), $e);
        }
    }
}


<?php namespace im\Primitive\Support;

use Exception;

/**
 * Class Arr
 *
 * @package im\Primitive\Support
 * @author Taylor Otwell | refactored Igor Krimerman <i.m.krimerman@gmail.com>
 */
class Arr {

    /**
     * Add an element to an array using "dot" notation if it doesn't exist.
     *
     * @param  array  $array
     * @param  string $key
     * @param  mixed  $value
     *
     * @return array
     */
    public static function add($array, $key, $value)
    {
        if (is_null(static::get($array, $key)))
        {
            static::set($array, $key, $value);
        }

        return $array;
    }

    /**
     * Divide an array into two arrays. One with keys and the other with values.
     *
     * @param  array $array
     *
     * @return array
     */
    public static function divide($array)
    {
        return [array_keys($array), array_values($array)];
    }

    /**
     * Flatten a multi-dimensional associative array with dots.
     *
     * @param  array  $array
     * @param  string $prepend
     *
     * @return array
     */
    public static function dot($array, $prepend = '')
    {
        $results = [];

        foreach ($array as $key => $value)
        {
            if (is_array($value))
            {
                $results = array_merge($results, static::dot($value, $prepend . $key . '.'));
            }
            else
            {
                $results[$prepend . $key] = $value;
            }
        }

        return $results;
    }

    /**
     * Get all of the given array except for a specified array of items.
     *
     * @param  array        $array
     * @param  array|string $keys
     *
     * @return array
     */
    public static function except($array, $keys)
    {
        return array_diff_key($array, array_flip((array) $keys));
    }

    /**
     * Fetch a flattened array of a nested array element.
     *
     * @param  array  $array
     * @param  string $key
     *
     * @return array
     */
    public static function fetch($array, $key)
    {
        $results = [];

        foreach (explode('.', $key) as $segment)
        {
            foreach ($array as $value)
            {
                if (array_key_exists($segment, $value = (array) $value))
                {
                    $results[] = $value[$segment];
                }
            }

            $array = array_values($results);
        }

        return array_values($results);
    }

    /**
     * Return the first element in an array passing a given truth test.
     *
     * @param  array    $array
     * @param  callable $callback
     * @param  mixed    $default
     *
     * @return mixed
     */
    public static function first($array, $callback, $default = null)
    {
        foreach ($array as $key => $value)
        {
            if (call_user_func($callback, $key, $value)) return $value;
        }

        return value($default);
    }

    /**
     * Return the last element in an array passing a given truth test.
     *
     * @param  array    $array
     * @param  callable $callback
     * @param  mixed    $default
     *
     * @return mixed
     */
    public static function last($array, $callback, $default = null)
    {
        return static::first(array_reverse($array), $callback, $default);
    }

    /**
     * Flatten a multi-dimensional array into a single level.
     *
     * @param  array $array
     *
     * @return array
     */
    public static function flatten($array)
    {
        $return = [];

        array_walk_recursive($array, function ($x) use (&$return) { $return[] = $x; });

        return $return;
    }

    /**
     * Remove one or many array items from a given array using "dot" notation.
     *
     * @param  array|object $array
     * @param  array|string $keys
     *
     * @return void
     */
    public static function forget(&$array, $keys)
    {
        $original =& $array;

        foreach ((array) $keys as $key)
        {
            $parts = explode('.', $key);

            while (count($parts) > 1)
            {
                $part = array_shift($parts);

                if ((is_array($array) && isset($array[$part]) && is_array($array[$part])) ||
                    (is_object($array) && isset($array->{$part}) && is_array($array->{$part})))
                {
                    if (is_array($array)) $array =& $array[$part];
                    elseif (is_object($array)) $array =& $array->{$part};
                }
            }

            $part = array_shift($parts);

            if (is_array($array)) unset($array[$part]);
            elseif (is_object($array)) unset($array->{$part});

            // clean up after each pass
            $array =& $original;
        }
    }

    /**
     * Get an item using "dot" notation.
     *
     * @param  array|object  $array
     * @param  string $key
     * @param  mixed  $default
     *
     * @return mixed
     */
    public static function get($array, $key, $default = null)
    {
        if (is_null($key)) return $array;

        if (isset($array[$key])) return $array[$key];

        return _data_get($array, $key, $default);
    }

    /**
     * Check if an item exists using "dot" notation.
     *
     * @param  array|object  $array
     * @param  string $key
     *
     * @return bool
     */
    public static function has($array, $key)
    {
        if (empty($array) || is_null($key)) return false;

        if (array_key_exists($key, $array)) return true;

        return _data_get($array, $key, new Exception('Not Found')) instanceof Exception ? false : true;
    }

    /**
     * Get a subset of the items from the given array.
     *
     * @param  array        $array
     * @param  array|string $keys
     *
     * @return array
     */
    public static function only($array, $keys)
    {
        return array_intersect_key($array, array_flip((array) $keys));
    }

    /**
     * Pluck an array of values from an array.
     *
     * @param  array  $array
     * @param  string $value
     * @param  string $key
     *
     * @return array
     */
    public static function pluck($array, $value, $key = null)
    {
        $results = [];

        foreach ($array as $item)
        {
            if (isset($item[$value]) || isset($item->{$value}))
            {
                $itemValue = is_object($item) ? $item->{$value} : $item[$value];

                // If the key is "null", we will just append the value to the array and keep
                // looping. Otherwise we will key the array using the value of the key we
                // received from the developer. Then we'll return the final array form.
                if (is_null($key))
                {
                    $results[] = $itemValue;
                }
                else
                {
                    $itemKey = is_object($item) ? $item->{$key} : $item[$key];

                    $results[$itemKey] = $itemValue;
                }
            }
        }

        return $results;
    }

    /**
     * Get a value from the array, and remove it.
     *
     * @param  array  $array
     * @param  string $key
     * @param  mixed  $default
     *
     * @return mixed
     */
    public static function pull(&$array, $key, $default = null)
    {
        $value = static::get($array, $key, $default);

        static::forget($array, $key);

        return $value;
    }

    /**
     * Set item to a given key using "dot" notation.
     *
     * If no key is given to the method, the entire array will be replaced.
     *
     * @param  array|object  $array
     * @param  string $key
     * @param  mixed  $value
     *
     * @return array
     */
    public static function set(&$array, $key, $value)
    {
        if (is_null($key)) return $array = $value;

        $keys = explode('.', $key);

        while (count($keys) > 1)
        {
            $key = array_shift($keys);

            // If the key doesn't exist at this depth, we will just create an empty array
            // to hold the next value, allowing us to create the arrays to hold final
            // values at the correct depth. Then we'll keep digging into the array.
            if ((is_array($array) && ( ! isset($array[$key]) || ! is_array($array[$key]))) ||
                (is_object($array) && ( ! isset($array->{$key}) || ! is_array($array->{$key}))))
            {
                if (is_array($array)) $array[$key] = [];
                elseif (is_object($array)) $array->{$key} = [];
            }

            if (is_array($array)) $array =& $array[$key];
            elseif (is_object($array)) $array =& $array->{$key};
        }

        $key = array_shift($keys);

        if (is_array($array)) $array[$key] = $value;
        elseif (is_object($array)) $array->{$key} = $value;

        return $array;
    }

    /**
     * Search value in Traversable and return key using dot notation.
     *
     * @param $array
     * @param $value
     * @param bool $strict
     * @param string $prepend
     * @param null $default
     * @return int|null|string
     */
    public static function search($array, $value, $strict = false, $prepend = '', $default = null)
    {
        foreach ($array as $key => $_value_)
        {
            $key = $prepend === '' ? $key : $prepend.'.'.$key;

            if (($strict && $_value_ === $value) || ( ! $strict && $_value_ == $value))
            {
                return $key;
            }

            if (is_array($_value_) || $_value_ instanceof \Traversable)
            {
                $found = static::search($_value_, $value, $strict, $key, $default);

                if ($found === $default) continue;

                return $found;
            }
        }

        return $default;
    }

    /**
     * Check if not is array or not key exists.
     *
     * @param $array
     * @param $segment
     * @return bool
     */
    protected static function isNotArrayOrNotKeyExists($array, $segment)
    {
        return ! is_array($array) || ! array_key_exists($segment, $array);
    }
}


<?php

/*
 * This file is part of the Ivory Google Map package.
 *
 * (c) Eric GELOEN <geloen.eric@gmail.com>
 *
 * For the full copyright and license information, please read the LICENSE
 * file that was distributed with this source code.
 */

namespace Ivory\GoogleMap\Helper\Extension;

/**
 * Abstract extension.
 *
 * @author GeLo <geloen.eric@gmail.com>
 */
abstract class AbstractExtensionHelper implements ExtensionHelperInterface
{
    /**
     * Renders a library.
     *
     * @param string      $source   The library source.
     * @param string|null $callback The javascript callback.
     *
     * @return string The HTML output.
     */
    protected function renderLibrary($source, $callback = null)
    {
        if ($callback === null) {
            return sprintf('<script type="text/javascript" src="%s"></script>'.PHP_EOL, $source);
        }

        $output = array();

        $output[] = 'var s = document.createElement("script");'.PHP_EOL;
        $output[] = 's.type = "text/javascript";'.PHP_EOL;
        $output[] = 's.async = true;'.PHP_EOL;
        $output[] = sprintf('s.src = "%s";'.PHP_EOL, $source);
        $output[] = sprintf('s.addEventListener("load", function () { %s(); }, false);'.PHP_EOL, $callback);
        $output[] = 'document.getElementsByTagName("head")[0].appendChild(s);'.PHP_EOL;

        return implode('', $output);
    }
}


<?php
/**
 * Apache utilities.
 *
 * @author @jaswrks
 * @copyright WebSharks™
 */
declare (strict_types = 1);
namespace WebSharks\Core\Classes\Core\Utils;

use WebSharks\Core\Classes;
use WebSharks\Core\Interfaces;
use WebSharks\Core\Traits;
#
use WebSharks\Core\Classes\Core\Error;
use WebSharks\Core\Classes\Core\Base\Exception;
#
use function assert as debug;
use function get_defined_vars as vars;

/**
 * Apache utilities.
 *
 * @since 160522 Apache utilities.
 */
class Apache extends Classes\Core\Base\Core
{
    /**
     * Deny public access.
     *
     * @since 160522 Apache utilities.
     *
     * @param string $dir                 Directory to deny access in.
     * @param bool   $allow_static_access Allow static file access?
     *
     * @return int Number of bytes written to file.
     */
    public function htaccessDeny(string $dir, bool $allow_static_access = false): int
    {
        $deny = // `.htaccess` file.
            '<IfModule authz_core_module>'."\n".
            '	Require all denied'."\n".
            '</IfModule>'."\n".
            '<IfModule !authz_core_module>'."\n".
            '	deny from all'."\n".
            '</IfModule>';

        if ($allow_static_access) {
            $deny .= "\n\n".// Allow these file extensions.
            '<FilesMatch "\.(js|css|gif|jpg|png|svg|eot|woff|ttf|html|txt|md)$">'."\n".
            '	<IfModule authz_core_module>'."\n".
            '		Require all granted'."\n".
            '	</IfModule>'."\n".
            '	<IfModule !authz_core_module>'."\n".
            '		allow from all'."\n".
            '	</IfModule>'."\n".
            '</FilesMatch>';
        }
        return (int) file_put_contents($dir.'/.htaccess', $deny);
    }
}


<?php
/**
 * OAuth access token repository.
 *
 * @author @jaswrks
 * @copyright WebSharks™
 */
declare(strict_types=1);
namespace WebSharks\Core\Classes\Core\OAuth\Server\Repositories;

use WebSharks\Core\Classes;
use WebSharks\Core\Interfaces;
use WebSharks\Core\Traits;
#
use WebSharks\Core\Classes\Core\Error;
use WebSharks\Core\Classes\Core\Base\Exception;
#
use function assert as debug;
use function get_defined_vars as vars;
#
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
#
use WebSharks\Core\Classes\Core\OAuth\Server\Entities\ClientEntity;
use WebSharks\Core\Classes\Core\OAuth\Server\Entities\AccessTokenEntity;
#
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;

/**
 * OAuth access token repository.
 *
 * @since 17xxxx
 */
abstract class AccessTokenRepository extends Repository implements AccessTokenRepositoryInterface
{
    /**
     * New access token entity.
     *
     * @param ClientEntityInterface $ClientEntity    Client entity.
     * @param array                 $scopes          Scope entities.
     * @param mixed                 $user_identifier User identifier.
     *
     * @return AccessTokenEntity Access token entity.
     */
    public function getNewToken(ClientEntityInterface $ClientEntity, array $scopes, $user_identifier = null)
    {
        return $this->App->Di->get(AccessTokenEntity::class);
    }

    /*
     * Store access token entity in database.
     *
     * @param AccessTokenEntityInterface $AccessTokenEntity Access token entity.
     *
     * @throws UniqueTokenIdentifierConstraintViolationException::create()
     */
    # abstract public function persistNewAccessToken(AccessTokenEntityInterface $AccessTokenEntity);

    /*
     * Revoke access token.
     *
     * @param string $identifier Identifier.
     */
    # abstract public function revokeAccessToken($identifier);

    /*
     * Access token revoked?
     *
     * @param string $identifier Identifier.
     *
     * @return bool True if revoked.
     */
    # abstract public function isAccessTokenRevoked($identifier);
}


<?php

/*
 * This file is part of the Ivory Google Map package.
 *
 * (c) Eric GELOEN <geloen.eric@gmail.com>
 *
 * For the full copyright and license information, please read the LICENSE
 * file that was distributed with this source code.
 */

namespace Ivory\GoogleMap\Places;

use Ivory\GoogleMap\Assets\AbstractJavascriptVariableAsset;
use Ivory\GoogleMap\Base\Bound;
use Ivory\GoogleMap\Base\Coordinate;
use Ivory\GoogleMap\Exception\PlaceException;

/**
 * Places autocomplete.
 *
 * @author GeLo <geloen.eric@gmail.com>
 */
class Autocomplete extends AbstractJavascriptVariableAsset
{
    /** @var string */
    protected $inputId;

    /** @var \Ivory\GoogleMap\Base\Bound */
    protected $bound;

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

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

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

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

    /** @var boolean */
    protected $async;

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

    /**
     * Creates a place autocomplete.
     */
    public function __construct()
    {
        $this->setPrefixJavascriptVariable('place_autocomplete_');

        $this->inputId = 'place_input';
        $this->inputAttributes = array(
            'type'        => 'text',
            'placeholder' => 'off',
        );

        $this->types = array();
        $this->componentRestrictions = array();

        $this->async = false;
        $this->language = 'en';
    }

    /**
     * Gets the autocomplete input ID.
     *
     * @return string The autocomplete input ID.
     */
    public function getInputId()
    {
        return $this->inputId;
    }

    /**
     * Sets the autocomplete input ID.
     *
     * @param string $inputId The autocomplete input ID.
     *
     * @throws \Ivory\GoogleMap\Exception\PlaceException If the input ID is not a valid string.
     */
    public function setInputId($inputId)
    {
        if (!is_string($inputId) || (strlen($inputId) === 0)) {
            throw PlaceException::invalidAutocompleteInputId();
        }

        $this->inputId = $inputId;
    }

    /**
     * Checks if the autocomplete has a bound.
     *
     * @return boolean TRUE if the autocomplete has a bound else FALSE.
     */
    public function hasBound()
    {
        return $this->bound !== null;
    }

    /**
     * Gets the autocomplete bound.
     *
     * @return \Ivory\GoogleMap\Base\Bound The autocomplete bound.
     */
    public function getBound()
    {
        return $this->bound;
    }

    /**
     * Sets the autocomplete bound.
     *
     * Available prototypes:
     *  - function setBound(Ivory\GoogleMap\Base\Bound $bound = null)
     *  - function setBount(Ivory\GoogleMap\Base\Coordinate $southWest, Ivory\GoogleMap\Base\Coordinate $northEast)
     *  - function setBound(
     *      double $southWestLatitude,
     *      double $southWestLongitude,
     *      double $northEastLatitude,
     *      double $northEastLongitude,
     *      boolean southWestNoWrap = true,
     *      boolean $northEastNoWrap = true
     *  )
     *
     * @throws \Ivory\GoogleMap\Exception\PlaceException If the bound is not valid (prototypes).
     */
    public function setBound()
    {
        $args = func_get_args();

        if (isset($args[0]) && ($args[0] instanceof Bound)) {
            $this->bound = $args[0];
        } elseif ((isset($args[0]) && ($args[0] instanceof Coordinate))
            && (isset($args[1]) && ($args[1] instanceof Coordinate))
        ) {
            if ($this->bound === null) {
                $this->bound = new Bound();
            }

            $this->bound->setSouthWest($args[0]);
            $this->bound->setNorthEast($args[1]);
        } elseif ((isset($args[0]) && is_numeric($args[0]))
            && (isset($args[1]) && is_numeric($args[1]))
            && (isset($args[2]) && is_numeric($args[2]))
            && (isset($args[3]) && is_numeric($args[3]))
        ) {
            if ($this->bound === null) {
                $this->bound = new Bound();
            }

            $this->bound->setSouthWest(new Coordinate($args[0], $args[1]));
            $this->bound->setNorthEast(new Coordinate($args[2], $args[3]));

            if (isset($args[4]) && is_bool($args[4])) {
                $this->bound->getSouthWest()->setNoWrap($args[4]);
            }

            if (isset($args[5]) && is_bool($args[5])) {
                $this->bound->getNorthEast()->setNoWrap($args[5]);
            }
        } elseif (!isset($args[0])) {
            $this->bound = null;
        } else {
            throw PlaceException::invalidAutocompleteBound();
        }
    }

    /**
     * Checks if the autocomplete has types.
     *
     * @return boolean TRUE if the autocomplete has types else FALSE.
     */
    public function hasTypes()
    {
        return !empty($this->types);
    }

    /**
     * Checks if the autocomplete has a specific type.
     *
     * @param string $type The type.
     *
     * @return boolean TRUE if the autocomplete has te specific type else FALSE.
     */
    public function hasType($type)
    {
        return array_search($type, $this->types) !== false;
    }

    /**
     * Gets the autocomplete types.
     *
     * @return array The autocomplete types.
     */
    public function getTypes()
    {
        return $this->types;
    }

    /**
     * Sets the autocomplete types.
     *
     * @param array $types The autocomplete types.
     */
    public function setTypes(array $types)
    {
        $this->types = array();

        foreach ($types as $type) {
            $this->addType($type);
        }
    }

    /**
     * Adds a type to the autocomplete.
     *
     * @param string $type The type to add.
     *
     * @throws \Ivory\GoogleMap\Exception\PlaceException If the type is not valid.
     * @throws \Ivory\GoogleMap\Exception\PlaceException If the type already exists.
     */
    public function addType($type)
    {
        if (!in_array($type, AutocompleteType::getAvailableAutocompleteTypes())) {
            throw PlaceException::invalidAutocompleteType();
        }

        if ($this->hasType($type)) {
            throw PlaceException::autocompleteTypeAlreadyExists($type);
        }

        $this->types[] = $type;
    }

    /**
     * Removes a type from the autocomplete.
     *
     * @param string $type The type to remove.
     *
     * @throws \Ivory\GoogleMap\Exception\PlaceException If the type does not exist.
     */
    public function removeType($type)
    {
        if (!$this->hasType($type)) {
            throw PlaceException::autocompleteTypeDoesNotExist($type);
        }

        $index = array_search($type, $this->types);
        unset($this->types[$index]);
    }

    /**
     * Checks if the autocomplete has component restrictions.
     *
     * @return boolean TRUE if the autocomplete has component restrictions else FALSE.
     */
    public function hasComponentRestrictions()
    {
        return !empty($this->componentRestrictions);
    }

    /**
     * Checks if the autocomplete has a specific component restriction type.
     *
     * @param string $type The component restriction type.
     *
     * @return boolean TRUE if the autocomplete has the specific component restriction type else FALSE.
     */
    public function hasComponentRestriction($type)
    {
        return isset($this->componentRestrictions[$type]);
    }

    /**
     * Gets the component restrictions.
     *
     * @return array The component restrictions.
     */
    public function getComponentRestrictions()
    {
        return $this->componentRestrictions;
    }

    /**
     * Gets a specific component restriction.
     *
     * @param string $type The component restriction type.
     *
     * @throws \Ivory\GoogleMap\Exception\PlaceException If the component restriction type does not exist.
     *
     * @return mixed The component restriction.
     */
    public function getComponentRestriction($type)
    {
        if (!$this->hasComponentRestriction($type)) {
            throw PlaceException::autocompleteComponentRestrictionDoesNotExist($type);
        }

        return $this->componentRestrictions[$type];
    }

    /**
     * Sets the component restrictions.
     *
     * @param array $componentRestrictions The component restrictions.
     */
    public function setComponentRestrictions(array $componentRestrictions)
    {
        $this->componentRestrictions = array();

        foreach ($componentRestrictions as $type => $value) {
            $this->addComponentRestriction($type, $value);
        }
    }

    /**
     * Adds a component restriction.
     *
     * @param string $type  The component restriction type.
     * @param mixed  $value The component restriction value.
     *
     * @throws \Ivory\GoogleMap\Exception\PlaceException If the component restriction type is not supported.
     * @throws \Ivory\GoogleMap\Exception\PlaceException If the component restriction type already exists.
     */
    public function addComponentRestriction($type, $value)
    {
        if (!in_array($type, AutocompleteComponentRestriction::getAvailableAutocompleteComponentRestrictions())) {
            throw PlaceException::invalidAutocompleteComponentRestriction();
        }

        if ($this->hasComponentRestriction($type)) {
            throw PlaceException::autocompleteComponentRestrictionAlreadyExists($type);
        }

        $this->componentRestrictions[$type] = $value;
    }

    /**
     * Removes a component restriction.
     *
     * @param string $type The component restriction.
     *
     * @throws \Ivory\GoogleMap\Exception\PlaceException If the component restriction type does not exists.
     */
    public function removeComponentRestriction($type)
    {
        if (!$this->hasComponentRestriction($type)) {
            throw PlaceException::autocompleteComponentRestrictionDoesNotExist($type);
        }

        unset($this->componentRestrictions[$type]);
    }

    /**
     * Checks if the autocomplete has a value.
     *
     * @return boolean TRUE if the autocomplete has a value else FALSE.
     */
    public function hasValue()
    {
        return $this->value !== null;
    }

    /**
     * Gets the autocomplete value.
     *
     * @return string The autocomplete value.
     */
    public function getValue()
    {
        return $this->value;
    }

    /**
     * Sets the autocomplete value.
     *
     * @param string $value The autocomplete value.
     */
    public function setValue($value)
    {
        $this->value = $value;
    }

    /**
     * Gets the autocomplete input attributes.
     *
     * @return array The autocomplete input attributes.
     */
    public function getInputAttributes()
    {
        return $this->inputAttributes;
    }

    /**
     * Sets the autocomplete input attributes.
     *
     * @param array $inputAttributes The autocomplete input attributes.
     */
    public function setInputAttributes(array $inputAttributes)
    {
        $this->inputAttributes = array();

        foreach ($inputAttributes as $name => $value) {
            $this->setInputAttribute($name, $value);
        }
    }

    /**
     * Sets an autocomplete attribute.
     *
     * You can remove an attribute by setting it to `null`.
     *
     * @param string $name  The attribute name.
     * @param mixed  $value The attribute value.
     */
    public function setInputAttribute($name, $value)
    {
        if ($value === null) {
            if (isset($this->inputAttributes[$name])) {
                unset($this->inputAttributes[$name]);
            }
        } else {
            $this->inputAttributes[$name] = $value;
        }
    }

    /**
     * Checks if the autocomplete is loaded asynchronously.
     *
     * @return boolean TRUE if the autocomplete is loaded asynchronounsly else FALSE.
     */
    public function isAsync()
    {
        return $this->async;
    }

    /**
     * Sets if the autocomplete is loaded asynchronously.
     *
     * @param boolean $async TRUE if the autocomplete is loaded asynchronously else FALSE.
     */
    public function setAsync($async)
    {
        if (!is_bool($async)) {
            throw PlaceException::invalidAutocompleteAsync();
        }

        $this->async = $async;
    }

    /**
     * Gets the autocomplete language
     *
     * @return string The autocomplete language
     */
    public function getLanguage()
    {
        return $this->language;
    }

    /**
     * Sets the autocomplete language.
     *
     * @param string $language The autocomplete language
     */
    public function setLanguage($language)
    {
        $this->language = $language;
    }
}


<?php
//------------------------
//· 调试信息函数
//-------------------------
namespace denha;

class Trace
{
    private static $tracePageTabs  = ['BASE' => '基本', 'FILE' => '文件', 'ERR|NOTIC' => '错误', 'SQL' => 'SQL', 'DEBUG' => '调试'];
    private static $traceErrorType = [0 => '', 1 => 'FATAL ERROR', 2 => 'WARNING', 4 => 'PARSE', 8 => 'NOTICE', 100 => 'SQL'];

    public static $errorInfo = []; //错误信息
    public static $sqlInfo   = []; //sql执行信息

    //执行
    public static function run()
    {
        echo self::showTrace();
    }

    //展示调试信息
    private static function showTrace()
    {
        $trace = [];
        $tabs  = self::$tracePageTabs;
        foreach ($tabs as $name => $title) {
            switch (strtoupper($name)) {
                case 'BASE':
                    $trace[$title] = self::baseInfo();
                    break;
                case 'FILE':
                    $trace[$title] = self::fileInfo();
                    break;
                case 'ERR|NOTIC':
                    $trace[$title] = self::$errorInfo;
                    break;
                case 'SQL':
                    $trace[$title] = self::$sqlInfo;
                    break;
                case 'DEBUG':
                    $trace[$title] = self::baseInfo();
                    break;
                default:
                    $trace[$title] = $name;
                    break;
            }
        }

        ob_start();
        include FARM_PATH . DS . 'trace' . DS . 'debug.html';
        return ob_get_clean();
    }

    //捕获Notice错误信息
    public static function catchNotice($level, $message, $file, $line)
    {
        if ($level) {

            $debugContent = self::getFileContent($file, $line);

            $type = isset(self::$traceErrorType[$level]) ? self::$traceErrorType[$level] : 'unknown';
            $info = $type . ' : ' . $message . ' from ' . $file . ' on ' . $line;
            self::addErrorInfo($info);

            if (config('trace')) {
                $e = array(
                    'message' => $type . ' : ' . $message,
                    'file'    => $file,
                    'line'    => $line,
                );

                return include FARM_PATH . DS . 'trace' . DS . 'error.html';
            }
        }
    }

    //捕获致命错误信息 并显示
    public static function catchError()
    {
        $e = error_get_last();
        if ($e) {

            $debugContent = self::getFileContent($e['file'], $e['line']);

            if (config('debug')) {
                return include FARM_PATH . DS . 'trace' . DS . 'error.html';
            } else {
                //FATAL ERROR 发送到邮箱
                if (config('send_debug_mail')) {
                    $title   = $_SERVER['HTTP_HOST'] . ' 有一个致命错误 ip:' . getIP() . ' ' . $_SERVER['SERVER_PROTOCOL'];
                    $content = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] . PHP_EOL . 'FATAL ERROR : ' . $e['message'] . ' from ' . $e['file'] . ' on line ' . $e['line'];
                    dao('Mail')->send(config('send_mail'), $title, $content);
                }

                header("http/1.1 404 not found");
                header("status: 404 not found");
                return include FARM_PATH . DS . 'trace' . DS . '404.html';
            }
        }
    }

    //捕获未处理的自定义错误信息 并显示
    public static function catchApp($error)
    {
        $e['type']    = 0;
        $e['message'] = $error->getMessage();
        $e['file']    = $error->getFile();
        $e['line']    = $error->getLine();
        $e['trace']   = '';

        $debugContent = self::getFileContent($e['file'], $e['line']);

        if ($error->getTrace()) {
            foreach ($error->getTrace() as $key => $value) {
                $e['trace'] .= ($key + 1) . ' File :' . (!empty($value['file']) ? $value['file'] : '未知地址');
                if (isset($value['class'])) {
                    $e['trace'] .= ' From Class :' . $value['class'];
                }

                if (isset($value['class'])) {
                    $e['trace'] .= ' In Function :' . $value['function'];
                }

                $e['trace'] .= ' on line ' . (!empty($value['line']) ? $value['line'] : '未知行数') . PHP_EOL;
            }
        }

        if (config('trace')) {
            return include FARM_PATH . DS . 'trace' . DS . 'error.html';
        } else {
            header("http/1.1 404 not found");
            header("status: 404 not found");
            return include FARM_PATH . DS . 'trace' . DS . '404.html';
        }
    }

    //增加非致命错误信息记录
    public static function addErrorInfo($data)
    {

        if (!self::$errorInfo) {
            self::$errorInfo[] = $data;
        } else {
            self::$errorInfo = array_merge(self::$errorInfo, (array) $data);
        }
    }

    //增加sql执行信息记录
    public static function addSqlInfo($data)
    {
        if (is_array($data)) {
            $info[] = 'SQL :' . $data['sql'] . ' [' . $data['time'] . 's]';
            if (isset($data['explain'])) {
                foreach ($data['explain'] as $explain) {
                    $info[] = 'EXPLAIN :' . json_encode($explain);
                }
            }
        } else {
            $info[] = $data;
        }

        if (!self::$sqlInfo) {
            self::$sqlInfo = $info;
        } else {
            self::$sqlInfo = array_merge(self::$sqlInfo, (array) $info);
        }
    }

    /** 获取文件代码 */
    private static function getFileContent($file, $line)
    {
        $content = '';

        if (is_file($file)) {

            $fp = new \SplFileObject($file, 'r');
            $fp->seek($i = max($line - 10, 0)); // 转到第N行, seek方法参数从0开始计数

            $count = $line + 10;
            for (; $i <= $count; ++$i) {
                $content .= $i . ' ' . trim($fp->current()) . PHP_EOL; // current()获取当前行内容
                if ($i == $line) {

                }

                $fp->next(); // 下一行
            }

            $content = '<pre>' . htmlspecialchars($content) . '</pre>';

        }

        return $content;
    }

    //获取基本信息
    private static function baseInfo()
    {

        $dbConfig = config('dbConfig', 'db');
        $dbName   = '';
        foreach ($dbConfig as $key => $value) {
            !isset($value['host']) ?: $dbName .= $value['host'] . ' : ';
            !isset($value['name']) ?: $dbName .= $value['name'] . '  / ';
        }

        $base = [
            '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . strip_tags($_SERVER['REQUEST_URI']),
            '运行时间' => number_format(microtime(true) - START_TIME, 6) . ' s',
            '吞吐率'    => number_format(1 / (microtime(true) - START_TIME), 2) . 'req/s',
            '内存开销' => number_format((memory_get_usage() - START_USE_MENUS) / 1024, 2) . ' kb',
            '文件加载' => count(get_included_files()),
            //'缓存信息' => n('cache_read') . ' gets ' . n('cache_write') . ' writes ',
            '配置加载' => count(config()),
            '会话信息' => 'SESSION_ID=' . session_id(),
            '数据库'    => $dbName,
            '磁盘信息' => number_format(DISK_TOTAL_SPACE / 1024 / 1024 / 1024, 3) . ' G (all) / ' . number_format((DISK_TOTAL_SPACE - DISK_FREE_SPACE) / 1024 / 1024 / 1024, 3) . ' G (use) / ' . number_format(DISK_FREE_SPACE / 1024 / 1024 / 1024, 3) . 'G (free)',
        ];

        return $base;
    }

    //获取加载文件
    private static function fileInfo()
    {
        $files = get_included_files();
        $info  = [];

        foreach ($files as $key => $file) {
            $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )';
        }
        return $info;
    }

}