<?php

namespace Phug;

use Phug\Compiler\Locator\FileLocator;

class Optimizer
{
    /**
     * Facade for rendering fallback.
     *
     * @const string
     */
    const FACADE = Phug::class;

    /**
     * Rendering options.
     *
     * @var array
     */
    private $options;

    /**
     * Templates directories.
     *
     * @var array
     */
    private $paths;

    /**
     * Cache directory.
     *
     * @var string
     */
    private $cacheDirectory;

    /**
     * Locator to resolve template file paths.
     *
     * @var FileLocator
     */
    private $locator;

    /**
     * Return a hashed print from input file or content.
     *
     * @param string $input
     *
     * @return string
     */
    private function hashPrint($input)
    {
        // Get the stronger hashing algorithm available to minimize collision risks
        $algorithms = hash_algos();
        $algorithm = $algorithms[0];
        $number = 0;
        foreach ($algorithms as $hashAlgorithm) {
            $lettersLength = substr($hashAlgorithm, 0, 2) === 'md'
                ? 2
                : (substr($hashAlgorithm, 0, 3) === 'sha'
                    ? 3
                    : 0
                );
            if ($lettersLength) {
                $hashNumber = substr($hashAlgorithm, $lettersLength);
                if ($hashNumber > $number) {
                    $number = $hashNumber;
                    $algorithm = $hashAlgorithm;
                }

                continue;
            }
        }

        return rtrim(strtr(base64_encode(hash($algorithm, $input, true)), '+/', '-_'), '=');
    }

    /**
     * Returns true if the path has an expired imports linked.
     *
     * @param $path
     *
     * @return bool
     */
    private function hasExpiredImport($sourcePath, $cachePath)
    {
        $importsMap = $cachePath.'.imports.serialize.txt';

        if (!file_exists($importsMap)) {
            return true;
        }

        $importPaths = unserialize(file_get_contents($importsMap)) ?: [];
        $importPaths[] = $sourcePath;
        $time = filemtime($cachePath);
        foreach ($importPaths as $importPath) {
            if (!file_exists($importPath) || filemtime($importPath) >= $time) {
                // If only one file has changed, expires
                return true;
            }
        }

        // If only no files changed, it's up to date
        return false;
    }

    public function __construct(array $options = [])
    {
        $this->locator = new FileLocator();
        $this->paths = isset($options['paths']) ? $options['paths'] : [];
        if (isset($options['base_dir'])) {
            $this->paths[] = $options['base_dir'];
            unset($options['base_dir']);
            $options['paths'] = $this->paths;
        }
        if (isset($options['basedir'])) {
            $this->paths[] = $options['basedir'];
            unset($options['basedir']);
            $options['paths'] = $this->paths;
        }
        if (isset($options['cache']) && !isset($options['cache_dir'])) {
            $options['cache_dir'] = $options['cache'];
        }
        $this->options = $options;
        $this->cacheDirectory = isset($options['cache_dir']) ? $options['cache_dir'] : '';
    }

    /**
     * Resolve a template file path.
     *
     * @param string $file
     *
     * @return bool|null|string
     */
    public function resolve($file)
    {
        return $this->locator->locate(
            $file,
            $this->paths,
            isset($this->options['extensions'])
                ? $this->options['extensions']
                : ['', '.pug', '.jade']
        );
    }

    /**
     * Returns true is a template file is expired, false else.
     * $cachePath will be set with the template cache file path.
     *
     * @param string $file
     * @param string &$cachePath
     *
     * @return bool
     */
    public function isExpired($file, &$cachePath = null)
    {
        if (isset($this->options['up_to_date_check']) && !$this->options['up_to_date_check']) {
            return false;
        }

        if (!$this->cacheDirectory) {
            return true;
        }

        $sourcePath = $this->resolve($file);
        $cachePath = rtrim($this->cacheDirectory, '\\/').DIRECTORY_SEPARATOR.$this->hashPrint($sourcePath).'.php';

        if (!file_exists($cachePath)) {
            return true;
        }

        return $this->hasExpiredImport($sourcePath, $cachePath);
    }

    /**
     * Display a template.
     *
     * @param string $__pug_file
     * @param array  $__pug_parameters
     */
    public function displayFile($__pug_file, array $__pug_parameters = [])
    {
        if ($this->isExpired($__pug_file, $__pug_cache_file)) {
            if (isset($this->options['render'])) {
                call_user_func($this->options['render'], $__pug_file, $__pug_parameters, $this->options);

                return;
            }
            if (isset($this->options['renderer'])) {
                $this->options['renderer']->displayFile($__pug_file, $__pug_parameters);

                return;
            }
            if (isset($this->options['renderer_class_name'])) {
                $className = $this->options['renderer_class_name'];
                $renderer = new $className($this->options);
                $renderer->displayFile($__pug_file, $__pug_parameters);

                return;
            }
            $facade = isset($this->options['facade'])
                ? $this->options['facade']
                : static::FACADE;
            if (is_callable([$facade, 'displayFile'])) {
                $facade::displayFile($__pug_file, $__pug_parameters, $this->options);

                return;
            }

            throw new \RuntimeException(
                'No valid render method, renderer engine, renderer class or facade provided.'
            );
        }

        if (isset($this->options['shared_variables'])) {
            $__pug_parameters = array_merge($this->options['shared_variables'], $__pug_parameters);
        }
        if (isset($this->options['globals'])) {
            $__pug_parameters = array_merge($this->options['globals'], $__pug_parameters);
        }
        if (isset($this->options['self']) && $this->options['self']) {
            $self = $this->options['self'] === true ? 'self' : strval($this->options['self']);
            $__pug_parameters = [$self => $__pug_parameters];
        }

        extract($__pug_parameters);
        include $__pug_cache_file;
    }

    /**
     * Returns a rendered template file.
     *
     * @param string $file
     * @param array  $parameters
     *
     * @return string
     */
    public function renderFile($file, array $parameters = [])
    {
        ob_start();
        $this->displayFile($file, $parameters);

        return ob_get_clean();
    }

    /**
     * Call an optimizer method statically.
     *
     * @param string $name      method name
     * @param array  $arguments method argument to be passed
     * @param array  $options   options the optimizer will be created with
     *
     * @return mixed
     */
    public static function call($name, array $arguments = [], array $options = [])
    {
        return call_user_func_array([new static($options), $name], $arguments);
    }
}


<?php

namespace Phug\Partial;

use Phug\PhugException;
use Phug\Renderer;
use Phug\Util\ModuleInterface;
use SplObjectStorage;

trait ExtensionsTrait
{
    /**
     * List of global extensions. Class names that add custom behaviors to the engine.
     *
     * @var array
     */
    private static $extensions = [];

    private static function getExtensionsGetters()
    {
        return [
            'includes'            => 'getIncludes',
            'scanners'            => 'getScanners',
            'token_handlers'      => 'getTokenHandlers',
            'node_compilers'      => 'getCompilers',
            'formats'             => 'getFormats',
            'patterns'            => 'getPatterns',
            'filters'             => 'getFilters',
            'keywords'            => 'getKeywords',
            'element_handlers'    => 'getElementHandlers',
            'php_token_handlers'  => 'getPhpTokenHandlers',
            'assignment_handlers' => 'getAssignmentHandlers',
        ];
    }

    private static function resolveExtension($extensionClassName)
    {
        if (!is_string($extensionClassName)) {
            return $extensionClassName;
        }

        static $cache = [];

        if (!isset($cache[$extensionClassName])) {
            $cache[$extensionClassName] = new $extensionClassName();
        }

        return $cache[$extensionClassName];
    }

    private static function getExtensionMethodResult($extensionClassName, $method)
    {
        static $cache = null;

        $extension = static::resolveExtension($extensionClassName);

        if (is_null($cache)) {
            $cache = new SplObjectStorage();
        }

        if (!isset($cache[$extension])) {
            $cache[$extension] = [];
        }

        $methods = $cache[$extension];

        if (!isset($methods[$method])) {
            $methods[$method] = $extension->$method();
            $cache[$extension] = $methods;
        }

        return $methods[$method];
    }

