<?php

namespace KsUtils;

/**
 * Various string utils
 */
class Str
{
    /**
     * Checks if string ends with another string
     * @param  string  $needle Supposed end
     * @param  string  $str    Tested string
     * @return boolean TRUE if string ends with another string, FALSE
     *              otherwise
     */
    public static function endsWith($needle, $str)
    {
        $end = substr($str, -1*strlen($needle));

        return $end == $needle;
    }

    /**
     * Returns a random string
     * @param int    $charsAmount String's length
     * @param string $chars       Chars in the random string. Default is a-z,
     *          digits and "_" (underscore)
     * @return string Random string with specified length
     */
    public static function random($charsAmount, $chars = array())
    {
        if (!$chars) {
            $chars = array(
                'a', 'b', 'c', 'd', 'e',
                'f', 'g', 'h', 'i', 'j',
                'k', 'l', 'm', 'n', 'o',
                'p', 'q', 'r', 's', 't',
                'u', 'v', 'w', 'x', 'y',
                'z', '0', '1', '2', '3',
                '4', '5', '6', '7', '8',
                '9', '_'
            );
        }

        $str = '';

        for ($i=1; $i<=$charsAmount; $i++) {
            $str .= $chars[array_rand($chars)];
        }

        return $str;
    }

    /**
     * Returns substring after last occurance of $char. Search is
     * case insensitive
     * @param  string $char What to search
     * @param  string $str  Where to search
     * @return string Resulting string
     */
    public static function getSubstrAfterLast($char, $str)
    {
        $pos = strripos($str, $char);

        return $pos ? substr($str, $pos+1) : null;
    }

    /**
     * Replaces cyrillic chars to latin
     * @param  string $string Non-latin string
     * @return string Latin string
     */
    public static function translit($string)
    {
        $converter = array(
            'а' => 'a',   'б' => 'b',   'в' => 'v',
            'г' => 'g',   'д' => 'd',   'е' => 'e',
            'ё' => 'e',   'ж' => 'zh',  'з' => 'z',
            'и' => 'i',   'й' => 'y',   'к' => 'k',
            'л' => 'l',   'м' => 'm',   'н' => 'n',
            'о' => 'o',   'п' => 'p',   'р' => 'r',
            'с' => 's',   'т' => 't',   'у' => 'u',
            'ф' => 'f',   'х' => 'h',   'ц' => 'c',
            'ч' => 'ch',  'ш' => 'sh',  'щ' => 'sch',
            'ь' => '\'',  'ы' => 'y',   'ъ' => '\'',
            'э' => 'e',   'ю' => 'yu',  'я' => 'ya',

            'А' => 'A',   'Б' => 'B',   'В' => 'V',
            'Г' => 'G',   'Д' => 'D',   'Е' => 'E',
            'Ё' => 'E',   'Ж' => 'Zh',  'З' => 'Z',
            'И' => 'I',   'Й' => 'Y',   'К' => 'K',
            'Л' => 'L',   'М' => 'M',   'Н' => 'N',
            'О' => 'O',   'П' => 'P',   'Р' => 'R',
            'С' => 'S',   'Т' => 'T',   'У' => 'U',
            'Ф' => 'F',   'Х' => 'H',   'Ц' => 'C',
            'Ч' => 'Ch',  'Ш' => 'Sh',  'Щ' => 'Sch',
            'Ь' => '\'',  'Ы' => 'Y',   'Ъ' => '\'',
            'Э' => 'E',   'Ю' => 'Yu',  'Я' => 'Ya',
            'é' => 'e', 'â' => 'a', 'ô' => 'o', 'è' => 'e',
            "'" => '_', 'ë' => 'e', 'à' => 'a',
            'î' => 'i', 'ê' => 'e', 'û' => 'u',
            'ç' => 'c', 'ï' => 'i', 'ü' => 'u',
            '(' => '', ')' => ''
        );

        return strtr($string, $converter);
    }

    /**
     * Returns a slugabized string (for example in url usage)
     * @param  string $str         Unslugabized string
     * @param  string $defaultChar Replacement for wrong chars
     * @return string Slugabized string. Contains only chars: a-z,0-9,-,_
     */
    public static function slugabize($str, $defaultChar = "")
    {
        $str = mb_strtolower($str, 'UTF-8');
        $str = self::translit($str);

        /*if (!preg_match('|^[A-Za-z0-9\s_\.-]+$|', $str)) {
            die('Unknown symbol in string: ' . $str);
        }*/

        $str = preg_replace('~[^-a-z0-9_]+~u', $defaultChar, $str);

        return trim($str, $defaultChar);
    }

    /**
     * Encodes variable to a json and utf codes into russian chars
     * @param  mixed  $data The value being encoded. Can be any type except a resource.
     * @return string String containing the JSON representation of value
     */
    public static function jsonEncode($data)
    {
        $arrReplaceUtf = array('\u0410', '\u0430','\u0411','\u0431','\u0412','\u0432',
        '\u0413','\u0433','\u0414','\u0434','\u0415','\u0435','\u0401','\u0451','\u0416',
        '\u0436','\u0417','\u0437','\u0418','\u0438','\u0419','\u0439','\u041a','\u043a',
        '\u041b','\u043b','\u041c','\u043c','\u041d','\u043d','\u041e','\u043e','\u041f',
        '\u043f','\u0420','\u0440','\u0421','\u0441','\u0422','\u0442','\u0423','\u0443',
        '\u0424','\u0444','\u0425','\u0445','\u0426','\u0446','\u0427','\u0447','\u0428',
        '\u0448','\u0429','\u0449','\u042a','\u044a','\u042d','\u044b','\u042c','\u044c',
        '\u042d','\u044d','\u042e','\u044e','\u042f','\u044f');

        $arrReplaceCyr = array('А', 'а', 'Б', 'б', 'В', 'в', 'Г', 'г', 'Д', 'д', 'Е', 'е',
        'Ё', 'ё', 'Ж','ж','З','з','И','и','Й','й','К','к','Л','л','М','м','Н','н','О','о',
        'П','п','Р','р','С','с','Т','т','У','у','Ф','ф','Х','х','Ц','ц','Ч','ч','Ш','ш',
        'Щ','щ','Ъ','ъ','Ы','ы','Ь','ь','Э','э','Ю','ю','Я','я');

        $str1 = json_encode($data);
        $str2 = str_replace($arrReplaceUtf, $arrReplaceCyr, $str1);

        return $str2;
    }

    /**
     * Use whitespace to format json string
     * @param  string $json Unformatted json string
     * @return string Formatted json string
     */
    public static function jsonBeautify($json)
    {
        $result      = '';
        $pos         = 0;
        $strLen      = strlen($json);
        $indentStr   = '  ';
        $newLine     = "\n";
        $prevChar    = '';
        $outOfQuotes = true;

        for ($i=0; $i<=$strLen; $i++) {

            // Grab the next character in the string.
            $char = substr($json, $i, 1);

            // Are we inside a quoted string?
            if ($char == '"' && $prevChar != '\\') {
                $outOfQuotes = !$outOfQuotes;

                // If this character is the end of an element,
                // output a new line and indent the next line.
            } elseif (($char == '}' || $char == ']') && $outOfQuotes) {
                $result .= $newLine;
                $pos --;
                for ($j=0; $j<$pos; $j++) {
                    $result .= $indentStr;
                }
            }

            // Add the character to the result string.
            $result .= $char;

            // If the last character was the beginning of an element,
            // output a new line and indent the next line.
            if (($char == ',' || $char == '{' || $char == '[') && $outOfQuotes) {
                $result .= $newLine;
                if ($char == '{' || $char == '[') {
                    $pos ++;
                }

                for ($j = 0; $j < $pos; $j++) {
                    $result .= $indentStr;
                }
            }

            $prevChar = $char;
        }

        return $result;
    }

    /**
     * Removes all underscores and uppercase next char (user_id_key -> userIdKey)
     * @param  string $str Source string
     * @return string Destination string
     */
    public static function undescoresToUpper($str)
    {
        $len = strlen($str);
        $uppFlag = false;

        $ret = array();

        for ($i = 0; $i < $len; $i++) {
            $chr = $str[$i];

            if ($chr == "_") {
                $uppFlag = true;
            } elseif ($uppFlag) {
                $ret[] = strtoupper($chr);
                $uppFlag = false;
            } else {
                $ret[] = $chr;
            }
        }

        return implode("", $ret);
    }

    /**
     * Replaces all uppercase chars to lowercase char with preceeding underscore (userIdKey -> user_id_key)
     * @param  string $str Source string
     * @return string Destination string
     */
    public static function upperToUnderscores($str)
    {
        $ret = array();
        $len = strlen($str);
        $uppChars = "ABCDEFGHIJKLMNOQPRSTUVWXYZ";

        for ($i = 0; $i < $len; $i++) {
            $chr = $str[$i];
            $ret[] = strstr($uppChars, $chr) !== false
                    ? "_" . strtolower($chr)
                    : $chr;
        }

        return implode("", $ret);
    }
}


