<?php

namespace Gwsn\Prismic\Models\Dom;

use Gwsn\Prismic\Models\LinkResolver;

/**
 * This class embodies a RichText fragment.
 *
 * Technically, a RichText fragment is not much more than an array of blocks,
 * but there are many things to do with this fragment, including in the HTML serialization,
 * but not only. It is arguably the most powerful and manipulable way any CMS stores
 * structured text nowadays.
 */
class RichText
{
    /**
     * Builds a text version of the RichText fragment
     *
     *
     * @param object $richText the rich text object
     *
     * @return string the text version of the RichText fragment
     */
    public static function asText($richText)
    {
        $result = '';
        foreach ($richText as $block) {
            if (isset($block->text)) {
                $result .= $block->text . "\n";
            }
        }
        return $result;
    }
    /**
     * Builds a HTML version of the RichText fragment
     *
     *
     * @param array $richText the rich text object
     * @param LinkResolver $linkResolver   the link resolver

     *
     * @return string the HTML version of the RichText fragment
     */
    public static function asHtml(array $richText, LinkResolver $linkResolver = null)
    {
        $groups = [];

        if(is_array($richText) && key_exists('dimensions', $richText)) {
            return self::asImageBlock($richText);
        }

        foreach ($richText as $block) {
            $block = (object) $block;
            $count = count($groups);

            // Check if block is image
            if(empty($block->type)) {
                var_dump($richText);
                die();
            }

            if ($count > 0) {
                $lastOne = $groups[$count - 1];
                if ('ul' == $lastOne->getTag() && $block->type === 'list-item') {
                    $lastOne->addBlock($block);
                } elseif ('ol' == $lastOne->getTag() && $block->type === 'o-list-item') {
                    $lastOne->addBlock($block);
                } elseif ($block->type === 'list-item') {
                    $newBlockGroup = new BlockGroup('ul', array());
                    $newBlockGroup->addBlock($block);
                    array_push($groups, $newBlockGroup);
                } else {
                    if ($block->type === 'o-list-item') {
                        $newBlockGroup = new BlockGroup('ol', array());
                        $newBlockGroup->addBlock($block);
                        array_push($groups, $newBlockGroup);
                    } else {
                        $newBlockGroup = new BlockGroup(null, array());
                        $newBlockGroup->addBlock($block);
                        array_push($groups, $newBlockGroup);
                    }
                }
            } else {
                if ($block->type === 'list-item') {
                    $tag = 'ul';
                } elseif ($block->type === 'o-list-item') {
                    $tag = 'ol';
                } else {
                    $tag = null;
                }
                $newBlockGroup = new BlockGroup($tag, array());
                $newBlockGroup->addBlock($block);
                array_push($groups, $newBlockGroup);
            }
        }
        $html = '';
        foreach ($groups as $group) {
            $maybeTag = $group->getTag();
            if ($maybeTag) {
                $html = $html . '<' . $group->getTag() . '>';
                foreach ($group->getBlocks() as $block) {
                    $html = $html . RichText::asHtmlBlock($block, $linkResolver);
                }
                $html = $html . '</' . $group->getTag() . '>';
            } else {
                foreach ($group->getBlocks() as $block) {
                    $html = $html . RichText::asHtmlBlock($block, $linkResolver);
                }
            }
        }
        return $html;
    }

    /**
     * @param array $richText
     *
     * @return string
     */
    private static function asImageBlock(array $richText) {

        // Check if the value is a image object:
        foreach(['dimensions', 'alt','url'] as $key) {
            if(!key_exists($key, $richText)) return '';
        }

        $alt = (!empty($richText['alt']) ? ' alt="'.$richText['alt'].'"' : '');
        $width = (!empty($richText['dimensions']['width']) ? ' width="'.$richText['dimensions']['width'].'"' : '');
        $height = (!empty($richText['dimensions']['height']) ? ' height="'.$richText['dimensions']['height'].'"' : '');

        return '';

    }

    /**
     * Transforms a block into HTML
     *
     *
     * @param object $block a given block
     * @param LinkResolver $linkResolver   the link resolver
     * @param lambda                $htmlSerializer the user's custom HTML serializer
     *
     * @return string the HTML representation of the block
     */
    private static function asHtmlBlock(\stdClass $block, $linkResolver = null, $htmlSerializer = null)
    {
        $content = '';
        if ($block->type === 'heading1' ||
            $block->type === 'heading2' ||
            $block->type === 'heading3' ||
            $block->type === 'heading4' ||
            $block->type === 'heading5' ||
            $block->type === 'heading6' ||
            $block->type === 'paragraph' ||
            $block->type === 'list-item' ||
            $block->type === 'o-list-item' ||
            $block->type === 'preformatted'
        ) {
            $content = RichText::insertSpans($block->text, $block->spans, $linkResolver, $htmlSerializer);
        }
        return RichText::serialize((object)$block, $content, $linkResolver, $htmlSerializer);
    }