    private static function removeExtensionFromCurrentRenderer($extensionClassName)
    {
        /* @var Renderer $renderer */
        $renderer = self::$renderer;

        if (is_a($extensionClassName, ModuleInterface::class, true)) {
            $renderer->setOption(
                'modules',
                array_filter($renderer->getOption('modules'), function ($module) use ($extensionClassName) {
                    return $module !== $extensionClassName;
                })
            );

            return;
        }

        foreach (['getOptions', 'getEvents'] as $method) {
            static::removeOptions([], static::getExtensionMethodResult($extensionClassName, $method));
        }
        foreach (static::getExtensionsGetters() as $option => $method) {
            static::removeOptions([$option], static::getExtensionMethodResult($extensionClassName, $method));
        }
        $rendererClassName = self::getRendererClassName();
        $renderer->setOptionsDefaults((new $rendererClassName())->getOptions());
    }

    private static function mergeOptions($options, $values)
    {
        foreach ($values as $key => &$value) {
            if (substr($key, 0, 3) === 'on_') {
                if (!is_array($value) || is_callable($value)) {
                    $value = [$value];
                }
                if (isset($options[$key]) && (!is_array($options[$key]) || is_callable($options[$key]))) {
                    $options[$key] = [$options[$key]];
                }
            }
        }

        return array_merge_recursive($options, $values);
    }

    private static function extractExtensionOptions(&$options, $extensionClassName, $methods)
    {
        foreach (['getOptions', 'getEvents'] as $method) {
            $value = static::getExtensionMethodResult($extensionClassName, $method);
            if (!empty($value)) {
                $options = static::mergeOptions($options, $value);
            }
        }
        foreach ($methods as $option => $method) {
            $value = static::getExtensionMethodResult($extensionClassName, $method);
            if (!empty($value)) {
                $options = static::mergeOptions($options, [$option => $value]);
            }
        }
    }

    protected static function normalizeExtensionClassName($name)
    {
        return ltrim('\\', strtolower($name));
    }

    /**
     * Get options from extensions list and default options.
     *
     * @param array $extensions list of extensions instances of class names
     * @param array $options    optional default options to merge with
     *
     * @return array
     */
    public static function getExtensionsOptions(array $extensions, array $options = [])
    {
        $methods = static::getExtensionsGetters();
        foreach ($extensions as $extensionClassName) {
            if (is_a($extensionClassName, ModuleInterface::class, true)) {
                if (!isset($options['modules'])) {
                    $options['modules'] = [];
                }
                $options['modules'][] = $extensionClassName;

                continue;
            }

            static::extractExtensionOptions($options, $extensionClassName, $methods);
        }

        return $options;
    }

    /**
     * Check if an extension is available globally.
     *
     * @param string $extensionClassName
     *
     * @return bool
     */
    public static function hasExtension($extensionClassName)
    {
        return in_array(
            static::normalizeExtensionClassName($extensionClassName),
            array_map(
                [static::class, 'normalizeExtensionClassName'],
                self::$extensions
            )
        );
    }

    /**
     * Add an extension to the Phug facade (will be available in the current renderer instance and next static calls).
     * Throws an exception if the extension is not a valid class name.
     *
     * @param string $extensionClassName
     *
     * @throws PhugException
     */
    public static function addExtension($extensionClassName)
    {
        if (!class_exists($extensionClassName)) {
            throw new PhugException(
                'Invalid '.$extensionClassName.' extension given: '.
                'it must be a class name.'
            );
        }

        if (!static::hasExtension($extensionClassName)) {
            self::$extensions[] = $extensionClassName;

            /* @var Renderer $renderer */
            if ($renderer = self::$renderer) {
                $renderer->setOptionsRecursive(static::getOptions());
            }
        }
    }

    /**
     * Remove an extension from the Phug facade (remove from current renderer instance).
     *
     * @param string $extensionClassName
     */
    public static function removeExtension($extensionClassName)
    {
        if (static::hasExtension($extensionClassName)) {
            if (self::$renderer) {
                self::removeExtensionFromCurrentRenderer($extensionClassName);
                self::$renderer->initCompiler();
            }

            self::$extensions = array_diff(self::$extensions, [$extensionClassName]);
        }
    }

    /**
     * Get extensions list added through the Phug facade.
     *
     * @return array
     */
    public static function getExtensions()
    {
        return self::$extensions;
    }
}


<?php

namespace Phug;

use Phug\Partial\ExtensionsTrait;
use Phug\Partial\FacadeOptionsTrait;

class Phug
{
    use ExtensionsTrait;
    use FacadeOptionsTrait;

    /**
     * List of global filters stored as array where keys are filter names, and values the action callback.
     *
     * @var array
     */
    private static $filters = [];

    /**
     * List of global custom keywords stored as array where keys are keyword names, and values the action callback.
     *
     * @var array
     */
    private static $keywords = [];

    /**
     * The current rendering instance used for ::compile(File,Directory), ::render(File,Directory), ::display(File).
     *
     * @var Renderer
     */
    private static $renderer = null;

    /**
     * Rendering class to be used to create the renderer engine.
     *
     * @var Renderer
     */
    private static $rendererClassName = Renderer::class;

    private static function normalizeFilterName($name)
    {
        return str_replace(' ', '-', strtolower($name));
    }

    private static function normalizeKeywordName($name)
    {
        return str_replace(' ', '-', strtolower($name));
    }

    private static function getOptions(array $options = [])
    {
        $extras = static::getFacadeOptions();
        foreach (['filters', 'keywords'] as $option) {
            $method = 'get'.ucfirst($option);
            $extras[$option] = static::$method();
        }

        return array_merge_recursive(static::getExtensionsOptions(self::$extensions, $extras), $options);
    }

    /**
     * Set the engine class used to render templates.
     *
     * @example Phug::setRendererClassName(\Tale\Pug\Renderer::class)
     * @example Phug::setRendererClassName(\Pug\Pug::class)
     *
     * @param string $rendererClassName class name of the custom renderer engine
     */
    public static function setRendererClassName($rendererClassName)
    {
        self::$rendererClassName = $rendererClassName;
    }

    /**
     * Get the current engine class used to render templates.
     *
     * @return string $rendererClassName class name of the custom renderer engine
     */
    public static function getRendererClassName()
    {
        return self::$rendererClassName;
    }

    /**
     * Cleanup previously set options.
     *
     * @example Phug::removeOptions(['filters'], ['coffee' => null]])
     *
     * @param array $path    option base path
     * @param mixed $options options to remove
     */
    public static function removeOptions($path, $options)
    {
        if (self::$renderer && (empty($path) || self::$renderer->hasOption($path))) {
            if (is_array($options)) {
                foreach ($options as $key => $value) {
                    if (is_int($key)) {
                        $callbacks = self::$renderer->getOption($path);
                        if (!is_array($callbacks) || is_callable($callbacks)) {
                            $callbacks = [$callbacks];
                        }
                        self::$renderer->setOption($path, array_filter(
                            $callbacks,
                            function ($item) use ($value) {
                                return $item !== $value;
                            }
                        ));

                        continue;
                    }

                    static::removeOptions(array_merge($path, [$key]), $value);
                }

                return;
            }

            self::$renderer->unsetOption($path);
        }
    }

    /**
     * Reset all static options, filters and extensions.
     */
    public static function reset()
    {
        static::resetFacadeOptions();
        self::$renderer = null;
        self::$extensions = [];
        self::$filters = [];
        self::$keywords = [];
    }

    /**
     * Get a renderer with global options and argument options merged.
     *
     * @example Phug::getRenderer([])
     *
     * @param array $options
     *
     * @return Renderer
     */
    public static function getRenderer(array $options = [])
    {
        $options = static::getOptions($options);

        if (!self::$renderer) {
            $rendererClassName = self::getRendererClassName();
            self::$renderer = new $rendererClassName($options);
        } elseif (!empty($options)) {
            self::$renderer->reInitOptions($options);
            self::$renderer->getCompiler()->getFormatter()->initFormats();
        }

        return self::$renderer;
    }