<?php
/*
 * Copyright (c) 2013 Evispa Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * @author Darius Krištapavičius <darius@evispa.lt>
 */

namespace Evispa\ObjectMigration\VersionPath;

use Evispa\ObjectMigration\Migration\MethodInfo;
use Evispa\ObjectMigration\VersionReader;
use Fhaculty\Graph\Algorithm\ShortestPath\BreadthFirst;
use Fhaculty\Graph\Edge\Directed;
use Fhaculty\Graph\Exception\OutOfBoundsException;
use Fhaculty\Graph\Graph as Graph;

class VersionPathSearch
{
    /**
     * Version reader.
     *
     * @var VersionReader
     */
    private $reader;

    private $annotations;

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

    /**
     * Create edges between versions graph
     *
     * @param Graph  $graph
     * @param string $className
     */
    private function createEdges(Graph $graph, $className)
    {
        $migrationsAnnotations = $this->reader->getClassMigrationMethodInfo($className);

        $parentVertex = $graph->hasVertex($className) ? $graph->getVertex($className) : $graph->createVertex(
            $className
        );

        foreach ($migrationsAnnotations as $migrationsAnnotation) {
            if ($migrationsAnnotation->annotation->from) {
                $fromClass = $migrationsAnnotation->annotation->from;
                $fromVertex = $graph->hasVertex($fromClass) ? $graph->getVertex($fromClass) : $graph->createVertex(
                    $fromClass
                );

                if (!$parentVertex->hasEdgeTo($fromVertex)) {
                    $edge = $fromVertex->createEdgeTo($parentVertex);
                    $this->annotations[$this->getEdgeId($edge)] = $migrationsAnnotation;
                    $this->createEdges($graph, $fromClass);
                }
            }

            if ($migrationsAnnotation->annotation->to) {
                $toClass = $migrationsAnnotation->annotation->to;
                $fromVertex = $graph->hasVertex($toClass) ? $graph->getVertex($toClass) : $graph->createVertex(
                    $toClass
                );

                if (!$parentVertex->hasEdgeTo($fromVertex)) {
                    $edge = $parentVertex->createEdgeTo($fromVertex);
                    $this->annotations[$this->getEdgeId($edge)] = $migrationsAnnotation;
                    $this->createEdges($graph, $toClass);
                }
            }
        }
    }

    /**
     * Get edge id
     *
     * @param \Fhaculty\Graph\Edge\Directed $edge
     *
     * @return string
     */
    private function getEdgeId($edge)
    {
        return $edge->getVertexStart()->getId() . '_' . $edge->getVertexEnd()->getId();
    }

    /**
     * Find shortest path between versions
     *
     * @param string $fromClassName
     * @param string $toClassName
     *
     * @return MethodInfo[]
     */
    public function find($fromClassName, $toClassName)
    {
        $annotations = array();

        $graph = new Graph();

        $this->createEdges($graph, $fromClassName);
        $this->createEdges($graph, $toClassName);

        try {
            $breadFirst = new BreadthFirst($graph->getVertex($fromClassName));
            $edges = $breadFirst->getEdgesTo($graph->getVertex($toClassName));
            /** @var Directed $edge */
            foreach ($edges as $edge) {
                $annotations[] = $this->annotations[$this->getEdgeId($edge)];
            }
        } catch (OutOfBoundsException $e) {
            return null;
        }

        return $annotations;
    }

}


<?php

/*
 * This file is part of the Valkyrja framework.
 *
 * (c) Melech Mizrachi
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Valkyrja\Console\Output;

use InvalidArgumentException;
use RuntimeException;

/**
 * Class Output.
 *
 * @author Melech Mizrachi
 */
class NativeStreamNativeOutput extends NativeOutput implements StreamOutput
{
    /**
     * The stream.
     *
     * @var resource
     */
    protected $stream;

    /**
     * Output constructor.
     *
     * @param OutputFormatter $formatter The output formatter
     * @param resource        $stream    The resource to use as a stream
     *
     * @throws \InvalidArgumentException
     * @throws \RuntimeException
     */
    public function __construct(OutputFormatter $formatter, $stream = null)
    {
        parent::__construct($formatter);

        // Set the stream
        $this->stream = $stream;

        // If there is no stream and the stdout failed
        if (! $this->stream = $this->stream ?? fopen('php://stdout', 'wb')) {
            throw new RuntimeException(
                'Unable to create stdout.'
            );
        }

        // If the stream isn't a valid resource or not a stream resource
        if (! \is_resource($this->stream) || 'stream' !== get_resource_type($this->stream)) {
            throw new InvalidArgumentException(
                'Stream is not a valid stream resource.'
            );
        }
    }

    /**
     * Write a message out to the console.
     *
     * @param string $message
     * @param bool   $newLine
     *
     * @throws \RuntimeException
     *
     * @return void
     */
    protected function writeOut(string $message, bool $newLine): void
    {
        if (
            false === @fwrite($this->stream, $message)
            || ($newLine && (false === @fwrite($this->stream, PHP_EOL)))
        ) {
            // should never happen
            throw new RuntimeException(
                'Unable to write output.'
            );
        }

        fflush($this->stream);
    }
}


<?php
/**
 * OpenWebPresence Support Library - openwebpresence.com
 *
 * @copyright 2001 - 2017, Brian Tafoya.
 * @package   OwpMod
 * @author    Brian Tafoya <btafoya@briantafoya.com>
 * @version   1.0
 * @license   MIT
 * @license   https://opensource.org/licenses/MIT The MIT License
 * @category  OpenWebPresence_Support_Library
 * @link      http://openwebpresence.com OpenWebPresence
 *
 * Copyright (c) 2017, Brian Tafoya
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * This class provides mod data management functionality. (Non session based global storage.)
 */
class OwpMod
{

    /**
     * @var array $mod_data Data storage array
     */
    private  $mod_data = array();

    /**
     * __debugInfo
     *
     * @method mixed __debugInfo()
     * @access public
     *
     * @return mixed mod_data
     *
     * @author  Brian Tafoya <btafoya@briantafoya.com>
     * @version 1.0
     */
    public function __debugInfo()
    {
        return [
                'mod_data' => $this->mod_data,
               ];

    }//end __debugInfo()

    /**
     * __get
     *
     * @method mixed __get($itemName) Add mod data by key
     * @access public
     *
     * @param string $itemName Mod data array key
     *
     * @return mixed Array value
     *
     * @throws InvalidArgumentException Mod data key does not exist, code 20
     *
     * @author  Brian Tafoya <btafoya@briantafoya.com>
     * @version 1.0
     */
    public function __get($itemName)
    {
        return $this->getModDataItem($itemName);

    }//end __get()

    /**
     * __isset
     *
     * @method mixed __isset($itemName) Add mod data by key
     * @access public
     *
     * @param string $itemName Mod data array key
     *
     * @return boolean
     *
     * @author  Brian Tafoya <btafoya@briantafoya.com>
     * @version 1.0
     */
    public function __isset($itemName)
    {
        return isset($this->mod_data[$itemName]);

    }//end __isset()

    /**
     * __set
     *
     * @method mixed __set($itemName, $itemValue) Add mod data by key
     * @access public
     *
     * @param string $itemName  Mod data array key
     * @param mixed  $itemValue Mod data value
     *
     * @return mixed Array value

     * @author  Brian Tafoya <btafoya@briantafoya.com>
     * @version 1.0
     */
    public function __set($itemName, $itemValue)
    {
        return $this->setModDataItem($itemName, $itemValue);

    }//end __set()

    /**
     * __unset
     *
     * @method mixed __unset($itemName) Add mod data by key
     * @access public
     *
     * @param string $itemName Mod data array key
     *
     * @author  Brian Tafoya <btafoya@briantafoya.com>
     * @version 1.0
     */
    public function __unset($itemName)
    {
        if(isset($this->mod_data[$itemName])) {
            unset($this->mod_data[$itemName]);
        }

    }//end __unset()

    /**
     * getModDataItem
     *
     * @method mixed getModDataItem($itemName) Add mod data by key
     * @access public
     *
     * @param string $itemName Mod data array key
     *
     * @return mixed Array value
     *
     * @throws InvalidArgumentException Mod data key does not exist, code 20
     *
     * @author  Brian Tafoya <btafoya@briantafoya.com>
     * @version 1.0
     */
    public function getModDataItem($itemName)
    {
        if(isset($this->mod_data[$itemName])) {
            return $this->mod_data[$itemName];
        } else {
            throw new InvalidArgumentException("Mod data item ".$itemName." does not exist.", 20);
        }

    }//end getModDataItem()

