<?php

namespace Orchid\Log\Entities;

use Carbon\Carbon;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use JsonSerializable;

class LogEntry implements Arrayable, Jsonable, JsonSerializable
{
    /**
     * @var string
     */
    public $env;

    /**
     * @var string
     */
    public $level;

    /**
     * @var \Carbon\Carbon
     */
    public $datetime;

    /**
     * @var string
     */
    public $header;

    /**
     * @var string
     */
    public $stack;

    /**
     * Construct the log entry instance.
     *
     * @param string $level
     * @param string $header
     * @param string $stack
     */
    public function __construct($level, $header, $stack)
    {
        $this->setLevel($level);
        $this->setHeader($header);
        $this->setStack($stack);
    }

    /**
     * Set the entry level.
     *
     * @param string $level
     *
     * @return self
     */
    private function setLevel($level)
    {
        $this->level = $level;

        return $this;
    }

    /**
     * Set the entry header.
     *
     * @param string $header
     *
     * @return self
     */
    private function setHeader($header)
    {
        $this->setDatetime($this->extractDatetime($header));

        $header = $this->cleanHeader($header);

        if (preg_match('/^[a-z]+.[A-Z]+:/', $header, $out)) {
            $this->setEnv($out[0]);
            $header = trim(str_replace($out[0], '', $header));
        }

        $this->header = $header;

        return $this;
    }

    /**
     * Set the entry date time.
     *
     * @param string $datetime
     *
     * @return \Orchid\Log\Entities\LogEntry
     */
    private function setDatetime($datetime)
    {
        $this->datetime = Carbon::createFromFormat('Y-m-d H:i:s', $datetime);

        return $this;
    }

    /**
     * Extract datetime from the header.
     *
     * @param string $header
     *
     * @return string
     */
    private function extractDatetime($header)
    {
        return preg_replace('/^\[('.REGEX_DATETIME_PATTERN.')\].*/', '$1', $header);
    }

    /**
     * Clean the entry header.
     *
     * @param string $header
     *
     * @return string
     */
    private function cleanHeader($header)
    {
        return preg_replace('/\['.REGEX_DATETIME_PATTERN.'\][ ]/', '', $header);
    }

    /**
     * Set entry environment.
     *
     * @param string $env
     *
     * @return self
     */
    private function setEnv($env)
    {
        $this->env = head(explode('.', $env));

        return $this;
    }

    /**
     * Set the entry stack.
     *
     * @param string $stack
     *
     * @return self
     */
    private function setStack($stack)
    {
        $this->stack = $stack;

        return $this;
    }

    /**
     * Get translated level name with icon.
     *
     * @return string
     */
    public function level()
    {
        return $this->icon().' '.$this->name();
    }

    /**
     * Get level icon.
     *
     * @return string
     */
    public function icon()
    {
        return log_styler()->icon($this->level);
    }

    /**
     * Get translated level name.
     *
     * @return string
     */
    public function name()
    {
        return log_levels()->get($this->level);
    }

    /**
     * Get the entry stack.
     *
     * @return string
     */
    public function stack()
    {
        return nl2br(htmlentities($this->stack), false);
    }

    /**
     * Check if same log level.
     *
     * @param string $level
     *
     * @return bool
     */
    public function isSameLevel($level)
    {
        return $this->level === $level;
    }

    /**
     * Convert the log entry to its JSON representation.
     *
     * @param int $options
     *
     * @return string
     */
    public function toJson($options = 0)
    {
        return json_encode($this->toArray(), $options);
    }

    /**
     * Get the log entry as an array.
     *
     * @return array
     */
    public function toArray()
    {
        return [
            'level'    => $this->level,
            'datetime' => $this->datetime->format('Y-m-d H:i:s'),
            'header'   => $this->header,
            'stack'    => $this->stack,
        ];
    }

    /**
     * Serialize the log entry object to json data.
     *
     * @return array
     */
    public function jsonSerialize()
    {
        return $this->toArray();
    }

    /**
     * Check if the entry has a stack.
     *
     * @return bool
     */
    public function hasStack()
    {
        return $this->stack !== "\n";
    }
}


<?php

/**
 * MvcCore
 *
 * This source file is subject to the BSD 3 License
 * For the full copyright and license information, please view
 * the LICENSE.md file that are distributed with this source code.
 *
 * @copyright	Copyright (c) 2016 Tom Flídr (https://github.com/mvccore/mvccore)
 * @license		https://mvccore.github.io/docs/mvccore/4.0.0/LICENCE.md
 */

namespace MvcCore\Ext\Debugs\Tracys;

/**
 * Responsibility - render used routes by application router and mark and render matched route with request params.
 */
class RoutingPanel implements \Tracy\IBarPanel
{
	/**
	 * MvcCore Extension - Debug - Tracy Panel - Routing - version:
	 * Comparison by PHP function version_compare();
	 * @see http://php.net/manual/en/function.version-compare.php
	 */
	const VERSION = '5.0.0-alpha';

	/**
	 * Reference to main application instance.
	 * @var \MvcCore\Application|\MvcCore\IApplication
	 */
	protected $app = NULL;

	/**
	 * Full class name for configured core tools class.
	 * @var string|NULL
	 */
	protected $toolClass = NULL;

	/**
	 * Reference to current application request instance.
	 * @var \MvcCore\Request|\MvcCore\IRequest
	 */
	protected $request = NULL;

	/**
	 * Currently requested language.
	 * @var string|NULL
	 */
	protected $requestLang = NULL;

	/**
	 * Currently requested params with escaped `<` and `>`.
	 * @var array
	 */
	protected $requestParams = [];

	/**
	 * Reference to current application router instance.
	 * @var \MvcCore\Router|\MvcCore\IRouter
	 */
	protected $router = NULL;

	/**
	 * Reference to all initialized application routes in router.
	 * @var \MvcCore\Route[]|\MvcCore\IRoute[]
	 */
	protected $routes = [];

	/**
	 * Reference to route matched by current request.
	 * @var \MvcCore\Route|\MvcCore\IRoute
	 */
	protected $currentRoute = NULL;

	/**
	 * Default language for possible multi-language router version.
	 * @var string|NULL
	 */
	protected $defaultLang = NULL;

	/**
	 * Prepared view data, only once,
	 * to render debug tab and debug panel content.
	 * @var \stdClass|NULL
	 */
	protected $view = NULL;

	/**
	 * Debug code for this panel, printed at panel bottom.
	 * @var string
	 */
	private $_debugCode = '';

	/**
	 * Return unique panel id.
	 * @return string
	 */
	public function getId() {
		return 'routing-panel';
	}
	/**
	 * Render tab (panel header).
	 * Set up view data if necessary.
	 * @return string
	 */
	public function getTab() {
		ob_start();
		$view = $this->getViewData();
		if ($view) include(__DIR__ . '/routing.tab.phtml');
		return ob_get_clean();
	}
	/**
	 * Render panel (panel content).
	 * Set up view data if necessary.
	 * @return string
	 */
	public function getPanel() {
		ob_start();
		$view = $this->getViewData();
		if ($view) include(__DIR__ . '/routing.panel.phtml');
		return ob_get_clean();
	}
	/**
	 * Set up view data, if data are completed,
	 * return them directly.
	 * - complete basic \MvcCore core objects to complete other view data
	 * - complete panel title
	 * - complete routes table items
	 * - set result data into static field
	 * @return object
	 */
	public function getViewData () {
		if ($this->view !== NULL) return $this->view;
		$this->view = new \stdClass;
		try {
			// complete basic \MvcCore core objects to complete other view data
			$this->initMainApplicationProperties();
			// those cases are only when request is redirected very soon
			if ($this->router === NULL) return $this->view;
			// complete panel title
			$this->initViewPanelTitle();
			// complete routes table items
			$this->initViewPanelTableData();
			// complete requested URL data under routes table
			$this->initViewPanelRequestedUrlData();
		} catch (\Exception $e) {
			$this->_debug($e);
			$this->_debug($e->getTrace());
		}
		// debug code
		$this->view->_debugCode = $this->_debugCode;
		return $this->view;
	}

	/**
	 * Initialize main application properties into current `$this`
	 * context: app, request, router, routes and current route.
	 * @return void
	 */
	protected function initMainApplicationProperties () {
		$this->app = & \MvcCore\Application::GetInstance();
		$this->router = & $this->app->GetRouter();
		$this->routes = & $this->router->GetRoutes();
		$this->currentRoute = $this->router->GetCurrentRoute();
		$this->request = & $this->app->GetRequest();
		$this->requestLang = $this->request->GetLang();
		$router = & $this->router;
		$ctrlParamName = $router::URL_PARAM_CONTROLLER;
		$actionParamName = $router::URL_PARAM_ACTION;
		$getParamsKeys = array_unique(array_merge(
			[$ctrlParamName => NULL, $actionParamName => NULL],
			$this->currentRoute ? $this->currentRoute->GetMatchedParams() : [],
			array_keys($_GET)
		));
		$this->requestParams = & $this->request->GetParams(['#[\<\>\'"]#' => ''], array_keys($getParamsKeys));
		if (method_exists($this->router, 'GetDefaultLang'))
			$this->defaultLang = $this->router->GetDefaultLang();
	}