    /**
     * Return a rendered Pug template string.
     *
     * @param string $input      pug source
     * @param array  $parameters variables values
     * @param array  $options    custom options
     *
     * @return string
     */
    public static function render($input, array $parameters = [], array $options = [])
    {
        return static::getRenderer($options)->render($input, $parameters);
    }

    /**
     * Return a rendered Pug file.
     *
     * @param string $path       path to template
     * @param array  $parameters variables values
     * @param array  $options    custom options
     *
     * @return string
     */
    public static function renderFile($path, array $parameters = [], array $options = [])
    {
        return static::getRenderer($options)->renderFile($path, $parameters);
    }

    /**
     * Display a rendered Pug template string. By default, it means HTML output to the buffer.
     *
     * @param string $input      pug source
     * @param array  $parameters variables values
     * @param array  $options    custom options
     */
    public static function display($input, array $parameters = [], array $options = [])
    {
        return static::getRenderer($options)->display($input, $parameters);
    }

    /**
     * Display a rendered Pug file. By default, it means HTML output to the buffer.
     *
     * @param string $path       path to template
     * @param array  $parameters variables values
     * @param array  $options    custom options
     */
    public static function displayFile($path, array $parameters = [], array $options = [])
    {
        return static::getRenderer($options)->displayFile($path, $parameters);
    }

    /**
     * Check if a filter is available globally.
     *
     * @param string $name
     *
     * @return bool
     */
    public static function hasFilter($name)
    {
        return isset(self::$filters[self::normalizeFilterName($name)]);
    }

    /**
     * Get a global filter by name.
     *
     * @param string $name
     *
     * @return callable
     */
    public static function getFilter($name)
    {
        return self::$filters[self::normalizeFilterName($name)];
    }

    /**
     * Set a filter to the Phug facade (will be available in the current renderer instance and next static calls).
     * Throws an exception if the filter is not callable and do not have a parse method.
     *
     * @param string          $name
     * @param callable|string $filter
     *
     * @throws PhugException
     */
    public static function setFilter($name, $filter)
    {
        if (!(
            is_callable($filter) ||
            class_exists($filter) ||
            method_exists($filter, 'parse')
        )) {
            throw new PhugException(
                'Invalid '.$name.' filter given: '.
                'it must be a callable or a class name.'
            );
        }

        self::$filters[self::normalizeFilterName($name)] = $filter;

        if (self::$renderer) {
            self::$renderer->setOptionsRecursive(static::getOptions());
        }
    }

    /**
     * Add a filter. Throws an exception if the name is already taken.
     *
     * @param string          $name
     * @param callable|string $filter
     *
     * @throws PhugException
     */
    public static function addFilter($name, $filter)
    {
        $key = self::normalizeFilterName($name);

        if (isset(self::$filters[$key])) {
            throw new PhugException(
                'Filter '.$name.' is already set.'
            );
        }

        self::setFilter($name, $filter);
    }

    /**
     * Replace a filter. Throws an exception if the name is set.
     *
     * @param string          $name
     * @param callable|string $filter
     *
     * @throws PhugException
     */
    public static function replaceFilter($name, $filter)
    {
        $key = self::normalizeFilterName($name);

        if (!isset(self::$filters[$key])) {
            throw new PhugException(
                'Filter '.$name.' is not set.'
            );
        }

        self::setFilter($name, $filter);
    }

    /**
     * Remove a filter from the Phug facade (remove from current renderer instance).
     *
     * @param string $name
     */
    public static function removeFilter($name)
    {
        $key = self::normalizeFilterName($name);

        if (isset(self::$filters[$key])) {
            unset(self::$filters[$key]);

            if (self::$renderer) {
                self::$renderer->unsetOption(['filters', $key]);
            }
        }
    }

    /**
     * Get filters list added through the Phug facade.
     *
     * @return array
     */
    public static function getFilters()
    {
        return self::$filters;
    }

    /**
     * Check if a keyword is available globally.
     *
     * @param string $name
     *
     * @return bool
     */
    public static function hasKeyword($name)
    {
        return isset(self::$keywords[self::normalizeKeywordName($name)]);
    }

    /**
     * Get a global custom keyword by name.
     *
     * @param string $name
     *
     * @return callable
     */
    public static function getKeyword($name)
    {
        return self::$keywords[self::normalizeKeywordName($name)];
    }

    /**
     * Set a keyword to the Phug facade (will be available in the current renderer instance and next static calls).
     * Throws an exception if the keyword is not callable.
     *
     * @param string          $name
     * @param callable|string $keyword
     *
     * @throws PhugException
     */
    public static function setKeyword($name, $keyword)
    {
        if (!is_callable($keyword)) {
            throw new PhugException(
                'Invalid '.$name.' keyword given: '.
                'it must be a callable or a class name.'
            );
        }

        self::$keywords[self::normalizeKeywordName($name)] = $keyword;

        if (self::$renderer) {
            self::$renderer->setOptionsRecursive(static::getOptions());
        }
    }

    /**
     * Add a keyword. Throws an exception if the name is already taken.
     *
     * @param string          $name
     * @param callable|string $keyword
     *
     * @throws PhugException
     */
    public static function addKeyword($name, $keyword)
    {
        $key = self::normalizeKeywordName($name);

        if (isset(self::$keywords[$key])) {
            throw new PhugException(
                'Keyword '.$name.' is already set.'
            );
        }

        self::setKeyword($name, $keyword);
    }

    /**
     * Replace a keyword. Throws an exception if the name is set.
     *
     * @param string          $name
     * @param callable|string $keyword
     *
     * @throws PhugException
     */
    public static function replaceKeyword($name, $keyword)
    {
        $key = self::normalizeKeywordName($name);

        if (!isset(self::$keywords[$key])) {
            throw new PhugException(
                'Keyword '.$name.' is not set.'
            );
        }

        self::setKeyword($name, $keyword);
    }

    /**
     * Remove a keyword from the Phug facade (remove from current renderer instance).
     *
     * @param string $name
     */
    public static function removeKeyword($name)
    {
        $key = self::normalizeKeywordName($name);

        if (isset(self::$keywords[$key])) {
            unset(self::$keywords[$key]);

            if (self::$renderer) {
                self::$renderer->unsetOption(['keywords', $key]);
            }
        }
    }

    /**
     * Get keywords list added through the Phug facade.
     *
     * @return array
     */
    public static function getKeywords()
    {
        return self::$keywords;
    }

    /**
     * Cache a whole directory and return an array with [$successCount, $errorCount, $errorDetails].
     *
     * @param string      $source      input directories containing pug files
     * @param string|null $destination output for compiled PHP files
     *                                 (if array given, will be used as options array)
     * @param array|null  $options     optional options
     *
     * @throws RendererException
     *
     * @return array
     */
    public static function cacheDirectory($source, $destination = null, $options = null)
    {
        if ($destination && !is_array($destination)) {
            $destination = [
                'cache_dir' => $destination,
            ];
        }

        return static::getRenderer(array_merge(
            $options ?: [], // @codeCoverageIgnore
            $destination ?: [] // @codeCoverageIgnore
        ))->cacheDirectory($source);
    }

    /**
     * Same method as cacheDirectory but with textual human-friendly output.
     *
     * @param string      $source      input directories containing pug files
     * @param string|null $destination output for compiled PHP files
     *                                 (if array given, will be used as options array)
     * @param array|null  $options     optional options
     *
     * @throws RendererException
     *
     * @return string
     */
    public static function textualCacheDirectory($source, $destination = null, $options = null)
    {
        list($success, $errors, $errorDetails) = static::cacheDirectory($source, $destination, $options);

        return
            "$success templates cached.\n".
            "$errors templates failed to be cached.\n".
            implode('', array_map(function ($detail) {
                return $detail['inputFile']."\n".
                    $detail['error']->getMessage()."\n".
                    $detail['error']->getTraceAsString();
            }, $errorDetails));
    }