    /**
     * setModDataItem
     *
     * @method mixed setModDataItem($itemName, $itemValue) Add mod data by key
     * @access public
     *
     * @param string $itemName  Mod data array key
     * @param mixed  $itemValue Mod data value
     *
     * @return mixed Array value

     * @author  Brian Tafoya <btafoya@briantafoya.com>
     * @version 1.0
     */
    public function setModDataItem($itemName, $itemValue)
    {
        $this->mod_data[$itemName] = $itemValue;
        return $this->mod_data[$itemName];

    }//end setModDataItem()

}//end class


<?php

namespace Rhubarb\Leaf\Crud\Generic;

use Rhubarb\Crown\Settings\HtmlPageSettings;
use Rhubarb\Crown\String\StringTools;
use Rhubarb\Leaf\Crud\Leaves\CrudView;

abstract class GenericItemView extends CrudView
{
    use URLRestrictedAccess, PageHeaderBar;

    /** @var GenericCrudModel */
    protected $model;

    /** @var string */
    protected $deleteConfirmMessage = 'Are you sure you want to delete this?';

    protected function createSubLeaves()
    {
        parent::createSubLeaves();

        $this->leaves['Save']->addCssClassNames('c-button');

        $this->leaves['Cancel']->addCssClassNames('c-button', 'c-button--light');
        $this->leaves['Delete']->addCssClassNames('c-button', 'c-button--text');
        $this->leaves['Delete']->setConfirmMessage($this->deleteConfirmMessage);

        foreach ($this->getInputs() as $input) {
            $this->registerSubLeaf($input);
        }
    }

    protected function printViewContent()
    {
        $htmlPageSettings = HtmlPageSettings::singleton();

        print <<<HTML

HTML;
        if($this->model->showHeader) {
            $this->getHeaderBarHTML();
        }
        print <<pageTitle}

HTML;

        $this->printInputs();

        if($this->model->showButtonBar)
        {
            $this->printButtonBar();
        }

        print <<<HTML

HTML;
    }

    protected function printButtonBar()
    {
        $delete = $this->model->restModel->isNewRecord() ? '' : $this->leaves['Delete'];
        print <<leaves['Save']}{$this->leaves['Cancel']}

{$delete}

HTML;
    }

    abstract protected function getInputs();

    protected function printInputs()
    {
        foreach ($this->getInputs() as $input) {
            $input = $this->leaves[$input];
            $input->addHtmlAttribute('class', 'c-input');

            if (isset($this->model->errors) && isset($this->model->errors[$input->getName()])) {
                $error = '

                                        ' . $this->model->errors[$input->getName()] . '

';

                unset($this->model->errors[$input->getName()]);
            } else {
                $error = '';
            }

            print <<<HTML

                    getName()}" class="c-label">{$input->getLabel()}:
                    {$input}
                {$error}

HTML;
        }

        if (isset($this->model->errors) && sizeof($this->model->errors) > 0) {
            $error = implode(' ', $this->model->errors);
            print <<<HTML

                            {$error}

HTML;
        }
    }

    /**
     * @return string
     */
    protected function getModelDisplayName()
    {
        if ($this->model->restModel !== null) {
            return StringTools::wordifyStringByUpperCase($this->model->restModel->getModelName());
        }

        return '';
    }

    /**
     * @return array Dictionary of title => url for all pages BETWEEN "home" and this page
     */
    protected function getBreadcrumbItems()
    {
        return [$this->getModelDisplayName() . 's' => '../'];
    }

    /**
     * Should configure the HtmlPageSettings::pageTitle property
     */
    protected function setTitle()
    {
        $settings = HtmlPageSettings::singleton();
        $settings->pageTitle = $this->model->restModel->isNewRecord()
            ? 'New ' . $this->getModelDisplayName()
            : $this->model->restModel->getLabel();
    }
}


<?php
/**
 * Kapitchi Zend Framework 2 Modules
 *
 * @copyright Copyright (c) 2012-2014 Kapitchi Open Source Community (http://kapitchi.com/open-source)
 * @license   http://opensource.org/licenses/MIT MIT
 */

namespace KapApigility;

use ZF\Rest\AbstractResourceListener;

class EntityRepositoryResource extends AbstractResourceListener
{
    /**
     * @var EntityRepositoryInterface
     */
    protected $repository;

    public function __construct(EntityRepositoryInterface $repository)
    {
        $this->setRepository($repository);
    }

    /**
     * Create a resource
     *
     * @param  mixed $data
     * @return ApiProblem|mixed
     */
    public function create($data)
    {
        $data = $this->sanitizeData((array)$data);
        return $this->getRepository()->create($data);
    }

    /**
     * Delete a resource
     *
     * @param  mixed $id
     * @return ApiProblem|mixed
     */
    public function delete($id)
    {
        $ret = $this->getRepository()->remove($id);

        return true;
    }

    /**
     * Fetch a resource
     *
     * @param  mixed $id
     * @return ApiProblem|mixed
     */
    public function fetch($id)
    {
        return $this->getRepository()->find($id);
    }

    /**
     * Fetch all or a subset of resources
     *
     * @param  array $params
     * @return ApiProblem|mixed
     */
    public function fetchAll($params = array())
    {
        $criteria = $params->get('query', []);
        $orderBy = $params->get('order_by', []);

        $adapter = $this->getRepository()->getPaginatorAdapter($criteria, $orderBy);
        return new $this->collectionClass($adapter);
    }

    /**
     * Patch (partial in-place update) a resource
     *
     * @param  mixed $id
     * @param  mixed $data
     * @return ApiProblem|mixed
     */
    public function patch($id, $data)
    {
        $data = $this->sanitizeData((array)$data);
        return $this->getRepository()->update($id, $data);
    }

    /**
     * Update a resource
     *
     * @param  mixed $id
     * @param  mixed $data
     * @return ApiProblem|mixed
     */
    public function update($id, $data)
    {
        $data = $this->sanitizeData((array)$data);
        return $this->getRepository()->update($id, $data);
    }

    /**
     * Removes all underscored properties e.g. _links _embedded
     *
     * @param $data
     * @return array
     */
    protected function sanitizeData($data)
    {
        $ret = [];

        foreach($data as $key => $value) {
            if($key[0] === '_') {
                continue;
            }

            $ret[$key] = $value;
        }

        return $ret;
    }

    /**
     * @param \KapApigility\EntityRepositoryInterface $repository
     */
    public function setRepository($repository)
    {
        $this->repository = $repository;
    }

    /**
     * @return \KapApigility\EntityRepositoryInterface
     */
    public function getRepository()
    {
        return $this->repository;
    }
}


<?php

/*
 * This file is part of the Valkyrja framework.
 *
 * (c) Melech Mizrachi
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Valkyrja\Http;

use DateTime;
use DateTimeZone;
use Valkyrja\Application;
use Valkyrja\Http\Exceptions\InvalidStatusCodeException;
use Valkyrja\Support\Providers\Provides;

/**
 * Class Response.
 *
 * @author Melech Mizrachi
 */
class NativeResponse implements Response
{
    use Provides;

    /**
     * Response headers.
     *
     * @var \Valkyrja\Http\Headers
     */
    protected $headers;

    /**
     * Cache control.
     *
     * @var array
     */
    protected $cacheControl = [];

    /**
     * Response cookies.
     *
     * @var \Valkyrja\Http\Cookies
     */
    protected $cookies;

    /**
     * Response content.
     *
     * @var string
     */
    protected $content;

    /**
     * Response protocol version.
     *
     * @var string
     */
    protected $version;

    /**
     * Response status code.
     *
     * @var int
     */
    protected $statusCode;

    /**
     * Response status text.
     *
     * @var string
     */
    protected $statusText;

    /**
     * Response charset.
     *
     * @var string
     */
    protected $charset;

    /**
     * The view to use.
     *
     * @var \Valkyrja\View\View
     */
    protected $view;

    /**
     * Response constructor.
     *
     * @param string $content [optional] The response content, see setContent()
     * @param int    $status  [optional] The response status code
     * @param array  $headers [optional] An array of response headers
     *
     * @throws \InvalidArgumentException
     */
    public function __construct(string $content = '', int $status = StatusCode::OK, array $headers = [])
    {
        $this->setHeaders($headers);
        $this->setContent($content);
        $this->setStatusCode($status);
        $this->setProtocolVersion();

        $this->cookies = new Cookies();
    }

    /**
     * Create a new response.
     *
     * @param string $content [optional] The response content, see setContent()
     * @param int    $status  [optional] The response status code
     * @param array  $headers [optional] An array of response headers
     *
     * @throws \InvalidArgumentException
     *
     * @return \Valkyrja\Http\Response
     */
    public static function create(string $content = '', int $status = StatusCode::OK, array $headers = []): Response
    {
        return new static($content, $status, $headers);
    }