	/**
	 * Initialize panel title. Set into panel title current route name
	 * with optional `Controller:Action` combination if route name is different from it
	 * and set `No route match` if no route matched by default.
	 * @return void
	 */
	protected function initViewPanelTitle () {
		$panelTitle = 'No route match';
		if ($this->currentRoute !== NULL)
			$panelTitle = $this->currentRoute->GetName();
		$this->view->panelTitle = htmlSpecialChars($panelTitle, ENT_NOQUOTES, 'UTF-8');
	}

	/**
	 * Complete routes table data by router routes.
	 * @return void
	 */
	protected function initViewPanelTableData () {
		$items = [];
		$currentRouteName = $this->currentRoute ? $this->currentRoute->GetName() : NULL;
		/** @var $route \MvcCore\IRoute */
		foreach ($this->routes as & $route) {
			$matched = FALSE;
			if ($currentRouteName !== NULL && $route->GetName() === $currentRouteName) {
				$matched = TRUE;
			}
			$items[] = $this->initViewPanelTableRow($route, $matched);
		}
		$this->view->items = $items;
	}

	/**
	 * Complete single route table row view data.
	 * @param \MvcCore\IRoute $route
	 * @param bool $matched
	 * @return \stdClass
	 */
	protected function initViewPanelTableRow (\MvcCore\IRoute & $route, $matched) {
		$route->InitAll();
		$row = new \stdClass;

		// first column
		$row->matched = $matched;

		// second column
		$row->method = $route->GetMethod();
		$row->method = $row->method === NULL ? '*' : $row->method;

		// third column
		$row->className = htmlSpecialChars('\\'.get_class($route), ENT_QUOTES, 'UTF-8');
		$routeMatch = $this->getRouteLocalizedRecord($route, 'GetMatch');
		$routeMatch = rtrim($routeMatch, 'imsxeADSUXJu'); // remove all modifiers
		$routeReverse = $this->getRouteLocalizedRecord($route, 'GetReverse');
		$routeDefaults = $this->getRouteLocalizedRecord($route, 'GetDefaults');
		$row->match = $this->completeFormatedPatternCharGroups($routeMatch, ['(', ')']);
		if ($routeReverse !== NULL) {
			$row->reverse = $this->completeFormatedPatternCharGroups($routeReverse, ['<', '>']);
		} else {
			$row->reverse = NULL;
		}

		// fourth column
		$row->routeName = $route->GetName();
		$row->ctrlActionName = $route->GetControllerAction();
		if ($row->ctrlActionName !== ':') {
			$row->ctrlActionLink = $this->completeCtrlActionLink($route->GetController(), $route->GetAction());
		} else {
			$row->ctrlActionName = NULL;
			$row->ctrlActionLink = NULL;
		}
		$routeReverseParams = $route->GetReverseParams() ?: []; // route could NULL reverse params when redirect route defined
		$paramsKeys = array_unique(array_merge($routeReverseParams, array_keys($routeDefaults)));
		$row->defaults = $this->completeParams($route, $paramsKeys, TRUE);

		// fifth column (only for matched route)
		$row->params = [];
		if ($matched) {
			$paramsAndReqestParams = array_merge($routeDefaults, $this->requestParams);
			$row->params = $this->completeParams($route, array_keys($paramsAndReqestParams), FALSE);
		}

		return $row;
	}

	/**
	 * Complete fourth column (and fifth if matched) params collection string for template.
	 * @param \MvcCore\IRoute $route
	 * @param array $paramsNames Array with param keys to render.
	 * @param bool  $useDefaults If `TRUE`, render params from route defaults, if `FALSE`, render params from request params.
	 * @return array
	 */
	protected function completeParams (\MvcCore\IRoute & $route, $paramsNames = [], $useDefaults = TRUE) {
		$result = [];
		if ($this->defaultLang !== NULL) {
			$result['lang'] = '"' . $this->requestLang . '"
';
		}
		if (!$paramsNames) return $result;
		$routeDefaults = $this->getRouteLocalizedRecord($route, 'GetDefaults');
		$routeDefaultsKeys = array_keys($routeDefaults);
		if ($useDefaults) {
			$paramValues = $routeDefaults;
		} else {
			$paramValues = $this->requestParams;
		}
		$router = & $this->router;
		$ctrlParamName = $router::URL_PARAM_CONTROLLER;
		$actionParamName = $router::URL_PARAM_ACTION;
		foreach ($paramsNames as $key => $paramName) {
			if ($paramName == $ctrlParamName || $paramName == $actionParamName) {
				if (!in_array($paramName, $routeDefaultsKeys, TRUE) && !isset($_GET[$paramName])) continue;
			}
			$paramValue = isset($paramValues[$paramName])
				? $paramValues[$paramName]
				: NULL;
			if ($key === 0 && $paramName === 0 && $paramValue === NULL) continue; // weird fix
			$paramNameEncoded = htmlSpecialChars($paramName, ENT_IGNORE, 'UTF-8');
			if ($paramValue === NULL) {
				$paramValueRendered = 'NULL
';
			} else if (is_string($paramValue)) {
				$paramValueRendered = '"'
					. htmlSpecialChars($paramValue, ENT_IGNORE, 'UTF-8')
					. '"
';
			} else {
				$paramValueRendered = \Tracy\Dumper::toHtml($paramValue, [
					\Tracy\Dumper::COLLAPSE => TRUE,
					\Tracy\Dumper::LIVE => TRUE
				]);
			}
			$result[$paramNameEncoded] = $paramValueRendered;
		}
		return $result;
	}

	/**
	 * Add into route regular expression pattern or reverse ($route->GetPattern()
	 * or $route->GetReverse()) around all detected character groups special
	 * html span elements to color them in template.
	 * @param string   $str	  route pattern string or reverse string
	 * @param string[] $brackets array with specified opening bracket and closing bracket type
	 * @return string
	 */
	protected function completeFormatedPatternCharGroups ($str, $brackets) {
		$matches = $this->completeMatchingBracketsPositions($str, $brackets[0], $brackets[1]);
		if ($matches) {
			$pos = 0;
			$result = '';
			foreach ($matches as $key => & $match) {
				list($subStr, $begin, $end) = $match;
				$result .= mb_substr($str, $pos, $begin - $pos);
				$result .= '';
				$result .= htmlSpecialChars($subStr, ENT_NOQUOTES, 'UTF-8');
				$result .= '';
				$pos = $end + 1;
			}
			$result .= mb_substr($str, $pos);
		} else {
			$result = $str;
		}
		return $result;
	}

	/**
	 * Complete collection with first level matching brackets,
	 * info about substrings between them and their opening and closing
	 * positions to complete task with character group colouring in
	 * local method $this->completeFormatedPatternOrReverseCharGroups().
	 * @param string $str	string to search brackets in
	 * @param string $begin	opening bracket char
	 * @param string $end	closing bracket char
	 * @return array[]
	 */
	protected function completeMatchingBracketsPositions ($str, $begin, $end) {
		$result = [];
		$i = 0;
		$l = mb_strlen($str);
		$matches = [];
		while ($i < $l) {
			$beginPos = mb_strpos($str, $begin, $i);
			$endPos = mb_strpos($str, $end, $i);
			$beginContained = $beginPos !== FALSE;
			$endContained = $endPos !== FALSE;
			if ($beginContained && $endContained) {
				if ($beginPos < $endPos) {
					$matches[] = [$begin, $beginPos];
					$i = $beginPos + 1;
				} else {
					$matches[] = [$end, $endPos];
					$i = $endPos + 1;
				}
			} else if ($beginContained) {
				$matches[] = [$begin, $beginPos];
				$i = $beginPos + 1;
			} else if ($endContained) {
				$matches[] = [$end, $endPos];
				$i = $endPos + 1;
			} else {
				break;
			}
		}
		if ($matches) {
			$level = 0;
			$groupBegin = -1;
			$paramLevel = 0;
			foreach ($matches as $item) {
				list($itemChar, $itemPos) = $item;
				$backSlashesCnt = 0;
				$backSlashPos = $itemPos - 1;
				while ($backSlashPos > -1 && true) {
					$prevChar = mb_substr($str, $backSlashPos, 1);
					if ($prevChar == '\\') {
						$backSlashesCnt += 1;
						$backSlashPos -= 1;
					} else {
						break;
					}
				}
				if (
					$backSlashesCnt === 0 || (
					($backSlashesCnt > 0 && $backSlashesCnt % 2 === 0)
				)) {
					if ($itemChar == $begin) {
						if ($begin == '(') {
							$itemCharNext = mb_substr($str, $itemPos + 1, 1);
							if ($itemCharNext !== '?') {
								$level += 1;
								continue;
							}
						}
						$paramLevel = $level;
						$groupBegin = $itemPos;
						$level += 1;
					} else {
						$level -= 1;
						if ($level === $paramLevel && $groupBegin > -1) {
							$result[] = [
								mb_substr($str, $groupBegin, $itemPos - $groupBegin + 1),
								$groupBegin,
								$itemPos
							];
						}
					}
				}
			}
		}
		return $result;
	}