    /**
     * Transforms a text block into HTML
     *
     *
     * @param string                $text           the raw text of the block
     * @param array                 $spans          the spans of the block
     * @param LinkResolver $linkResolver   the link resolver
     *
     * @return string the HTML representation of the block
     */
    private static function insertSpans($text, array $spans, $linkResolver = null)
    {
        if (empty($spans)) {
            return htmlentities($text, null, 'UTF-8');
        }
        $tagsStart = array();
        $tagsEnd = array();
        foreach ($spans as $span) {
            $span = (object) $span;
            if (!array_key_exists($span->start, $tagsStart)) {
                $tagsStart[$span->start] = array();
            }
            if (!array_key_exists($span->end, $tagsEnd)) {
                $tagsEnd[$span->end] = array();
            }
            array_push($tagsStart[$span->start], $span);
            array_push($tagsEnd[$span->end], $span);
        }
        $c = null;
        $html = '';
        $stack = array();
        for ($pos = 0, $len = strlen($text) + 1; $pos < $len; $pos++) { // Looping to length + 1 to catch closing tags
            if (array_key_exists($pos, $tagsEnd)) {
                foreach ($tagsEnd[$pos] as $endTag) {
                    // Close a tag
                    $tag = array_pop($stack);
                    // Continue only if block contains content.
                    if ($tag && $tag['span']) {
                        $innerHtml = trim(RichText::serialize((object)$tag['span'], $tag['text'], $linkResolver));
                        if (count($stack) == 0) {
                            // The tag was top level
                            $html .= $innerHtml;
                        } else {
                            // Add the content to the parent tag
                            $last = array_pop($stack);
                            $last['text'] = $last['text'] . $innerHtml;
                            array_push($stack, $last);
                        }
                    }
                }
            }
            if (array_key_exists($pos, $tagsStart)) {
                // Sort bigger tags first to ensure the right tag hierarchy
                $sspans = $tagsStart[$pos];
                $spanSort = function ($a, $b) {
                    return ($b->end - $b->start) - ($a->end - $a->start);
                };
                usort($sspans, $spanSort);
                foreach ($sspans as $span) {
                    // Open a tag
                    array_push($stack, array(
                        'span' => $span,
                        'text' => ''
                    ));
                }
            }
            if ($pos < strlen($text)) {
                $c = mb_substr($text, $pos, 1, 'UTF-8');
                if (count($stack) == 0) {
                    // Top-level text
                    $html .= htmlentities($c, null, 'UTF-8');
                } else {
                    // Inner text of a span
                    $last_idx = count($stack) - 1;
                    $last = $stack[$last_idx];
                    $stack[$last_idx] = array(
                        'span' => $last['span'],
                        'text' => $last['text'] . htmlentities($c, null, 'UTF-8')
                    );
                }
            }
        }
        return $html;
    }
    /**
     * Transforms an element into HTML
     *
     *
     * @param \stdClass $element element to serialize
     * @param string $content inner HTML content of the element
     * @param LinkResolver $linkResolver   the link resolver
     *
     * @return string the HTML representation of the element
     */
    private static function serialize(\stdClass $element, $content, $linkResolver)
    {

        $classCode = '';
        if (isset($element->label)) {
            $classCode = ' class="' . $element->label . '"';
        }
        // Blocks
        switch ($element->type) {
            case 'heading1':
                return nl2br('
' . $content . '
');
            case 'heading2':
                return nl2br('
' . $content . '
');
            case 'heading3':
                return nl2br('
' . $content . '
');
            case 'heading4':
                return nl2br('
' . $content . '
');
            case 'heading5':
                return nl2br('
' . $content . '
');
            case 'heading6':
                return nl2br('
' . $content . '
');
            case 'paragraph':
                return nl2br('
' . $content . '
');
            case 'preformatted':
                return '
' . $content . '
';
            case 'list-item':
            case 'o-list-item':
                return nl2br('
' . $content . '
');
            case 'image':
                return (
                    '
label) ? (' ' . $element->label) : '') . '">' .
                    'url . '" alt="' . htmlentities($element->alt) . '">' .
                    '
'
                );
            case 'embed':
                $providerAttr = '';
                $element->oembed = (object) $element->oembed;
                if ($element->oembed->provider_name) {
                    $providerAttr = ' data-oembed-provider="' . strtolower($element->oembed->provider_name) . '"';
                }
                if ($element->oembed->html) {
                    return (
                        '
oembed->embed_url . '" data-oembed-type="' . strtolower($element->oembed->type) . '"' . $providerAttr . '>' .
                        $element->oembed->html .
                        '
'
                    );
                }
                return '';
        }
        // Spans
        $attributes = array();
        switch ($element->type) {
            case 'strong':
                $nodeName = 'strong';
                break;
            case 'em':
                $nodeName = 'em';
                break;
            case 'hyperlink':
                $nodeName = 'a';
                $element->data = (object) $element->data;
                if (isset($element->data->target)) {
                    $attributes = array_merge(array(
                        'target' => $element->data->target,
                        'rel' => 'noopener',
                    ), $attributes);
                }
                if ($element->data->link_type === 'Document') {
                    $attributes['href'] = $linkResolver ? $linkResolver($element->data) : '';
                } else {
                    $attributes['href'] = $element->data->url;
                }
                if ($attributes['href'] === null) {
                    // We have no link (LinkResolver said it is not valid,
                    // or something else went wrong). Abort this span.
                    return $content;
                }
                break;
            default:
                // throw new \Exception("Unknown span type " . get_class($span));
                $nodeName = 'span';
        }
        if (isset($element->label)) {
            $attributes['class'] = $element->label;
        } elseif (isset($element->data->label)) {
            $attributes['class'] = $element->data->label;
        }
        $html = '<' . $nodeName;
        foreach ($attributes as $k => $v) {
            $html .= (' ' . $k . '="' . $v . '"');
        }
        $html .= ('>' . $content . '</' . $nodeName . '>');
        return $html;
    }
}


<?php namespace Ordercloud\Support;

use JsonSerializable;

class PaginatedCollection extends Collection implements JsonSerializable
{
    /** @var int */
    protected $totalCount;
    /** @var int */
    protected $currentPage;
    /** @var int */
    protected $pageSize;
    /** @var int */
    protected $totalNrPages;
    /** @var int */
    protected $nextPage;
    /** @var int */
    protected $previousPage;

    /**
     * @param array $items
     * @param int   $totalCount
     * @param int   $currentPage
     * @param int   $pageSize
     */
    public function __construct(array $items, $totalCount, $currentPage, $pageSize)
    {
        parent::__construct($items);
        $this->totalCount = $totalCount;
        $this->currentPage = $currentPage;
        $this->pageSize = $pageSize;
        $this->totalNrPages = $pageSize > 0 ? intval(ceil($totalCount / $pageSize)) : 1;
        $this->nextPage = $this->totalNrPages > $currentPage ? $currentPage + 1 : null;
        $this->previousPage = $currentPage > 1 ? $currentPage - 1 : null;
    }

    /**
     * @return int
     */
    public function getTotalCount()
    {
        return $this->totalCount;
    }

    /**
     * @return int
     */
    public function getCurrentPage()
    {
        return $this->currentPage;
    }

    /**
     * @return int
     */
    public function getPageSize()
    {
        return $this->pageSize;
    }

    /**
     * @return int
     */
    public function getTotalNrPages()
    {
        return $this->totalNrPages;
    }

    /**
     * @return int
     */
    public function getNextPage()
    {
        return $this->nextPage;
    }

    /**
     * @return int
     */
    public function getPreviousPage()
    {
        return $this->previousPage;
    }

    public function hasNextPage()
    {
        return ! is_null($this->getNextPage());
    }

    public function hasPreviousPage()
    {
        return ! is_null($this->getPreviousPage());
    }

    /**
     * Specify data which should be serialized to JSON
     */
    public function jsonSerialize()
    {
        $json = parent::jsonSerialize();

        $json['totalCount'] = $this->getTotalCount();
        $json['currentPage'] = $this->getCurrentPage();
        $json['pageSize'] = $this->getPageSize();
        $json['totalNrPages'] = $this->getTotalNrPages();
        $json['nextPage'] = $this->getNextPage();
        $json['previousPage'] = $this->getPreviousPage();

        return $json;
    }
}


<?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\StatusCode;
use Valkyrja\HttpMessage\Exceptions\InvalidStatusCode;

/**
 * Representation of an outgoing, server-side response.
 *
 * Per the HTTP specification, this interface includes properties for
 * each of the following:
 *
 * - Protocol version
 * - Status code and reason phrase
 * - Headers
 * - Message body
 *
 * Responses 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
 */
class NativeResponse implements Response
{
    use MessageTrait;

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

    /**
     * The status phrase.
     *
     * @var string
     */
    protected $statusPhrase;

    /**
     * NativeResponse constructor.
     *
     * @param Stream $body    [optional] The body
     * @param int    $status  [optional] The status
     * @param array  $headers [optional] The headers
     *
     * @throws \InvalidArgumentException
     * @throws \Valkyrja\HttpMessage\Exceptions\InvalidStatusCode
     * @throws \Valkyrja\HttpMessage\Exceptions\InvalidStream
     */
    public function __construct(Stream $body = null, int $status = null, array $headers = null)
    {
        $this->stream     = $body
            ?? new NativeStream('php://input', 'rw');
        $this->statusCode = $this->validateStatusCode(
            $status ?? StatusCode::OK
        );

        $this->setHeaders($headers ?? []);
    }

    /**
     * Gets the response status code.
     *
     * The status code is a 3-digit integer result code of the server's attempt
     * to understand and satisfy the request.
     *
     * @return int Status code.
     */
    public function getStatusCode(): int
    {
        return $this->statusCode;
    }

    /**
     * Return an instance with the specified status code and, optionally,
     * reason phrase.
     *
     * If no reason phrase is specified, implementations MAY choose to default
     * to the RFC 7231 or IANA recommended reason phrase for the response's
     * status code.
     *
     * 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
     * updated status and reason phrase.
     *
     * @link http://tools.ietf.org/html/rfc7231#section-6
     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
     *
     * @param int    $code         The 3-digit integer result code to set.
     * @param string $reasonPhrase The reason phrase to use with the
     *                             provided status code; if none is provided,
     *                             implementations MAY use the defaults as
     *                             suggested in the HTTP specification.
     *
     * @throws \Valkyrja\HttpMessage\Exceptions\InvalidStatusCode
     *      For invalid status code arguments.
     *
     * @return static
     */
    public function withStatus(int $code, string $reasonPhrase = null)
    {
        $new = clone $this;

        $new->statusCode   = $new->validateStatusCode($code);
        $new->statusPhrase = $reasonPhrase
            ?? StatusCode::TEXTS[$this->statusCode];

        return $new;
    }

    /**
     * Gets the response reason phrase associated with the status code.
     *
     * Because a reason phrase is not a required element in a response
     * status line, the reason phrase value MAY be null. Implementations MAY
     * choose to return the default RFC 7231 recommended reason phrase (or
     * those listed in the IANA HTTP Status Code Registry) for the response's
     * status code.
     *
     * @link http://tools.ietf.org/html/rfc7231#section-6
     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
     *
     * @return string Reason phrase; must return an empty string if none
     *                present.
     */
    public function getReasonPhrase(): string
    {
        return $this->statusPhrase ?: StatusCode::TEXTS[$this->statusCode];
    }

    /**
     * Return an instance with the specified cookie appended with the given
     * value.
     *
     * Existing values for the specified header will be maintained. The new
     * value(s) will be appended to the existing list. If the cookie header
     * did not exist previously, it will be added.
     *
     * 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 cookie header and/or value.
     *
     * @param Cookie $cookie The cookie model
     *
     * @throws \InvalidArgumentException for invalid header names or values.
     *
     * @return static
     */
    public function withCookie(Cookie $cookie)
    {
        return $this->withAddedHeader(
            Header::SET_COOKIE,
            (string) $cookie
        );
    }

    /**
     * Return an instance with the specified cookie appended to the
     * Set-Cookie header as expired.
     *
     * Existing values for the specified header will be maintained. The new
     * value(s) will be appended to the existing list. If the cookie header
     * did not exist previously, it will be added.
     *
     * 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 cookie header and/or value.
     *
     * @param Cookie $cookie The cookie model
     *
     * @throws \InvalidArgumentException for invalid header names or values.
     *
     * @return static
     */
    public function withoutCookie(Cookie $cookie)
    {
        $cookie->setValue();
        $cookie->setExpire();

        return $this->withAddedHeader(
            Header::SET_COOKIE,
            (string) $cookie
        );
    }

    /**
     * Send the response.
     *
     * @throws \RuntimeException
     *
     * @return Response
     */
    public function send(): Response
    {
        $http_line = sprintf(
            'HTTP/%s %s %s',
            $this->getProtocolVersion(),
            $this->getStatusCode(),
            $this->getReasonPhrase()
        );

        header($http_line, true, $this->getStatusCode());

        foreach ($this->getHeaders() as $name => $values) {
            /** @var array $values */
            foreach ($values as $value) {
                header("$name: $value", false);
            }
        }

        $stream = $this->getBody();

        if ($stream->isSeekable()) {
            $stream->rewind();
        }

        while (! $stream->eof()) {
            echo $stream->read(1024 * 8);
        }

        return $this;
    }

    /**
     * Validate a status code.
     *
     * @param int $code The code
     *
     * @throws \Valkyrja\HttpMessage\Exceptions\InvalidStatusCode
     *
     * @return int
     */
    protected function validateStatusCode(int $code): int
    {
        if (StatusCode::MIN > $code || $code > StatusCode::MAX) {
            throw new InvalidStatusCode(
                sprintf(
                    'Invalid status code "%d"; must adhere to values set in the %s enum class.',
                    $code,
                    StatusCode::class
                )
            );
        }

        return $code;
    }
}


<?php

namespace tourze\Http;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
use tourze\Base\Exception\BaseException;
use tourze\Base\Helper\Arr;
use tourze\Base\Helper\Url;
use tourze\Base\Base;
use tourze\Http\Exception\HttpException;
use tourze\Http\Request\Client\ExternalClient;
use tourze\Http\Request\Client\InternalClient;
use tourze\Http\Request\Exception\ClientRecursionException;
use tourze\Http\Request\Exception\RequestException;
use tourze\Http\Request\RequestClient;
use tourze\Route\Entry;
use tourze\Route\Route;
use tourze\Base\Security\Valid;

/**
 * 请求处理类
 *
 * @property RequestClient client
 * @property Entry         route
 * @property array         routes
 * @property string        controller
 * @property string        action
 * @property string        directory
 * @property string        method
 * @property bool          ajax
 * @property bool          secure
 * @property bool          initial
 * @property string        uri
 * @property string        referrer
 * @property bool          external
 * @property string        requestedWith
 * @property array         params
 * @property array         get
 * @property array         post
 * @property array         persistRouteParams
 * @package tourze\Http
 */
class Request extends Stream implements RequestInterface
{

    /**
     * @var string 客户端UA
     */
    public static $userAgent = '';

    /**
     * @var string 客户端IP
     */
    public static $clientIp = '0.0.0.0';

    /**
     * @var string 可信任的代理服务器列表
     */
    public static $trustedProxies = [
        '127.0.0.1',
        'localhost',
    ];

    /**
     * @var static 系统主请求
     */
    public static $initialRequest;

    /**
     * @var static 当前正在处理的请求
     */
    public static $current;

    /**
     * 创建一个新的实例
     *
     *     $request = Request::factory($uri);
     *
     * @param   bool|string $uri            URI
     * @param   array       $clientParams   注入到client中的参数
     * @param   array       $injectedRoutes 注入到路由中的参数，一般用于测试
     * @return  Request|void
     * @throws  BaseException
     */
    public static function factory($uri = true, $clientParams = [], $injectedRoutes = [])
    {
        $request = new Request($uri, $clientParams, $injectedRoutes);
        return $request;
    }

    /**
     * 当前当前正在处理的请求
     *
     * @return Request
     */
    public static function current()
    {
        return Request::$current;
    }

    /**
     * 解析请求，查找路由
     *
     * @param  Request $request Request
     * @param  Route[] $routes  Route
     * @return array
     */
    public static function process(Request $request, $routes = null)
    {
        Base::getLog()->debug(__METHOD__ . ' process request and find the route');

        $routes = (empty($routes)) ? Route::all() : $routes;
        Base::getLog()->debug(__METHOD__ . ' get route list for process route', [
            'routes' => array_keys($routes),
        ]);

        $params = null;
        foreach ($routes as $name => $route)
        {
            Base::getLog()->debug(__METHOD__ . ' check route', [
                'name'   => $name,
                'method' => $request->method,
            ]);

            /* @var $route Entry */
            if ($params = $route->matches($request->uri, $request->method))
            {
                Base::getLog()->debug(__METHOD__ . ' matched route found', [
                    'name'   => $name,
                    'params' => $params,
                ]);
                return [
                    'params' => $params,
                    'route'  => $route,
                ];
            }
        }

        return null;
    }

    /**
     * 读取客户端IP地址
     *
     * @return string
     */
    public static function getClientIP()
    {
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])
            && isset($_SERVER['REMOTE_ADDR'])
            && in_array($_SERVER['REMOTE_ADDR'], Request::$trustedProxies)
        )
        {
            // Format: "X-Forwarded-For: client1, proxy1, proxy2"
            $clientIps = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);

            return array_shift($clientIps);
        }
        elseif (isset($_SERVER['HTTP_CLIENT_IP'])
            && isset($_SERVER['REMOTE_ADDR'])
            && in_array($_SERVER['REMOTE_ADDR'], Request::$trustedProxies)
        )
        {
            $clientIps = explode(',', $_SERVER['HTTP_CLIENT_IP']);

            return array_shift($clientIps);
        }
        elseif (isset($_SERVER['REMOTE_ADDR']))
        {
            return $_SERVER['REMOTE_ADDR'];
        }

        return '0.0.0.0';
    }

    /**
     * 根据URI创建一个新的请求对象
     *
     *     $request = new Request($uri);
     *
     * If $cache parameter is set, the response for the request will attempt to be retrieved from the cache.
     *
     * @param  string $uri            URI of the request
     * @param  array  $clientParams   Array of params to pass to the request client
     * @param  array  $injectedRoutes An array of routes to use, for testing
     * @throws RequestException
     */
    public function __construct($uri, $clientParams = [], $injectedRoutes = [])
    {
        $clientParams = is_array($clientParams) ? $clientParams : [];

        $this->message = new Message;
        $this->routes = $injectedRoutes;

        $splitUri = explode('?', $uri);
        $uri = array_shift($splitUri);

        if (null !== Request::$initialRequest)
        {
            if ($splitUri)
            {
                parse_str($splitUri[0], $this->_get);
            }
        }

        // 要区分内部链接和外部链接
        if (Valid::url($uri))
        {
            // 为其创建一个路由
            $this->route = new Entry([
                'uri' => $uri,
            ]);
            $this->uri = $uri;

            if (0 === strpos($uri, 'https://'))
            {
                $this->secure = true;
            }

            $this->external = true;
            $this->client = new ExternalClient($clientParams);

            Base::getLog()->debug(__METHOD__ . ' make an internal request', [
                'uri'    => $uri,
                'params' => $clientParams,
            ]);
        }
        else
        {
            $this->uri = trim($uri, '/');
            $this->client = new InternalClient($clientParams);

            Base::getLog()->debug(__METHOD__ . ' make an external request', [
                'uri'    => $uri,
                'params' => $clientParams,
            ]);
        }

        parent::__construct();
    }

    /**
     * @var RequestClient 请求处理驱动客户端
     */
    protected $_client;

    /**
     * @return RequestClient
     */
    public function getClient()
    {
        return $this->_client;
    }

    /**
     * @param RequestClient $client
     */
    public function setClient($client)
    {
        $this->_client = $client;
    }

    /**
     * @var string x-requested-with头，一般xmlhttprequest都有这个
     */
    protected $_requestedWith;

    /**
     * @return string
     */
    protected function getRequestedWith()
    {
        return $this->_requestedWith;
    }

    /**
     * @param string $requestWith
     */
    protected function setRequestedWith($requestWith)
    {
        $this->_requestedWith = $requestWith;
    }

    /**
     * @var string 请求方法，GET、POST或其他
     */
    protected $_method = 'GET';

    /**
     * @return string
     */
    public function getMethod()
    {
        return $this->_method;
    }

    /**
     * @param string $method
     */
    public function setMethod($method)
    {
        $this->_method = strtoupper($method);
    }

    /**
     * @var bool 当前请求是否为安全连接
     */
    protected $_secure = false;

    /**
     * @return bool
     */
    protected function getSecure()
    {
        return $this->_secure;
    }

    /**
     * @param bool $secure
     */
    protected function setSecure($secure)
    {
        $this->_secure = (bool) $secure;
    }

    /**
     * @var string 来路URL
     */
    protected $_referrer;

    /**
     * @return string
     */
    protected function getReferrer()
    {
        return $this->_referrer;
    }

    /**
     * @param string $referrer
     */
    protected function setReferrer($referrer)
    {
        $this->_referrer = (string) $referrer;
    }

    /**
     * @var Entry 当前请求匹配到的路由
     */
    protected $_route;

    /**
     * @return Entry
     */
    protected function getRoute()
    {
        return $this->_route;
    }

    /**
     * @param Entry $route
     */
    protected function setRoute(Entry $route)
    {
        $this->_route = $route;
    }

    /**
     * @var Route[] 要在这些路由中查找请求，如果为空的话，那就直接用当前请求
     */
    protected $_routes;

    /**
     * @return Route[]
     */
    protected function getRoutes()
    {
        return $this->_routes;
    }

    /**
     * @param Route[] $routes
     */
    protected function setRoutes($routes)
    {
        $this->_routes = $routes;
    }

    /**
     * @var string 控制器子目录
     */
    protected $_directory = '';

    /**
     * @return string
     */
    protected function getDirectory()
    {
        return $this->_directory;
    }

    /**
     * @param string $directory
     */
    protected function setDirectory($directory)
    {
        $this->_directory = (string) $directory;
    }

    /**
     * @var  string  当前请求要执行的控制器
     */
    protected $_controller;

    /**
     * @return string
     */
    protected function getController()
    {
        return $this->_controller;
    }

    /**
     * @param string $controller
     */
    protected function setController($controller)
    {
        $this->_controller = (string) $controller;
    }

    /**
     * @var  string  控制器要执行的动作
     */
    protected $_action;

    /**
     * @return string
     */
    protected function getAction()
    {
        return $this->_action;
    }

    /**
     * @param string $action
     */
    protected function setAction($action)
    {
        $this->_action = (string) $action;
    }

    /**
     * @var string 当前请求的URI
     */
    protected $_uri;

    /**
     * @return string
     */
    public function getUri()
    {
        return empty($this->_uri) ? '/' : $this->_uri;
    }

    /**
     * @param mixed $uri
     */
    protected function setUri($uri)
    {
        $this->_uri = $uri;
    }

    /**
     * @var bool 是否是外部请求
     */
    protected $_external = false;

    /**
     * @return bool
     */
    protected function getExternal()
    {
        return $this->_external;
    }

    /**
     * @param bool $external
     */
    protected function setExternal($external)
    {
        $this->_external = (bool) $external;
    }

    /**
     * @var array 当前请求的路由参数
     */
    protected $_params = [];

    /**
     * @return array
     */
    public function getParams()
    {
        return $this->_params;
    }

    /**
     * @param array $params
     */
    public function setParams($params)
    {
        $this->_params = $params;
    }

    /**
     * @var array get参数
     */
    protected $_get = [];

    /**
     * @return array
     */
    public function getGet()
    {
        return $this->_get;
    }

    /**
     * @param array $get
     */
    public function setGet($get)
    {
        $this->_get = $get;
    }

    /**
     * @var array post参数
     */
    protected $_post = [];

    /**
     * @return array
     */
    public function getPost()
    {
        return $this->_post;
    }

    /**
     * @param array $post
     */
    public function setPost($post)
    {
        $this->_post = $post;
    }

    /**
     * @var bool 当前请求实例是否为初始实例
     */
    protected $_initial = false;

    /**
     * @return bool
     */
    public function isInitial()
    {
        return $this->_initial;
    }

    /**
     * @param bool $initial
     */
    public function setInitial($initial)
    {
        $this->_initial = $initial;
    }

    /**
     * @var array 要保留使用的路由参数
     */
    protected $_persistRouteParams = [
        'namespace',
        'directory',
        'controller',
        'action',
        'host',
    ];

    /**
     * @return array
     */
    public function getPersistRouteParams()
    {
        return $this->_persistRouteParams;
    }

    /**
     * @param array $persistRouteParams
     */
    public function setPersistRouteParams($persistRouteParams)
    {
        $this->_persistRouteParams = $persistRouteParams;
    }

    /**
     * 初始化
     */
    public function init()
    {
        // 如果当前运行环境是CLI，那么就会没有初始请求这个概念
        if ( ! self::$initialRequest)
        {
            self::$initialRequest = $this;
            $this->initial = true;
            $this->message->setHeaders(Http::requestHeaders());
        }
    }

    /**
     * 返回请求的输出
     *
     *     echo $request;
     *
     * @return string
     */
    public function __toString()
    {
        return $this->render();
    }

    /**
     * 创建一个当前请求的URL
     *
     * @param  mixed $protocol 协议字符串
     * @return string
     */
    public function url($protocol = null)
    {
        return Url::site($this->uri, $protocol);
    }

    /**
     * 获取路由参数
     *
     *     $id = $request->param('id');
     *
     * @param  string $key     参数名
     * @param  mixed  $default 默认返回值
     * @return mixed
     */
    public function param($key = null, $default = null)
    {
        // 一次性返回所有参数
        if (null === $key)
        {
            return $this->_params;
        }

        return Arr::get($this->_params, $key, $default);
    }

    /**
     * 执行请求
     *
     * @return Response
     * @throws HttpException
     * @throws ClientRecursionException
     * @throws RequestException
     */
    public function execute()
    {
        if ( ! $this->external)
        {
            Base::getLog()->debug(__METHOD__ . ' execute internal request');
            $processed = Request::process($this, $this->routes);

            if ($processed)
            {
                // 保存匹配到的路由
                $this->route = Arr::get($processed, 'route');
                $params = Arr::get($processed, 'params');

                // 是否为外部链接
                $this->external = $this->route->isExternal();

                // 控制器放在子目录中的情况
                if (isset($params['directory']))
                {
                    $this->directory = $params['directory'];
                }

                // 附加上命名空间
                if (isset($params['namespace']))
                {
                    $this->directory = $params['namespace'];
                }

                // 命名空间处理
                if ( ! $this->directory)
                {
                    $this->directory = Route::$defaultNamespace;
                }

                // 修正命名空间
                if (false === strpos($this->directory, '\\'))
                {
                    $this->directory = Route::$defaultNamespace . $this->directory . '\\';
                }

                // 保存控制器
                $this->controller = Arr::get($params, 'controller');

                // 保存动作
                $this->action = Arr::get($params, 'action', Route::$defaultAction);

                // 清理保留字段
                foreach ($this->persistRouteParams as $name)
                {
                    unset($params[$name]);
                }

                $this->_params = $params;

                Base::getLog()->debug(__METHOD__ . ' execute info', [
                    'directory'  => $this->directory,
                    'controller' => $this->controller,
                    'action'     => $this->action,
                    'params'     => $this->_params,
                ]);
            }
            else
            {
                Base::getLog()->debug(__METHOD__ . ' not route matched');
            }
        }

        if ( ! $this->route instanceof Entry)
        {
            $e = HttpException::factory(Http::NOT_FOUND, 'Unable to find a route to match the URI: :uri', [
                ':uri' => $this->uri,
            ]);
            $e->request($this);

            throw $e;
        }

        if ( ! $this->client instanceof RequestClient)
        {
            throw new RequestException('Unable to execute :uri without a RequestClient', [
                ':uri' => $this->uri,
            ]);
        }

        return $this->client->execute($this);
    }

    /**
     * 当前请求是否是ajax请求的
     *
     * @return bool
     */
    public function isAjax()
    {
        return ('xmlhttprequest' === $this->requestedWith);
    }

    /**
     * 读取或者设置header信息
     *
     * @param  string|array $name  头部名或包含了头部名和数据的数组
     * @param  string       $value 值
     * @return mixed|$this
     */
    public function headers($name = null, $value = null)
    {
        if (is_array($name))
        {
            foreach ($name as $k => $v)
            {
                $this->message->withHeader($k, $v);
            }
            return $this;
        }

        if (null === $name)
        {
            return $this->message->getHeaders();
        }
        elseif (null === $value)
        {
            return $this->message->getHeaderLine($name);
        }

        $this->message->withHeader($name, $value);

        return $this;
    }

    /**
     * 设置或者读取cookie
     *
     * @param  mixed  $key   Cookie名，或一个cookie数组
     * @param  string $value 值
     * @return string
     * @return mixed
     */
    public function cookie($key = null, $value = null)
    {
        if (is_array($key))
        {
            $this->_cookies = $key;

            return $this;
        }
        elseif (null === $key)
        {
            return $this->_cookies;
        }
        elseif (null === $value)
        {
            return isset($this->_cookies[$key]) ? $this->_cookies[$key] : null;
        }

        $this->_cookies[$key] = (string) $value;
        return $this;
    }

    /**
     * 渲染请求，保存：协议、头部、内容主体
     *
     * @return  string
     */
    public function render()
    {
        if ( ! $post = $this->post())
        {
            $body = $this->body;
        }
        else
        {
            $this->headers('content-type', 'application/x-www-form-urlencoded; charset=' . $this->charset);
            $body = http_build_query($post, null, '&');
        }

        $this->headers('content-length', (string) $this->contentLength);

        if (Base::$expose)
        {
            $this->headers('user-agent', Base::version());
        }

        if ($this->_cookies)
        {
            $cookieString = [];

            // Parse each
            foreach ($this->_cookies as $key => $value)
            {
                $cookieString[] = $key . '=' . $value;
            }

            // 创建cookie字符串
            $this->headers('cookie', implode('; ', $cookieString));
        }

        $output = $this->method . ' ' . $this->uri . ' ' . $this->protocol . "\r\n";
        $output .= $this->message->headerLines;
        $output .= $body;

        return $output;
    }

    /**
     * 获取或者设置GET数据
     *
     * @param  mixed  $key   键名
     * @param  string $value 值
     * @return mixed|$this
     */
    public function query($key = null, $value = null)
    {
        if (is_array($key))
        {
            $this->_get = $key;
            return $this;
        }
        if (null === $key)
        {
            return $this->_get;
        }
        elseif (null === $value)
        {
            return Arr::path($this->_get, $key);
        }

        $this->_get[$key] = $value;
        return $this;
    }

    /**
     * 读取或者设置post数据
     *
     * @param  mixed  $key   键名
     * @param  string $value 值
     * @return mixed|$this
     */
    public function post($key = null, $value = null)
    {
        if (is_array($key))
        {
            $this->_post = $key;
            return $this;
        }
        if (null === $key)
        {
            return $this->_post;
        }
        elseif (null === $value)
        {
            return Arr::path($this->_post, $key);
        }

        $this->_post[$key] = $value;
        return $this;
    }

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

    /**
     * @param string $version
     * @return $this
     */
    public function withProtocolVersion($version)
    {
        $this->message->withProtocolVersion($version);
        return $this;
    }

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

    /**
     * {@inheritdoc}
     */
    public function hasHeader($name)
    {
        return $this->message->hasHeader($name);
    }

    /**
     * {@inheritdoc}
     */
    public function getHeaderLine($name)
    {
        return $this->message->getHeaderLine($name);
    }

    /**
     * @inheritdoc
     */
    public function withHeader($name, $value)
    {
        $this->message->withHeader($name, $value);
        return $this;
    }

    /**
     * @inheritdoc
     */
    public function withAddedHeader($name, $value)
    {
        $this->message->withAddedHeader($name, $value);
        return $this;
    }

    /**
     * @inheritdoc
     */
    public function withoutHeader($name)
    {
        $this->message->withoutHeader($name);
        return $this;
    }

    /**
     * @inheritdoc
     */
    public function getHeader($name)
    {
        return $this->headers($name);
    }

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

    /**
     * @inheritdoc
     */
    public function withRequestTarget($requestTarget)
    {
        $this->uri = $requestTarget;
        return $this;
    }

    /**
     * @inheritdoc
     */
    public function withMethod($method)
    {
        $this->method = $method;
        return $this;
    }

    /**
     * @inheritdoc
     */
    public function withUri(UriInterface $uri, $preserveHost = false)
    {
        $this->uri = (string) $uri;
        return $this;
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace Reaction\Validators;

use Reaction\Exceptions\InvalidConfigException;
use Reaction\Base\Model;
use Reaction\I18n\I18N;

/**
 * EachValidator validates an array by checking each of its elements against an embedded validation rule.
 *
 * ```php
 * class MyModel extends Model
 * {
 *     public $categoryIDs = [];
 *
 *     public function rules()
 *     {
 *         return [
 *             // checks if every category ID is an integer
 *             ['categoryIDs', 'each', 'rule' => ['integer']],
 *         ]
 *     }
 * }
 * ```
 *
 * > Note: This validator will not work with inline validation rules in case of usage outside the model scope,
 *   e.g. via [[validate()]] method.
 *
 * > Note: EachValidator is meant to be used only in basic cases, you should consider usage of tabular input,
 *   using several models for the more complex case.
 *
 * @author Paul Klimov <klimov.paul@gmail.com>
 * @since 2.0.4
 */
class EachValidator extends Validator
{
    /**
     * @var array|Validator definition of the validation rule, which should be used on array values.
     * It should be specified in the same format as at [[\yii\base\Model::rules()]], except it should not
     * contain attribute list as the first element.
     * For example:
     *
     * ```php
     * ['integer']
     * ['match', 'pattern' => '/[a-z]/is']
     * ```
     *
     * Please refer to [[\yii\base\Model::rules()]] for more details.
     */
    public $rule;
    /**
     * @var bool whether to use error message composed by validator declared via [[rule]] if its validation fails.
     * If enabled, error message specified for this validator itself will appear only if attribute value is not an array.
     * If disabled, own error message value will be used always.
     */
    public $allowMessageFromRule = true;
    /**
     * @var bool whether to stop validation once first error among attribute value elements is detected.
     * When enabled validation will produce single error message on attribute, when disabled - multiple
     * error messages mya appear: one per each invalid value.
     * Note that this option will affect only [[validateAttribute()]] value, while [[validateValue()]] will
     * not be affected.
     * @since 2.0.11
     */
    public $stopOnFirstError = true;

    /**
     * @var Validator validator instance.
     */
    private $_validator;

    /**
     * {@inheritdoc}
     */
    public function init()
    {
        parent::init();
        if ($this->message === null) {
            $this->message = \Reaction::t('rct', '{attribute} is invalid.');
        }
    }

    /**
     * Returns the validator declared in [[rule]].
     * @param Model|null $model model in which context validator should be created.
     * @return Validator the declared validator.
     */
    private function getValidator($model = null)
    {
        if ($this->_validator === null) {
            $this->_validator = $this->createEmbeddedValidator($model);
        }

        return $this->_validator;
    }

    /**
     * Creates validator object based on the validation rule specified in [[rule]].
     * @param Model|null $model model in which context validator should be created.
     * @throws \Reaction\Exceptions\InvalidConfigException
     * @return Validator validator instance
     */
    private function createEmbeddedValidator($model)
    {
        $rule = $this->rule;
        if ($rule instanceof Validator) {
            return $rule;
        } elseif (is_array($rule) && isset($rule[0])) { // validator type
            if (!is_object($model)) {
                $model = new Model(); // mock up context model
            }

            return Validator::createValidator($rule[0], $model, $this->attributes, array_slice($rule, 1));
        }

        throw new InvalidConfigException('Invalid validation rule: a rule must be an array specifying validator type.');
    }

    /**
     * {@inheritdoc}
     */
    public function validateAttribute($model, $attribute)
    {
        $value = $model->$attribute;
        if (!is_array($value) && !$value instanceof \ArrayAccess) {
            $this->addError($model, $attribute, $this->message, []);
            return;
        }

        $validator = $this->getValidator($model); // ensure model context while validator creation

        $detectedErrors = $model->getErrors($attribute);
        $filteredValue = $model->$attribute;
        foreach ($value as $k => $v) {
            $model->clearErrors($attribute);
            $model->$attribute = $v;
            if (!$validator->skipOnEmpty || !$validator->isEmpty($v)) {
                $validator->validateAttribute($model, $attribute);
            }
            $filteredValue[$k] = $model->$attribute;
            if ($model->hasErrors($attribute)) {
                if ($this->allowMessageFromRule) {
                    $validationErrors = $model->getErrors($attribute);
                    $detectedErrors = array_merge($detectedErrors, $validationErrors);
                } else {
                    $model->clearErrors($attribute);
                    $this->addError($model, $attribute, $this->message, ['value' => $v]);
                    $detectedErrors[] = $model->getFirstError($attribute);
                }
                $model->$attribute = $value;

                if ($this->stopOnFirstError) {
                    break;
                }
            }
        }

        $model->$attribute = $filteredValue;
        $model->clearErrors($attribute);
        $model->addErrors([$attribute => $detectedErrors]);
    }

    /**
     * {@inheritdoc}
     */
    protected function validateValue($value)
    {
        if (!is_array($value) && !$value instanceof \ArrayAccess) {
            return [$this->message, []];
        }

        $validator = $this->getValidator();
        foreach ($value as $v) {
            if ($validator->skipOnEmpty && $validator->isEmpty($v)) {
                continue;
            }
            $result = $validator->validateValue($v);
            if ($result !== null) {
                if ($this->allowMessageFromRule) {
                    $result[1]['value'] = $v;
                    return $result;
                }

                return [$this->message, ['value' => $v]];
            }
        }

        return null;
    }
}