    /**
     * Sends HTTP headers.
     *
     * @return \Valkyrja\Http\Response
     */
    public function sendHeaders(): Response
    {
        // Headers have already been sent so there's nothing to do here
        if (headers_sent()) {
            return $this;
        }

        // If there is no date header
        if (! $this->headers->has('Date')) {
            // Set it with the current time
            $this->setDateHeader(DateTime::createFromFormat('U', time()));
        }

        // Iterate through all the headers
        foreach ($this->headers->all() as $name => $value) {
            // Set the headers
            header($name . ': ' . $value, false, $this->statusCode);
        }

        // Set the status of the response
        header(
            sprintf(
                'HTTP/%s %s %s',
                $this->version,
                $this->statusCode,
                $this->statusText
            ),
            true,
            $this->statusCode
        );

        // Set the response cookies
        foreach ($this->cookies->all() as $cookie) {
            // If this is a raw cookie
            if ($cookie->isRaw()) {
                // Set the raw cookie
                setrawcookie(
                    $cookie->getName(),
                    $cookie->getValue(),
                    $cookie->getExpire(),
                    $cookie->getPath(),
                    $cookie->getDomain(),
                    $cookie->isSecure(),
                    $cookie->isHttpOnly()
                );
            } // Otherwise
            else {
                // Set the cookie normally
                setcookie(
                    $cookie->getName(),
                    $cookie->getValue(),
                    $cookie->getExpire(),
                    $cookie->getPath(),
                    $cookie->getDomain(),
                    $cookie->isSecure(),
                    $cookie->isHttpOnly()
                );
            }
        }

        return $this;
    }

    /**
     * Sends content for the current web response.
     *
     * @return \Valkyrja\Http\Response
     */
    public function sendContent(): Response
    {
        echo $this->content;

        return $this;
    }

    /**
     * Sends HTTP headers and content.
     *
     * @return \Valkyrja\Http\Response
     */
    public function send(): Response
    {
        // Send headers
        $this->sendHeaders()
            // And content
             ->sendContent();

        // If fastcgi is enabled
        if (\function_exists('fastcgi_finish_request')) {
            // Use it to finish the request
            fastcgi_finish_request();
        } // Otherwise if this isn't a cli request
        elseif ('cli' !== PHP_SAPI) {
            // Use an internal method to finish the request
            static::closeOutputBuffers(0, true);
        }

        return $this;
    }

    /**
     * Returns the Response as an HTTP string.
     *
     * The string representation of the Response is the same as the
     * one that will be sent to the client only if the prepare() method
     * has been called before.
     *
     * @return string The Response as an HTTP string
     *
     * @see prepare()
     */
    public function __toString(): string
    {
        $cookies = '';

        // Iterate through all the cookies
        foreach ($this->cookies->all() as $cookie) {
            // Set the cookie's string
            $cookies .= 'Set-Cookie: '
                . $cookie
                . "\r\n";
        }

        return sprintf(
                'HTTP/%s %s %s',
                $this->version,
                $this->statusCode,
                $this->statusText
            )
            . "\r\n"
            . $cookies
            . $this->headers
            . "\r\n"
            . $this->getContent();
    }

    /**
     * Set the content for the response.
     *
     * @param string $content The response content to set
     *
     * @return \Valkyrja\Http\Response
     */
    public function setContent(string $content): Response
    {
        $this->content = $content;

        return $this;
    }

    /**
     * Get the content for the response.
     *
     * @return string
     */
    public function getContent(): string
    {
        return $this->content;
    }

    /**
     * Sets the HTTP protocol version (1.0 or 1.1).
     *
     * @param string $version [optional] The protocol version to set
     *
     * @return \Valkyrja\Http\Response
     */
    public function setProtocolVersion(string $version = '1.0'): Response
    {
        $this->version = $version;

        return $this;
    }

    /**
     * Gets the HTTP protocol version.
     *
     * @return string
     */
    public function getProtocolVersion(): string
    {
        return $this->version;
    }

    /**
     * Sets the response status code.
     *
     * @param int    $code HTTP status code
     * @param string $text [optional] HTTP status text
     *
     * If the status text is null it will be automatically populated for the
     * known status codes and left empty otherwise.
     *
     * @throws \Valkyrja\Http\Exceptions\InvalidStatusCodeException
     *
     * @return \Valkyrja\Http\Response
     */
    public function setStatusCode(int $code, string $text = null): Response
    {
        $this->statusCode = $code;

        // Check if the status code is valid
        if ($this->isInvalid()) {
            throw new InvalidStatusCodeException(
                sprintf('The HTTP status code "%s" is not valid.', $code)
            );
        }

        // If no text was supplied
        if (null === $text) {
            // Set the status text from the status texts array
            $this->statusText = StatusCode::TEXTS[$code] ?? 'unknown status';

            return $this;
        }

        $this->statusText = $text;

        return $this;
    }

    /**
     * Retrieves the status code for the current web response.
     *
     * @return int Status code
     */
    public function getStatusCode(): int
    {
        return $this->statusCode;
    }

    /**
     * Sets the response charset.
     *
     * @param string $charset Character set
     *
     * @return \Valkyrja\Http\Response
     */
    public function setCharset(string $charset): Response
    {
        $this->charset = $charset;

        return $this;
    }

    /**
     * Retrieves the response charset.
     *
     * @return string Character set
     */
    public function getCharset(): string
    {
        return $this->charset;
    }

    /**
     * Set response headers.
     *
     * @param array $headers [optional] The headers to set
     *
     * @return \Valkyrja\Http\Response
     */
    public function setHeaders(array $headers = []): Response
    {
        // If the headers have no been set yet
        if (null === $this->headers) {
            // Set them to a new Headers collection
            $this->headers = new Headers();
        }

        // Set all the headers with the array provided
        $this->headers->setAll($headers);

        // If there is no cache control header
        if (! $this->headers->has('Cache-Control')) {
            // Set it to an empty string
            $this->headers->set('Cache-Control', '');
        }

        return $this;
    }

    /**
     * Get response headers collection.
     *
     * @return \Valkyrja\Http\Headers
     */
    public function headers(): Headers
    {
        return $this->headers;
    }

    /**
     * Returns the Date header as a DateTime instance.
     *
     * @throws \RuntimeException When the header is not parseable
     *
     * @return DateTime A DateTime instance
     */
    public function getDateHeader(): DateTime
    {
        if (! $this->headers->has('Date')) {
            $this->setDateHeader(DateTime::createFromFormat('U', time()));
        }

        return $this->headers->get('Date');
    }

    /**
     * Sets the Date header.
     *
     * @param DateTime $date A DateTime instance
     *
     * @return \Valkyrja\Http\Response
     */
    public function setDateHeader(DateTime $date): Response
    {
        $date->setTimezone(new DateTimeZone('UTC'));
        $this->headers->set('Date', $date->format('D, d M Y H:i:s') . ' GMT');

        return $this;
    }

    /**
     * Get response cookies collection.
     *
     * @return \Valkyrja\Http\Cookies
     */
    public function cookies(): Cookies
    {
        return $this->cookies;
    }

    /**
     * Set a response cache control.
     *
     * @param string $name  Cache control name
     * @param string $value [optional] Cache control value
     *
     * @return \Valkyrja\Http\Response
     */
    public function addCacheControl(string $name, string $value = null): Response
    {
        $this->cacheControl[$name] = $value;

        return $this;
    }

    /**
     * Get a response cache control.
     *
     * @param string $name Cache control name
     *
     * @return string
     */
    public function getCacheControl(string $name): string
    {
        return $this->hasCacheControl($name)
            ? $this->cacheControl[$name]
            : false;
    }

    /**
     * Check if a response cache control exists.
     *
     * @param string $name Cache control name
     *
     * @return bool
     */
    public function hasCacheControl(string $name): bool
    {
        return isset($this->cacheControl[$name]);
    }

    /**
     * Remove a response cache control.
     *
     * @param string $name Cache control name
     *
     * @return \Valkyrja\Http\Response
     */
    public function removeCacheControl(string $name): Response
    {
        if (! $this->hasCacheControl($name)) {
            return $this;
        }

        unset($this->cacheControl[$name]);

        return $this;
    }

    /**
     * Returns true if the response is worth caching under any circumstance.
     *
     * Responses marked "private" with an explicit Cache-Control directive are
     * considered uncacheable.
     *
     * Responses with neither a freshness lifetime (Expires, max-age) nor cache
     * validator (Last-Modified, ETag) are considered uncacheable.
     *
     * @throws \RuntimeException
     *
     * @return bool true if the response is worth caching, false otherwise
     */
    public function isCacheable(): bool
    {
        if (! \in_array(
            $this->statusCode,
            [
                StatusCode::OK,
                StatusCode::NON_AUTHORITATIVE_INFORMATION,
                StatusCode::MULTIPLE_CHOICES,
                StatusCode::MOVED_PERMANENTLY,
                StatusCode::FOUND,
                StatusCode::NOT_FOUND,
                StatusCode::GONE,
            ],
            true
        )
        ) {
            return false;
        }

        if (
            $this->hasCacheControl('no-store')
            || $this->hasCacheControl('private')
        ) {
            return false;
        }

        return $this->isValidateable() || $this->isFresh();
    }