	/**
	 * Try to complete editor link by controller class name and it's action
	 * by PHP reflection object if controller class exist and it's possible by
	 * loaded controller class to create reflection object to complete link properties.
	 * Result is returned as array containing:
	 *  0 - editor link URL
	 *  1 - link text
	 * @param string $ctrlName
	 * @param string $actionName
	 * @return array
	 */
	protected function completeCtrlActionLink ($ctrlName = '', $actionName = '') {
		$fullControllerClassName = '';
		static $controllersDir = NULL;
		if ($controllersDir === NULL) {
			$controllersDir = '\\' . implode('\\', [$this->app->GetAppDir(), $this->app->GetControllersDir()]) . '\\';
		}
		if (substr($ctrlName, 0, 2) == '//') {
			$fullControllerClassName = $ctrlName;
			$fullClassToSearch = substr($ctrlName, 2);
		} else {
			$fullControllerClassName = $controllersDir . $ctrlName;
			$fullClassToSearch = $fullControllerClassName;
		}
		$result = ['', $fullControllerClassName . ':' . $actionName . 'Action'];
		try {
			$ctrlReflection = new \ReflectionClass($fullClassToSearch);
			if ($ctrlReflection instanceof \ReflectionClass) {
				$file = $ctrlReflection->getFileName();
				$actionReflection = $ctrlReflection->getMethod($actionName . 'Action');
				if ($actionReflection instanceof \ReflectionMethod) {
					$line = $actionReflection->getStartLine();
					$result = [
						\Tracy\Helpers::editorUri($file, $line),
						$fullControllerClassName . ':' . $actionName . 'Action'
					];
				}
			}
		} catch (\Exception $e) {
		}
		return $result;
	}

	/**
	 * Get route non-localized or localized record - 'Pattern' and 'Reverse'
	 * @param \MvcCore\IRoute $route
	 * @param string $getter
	 * @return string|array
	 */
	protected function getRouteLocalizedRecord (\MvcCore\IRoute & $route, $getter) {
		$result = $route->$getter($this->requestLang);
		if ($result === NULL && $this->defaultLang !== NULL)
			$result = $route->$getter($this->defaultLang);
		return $result;
	}

	/**
	 * Complete data about requested URL under routes table.
	 * @return void
	 */
	protected function initViewPanelRequestedUrlData () {
		$req = & $this->request;
		$this->view->requestedUrl = (object) [
			'method'	=> htmlSpecialChars($req->GetMethod(), ENT_IGNORE, 'UTF-8'),
			'baseUrl'	=> htmlSpecialChars($req->GetBaseUrl(), ENT_IGNORE, 'UTF-8'),
			'path'		=> htmlSpecialChars($req->GetRequestPath(), ENT_IGNORE, 'UTF-8'),
		];
	}

	/**
	 * Print any variable in panel body under routes table.
	 * @param mixed $var
	 * @return void
	 */
	private function _debug ($var) {
		$this->_debugCode .= \Tracy\Dumper::toHtml($var, [
			\Tracy\Dumper::LIVE => TRUE
		]);
	}
}


<?php
/**
 * Created by PhpStorm.
 * User: sicmouse
 * Date: 2018/9/4
 * Time: 下午2:52
 */

namespace DdvPhp\DdvRestfulApi\Lib;

use DdvPhp\DdvRestfulApi\Util\ResponseParse;
use DdvPhp\DdvRestfulApi\Interfaces\RestfulApiEcho as RestfulApiEchoInterface;
use DdvPhp\DdvRestfulApi\Interfaces\RequestInfo as RequestInfoInterface;

class RestfulApiEcho extends RestfulApi implements RestfulApiEchoInterface
{
    /**
     * @var array
     */
    public $responseData = array(
        //错误识别码
        'errorId'=>'OK',
        //消息
        'message'=>'',
        //代码
        'code'=>0,
        //数据
        'data'=>null,
        //列表
        'lists'=>array(),
        //分页
        'page'=>null
    );
    /**
     * @var array
     */
    protected $corsConfig = array();

    /**
     * [onHandler 监听错误]
     * @author: 桦 <yuchonghua@163.com>
     * @DateTime 2017-04-26T18:55:58+0800
     * @return   [type]                   [description]
     */
    public function onHandler($r, $e)
    {
        if (isset($r['isIgnoreError']) && $r['isIgnoreError'] === true) {
            return;
        }
        if (!empty($r['responseData'])) {
            array_merge($r, $r['responseData']);
        }
        if (isset($r['responseData'])) unset($r['responseData']);
        if (!$this->isDevelopment()) {
            if (isset($r['debug'])) unset($r['debug']);
            if (isset($r['isIgnoreError'])) unset($r['isIgnoreError']);
        }
        return $this->echoStr($r);
    }

    public function isDevelopment()
    {
        $isDebug = defined('ENVIRONMENT') && ENVIRONMENT === 'development';
        $isDebug = $isDebug || function_exists('env') && env('APP_DEBUG');
        return $isDebug;
    }

    public function config($config = array())
    {
        if (!empty($config['cors'])){
            $this->corsConfig = $config['cors'];
        }
        if (!empty($config['headersPrefix'])){
            $this->authHeadersPrefix = $config['headersPrefix'];
        }
        $this->setConfig($config);
    }
    public function requestParse($parameters = null, $cookies = '', $files = null, $server = null, $headers = array())
    {
        $isCompleted = false;
        $requestInfo = false;
        // 获取ip
        $remoteAddress = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
        $remotePort = isset($_SERVER['REMOTE_PORT']) ? $_SERVER['REMOTE_PORT'] : '0';
        $requestStream = new HttpRequestStream();
        $requestStream
            ->baseInit(array(), $_SERVER)
            ->setRemoteInfo($remoteAddress, $remotePort, array(
                'remoteIp' => $remoteAddress,
                'remoteAddress' => $remoteAddress,
                'remotePort' => $remotePort
            ))
            // 解析完成
            ->onRequested(function (RequestInfoInterface $requestInfoInput) use (&$requestInfo, &$isCompleted) {
                $requestInfo = $requestInfoInput;
                $isCompleted = true;
                // 释放内存
                unset($isCompleted, $requestInfo, $requestInfoInput);
            });
        $fp = fopen('php://input', 'rb');
        while (!feof($fp)) {
            $requestStream->write(fread($fp, 512));
        }
        fclose($fp);
        // 判断是否有响应
        if (!$isCompleted || !($requestInfo instanceof RequestInfoInterface)) {
            throw new RequestParseError('ddvHttpRequestInfo Error', 'DDV_HTTP_REQUEST_INFO_ERROR', 500);
        }
        $this->setRequestInfo($requestInfo);
        unset($isCompleted, $requestInfo);
    }
    /**
     * [echoData 输出]
     * @author: 桦 <yuchonghua@163.com>
     * @DateTime 2017-04-26T18:56:12+0800
     */
    public function echoData($data)
    {
        $data = array_merge($this->responseData, $data);
        return $this->echoStr($data);
    }

    /**
     * [echo404 输出]
     * @author: 桦 <yuchonghua@163.com>
     * @DateTime 2017-04-26T18:56:12+0800
     */
    public function echo404($message = 'Api interface not found', $errorId = '404 Not Found', $statusCode = 404)
    {
        $responseData = array(
            'statusCode' => $statusCode,
            'code' => 0,
            'errorId' => $errorId,
            'message' => $message
        );
        return $this->echoData($responseData);
    }

    /**
     * [setHandler 设置错误监听]
     * @author: 桦 <yuchonghua@163.com>
     * @DateTime 2017-04-26T18:56:12+0800
     */
    public function useHandler()
    {
        \DdvPhp\DdvException\Handler::setHandler(function (array $r, $e) {
            $this->onHandler($r, $e);
        }, function () {
            return $this->isDevelopment();
        });
        return $this;
    }

    /**
     * @return RestfulApiInterface|RestfulApi
     */
    public function corsInit($config = array())
    {
        $this->corsConfig = array_merge($this->corsConfig, $config);
        $cors = new Cors();
        $cors->setRequestInfo($this->getRequestInfo());
        $cors->setConfig($this->corsConfig);
        if ($cors->checkAllow()) {
            foreach ($cors->getResponseHeaders() as $key => $value) {
                @header($key.': ' . $value);
            }
            if ($cors->isResponseOnlyHeader()){
                die;
            }
        }
    }
    public function initCors($config = array()){
        return $this->corsInit($config);
    }
    public function authSign(){
        return $this->sign();
    }