    /**
     * All dynamic methods from the renderer can be called statically with Phug facade.
     *
     * @param $name
     * @param $arguments
     *
     * @return mixed
     */
    public static function __callStatic($name, $arguments)
    {
        if (!self::$renderer && static::isOptionMethod($name)) {
            return static::callOption($name, $arguments);
        }

        return call_user_func_array([static::getRenderer(), $name], $arguments);
    }
}


<?php

namespace Phug;

class Cli
{
    /**
     * The facade class name that the cli application will call the methods on.
     *
     * @var string
     */
    protected $facade;

    /**
     * List of available methods/actions in the cli application.
     *
     * @example
     * [
     *   'myMethod'
     *   'anAlias' => 'myMethod',
     *   'anAction' => function ($facade, $arguments) {
     *     list($value, $factor) = $arguments;
     *
     *     return call_user_func([$facade, 'multiply'], $value, $factor);
     *   },
     * ]
     *
     * @var array
     */
    protected $methods;

    /**
     * Cli application constructor. Needs a facade name and a methods list.
     *
     * @param string $facade
     * @param array  $methods
     */
    public function __construct($facade, array $methods)
    {
        $this->facade = $facade;
        $this->methods = $methods;
    }

    protected function convertToKebabCase($string)
    {
        return preg_replace_callback('/[A-Z]/', function ($match) {
            return '-'.strtolower($match[0]);
        }, $string);
    }

    protected function convertToCamelCase($string)
    {
        return preg_replace_callback('/-([a-z])/', function ($match) {
            return strtoupper($match[1]);
        }, $string);
    }

    protected function execute($facade, $method, $arguments, $outputFile)
    {
        $callable = [$facade, $method];
        $arguments = array_map(function ($argument) {
            return in_array(substr($argument, 0, 1), ['[', '{'])
                ? json_decode($argument, true)
                : $argument;
        }, $arguments);
        if (isset($this->methods[$method])) {
            $method = $this->methods[$method];
            $callable = [$facade, $method];
            if (!is_string($method)) {
                $callable = $method;
                $arguments = [$facade, $arguments];
            }
        }

        $text = call_user_func_array($callable, $arguments);
        if ($outputFile) {
            return file_put_contents($outputFile, $text);
        }

        echo $text;

        return true;
    }

    protected function getNamedArgumentBySpaceDelimiter(array &$arguments, $index, $name)
    {
        if ($arguments[$index] === $name) {
            array_splice($arguments, $index, 1);
            if (isset($arguments[$index])) {
                $value = $arguments[$index];
                array_splice($arguments, $index, 1);

                return $value;
            }
        }

        return false;
    }

    protected function getNamedArgumentByEqualOperator(array &$arguments, $index, $name)
    {
        if (preg_match('/^'.preg_quote($name).'=(.*)$/', $arguments[$index], $match)) {
            array_splice($arguments, $index, 1);

            return $match[1];
        }

        return false;
    }

    protected function getNamedArgument(array &$arguments, array $names)
    {
        foreach ($names as $name) {
            foreach ($arguments as $index => $argument) {
                $value = $this->getNamedArgumentBySpaceDelimiter($arguments, $index, $name) ?:
                    $this->getNamedArgumentByEqualOperator($arguments, $index, $name);
                if ($value) {
                    return $value;
                }
            }
        }

        return false;
    }

    public function getCustomMethods()
    {
        $facade = $this->facade;
        $options = is_callable([$facade, 'getOptions']) ? call_user_func([$facade, 'getOptions']) : [];

        if (isset($options['commands'])) {
            return $options['commands'];
        }

        if (!is_callable([$facade, 'hasOption']) ||
            !call_user_func([$facade, 'hasOption'], 'commands') ||
            !is_callable([$facade, 'getOption'])
        ) {
            return [];
        }

        return call_user_func([$facade, 'getOption'], 'commands') ?: [];
    }

    /**
     * Run the CLI applications with arguments list, return true for a success status, false for an error status.
     *
     * @param $arguments
     *
     * @return bool
     */
    public function run($arguments)
    {
        $outputFile = $this->getNamedArgument($arguments, ['--output-file', '-o']);
        $bootstrapFile = $this->getNamedArgument($arguments, ['--bootstrap', '-b']);
        if ($bootstrapFile) {
            include $bootstrapFile;
        } elseif (file_exists('phugBootstrap.php')) {
            include 'phugBootstrap.php';
        }
        list(, $action) = array_pad($arguments, 2, null);
        $arguments = array_slice($arguments, 2);
        $facade = $this->facade;
        $method = $this->convertToCamelCase($action);
        $customMethods = $this->getCustomMethods();

        if (!$action) {
            echo "You must provide a method.\n";
            $this->listAvailableMethods($customMethods);

            return false;
        }

        if (!in_array($method, iterator_to_array($this->getAvailableMethods($customMethods)))) {
            echo "The method $action is not available as CLI command in the $facade facade.\n";
            $this->listAvailableMethods($customMethods);

            return false;
        }

        return $this->execute($facade, $method, $arguments, $outputFile);
    }

    /**
     * Yield all available methods.
     *
     * @param array|null $customMethods pass custom methods list (calculated from the facade if missing)
     *
     * @return \Generator
     */
    public function getAvailableMethods($customMethods = null)
    {
        foreach ([$this->methods, $customMethods ?: $this->getCustomMethods()] as $methods) {
            foreach ($methods as $method => $action) {
                $method = is_int($method) ? $action : $method;
                if (substr($method, 0, 2) !== '__') {
                    yield $method;
                }
            }
        }
    }

    /**
     * Dump the list of available methods as textual output.
     *
     * @param array|null $customMethods pass custom methods list (calculated from the facade if missing)
     */
    public function listAvailableMethods($customMethods = null)
    {
        echo "Available methods are:\n";
        foreach ($this->getAvailableMethods($customMethods ?: $this->getCustomMethods()) as $method) {
            $action = $this->convertToKebabCase($method);
            $target = isset($this->methods[$method]) ? $this->methods[$method] : $method;
            $key = array_search($target, $this->methods);
            if (is_int($key)) {
                $key = $this->methods[$key];
            }

            echo ' - '.$action.($key && $key !== $method
                    ? ' ('.$this->convertToKebabCase($key).' alias)'
                    : ''
                )."\n";
        }
    }
}


<?php

namespace LittleGiant\SEOEditor\Forms\GridField;

use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Core\Convert;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridField_ColumnProvider;
use SilverStripe\Forms\GridField\GridField_HTMLProvider;
use SilverStripe\Forms\GridField\GridField_URLHandler;
use SilverStripe\Forms\GridField\GridFieldDataColumns;
use SilverStripe\Forms\TextareaField;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\ORM\DB;

/**
 * Class SEOEditorMetaDescriptionColumn
 */