    /**
     * Returns true if the response is "fresh".
     *
     * Fresh responses may be served from cache without any interaction with
     * the
     * origin. A response is considered fresh when it includes a
     * Cache-Control/max-age indicator or Expires header and the calculated age
     * is less than the freshness lifetime.
     *
     * @throws \RuntimeException
     *
     * @return bool true if the response is fresh, false otherwise
     */
    public function isFresh(): bool
    {
        return $this->getTtl() > 0;
    }

    /**
     * Returns true if the response includes headers that can be used to
     * validate the response with the origin server using a conditional GET
     * request.
     *
     * @return bool true if the response is validateable, false otherwise
     */
    public function isValidateable(): bool
    {
        return $this->headers->has('Last-Modified')
            || $this->headers->has('ETag');
    }

    /**
     * Marks the response as "private".
     *
     * It makes the response ineligible for serving other clients.
     *
     * @return \Valkyrja\Http\Response
     */
    public function setPrivate(): Response
    {
        $this->removeCacheControl('public');
        $this->addCacheControl('private');

        return $this;
    }

    /**
     * Marks the response as "public".
     *
     * It makes the response eligible for serving other clients.
     *
     * @return \Valkyrja\Http\Response
     */
    public function setPublic(): Response
    {
        $this->addCacheControl('public');
        $this->removeCacheControl('private');

        return $this;
    }

    /**
     * Returns the age of the response.
     *
     * @throws \RuntimeException
     *
     * @return int The age of the response in seconds
     */
    public function getAge(): int
    {
        if (null !== $age = $this->headers->get('Age')) {
            return $age;
        }

        return max(
            time() - date('U', strtotime($this->getDateHeader())),
            0
        );
    }

    /**
     * Marks the response stale by setting the Age header to be equal to the
     * maximum age of the response.
     *
     * @throws \RuntimeException
     *
     * @return \Valkyrja\Http\Response
     */
    public function expire(): Response
    {
        if ($this->isFresh()) {
            $this->headers->set('Age', $this->getMaxAge());
        }

        return $this;
    }

    /**
     * Returns the value of the Expires header as a DateTime instance.
     *
     * @return DateTime A DateTime instance or null if the header does not exist
     */
    public function getExpires(): DateTime
    {
        try {
            return $this->headers->get('Expires');
        } catch (\RuntimeException $e) {
            // according to RFC 2616 invalid date formats (e.g. "0" and "-1")
            // must be treated as in the past
            return DateTime::createFromFormat(
                DATE_RFC2822,
                'Sat, 01 Jan 00 00:00:00 +0000'
            );
        }
    }

    /**
     * Sets the Expires HTTP header with a DateTime instance.
     *
     * Passing null as value will remove the header.
     *
     * @param DateTime $date [optional] A DateTime instance or null to remove
     *                       the header
     *
     * @return \Valkyrja\Http\Response
     */
    public function setExpires(DateTime $date = null): Response
    {
        if (null === $date) {
            $this->headers->remove('Expires');
        } else {
            $date = clone $date;
            $date->setTimezone(new DateTimeZone('UTC'));
            $this->headers->set(
                'Expires',
                $date->format('D, d M Y H:i:s') . ' GMT'
            );
        }

        return $this;
    }

    /**
     * Returns the number of seconds after the time specified in the response's
     * Date header when the response should no longer be considered fresh.
     *
     * First, it checks for a s-maxage directive, then a max-age directive, and
     * then it falls back on an expires header. It returns null when no maximum
     * age can be established.
     *
     * @throws \RuntimeException
     *
     * @return int Number of seconds
     */
    public function getMaxAge(): int
    {
        if ($this->hasCacheControl('s-maxage')) {
            return $this->getCacheControl('s-maxage');
        }

        if ($this->hasCacheControl('max-age')) {
            return $this->getCacheControl('max-age');
        }

        if (null !== $this->getExpires()) {
            return date('U', strtotime($this->getExpires())) - date(
                    'U',
                    strtotime(
                        $this->getDateHeader()
                    )
                );
        }

        return 0;
    }

    /**
     * Sets the number of seconds after which the response should no longer be
     * considered fresh.
     *
     * This methods sets the Cache-Control max-age directive.
     *
     * @param int $value Number of seconds
     *
     * @return \Valkyrja\Http\Response
     */
    public function setMaxAge(int $value): Response
    {
        $this->addCacheControl('max-age', $value);

        return $this;
    }

    /**
     * Sets the number of seconds after which the response should no longer be
     * considered fresh by shared caches.
     *
     * This methods sets the Cache-Control s-maxage directive.
     *
     * @param int $value Number of seconds
     *
     * @return \Valkyrja\Http\Response
     */
    public function setSharedMaxAge(int $value): Response
    {
        $this->setPublic();
        $this->addCacheControl('s-maxage', $value);

        return $this;
    }

    /**
     * Returns the response's time-to-live in seconds.
     *
     * It returns null when no freshness information is present in the
     * response.
     *
     * When the responses TTL is <= 0, the response may not be served from
     * cache without first re-validating with the origin.
     *
     * @throws \RuntimeException
     *
     * @return int The TTL in seconds
     */
    public function getTtl(): int
    {
        if (null !== $maxAge = $this->getMaxAge()) {
            return $maxAge - $this->getAge();
        }

        return 0;
    }

    /**
     * Sets the response's time-to-live for shared caches.
     *
     * This method adjusts the Cache-Control/s-maxage directive.
     *
     * @param int $seconds Number of seconds
     *
     * @throws \RuntimeException
     *
     * @return \Valkyrja\Http\Response
     */
    public function setTtl(int $seconds): Response
    {
        $this->setSharedMaxAge($this->getAge() + $seconds);

        return $this;
    }

    /**
     * Sets the response's time-to-live for private/client caches.
     *
     * This method adjusts the Cache-Control/max-age directive.
     *
     * @param int $seconds Number of seconds
     *
     * @throws \RuntimeException
     *
     * @return \Valkyrja\Http\Response
     */
    public function setClientTtl(int $seconds): Response
    {
        $this->setMaxAge($this->getAge() + $seconds);

        return $this;
    }

    /**
     * Returns the Last-Modified HTTP header as a DateTime instance.
     *
     * @throws \RuntimeException When the HTTP header is not parseable
     *
     * @return string A date string
     */
    public function getLastModified(): string
    {
        return $this->headers->get('Last-Modified');
    }

    /**
     * Sets the Last-Modified HTTP header with a DateTime instance.
     *
     * Passing null as value will remove the header.
     *
     * @param DateTime $date [optional] A DateTime instance or null to remove
     *                       the header
     *
     * @return \Valkyrja\Http\Response
     */
    public function setLastModified(DateTime $date = null): Response
    {
        if (null === $date) {
            $this->headers->remove('Last-Modified');
        } else {
            $date = clone $date;
            $date->setTimezone(new DateTimeZone('UTC'));
            $this->headers->set(
                'Last-Modified',
                $date->format('D, d M Y H:i:s') . ' GMT'
            );
        }

        return $this;
    }

    /**
     * Returns the literal value of the ETag HTTP header.
     *
     * @return string The ETag HTTP header or null if it does not exist
     */
    public function getEtag(): string
    {
        return $this->headers->get('ETag');
    }

    /**
     * Sets the ETag value.
     *
     * @param string $etag [optional] The ETag unique identifier or null to
     *                     remove the header
     * @param bool   $weak [optional] Whether you want a weak ETag or not
     *
     * @return \Valkyrja\Http\Response
     */
    public function setEtag(string $etag = null, bool $weak = false): Response
    {
        if (null === $etag) {
            $this->headers->remove('Etag');
        } else {
            if (0 !== strpos($etag, '"')) {
                $etag = '"' . $etag . '"';
            }
            $this->headers->set(
                'ETag',
                (true === $weak ? 'W/' : '') . $etag
            );
        }

        return $this;
    }

    /**
     * Sets the response's cache headers (validation and/or expiration).
     *
     * Available options are: etag, last_modified, max_age, s_maxage, private,
     * and public.
     *
     * @param array $options An array of cache options
     *
     * @return \Valkyrja\Http\Response
     */
    public function setCache(array $options): Response
    {
        if (isset($options['etag'])) {
            $this->setEtag($options['etag']);
        }

        if (isset($options['last_modified'])) {
            $this->setLastModified($options['last_modified']);
        }

        if (isset($options['max_age'])) {
            $this->setMaxAge($options['max_age']);
        }

        if (isset($options['s_maxage'])) {
            $this->setSharedMaxAge($options['s_maxage']);
        }

        if (isset($options['public'])) {
            if ($options['public']) {
                $this->setPublic();
            } else {
                $this->setPrivate();
            }
        }

        if (isset($options['private'])) {
            if ($options['private']) {
                $this->setPrivate();
            } else {
                $this->setPublic();
            }
        }

        return $this;
    }