    /**
     * [setHandler 输出]
     * @author: 桦 <yuchonghua@163.com>
     * @DateTime 2017-04-26T18:56:12+0800
     */
    public function echoStr($data, $isEcho = true, $isAutoHeader = true, $isAutoSessionClose = true, $isAutoObClean = null, $isNotUnescapedUnicode = true)
    {
        // 关闭会话
        try {
            if ($isAutoSessionClose === true && function_exists('session_write_close')) {
                @session_write_close();
            }
        } catch (Exception $e) {
        }
        $isAutoObClean = !$this->isDevelopment();

        $statusCode = empty($data['statusCode']) ? (isset($data['errorId']) && $data['errorId'] !== 'OK' ? 500 : 200) : $data['statusCode'];
        $statusText = empty($data['errorId']) ? '' : $data['errorId'];
        $statusText = empty($statusText) ? (empty($data['statusText']) ? '' : $data['statusText']) : $statusText;
        $statusText = empty($statusText) ? (($statusCode >= 200 && $statusCode < 300) ? 'OK' : 'UNKNOWN_ERROR') : $statusText;
        if (function_exists('set_status_header')) {
            set_status_header($statusCode, $statusText);
        } else {
            try {
                //nginx模式
                if (strpos(PHP_SAPI, 'cgi') === 0) {
                    @header('Status: ' . $statusCode . ' ' . $statusText, TRUE);
                } else {
                    $serverProtocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
                    @header($serverProtocol . ' ' . $statusCode . ' ' . $statusText, TRUE, $statusCode);
                    unset($serverProtocol);
                }
            } catch (Exception $e) {
            }
        }
        if ($isAutoHeader === true) {
            @header('Content-Type:application/json;charset=utf-8', true);
        }
        if ($isAutoObClean === true) {
            try {
                ob_clean();
            } catch (Exception $e) {
            }
        }
        $data['data'] = empty($data['data']) ? (object)array() : $data['data'];
        $data['page'] = empty($data['page']) ? (object)array() : $data['page'];
        if ($isEcho === true) {
            echo self::toJsonString($data);
            die();
        } else {
            return self::toJsonString($data);
        }
    }
    //获取签名信息
    public static function toJsonString($data, $isNotUnescapedUnicode = true)
    {
        if ($isNotUnescapedUnicode !== true) {
            $r = json_encode($data);
        } else {
            if (version_compare(PHP_VERSION, '5.4.0', '<')) {
                $r = json_encode($data);
                $r = preg_replace_callback(
                    "#\\\u([0-9a-f]{4})#i",
                    function ($matchs) {
                        return iconv('UCS-2BE', 'UTF-8', pack('H4', $matchs[1]));
                    },
                    $r
                );
            } else {
                $r = json_encode($data, JSON_UNESCAPED_UNICODE);
            }
        }
        return $r;
    }
}


<?php

namespace futuretek\shared;

/**
 * Class UrlBuilder
 *
 * @package futuretek\shared
 * @author  Lukas Cerny <lukas.cerny@futuretek.cz>
 * @license https://www.apache.org/licenses/LICENSE-2.0.html Apache-2.0
 * @link    http://www.futuretek.cz
 */
class UrlBuilder
{
    /** @var array */
    private $_urlParts;

    /**
     * UrlBuilder constructor.
     *
     * @param string $url URL to manipulate
     */
    public function __construct($url)
    {
        $this->_urlParts = parse_url($url);
        if (!array_key_exists('query', $this->_urlParts)) {
            $this->_urlParts['query'] = null;
        }
    }

    /**
     * Return complete URL
     *
     * @return string
     */
    public function returnUrl()
    {
        $return = '';

        $return .= empty($this->_urlParts['scheme']) ? '' : $this->_urlParts['scheme'] . '://';

        $return .= empty($this->_urlParts['user']) ? '' : $this->_urlParts['user'];
        $return .= empty($this->_urlParts['pass']) || empty($this->_urlParts['user']) ? '' : ':' . $this->_urlParts['pass'];

        $return .= empty($this->_urlParts['user']) ? '' : '@';

        $return .= empty($this->_urlParts['host']) ? '' : $this->_urlParts['host'];
        $return .= empty($this->_urlParts['port']) ? '' : ':' . $this->_urlParts['port'];

        $return .= empty($this->_urlParts['path']) ? '' : '/' . ltrim($this->_urlParts['path'], '/');

        $return .= empty($this->_urlParts['query']) ? '' : '?' . $this->_urlParts['query'];

        $return .= empty($this->_urlParts['fragment']) ? '' : '#' . $this->_urlParts['fragment'];

        return $return;
    }

    /**
     * Change URL path part
     *
     * @param string $path URL path part
     * @return $this
     */
    public function changePath($path)
    {
        $this->_urlParts['path'] = $path;

        return $this;
    }

    /**
     * Edit existing query parameter
     *
     * @param string $name Parameter name
     * @param string $value New value
     * @return $this
     */
    public function editQuery($name, $value)
    {
        $parts = explode('&', $this->_urlParts['query']);
        $return = [];
        foreach ($parts as $p) {
            $paramData = explode('=', $p);
            if ($paramData[0] === $name) {
                $paramData[1] = $value;
            }
            $return[] = implode('=', $paramData);
        }

        $this->_urlParts['query'] = implode('&', $return);

        return $this;
    }

    /**
     * Add new query parameter
     *
     * @param string $name Parameter name
     * @param string $value Parameter value
     * @return $this
     */
    public function addQuery($name, $value)
    {
        $part = $name . '=' . $value;

        $this->_urlParts['query'] .= empty($this->_urlParts['query']) ? $part : '&' . $part;

        return $this;
    }

    /**
     * Check if parameter is present in query
     *
     * @param string $name Parameter name
     * @return bool
     */
    public function checkQuery($name)
    {
        $parts = explode('&', $this->_urlParts['query']);

        foreach ($parts as $p) {
            $paramData = explode('=', $p);
            if ($paramData[0] === $name) {
                return true;
            }
        }

        return false;
    }

    /**
     * Set value of existing query parameter or create new one
     *
     * @param string $name Parameter name
     * @param string $value Parameter value
     * @return $this
     */
    public function setQueryParam($name, $value)
    {
        if ($this->checkQuery($name)) {
            $this->editQuery($name, $value);
        } else {
            $this->addQuery($name, $value);
        }

        return $this;
    }
}


<?php
//
// +---------------------------------------------------------------------+
// | CODE INC. SOURCE CODE                                               |
// +---------------------------------------------------------------------+
// | Copyright (c) 2017 - Code Inc. SAS - All Rights Reserved.           |
// | Visit https://www.codeinc.fr for more information about licensing.  |
// +---------------------------------------------------------------------+
// | NOTICE:  All information contained herein is, and remains the       |
// | property of Code Inc. SAS. The intellectual and technical concepts  |
// | contained herein are proprietary to Code Inc. SAS are protected by  |
// | trade secret or copyright law. Dissemination of this information or |
// | reproduction of this material  is strictly forbidden unless prior   |
// | written permission is obtained from Code Inc. SAS.                  |
// +---------------------------------------------------------------------+
//
// Author:   Joan Fabrégat <joan@codeinc.fr>
// Date:     23/02/2018
// Time:     18:59
// Project:  Psr15Middlewares
//
declare(strict_types = 1);
namespace CodeInc\Psr15Middlewares\HttpHeaders;
use CodeInc\Psr15Middlewares\Tests\HttpHeaders\AddHttpHeadersMiddlewareTest;
use function GuzzleHttp\Psr7\str;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

/**
 * Class AddHttpHeadersMiddleware
 *
 * @see AddHttpHeadersMiddlewareTest
 * @package CodeInc\Psr15Middlewares\HttpHeaders
 * @author Joan Fabrégat <joan@codeinc.fr>
 */
class AddHttpHeadersMiddleware implements MiddlewareInterface
{
	/**
	 * @var string[]
	 */
	private $headers = [];

    /**
     * AddHttpHeadersMiddleware constructor.
     *
     * @param array|null $headers
     */
	public function __construct(?array $headers = null)
    {
        if ($headers) {
            $this->addHeaders($headers);
        }
    }

    /**
     * Adds multiple headers.
     *
     * @param array $headers
     */
    public function addHeaders(array $headers):void
    {
        foreach ($headers as $name => $value) {
            $this->addHeader((string)$name, $value);
        }
    }

    /**
     * Adds one header.
     *
     * @param string $header
     * @param string|array|iterable $value
     */
	public function addHeader(string $header, $value):void
	{
	    if (is_iterable($value) || is_array($value)) {
	        $iterable = $value;
	        $value = [];
	        foreach ($iterable as $key => $entry) {
                $value[$key] = (string)$entry;
            }
        }
        else {
	        $value = (string)$value;
        }

		$this->headers[] = [$header, $value];
	}

	/**
	 * @inheritdoc
	 */
	public function process(ServerRequestInterface $request, RequestHandlerInterface $handler):ResponseInterface
	{
		$response = $handler->handle($request);

		// adding HTTP headers
		foreach ($this->headers as [$header, $value]) {
		    $response = $response->withHeader($header, $value);
		}

		return $response;
	}
}


<?php
/*
 * Copyright (c) 2014 @trashtoy
 * https://github.com/trashtoy/
 *
 * 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.
 */
/**
 * PHP class file.
 * @auhtor trashtoy
 * @since  2.0.0
 */
namespace Peach\Markup;
use Peach\Util\Values;