class SEOEditorMetaDescriptionColumn extends GridFieldDataColumns implements
    GridField_ColumnProvider,
    GridField_HTMLProvider,
    GridField_URLHandler
{

    /**
     * Modify the list of columns displayed in the table.
     *
     * @see {@link GridFieldDataColumns->getDisplayFields()}
     * @see {@link GridFieldDataColumns}.
     *
     * @param GridField $gridField
     * @param array - List reference of all column names.
     */
    public function augmentColumns($gridField, &$columns)
    {
        $columns[] = 'MetaDescription';
    }

    /**
     * Names of all columns which are affected by this component.
     *
     * @param GridField $gridField
     * @return array
     */
    public function getColumnsHandled($gridField)
    {
        return [
            'MetaDescription',
        ];
    }

    /**
     * Attributes for the element containing the content returned by {@link getColumnContent()}.
     *
     * @param  GridField $gridField
     * @param  DataObject $record displayed in this row
     * @param  string $columnName
     * @return array
     */
    public function getColumnAttributes($gridField, $record, $columnName)
    {
        $errors = $this->getErrors($record);

        return [
            'class' => count($errors)
                ? 'seo-editor-error ' . implode(' ', $errors)
                : 'seo-editor-valid',
        ];
    }

    /**
     * HTML for the column, content of the  element.
     *
     * @param  GridField $gridField
     * @param  DataObject $record - Record displayed in this row
     * @param  string $columnName
     * @return string - HTML for the column. Return NULL to skip.
     */
    public function getColumnContent($gridField, $record, $columnName)
    {
        $field = new TextareaField('MetaDescription');
        $value = $gridField->getDataFieldValue($record, $columnName);
        $value = $this->formatValue($gridField, $record, $columnName, $value);
        $field->setName($this->getFieldName($field->getName(), $gridField, $record));
        $field->setValue($value);
        $field->setAttribute('data-name', 'MetaDescription');

        return $field->Field() . $this->getErrorMessages();
    }

    /**
     * Additional metadata about the column which can be used by other components,
     * e.g. to set a title for a search column header.
     *
     * @param GridField $gridField
     * @param string $column
     * @return array - Map of arbitrary metadata identifiers to their values.
     */
    public function getColumnMetadata($gridField, $column)
    {
        return [
            'title' => 'MetaDescription',
        ];
    }

    /**
     * Get the errors which are specific to MetaDescription
     *
     * @param DataObject $record
     * @return array
     */
    public function getErrors(DataObject $record)
    {
        $errors = [];

        if (strlen($record->MetaDescription) < 10) {
            $errors[] = 'seo-editor-error-too-short';
        }
        if (strlen($record->MetaDescription) > 160) {
            $errors[] = 'seo-editor-error-too-long';
        }
        if (strlen(SiteTree::get()->filter('MetaDescription', $record->MetaDescription)->count() > 1)) {
            $errors[] = 'seo-editor-error-duplicate';
        }

        return $errors;
    }

    /**
     * Return all the error messages
     *
     * @return string
     */
    public function getErrorMessages()
    {
        return '
' .
            'This meta description is too short. It should be greater than 10 characters long.' .
            'This meta description is too long. It should be less than 160 characters long.' .
            'This meta description is a duplicate. It should be unique.' .
            '
';
    }

    /**
     * Add a class to the gridfield
     *
     * @param $gridField
     * @return array|void
     */
    public function getHTMLFragments($gridField)
    {
        $gridField->addExtraClass('ss-seo-editor');
    }

    /**
     * @param $name
     * @param GridField $gridField
     * @param DataObjectInterface $record
     * @return string
     */
    protected function getFieldName($name, GridField $gridField, DataObjectInterface $record)
    {
        return sprintf(
            '%s[%s][%s]', $gridField->getName(), $record->ID, $name
        );
    }

    /**
     * Return URLs to be handled by this grid field, in an array the same form as $url_handlers.
     * Handler methods will be called on the component, rather than the grid field.
     *
     * @param $gridField
     * @return array
     */
    public function getURLHandlers($gridField)
    {
        return [
            'updateMetaDescription/$ID' => 'handleAction',
        ];
    }

    /**
     * @param $gridField
     * @param $request
     * @return string
     */
    public function handleAction($gridField, $request)
    {
        $data = $request->postVar($gridField->getName());

        foreach ($data as $id => $params) {
            $page = $gridField->getList()->byId((int)$id);

            foreach ($params as $fieldName => $val) {
                $sqlValue = Convert::raw2sql($val);
                $page->$fieldName = $sqlValue;
                DB::query("UPDATE SiteTree SET {$fieldName} = '{$sqlValue}' WHERE ID = {$page->ID}");
                if ($page->isPublished()) {
                    DB::query("UPDATE SiteTree_Live SET {$fieldName} = '{$sqlValue}' WHERE ID = {$page->ID}");
                }

                return json_encode([
                    'type'    => 'success',
                    'message' => $fieldName . ' saved',
                    'errors'  => $this->getErrors($page),
                ]);
            }
        }

        return json_encode([
            'type'    => 'error',
            'message' => 'An error occurred while saving',
        ]);
    }
}


<?php

namespace LittleGiant\SEOEditor\ModelAdmin;

use LittleGiant\SEOEditor\Forms\GridField\SEOEditorMetaDescriptionColumn;
use LittleGiant\SEOEditor\Forms\GridField\SEOEditorMetaTitleColumn;
use LittleGiant\SEOEditor\Forms\SEOEditorCSVLoader;
use SilverStripe\Admin\ModelAdmin;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Forms\CheckboxField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\GridField\GridFieldAddNewButton;
use SilverStripe\Forms\GridField\GridFieldDataColumns;
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
use SilverStripe\Forms\GridField\GridFieldEditButton;
use SilverStripe\Forms\GridField\GridFieldExportButton;
use SilverStripe\Forms\GridField\GridFieldPrintButton;
use SilverStripe\Forms\TextField;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\Filters\PartialMatchFilter;
use SilverStripe\ORM\Search\SearchContext;
use SilverStripe\ORM\SS_List;
use SilverStripe\View\Requirements;

/**
 * Class SEOEditorAdmin
 */
class SEOEditorAdmin extends ModelAdmin
{
    /**
     * @var string
     */
    private static $menu_title = 'SEO Editor';

    /**
     * @var string
     */
    private static $url_segment = 'seo-editor';

    /**
     * @var string
     */
    private static $menu_icon_class = 'font-icon-monitor';

    /**
     * @var array
     */
    private static $managed_models = [
        SiteTree::class
    ];

    /**
     * @var array
     */
    private static $model_importers = [
        SiteTree::class => SEOEditorCSVLoader::class
    ];

    /**
     * @var array
     */
    private static $allowed_actions = [
        'ImportForm'
    ];

    /**
     * @return HTTPResponse|string|void
     */
    public function init()
    {
        parent::init();
        Requirements::css('littlegiant/silverstripe-seo-editor: client/css/seo-editor.css');
        Requirements::javascript('littlegiant/silverstripe-seo-editor: client/javascript/seo-editor.js');
    }

    /**
     * @return SearchContext
     */
    public function getSearchContext()
    {
        $context = parent::getSearchContext();

        $fields = FieldList::create(
            TextField::create('Title', 'Title'),
            TextField::create('MetaTitle', 'MetaTitle'),
            TextField::create('MetaDescription', 'MetaDescription'),
            CheckboxField::create('DuplicatesOnly', 'Duplicates Only'),
            CheckboxField::create('RemoveEmptyMetaTitles', 'Remove Empty MetaTitles'),
            CheckboxField::create('RemoveEmptyMetaDescriptions', 'Remove Empty MetaDescriptions')
        );

        $context->setFields($fields);
        $filters = [
            'Title' => new PartialMatchFilter('Title'),
            'MetaTitle' => new PartialMatchFilter('MetaTitle'),
            'MetaDescription' => new PartialMatchFilter('MetaDescription')
        ];

        $context->setFilters($filters);

        // Namespace fields, for easier detection if a search is present
        foreach ($context->getFields() as $field) $field->setName(sprintf('q[%s]', $field->getName()));
        foreach ($context->getFilters() as $filter) $filter->setFullName(sprintf('q[%s]', $filter->getFullName()));

        return $context;
    }

    /**
     * @param null $id
     * @param null $fields
     * @return Form
     */
    public function getEditForm($id = null, $fields = null)
    {
        $form = parent::getEditForm($id, $fields);

        $grid = $form->Fields()->dataFieldByName('SilverStripe-CMS-Model-SiteTree');
        $gridField = $grid->getConfig();
        if ($gridField) {
            $gridField->removeComponentsByType(GridFieldAddNewButton::class);
            $gridField->removeComponentsByType(GridFieldPrintButton::class);
            $gridField->removeComponentsByType(GridFieldEditButton::class);
            $gridField->removeComponentsByType(GridFieldExportButton::class);
            $gridField->removeComponentsByType(GridFieldDeleteAction::class);

            $gridField->getComponentByType(GridFieldDataColumns::class)->setDisplayFields(
                [
                    'ID' => 'ID',
                    'Title' => 'Title',
                ]
            );

            $gridField->addComponent(
                new GridFieldExportButton(
                    'before',
                    [
                        'ID' => 'ID',
                        'Title' => 'Title',
                        'MetaTitle' => 'MetaTitle',
                        'MetaDescription' => 'MetaDescription'
                    ]
                )
            );

            $gridField->addComponent(new SEOEditorMetaTitleColumn());
            $gridField->addComponent(new SEOEditorMetaDescriptionColumn());
        }

        return $form;
    }