    /**
     * Modifies the response so that it conforms to the rules defined for a 304
     * status code.
     *
     * This sets the status, removes the body, and discards any headers
     * that MUST NOT be included in 304 responses.
     *
     * @throws \InvalidArgumentException
     *
     * @return \Valkyrja\Http\Response
     *
     * @see http://tools.ietf.org/html/rfc2616#section-10.3.5
     */
    public function setNotModified(): Response
    {
        $this->setStatusCode(StatusCode::NOT_MODIFIED);
        $this->setContent(null);
        $this->headers
            ->remove('Allow')
            ->remove('Content-Encoding')
            ->remove('Content-Language')
            ->remove('Content-Length')
            ->remove('Content-MD5')
            ->remove('Content-Type')
            ->remove('Last-Modified');

        return $this;
    }

    /**
     * Is response invalid?
     *
     * @return bool
     *
     * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
     */
    public function isInvalid(): bool
    {
        return $this->statusCode < StatusCode::CONTINUE
            || $this->statusCode >= StatusCode::NETWORK_AUTHENTICATION_REQUIRED;
    }

    /**
     * Is response informative?
     *
     * @return bool
     */
    public function isInformational(): bool
    {
        return $this->statusCode >= StatusCode::CONTINUE
            && $this->statusCode < StatusCode::OK;
    }

    /**
     * Is response successful?
     *
     * @return bool
     */
    public function isSuccessful(): bool
    {
        return $this->statusCode >= StatusCode::OK
            && $this->statusCode < StatusCode::MULTIPLE_CHOICES;
    }

    /**
     * Is the response a redirect?
     *
     * @return bool
     */
    public function isRedirection(): bool
    {
        return $this->statusCode >= StatusCode::MULTIPLE_CHOICES
            && $this->statusCode < StatusCode::BAD_REQUEST;
    }

    /**
     * Is there a client error?
     *
     * @return bool
     */
    public function isClientError(): bool
    {
        return $this->statusCode >= StatusCode::BAD_REQUEST
            && $this->statusCode < StatusCode::INTERNAL_SERVER_ERROR;
    }

    /**
     * Was there a server side error?
     *
     * @return bool
     */
    public function isServerError(): bool
    {
        return $this->statusCode >= StatusCode::INTERNAL_SERVER_ERROR
            && $this->statusCode < 600;
    }

    /**
     * Is the response OK?
     *
     * @return bool
     */
    public function isOk(): bool
    {
        return StatusCode::OK === $this->statusCode;
    }

    /**
     * Is the response forbidden?
     *
     * @return bool
     */
    public function isForbidden(): bool
    {
        return StatusCode::FORBIDDEN === $this->statusCode;
    }

    /**
     * Is the response a not found error?
     *
     * @return bool
     */
    public function isNotFound(): bool
    {
        return StatusCode::NOT_FOUND === $this->statusCode;
    }

    /**
     * Is the response a redirect of some form?
     *
     * @param string $location [optional] Redirect location
     *
     * @return bool
     */
    public function isRedirect(string $location = null): bool
    {
        return \in_array(
                $this->statusCode,
                [
                    StatusCode::CREATED,
                    StatusCode::MOVED_PERMANENTLY,
                    StatusCode::FOUND,
                    StatusCode::SEE_OTHER,
                    StatusCode::TEMPORARY_REDIRECT,
                    StatusCode::PERMANENT_REDIRECT,
                ],
                true
            )
            && (null === $location
                ?: $location === $this->headers->get('Location'));
    }

    /**
     * Is the response empty?
     *
     * @return bool
     */
    public function isEmpty(): bool
    {
        return \in_array(
            $this->statusCode,
            [
                StatusCode::NO_CONTENT,
                StatusCode::NOT_MODIFIED,
            ],
            true
        );
    }

    /**
     * Cleans or flushes output buffers up to target level.
     *
     * Resulting level can be greater than target level if a non-removable
     * buffer has been encountered.
     *
     * @param int  $targetLevel The target output buffering level
     * @param bool $flush       Whether to flush or clean the buffers
     *
     * @return void
     */
    public static function closeOutputBuffers(int $targetLevel, bool $flush): void
    {
        $status = ob_get_status(true);
        $level  = \count($status);
        // PHP_OUTPUT_HANDLER_* are not defined on HHVM 3.3
        $flags = \defined('PHP_OUTPUT_HANDLER_REMOVABLE')
            ? PHP_OUTPUT_HANDLER_REMOVABLE | ($flush
                ? PHP_OUTPUT_HANDLER_FLUSHABLE
                : PHP_OUTPUT_HANDLER_CLEANABLE)
            : -1;

        while (
            $level-- > $targetLevel && ($s = $status[$level]) &&
            ($s['del'] ?? ! isset($s['flags']) || $flags === ($s['flags'] & $flags))
        ) {
            if ($flush) {
                ob_end_flush();
            } else {
                ob_end_clean();
            }
        }
    }

    /**
     * The items provided by this provider.
     *
     * @return array
     */
    public static function provides(): array
    {
        return [
            Response::class,
        ];
    }

    /**
     * Publish the provider.
     *
     * @param Application $app The application
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public static function publish(Application $app): void
    {
        $app->container()->singleton(
            Response::class,
            new static()
        );
    }
}


<?php

namespace Think;

use Think\Url\UrlSchema;

/**
 * 内置的Dispatcher类
 * 完成URL解析、路由和调度
 * Class Dispatcher
 * @package Think
 */
final class Dispatcher
{

    /**
     * URL映射到控制器
     * @return void
     * @throws BaseException
     */
    public static function dispatch()
    {
        $varPath = C('VAR_PATHINFO');
        $varModule = C('VAR_MODULE');
        $varController = C('VAR_CONTROLLER');
        $varAction = C('VAR_ACTION');
        $urlCase = C('URL_CASE_INSENSITIVE');
        // 判断URL里面是否有兼容模式参数
        if (isset($_GET[$varPath])) {
            $_SERVER['PATH_INFO'] = $_GET[$varPath];
            unset($_GET[$varPath]);
        } elseif (IS_CLI) {
            // CLI模式下 index.php module/controller/action/params/...
            $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
        }

        // 开启子域名部署
        if (C('APP_SUB_DOMAIN_DEPLOY')) {
            $rules = C('APP_SUB_DOMAIN_RULES');
            if (isset($rules[$_SERVER['HTTP_HOST']])) {
                // 完整域名或者IP配置
                define('APP_DOMAIN', $_SERVER['HTTP_HOST']);
                // 当前完整域名
                $rule = $rules[APP_DOMAIN];
            } else {
                if (strpos(C('APP_DOMAIN_SUFFIX'), '.')) {
                    // com.cn net.cn
                    $domain = array_slice(explode('.', $_SERVER['HTTP_HOST']), 0, -3);
                } else {
                    $domain = array_slice(explode('.', $_SERVER['HTTP_HOST']), 0, -2);
                }
                if (!empty($domain)) {
                    $subDomain = implode('.', $domain);
                    // 当前完整子域名
                    define('SUB_DOMAIN', $subDomain);
                    // 二级域名
                    $domain2 = array_pop($domain);
                    if ($domain) {
                        // 存在三级域名
                        $domain3 = array_pop($domain);
                    }
                    if (isset($rules[$subDomain])) {
                        // 子域名
                        $rule = $rules[$subDomain];
                    } elseif (isset($rules['*.'.$domain2]) && !empty($domain3)) {
                        // 泛三级域名
                        $rule = $rules['*.'.$domain2];
                        $panDomain = $domain3;
                    } elseif (isset($rules['*']) && !empty($domain2) && 'www' != $domain2) {
                        // 泛二级域名
                        $rule = $rules['*'];
                        $panDomain = $domain2;
                    }
                }
            }

            if (!empty($rule)) {
                // 子域名部署规则 '子域名'=>array('模块名[/控制器名]','var1=a&var2=b');
                if (is_array($rule)) {
                    list($rule, $vars) = $rule;
                }
                $array = explode('/', $rule);
                // 模块绑定
                define('BIND_MODULE', array_shift($array));
                // 控制器绑定
                if (!empty($array)) {
                    $controller = array_shift($array);
                    if ($controller) {
                        define('BIND_CONTROLLER', $controller);
                    }
                }
                if (isset($vars)) {
                    // 传入参数
                    parse_str($vars, $parms);
                    if (isset($panDomain)) {
                        $pos = array_search('*', $parms);
                        if (false !== $pos) {
                            // 泛域名作为参数
                            $parms[$pos] = $panDomain;
                        }
                    }
                    $_GET = array_merge($_GET, $parms);
                }
            }
        }
        // 分析PATHINFO信息
        if (!isset($_SERVER['PATH_INFO'])) {
            $types = explode(',', C('URL_PATHINFO_FETCH'));
            foreach ($types as $type) {
                if (0 === strpos($type, ':')) {
                    // 支持函数判断
                    $_SERVER['PATH_INFO'] = call_user_func(substr($type, 1));
                    break;
                } elseif (!empty($_SERVER[$type])) {
                    $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ?
                        substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type];
                    break;
                }
            }
        }