/**
 * ノードの配列をあらわすクラスです.
 *
 * このクラスは Component を実装しているため,
 * {@link Context::handle()} の引数に渡すことが出来ます.
 * (実際の処理は {@link Context::handleNodeList()} で行われます)
 *
 * ある要素に対して NodeList を追加した場合, このオブジェクト自体ではなく,
 * このオブジェクトに含まれる各ノードが追加されます.
 *
 * 例えるならば DOM の NodeList と NodeFragment を兼任するクラスです.
 */
class NodeList implements Container
{
    /**
     * Node の配列です.
     * @var array
     */
    private $nodeList;

    /**
     * この NodeList を持つノードです. 存在しない場合は null となります.
     * @var Node
     */
    private $owner;

    /**
     * 新しい NodeList を生成します.
     * 引数に値を設定した場合, その値をリストに追加した状態で初期化します.
     *
     * @param Component|array|string $var 追加するノード
     * @param Node $owner この NodeList を内部に持つ Node オブジェクト (エンドユーザーが直接使用することはありません)
     */
    public function __construct($var = null, Node $owner = null)
    {
        $this->nodeList = array();
        $this->owner    = $owner;
        $this->appendNode($var);
    }

    /**
     * この NodeList に実際に追加される値を返します.
     *
     * @param  mixed $var
     * @return Node[] 追加されるノードの配列
     */
    private function prepareAppendee($var)
    {
        if ($var instanceof Node) {
            return $var;
        }
        if ($var instanceof Component) {
            return $var->getAppendee()->getChildNodes();
        }

        if (is_array($var)) {
            $result = array();
            foreach ($var as $i) {
                $appendee = $this->prepareAppendee($i);
                if (is_array($appendee)) {
                    array_splice($result, count($result), 0, $appendee);
                } else {
                    $result[] = $appendee;
                }
            }
            return $result;
        }
        if (!isset($var)) {
            return array();
        }

        return array(new Text(Values::stringValue($var)));
    }

    /**
     * この NodeList の末尾に引数の値を追加します.
     *
     * 引数がノードの場合は, 引数をそのまま NodeList の末尾に追加します.
     *
     * 引数が Container の場合は, その Container に含まれる各ノードを追加します.
     * (Container 自身は追加されません)
     *
     * 引数が配列の場合は, 配列に含まれる各ノードをこの NodeList に追加します.
     *
     * 引数がノードではない場合は, 引数の文字列表現をテキストノードとして追加します.
     *
     * @param Node|Container|array|string $var
     */
    public function appendNode($var)
    {
        $appendee = $this->prepareAppendee($var);
        if (isset($this->owner)) {
            $this->checkOwner($appendee);
        }
        if (is_array($appendee)) {
            $this->nodeList = array_merge($this->nodeList, $appendee);
        } else {
            $this->nodeList[] = $appendee;
        }
    }

    /**
     * 指定された Context にこのノードを処理させます.
     * {@link Context::handleNodeList()} を呼び出します.
     * @param Context $context
     */
    public function accept(Context $context)
    {
        $context->handleNodeList($this);
    }

    /**
     * この NodeList に子ノードを追加する際に,
     * 親子関係が無限ループしないかどうか検査します.
     * 引数がこの NodeList のオーナーだった場合に InvalidArgumentException をスローします.
     * 引数が配列もしくは {@link Container} だった場合は,
     * その子ノードの一覧について再帰的に検査します.
     *
     * @param  mixed $var 検査対象
     * @throws \InvalidArgumentException 検査対象にこの NodeList のオーナーが含まれていた場合
     */
    private function checkOwner($var)
    {
        if (is_array($var)) {
            foreach ($var as $i) {
                $this->checkOwner($i);
            }
            return;
        }

        if ($var instanceof Container) {
            $this->checkOwner($var->getChildNodes());
        }
        if ($var === $this->owner) {
            throw new \InvalidArgumentException("Tree-loop detected.");
        }
    }

    /**
     * この NodeList に含まれるノードの個数を返します.
     * @return int ノードの個数
     */
    public function size()
    {
        return count($this->nodeList);
    }

    /**
     * この NodeList に含まれるノードの一覧を配列で返します.
     * @return array
     */
    public function getChildNodes()
    {
        return $this->nodeList;
    }

    /**
     * この NodeList 自身を返します.
     * この NodeList が Container に追加される場合,
     * このオブジェクトの代わりに NodeList に含まれる各ノードが追加されます.
     *
     * @return Component このオブジェクト
     */
    public function getAppendee()
    {
        return $this;
    }
}


<?php
/**
 * Created by PhpStorm.
 * User: miky
 * Date: 22/08/16
 * Time: 22:40
 */

namespace Miky\Bundle\UserBundle\Controller\Frontend;

use FOS\UserBundle\Model\UserInterface;
use Miky\Bundle\NewsletterBundle\Entity\NewsletterSubscriber;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\AccountStatusException;

class RegistrationController extends Controller
{
    /**
     * @return RedirectResponse
     */
    public function registerAction()
    {
        $user = $this->getUser();
        if ($user instanceof UserInterface) {
            $this->get('session')->getFlashBag()->set('sonata_user_error', 'sonata_user_already_authenticated');
            return $this->redirect($this->generateUrl('miky_app_profile_edit'));
        }
        $form = $this->get('sonata.user.registration.form')->add("newsletter", CheckboxType::class, array(
            "mapped" => false,
            "required" => false,
            "data" => true,
        ));
        $formHandler = $this->get('miky_user.registration_customer.form.handler');

        $process = $formHandler->process();
        if ($process) {
            $user = $form->getData();
            $authUser = false;
            if ($form->get("newsletter")->getData()){
                if ($user->getEmail() != null){
                    $subscriber = new NewsletterSubscriber();
                    $subscriber->setEmail($user->getEmail());
                    $em = $this->get("doctrine.orm.entity_manager");
                    $repository = $em->getRepository(NewsletterSubscriber::class);
                    if ($repository->findOneBy(array("email" => $subscriber->getEmail())) == null){
                        $em->persist($subscriber);
                        $em->flush();
                        $this->addFlash('success', 'miky.ui.newsletter_subscribe_success');
                    }else{
                        $this->addFlash('success', 'miky.ui.newsletter_subscribe_yet');
                    }
                }
            }
                $this->get('session')->set('fos_user_send_confirmation_email/email', $user->getEmail());
                $url = $this->generateUrl('miky_app_customer_registration_check_email');

            $this->setFlash('fos_user_success', 'registration.flash.user_created');
            $response = new RedirectResponse($url);
            if ($authUser) {
                $this->authenticateUser($user, $response);
            }
            return $response;
        }
        $this->get('session')->set('sonata_user_redirect_url', $this->get('request')->headers->get('referer'));
        return $this->render('@MikyUser/Frontend/Registration/register.html.twig', array(
            'form' => $form->createView(),
        ));
    }

    /**
     * Tell the user to check his email provider.
     *
     * @return Response
     *
     * @throws NotFoundHttpException
     */
    public function checkEmailAction()
    {
        $email = $this->get('session')->get('fos_user_send_confirmation_email/email');
        $this->get('session')->remove('fos_user_send_confirmation_email/email');
        $user = $this->get('fos_user.user_manager')->findUserByEmail($email);
        if (null === $user) {
            throw new NotFoundHttpException(sprintf('The user with email "%s" does not exist', $email));
        }
        return $this->render('@MikyUser/Frontend/Registration/checkEmail.html.twig', array(
            'user' => $user,
        ));
    }

    /**
     * Receive the confirmation token from user email provider, login the user.
     *
     * @param string $token
     *
     * @return RedirectResponse
     *
     * @throws NotFoundHttpException
     */
    public function confirmAction($token)
    {
        $user = $this->get('fos_user.user_manager')->findUserByConfirmationToken($token);
        if (null === $user) {
            throw new NotFoundHttpException(sprintf('The user with confirmation token "%s" does not exist', $token));
        }
        $user->setConfirmationToken(null);
        $user->setEnabled(true);
        $user->setLastLogin(new \DateTime());
        $this->get('fos_user.user_manager')->updateUser($user);

        $response = $this->redirect($this->generateUrl('miky_app_customer_registration_confirmed'));

        $this->authenticateUser($user, $response);
        return $response;
    }

    /**
     * Tell the user his account is now confirmed.
     *
     * @return Response
     *
     * @throws AccessDeniedException
     */
    public function confirmedAction()
    {
        $user = $this->getUser();
        if (!is_object($user) || !$user instanceof UserInterface) {
            throw $this->createAccessDeniedException('This user does not have access to this section.');
        }
        return $this->render("@MikyUser/Frontend/Registration/confirmed.html.twig", array(
            'user' => $user,
        ));
    }

    /**
     * Authenticate a user with Symfony Security.
     *
     * @param UserInterface $user
     * @param Response $response
     */
    protected function authenticateUser(UserInterface $user, Response $response)
    {
        try {
            $this->get('fos_user.security.login_manager')->loginUser(
                $this->container->getParameter('fos_user.firewall_name'),
                $user,
                $response);
        } catch (AccountStatusException $ex) {
            // We simply do not authenticate users which do not pass the user
            // checker (not enabled, expired, etc.).
        }
    }