    /**
     * @return Form|bool
     */
    public function ImportForm()
    {
        $form = parent::ImportForm();
        $modelName = $this->modelClass;

        if ($form) {
            $form->Fields()->removeByName("SpecFor{$modelName}");
            $form->Fields()->removeByName("EmptyBeforeImport");
        }

        return $form;
    }

    /**
     * Get the list for the GridField
     *
     * @return SS_List
     */
    public function getList()
    {
        $list = parent::getList();
        $params = $this->request->requestVar('q');

        if (isset($params['RemoveEmptyMetaTitles']) && $params['RemoveEmptyMetaTitles']) {
            $list = $this->removeEmptyAttributes($list, 'MetaTitle');
        }

        if (isset($params['RemoveEmptyMetaDescriptions']) && $params['RemoveEmptyMetaDescriptions']) {
            $list = $this->removeEmptyAttributes($list, 'MetaDescription');
        }

        $list = $this->markDuplicates($list);

        if (isset($params['DuplicatesOnly']) && $params['DuplicatesOnly']) {
            $list = $list->filter('IsDuplicate', true);
        }

        $list = $list->sort('ID');

        return $list;
    }

    /**
     * Mark duplicate attributes
     *
     * @param SS_List $list
     * @return SS_List
     */
    private function markDuplicates($list)
    {
        $duplicates = $this->findDuplicates($list, 'MetaTitle')->map('ID', 'ID')->toArray();
        $duplicateList = new ArrayList();

        foreach ($list as $item) {
            if (in_array($item->ID, $duplicates)) {
                $item->IsDuplicate = true;
                $duplicateList->push($item);
            }
        }

        $duplicates = $this->findDuplicates($list, 'MetaDescription')->map('ID', 'ID')->toArray();
        foreach ($list as $item) {
            if (in_array($item->ID, $duplicates)) {
                $item->IsDuplicate = true;
                if (!$list->byID($item->ID)) {
                    $duplicateList->push($item);
                }
            }
        }

        $duplicateList->merge($list);
        $duplicateList->removeDuplicates();
        return $duplicateList;
    }

    /**
     * Find duplicate attributes within a list
     *
     * @param SS_List $list
     * @param string $type
     * @return SS_List
     */
    private function findDuplicates(SS_List $list, $type)
    {
        $pageAttributes = $list->map('ID', $type)->toArray();

        $potentialDuplicateAttributes = array_unique(
            array_diff_assoc(
                $pageAttributes,
                array_unique($pageAttributes)
            )
        );
        $duplicateAttributes = array_filter($pageAttributes, function ($value) use ($potentialDuplicateAttributes) {
            return in_array($value, $potentialDuplicateAttributes);
        });

        if (!count($duplicateAttributes)) {
            return $list;
        }

        return $list->filter([
            'ID' => array_keys($duplicateAttributes),
        ]);
    }

    /**
     * Remove pages with empty attributes
     *
     * @param SS_List $list
     * @param string $type
     * @return SS_List
     */
    private function removeEmptyAttributes(SS_List $list, $type)
    {
        $pageAttributes = $list->map('ID', $type)->toArray();

        $emptyAttributess = array_map(function ($value) {
            return $value == '';
        }, $pageAttributes);

        if (!count($emptyAttributess)) {
            return $list;
        }

        return $list->filter([
            'ID:not' => array_keys(
                array_filter($emptyAttributess, function ($value) {
                    return $value == 1;
                })
            )
        ]);
    }

}


<?php

namespace LittleGiant\SEOEditor\Forms;

use SilverStripe\Core\Convert;
use SilverStripe\Dev\CsvBulkLoader;
use SilverStripe\ORM\DB;

/**
 * Class SEOEditorCSVLoader
 */
class SEOEditorCSVLoader extends CsvBulkLoader
{

    /**
     * @var array
     */
    public $duplicateChecks = [
        'ID' => 'ID',
    ];

    /**
     * Update the columns needed when importing from CSV
     *
     * @param array $record
     * @param array $columnMap
     * @param BulkLoader_Result $results
     * @param bool $preview
     * @return bool|int
     */
    public function processRecord($record, $columnMap, &$results, $preview = false)
    {
        $page = $this->findExistingObject($record, $columnMap);

        if (!$page || !$page->exists()) {
            return false;
        }

        foreach ($record as $fieldName => $val) {
            if ($fieldName == 'MetaTitle' || $fieldName == 'MetaDescription') {
                $sqlValue = Convert::raw2sql($val);
                DB::query("UPDATE SiteTree SET {$fieldName} = '{$sqlValue}' WHERE ID = {$page->ID}");
                if ($page->isPublished()) {
                    DB::query("UPDATE SiteTree_Live SET {$fieldName} = '{$sqlValue}' WHERE ID = {$page->ID}");
                }
            }
        }

        return $page->ID;
    }

}


<?php

namespace Tightenco\NovaReleases\Http\Middleware;

use Tightenco\NovaReleases\AllReleases;

class Authorize
{
    /**
     * Handle the incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return \Illuminate\Http\Response
     */
    public function handle($request, $next)
    {
        return resolve(AllReleases::class)->authorize($request) ? $next($request) : abort(403);
    }
}


<?php

/**
 * Insert a new value as the element before or after the reference value
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Commands
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Command_InsertToList extends Rediska_Command_InsertToListAbstract
{
    /**
     * Create command
     *
     * @param string  $key            Key name
     * @param string  $position       BEFORE or AFTER
     * @param mixed   $referenceValue Reference value
     * @param mixed   $value          Value
     * @return Rediska_Connection_Exec
     */
    public function create($key, $position, $referenceValue, $value)
    {
        return $this->_create($key, $position, $referenceValue, $value);
    }
}


<?php

/**
 * Delete the specified member from the sorted set by value
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Commands
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Command_DeleteFromSortedSet extends Rediska_Command_Abstract
{
    /**
     * Supported version
     *
     * @var string
     */
    protected $_version = '1.1';

    /**
     * Create command
     *
     * @param string $key    Key name
     * @param mixed  $member Member
     * @return Rediska_Connection_Exec
     */
    public function create($key, $member)
    {
        $connection = $this->_rediska->getConnectionByKeyName($key);

        $member = $this->_rediska->getSerializer()->serialize($member);

        $command = array('ZREM',
                         $this->_rediska->getOption('namespace') . $key,
                         $member);

        return new Rediska_Connection_Exec($connection, $command);
    }

    /**
     * Parse response
     *
     * @param string $response
     * @return boolean
     */
    public function parseResponse($response)
    {
        return (boolean)$response;
    }
}


<?php