        $depr = C('URL_PATHINFO_DEPR');
        define('MODULE_PATHINFO_DEPR', $depr);

        if (empty($_SERVER['PATH_INFO'])) {
            $_SERVER['PATH_INFO'] = '';
            define('__INFO__', '');
            define('__EXT__', '');
        } else {
            define('__INFO__', trim($_SERVER['PATH_INFO'], '/'));
            // URL后缀
            define('__EXT__', strtolower(pathinfo($_SERVER['PATH_INFO'], PATHINFO_EXTENSION)));
            $_SERVER['PATH_INFO'] = __INFO__;
            if (!defined('BIND_MODULE') && (!C('URL_ROUTER_ON') || !Route::check())) {
                if (__INFO__ && C('MULTI_MODULE')) {
                    // 获取模块名
                    $paths = explode($depr, __INFO__, 2);
                    // 允许的模块列表
                    $allowList = C('MODULE_ALLOW_LIST');
                    $module = preg_replace('/\.'.__EXT__.'$/i', '', $paths[0]);
                    if (empty($allowList) || (is_array($allowList) && in_array_case($module, $allowList))) {
                        $_GET[$varModule] = $module;
                        $_SERVER['PATH_INFO'] = isset($paths[1]) ? $paths[1] : '';
                    }
                }
            }
        }

        // URL常量
        define('__SELF__', strip_tags($_SERVER[C('URL_REQUEST_URI')]));

        // 获取模块名称
        define('MODULE_NAME', defined('BIND_MODULE') ? BIND_MODULE : static::getModule($varModule));

        // 检测模块是否存在
        if (MODULE_NAME && (defined('BIND_MODULE') || !in_array_case(
                    MODULE_NAME,
                    C('MODULE_DENY_LIST')
                )) && is_dir(APP_PATH.MODULE_NAME)
        ) {
            // 定义当前模块路径
            define('MODULE_PATH', APP_PATH.MODULE_NAME.'/');
            // 定义当前模块的模版缓存路径
            C('CACHE_PATH', CACHE_PATH.MODULE_NAME.'/');
            // 定义当前模块的日志目录
            C('LOG_PATH', realpath(LOG_PATH).'/'.MODULE_NAME.'/');

            // 模块检测
            Hook::listen('module_check');

            // 加载模块配置文件
            if (is_file(MODULE_PATH.'Conf/config.php')) {
                C(load_config(MODULE_PATH.'Conf/config.php'));
            }

            // 加载模块tags文件定义
            if (is_file(MODULE_PATH.'Conf/tags.php')) {
                $moduleBehaviors = include MODULE_PATH.'Conf/tags.php';
                is_array($moduleBehaviors) && Hook::import($moduleBehaviors);
            }
            // 加载模块函数文件
            if (is_file(MODULE_PATH.'Common/function.php')) {
                include MODULE_PATH.'Common/function.php';
            }

            $urlCase = C('URL_CASE_INSENSITIVE');
            // 加载模块的扩展配置文件
            load_ext_file(MODULE_PATH);
        } else {
            throw new BaseException(L('_MODULE_NOT_EXIST_').':'.MODULE_NAME);
        }

        if (!defined('__APP__')) {
            $urlMode = C('URL_MODEL');
            // 兼容模式判断
            if ($urlMode == UrlSchema::COMPAT) {
                define('PHP_FILE', _PHP_FILE_.'?'.$varPath.'=');
            } elseif ($urlMode == UrlSchema::REWRITE) {
                $url = dirname(_PHP_FILE_);
                if ($url == '/' || $url == '\\') {
                    $url = '';
                }
                define('PHP_FILE', $url);
            } else {
                define('PHP_FILE', _PHP_FILE_);
            }
            // 当前应用地址
            define('__APP__', strip_tags(PHP_FILE));
        }
        // 模块URL地址
        $moduleName = MODULE_NAME;
        define(
            '__MODULE__',
            (defined('BIND_MODULE') || !C('MULTI_MODULE')) ? __APP__ : __APP__.'/'.($urlCase ? strtolower(
                    $moduleName
                ) : $moduleName)
        );

        if ('' != $_SERVER['PATH_INFO'] && (!C('URL_ROUTER_ON') || !Route::check())) {
            // 检测路由规则 如果没有则按默认规则调度URL
            Hook::listen('path_info');
            // 检查禁止访问的URL后缀
            if (C('URL_DENY_SUFFIX') && preg_match(
                    '/\.('.trim(C('URL_DENY_SUFFIX'), '.').')$/i',
                    $_SERVER['PATH_INFO']
                )
            ) {
                send_http_status(404);
                return;
            }

            // 去除URL后缀
            $_SERVER['PATH_INFO'] = preg_replace(
                C('URL_HTML_SUFFIX') ? '/\.('.trim(C('URL_HTML_SUFFIX'), '.').')$/i' : '/\.'.__EXT__.'$/i',
                '',
                $_SERVER['PATH_INFO']
            );

            $depr = C('URL_PATHINFO_DEPR');
            $paths = explode($depr, trim($_SERVER['PATH_INFO'], $depr));

            // 获取控制器
            if (!defined('BIND_CONTROLLER')) {
                // 控制器层次
                if (C('CONTROLLER_LEVEL') > 1) {
                    $_GET[$varController] = implode('/', array_slice($paths, 0, C('CONTROLLER_LEVEL')));
                    $paths = array_slice($paths, C('CONTROLLER_LEVEL'));
                } else {
                    $_GET[$varController] = array_shift($paths);
                }
            }
            // 获取操作
            $_GET[$varAction] = array_shift($paths);
            // 解析剩余的URL参数
            $var = [];
            if (C('URL_PARAMS_BIND') && 1 == C('URL_PARAMS_BIND_TYPE')) {
                // URL参数按顺序绑定变量
                $var = $paths;
            } else {
                preg_replace_callback(
                    '/(\w+)\/([^\/]+)/',
                    function ($match) use (&$var) {
                        $var[$match[1]] = strip_tags($match[2]);
                    },
                    implode('/', $paths)
                );
            }
            $_GET = array_merge($var, $_GET);
        }
        // 获取控制器和操作名
        define(
            'CONTROLLER_NAME',
            defined('BIND_CONTROLLER') ? BIND_CONTROLLER : static::getController($varController, $urlCase)
        );
        define('ACTION_NAME', static::getAction($varAction, $urlCase));

        // 当前控制器的UR地址
        $controllerName = CONTROLLER_NAME;
        define(
            '__CONTROLLER__',
            __MODULE__.$depr.(defined('BIND_CONTROLLER') ? '' : ($urlCase ? parse_name(
                $controllerName
            ) : $controllerName))
        );

        // 当前操作的URL地址
        define('__ACTION__', __CONTROLLER__.$depr.ACTION_NAME);

        //保证$_REQUEST正常取值
        $_REQUEST = array_merge($_POST, $_GET, $_COOKIE);
    }

    /**
     * 获得实际的控制器名称
     *
     * @param $var
     * @param $urlCase
     *
     * @return string
     */
    private static function getController($var, $urlCase)
    {
        $controller = (!empty($_GET[$var]) ? $_GET[$var] : C('DEFAULT_CONTROLLER'));
        unset($_GET[$var]);
        if ($urlCase) {
            // URL地址不区分大小写
            // 智能识别方式 user_type 识别到 UserTypeController 控制器
            $controller = parse_name($controller, 1);
        }

        return strip_tags(ucfirst($controller));
    }

    /**
     * 获得实际的操作名称
     *
     * @param      $var
     * @param bool $urlCase
     *
     * @return mixed|string
     */
    private static function getAction($var, $urlCase)
    {
        $action = !empty($_POST[$var]) ?
            $_POST[$var] :
            (!empty($_GET[$var]) ? $_GET[$var] : C('DEFAULT_ACTION'));
        unset($_POST[$var], $_GET[$var]);

        return strip_tags($urlCase ? strtolower($action) : $action);
    }

    /**
     * 获得实际的模块名称
     *
     * @param $var
     *
     * @return string
     */
    private static function getModule($var)
    {
        $module = (!empty($_GET[$var]) ? $_GET[$var] : C('DEFAULT_MODULE'));
        unset($_GET[$var]);

        return strip_tags(ucfirst($module));
    }

}


<?php

/*
 * This file is part of the Valkyrja framework.
 *
 * (c) Melech Mizrachi
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Valkyrja\HttpMessage;

use Valkyrja\Http\RequestMethod;
use Valkyrja\HttpMessage\Exceptions\InvalidMethod;
use Valkyrja\HttpMessage\Exceptions\InvalidRequestTarget;

/**
 * Representation of an outgoing, client-side request.
 *
 * Per the HTTP specification, this interface includes properties for
 * each of the following:
 *
 * - Protocol version
 * - HTTP method
 * - URI
 * - Headers
 * - Message body
 *
 * During construction, implementations MUST attempt to set the Host header from
 * a provided URI if no Host header is provided.
 *
 * Requests are considered immutable; all methods that might change state MUST
 * be implemented such that they retain the internal state of the current
 * message and return an instance that contains the changed state.
 *
 * @author Melech Mizrachi
 */