    /**
     * @param string $action
     * @param string $value
     */
    protected function setFlash($action, $value)
    {
        $this->get('session')->getFlashBag()->set($action, $value);
    }

    /**
     * @return string
     */
    protected function getEngine()
    {
        return $this->container->getParameter('fos_user.template.engine');
    }
}


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

namespace yii\web;

use Yii;
use yii\base\Object;
use yii\base\InvalidConfigException;

/**
 * UrlRule represents a rule used by [[UrlManager]] for parsing and generating URLs.
 *
 * To define your own URL parsing and creation logic you can extend from this class
 * and add it to [[UrlManager::rules]] like this:
 *
 * ```php
 * 'rules' => [
 *     ['class' => 'MyUrlRule', 'pattern' => '...', 'route' => 'site/index', ...],
 *     // ...
 * ]
 * ```
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class UrlRule extends Object implements UrlRuleInterface
{
    /**
     * Set [[mode]] with this value to mark that this rule is for URL parsing only
     */
    const PARSING_ONLY = 1;
    /**
     * Set [[mode]] with this value to mark that this rule is for URL creation only
     */
    const CREATION_ONLY = 2;

    /**
     * @var string the name of this rule. If not set, it will use [[pattern]] as the name.
     */
    public $name;
    /**
     * On the rule initialization, the [[pattern]] matching parameters names will be replaced with [[placeholders]].
     * @var string the pattern used to parse and create the path info part of a URL.
     * @see host
     * @see placeholders
     */
    public $pattern;
    /**
     * @var string the pattern used to parse and create the host info part of a URL (e.g. `http://example.com`).
     * @see pattern
     */
    public $host;
    /**
     * @var string the route to the controller action
     */
    public $route;
    /**
     * @var array the default GET parameters (name => value) that this rule provides.
     * When this rule is used to parse the incoming request, the values declared in this property
     * will be injected into $_GET.
     */
    public $defaults = [];
    /**
     * @var string the URL suffix used for this rule.
     * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
     * If not, the value of [[UrlManager::suffix]] will be used.
     */
    public $suffix;
    /**
     * @var string|array the HTTP verb (e.g. GET, POST, DELETE) that this rule should match.
     * Use array to represent multiple verbs that this rule may match.
     * If this property is not set, the rule can match any verb.
     * Note that this property is only used when parsing a request. It is ignored for URL creation.
     */
    public $verb;
    /**
     * @var integer a value indicating if this rule should be used for both request parsing and URL creation,
     * parsing only, or creation only.
     * If not set or 0, it means the rule is both request parsing and URL creation.
     * If it is [[PARSING_ONLY]], the rule is for request parsing only.
     * If it is [[CREATION_ONLY]], the rule is for URL creation only.
     */
    public $mode;
    /**
     * @var boolean a value indicating if parameters should be url encoded.
     */
    public $encodeParams = true;
    /**
     * @var array list of placeholders for matching parameters names. Used in [[parseRequest()]], [[createUrl()]]
     * On the rule initialization, the [[pattern]] parameters names will be replaced with placeholders.
     * This array contains relations between the original parameters names and their placeholders.
     *  key - placeholder
     *  value - original name
     *
     * @see parseRequest()
     * @see createUrl()
     * @since 2.0.7
     */
    protected $placeholders = [];
    /**
     * @var string the template for generating a new URL. This is derived from [[pattern]] and is used in generating URL.
     */
    private $_template;
    /**
     * @var string the regex for matching the route part. This is used in generating URL.
     */
    private $_routeRule;
    /**
     * @var array list of regex for matching parameters. This is used in generating URL.
     */
    private $_paramRules = [];
    /**
     * @var array list of parameters used in the route.
     */
    private $_routeParams = [];

    /**
     * Initializes this rule.
     */
    public function init()
    {
        if ($this->pattern === null) {
            throw new InvalidConfigException('UrlRule::pattern must be set.');
        }
        if ($this->route === null) {
            throw new InvalidConfigException('UrlRule::route must be set.');
        }
        if ($this->verb !== null) {
            if (is_array($this->verb)) {
                foreach ($this->verb as $i => $verb) {
                    $this->verb[$i] = strtoupper($verb);
                }
            } else {
                $this->verb = [strtoupper($this->verb)];
            }
        }
        if ($this->name === null) {
            $this->name = $this->pattern;
        }

        $this->pattern = trim($this->pattern, '/');
        $this->route = trim($this->route, '/');

        if ($this->host !== null) {
            $this->host = rtrim($this->host, '/');
            $this->pattern = rtrim($this->host . '/' . $this->pattern, '/');
        } elseif ($this->pattern === '') {
            $this->_template = '';
            $this->pattern = '#^$#u';

            return;
        } elseif (($pos = strpos($this->pattern, '://')) !== false) {
            if (($pos2 = strpos($this->pattern, '/', $pos + 3)) !== false) {
                $this->host = substr($this->pattern, 0, $pos2);
            } else {
                $this->host = $this->pattern;
            }
        } else {
            $this->pattern = '/' . $this->pattern . '/';
        }

        if (strpos($this->route, '<') !== false && preg_match_all('/<([\w._-]+)>/', $this->route, $matches)) {
            foreach ($matches[1] as $name) {
                $this->_routeParams[$name] = "<$name>";
            }
        }

        $tr = [
            '.' => '\\.',
            '*' => '\\*',
            '$' => '\\$',
            '[' => '\\[',
            ']' => '\\]',
            '(' => '\\(',
            ')' => '\\)',
        ];

        $tr2 = [];
        if (preg_match_all('/<([\w._-]+):?([^>]+)?>/', $this->pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
            foreach ($matches as $match) {
                $name = $match[1][0];
                $pattern = isset($match[2][0]) ? $match[2][0] : '[^\/]+';
                $placeholder = 'a' . hash('crc32b', $name); // placeholder must begin with a letter
                $this->placeholders[$placeholder] = $name;
                if (array_key_exists($name, $this->defaults)) {
                    $length = strlen($match[0][0]);
                    $offset = $match[0][1];
                    if ($offset > 1 && $this->pattern[$offset - 1] === '/' && (!isset($this->pattern[$offset + $length]) || $this->pattern[$offset + $length] === '/')) {
                        $tr["/<$name>"] = "(/(?P<$placeholder>$pattern))?";
                    } else {
                        $tr["<$name>"] = "(?P<$placeholder>$pattern)?";
                    }
                } else {
                    $tr["<$name>"] = "(?P<$placeholder>$pattern)";
                }
                if (isset($this->_routeParams[$name])) {
                    $tr2["<$name>"] = "(?P<$placeholder>$pattern)";
                } else {
                    $this->_paramRules[$name] = $pattern === '[^\/]+' ? '' : "#^$pattern$#u";
                }
            }
        }

        $this->_template = preg_replace('/<([\w._-]+):?([^>]+)?>/', '<$1>', $this->pattern);
        $this->pattern = '#^' . trim(strtr($this->_template, $tr), '/') . '$#u';

        if (!empty($this->_routeParams)) {
            $this->_routeRule = '#^' . strtr($this->route, $tr2) . '$#u';
        }
    }

    /**
     * Parses the given request and returns the corresponding route and parameters.
     * @param UrlManager $manager the URL manager
     * @param Request $request the request component
     * @return array|boolean the parsing result. The route and the parameters are returned as an array.
     * If false, it means this rule cannot be used to parse this path info.
     */
    public function parseRequest($manager, $request)
    {
        if ($this->mode === self::CREATION_ONLY) {
            return false;
        }

        if (!empty($this->verb) && !in_array($request->getMethod(), $this->verb, true)) {
            return false;
        }

        $pathInfo = $request->getPathInfo();
        $suffix = (string)($this->suffix === null ? $manager->suffix : $this->suffix);
        if ($suffix !== '' && $pathInfo !== '') {
            $n = strlen($suffix);
            if (substr_compare($pathInfo, $suffix, -$n, $n) === 0) {
                $pathInfo = substr($pathInfo, 0, -$n);
                if ($pathInfo === '') {
                    // suffix alone is not allowed
                    return false;
                }
            } else {
                return false;
            }
        }

        if ($this->host !== null) {
            $pathInfo = strtolower($request->getHostInfo()) . ($pathInfo === '' ? '' : '/' . $pathInfo);
        }

        if (!preg_match($this->pattern, $pathInfo, $matches)) {
            return false;
        }
        $matches = $this->substitutePlaceholderNames($matches);

        foreach ($this->defaults as $name => $value) {
            if (!isset($matches[$name]) || $matches[$name] === '') {
                $matches[$name] = $value;
            }
        }
        $params = $this->defaults;
        $tr = [];
        foreach ($matches as $name => $value) {
            if (isset($this->_routeParams[$name])) {
                $tr[$this->_routeParams[$name]] = $value;
                unset($params[$name]);
            } elseif (isset($this->_paramRules[$name])) {
                $params[$name] = $value;
            }
        }
        if ($this->_routeRule !== null) {
            $route = strtr($this->route, $tr);
        } else {
            $route = $this->route;
        }

        Yii::trace("Request parsed with URL rule: {$this->name}", __METHOD__);

        return [$route, $params];
    }

    /**
     * Creates a URL according to the given route and parameters.
     * @param UrlManager $manager the URL manager
     * @param string $route the route. It should not have slashes at the beginning or the end.
     * @param array $params the parameters
     * @return string|boolean the created URL, or false if this rule cannot be used for creating this URL.
     */
    public function createUrl($manager, $route, $params)
    {
        if ($this->mode === self::PARSING_ONLY) {
            return false;
        }

        $tr = [];

        // match the route part first
        if ($route !== $this->route) {
            if ($this->_routeRule !== null && preg_match($this->_routeRule, $route, $matches)) {
                $matches = $this->substitutePlaceholderNames($matches);
                foreach ($this->_routeParams as $name => $token) {
                    if (isset($this->defaults[$name]) && strcmp($this->defaults[$name], $matches[$name]) === 0) {
                        $tr[$token] = '';
                    } else {
                        $tr[$token] = $matches[$name];
                    }
                }
            } else {
                return false;
            }
        }

        // match default params
        // if a default param is not in the route pattern, its value must also be matched
        foreach ($this->defaults as $name => $value) {
            if (isset($this->_routeParams[$name])) {
                continue;
            }
            if (!isset($params[$name])) {
                return false;
            } elseif (strcmp($params[$name], $value) === 0) { // strcmp will do string conversion automatically
                unset($params[$name]);
                if (isset($this->_paramRules[$name])) {
                    $tr["<$name>"] = '';
                }
            } elseif (!isset($this->_paramRules[$name])) {
                return false;
            }
        }

        // match params in the pattern
        foreach ($this->_paramRules as $name => $rule) {
            if (isset($params[$name]) && !is_array($params[$name]) && ($rule === '' || preg_match($rule, $params[$name]))) {
                $tr["<$name>"] = $this->encodeParams ? urlencode($params[$name]) : $params[$name];
                unset($params[$name]);
            } elseif (!isset($this->defaults[$name]) || isset($params[$name])) {
                return false;
            }
        }

        $url = trim(strtr($this->_template, $tr), '/');
        if ($this->host !== null) {
            $pos = strpos($url, '/', 8);
            if ($pos !== false) {
                $url = substr($url, 0, $pos) . preg_replace('#/+#', '/', substr($url, $pos));
            }
        } elseif (strpos($url, '//') !== false) {
            $url = preg_replace('#/+#', '/', $url);
        }

        if ($url !== '') {
            $url .= ($this->suffix === null ? $manager->suffix : $this->suffix);
        }

        if (!empty($params) && ($query = http_build_query($params)) !== '') {
            $url .= '?' . $query;
        }

        return $url;
    }

    /**
     * Returns list of regex for matching parameter.
     * @return array parameter keys and regexp rules.
     *
     * @since 2.0.6
     */
    protected function getParamRules()
    {
        return $this->_paramRules;
    }

    /**
     * Iterates over [[placeholders]] and checks whether each placeholder exists as a key in $matches array.
     * When found - replaces this placeholder key with a appropriate name of matching parameter.
     * Used in [[parseRequest()]], [[createUrl()]].
     *
     * @param array $matches result of `preg_match()` call
     * @return array input array with replaced placeholder keys
     * @see placeholders
     * @since 2.0.7
     */
     protected function substitutePlaceholderNames (array $matches) {
        foreach ($this->placeholders as $placeholder => $name) {
            if (isset($matches[$placeholder])) {
                $matches[$name] = $matches[$placeholder];
                unset($matches[$placeholder]);
            }
        }
        return $matches;
    }
}