/**
 * Abstract class for provide options to Rediska components
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
abstract class Rediska_Options
{
    protected $_options = array();

    /**
     * Exception class name for Rediska setter and getter
     *
     * @var string
     */
    protected $_optionsException = 'Rediska_Exception';

    public function __construct(array $options = array())
    {
        $options = array_merge($this->_options, $options);

        $this->setOptions($options);
    }

    /**
     * Set options array
     *
     * @param array $options Options (see $_options description)
     * @return Rediska_Options
     */
    public function setOptions(array $options)
    {
        foreach($options as $name => $value) {
            $this->setOption($name, $value);
        }

        return $this;
    }

    /**
     * Get associative array of options
     *
     * @return array
     */
    public function getOptions()
    {
        return $this->_options;
    }

    /**
     * Set option
     *
     * @param string $name Name of option
     * @param mixed $value Value of option
     * @return Rediska_Options
     */
    public function setOption($name, $value)
    {
        if (method_exists($this, "set$name")) {
            return call_user_func(array($this, "set$name"), $value);
        } else if (array_key_exists($name, $this->_options)) {
            $this->_options[$name] = $value;
            return $this;
        } else {
            throw new $this->_optionsException("Unknown option '$name'");
        }
    }

    /**
     * Get option
     *
     * @param string $name Name of option
     * @return mixed
     */
    public function getOption($name)
    {
        if (method_exists($this, "get$name")) {
            return call_user_func(array($this, "get$name"));
        } else if (array_key_exists($name, $this->_options)) {
            return $this->_options[$name];
        } else {
            throw new $this->_optionsException("Unknown option '$name'");
        }
    }
}


<?php

/**
 * Delete a key or keys
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Commands
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Command_Delete extends Rediska_Command_Abstract
{
    /**
     * Create command
     *
     * @param string|array $keyOrKeys Key name or array of key names
     * @return Rediska_Connection_Exec
     */
    public function create($keyOrKeys)
    {
        if (is_array($keyOrKeys)) {
            $keys = $keyOrKeys;

            if (empty($keys)) {
               throw new Rediska_Command_Exception('Not present keys for delete');
            }

            $connections = array();
            $keysByConnections = array();
            foreach ($keys as $key) {
                $connection = $this->_rediska->getConnectionByKeyName($key);
                $connectionAlias = $connection->getAlias();
                if (!array_key_exists($connectionAlias, $connections)) {
                    $connections[$connectionAlias] = $connection;
                    $keysByConnections[$connectionAlias] = array();
                }
                $keysByConnections[$connectionAlias][] = $key;
            }

            $commands = array();
            foreach($keysByConnections as $connectionAlias => $keys) {
                $command = array('DEL');
                foreach($keys as $key) {
                    $command[] = $this->_rediska->getOption('namespace') . $key;
                }
                $commands[] = new Rediska_Connection_Exec($connections[$connectionAlias], $command);
            }

            return $commands;
        } else {
            $key = $keyOrKeys;

            $connection = $this->_rediska->getConnectionByKeyName($key);

            $command = array('DEL',
                             $this->_rediska->getOption('namespace') . $key);

            return new Rediska_Connection_Exec($connection, $command);
        }
    }

    /**
     * Parse responses
     *
     * @param array $responses
     * @return integer
     */
    public function parseResponses($responses)
    {
        return array_sum($responses);
    }
}


<?php

/**
 * Return a random key from the key space
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Commands
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Command_GetRandomKey extends Rediska_Command_Abstract
{
    /**
     * Create command
     *
     * @return Rediska_Connection_Exec
     */
    public function create()
    {
        $connections = $this->_rediska->getConnections();
        $index = rand(0, count($connections) - 1);
        $connection = $connections[$index];

        $command = array('RANDOMKEY');

        return new Rediska_Connection_Exec($connection, $command);
    }

    /**
     * Parse response
     *
     * @param string $response
     * @return string|null
     */
    public function parseResponse($response)
    {
        if ($response == '') {
            return null;
        } else {
            if ($this->_rediska->getOption('namespace') != '' && strpos($response, $this->_rediska->getOption('namespace')) === 0) {
                $response = substr($response, strlen($this->_rediska->getOption('namespace')));
            }

            return $response;
        }
    }
}


<?php

/**
 * Publish message to pubsub channel
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Commands
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Command_Publish extends Rediska_Command_Abstract
{
    /**
     * Create command
     *
     * @param array|string $channelOrChannels Channel or array of channels
     * @param mixed        $message           Message
     * @return Rediska_Connection_Exec
     */
    public function create($channelOrChannels, $message)
    {
        $channels = array();
        if (!is_array($channelOrChannels)) {
            $channels = array($channelOrChannels);
        } else {
            $channels = $channelOrChannels;
        }

        // TODO: Param may be a Rediska_PubSub_Channel object

        $execs = array();
        foreach($channels as $channel) {
            $connection = $this->_rediska->getConnectionByKeyName($channel);

            $command = array('PUBLISH');
            $command[] = $this->_rediska->getOption('namespace') . $channel;
            $command[] = $this->_rediska->getSerializer()->serialize($message);

            $execs[] = new Rediska_Connection_Exec($connection, $command);
        }

        return $execs;
    }

    /**
     * Parse response
     *
     * @param array $responses
     * @return integer
     */
    public function parseResponses($responses)
    {
        return array_sum($responses);
    }
}


<?php

/**
 * Returns all the keys matching the glob-style pattern
 * Glob style patterns examples:
 *   h?llo will match hello hallo hhllo
 *   h*llo will match hllo heeeello
 *   h[ae]llo will match hello and hallo, but not hillo
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Commands
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Command_GetKeysByPattern extends Rediska_Command_Abstract
{
    /**
     * Create command
     *
     * @param string $pattern Pattern
     * @return Rediska_Connection_Exec
     */
    public function create($pattern)
    {
        $commands = array();
        $command = array('KEYS',
                         $this->_rediska->getOption('namespace') . $pattern);

        foreach($this->_rediska->getConnections() as $connection) {
            $commands[] = new Rediska_Connection_Exec($connection, $command);
        }

        return $commands;
    }

    /**
     * Parse responses
     *
     * @param array $responses
     * @return array
     */
    public function parseResponses($responses)
    {
        $keys = array();
        foreach($responses as $response) {
            if (!empty($response)) {
                $keys = array_merge($keys, is_array($response) ? $response : explode(' ', $response));
            }
        }

        $keys = array_unique($keys);

        if ($this->_rediska->getOption('namespace') != '') {
            $namespaceLength = strlen($this->_rediska->getOption('namespace'));
            foreach($keys as &$key) {
                if (strpos($key, $this->_rediska->getOption('namespace')) === 0) {
                    $key = substr($key, $namespaceLength);
                }
            }
        }

        return $keys;
    }
}


<?php

/**
 * Rediska profile
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Profiler
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Profiler_Profile
{
    /**
     * Profiler object
     *
     * @var Rediska_Profiler_Interface
     */
    protected $_profiler;

    /**
     * Context
     *
     * @var mixed
     */
    protected $_context;

    /**
     * Start time
     *
     * @var integer|null
     */
    protected $_startTime;

    /**
     * Stop time
     *
     * @var integer|null
     */
    protected $_stopTime;

    /**
     * Constructor
     *
     * @param Rediska_Profiler_Interface $profiler
     * @param mixed $context
     */
    public function __construct(Rediska_Profiler_Interface $profiler, $context)
    {
        $this->_profiler = $profiler;
        $this->_context  = $context;
    }

    /**
     * Start profile
     *
     * @return integer
     */
    public function start()
    {
        $this->_startTime = microtime(true);

        $this->_profiler->startCallback($this);

        return $this->_startTime;
    }

    /**
     * Stop profile
     *
     * @return integer
     */
    public function stop()
    {
        $this->_stopTime = microtime(true);

        $this->_profiler->stopCallBack($this);

        return $this->getElapsedTime();
    }

    /**
     * Reset profile
     *
     * @return boolean
     */
    public function reset()
    {
        $this->_startTime = null;
        $this->_stopTime  = null;

        return true;
    }

    /**
     * Has stopped?
     *
     * @return boolean
     */
    public function hasStopped()
    {
        return $this->_stopTime !== null;
    }

    /**
     * Get context
     *
     * @return mixed
     */
    public function getContext()
    {
        return $this->_context;
    }

    /**
     * Get elapsed time
     *
     * @param ineger[optioanl] $decimals
     * @return integer|false
     */
    public function getElapsedTime($decimals = null)
    {
        if (!$this->hasStopped()) {
            return false;
        }

        $elapsedTime = $this->_stopTime - $this->_startTime;

        if ($decimals) {
            return number_format($elapsedTime, $decimals);
        } else {
            return $elapsedTime;
        }
    }

    /**
     * Magic to string
     *
     * @return string
     */
    public function  __toString()
    {
        return $this->getContext() . ' => ' . $this->getElapsedTime(4);
    }
}