trait RequestTrait
{
    use MessageTrait;

    public static $HOST_NAME      = 'Host';
    public static $HOST_NAME_NORM = 'host';

    /**
     * The uri.
     *
     * @var \Valkyrja\HttpMessage\Uri
     */
    protected $uri;

    /**
     * The method.
     *
     * @var string
     */
    protected $method;

    /**
     * The request target.
     *
     * @var string
     */
    protected $requestTarget;

    /**
     * The body.
     *
     * @var resource
     */
    protected $body;

    /**
     * Initialize the Request.
     *
     * @param Uri    $uri     [optional] The uri
     * @param string $method  [optional] The method
     * @param Stream $body    [optional] The body stream
     * @param array  $headers [optional] The headers
     *
     * @throws \InvalidArgumentException
     * @throws \Valkyrja\HttpMessage\Exceptions\InvalidMethod
     * @throws \Valkyrja\HttpMessage\Exceptions\InvalidPath
     * @throws \Valkyrja\HttpMessage\Exceptions\InvalidPort
     * @throws \Valkyrja\HttpMessage\Exceptions\InvalidProtocolVersion
     * @throws \Valkyrja\HttpMessage\Exceptions\InvalidQuery
     * @throws \Valkyrja\HttpMessage\Exceptions\InvalidScheme
     * @throws \Valkyrja\HttpMessage\Exceptions\InvalidStream
     *
     * @return void
     */
    protected function initialize(
        Uri $uri = null,
        string $method = null,
        Stream $body = null,
        array $headers = null
    ): void {
        $this->uri     = $uri ?? new NativeUri();
        $this->method  = $method ?? RequestMethod::GET;
        $this->body    = $body ?? new NativeStream('php://input');
        $this->headers = $headers ?? [];

        $this->setHeaders($headers);
        $this->validateMethod($this->method);
        $this->validateProtocolVersion($this->protocol);

        if ($this->hasHeader(static::$HOST_NAME) && $this->uri->getHost()) {
            $this->headerNames[static::$HOST_NAME_NORM] = static::$HOST_NAME;
            $this->headers[static::$HOST_NAME]          = [
                $this->uri->getHost(),
            ];
        }
    }

    /**
     * Retrieves the message's request target.
     *
     * Retrieves the message's request-target either as it will appear (for
     * clients), as it appeared at request (for servers), or as it was
     * specified for the instance (see withRequestTarget()).
     *
     * In most cases, this will be the origin-form of the composed URI,
     * unless a value was provided to the concrete implementation (see
     * withRequestTarget() below).
     *
     * If no URI is available, and no request-target has been specifically
     * provided, this method MUST return the string "/".
     *
     * @return string
     */
    public function getRequestTarget(): string
    {
        if (null !== $this->requestTarget) {
            return $this->requestTarget;
        }

        $target = $this->uri->getPath();

        if ($this->uri->getQuery()) {
            $target .= '?' . $this->uri->getQuery();
        }

        if (empty($target)) {
            $target = '/';
        }

        return $target;
    }

    /**
     * Return an instance with the specific request-target.
     *
     * If the request needs a non-origin-form request-target — e.g., for
     * specifying an absolute-form, authority-form, or asterisk-form —
     * this method may be used to create an instance with the specified
     * request-target, verbatim.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * changed request target.
     *
     * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
     *     request-target forms allowed in request messages)
     *
     * @param string $requestTarget The request target
     *
     * @throws \Valkyrja\HttpMessage\Exceptions\InvalidRequestTarget
     *
     * @return static
     */
    public function withRequestTarget(string $requestTarget)
    {
        $this->validateRequestTarget($requestTarget);

        $new = clone $this;

        $new->requestTarget = $requestTarget;

        return $new;
    }

    /**
     * Retrieves the HTTP method of the request.
     *
     * @return string Returns the request method.
     */
    public function getMethod(): string
    {
        return $this->method;
    }

    /**
     * Return an instance with the provided HTTP method.
     *
     * While HTTP method names are typically all uppercase characters, HTTP
     * method names are case-sensitive and thus implementations SHOULD NOT
     * modify the given string.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * changed request method.
     *
     * @param string $method Case-sensitive method.
     *
     * @throws \Valkyrja\HttpMessage\Exceptions\InvalidMethod for invalid HTTP
     *          methods.
     *
     * @return static
     */
    public function withMethod(string $method)
    {
        $this->validateMethod($method);

        $new = clone $this;

        $new->method = $method;

        return $new;
    }

    /**
     * Retrieves the URI instance.
     *
     * This method MUST return a Uri instance.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-4.3
     *
     * @return Uri Returns a Uri instance
     *             representing the URI of the request.
     */
    public function getUri(): Uri
    {
        return $this->uri;
    }

    /**
     * Returns an instance with the provided URI.
     *
     * This method MUST update the Host header of the returned request by
     * default if the URI contains a host component. If the URI does not
     * contain a host component, any pre-existing Host header MUST be carried
     * over to the returned request.
     *
     * You can opt-in to preserving the original state of the Host header by
     * setting `$preserveHost` to `true`. When `$preserveHost` is set to
     * `true`, this method interacts with the Host header in the following
     * ways:
     *
     * - If the Host header is missing or empty, and the new URI contains
     *   a host component, this method MUST update the Host header in the
     *   returned request.
     * - If the Host header is missing or empty, and the new URI does not
     * contain a host component, this method MUST NOT update the Host header in
     * the returned request.
     * - If a Host header is present and non-empty, this method MUST NOT update
     *   the Host header in the returned request.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * new Uri instance.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-4.3
     *
     * @param Uri  $uri          New request URI to use.
     * @param bool $preserveHost Preserve the original state of the Host
     *                           header.
     *
     * @return static
     */
    public function withUri(Uri $uri, bool $preserveHost = false)
    {
        $new = clone $this;

        $new->uri = $uri;

        if ($preserveHost && $this->hasHeader(static::$HOST_NAME)) {
            return $new;
        }

        if (! $uri->getHost()) {
            return $new;
        }

        $host = $uri->getHost();

        $new->headerNames[static::$HOST_NAME_NORM] = static::$HOST_NAME;

        $new->headers =
            $this->injectHeader(
                static::$HOST_NAME,
                $host,
                $new->headerNames,
                true
            );

        return $new;
    }

    /**
     * Validate a request target.
     *
     * @param string $requestTarget The request target
     *
     * @throws \Valkyrja\HttpMessage\Exceptions\InvalidRequestTarget
     *
     * @return void
     */
    protected function validateRequestTarget(string $requestTarget): void
    {
        if (preg_match('#\s#', $requestTarget)) {
            throw new InvalidRequestTarget(
                'Invalid request target provided; cannot contain whitespace'
            );
        }
    }

    /**
     * Validate a method.
     *
     * @param string $method The method
     *
     * @throws \Valkyrja\HttpMessage\Exceptions\InvalidMethod
     *
     * @return void
     */
    protected function validateMethod(string $method): void
    {
        if (! RequestMethod::isValid($method)) {
            throw new InvalidMethod(
                sprintf(
                    'Unsupported HTTP method "%s" provided',
                    $method
                )
            );
        }
    }
}


<?php
namespace Nubs\Which;

use Nubs\Which\PathBuilder\PathBuilderInterface;

/**
 * Provides the ability to locate commands in the user's PATH.
 */
class Locator
{
    /** @type \Nubs\Which\PathBuilder\PathBuilderInterface The path builder. */
    private $_pathBuilder;

    /** @type callable The executable tester. */
    private $_executableTester;

    /**
     * Initialize the locator.
     *
     * @api
     * @param \Nubs\Which\PathBuilder\PathBuilderInterface $pathBuilder The path
     *     builder.
     * @param callable $executableTester The executable tester, a callable that
     *     is passed a file path and should return true for an executable
     *     command, false otherwise.  Defaults to the invokable
     *     \Nubs\Which\ExecutableTester.
     */
    public function __construct(PathBuilderInterface $pathBuilder, callable $executableTester = null)
    {
        $this->_pathBuilder = $pathBuilder;
        $this->_executableTester = $executableTester ?: new ExecutableTester();
    }

    /**
     * Locates the given command in the user's path.
     *
     * @api
     * @param string $command The command to locate.
     * @return string|null The full path to the command or null if not path
     *     found.
     */
    public function locate($command)
    {
        $paths = $this->locateAll($command);

        return empty($paths) ? null : $paths[0];
    }

    /**
     * Locates all versions of the given command in the user's path.
     *
     * @api
     * @param string $command The command to locate.
     * @return array The locations where the command exists.
     */
    public function locateAll($command)
    {
        return array_values(array_filter($this->_pathBuilder->getPermutations($command), $this->_executableTester));
    }
}