<?php
/*
This is part of Wedeto, the WEb DEvelopment TOolkit.
It is published under the MIT Open Source License.

Copyright 2018, Egbert van der Wal

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.
*/

namespace Wedeto\Application\Plugins;

use Wedeto\Util\DI\BasicFactory;
use Wedeto\Util\DI\DI;
use Wedeto\Util\Functions as WF;
use Wedeto\Util\Hook;
use Wedeto\Util\Dictionary;

use Wedeto\Log\Logger;

use Wedeto\Application\Application;
use Wedeto\DB\Migrate\Repository;
use Wedeto\DB\Migrate\Module;

use Wedeto\Application\Task\TaskRunner;
use Wedeto\Application\Task\TaskInterface;

/**
 * Plugin that connects the migrations of Wedeto\DB to the application, to make
 * sure that all modules have their migrations automatically registered.
 */
class DBMigratePlugin implements WedetoPlugin
{
    private $app;

    public function initialize(Application $app)
    {
        $this->app = $app;
        $app->injector->registerFactory(Repository::class, new BasicFactory([$this, "createMigrateRepository"]));

        Hook::subscribe("Wedeto.Application.Task.TaskRunner.findTasks", [$this, 'registerTasks']);
    }

    public function registerTasks(Dictionary $params)
    {
        $taskRunner = $params['taskrunner'];
        $taskRunner->registerTask(MigrationRunner::class, 'Run database migrations');
    }

    /**
     * Factory method to create the DB\Migrate\Repository instance. Registered with Wedeto\DI to
     * make sure that new instances are created here.
     */
    public function createMigrateRepository(array $args)
    {
        $db = $this->app->db;
        $repo = new Repository($db);

        // Add all module paths to the Migration object
        $resolver = $this->app->resolver->getResolver("migrations");
        $mods = [];

        foreach ($resolver->getSearchPath() as $name => $path)
        {
            $module = new Module($name, $path, $db);
            if ($name === "wedeto.db")
                array_unshift($mods, $module);
            else
                array_push($mods, $module);
        }

        foreach ($mods as $module)
            $repo->addModule($module);

        return $repo;
    }
}

class MigrationRunner implements TaskInterface
{
    public function execute()
    {
        $log = Logger::getLogger(MigrationRunner::class);
        $repository = DI::getInjector()->getInstance(Repository::class);

        $target_version = getenv("WDB_VERSION");
        $target_module = getenv("WDB_MODULE");

        if (is_numeric($target_version) && !empty($target_module))
        {
            $ver = (int)$target_version;
            $module = $repository->getMigration($target_module);
            if (empty($module))
            {
                WF::debug("Invalid module: $target_module");
            }
            else
            {
                $module->migrateTo($ver);
            }
            return;
        }

        foreach ($repository as $module)
        {
            $cv = $module->getCurrentVersion();
            $lv = $module->getLatestVersion();

            if ($module->isUpToDate())
            {
                $log->info("Module {0} is up to date (@{1})", [$module->getModule(), $cv]);
            }
            else
            {
                $log->info("Upgrading module {0} from {1} to {2}", [$module->getModule(), $cv, $lv]);
                $module->upgradeToLatest();
            }
        }
    }
}


<?php
/**
 * @link    https://github.com/nnx-framework/module
 * @author  Malofeykin Andrey  <and-rey2@yandex.ru>
 */
namespace Nnx\Module\Listener;

use Zend\EventManager\AbstractListenerAggregate;
use Zend\EventManager\EventManagerInterface;
use Nnx\Module\IntegrationModuleInterface;
use Nnx\Module\Event\IntegrationModuleEventInterface;
use Zend\ModuleManager\ModuleEvent;
use Zend\ModuleManager\Listener\ConfigMergerInterface;
use Zend\ModuleManager\ModuleManagerInterface;
use Nnx\ModuleOptions\ModuleConfigKeyProviderInterface;
use Nnx\Module\CommonModuleOptionsInterface;
use Zend\Stdlib\ArrayUtils;

/**
 * Class ModuleOptions
 *
 * @package Nnx\Module\Options
 */
class IntegrationModuleListener extends AbstractListenerAggregate implements IntegrationModuleListenerInterface
{

    /**
     * Стек содержащий объекты событий, полученных когда происходит иницилацзия интеграционного модуля.
     * (порядок соответствует, тому в какой очередности происходила инициализация модулей)
     *
     * @var IntegrationModuleEventInterface[]
     */
    protected $stackInitIntegrationModuleEvent = [];

    /**
     * Идендфикаторы подписчиков на SharedManagerEvent
     *
     * @var array
     */
    protected $sharedListeners = [];

    /**
     * Приоритет обработчки отвечающего за конфигурирование модулей сервиса. Для корректной работы
     * интеграционного модуля, это значение должно быть больше, по приоритету чем значение свойства
     * @see \Nnx\Module\Listener\IntegrationModuleListener::$configuringServiceModulesHandlerPriority, но меньше
     * @see \Nnx\Module\IntegrationModuleTrait::$loadModulesPostProxyHandlerPriority
     *
     *
     * @var int
     */
    protected $configuringServiceModulesHandlerPriority = 50;