<?php

/**
 * Rediska command abstract class
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Commands
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
abstract class Rediska_Command_Abstract implements Rediska_Command_Interface
{
    const QUEUED = 'QUEUED';

    /**
     * Supported version
     *
     * @var string
     */
    protected $_version = '1.0';

    /**
     * Rediska instance
     *
     * @var Rediska
     */
    protected $_rediska;

    /**
     * Name of command
     *
     * @var string
     */
    protected $_name;

    /**
     * Arguments
     *
     * @var array
     */
    protected $_arguments = array();

    /**
     * Argument names and values
     *
     * @var array
     */
    protected $_argumentNamesAndValues = array();

    /**
     * Arguments name
     *
     * @var array
     */
    static protected $_argumentNames = array();

    /**
     * Execs
     *
     * @var array
     */
    protected $_execs;

    /**
     * Atomic flag for pipelines
     *
     * @var boolean
     */
    protected $_atomic = true;

    /**
     * Command is writed to connection
     *
     * @var unknown_type
     */
    protected $_isWritten = false;

    /**
     * Is queued to transaction
     *
     * @var boolean
     */
    protected $_isQueued = false;

    /**
     * Constructor
     *
     * @param Rediska $rediska   Rediska instance
     * @param string  $name      Command name
     * @param array   $arguments Command arguments
     */
    public function __construct(Rediska $rediska, $name, $arguments = array())
    {
        $this->_rediska   = $rediska;
        $this->_name      = $name;
        $this->_arguments = $arguments;

        $this->_argumentNamesAndValues = $this->_getAndValidateArguments($arguments);

        $this->_throwExceptionIfNotSupported();
    }

    /**
     * Initialize command
     *
     * @return boolean
     */
    public function initialize()
    {
        if ($this->_execs === null) {
            $this->_execs = call_user_func_array(array($this, 'create'), $this->_arguments);

            if (!is_array($this->_execs)) {
                $this->_execs = array($this->_execs);
            }

            return true;
        }

        return false;
    }

    /**
     * Write commands
     *
     * @return boolean
     */
    public function write()
    {
        $this->initialize();

        foreach($this->_execs as $exec) {
            $exec->write();
        }

        $this->_isWritten = true;

        return true;
    }

    /**
     * Read reponses from connection
     *
     * @return array
     */
    public function read()
    {
        $responses = array();

        if (!$this->_isWritten) {
            throw new Rediska_Command_Exception('You need write before');
        }

        foreach ($this->_execs as $exec) {
            $responses[] = $exec->read();
        }

        if (isset($responses[0]) && $responses[0] === self::QUEUED) {
            $this->_isQueued = true;

            return true;
        } else {
            $this->_isWritten = false;
            return $this->parseResponses($responses);
        }
    }

    /**
     * Execute a command
     *
     * @return mixed
     */
    public function execute()
    {
        $this->write();
        return $this->read();
    }

    /**
     * Magic method for execute
     *
     * @return mixed
     */
    public function __invoke()
    {
        return $this->execute();
    }

    /**
     * Parse responses
     *
     * @param array $responses
     * @return mixed
     */
    public function parseResponses($responses)
    {
        foreach($responses as &$response) {
            $response = $this->parseResponse($response);
        }

        if (sizeof($responses) == 1) {
            return $responses[0];
        }
    }

    /**
     * Parse response
     *
     * @param string|array $response
     * @return mixed
     */
    public function parseResponse($response)
    {
        return $response;
    }

    /**
     * Check atomic command
     *
     * @return boolean
     */
    public function isAtomic()
    {
        return $this->_atomic;
    }

    /**
     * Set atomic command flag
     *
     * @param boolean $flag
     * @return Rediska_Command_Abstract
     */
    public function setAtomic($flag = true)
    {
        $this->_atomic = $flag;

        return $this;
    }

    /**
     * Get command name
     *
     * @return string
     */
    public function getName()
    {
        return $this->_name;
    }

    /**
     * Is command queued in transaction
     *
     * @return boolean
     */
    public function isQueued()
    {
        return $this->_isQueued;
    }

    /**
     * Get Rediska
     *
     * @return Rediska
     */
    public function getRediska()
    {
	    return $this->_rediska;
    }

    /**
     * Set Rediska
     *
     * @param Rediska $rediska Rediska object
     * @return Rediska_Command_Abstract
     */
    public function setRediska(Rediska $rediska)
    {
        $this->_rediska = $rediska;

        return $this;
    }

    /**
     * Magic method for get command argument
     *
     * @param string $name
     * @return mixed
     */
    public function __get($name)
    {
        if (array_key_exists($name, $this->_argumentNamesAndValues)) {
            return $this->_argumentNamesAndValues[$name];
        } else {
            throw new Rediska_Command_Exception("Argument '$name' not present for command '{$this->getName()}'");
        }
    }

    /**
     * Magic method for test if has command argument
     *
     * @param string $name
     * @return boolean
     */
    public function __isset($name)
    {
        return isset($this->_argumentNamesAndValues[$name]);
    }

    /**
     * Magic to string
     *
     * @return string
     */
    public function __toString()
    {
        $string = $this->getName() . '(';
        if (!empty($this->_arguments)) {
            $arguments = array_values($this->_arguments);
            $string .= $this->_argumentsToString($arguments);
        }
        $string .= ')';

        return $string;
    }

    /**
     * Convert arguments to string
     *
     * @param string $string
     * @param array $arguments
     * @return string
     */
    protected function _argumentsToString($arguments)
    {
        $strings = array();
        foreach($arguments as $name => $value) {
            $key = !is_integer($name) ? "'$name' => " : '';

            if (is_object($value)) {
                $argument = get_class($value) . ' $' . $name;
            } else if (is_numeric($value)) {
                $argument = $value;
            } else if (is_string($value)) {
                $argument = "'$value'";
            } else if (is_array($value)) {
                $argument = 'array(' . $this->_argumentsToString($value) . ')';
            } else if (is_null($value)) {
                $argument = 'null';
            } else if ($value === true) {
                $argument = 'true';
            } else if ($value === false) {
                $argument = 'false';
            }

            $strings[] = $key . $argument;
        }

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

    /**
     * Get and validate command arguments
     *
     * @param array $arguments
     * @return array
     */
    protected function _getAndValidateArguments($arguments)
    {
        $className = get_class($this);
        if (!isset(self::$_argumentNames[$className])) {
            $reflection = new ReflectionMethod($this, 'create');
            self::$_argumentNames[$className] = array();
            foreach($reflection->getParameters() as $parameter) {
                self::$_argumentNames[$className][] = $parameter;
            }
        }

        $count = 0;
        $argumentNamesAndValues = array();
        foreach(self::$_argumentNames[$className] as $parameter) {
            if (array_key_exists($count, $arguments)) {
                $value = $arguments[$count];
            } else if ($parameter->isOptional()) {
                $value = $parameter->getDefaultValue();
            } else {
                throw new Rediska_Command_Exception("Argument '{$parameter->getName()}' not present for command '$this->_name'");
            }
            $argumentNamesAndValues[$parameter->getName()] = $value;
            $count++;
        }

        return $argumentNamesAndValues;
    }

    /**
     * Throw exception if command not supported by this version of Redis
     *
     * @param string $version
     */
    protected function _throwExceptionIfNotSupported($version = null)
    {
        if (null === $version) {
            $version = $this->_version;
        }

        $redisVersion = $this->_rediska->getOption('redisVersion');

        if (version_compare($version, $redisVersion) == 1) {
            throw new Rediska_Command_Exception("Command '{$this->_name}' requires {$version}+ version of Redis server. Current version is {$redisVersion}. To change it specify 'redisVersion' option.");
        }
    }
}