    /**
     * @inheritdoc
     *
     * @param EventManagerInterface $events - это EventManager сервиса \Zend\ModuleManager\ModuleManagerInterface
     */
    public function attach(EventManagerInterface $events)
    {
        $sharedEventManager = $events->getSharedManager();
        $integrationModuleEventHandler = $sharedEventManager->attach(
            IntegrationModuleInterface::class,
            IntegrationModuleEventInterface::INIT_INTEGRATION_MODULE_EVENT,
            [$this, 'initIntegrationModuleEventHandler']
        );
        $this->sharedListeners[] = [
            'listener' => $integrationModuleEventHandler,
            'id' => IntegrationModuleInterface::class
        ];

        $this->listeners[] = $events->attach(
            ModuleEvent::EVENT_LOAD_MODULES_POST,
            [$this, 'configuringServiceModulesHandler'],
            $this->configuringServiceModulesHandlerPriority
        );
    }

    /**
     * Обработчик события бросаемого, когда все интеграционные модули иницилизированны
     *
     * @param ModuleEvent $e
     */
    public function configuringServiceModulesHandler(ModuleEvent $e)
    {
        $configListener = $e->getConfigListener();

        $stackIntegrationsModule = $this->getStackInitIntegrationModuleEvent();
        /** @var IntegrationModuleEventInterface[] $sortStackIntegrationsModule */
        $sortStackIntegrationsModule = array_reverse($stackIntegrationsModule);

        foreach ($sortStackIntegrationsModule as $event) {
            $integrationModule = $event->getModule();

            if (!$integrationModule instanceof IntegrationModuleInterface) {
                continue;
            }
            $moduleManager = $event->getModuleManager();

            $this->rebuildServiceModulesConfigs($integrationModule, $moduleManager, $configListener);
        }
    }

    /**
     * Возвращает для итеграционного модуля список настроек, которые должны быть применены, ко всем модулям сервиса
     *
     * @param mixed                 $integrationModule
     * @param ConfigMergerInterface $configListener
     *
     * @return array
     */
    public function getCommonModuleOptionsByIntegrationModule($integrationModule, ConfigMergerInterface $configListener)
    {
        $commonModuleOptions = [];
        if (
            (!$integrationModule instanceof CommonModuleOptionsInterface)
            || (!$integrationModule instanceof ModuleConfigKeyProviderInterface)
        ) {
            return $commonModuleOptions;
        }

        $listCommonModuleOptions = $integrationModule->getCommonModuleOptions();
        $integrationModuleConfigKey = $integrationModule->getModuleConfigKey();

        $appConfig = $configListener->getMergedConfig(false);
        if (!is_array($appConfig)) {
            return $commonModuleOptions;
        }

        $integrationModuleConfig = array_key_exists($integrationModuleConfigKey, $appConfig) ? $appConfig[$integrationModuleConfigKey] : [];

        foreach ($listCommonModuleOptions as $key) {
            $commonModuleOptions[$key] = array_key_exists($key, $integrationModuleConfig) ? $integrationModuleConfig[$key] : null;
        }

        return $commonModuleOptions;
    }

    /**
     * Применяет настройки определенные в интеграционном модуле, для модулей сервиса
     *
     * @param IntegrationModuleInterface $integrationModule
     * @param ModuleManagerInterface     $moduleManager
     * @param ConfigMergerInterface      $configListener
     */
    public function rebuildServiceModulesConfigs(
        IntegrationModuleInterface $integrationModule,
        ModuleManagerInterface $moduleManager,
        ConfigMergerInterface $configListener
    ) {
        $serviceModules = $integrationModule->getServiceModules();

        $loadedModules = $moduleManager->getLoadedModules(false);

        $appConfig = $configListener->getMergedConfig(false);

        $commonModuleOptions = $this->getCommonModuleOptionsByIntegrationModule($integrationModule, $configListener);
        foreach ($serviceModules as $moduleName) {
            if (!array_key_exists($moduleName, $loadedModules)) {
                continue;
            }
            $module = $loadedModules[$moduleName];

            if (!$module instanceof ModuleConfigKeyProviderInterface) {
                continue;
            }

            $moduleConfigKey = $module->getModuleConfigKey();

            $moduleConfig = array_key_exists($moduleConfigKey, $appConfig) ? $appConfig[$moduleConfigKey] : [];

            $newModuleConfig = ArrayUtils::merge($moduleConfig, $commonModuleOptions);

            $appConfig[$moduleConfigKey] = $newModuleConfig;
        }

        $configListener->setMergedConfig($appConfig);
    }

    /**
     * {@inheritDoc}
     */
    public function detach(EventManagerInterface $events)
    {
        parent::detach($events);
        $sharedEventManager = $events->getSharedManager();
        foreach ($this->sharedListeners as $index => $item) {
            if ($sharedEventManager->detach($item['id'], $item['listener'])) {
                unset($this->sharedListeners[$index]);
            }
        }
    }

    /**
     * Обработчик события возникающего при инциализации интеграционного модуля
     *
     * @param IntegrationModuleEventInterface $event
     */
    public function initIntegrationModuleEventHandler(IntegrationModuleEventInterface $event)
    {
        $this->addInitIntegrationModuleEventInStack($event);
    }

    /**
     * Добавляет в стек, содержащий объекты событий бросаемых при иницилацзия интеграционного модуля.
     *
     * @param $initIntegrationModuleEvent $stackInitIntegrationModuleEvent
     *
     * @return $this
     */
    public function addInitIntegrationModuleEventInStack(IntegrationModuleEventInterface $initIntegrationModuleEvent)
    {
        $this->stackInitIntegrationModuleEvent[] = $initIntegrationModuleEvent;

        return $this;
    }

    /**
     * Стек содержащий объекты событий, полученных когда происходит иницилацзия интеграционного модуля.
     * (порядок соответствует, тому в какой очередности происходила инициализация модулей)
     *
     * @return IntegrationModuleEventInterface[]
     */
    public function getStackInitIntegrationModuleEvent()
    {
        return $this->stackInitIntegrationModuleEvent;
    }
}


<?php
/**
 * AnimeDb package.
 *
 * @author    Peter Gribanov <info@peter-gribanov.ru>
 * @copyright Copyright (c) 2014, Peter Gribanov
 * @license   http://opensource.org/licenses/MIT
 */

namespace AnimeDb\Bundle\CacheTimeKeeperBundle\Service;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;

class ResponseConfigurator
{
    /**
     * @var CacheKeyBuilder
     */
    protected $key_builder;

    /**
     * @var RequestStack
     */
    protected $request_stack;

    /**
     * @var array
     */
    protected $private_headers = [];

    /**
     * @param CacheKeyBuilder $key_builder
     * @param RequestStack $request_stack
     * @param array $private_headers
     */
    public function __construct(CacheKeyBuilder $key_builder, RequestStack $request_stack, array $private_headers)
    {
        $this->key_builder = $key_builder;
        $this->request_stack = $request_stack;
        $this->private_headers = $private_headers;
    }

    /**
     * Configure response.
     *
     * Set $lifetime as < 0 for not set max-age
     *
     * @param Response $response
     * @param \DateTime $last_modified
     * @param int $lifetime
     *
     * @return Response
     */
    public function configure(Response $response, \DateTime $last_modified, $lifetime)
    {
        $request = $this->request_stack->getMasterRequest();

        if (!($request instanceof Request)) {
            return $response;
        }

        // order is important
        $this
            ->setPrivateCache($response, $request)
            ->setLastModified($response, $last_modified)
            ->setLifetime($response, $lifetime)
            ->setEtag($response);

        return $response;
    }

    /**
     * @param Response $response
     * @param Request $request
     *
     * @return ResponseConfigurator
     */
    protected function setPrivateCache(Response $response, Request $request)
    {
        if (
            !$response->headers->hasCacheControlDirective('public') ||
            $response->headers->hasCacheControlDirective('private')
        ) {
            $response->setPublic();
            foreach ($this->private_headers as $private_header) {
                if ($request->headers->has($private_header)) {
                    $response->setPrivate();
                    break;
                }
            }
        }

        return $this;
    }

    /**
     * @param Response $response
     * @param \DateTime $last_modified
     *
     * @return ResponseConfigurator
     */
    protected function setLastModified(Response $response, \DateTime $last_modified)
    {
        $response
            ->setLastModified($last_modified)
            ->headers
            ->addCacheControlDirective('must-revalidate', true);

        return $this;
    }

    /**
     * Set max-age, s-maxage and expires headers.
     *
     * Set $lifetime as < 0 for not set max-age
     * Need set Cache-Control public/private before set lifetime
     *
     * @param Response $response
     * @param int $lifetime
     *
     * @return ResponseConfigurator
     */
    protected function setLifetime(Response $response, $lifetime)
    {
        if ($lifetime >= 0) {
            $date = clone $response->getDate();
            $response
                ->setMaxAge($lifetime)
                ->setExpires($date->modify(sprintf('+%s seconds', $lifetime)));

            if (!$response->headers->hasCacheControlDirective('private')) {
                $response->setPublic()->setSharedMaxAge($lifetime);
            }
        }

        return $this;
    }

    /**
     * Set ETag.
     *
     * Need set Last-Modified before ETag
     *
     * @param Response $response
     *
     * @return ResponseConfigurator
     */
    protected function setEtag(Response $response)
    {
        if (!$response->getEtag()) {
            $response->setEtag($this->key_builder->getEtag($response));
        }

        return $this;
    }
}