no message

This commit is contained in:
BarboraFleg
2019-04-04 18:34:40 +02:00
commit ce48ca4a21
589 changed files with 82184 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
{
"name": "nette/application",
"description": "🏆 Nette Application: a full-stack component-based MVC kernel for PHP that helps you write powerful and modern web applications. Write less, have cleaner code and your work will bring you joy.",
"keywords": ["nette", "mvc", "framework", "component-based", "routing", "seo", "mvp", "presenter", "control", "forms"],
"homepage": "https://nette.org",
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"require": {
"php": ">=5.6.0",
"nette/component-model": "^2.3",
"nette/http": "^2.2",
"nette/reflection": "^2.2",
"nette/utils": "^2.4 || ~3.0.0"
},
"suggest": {
"nette/forms": "Allows to use Nette\\Application\\UI\\Form",
"latte/latte": "Allows using Latte in templates"
},
"require-dev": {
"nette/tester": "^2.0",
"nette/di": "^2.4",
"nette/forms": "^2.4",
"nette/robot-loader": "^2.4.2 || ^3.0",
"nette/security": "^2.4",
"latte/latte": "^2.4.3",
"tracy/tracy": "^2.4",
"mockery/mockery": "^0.9.5"
},
"conflict": {
"nette/nette": "<2.2",
"nette/di": "<2.4",
"nette/forms": "<2.4",
"nette/latte": "<2.4"
},
"autoload": {
"classmap": ["src/"],
"files": ["src/compatibility.php"]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
}
}
}
+33
View File
@@ -0,0 +1,33 @@
How to contribute & use the issue tracker
=========================================
Nette welcomes your contributions. There are several ways to help out:
* Create an issue on GitHub, if you have found a bug
* Write test cases for open bug issues
* Write fixes for open bug/feature issues, preferably with test cases included
* Contribute to the [documentation](https://nette.org/en/writing)
Issues
------
Please **do not use the issue tracker to ask questions**. We will be happy to help you
on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
A good bug report shouldn't leave others needing to chase you up for more
information. Please try to be as detailed as possible in your report.
**Feature requests** are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature.
Contributing
------------
If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
Please do not fix whitespace, format code, or make a purely cosmetic patch.
Thanks! :heart:
+60
View File
@@ -0,0 +1,60 @@
Licenses
========
Good news! You may use Nette Framework under the terms of either
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
The BSD License is recommended for most projects. It is easy to understand and it
places almost no restrictions on what you can do with the framework. If the GPL
fits better to your project, you can use the framework under this license.
You don't have to notify anyone which license you are using. You can freely
use Nette Framework in commercial projects as long as the copyright header
remains intact.
Please be advised that the name "Nette Framework" is a protected trademark and its
usage has some limitations. So please do not use word "Nette" in the name of your
project or top-level domain, and choose a name that stands on its own merits.
If your stuff is good, it will not take long to establish a reputation for yourselves.
New BSD License
---------------
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of "Nette Framework" nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
This software is provided by the copyright holders and contributors "as is" and
any express or implied warranties, including, but not limited to, the implied
warranties of merchantability and fitness for a particular purpose are
disclaimed. In no event shall the copyright owner or contributors be liable for
any direct, indirect, incidental, special, exemplary, or consequential damages
(including, but not limited to, procurement of substitute goods or services;
loss of use, data, or profits; or business interruption) however caused and on
any theory of liability, whether in contract, strict liability, or tort
(including negligence or otherwise) arising in any way out of the use of this
software, even if advised of the possibility of such damage.
GNU General Public License
--------------------------
GPL licenses are very very long, so instead of including them here we offer
you URLs with full text:
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
+12
View File
@@ -0,0 +1,12 @@
Nette Application MVC
=====================
[![Downloads this Month](https://img.shields.io/packagist/dm/nette/application.svg)](https://packagist.org/packages/nette/application)
[![Build Status](https://travis-ci.org/nette/application.svg?branch=master)](https://travis-ci.org/nette/application)
[![Coverage Status](https://coveralls.io/repos/github/nette/application/badge.svg?branch=master)](https://coveralls.io/github/nette/application?branch=master)
[![Latest Stable Version](https://poser.pugx.org/nette/application/v/stable)](https://github.com/nette/application/releases)
[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/application/blob/master/license.md)
Model-View-Controller is a software architecture that was created to satisfy the need to separate utility code (controller) from application logic code (model) and from code for displaying data (view) in applications with graphical user interface. With this approach we make the application better understandable, simplify future development and enable testing each unit of the application separately.
Please, [see documentation](https://doc.nette.org/en/presenters).
+225
View File
@@ -0,0 +1,225 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application;
use Nette;
/**
* Front Controller.
*/
class Application
{
use Nette\SmartObject;
/** @var int */
public static $maxLoop = 20;
/** @var bool enable fault barrier? */
public $catchExceptions;
/** @var string|null */
public $errorPresenter;
/** @var callable[] function (Application $sender); Occurs before the application loads presenter */
public $onStartup;
/** @var callable[] function (Application $sender, \Exception|\Throwable $e = null); Occurs before the application shuts down */
public $onShutdown;
/** @var callable[] function (Application $sender, Request $request); Occurs when a new request is received */
public $onRequest;
/** @var callable[] function (Application $sender, Presenter $presenter); Occurs when a presenter is created */
public $onPresenter;
/** @var callable[] function (Application $sender, IResponse $response); Occurs when a new response is ready for dispatch */
public $onResponse;
/** @var callable[] function (Application $sender, \Exception|\Throwable $e); Occurs when an unhandled exception occurs in the application */
public $onError;
/** @var Request[] */
private $requests = [];
/** @var IPresenter|null */
private $presenter;
/** @var Nette\Http\IRequest */
private $httpRequest;
/** @var Nette\Http\IResponse */
private $httpResponse;
/** @var IPresenterFactory */
private $presenterFactory;
/** @var IRouter */
private $router;
public function __construct(IPresenterFactory $presenterFactory, IRouter $router, Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse)
{
$this->httpRequest = $httpRequest;
$this->httpResponse = $httpResponse;
$this->presenterFactory = $presenterFactory;
$this->router = $router;
}
/**
* Dispatch a HTTP request to a front controller.
* @return void
*/
public function run()
{
try {
$this->onStartup($this);
$this->processRequest($this->createInitialRequest());
$this->onShutdown($this);
} catch (\Exception $e) {
} catch (\Throwable $e) {
}
if (isset($e)) {
$this->onError($this, $e);
if ($this->catchExceptions && $this->errorPresenter) {
try {
$this->processException($e);
$this->onShutdown($this, $e);
return;
} catch (\Exception $e) {
$this->onError($this, $e);
} catch (\Throwable $e) {
$this->onError($this, $e);
}
}
$this->onShutdown($this, $e);
throw $e;
}
}
/**
* @return Request
*/
public function createInitialRequest()
{
$request = $this->router->match($this->httpRequest);
if (!$request instanceof Request) {
throw new BadRequestException('No route for HTTP request.');
}
return $request;
}
/**
* @return void
*/
public function processRequest(Request $request)
{
process:
if (count($this->requests) > self::$maxLoop) {
throw new ApplicationException('Too many loops detected in application life cycle.');
}
$this->requests[] = $request;
$this->onRequest($this, $request);
if (!$request->isMethod($request::FORWARD) && !strcasecmp($request->getPresenterName(), $this->errorPresenter)) {
throw new BadRequestException('Invalid request. Presenter is not achievable.');
}
try {
$this->presenter = $this->presenterFactory->createPresenter($request->getPresenterName());
} catch (InvalidPresenterException $e) {
throw count($this->requests) > 1 ? $e : new BadRequestException($e->getMessage(), 0, $e);
}
$this->onPresenter($this, $this->presenter);
$response = $this->presenter->run(clone $request);
if ($response instanceof Responses\ForwardResponse) {
$request = $response->getRequest();
goto process;
} elseif ($response) {
$this->onResponse($this, $response);
$response->send($this->httpRequest, $this->httpResponse);
}
}
/**
* @param \Exception|\Throwable
* @return void
*/
public function processException($e)
{
if (!$e instanceof BadRequestException && $this->httpResponse instanceof Nette\Http\Response) {
$this->httpResponse->warnOnBuffer = false;
}
if (!$this->httpResponse->isSent()) {
$this->httpResponse->setCode($e instanceof BadRequestException ? ($e->getHttpCode() ?: 404) : 500);
}
$args = ['exception' => $e, 'request' => end($this->requests) ?: null];
if ($this->presenter instanceof UI\Presenter) {
try {
$this->presenter->forward(":$this->errorPresenter:", $args);
} catch (AbortException $foo) {
$this->processRequest($this->presenter->getLastCreatedRequest());
}
} else {
$this->processRequest(new Request($this->errorPresenter, Request::FORWARD, $args));
}
}
/**
* Returns all processed requests.
* @return Request[]
*/
public function getRequests()
{
return $this->requests;
}
/**
* Returns current presenter.
* @return IPresenter|null
*/
public function getPresenter()
{
return $this->presenter;
}
/********************* services ****************d*g**/
/**
* Returns router.
* @return IRouter
*/
public function getRouter()
{
return $this->router;
}
/**
* Returns presenter factory.
* @return IPresenterFactory
*/
public function getPresenterFactory()
{
return $this->presenterFactory;
}
}
@@ -0,0 +1,53 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace NetteModule;
use Nette;
use Nette\Application;
use Nette\Http;
use Tracy\ILogger;
/**
* Default Error Presenter.
*/
class ErrorPresenter implements Application\IPresenter
{
use Nette\SmartObject;
/** @var ILogger|null */
private $logger;
public function __construct(ILogger $logger = null)
{
$this->logger = $logger;
}
/**
* @return Application\IResponse
*/
public function run(Application\Request $request)
{
$e = $request->getParameter('exception');
if ($e instanceof Application\BadRequestException) {
$code = $e->getHttpCode();
} else {
$code = 500;
if ($this->logger) {
$this->logger->log($e, ILogger::EXCEPTION);
}
}
return new Application\Responses\CallbackResponse(function (Http\IRequest $httpRequest, Http\IResponse $httpResponse) use ($code) {
if (preg_match('#^text/html(?:;|$)#', $httpResponse->getHeader('Content-Type'))) {
require __DIR__ . '/templates/error.phtml';
}
});
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application;
use Nette;
/**
* Application helpers.
*/
class Helpers
{
use Nette\StaticClass;
/**
* Splits name into [module, presenter] or [presenter, action]
* @return array
*/
public static function splitName($name)
{
$pos = strrpos($name, ':');
return $pos === false
? ['', $name, '']
: [substr($name, 0, $pos), (string) substr($name, $pos + 1), ':'];
}
}
+21
View File
@@ -0,0 +1,21 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application;
/**
* Presenter converts Request to IResponse.
*/
interface IPresenter
{
/**
* @return IResponse
*/
function run(Request $request);
}
@@ -0,0 +1,31 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application;
/**
* Responsible for creating a new instance of given presenter.
*/
interface IPresenterFactory
{
/**
* Generates and checks presenter class name.
* @param string presenter name
* @return string class name
* @throws InvalidPresenterException
*/
function getPresenterClass(&$name);
/**
* Creates new presenter instance.
* @param string presenter name
* @return IPresenter
*/
function createPresenter($name);
}
+24
View File
@@ -0,0 +1,24 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application;
use Nette;
/**
* Any response returned by presenter.
*/
interface IResponse
{
/**
* Sends response to output.
* @return void
*/
function send(Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse);
}
+35
View File
@@ -0,0 +1,35 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application;
use Nette;
/**
* The bi-directional router.
*/
interface IRouter
{
/** only matching route */
const ONE_WAY = 0b0001;
/** @deprecated */
const SECURED = 0b0010;
/**
* Maps HTTP request to a Request object.
* @return Request|null
*/
function match(Nette\Http\IRequest $httpRequest);
/**
* Constructs absolute URL from Request object.
* @return string|null
*/
function constructUrl(Request $appRequest, Nette\Http\Url $refUrl);
}
@@ -0,0 +1,88 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application;
use Nette;
/**
* Link generator.
*/
class LinkGenerator
{
use Nette\SmartObject;
/** @var IRouter */
private $router;
/** @var Nette\Http\Url */
private $refUrl;
/** @var IPresenterFactory|null */
private $presenterFactory;
public function __construct(IRouter $router, Nette\Http\Url $refUrl, IPresenterFactory $presenterFactory = null)
{
$this->router = $router;
$this->refUrl = $refUrl;
$this->presenterFactory = $presenterFactory;
}
/**
* Generates URL to presenter.
* @param string destination in format "[[[module:]presenter:]action] [#fragment]"
* @return string
* @throws UI\InvalidLinkException
*/
public function link($dest, array $params = [])
{
if (!preg_match('~^([\w:]+):(\w*+)(#.*)?()\z~', $dest, $m)) {
throw new UI\InvalidLinkException("Invalid link destination '$dest'.");
}
list(, $presenter, $action, $frag) = $m;
try {
$class = $this->presenterFactory ? $this->presenterFactory->getPresenterClass($presenter) : null;
} catch (InvalidPresenterException $e) {
throw new UI\InvalidLinkException($e->getMessage(), 0, $e);
}
if (is_subclass_of($class, UI\Presenter::class)) {
if ($action === '') {
$action = UI\Presenter::DEFAULT_ACTION;
}
if (
method_exists($class, $method = $class::formatActionMethod($action))
|| method_exists($class, $method = $class::formatRenderMethod($action))
) {
UI\Presenter::argsToParams($class, $method, $params, [], $missing);
if ($missing) {
$rp = $missing[0];
throw new UI\InvalidLinkException("Missing parameter \${$rp->getName()} required by {$rp->getDeclaringClass()->getName()}::{$rp->getDeclaringFunction()->getName()}()");
}
} elseif (array_key_exists(0, $params)) {
throw new UI\InvalidLinkException("Unable to pass parameters to action '$presenter:$action', missing corresponding method.");
}
}
if ($action !== '') {
$params[UI\Presenter::ACTION_KEY] = $action;
}
$url = $this->router->constructUrl(new Request($presenter, null, $params), $this->refUrl);
if ($url === null) {
unset($params[UI\Presenter::ACTION_KEY]);
$params = urldecode(http_build_query($params, null, ', '));
throw new UI\InvalidLinkException("No route for $dest($params)");
}
return $url . $frag;
}
}
@@ -0,0 +1,162 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace NetteModule;
use Latte;
use Nette;
use Nette\Application;
use Nette\Application\Responses;
use Nette\Http;
/**
* Micro presenter.
*/
class MicroPresenter implements Application\IPresenter
{
use Nette\SmartObject;
/** @var Nette\DI\Container|null */
private $context;
/** @var Nette\Http\IRequest|null */
private $httpRequest;
/** @var Application\IRouter|null */
private $router;
/** @var Application\Request|null */
private $request;
public function __construct(Nette\DI\Container $context = null, Http\IRequest $httpRequest = null, Application\IRouter $router = null)
{
$this->context = $context;
$this->httpRequest = $httpRequest;
$this->router = $router;
}
/**
* Gets the context.
* @return Nette\DI\Container|null
*/
public function getContext()
{
return $this->context;
}
/**
* @return Nette\Application\IResponse
*/
public function run(Application\Request $request)
{
$this->request = $request;
if ($this->httpRequest && $this->router && !$this->httpRequest->isAjax() && ($request->isMethod('get') || $request->isMethod('head'))) {
$refUrl = clone $this->httpRequest->getUrl();
$url = $this->router->constructUrl($request, $refUrl->setPath($refUrl->getScriptPath()));
if ($url !== null && !$this->httpRequest->getUrl()->isEqual($url)) {
return new Responses\RedirectResponse($url, Http\IResponse::S301_MOVED_PERMANENTLY);
}
}
$params = $request->getParameters();
if (!isset($params['callback'])) {
throw new Application\BadRequestException('Parameter callback is missing.');
}
$callback = $params['callback'];
$reflection = Nette\Utils\Callback::toReflection(Nette\Utils\Callback::check($callback));
if ($this->context) {
foreach ($reflection->getParameters() as $param) {
if ($param->getClass()) {
$params[$param->getName()] = $this->context->getByType($param->getClass()->getName(), false);
}
}
}
$params['presenter'] = $this;
$params = Application\UI\ComponentReflection::combineArgs($reflection, $params);
$response = call_user_func_array($callback, $params);
if (is_string($response)) {
$response = [$response, []];
}
if (is_array($response)) {
list($templateSource, $templateParams) = $response;
$response = $this->createTemplate()->setParameters($templateParams);
if (!$templateSource instanceof \SplFileInfo) {
$response->getLatte()->setLoader(new Latte\Loaders\StringLoader);
}
$response->setFile($templateSource);
}
if ($response instanceof Application\UI\ITemplate) {
return new Responses\TextResponse($response);
} else {
return $response;
}
}
/**
* Template factory.
* @param string
* @return Application\UI\ITemplate
*/
public function createTemplate($class = null, callable $latteFactory = null)
{
$latte = $latteFactory ? $latteFactory() : $this->getContext()->getByType(Nette\Bridges\ApplicationLatte\ILatteFactory::class)->create();
$template = $class ? new $class : new Nette\Bridges\ApplicationLatte\Template($latte);
$template->setParameters($this->request->getParameters());
$template->presenter = $this;
$template->context = $this->context;
if ($this->httpRequest) {
$url = $this->httpRequest->getUrl();
$template->baseUrl = rtrim($url->getBaseUrl(), '/');
$template->basePath = rtrim($url->getBasePath(), '/');
}
return $template;
}
/**
* Redirects to another URL.
* @param string
* @param int HTTP code
* @return Nette\Application\Responses\RedirectResponse
*/
public function redirectUrl($url, $httpCode = Http\IResponse::S302_FOUND)
{
return new Responses\RedirectResponse($url, $httpCode);
}
/**
* Throws HTTP error.
* @param string
* @param int HTTP error code
* @return void
* @throws Nette\Application\BadRequestException
*/
public function error($message = null, $httpCode = Http\IResponse::S404_NOT_FOUND)
{
throw new Application\BadRequestException($message, $httpCode);
}
/**
* @return Nette\Application\Request|null
*/
public function getRequest()
{
return $this->request;
}
}
@@ -0,0 +1,153 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application;
use Nette;
/**
* Default presenter loader.
*/
class PresenterFactory implements IPresenterFactory
{
use Nette\SmartObject;
/** @var array[] of module => splited mask */
private $mapping = [
'*' => ['', '*Module\\', '*Presenter'],
'Nette' => ['NetteModule\\', '*\\', '*Presenter'],
];
/** @var array */
private $cache = [];
/** @var callable */
private $factory;
/**
* @param callable function (string $class): IPresenter
*/
public function __construct(callable $factory = null)
{
$this->factory = $factory ?: function ($class) { return new $class; };
}
/**
* Creates new presenter instance.
* @param string presenter name
* @return IPresenter
*/
public function createPresenter($name)
{
return call_user_func($this->factory, $this->getPresenterClass($name));
}
/**
* Generates and checks presenter class name.
* @param string presenter name
* @return string class name
* @throws InvalidPresenterException
*/
public function getPresenterClass(&$name)
{
if (isset($this->cache[$name])) {
return $this->cache[$name];
}
if (!is_string($name) || !Nette\Utils\Strings::match($name, '#^[a-zA-Z\x7f-\xff][a-zA-Z0-9\x7f-\xff:]*\z#')) {
throw new InvalidPresenterException("Presenter name must be alphanumeric string, '$name' is invalid.");
}
$class = $this->formatPresenterClass($name);
if (!class_exists($class)) {
throw new InvalidPresenterException("Cannot load presenter '$name', class '$class' was not found.");
}
$reflection = new \ReflectionClass($class);
$class = $reflection->getName();
if (!$reflection->implementsInterface(IPresenter::class)) {
throw new InvalidPresenterException("Cannot load presenter '$name', class '$class' is not Nette\\Application\\IPresenter implementor.");
} elseif ($reflection->isAbstract()) {
throw new InvalidPresenterException("Cannot load presenter '$name', class '$class' is abstract.");
}
$this->cache[$name] = $class;
if ($name !== ($realName = $this->unformatPresenterClass($class))) {
trigger_error("Case mismatch on presenter name '$name', correct name is '$realName'.", E_USER_WARNING);
$name = $realName;
}
return $class;
}
/**
* Sets mapping as pairs [module => mask]
* @return static
*/
public function setMapping(array $mapping)
{
foreach ($mapping as $module => $mask) {
if (is_string($mask)) {
if (!preg_match('#^\\\\?([\w\\\\]*\\\\)?(\w*\*\w*?\\\\)?([\w\\\\]*\*\w*)\z#', $mask, $m)) {
throw new Nette\InvalidStateException("Invalid mapping mask '$mask'.");
}
$this->mapping[$module] = [$m[1], $m[2] ?: '*Module\\', $m[3]];
} elseif (is_array($mask) && count($mask) === 3) {
$this->mapping[$module] = [$mask[0] ? $mask[0] . '\\' : '', $mask[1] . '\\', $mask[2]];
} else {
throw new Nette\InvalidStateException("Invalid mapping mask for module $module.");
}
}
return $this;
}
/**
* Formats presenter class name from its name.
* @param string
* @return string
* @internal
*/
public function formatPresenterClass($presenter)
{
$parts = explode(':', $presenter);
$mapping = isset($parts[1], $this->mapping[$parts[0]])
? $this->mapping[array_shift($parts)]
: $this->mapping['*'];
while ($part = array_shift($parts)) {
$mapping[0] .= str_replace('*', $part, $mapping[$parts ? 1 : 2]);
}
return $mapping[0];
}
/**
* Formats presenter name from class name.
* @param string
* @return string|null
* @internal
*/
public function unformatPresenterClass($class)
{
foreach ($this->mapping as $module => $mapping) {
$mapping = str_replace(['\\', '*'], ['\\\\', '(\w+)'], $mapping);
if (preg_match("#^\\\\?$mapping[0]((?:$mapping[1])*)$mapping[2]\\z#i", $class, $matches)) {
return ($module === '*' ? '' : $module . ':')
. preg_replace("#$mapping[1]#iA", '$1:', $matches[1]) . $matches[3];
}
}
return null;
}
}
+237
View File
@@ -0,0 +1,237 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application;
use Nette;
/**
* Presenter request.
*
* @property string $presenterName
* @property array $parameters
* @property array $post
* @property array $files
* @property string|null $method
*/
class Request
{
use Nette\SmartObject;
/** method */
const FORWARD = 'FORWARD';
/** flag */
const SECURED = 'secured';
/** flag */
const RESTORED = 'restored';
/** flag */
const VARYING = 'varying';
/** @var string|null */
private $method;
/** @var array */
private $flags = [];
/** @var string */
private $name;
/** @var array */
private $params;
/** @var array */
private $post;
/** @var array */
private $files;
/**
* @param string fully qualified presenter name (module:module:presenter)
* @param string method
* @param array variables provided to the presenter usually via URL
* @param array variables provided to the presenter via POST
* @param array all uploaded files
* @param array flags
*/
public function __construct($name, $method = null, array $params = [], array $post = [], array $files = [], array $flags = [])
{
$this->name = $name;
$this->method = $method;
$this->params = $params;
$this->post = $post;
$this->files = $files;
$this->flags = $flags;
}
/**
* Sets the presenter name.
* @param string
* @return static
*/
public function setPresenterName($name)
{
$this->name = $name;
return $this;
}
/**
* Retrieve the presenter name.
* @return string
*/
public function getPresenterName()
{
return $this->name;
}
/**
* Sets variables provided to the presenter.
* @return static
*/
public function setParameters(array $params)
{
$this->params = $params;
return $this;
}
/**
* Returns all variables provided to the presenter (usually via URL).
* @return array
*/
public function getParameters()
{
return $this->params;
}
/**
* Returns a parameter provided to the presenter.
* @param string
* @return mixed
*/
public function getParameter($key)
{
return isset($this->params[$key]) ? $this->params[$key] : null;
}
/**
* Sets variables provided to the presenter via POST.
* @return static
*/
public function setPost(array $params)
{
$this->post = $params;
return $this;
}
/**
* Returns a variable provided to the presenter via POST.
* If no key is passed, returns the entire array.
* @param string
* @return mixed
*/
public function getPost($key = null)
{
if (func_num_args() === 0) {
return $this->post;
} elseif (isset($this->post[$key])) {
return $this->post[$key];
} else {
return null;
}
}
/**
* Sets all uploaded files.
* @return static
*/
public function setFiles(array $files)
{
$this->files = $files;
return $this;
}
/**
* Returns all uploaded files.
* @return array
*/
public function getFiles()
{
return $this->files;
}
/**
* Sets the method.
* @param string|null
* @return static
*/
public function setMethod($method)
{
$this->method = $method;
return $this;
}
/**
* Returns the method.
* @return string|null
*/
public function getMethod()
{
return $this->method;
}
/**
* Checks if the method is the given one.
* @param string
* @return bool
*/
public function isMethod($method)
{
return strcasecmp($this->method, $method) === 0;
}
/**
* Sets the flag.
* @param string
* @param bool
* @return static
*/
public function setFlag($flag, $value = true)
{
$this->flags[$flag] = (bool) $value;
return $this;
}
/**
* Checks the flag.
* @param string
* @return bool
*/
public function hasFlag($flag)
{
return !empty($this->flags[$flag]);
}
}
@@ -0,0 +1,41 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\Responses;
use Nette;
/**
* Callback response.
*/
class CallbackResponse implements Nette\Application\IResponse
{
use Nette\SmartObject;
/** @var callable */
private $callback;
/**
* @param callable function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse)
*/
public function __construct(callable $callback)
{
$this->callback = $callback;
}
/**
* Sends response to output.
* @return void
*/
public function send(Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse)
{
call_user_func($this->callback, $httpRequest, $httpResponse);
}
}
@@ -0,0 +1,132 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\Responses;
use Nette;
/**
* File download response.
*/
class FileResponse implements Nette\Application\IResponse
{
use Nette\SmartObject;
/** @var bool */
public $resuming = true;
/** @var string */
private $file;
/** @var string */
private $contentType;
/** @var string */
private $name;
/** @var bool */
private $forceDownload;
/**
* @param string file path
* @param string imposed file name
* @param string MIME content type
*/
public function __construct($file, $name = null, $contentType = null, $forceDownload = true)
{
if (!is_file($file)) {
throw new Nette\Application\BadRequestException("File '$file' doesn't exist.");
}
$this->file = $file;
$this->name = $name ?: basename($file);
$this->contentType = $contentType ?: 'application/octet-stream';
$this->forceDownload = $forceDownload;
}
/**
* Returns the path to a downloaded file.
* @return string
*/
public function getFile()
{
return $this->file;
}
/**
* Returns the file name.
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Returns the MIME content type of a downloaded file.
* @return string
*/
public function getContentType()
{
return $this->contentType;
}
/**
* Sends response to output.
* @return void
*/
public function send(Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse)
{
$httpResponse->setContentType($this->contentType);
$httpResponse->setHeader('Content-Disposition',
($this->forceDownload ? 'attachment' : 'inline')
. '; filename="' . $this->name . '"'
. '; filename*=utf-8\'\'' . rawurlencode($this->name));
$filesize = $length = filesize($this->file);
$handle = fopen($this->file, 'r');
if ($this->resuming) {
$httpResponse->setHeader('Accept-Ranges', 'bytes');
if (preg_match('#^bytes=(\d*)-(\d*)\z#', $httpRequest->getHeader('Range'), $matches)) {
list(, $start, $end) = $matches;
if ($start === '') {
$start = max(0, $filesize - $end);
$end = $filesize - 1;
} elseif ($end === '' || $end > $filesize - 1) {
$end = $filesize - 1;
}
if ($end < $start) {
$httpResponse->setCode(416); // requested range not satisfiable
return;
}
$httpResponse->setCode(206);
$httpResponse->setHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $filesize);
$length = $end - $start + 1;
fseek($handle, $start);
} else {
$httpResponse->setHeader('Content-Range', 'bytes 0-' . ($filesize - 1) . '/' . $filesize);
}
}
$httpResponse->setHeader('Content-Length', $length);
while (!feof($handle) && $length > 0) {
echo $s = fread($handle, min(4000000, $length));
$length -= strlen($s);
}
fclose($handle);
}
}
@@ -0,0 +1,46 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\Responses;
use Nette;
/**
* Forwards to new request.
*/
class ForwardResponse implements Nette\Application\IResponse
{
use Nette\SmartObject;
/** @var Nette\Application\Request */
private $request;
public function __construct(Nette\Application\Request $request)
{
$this->request = $request;
}
/**
* @return Nette\Application\Request
*/
public function getRequest()
{
return $this->request;
}
/**
* Sends response to output.
* @return void
*/
public function send(Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse)
{
}
}
@@ -0,0 +1,66 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\Responses;
use Nette;
/**
* JSON response used mainly for AJAX requests.
*/
class JsonResponse implements Nette\Application\IResponse
{
use Nette\SmartObject;
/** @var mixed */
private $payload;
/** @var string */
private $contentType;
/**
* @param mixed payload
* @param string MIME content type
*/
public function __construct($payload, $contentType = null)
{
$this->payload = $payload;
$this->contentType = $contentType ?: 'application/json';
}
/**
* @return mixed
*/
public function getPayload()
{
return $this->payload;
}
/**
* Returns the MIME content type of a downloaded file.
* @return string
*/
public function getContentType()
{
return $this->contentType;
}
/**
* Sends response to output.
* @return void
*/
public function send(Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse)
{
$httpResponse->setContentType($this->contentType, 'utf-8');
echo Nette\Utils\Json::encode($this->payload);
}
}
@@ -0,0 +1,65 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\Responses;
use Nette;
use Nette\Http;
/**
* Redirects to new URI.
*/
class RedirectResponse implements Nette\Application\IResponse
{
use Nette\SmartObject;
/** @var string */
private $url;
/** @var int */
private $httpCode;
/**
* @param string URI
* @param int HTTP code 3xx
*/
public function __construct($url, $httpCode = Http\IResponse::S302_FOUND)
{
$this->url = (string) $url;
$this->httpCode = (int) $httpCode;
}
/**
* @return string
*/
public function getUrl()
{
return $this->url;
}
/**
* @return int
*/
public function getCode()
{
return $this->httpCode;
}
/**
* Sends response to output.
* @return void
*/
public function send(Http\IRequest $httpRequest, Http\IResponse $httpResponse)
{
$httpResponse->redirect($this->url, $this->httpCode);
}
}
@@ -0,0 +1,55 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\Responses;
use Nette;
/**
* String output response.
*/
class TextResponse implements Nette\Application\IResponse
{
use Nette\SmartObject;
/** @var mixed */
private $source;
/**
* @param mixed renderable variable
*/
public function __construct($source)
{
$this->source = $source;
}
/**
* @return mixed
*/
public function getSource()
{
return $this->source;
}
/**
* Sends response to output.
* @return void
*/
public function send(Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse)
{
if ($this->source instanceof Nette\Application\UI\ITemplate) {
$this->source->render();
} else {
echo $this->source;
}
}
}
@@ -0,0 +1,25 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\Application\Responses;
use Nette;
/**
* No response.
*/
final class VoidResponse implements Nette\Application\IResponse
{
use Nette\SmartObject;
public function send(Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse): void
{
}
}
@@ -0,0 +1,113 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\Routers;
use Nette;
use Nette\Application;
/**
* The unidirectional router for CLI. (experimental)
*/
class CliRouter implements Application\IRouter
{
use Nette\SmartObject;
const PRESENTER_KEY = 'action';
/** @var array */
private $defaults;
/**
* @param array default values
*/
public function __construct($defaults = [])
{
$this->defaults = $defaults;
}
/**
* Maps command line arguments to a Request object.
* @return Nette\Application\Request|null
*/
public function match(Nette\Http\IRequest $httpRequest)
{
if (empty($_SERVER['argv']) || !is_array($_SERVER['argv'])) {
return null;
}
$names = [self::PRESENTER_KEY];
$params = $this->defaults;
$args = $_SERVER['argv'];
array_shift($args);
$args[] = '--';
foreach ($args as $arg) {
$opt = preg_replace('#/|-+#A', '', $arg);
if ($opt === $arg) {
if (isset($flag) || $flag = array_shift($names)) {
$params[$flag] = $arg;
} else {
$params[] = $arg;
}
$flag = null;
continue;
}
if (isset($flag)) {
$params[$flag] = true;
$flag = null;
}
if ($opt !== '') {
$pair = explode('=', $opt, 2);
if (isset($pair[1])) {
$params[$pair[0]] = $pair[1];
} else {
$flag = $pair[0];
}
}
}
if (!isset($params[self::PRESENTER_KEY])) {
throw new Nette\InvalidStateException('Missing presenter & action in route definition.');
}
list($module, $presenter) = Nette\Application\Helpers::splitName($params[self::PRESENTER_KEY]);
if ($module !== '') {
$params[self::PRESENTER_KEY] = $presenter;
$presenter = $module;
}
return new Application\Request(
$presenter,
'CLI',
$params
);
}
/**
* This router is only unidirectional.
* @return void
*/
public function constructUrl(Application\Request $appRequest, Nette\Http\Url $refUrl)
{
}
/**
* Returns default values.
* @return array
*/
public function getDefaults()
{
return $this->defaults;
}
}
@@ -0,0 +1,793 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\Routers;
use Nette;
use Nette\Application;
use Nette\Utils\Strings;
/**
* The bidirectional route is responsible for mapping
* HTTP request to a Request object for dispatch and vice-versa.
*/
class Route implements Application\IRouter
{
use Nette\SmartObject;
const PRESENTER_KEY = 'presenter';
const MODULE_KEY = 'module';
/** @internal url type */
const HOST = 1,
PATH = 2,
RELATIVE = 3;
/** key used in {@link Route::$styles} or metadata {@link Route::__construct} */
const VALUE = 'value';
const PATTERN = 'pattern';
const FILTER_IN = 'filterIn';
const FILTER_OUT = 'filterOut';
const FILTER_TABLE = 'filterTable';
const FILTER_STRICT = 'filterStrict';
/** @internal fixity types - how to handle default value? {@link Route::$metadata} */
const OPTIONAL = 0,
PATH_OPTIONAL = 1,
CONSTANT = 2;
/** @deprecated */
public static $defaultFlags = 0;
/** @var array */
public static $styles = [
'#' => [ // default style for path parameters
self::PATTERN => '[^/]+',
self::FILTER_OUT => [__CLASS__, 'param2path'],
],
'?#' => [// default style for query parameters
],
'module' => [
self::PATTERN => '[a-z][a-z0-9.-]*',
self::FILTER_IN => [__CLASS__, 'path2presenter'],
self::FILTER_OUT => [__CLASS__, 'presenter2path'],
],
'presenter' => [
self::PATTERN => '[a-z][a-z0-9.-]*',
self::FILTER_IN => [__CLASS__, 'path2presenter'],
self::FILTER_OUT => [__CLASS__, 'presenter2path'],
],
'action' => [
self::PATTERN => '[a-z][a-z0-9-]*',
self::FILTER_IN => [__CLASS__, 'path2action'],
self::FILTER_OUT => [__CLASS__, 'action2path'],
],
'?module' => [
],
'?presenter' => [
],
'?action' => [
],
];
/** @var string */
private $mask;
/** @var array */
private $sequence;
/** @var string regular expression pattern */
private $re;
/** @var string[] parameter aliases in regular expression */
private $aliases;
/** @var array of [value & fixity, filterIn, filterOut] */
private $metadata = [];
/** @var array */
private $xlat;
/** @var int HOST, PATH, RELATIVE */
private $type;
/** @var string http | https */
private $scheme;
/** @var int */
private $flags;
/** @var Nette\Http\Url */
private $lastRefUrl;
/** @var string */
private $lastBaseUrl;
/**
* @param string URL mask, e.g. '<presenter>/<action>/<id \d{1,3}>'
* @param array|string|\Closure default values or metadata or callback for NetteModule\MicroPresenter
* @param int flags
*/
public function __construct($mask, $metadata = [], $flags = 0)
{
if (is_string($metadata)) {
list($presenter, $action) = Nette\Application\Helpers::splitName($metadata);
if (!$presenter) {
throw new Nette\InvalidArgumentException("Second argument must be array or string in format Presenter:action, '$metadata' given.");
}
$metadata = [self::PRESENTER_KEY => $presenter];
if ($action !== '') {
$metadata['action'] = $action;
}
} elseif ($metadata instanceof \Closure || $metadata instanceof Nette\Callback) {
if ($metadata instanceof Nette\Callback) {
trigger_error('Nette\Callback is deprecated, use Nette\Utils\Callback::closure().', E_USER_DEPRECATED);
}
$metadata = [
self::PRESENTER_KEY => 'Nette:Micro',
'callback' => $metadata,
];
}
$this->flags = $flags | static::$defaultFlags;
$this->setMask($mask, $metadata);
if (static::$defaultFlags) {
trigger_error('Route::$defaultFlags is deprecated, router by default keeps the used protocol.', E_USER_DEPRECATED);
} elseif ($flags & self::SECURED) {
trigger_error('Router::SECURED is deprecated, specify scheme in mask.', E_USER_DEPRECATED);
$this->scheme = 'https';
}
}
/**
* Maps HTTP request to a Request object.
* @return Nette\Application\Request|null
*/
public function match(Nette\Http\IRequest $httpRequest)
{
// combine with precedence: mask (params in URL-path), fixity, query, (post,) defaults
// 1) URL MASK
$url = $httpRequest->getUrl();
$re = $this->re;
if ($this->type === self::HOST) {
$host = $url->getHost();
$path = '//' . $host . $url->getPath();
$parts = ip2long($host) ? [$host] : array_reverse(explode('.', $host));
$re = strtr($re, [
'/%basePath%/' => preg_quote($url->getBasePath(), '#'),
'%tld%' => preg_quote($parts[0], '#'),
'%domain%' => preg_quote(isset($parts[1]) ? "$parts[1].$parts[0]" : $parts[0], '#'),
'%sld%' => preg_quote(isset($parts[1]) ? $parts[1] : '', '#'),
'%host%' => preg_quote($host, '#'),
]);
} elseif ($this->type === self::RELATIVE) {
$basePath = $url->getBasePath();
if (strncmp($url->getPath(), $basePath, strlen($basePath)) !== 0) {
return null;
}
$path = (string) substr($url->getPath(), strlen($basePath));
} else {
$path = $url->getPath();
}
if ($path !== '') {
$path = rtrim(rawurldecode($path), '/') . '/';
}
if (!$matches = Strings::match($path, $re)) {
// stop, not matched
return null;
}
// assigns matched values to parameters
$params = [];
foreach ($matches as $k => $v) {
if (is_string($k) && $v !== '') {
$params[$this->aliases[$k]] = $v;
}
}
// 2) CONSTANT FIXITY
foreach ($this->metadata as $name => $meta) {
if (!isset($params[$name]) && isset($meta['fixity']) && $meta['fixity'] !== self::OPTIONAL) {
$params[$name] = null; // cannot be overwriten in 3) and detected by isset() in 4)
}
}
// 3) QUERY
if ($this->xlat) {
$params += self::renameKeys($httpRequest->getQuery(), array_flip($this->xlat));
} else {
$params += $httpRequest->getQuery();
}
// 4) APPLY FILTERS & FIXITY
foreach ($this->metadata as $name => $meta) {
if (isset($params[$name])) {
if (!is_scalar($params[$name])) {
// do nothing
} elseif (isset($meta[self::FILTER_TABLE][$params[$name]])) { // applies filterTable only to scalar parameters
$params[$name] = $meta[self::FILTER_TABLE][$params[$name]];
} elseif (isset($meta[self::FILTER_TABLE]) && !empty($meta[self::FILTER_STRICT])) {
return null; // rejected by filterTable
} elseif (isset($meta[self::FILTER_IN])) { // applies filterIn only to scalar parameters
$params[$name] = call_user_func($meta[self::FILTER_IN], (string) $params[$name]);
if ($params[$name] === null && !isset($meta['fixity'])) {
return null; // rejected by filter
}
}
} elseif (isset($meta['fixity'])) {
$params[$name] = $meta[self::VALUE];
}
}
if (isset($this->metadata[null][self::FILTER_IN])) {
$params = call_user_func($this->metadata[null][self::FILTER_IN], $params);
if ($params === null) {
return null;
}
}
// 5) BUILD Request
if (!isset($params[self::PRESENTER_KEY])) {
throw new Nette\InvalidStateException('Missing presenter in route definition.');
} elseif (!is_string($params[self::PRESENTER_KEY])) {
return null;
}
$presenter = $params[self::PRESENTER_KEY];
unset($params[self::PRESENTER_KEY]);
if (isset($this->metadata[self::MODULE_KEY])) {
$presenter = (isset($params[self::MODULE_KEY]) ? $params[self::MODULE_KEY] . ':' : '') . $presenter;
unset($params[self::MODULE_KEY]);
}
return new Application\Request(
$presenter,
$httpRequest->getMethod(),
$params,
$httpRequest->getPost(),
$httpRequest->getFiles(),
[Application\Request::SECURED => $httpRequest->isSecured()]
);
}
/**
* Constructs absolute URL from Request object.
* @return string|null
*/
public function constructUrl(Application\Request $appRequest, Nette\Http\Url $refUrl)
{
if ($this->flags & self::ONE_WAY) {
return null;
}
$params = $appRequest->getParameters();
$metadata = $this->metadata;
$presenter = $appRequest->getPresenterName();
$params[self::PRESENTER_KEY] = $presenter;
if (isset($metadata[self::MODULE_KEY])) { // try split into module and [submodule:]presenter parts
$module = $metadata[self::MODULE_KEY];
if (isset($module['fixity']) && strncmp($presenter, $module[self::VALUE] . ':', strlen($module[self::VALUE]) + 1) === 0) {
$a = strlen($module[self::VALUE]);
} else {
$a = strrpos($presenter, ':');
}
if ($a === false) {
$params[self::MODULE_KEY] = isset($module[self::VALUE]) ? '' : null;
} else {
$params[self::MODULE_KEY] = substr($presenter, 0, $a);
$params[self::PRESENTER_KEY] = substr($presenter, $a + 1);
}
}
if (isset($metadata[null][self::FILTER_OUT])) {
$params = call_user_func($metadata[null][self::FILTER_OUT], $params);
if ($params === null) {
return null;
}
}
foreach ($metadata as $name => $meta) {
if (!isset($params[$name])) {
continue; // retains null values
}
if (is_scalar($params[$name])) {
$params[$name] = $params[$name] === false ? '0' : (string) $params[$name];
}
if (isset($meta['fixity'])) {
if ($params[$name] === $meta[self::VALUE]) { // remove default values; null values are retain
unset($params[$name]);
continue;
} elseif ($meta['fixity'] === self::CONSTANT) {
return null; // missing or wrong parameter '$name'
}
}
if (is_scalar($params[$name]) && isset($meta['filterTable2'][$params[$name]])) {
$params[$name] = $meta['filterTable2'][$params[$name]];
} elseif (isset($meta['filterTable2']) && !empty($meta[self::FILTER_STRICT])) {
return null;
} elseif (isset($meta[self::FILTER_OUT])) {
$params[$name] = call_user_func($meta[self::FILTER_OUT], $params[$name]);
}
if (isset($meta[self::PATTERN]) && !preg_match($meta[self::PATTERN], rawurldecode($params[$name]))) {
return null; // pattern not match
}
}
// compositing path
$sequence = $this->sequence;
$brackets = [];
$required = null; // null for auto-optional
$url = '';
$i = count($sequence) - 1;
do {
$url = $sequence[$i] . $url;
if ($i === 0) {
break;
}
$i--;
$name = $sequence[$i--]; // parameter name
if ($name === ']') { // opening optional part
$brackets[] = $url;
} elseif ($name[0] === '[') { // closing optional part
$tmp = array_pop($brackets);
if ($required < count($brackets) + 1) { // is this level optional?
if ($name !== '[!') { // and not "required"-optional
$url = $tmp;
}
} else {
$required = count($brackets);
}
} elseif ($name[0] === '?') { // "foo" parameter
continue;
} elseif (isset($params[$name]) && $params[$name] != '') { // intentionally ==
$required = count($brackets); // make this level required
$url = $params[$name] . $url;
unset($params[$name]);
} elseif (isset($metadata[$name]['fixity'])) { // has default value?
if ($required === null && !$brackets) { // auto-optional
$url = '';
} else {
$url = $metadata[$name]['defOut'] . $url;
}
} else {
return null; // missing parameter '$name'
}
} while (true);
$scheme = $this->scheme ?: $refUrl->getScheme();
if ($this->type === self::HOST) {
$host = $refUrl->getHost();
$parts = ip2long($host) ? [$host] : array_reverse(explode('.', $host));
$url = strtr($url, [
'/%basePath%/' => $refUrl->getBasePath(),
'%tld%' => $parts[0],
'%domain%' => isset($parts[1]) ? "$parts[1].$parts[0]" : $parts[0],
'%sld%' => isset($parts[1]) ? $parts[1] : '',
'%host%' => $host,
]);
$url = $scheme . ':' . $url;
} else {
if ($this->lastRefUrl !== $refUrl) {
$basePath = ($this->type === self::RELATIVE ? $refUrl->getBasePath() : '');
$this->lastBaseUrl = $scheme . '://' . $refUrl->getAuthority() . $basePath;
$this->lastRefUrl = $refUrl;
}
$url = $this->lastBaseUrl . $url;
}
if (strpos($url, '//', strlen($scheme) + 3) !== false) {
return null;
}
// build query string
if ($this->xlat) {
$params = self::renameKeys($params, $this->xlat);
}
$sep = ini_get('arg_separator.input');
$query = http_build_query($params, '', $sep ? $sep[0] : '&');
if ($query != '') { // intentionally ==
$url .= '?' . $query;
}
return $url;
}
/**
* Parse mask and array of default values; initializes object.
* @param string
* @param array
* @return void
*/
private function setMask($mask, array $metadata)
{
$this->mask = $mask;
// detect '//host/path' vs. '/abs. path' vs. 'relative path'
if (preg_match('#(?:(https?):)?(//.*)#A', $mask, $m)) {
$this->type = self::HOST;
list(, $this->scheme, $mask) = $m;
} elseif (substr($mask, 0, 1) === '/') {
$this->type = self::PATH;
} else {
$this->type = self::RELATIVE;
}
foreach ($metadata as $name => $meta) {
if (!is_array($meta)) {
$metadata[$name] = $meta = [self::VALUE => $meta];
}
if (array_key_exists(self::VALUE, $meta)) {
if (is_scalar($meta[self::VALUE])) {
$metadata[$name][self::VALUE] = $meta[self::VALUE] === false ? '0' : (string) $meta[self::VALUE];
}
$metadata[$name]['fixity'] = self::CONSTANT;
}
}
if (strpbrk($mask, '?<>[]') === false) {
$this->re = '#' . preg_quote($mask, '#') . '/?\z#A';
$this->sequence = [$mask];
$this->metadata = $metadata;
return;
}
// PARSE MASK
// <parameter-name[=default] [pattern]> or [ or ] or ?...
$parts = Strings::split($mask, '/<([^<>= ]+)(=[^<> ]*)? *([^<>]*)>|(\[!?|\]|\s*\?.*)/');
$this->xlat = [];
$i = count($parts) - 1;
// PARSE QUERY PART OF MASK
if (isset($parts[$i - 1]) && substr(ltrim($parts[$i - 1]), 0, 1) === '?') {
// name=<parameter-name [pattern]>
$matches = Strings::matchAll($parts[$i - 1], '/(?:([a-zA-Z0-9_.-]+)=)?<([^> ]+) *([^>]*)>/');
foreach ($matches as list(, $param, $name, $pattern)) { // $pattern is not used
if (isset(static::$styles['?' . $name])) {
$meta = static::$styles['?' . $name];
} else {
$meta = static::$styles['?#'];
}
if (isset($metadata[$name])) {
$meta = $metadata[$name] + $meta;
}
if (array_key_exists(self::VALUE, $meta)) {
$meta['fixity'] = self::OPTIONAL;
}
unset($meta['pattern']);
$meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? null : array_flip($meta[self::FILTER_TABLE]);
$metadata[$name] = $meta;
if ($param !== '') {
$this->xlat[$name] = $param;
}
}
$i -= 5;
}
// PARSE PATH PART OF MASK
$brackets = 0; // optional level
$re = '';
$sequence = [];
$autoOptional = true;
$aliases = [];
do {
$part = $parts[$i]; // part of path
if (strpbrk($part, '<>') !== false) {
throw new Nette\InvalidArgumentException("Unexpected '$part' in mask '$mask'.");
}
array_unshift($sequence, $part);
$re = preg_quote($part, '#') . $re;
if ($i === 0) {
break;
}
$i--;
$part = $parts[$i]; // [ or ]
if ($part === '[' || $part === ']' || $part === '[!') {
$brackets += $part[0] === '[' ? -1 : 1;
if ($brackets < 0) {
throw new Nette\InvalidArgumentException("Unexpected '$part' in mask '$mask'.");
}
array_unshift($sequence, $part);
$re = ($part[0] === '[' ? '(?:' : ')?') . $re;
$i -= 4;
continue;
}
$pattern = trim($parts[$i--]); // validation condition (as regexp)
$default = $parts[$i--]; // default value
$name = $parts[$i--]; // parameter name
array_unshift($sequence, $name);
if ($name[0] === '?') { // "foo" parameter
$name = substr($name, 1);
$re = $pattern ? '(?:' . preg_quote($name, '#') . "|$pattern)$re" : preg_quote($name, '#') . $re;
$sequence[1] = $name . $sequence[1];
continue;
}
// pattern, condition & metadata
if (isset(static::$styles[$name])) {
$meta = static::$styles[$name];
} else {
$meta = static::$styles['#'];
}
if (isset($metadata[$name])) {
$meta = $metadata[$name] + $meta;
}
if ($pattern == '' && isset($meta[self::PATTERN])) {
$pattern = $meta[self::PATTERN];
}
if ($default !== '') {
$meta[self::VALUE] = (string) substr($default, 1);
$meta['fixity'] = self::PATH_OPTIONAL;
}
$meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? null : array_flip($meta[self::FILTER_TABLE]);
if (array_key_exists(self::VALUE, $meta)) {
if (isset($meta['filterTable2'][$meta[self::VALUE]])) {
$meta['defOut'] = $meta['filterTable2'][$meta[self::VALUE]];
} elseif (isset($meta[self::FILTER_OUT])) {
$meta['defOut'] = call_user_func($meta[self::FILTER_OUT], $meta[self::VALUE]);
} else {
$meta['defOut'] = $meta[self::VALUE];
}
}
$meta[self::PATTERN] = "#(?:$pattern)\\z#A";
// include in expression
$aliases['p' . $i] = $name;
$re = '(?P<p' . $i . '>(?U)' . $pattern . ')' . $re;
if ($brackets) { // is in brackets?
if (!isset($meta[self::VALUE])) {
$meta[self::VALUE] = $meta['defOut'] = null;
}
$meta['fixity'] = self::PATH_OPTIONAL;
} elseif (isset($meta['fixity'])) {
if ($autoOptional) {
$re = '(?:' . $re . ')?';
}
$meta['fixity'] = self::PATH_OPTIONAL;
} else {
$autoOptional = false;
}
$metadata[$name] = $meta;
} while (true);
if ($brackets) {
throw new Nette\InvalidArgumentException("Missing '[' in mask '$mask'.");
}
$this->aliases = $aliases;
$this->re = '#' . $re . '/?\z#A';
$this->metadata = $metadata;
$this->sequence = $sequence;
}
/**
* Returns mask.
* @return string
*/
public function getMask()
{
return $this->mask;
}
/**
* Returns default values.
* @return array
*/
public function getDefaults()
{
$defaults = [];
foreach ($this->metadata as $name => $meta) {
if (isset($meta['fixity'])) {
$defaults[$name] = $meta[self::VALUE];
}
}
return $defaults;
}
/**
* Returns flags.
* @return int
*/
public function getFlags()
{
return $this->flags;
}
/********************* Utilities ****************d*g**/
/**
* Proprietary cache aim.
* @internal
* @return string[]|null
*/
public function getTargetPresenters()
{
if ($this->flags & self::ONE_WAY) {
return [];
}
$m = $this->metadata;
$module = '';
if (isset($m[self::MODULE_KEY])) {
if (isset($m[self::MODULE_KEY]['fixity']) && $m[self::MODULE_KEY]['fixity'] === self::CONSTANT) {
$module = $m[self::MODULE_KEY][self::VALUE] . ':';
} else {
return null;
}
}
if (isset($m[self::PRESENTER_KEY]['fixity']) && $m[self::PRESENTER_KEY]['fixity'] === self::CONSTANT) {
return [$module . $m[self::PRESENTER_KEY][self::VALUE]];
}
return null;
}
/**
* Rename keys in array.
* @param array
* @param array
* @return array
*/
private static function renameKeys($arr, $xlat)
{
if (empty($xlat)) {
return $arr;
}
$res = [];
$occupied = array_flip($xlat);
foreach ($arr as $k => $v) {
if (isset($xlat[$k])) {
$res[$xlat[$k]] = $v;
} elseif (!isset($occupied[$k])) {
$res[$k] = $v;
}
}
return $res;
}
/********************* Inflectors ****************d*g**/
/**
* camelCaseAction name -> dash-separated.
* @param string
* @return string
*/
public static function action2path($s)
{
$s = preg_replace('#(.)(?=[A-Z])#', '$1-', $s);
$s = strtolower($s);
$s = rawurlencode($s);
return $s;
}
/**
* dash-separated -> camelCaseAction name.
* @param string
* @return string
*/
public static function path2action($s)
{
$s = preg_replace('#-(?=[a-z])#', ' ', $s);
$s = lcfirst(ucwords($s));
$s = str_replace(' ', '', $s);
return $s;
}
/**
* PascalCase:Presenter name -> dash-and-dot-separated.
* @param string
* @return string
*/
public static function presenter2path($s)
{
$s = strtr($s, ':', '.');
$s = preg_replace('#([^.])(?=[A-Z])#', '$1-', $s);
$s = strtolower($s);
$s = rawurlencode($s);
return $s;
}
/**
* dash-and-dot-separated -> PascalCase:Presenter name.
* @param string
* @return string
*/
public static function path2presenter($s)
{
$s = preg_replace('#([.-])(?=[a-z])#', '$1 ', $s);
$s = ucwords($s);
$s = str_replace('. ', ':', $s);
$s = str_replace('- ', '', $s);
return $s;
}
/**
* Url encode.
* @param string
* @return string
*/
public static function param2path($s)
{
return str_replace('%2F', '/', rawurlencode($s));
}
}
@@ -0,0 +1,130 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\Routers;
use Nette;
/**
* The router broker.
*/
class RouteList extends Nette\Utils\ArrayList implements Nette\Application\IRouter
{
/** @var array */
private $cachedRoutes;
/** @var string|null */
private $module;
public function __construct($module = null)
{
$this->module = $module ? $module . ':' : '';
}
/**
* Maps HTTP request to a Request object.
* @return Nette\Application\Request|null
*/
public function match(Nette\Http\IRequest $httpRequest)
{
foreach ($this as $route) {
$appRequest = $route->match($httpRequest);
if ($appRequest !== null) {
$name = $appRequest->getPresenterName();
if (strncmp($name, 'Nette:', 6)) {
$appRequest->setPresenterName($this->module . $name);
}
return $appRequest;
}
}
return null;
}
/**
* Constructs absolute URL from Request object.
* @return string|null
*/
public function constructUrl(Nette\Application\Request $appRequest, Nette\Http\Url $refUrl)
{
if ($this->cachedRoutes === null) {
$this->warmupCache();
}
if ($this->module) {
if (strncmp($tmp = $appRequest->getPresenterName(), $this->module, strlen($this->module)) === 0) {
$appRequest = clone $appRequest;
$appRequest->setPresenterName(substr($tmp, strlen($this->module)));
} else {
return null;
}
}
$presenter = $appRequest->getPresenterName();
if (!isset($this->cachedRoutes[$presenter])) {
$presenter = '*';
}
foreach ($this->cachedRoutes[$presenter] as $route) {
$url = $route->constructUrl($appRequest, $refUrl);
if ($url !== null) {
return $url;
}
}
return null;
}
public function warmupCache()
{
$routes = [];
$routes['*'] = [];
foreach ($this as $route) {
$presenters = $route instanceof Route && is_array($tmp = $route->getTargetPresenters())
? $tmp
: array_keys($routes);
foreach ($presenters as $presenter) {
if (!isset($routes[$presenter])) {
$routes[$presenter] = $routes['*'];
}
$routes[$presenter][] = $route;
}
}
$this->cachedRoutes = $routes;
}
/**
* Adds the router.
* @param mixed
* @param Nette\Application\IRouter
* @return void
*/
public function offsetSet($index, $route)
{
if (!$route instanceof Nette\Application\IRouter) {
throw new Nette\InvalidArgumentException('Argument must be IRouter descendant.');
}
parent::offsetSet($index, $route);
}
/**
* @return string|null
*/
public function getModule()
{
return $this->module;
}
}
@@ -0,0 +1,150 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\Routers;
use Nette;
use Nette\Application;
/**
* The bidirectional route for trivial routing via query parameters.
*/
class SimpleRouter implements Application\IRouter
{
use Nette\SmartObject;
const PRESENTER_KEY = 'presenter';
const MODULE_KEY = 'module';
/** @var string */
private $module = '';
/** @var array */
private $defaults;
/** @var int */
private $flags;
/**
* @param array default values
* @param int flags
*/
public function __construct($defaults = [], $flags = 0)
{
if (is_string($defaults)) {
list($presenter, $action) = Nette\Application\Helpers::splitName($defaults);
if (!$presenter) {
throw new Nette\InvalidArgumentException("Argument must be array or string in format Presenter:action, '$defaults' given.");
}
$defaults = [
self::PRESENTER_KEY => $presenter,
'action' => $action === '' ? Application\UI\Presenter::DEFAULT_ACTION : $action,
];
}
if (isset($defaults[self::MODULE_KEY])) {
$this->module = $defaults[self::MODULE_KEY] . ':';
unset($defaults[self::MODULE_KEY]);
}
$this->defaults = $defaults;
$this->flags = $flags;
if ($flags & self::SECURED) {
trigger_error('IRouter::SECURED is deprecated, router by default keeps the used protocol.', E_USER_DEPRECATED);
}
}
/**
* Maps HTTP request to a Request object.
* @return Nette\Application\Request|null
*/
public function match(Nette\Http\IRequest $httpRequest)
{
if ($httpRequest->getUrl()->getPathInfo() !== '') {
return null;
}
// combine with precedence: get, (post,) defaults
$params = $httpRequest->getQuery();
$params += $this->defaults;
if (!isset($params[self::PRESENTER_KEY]) || !is_string($params[self::PRESENTER_KEY])) {
return null;
}
$presenter = $this->module . $params[self::PRESENTER_KEY];
unset($params[self::PRESENTER_KEY]);
return new Application\Request(
$presenter,
$httpRequest->getMethod(),
$params,
$httpRequest->getPost(),
$httpRequest->getFiles(),
[Application\Request::SECURED => $httpRequest->isSecured()]
);
}
/**
* Constructs absolute URL from Request object.
* @return string|null
*/
public function constructUrl(Application\Request $appRequest, Nette\Http\Url $refUrl)
{
if ($this->flags & self::ONE_WAY) {
return null;
}
$params = $appRequest->getParameters();
// presenter name
$presenter = $appRequest->getPresenterName();
if (strncmp($presenter, $this->module, strlen($this->module)) === 0) {
$params[self::PRESENTER_KEY] = substr($presenter, strlen($this->module));
} else {
return null;
}
// remove default values; null values are retain
foreach ($this->defaults as $key => $value) {
if (isset($params[$key]) && $params[$key] == $value) { // intentionally ==
unset($params[$key]);
}
}
$url = ($this->flags & self::SECURED ? 'https://' : $refUrl->getScheme() . '://') . $refUrl->getAuthority() . $refUrl->getPath();
$sep = ini_get('arg_separator.input');
$query = http_build_query($params, '', $sep ? $sep[0] : '&');
if ($query != '') { // intentionally ==
$url .= '?' . $query;
}
return $url;
}
/**
* Returns default values.
* @return array
*/
public function getDefaults()
{
return $this->defaults;
}
/**
* Returns flags.
* @return int
*/
public function getFlags()
{
return $this->flags;
}
}
@@ -0,0 +1,20 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\UI;
use Nette;
/**
* Signal exception.
*/
class BadSignalException extends Nette\Application\BadRequestException
{
/** @var int */
protected $code = 403;
}
@@ -0,0 +1,412 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\UI;
use Nette;
/**
* Component is the base class for all Presenter components.
*
* Components are persistent objects located on a presenter. They have ability to own
* other child components, and interact with user. Components have properties
* for storing their status, and responds to user command.
*
* @property-read Presenter $presenter
* @property-read bool $linkCurrent
*/
abstract class Component extends Nette\ComponentModel\Container implements ISignalReceiver, IStatePersistent, \ArrayAccess
{
/** @var callable[] function (self $sender); Occurs when component is attached to presenter */
public $onAnchor;
/** @var array */
protected $params = [];
/**
* Returns the presenter where this component belongs to.
* @param bool throw exception if presenter doesn't exist?
* @return Presenter|null
*/
public function getPresenter($throw = true)
{
return $this->lookup(Presenter::class, $throw);
}
/**
* Returns a fully-qualified name that uniquely identifies the component
* within the presenter hierarchy.
* @return string
*/
public function getUniqueId()
{
return $this->lookupPath(Presenter::class, true);
}
/**
* This method will be called when the component (or component's parent)
* becomes attached to a monitored object. Do not call this method yourself.
* @param Nette\ComponentModel\IComponent
* @return void
*/
protected function attached($presenter)
{
if ($presenter instanceof Presenter) {
$this->loadState($presenter->popGlobalParameters($this->getUniqueId()));
$this->onAnchor($this);
}
}
/**
* @return void
*/
protected function validateParent(Nette\ComponentModel\IContainer $parent)
{
parent::validateParent($parent);
$this->monitor(Presenter::class);
}
/**
* Calls public method if exists.
* @param string
* @param array
* @return bool does method exist?
*/
protected function tryCall($method, array $params)
{
$rc = $this->getReflection();
if ($rc->hasMethod($method)) {
$rm = $rc->getMethod($method);
if ($rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic()) {
$this->checkRequirements($rm);
$rm->invokeArgs($this, $rc->combineArgs($rm, $params));
return true;
}
}
return false;
}
/**
* Checks for requirements such as authorization.
* @return void
*/
public function checkRequirements($element)
{
}
/**
* Access to reflection.
* @return ComponentReflection
*/
public static function getReflection()
{
return new ComponentReflection(get_called_class());
}
/********************* interface IStatePersistent ****************d*g**/
/**
* Loads state informations.
* @param array
* @return void
*/
public function loadState(array $params)
{
$reflection = $this->getReflection();
foreach ($reflection->getPersistentParams() as $name => $meta) {
if (isset($params[$name])) { // nulls are ignored
$type = gettype($meta['def']);
if (!$reflection->convertType($params[$name], $type)) {
throw new Nette\Application\BadRequestException(sprintf(
"Value passed to persistent parameter '%s' in %s must be %s, %s given.",
$name,
$this instanceof Presenter ? 'presenter ' . $this->getName() : "component '{$this->getUniqueId()}'",
$type === 'NULL' ? 'scalar' : $type,
is_object($params[$name]) ? get_class($params[$name]) : gettype($params[$name])
));
}
$this->$name = $params[$name];
} else {
$params[$name] = $this->$name;
}
}
$this->params = $params;
}
/**
* Saves state informations for next request.
* @return void
*/
public function saveState(array &$params)
{
$this->getReflection()->saveState($this, $params);
}
/**
* Returns component param.
* @param string key
* @param mixed default value
* @return mixed
*/
public function getParameter($name, $default = null)
{
if (isset($this->params[$name])) {
return $this->params[$name];
} else {
return $default;
}
}
/**
* Returns component parameters.
* @return array
*/
public function getParameters()
{
return $this->params;
}
/**
* Returns a fully-qualified name that uniquely identifies the parameter.
* @param string
* @return string
*/
public function getParameterId($name)
{
$uid = $this->getUniqueId();
return $uid === '' ? $name : $uid . self::NAME_SEPARATOR . $name;
}
/** @deprecated */
public function getParam($name = null, $default = null)
{
//trigger_error(__METHOD__ . '() is deprecated; use getParameter() instead.', E_USER_DEPRECATED);
return func_num_args() ? $this->getParameter($name, $default) : $this->getParameters();
}
/**
* Returns array of classes persistent parameters. They have public visibility and are non-static.
* This default implementation detects persistent parameters by annotation @persistent.
* @return array
*/
public static function getPersistentParams()
{
$rc = new \ReflectionClass(get_called_class());
$params = [];
foreach ($rc->getProperties(\ReflectionProperty::IS_PUBLIC) as $rp) {
if (!$rp->isStatic() && ComponentReflection::parseAnnotation($rp, 'persistent')) {
$params[] = $rp->getName();
}
}
return $params;
}
/********************* interface ISignalReceiver ****************d*g**/
/**
* Calls signal handler method.
* @param string
* @return void
* @throws BadSignalException if there is not handler method
*/
public function signalReceived($signal)
{
if (!$this->tryCall($this->formatSignalMethod($signal), $this->params)) {
$class = get_class($this);
throw new BadSignalException("There is no handler for signal '$signal' in class $class.");
}
}
/**
* Formats signal handler method name -> case sensitivity doesn't matter.
* @param string
* @return string
*/
public static function formatSignalMethod($signal)
{
return $signal == null ? null : 'handle' . $signal; // intentionally ==
}
/********************* navigation ****************d*g**/
/**
* Generates URL to presenter, action or signal.
* @param string destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]"
* @param array|mixed
* @return string
* @throws InvalidLinkException
*/
public function link($destination, $args = [])
{
try {
$args = func_num_args() < 3 && is_array($args) ? $args : array_slice(func_get_args(), 1);
return $this->getPresenter()->createRequest($this, $destination, $args, 'link');
} catch (InvalidLinkException $e) {
return $this->getPresenter()->handleInvalidLink($e);
}
}
/**
* Returns destination as Link object.
* @param string destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]"
* @param array|mixed
* @return Link
*/
public function lazyLink($destination, $args = [])
{
$args = func_num_args() < 3 && is_array($args) ? $args : array_slice(func_get_args(), 1);
return new Link($this, $destination, $args);
}
/**
* Determines whether it links to the current page.
* @param string destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]"
* @param array|mixed
* @return bool
* @throws InvalidLinkException
*/
public function isLinkCurrent($destination = null, $args = [])
{
if ($destination !== null) {
$args = func_num_args() < 3 && is_array($args) ? $args : array_slice(func_get_args(), 1);
$this->getPresenter()->createRequest($this, $destination, $args, 'test');
}
return $this->getPresenter()->getLastCreatedRequestFlag('current');
}
/**
* Redirect to another presenter, action or signal.
* @param int [optional] HTTP error code
* @param string destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]"
* @param array|mixed
* @return void
* @throws Nette\Application\AbortException
*/
public function redirect($code, $destination = null, $args = [])
{
if (!is_numeric($code)) { // first parameter is optional
$args = func_num_args() < 3 && is_array($destination) ? $destination : array_slice(func_get_args(), 1);
$destination = $code;
$code = null;
} elseif (func_num_args() > 3 || !is_array($args)) {
$args = array_slice(func_get_args(), 2);
}
$presenter = $this->getPresenter();
$presenter->redirectUrl($presenter->createRequest($this, $destination, $args, 'redirect'), $code);
}
/**
* Permanently redirects to presenter, action or signal.
* @param string destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]"
* @param array|mixed
* @return void
* @throws Nette\Application\AbortException
*/
public function redirectPermanent($destination = null, $args = [])
{
$args = func_num_args() < 3 && is_array($args) ? $args : array_slice(func_get_args(), 1);
$presenter = $this->getPresenter();
$presenter->redirectUrl(
$presenter->createRequest($this, $destination, $args, 'redirect'),
Nette\Http\IResponse::S301_MOVED_PERMANENTLY
);
}
/**
* Throws HTTP error.
* @param string
* @param int HTTP error code
* @return void
* @throws Nette\Application\BadRequestException
*/
public function error($message = null, $httpCode = Nette\Http\IResponse::S404_NOT_FOUND)
{
throw new Nette\Application\BadRequestException($message, $httpCode);
}
/********************* interface \ArrayAccess ****************d*g**/
/**
* Adds the component to the container.
* @param string component name
* @param Nette\ComponentModel\IComponent
* @return void
*/
public function offsetSet($name, $component)
{
$this->addComponent($component, $name);
}
/**
* Returns component specified by name. Throws exception if component doesn't exist.
* @param string component name
* @return Nette\ComponentModel\IComponent
* @throws Nette\InvalidArgumentException
*/
public function offsetGet($name)
{
return $this->getComponent($name, true);
}
/**
* Does component specified by name exists?
* @param string component name
* @return bool
*/
public function offsetExists($name)
{
return $this->getComponent($name, false) !== null;
}
/**
* Removes component from the container.
* @param string component name
* @return void
*/
public function offsetUnset($name)
{
$component = $this->getComponent($name, false);
if ($component !== null) {
$this->removeComponent($component);
}
}
}
@@ -0,0 +1,354 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\UI;
use Nette;
use Nette\Application\BadRequestException;
use Nette\Reflection\ClassType;
/**
* Helpers for Presenter & Component.
* @property-read string $name
* @property-read string $fileName
* @internal
*/
class ComponentReflection extends \ReflectionClass
{
use Nette\SmartObject;
/** @var array getPersistentParams cache */
private static $ppCache = [];
/** @var array getPersistentComponents cache */
private static $pcCache = [];
/** @var array isMethodCallable cache */
private static $mcCache = [];
/**
* @param string|null
* @return array of persistent parameters.
*/
public function getPersistentParams($class = null)
{
$class = $class === null ? $this->getName() : $class;
$params = &self::$ppCache[$class];
if ($params !== null) {
return $params;
}
$params = [];
if (is_subclass_of($class, Component::class)) {
$isPresenter = is_subclass_of($class, Presenter::class);
$defaults = get_class_vars($class);
foreach ($class::getPersistentParams() as $name => $default) {
if (is_int($name)) {
$name = $default;
$default = $defaults[$name];
}
$params[$name] = [
'def' => $default,
'since' => $isPresenter ? $class : null,
];
}
foreach ($this->getPersistentParams(get_parent_class($class)) as $name => $param) {
if (isset($params[$name])) {
$params[$name]['since'] = $param['since'];
continue;
}
$params[$name] = $param;
}
}
return $params;
}
/**
* @param string|null
* @return array of persistent components.
*/
public function getPersistentComponents($class = null)
{
$class = $class === null ? $this->getName() : $class;
$components = &self::$pcCache[$class];
if ($components !== null) {
return $components;
}
$components = [];
if (is_subclass_of($class, Presenter::class)) {
foreach ($class::getPersistentComponents() as $name => $meta) {
if (is_string($meta)) {
$name = $meta;
}
$components[$name] = ['since' => $class];
}
$components = $this->getPersistentComponents(get_parent_class($class)) + $components;
}
return $components;
}
/**
* Saves state informations for next request.
*/
public function saveState(Component $component, array &$params)
{
foreach ($this->getPersistentParams() as $name => $meta) {
if (isset($params[$name])) {
// injected value
} elseif (
array_key_exists($name, $params) // nulls are skipped
|| (isset($meta['since']) && !$component instanceof $meta['since']) // not related
|| !isset($component->$name)
) {
continue;
} else {
$params[$name] = $component->$name; // object property value
}
$type = gettype($meta['def']);
if (!self::convertType($params[$name], $type)) {
throw new InvalidLinkException(sprintf(
"Value passed to persistent parameter '%s' in %s must be %s, %s given.",
$name,
$component instanceof Presenter ? 'presenter ' . $component->getName() : "component '{$component->getUniqueId()}'",
$type === 'NULL' ? 'scalar' : $type,
is_object($params[$name]) ? get_class($params[$name]) : gettype($params[$name])
));
}
if ($params[$name] === $meta['def'] || ($meta['def'] === null && $params[$name] === '')) {
$params[$name] = null; // value transmit is unnecessary
}
}
}
/**
* Is a method callable? It means class is instantiable and method has
* public visibility, is non-static and non-abstract.
* @param string method name
* @return bool
*/
public function hasCallableMethod($method)
{
$class = $this->getName();
$cache = &self::$mcCache[strtolower($class . ':' . $method)];
if ($cache === null) {
try {
$cache = false;
$rm = new \ReflectionMethod($class, $method);
$cache = $this->isInstantiable() && $rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic();
} catch (\ReflectionException $e) {
}
}
return $cache;
}
/**
* @return array
*/
public static function combineArgs(\ReflectionFunctionAbstract $method, $args)
{
$res = [];
foreach ($method->getParameters() as $i => $param) {
$name = $param->getName();
list($type, $isClass) = self::getParameterType($param);
if (isset($args[$name])) {
$res[$i] = $args[$name];
if (!self::convertType($res[$i], $type, $isClass)) {
throw new BadRequestException(sprintf(
'Argument $%s passed to %s() must be %s, %s given.',
$name,
($method instanceof \ReflectionMethod ? $method->getDeclaringClass()->getName() . '::' : '') . $method->getName(),
$type === 'NULL' ? 'scalar' : $type,
is_object($args[$name]) ? get_class($args[$name]) : gettype($args[$name])
));
}
} elseif ($param->isDefaultValueAvailable()) {
$res[$i] = $param->getDefaultValue();
} elseif ($type === 'NULL' || $param->allowsNull()) {
$res[$i] = null;
} elseif ($type === 'array') {
$res[$i] = [];
} else {
throw new BadRequestException(sprintf(
'Missing parameter $%s required by %s()',
$name,
($method instanceof \ReflectionMethod ? $method->getDeclaringClass()->getName() . '::' : '') . $method->getName()
));
}
}
return $res;
}
/**
* Non data-loss type conversion.
* @param mixed
* @param string
* @return bool
*/
public static function convertType(&$val, $type, $isClass = false)
{
if ($isClass) {
return $val instanceof $type;
} elseif ($type === 'callable') {
return false;
} elseif ($type === 'NULL') { // means 'not array'
return !is_array($val);
} elseif ($type === 'array') {
return is_array($val);
} elseif (!is_scalar($val)) { // array, resource, null, etc.
return false;
} else {
$tmp = ($val === false ? '0' : (string) $val);
if ($type === 'double' || $type === 'float') {
$tmp = preg_replace('#\.0*\z#', '', $tmp);
}
$orig = $tmp;
settype($tmp, $type);
if ($orig !== ($tmp === false ? '0' : (string) $tmp)) {
return false; // data-loss occurs
}
$val = $tmp;
}
return true;
}
/**
* Returns an annotation value.
* @return array|false
*/
public static function parseAnnotation(\Reflector $ref, $name)
{
if (!preg_match_all('#[\\s*]@' . preg_quote($name, '#') . '(?:\(\\s*([^)]*)\\s*\)|\\s|$)#', $ref->getDocComment(), $m)) {
return false;
}
static $tokens = ['true' => true, 'false' => false, 'null' => null];
$res = [];
foreach ($m[1] as $s) {
foreach (preg_split('#\s*,\s*#', $s, -1, PREG_SPLIT_NO_EMPTY) ?: ['true'] as $item) {
$res[] = array_key_exists($tmp = strtolower($item), $tokens) ? $tokens[$tmp] : $item;
}
}
return $res;
}
/**
* @return array [string|null, bool]
*/
public static function getParameterType(\ReflectionParameter $param)
{
$def = gettype($param->isDefaultValueAvailable() ? $param->getDefaultValue() : null);
if (PHP_VERSION_ID >= 70000) {
return $param->hasType()
? [PHP_VERSION_ID >= 70100 ? $param->getType()->getName() : (string) $param->getType(), !$param->getType()->isBuiltin()]
: [$def, false];
} elseif ($param->isArray() || $param->isCallable()) {
return [$param->isArray() ? 'array' : 'callable', false];
} else {
try {
return ($ref = $param->getClass()) ? [$ref->getName(), true] : [$def, false];
} catch (\ReflectionException $e) {
if (preg_match('#Class (.+) does not exist#', $e->getMessage(), $m)) {
throw new \LogicException(sprintf(
"Class %s not found. Check type hint of parameter $%s in %s() or 'use' statements.",
$m[1],
$param->getName(),
$param->getDeclaringFunction()->getDeclaringClass()->getName() . '::' . $param->getDeclaringFunction()->getName()
));
}
throw $e;
}
}
}
/********************* compatiblity with Nette\Reflection ****************d*g**/
/**
* Has class specified annotation?
* @param string
* @return bool
*/
public function hasAnnotation($name)
{
return (bool) self::parseAnnotation($this, $name);
}
/**
* Returns an annotation value.
* @param string
* @return mixed
*/
public function getAnnotation($name)
{
$res = self::parseAnnotation($this, $name);
return $res ? end($res) : null;
}
/**
* @return MethodReflection
*/
public function getMethod($name)
{
return new MethodReflection($this->getName(), $name);
}
/**
* @return MethodReflection[]
*/
public function getMethods($filter = -1)
{
foreach ($res = parent::getMethods($filter) as $key => $val) {
$res[$key] = new MethodReflection($this->getName(), $val->getName());
}
return $res;
}
public function __toString()
{
trigger_error(__METHOD__ . ' is deprecated.', E_USER_DEPRECATED);
return $this->getName();
}
public function __get($name)
{
trigger_error("getReflection()->$name is deprecated.", E_USER_DEPRECATED);
return (new ClassType($this->getName()))->$name;
}
public function __call($name, $args)
{
if (method_exists(ClassType::class, $name)) {
trigger_error("getReflection()->$name() is deprecated, use Nette\\Reflection\\ClassType::from(\$presenter)->$name()", E_USER_DEPRECATED);
return call_user_func_array([new ClassType($this->getName()), $name], $args);
}
Nette\Utils\ObjectMixin::strictCall(get_class($this), $name);
}
}
+185
View File
@@ -0,0 +1,185 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\UI;
use Nette;
/**
* Control is renderable Presenter component.
*
* @property-read ITemplate|Nette\Bridges\ApplicationLatte\Template|\stdClass $template
*/
abstract class Control extends Component implements IRenderable
{
/** @var bool */
public $snippetMode;
/** @var ITemplateFactory */
private $templateFactory;
/** @var ITemplate */
private $template;
/** @var array */
private $invalidSnippets = [];
/********************* template factory ****************d*g**/
public function setTemplateFactory(ITemplateFactory $templateFactory)
{
$this->templateFactory = $templateFactory;
}
/**
* @return ITemplate
*/
public function getTemplate()
{
if ($this->template === null) {
$value = $this->createTemplate();
if (!$value instanceof ITemplate && $value !== null) {
$class2 = get_class($value);
$class = get_class($this);
throw new Nette\UnexpectedValueException("Object returned by $class::createTemplate() must be instance of Nette\\Application\\UI\\ITemplate, '$class2' given.");
}
$this->template = $value;
}
return $this->template;
}
/**
* @return ITemplate
*/
protected function createTemplate()
{
$templateFactory = $this->templateFactory ?: $this->getPresenter()->getTemplateFactory();
return $templateFactory->createTemplate($this);
}
/**
* Descendant can override this method to customize template compile-time filters.
* @param ITemplate
* @return void
*/
public function templatePrepareFilters($template)
{
}
/**
* Saves the message to template, that can be displayed after redirect.
* @param string
* @param string
* @return \stdClass
*/
public function flashMessage($message, $type = 'info')
{
$id = $this->getParameterId('flash');
$messages = $this->getPresenter()->getFlashSession()->$id;
$messages[] = $flash = (object) [
'message' => $message,
'type' => $type,
];
$this->getTemplate()->flashes = $messages;
$this->getPresenter()->getFlashSession()->$id = $messages;
return $flash;
}
/********************* rendering ****************d*g**/
/**
* Forces control or its snippet to repaint.
* @return void
*/
public function redrawControl($snippet = null, $redraw = true)
{
if ($redraw) {
$this->invalidSnippets[$snippet === null ? "\0" : $snippet] = true;
} elseif ($snippet === null) {
$this->invalidSnippets = [];
} else {
$this->invalidSnippets[$snippet] = false;
}
}
/** @deprecated */
public function invalidateControl($snippet = null)
{
trigger_error(__METHOD__ . '() is deprecated; use $this->redrawControl($snippet) instead.', E_USER_DEPRECATED);
$this->redrawControl($snippet);
}
/** @deprecated */
public function validateControl($snippet = null)
{
trigger_error(__METHOD__ . '() is deprecated; use $this->redrawControl($snippet, false) instead.', E_USER_DEPRECATED);
$this->redrawControl($snippet, false);
}
/**
* Is required to repaint the control or its snippet?
* @param string snippet name
* @return bool
*/
public function isControlInvalid($snippet = null)
{
if ($snippet === null) {
if (count($this->invalidSnippets) > 0) {
return true;
} else {
$queue = [$this];
do {
foreach (array_shift($queue)->getComponents() as $component) {
if ($component instanceof IRenderable) {
if ($component->isControlInvalid()) {
// $this->invalidSnippets['__child'] = true; // as cache
return true;
}
} elseif ($component instanceof Nette\ComponentModel\IContainer) {
$queue[] = $component;
}
}
} while ($queue);
return false;
}
} elseif (isset($this->invalidSnippets[$snippet])) {
return $this->invalidSnippets[$snippet];
} else {
return isset($this->invalidSnippets["\0"]);
}
}
/**
* Returns snippet HTML ID.
* @param string snippet name
* @return string
*/
public function getSnippetId($name = null)
{
// HTML 4 ID & NAME: [A-Za-z][A-Za-z0-9:_.-]*
return 'snippet-' . $this->getUniqueId() . '-' . $name;
}
}
+150
View File
@@ -0,0 +1,150 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\UI;
use Nette;
/**
* Web form adapted for Presenter.
*/
class Form extends Nette\Forms\Form implements ISignalReceiver
{
/** @var callable[] function (self $sender); Occurs when form is attached to presenter */
public $onAnchor;
/**
* Application form constructor.
*/
public function __construct(Nette\ComponentModel\IContainer $parent = null, $name = null)
{
parent::__construct();
if ($parent !== null) {
$parent->addComponent($this, $name);
}
}
/**
* @return void
*/
protected function validateParent(Nette\ComponentModel\IContainer $parent)
{
parent::validateParent($parent);
$this->monitor(Presenter::class);
}
/**
* Returns the presenter where this component belongs to.
* @param bool throw exception if presenter doesn't exist?
* @return Presenter|null
*/
public function getPresenter($throw = true)
{
return $this->lookup(Presenter::class, $throw);
}
/**
* This method will be called when the component (or component's parent)
* becomes attached to a monitored object. Do not call this method yourself.
* @param Nette\ComponentModel\IComponent
* @return void
*/
protected function attached($presenter)
{
if ($presenter instanceof Presenter) {
if (!isset($this->getElementPrototype()->id)) {
$this->getElementPrototype()->id = 'frm-' . $this->lookupPath(Presenter::class);
}
if (!$this->getAction()) {
$this->setAction(new Link($presenter, 'this'));
}
$controls = $this->getControls();
if (iterator_count($controls) && $this->isSubmitted()) {
foreach ($controls as $control) {
if (!$control->isDisabled()) {
$control->loadHttpData();
}
}
}
$this->onAnchor($this);
}
parent::attached($presenter);
}
/**
* Tells if the form is anchored.
* @return bool
*/
public function isAnchored()
{
return (bool) $this->getPresenter(false);
}
/**
* Internal: returns submitted HTTP data or null when form was not submitted.
* @return array|null
*/
protected function receiveHttpData()
{
$presenter = $this->getPresenter();
if (!$presenter->isSignalReceiver($this, 'submit')) {
return;
}
$request = $presenter->getRequest();
if ($request->isMethod('forward') || $request->isMethod('post') !== $this->isMethod('post')) {
return;
}
if ($this->isMethod('post')) {
return Nette\Utils\Arrays::mergeTree($request->getPost(), $request->getFiles());
} else {
return $request->getParameters();
}
}
protected function beforeRender()
{
parent::beforeRender();
$key = ($this->isMethod('post') ? '_' : '') . Presenter::SIGNAL_KEY;
if (!isset($this[$key])) {
$do = $this->lookupPath(Presenter::class) . self::NAME_SEPARATOR . 'submit';
$this[$key] = (new Nette\Forms\Controls\HiddenField($do))->setOmitted()->setHtmlId(false);
}
}
/********************* interface ISignalReceiver ****************d*g**/
/**
* This method is called by presenter.
* @param string
* @return void
*/
public function signalReceived($signal)
{
if ($signal === 'submit') {
if (!$this->getPresenter()->getRequest()->hasFlag(Nette\Application\Request::RESTORED)) {
$this->fireEvents();
}
} else {
$class = get_class($this);
throw new BadSignalException("Missing handler for signal '$signal' in $class.");
}
}
}
@@ -0,0 +1,28 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\UI;
/**
* Component with ability to repaint.
*/
interface IRenderable
{
/**
* Forces control to repaint.
* @return void
*/
function redrawControl();
/**
* Is required to repaint the control?
* @return bool
*/
function isControlInvalid();
}
@@ -0,0 +1,22 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\UI;
/**
* Component with ability to receive signal.
*/
interface ISignalReceiver
{
/**
* @param string
* @return void
*/
function signalReceived($signal); // handleSignal
}
@@ -0,0 +1,28 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\UI;
/**
* Component with ability to save and load its state.
*/
interface IStatePersistent
{
/**
* Loads state informations.
* @return void
*/
function loadState(array $params);
/**
* Saves state informations for next request.
* @return void
*/
function saveState(array &$params);
}
@@ -0,0 +1,35 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\UI;
/**
* Defines template.
*/
interface ITemplate
{
/**
* Renders template to output.
* @return void
*/
function render();
/**
* Sets the path to the template file.
* @param string
* @return static
*/
function setFile($file);
/**
* Returns the path to the template file.
* @return string|null
*/
function getFile();
}
@@ -0,0 +1,21 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\UI;
/**
* Defines ITemplate factory.
*/
interface ITemplateFactory
{
/**
* @return ITemplate
*/
function createTemplate(Control $control = null);
}
@@ -0,0 +1,16 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\UI;
/**
* Link generation exception.
*/
class InvalidLinkException extends \Exception
{
}
+105
View File
@@ -0,0 +1,105 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\UI;
use Nette;
/**
* Lazy encapsulation of Component::link().
* Do not instantiate directly, use Component::lazyLink()
*/
class Link
{
use Nette\SmartObject;
/** @var Component */
private $component;
/** @var string */
private $destination;
/** @var array */
private $params;
/**
* Link specification.
*/
public function __construct(Component $component, $destination, array $params = [])
{
$this->component = $component;
$this->destination = $destination;
$this->params = $params;
}
/**
* Returns link destination.
* @return string
*/
public function getDestination()
{
return $this->destination;
}
/**
* Changes link parameter.
* @param string
* @param mixed
* @return static
*/
public function setParameter($key, $value)
{
$this->params[$key] = $value;
return $this;
}
/**
* Returns link parameter.
* @param string
* @return mixed
*/
public function getParameter($key)
{
return isset($this->params[$key]) ? $this->params[$key] : null;
}
/**
* Returns link parameters.
* @return array
*/
public function getParameters()
{
return $this->params;
}
/**
* Converts link to URL.
* @return string
*/
public function __toString()
{
try {
return $this->component->link($this->destination, $this->params);
} catch (\Exception $e) {
} catch (\Throwable $e) {
}
if (isset($e)) {
if (func_num_args()) {
throw $e;
}
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
}
}
}
@@ -0,0 +1,63 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\UI;
use Nette;
use Nette\Reflection\Method;
/**
* @internal
*/
class MethodReflection extends \ReflectionMethod
{
use Nette\SmartObject;
/**
* Has method specified annotation?
* @param string
* @return bool
*/
public function hasAnnotation($name)
{
return (bool) ComponentReflection::parseAnnotation($this, $name);
}
/**
* Returns an annotation value.
* @param string
* @return mixed
*/
public function getAnnotation($name)
{
$res = ComponentReflection::parseAnnotation($this, $name);
return $res ? end($res) : null;
}
public function __toString()
{
trigger_error(__METHOD__ . ' is deprecated.', E_USER_DEPRECATED);
return parent::getDeclaringClass()->getName() . '::' . $this->getName() . '()';
}
public function __get($name)
{
trigger_error("getMethod('{$this->getName()}')->$name is deprecated.", E_USER_DEPRECATED);
return (new Method(parent::getDeclaringClass()->getName(), $this->getName()))->$name;
}
public function __call($name, $args)
{
trigger_error("getMethod('{$this->getName()}')->$name() is deprecated, use Nette\\Reflection\\Method::from(\$presenter, '{$this->getName()}')->$name()", E_USER_DEPRECATED);
return call_user_func_array([new Method(parent::getDeclaringClass()->getName(), $this->getName()), $name], $args);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application\UI;
/**
* Component multiplier.
*/
class Multiplier extends Component
{
/** @var callable */
private $factory;
public function __construct(callable $factory)
{
parent::__construct();
$this->factory = $factory;
}
protected function createComponent($name)
{
return call_user_func($this->factory, $name, $this);
}
}
File diff suppressed because it is too large Load Diff
+70
View File
@@ -0,0 +1,70 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Application;
use Nette\Http;
/**
* The exception that is thrown when user attempts to terminate the current presenter or application.
* This is special "silent exception" with no error message or code.
*/
class AbortException extends \Exception
{
}
/**
* Application fatal error.
*/
class ApplicationException extends \Exception
{
}
/**
* The exception that is thrown when a presenter cannot be loaded.
*/
class InvalidPresenterException extends \Exception
{
}
/**
* The exception that indicates client error with HTTP code 4xx.
*/
class BadRequestException extends \Exception
{
/** @var int */
protected $code = Http\IResponse::S404_NOT_FOUND;
public function __construct($message = '', $httpCode = 0, \Exception $previous = null)
{
parent::__construct($message, $httpCode ?: $this->code, $previous);
}
/**
* @return int
*/
public function getHttpCode()
{
return $this->code;
}
}
/**
* Forbidden request exception - access denied.
*/
class ForbiddenRequestException extends BadRequestException
{
/** @var int */
protected $code = Http\IResponse::S403_FORBIDDEN;
}
@@ -0,0 +1,38 @@
<?php
/**
* Default error page.
* @param int $code
*/
namespace NetteModule;
$messages = [
0 => ['Oops...', 'Your browser sent a request that this server could not understand or process.'],
403 => ['Access Denied', 'You do not have permission to view this page. Please try contact the web site administrator if you believe you should be able to view this page.'],
404 => ['Page Not Found', 'The page you requested could not be found. It is possible that the address is incorrect, or that the page no longer exists. Please use a search engine to find what you are looking for.'],
405 => ['Method Not Allowed', 'The requested method is not allowed for the URL.'],
410 => ['Page Not Found', 'The page you requested has been taken off the site. We apologize for the inconvenience.'],
500 => ['Server Error', 'We\'re sorry! The server encountered an internal error and was unable to complete your request. Please try again later.'],
];
$message = isset($messages[$code]) ? $messages[$code] : $messages[0];
?>
<!DOCTYPE html>
<meta charset="utf-8">
<meta name=robots content=noindex>
<meta name=generator content="Nette Framework">
<style>
body { color: #333; background: white; width: 500px; margin: 100px auto }
h1 { font: bold 47px/1.5 sans-serif; margin: .6em 0 }
p { font: 21px/1.5 Georgia,serif; margin: 1.5em 0 }
small { font-size: 70%; color: gray }
</style>
<title><?= $message[0] ?></title>
<h1><?= $message[0] ?></h1>
<p><?= $message[1] ?></p>
<?php if ($code): ?><p><small>error <?= $code ?></small></p><?php endif ?>
@@ -0,0 +1,165 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\ApplicationDI;
use Composer\Autoload\ClassLoader;
use Nette;
use Nette\Application\UI;
use Tracy;
/**
* Application extension for Nette DI.
*/
class ApplicationExtension extends Nette\DI\CompilerExtension
{
public $defaults = [
'debugger' => null,
'errorPresenter' => 'Nette:Error',
'catchExceptions' => null,
'mapping' => null,
'scanDirs' => [],
'scanComposer' => null,
'scanFilter' => 'Presenter',
'silentLinks' => false,
];
/** @var bool */
private $debugMode;
/** @var int */
private $invalidLinkMode;
/** @var string */
private $tempFile;
public function __construct($debugMode = false, array $scanDirs = null, $tempDir = null)
{
$this->defaults['debugger'] = interface_exists(Tracy\IBarPanel::class);
$this->defaults['scanDirs'] = (array) $scanDirs;
$this->defaults['scanComposer'] = class_exists(ClassLoader::class);
$this->defaults['catchExceptions'] = !$debugMode;
$this->debugMode = $debugMode;
$this->tempFile = $tempDir ? $tempDir . '/' . urlencode(__CLASS__) : null;
}
public function loadConfiguration()
{
$config = $this->validateConfig($this->defaults);
$builder = $this->getContainerBuilder();
$builder->addExcludedClasses([UI\Presenter::class]);
$this->invalidLinkMode = $this->debugMode
? UI\Presenter::INVALID_LINK_TEXTUAL | ($config['silentLinks'] ? 0 : UI\Presenter::INVALID_LINK_WARNING)
: UI\Presenter::INVALID_LINK_WARNING;
$application = $builder->addDefinition($this->prefix('application'))
->setFactory(Nette\Application\Application::class)
->addSetup('$catchExceptions', [$config['catchExceptions']])
->addSetup('$errorPresenter', [$config['errorPresenter']]);
if ($config['debugger']) {
$application->addSetup('Nette\Bridges\ApplicationTracy\RoutingPanel::initializePanel');
}
$touch = $this->debugMode && $config['scanDirs'] ? $this->tempFile : null;
$presenterFactory = $builder->addDefinition($this->prefix('presenterFactory'))
->setClass(Nette\Application\IPresenterFactory::class)
->setFactory(Nette\Application\PresenterFactory::class, [new Nette\DI\Statement(
Nette\Bridges\ApplicationDI\PresenterFactoryCallback::class, [1 => $this->invalidLinkMode, $touch]
)]);
if ($config['mapping']) {
$presenterFactory->addSetup('setMapping', [$config['mapping']]);
}
$builder->addDefinition($this->prefix('linkGenerator'))
->setFactory(Nette\Application\LinkGenerator::class, [
1 => new Nette\DI\Statement('@Nette\Http\IRequest::getUrl'),
]);
if ($this->name === 'application') {
$builder->addAlias('application', $this->prefix('application'));
$builder->addAlias('nette.presenterFactory', $this->prefix('presenterFactory'));
}
}
public function beforeCompile()
{
$builder = $this->getContainerBuilder();
$all = [];
foreach ($builder->findByType(Nette\Application\IPresenter::class) as $def) {
$all[$def->getClass()] = $def;
}
$counter = 0;
foreach ($this->findPresenters() as $class) {
if (empty($all[$class])) {
$all[$class] = $builder->addDefinition($this->prefix(++$counter))->setClass($class);
}
}
foreach ($all as $def) {
$def->addTag(Nette\DI\Extensions\InjectExtension::TAG_INJECT)
->addTag('nette.presenter', $def->getClass());
if (is_subclass_of($def->getClass(), UI\Presenter::class)) {
$def->addSetup('$invalidLinkMode', [$this->invalidLinkMode]);
}
}
}
/** @return string[] */
private function findPresenters()
{
$config = $this->getConfig();
$classes = [];
if ($config['scanDirs']) {
if (!class_exists(Nette\Loaders\RobotLoader::class)) {
throw new Nette\NotSupportedException("RobotLoader is required to find presenters, install package `nette/robot-loader` or disable option {$this->prefix('scanDirs')}: false");
}
$robot = new Nette\Loaders\RobotLoader;
$robot->addDirectory($config['scanDirs']);
$robot->acceptFiles = ['*' . $config['scanFilter'] . '*.php'];
$robot->rebuild();
$classes = array_keys($robot->getIndexedClasses());
$this->getContainerBuilder()->addDependency($this->tempFile);
}
if ($config['scanComposer']) {
$rc = new \ReflectionClass(ClassLoader::class);
$classFile = dirname($rc->getFileName()) . '/autoload_classmap.php';
if (is_file($classFile)) {
$this->getContainerBuilder()->addDependency($classFile);
$classes = array_merge($classes, array_keys(call_user_func(function ($path) {
return require $path;
}, $classFile)));
}
}
$presenters = [];
foreach (array_unique($classes) as $class) {
if (
strpos($class, $config['scanFilter']) !== false
&& class_exists($class)
&& ($rc = new \ReflectionClass($class))
&& $rc->implementsInterface(Nette\Application\IPresenter::class)
&& !$rc->isAbstract()
) {
$presenters[] = $rc->getName();
}
}
return $presenters;
}
}
@@ -0,0 +1,96 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\ApplicationDI;
use Latte;
use Nette;
/**
* Latte extension for Nette DI.
*/
class LatteExtension extends Nette\DI\CompilerExtension
{
public $defaults = [
'xhtml' => false,
'macros' => [],
'templateClass' => null,
];
/** @var bool */
private $debugMode;
/** @var string */
private $tempDir;
public function __construct($tempDir, $debugMode = false)
{
$this->tempDir = $tempDir;
$this->debugMode = $debugMode;
}
public function loadConfiguration()
{
if (!class_exists(Latte\Engine::class)) {
return;
}
$config = $this->validateConfig($this->defaults);
$builder = $this->getContainerBuilder();
$builder->addDefinition($this->prefix('latteFactory'))
->setFactory(Latte\Engine::class)
->addSetup('setTempDirectory', [$this->tempDir])
->addSetup('setAutoRefresh', [$this->debugMode])
->addSetup('setContentType', [$config['xhtml'] ? Latte\Compiler::CONTENT_XHTML : Latte\Compiler::CONTENT_HTML])
->addSetup('Nette\Utils\Html::$xhtml = ?', [(bool) $config['xhtml']])
->setImplement(Nette\Bridges\ApplicationLatte\ILatteFactory::class);
$builder->addDefinition($this->prefix('templateFactory'))
->setClass(Nette\Application\UI\ITemplateFactory::class)
->setFactory(Nette\Bridges\ApplicationLatte\TemplateFactory::class)
->setArguments(['templateClass' => $config['templateClass']]);
foreach ($config['macros'] as $macro) {
$this->addMacro($macro);
}
if ($this->name === 'latte') {
$builder->addAlias('nette.latteFactory', $this->prefix('latteFactory'));
$builder->addAlias('nette.templateFactory', $this->prefix('templateFactory'));
}
}
/**
* @param string
* @return void
*/
public function addMacro($macro)
{
$builder = $this->getContainerBuilder();
$definition = $builder->getDefinition($this->prefix('latteFactory'));
if (isset($macro[0]) && $macro[0] === '@') {
if (strpos($macro, '::') === false) {
$method = 'install';
} else {
list($macro, $method) = explode('::', $macro);
}
$definition->addSetup('?->onCompile[] = function ($engine) { ?->' . $method . '($engine->getCompiler()); }', ['@self', $macro]);
} else {
if (strpos($macro, '::') === false && class_exists($macro)) {
$macro .= '::install';
}
$definition->addSetup('?->onCompile[] = function ($engine) { ' . $macro . '($engine->getCompiler()); }', ['@self']);
}
}
}
@@ -0,0 +1,61 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\ApplicationDI;
use Nette;
/**
* PresenterFactory callback.
* @internal
*/
class PresenterFactoryCallback
{
/** @var Nette\DI\Container */
private $container;
/** @var int */
private $invalidLinkMode;
/** @var string|null */
private $touchToRefresh;
public function __construct(Nette\DI\Container $container, $invalidLinkMode, $touchToRefresh)
{
$this->container = $container;
$this->invalidLinkMode = $invalidLinkMode;
$this->touchToRefresh = $touchToRefresh;
}
/**
* @return Nette\Application\IPresenter
*/
public function __invoke($class)
{
$services = array_keys($this->container->findByTag('nette.presenter'), $class, true);
if (count($services) > 1) {
throw new Nette\Application\InvalidPresenterException("Multiple services of type $class found: " . implode(', ', $services) . '.');
} elseif (!$services) {
if ($this->touchToRefresh) {
touch($this->touchToRefresh);
}
$presenter = $this->container->createInstance($class);
$this->container->callInjects($presenter);
if ($presenter instanceof Nette\Application\UI\Presenter && $presenter->invalidLinkMode === null) {
$presenter->invalidLinkMode = $this->invalidLinkMode;
}
return $presenter;
}
return $this->container->createService($services[0]);
}
}
@@ -0,0 +1,87 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\ApplicationDI;
use Nette;
use Tracy;
/**
* Routing extension for Nette DI.
*/
class RoutingExtension extends Nette\DI\CompilerExtension
{
public $defaults = [
'debugger' => null,
'routes' => [], // of [mask => action]
'routeClass' => null,
'cache' => false,
];
/** @var bool */
private $debugMode;
public function __construct($debugMode = false)
{
$this->defaults['debugger'] = interface_exists(Tracy\IBarPanel::class);
$this->debugMode = $debugMode;
}
public function loadConfiguration()
{
$config = $this->validateConfig($this->defaults);
$builder = $this->getContainerBuilder();
$router = $builder->addDefinition($this->prefix('router'))
->setClass(Nette\Application\IRouter::class)
->setFactory(Nette\Application\Routers\RouteList::class);
$routeClass = $config['routeClass'] ?: 'Nette\Application\Routers\Route';
foreach ($config['routes'] as $mask => $action) {
$router->addSetup('$service[] = new ' . $routeClass . '(?, ?)', [$mask, $action]);
}
if ($this->name === 'routing') {
$builder->addAlias('router', $this->prefix('router'));
}
}
public function beforeCompile()
{
$builder = $this->getContainerBuilder();
if ($this->debugMode && $this->config['debugger'] && $application = $builder->getByType(Nette\Application\Application::class)) {
$builder->getDefinition($application)->addSetup('@Tracy\Bar::addPanel', [
new Nette\DI\Statement(Nette\Bridges\ApplicationTracy\RoutingPanel::class),
]);
}
}
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
if (!empty($this->config['cache'])) {
$method = $class->getMethod(Nette\DI\Container::getMethodName($this->prefix('router')));
try {
$router = eval($method->getBody());
if ($router instanceof Nette\Application\Routers\RouteList) {
$router->warmupCache();
}
$s = serialize($router);
} catch (\Exception $e) {
throw new Nette\DI\ServiceCreationException('Unable to cache router due to error: ' . $e->getMessage(), 0, $e);
} catch (\Throwable $e) {
throw new Nette\DI\ServiceCreationException('Unable to cache router due to error: ' . $e->getMessage(), 0, $e);
}
$method->setBody('return unserialize(?);', [$s]);
}
}
}
@@ -0,0 +1,20 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\ApplicationLatte;
use Latte;
interface ILatteFactory
{
/**
* @return Latte\Engine
*/
function create();
}
@@ -0,0 +1,28 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\ApplicationLatte;
use Latte;
use Nette;
/**
* Template loader.
*/
class Loader extends Latte\Loaders\FileLoader
{
/** @var Nette\Application\UI\Presenter */
private $presenter;
public function __construct(Nette\Application\UI\Presenter $presenter)
{
parent::__construct();
$this->presenter = $presenter;
}
}
@@ -0,0 +1,94 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\ApplicationLatte;
use Latte\Runtime\ISnippetBridge;
use Nette;
use Nette\Application\UI\Control;
use Nette\Application\UI\IRenderable;
/**
* @internal
*/
class SnippetBridge implements ISnippetBridge
{
use Nette\SmartObject;
/** @var Control */
private $control;
/** @var \stdClass|null */
private $payload;
public function __construct(Control $control)
{
$this->control = $control;
}
public function isSnippetMode()
{
return $this->control->snippetMode;
}
public function setSnippetMode($snippetMode)
{
$this->control->snippetMode = $snippetMode;
}
public function needsRedraw($name)
{
return $this->control->isControlInvalid($name);
}
public function markRedrawn($name)
{
if ($name !== '') {
$this->control->redrawControl($name, false);
}
}
public function getHtmlId($name)
{
return $this->control->getSnippetId($name);
}
public function addSnippet($name, $content)
{
if ($this->payload === null) {
$this->payload = $this->control->getPresenter()->getPayload();
}
$this->payload->snippets[$this->control->getSnippetId($name)] = $content;
}
public function renderChildren()
{
$queue = [$this->control];
do {
foreach (array_shift($queue)->getComponents() as $child) {
if ($child instanceof IRenderable) {
if ($child->isControlInvalid()) {
$child->snippetMode = true;
$child->render();
$child->snippetMode = false;
}
} elseif ($child instanceof Nette\ComponentModel\IContainer) {
$queue[] = $child;
}
}
} while ($queue);
}
}
@@ -0,0 +1,240 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\ApplicationLatte;
use Latte;
use Nette;
/**
* Latte powered template.
*/
class Template implements Nette\Application\UI\ITemplate
{
use Nette\SmartObject;
/** @var Latte\Engine */
private $latte;
/** @var string */
private $file;
/** @var array */
private $params = [];
public function __construct(Latte\Engine $latte)
{
$this->latte = $latte;
}
/**
* @return Latte\Engine
*/
public function getLatte()
{
return $this->latte;
}
/**
* Renders template to output.
* @return void
*/
public function render($file = null, array $params = [])
{
$this->latte->render($file ?: $this->file, $params + $this->params);
}
/**
* Renders template to output.
* @return string
*/
public function renderToString($file = null, array $params = [])
{
return $this->latte->renderToString($file ?: $this->file, $params + $this->params);
}
/**
* Renders template to string.
* @param can throw exceptions? (hidden parameter)
* @return string
*/
public function __toString()
{
try {
return $this->latte->renderToString($this->file, $this->params);
} catch (\Exception $e) {
} catch (\Throwable $e) {
}
if (isset($e)) {
if (func_num_args()) {
throw $e;
}
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
}
}
/********************* template filters & helpers ****************d*g**/
/**
* Registers run-time filter.
* @param string|null
* @param callable
* @return static
*/
public function addFilter($name, $callback)
{
$this->latte->addFilter($name, $callback);
return $this;
}
/**
* Alias for addFilter()
* @deprecated
*/
public function registerHelper($name, $callback)
{
trigger_error(__METHOD__ . '() is deprecated, use getLatte()->addFilter().', E_USER_DEPRECATED);
return $this->latte->addFilter($name, $callback);
}
/**
* Sets translate adapter.
* @return static
*/
public function setTranslator(Nette\Localization\ITranslator $translator = null)
{
$this->latte->addFilter('translate', $translator === null ? null : function (Latte\Runtime\FilterInfo $fi, ...$args) use ($translator) {
return $translator->translate(...$args);
});
return $this;
}
/********************* template parameters ****************d*g**/
/**
* Sets the path to the template file.
* @param string
* @return static
*/
public function setFile($file)
{
$this->file = $file;
return $this;
}
/**
* @return string|null
*/
public function getFile()
{
return $this->file;
}
/**
* Adds new template parameter.
* @return static
*/
public function add($name, $value)
{
if (array_key_exists($name, $this->params)) {
throw new Nette\InvalidStateException("The variable '$name' already exists.");
}
$this->params[$name] = $value;
return $this;
}
/**
* Sets all parameters.
* @param array
* @return static
*/
public function setParameters(array $params)
{
$this->params = $params + $this->params;
return $this;
}
/**
* Returns array of all parameters.
* @return array
*/
public function getParameters()
{
return $this->params;
}
/**
* @deprecated
*/
public function __call($name, $args)
{
trigger_error('Invoking filters on Template object is deprecated, use getLatte()->invokeFilter().', E_USER_DEPRECATED);
return $this->latte->invokeFilter($name, $args);
}
/**
* Sets a template parameter. Do not call directly.
* @return void
*/
public function __set($name, $value)
{
$this->params[$name] = $value;
}
/**
* Returns a template parameter. Do not call directly.
* @return mixed value
*/
public function &__get($name)
{
if (!array_key_exists($name, $this->params)) {
trigger_error("The variable '$name' does not exist in template.", E_USER_NOTICE);
}
return $this->params[$name];
}
/**
* Determines whether parameter is defined. Do not call directly.
* @return bool
*/
public function __isset($name)
{
return isset($this->params[$name]);
}
/**
* Removes a template parameter. Do not call directly.
* @param string name
* @return void
*/
public function __unset($name)
{
unset($this->params[$name]);
}
}
@@ -0,0 +1,134 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\ApplicationLatte;
use Latte;
use Nette;
use Nette\Application\UI;
/**
* Latte powered template factory.
*/
class TemplateFactory implements UI\ITemplateFactory
{
use Nette\SmartObject;
/** @var callable[] function (Template $template); Occurs when a new template is created */
public $onCreate;
/** @var ILatteFactory */
private $latteFactory;
/** @var Nette\Http\IRequest */
private $httpRequest;
/** @var Nette\Security\User */
private $user;
/** @var Nette\Caching\IStorage */
private $cacheStorage;
/** @var string */
private $templateClass;
public function __construct(ILatteFactory $latteFactory, Nette\Http\IRequest $httpRequest = null,
Nette\Security\User $user = null, Nette\Caching\IStorage $cacheStorage = null, $templateClass = null)
{
$this->latteFactory = $latteFactory;
$this->httpRequest = $httpRequest;
$this->user = $user;
$this->cacheStorage = $cacheStorage;
if ($templateClass && (!class_exists($templateClass) || !is_a($templateClass, Template::class, true))) {
throw new Nette\InvalidArgumentException("Class $templateClass does not extend " . Template::class . ' or it does not exist.');
}
$this->templateClass = $templateClass ?: Template::class;
}
/**
* @return Template
*/
public function createTemplate(UI\Control $control = null)
{
$latte = $this->latteFactory->create();
$template = new $this->templateClass($latte);
$presenter = $control ? $control->getPresenter(false) : null;
if ($control instanceof UI\Presenter) {
$latte->setLoader(new Loader($control));
}
if ($latte->onCompile instanceof \Traversable) {
$latte->onCompile = iterator_to_array($latte->onCompile);
}
array_unshift($latte->onCompile, function ($latte) use ($control, $template) {
if ($this->cacheStorage) {
$latte->getCompiler()->addMacro('cache', new Nette\Bridges\CacheLatte\CacheMacro);
}
UIMacros::install($latte->getCompiler());
if (class_exists(Nette\Bridges\FormsLatte\FormMacros::class)) {
Nette\Bridges\FormsLatte\FormMacros::install($latte->getCompiler());
}
if ($control) {
$control->templatePrepareFilters($template);
}
});
$latte->addFilter('url', 'rawurlencode'); // back compatiblity
foreach (['normalize', 'toAscii', 'webalize', 'reverse'] as $name) {
$latte->addFilter($name, 'Nette\Utils\Strings::' . $name);
}
$latte->addFilter('null', function () {});
$latte->addFilter('modifyDate', function ($time, $delta, $unit = null) {
return $time == null ? null : Nette\Utils\DateTime::from($time)->modify($delta . $unit); // intentionally ==
});
if (!isset($latte->getFilters()['translate'])) {
$latte->addFilter('translate', function (Latte\Runtime\FilterInfo $fi) {
throw new Nette\InvalidStateException('Translator has not been set. Set translator using $template->setTranslator().');
});
}
// default parameters
$template->user = $this->user;
$template->baseUri = $template->baseUrl = $this->httpRequest ? rtrim($this->httpRequest->getUrl()->getBaseUrl(), '/') : null;
$template->basePath = preg_replace('#https?://[^/]+#A', '', $template->baseUrl);
$template->flashes = [];
if ($control) {
$template->control = $control;
$template->presenter = $presenter;
$latte->addProvider('uiControl', $control);
$latte->addProvider('uiPresenter', $presenter);
$latte->addProvider('snippetBridge', new Nette\Bridges\ApplicationLatte\SnippetBridge($control));
if ($presenter) {
$header = $presenter->getHttpResponse()->getHeader('Content-Security-Policy')
?: $presenter->getHttpResponse()->getHeader('Content-Security-Policy-Report-Only');
}
$nonce = $presenter && preg_match('#\s\'nonce-([\w+/]+=*)\'#', (string) $header, $m) ? $m[1] : null;
$latte->addProvider('uiNonce', $nonce);
}
$latte->addProvider('cacheStorage', $this->cacheStorage);
// back compatibility
$template->_control = $control;
$template->_presenter = $presenter;
$template->netteCacheStorage = $this->cacheStorage;
if ($presenter instanceof UI\Presenter && $presenter->hasFlashSession()) {
$id = $control->getParameterId('flash');
$template->flashes = (array) $presenter->getFlashSession()->$id;
}
$this->onCreate($template);
return $template;
}
}
@@ -0,0 +1,158 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\ApplicationLatte;
use Latte;
use Latte\CompileException;
use Latte\MacroNode;
use Latte\PhpWriter;
use Nette;
use Nette\Utils\Strings;
/**
* Macros for Nette\Application\UI.
*
* - {link destination ...} control link
* - {plink destination ...} presenter link
* - {snippet ?} ... {/snippet ?} control snippet
* - n:nonce
*/
class UIMacros extends Latte\Macros\MacroSet
{
/** @var bool|string */
private $extends;
public static function install(Latte\Compiler $compiler)
{
$me = new static($compiler);
$me->addMacro('control', [$me, 'macroControl']);
$me->addMacro('href', null, null, function (MacroNode $node, PhpWriter $writer) use ($me) {
return ' ?> href="<?php ' . $me->macroLink($node, $writer) . ' ?>"<?php ';
});
$me->addMacro('plink', [$me, 'macroLink']);
$me->addMacro('link', [$me, 'macroLink']);
$me->addMacro('ifCurrent', [$me, 'macroIfCurrent'], '}'); // deprecated; use n:class="$presenter->linkCurrent ? ..."
$me->addMacro('extends', [$me, 'macroExtends']);
$me->addMacro('layout', [$me, 'macroExtends']);
$me->addMacro('nonce', null, null, 'echo $this->global->uiNonce ? " nonce=\"{$this->global->uiNonce}\"" : "";');
}
/**
* Initializes before template parsing.
* @return void
*/
public function initialize()
{
$this->extends = false;
}
/**
* Finishes template parsing.
* @return array(prolog, epilog)
*/
public function finalize()
{
return [$this->extends . 'Nette\Bridges\ApplicationLatte\UIRuntime::initialize($this, $this->parentName, $this->blocks);'];
}
/********************* macros ****************d*g**/
/**
* {control name[:method] [params]}
*/
public function macroControl(MacroNode $node, PhpWriter $writer)
{
$words = $node->tokenizer->fetchWords();
if (!$words) {
throw new CompileException('Missing control name in {control}');
}
$name = $writer->formatWord($words[0]);
$method = isset($words[1]) ? ucfirst($words[1]) : '';
$method = Strings::match($method, '#^\w*\z#') ? "render$method" : "{\"render$method\"}";
$tokens = $node->tokenizer;
$pos = $tokens->position;
$param = $writer->formatArray();
$tokens->position = $pos;
while ($tokens->nextToken()) {
if ($tokens->isCurrent('=>') && !$tokens->depth) {
$wrap = true;
break;
}
}
if (empty($wrap)) {
$param = substr($param, 1, -1); // removes array() or []
}
return "/* line $node->startLine */ "
. ($name[0] === '$' ? "if (is_object($name)) \$_tmp = $name; else " : '')
. '$_tmp = $this->global->uiControl->getComponent(' . $name . '); '
. 'if ($_tmp instanceof Nette\Application\UI\IRenderable) $_tmp->redrawControl(null, false); '
. ($node->modifiers === ''
? "\$_tmp->$method($param);"
: $writer->write("ob_start(function () {}); \$_tmp->$method($param); echo %modify(ob_get_clean());")
);
}
/**
* {link destination [,] [params]}
* {plink destination [,] [params]}
* n:href="destination [,] [params]"
*/
public function macroLink(MacroNode $node, PhpWriter $writer)
{
$node->modifiers = preg_replace('#\|safeurl\s*(?=\||\z)#i', '', $node->modifiers);
return $writer->using($node)
->write('echo %escape(%modify('
. ($node->name === 'plink' ? '$this->global->uiPresenter' : '$this->global->uiControl')
. '->link(%node.word, %node.array?)))'
);
}
/**
* {ifCurrent destination [,] [params]}
*/
public function macroIfCurrent(MacroNode $node, PhpWriter $writer)
{
if ($node->modifiers) {
throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
}
return $writer->write($node->args
? 'if ($this->global->uiPresenter->isLinkCurrent(%node.word, %node.array?)) {'
: 'if ($this->global->uiPresenter->getLastCreatedRequestFlag("current")) {'
);
}
/**
* {extends auto}
*/
public function macroExtends(MacroNode $node, PhpWriter $writer)
{
if ($node->modifiers || $node->parentNode || $node->args !== 'auto') {
return $this->extends = false;
}
$this->extends = $writer->write('$this->parentName = $this->global->uiPresenter->findLayoutTemplateFile();');
}
/** @deprecated */
public static function renderSnippets(Nette\Application\UI\Control $control, \stdClass $local, array $params)
{
trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
UIRuntime::renderSnippets($control, $local, $params);
}
}
@@ -0,0 +1,49 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\ApplicationLatte;
use Latte;
use Nette;
/**
* Runtime helpers for UI macros.
* @internal
*/
class UIRuntime
{
use Nette\StaticClass;
/**
* @return void
*/
public static function initialize(Latte\Runtime\Template $template, &$parentName, array $blocks)
{
$providers = $template->global;
$blocks = array_filter(array_keys($blocks), function ($s) { return $s[0] !== '_'; });
if (
$parentName === null
&& $blocks
&& !$template->getReferringTemplate()
&& isset($providers->uiControl) && $providers->uiControl instanceof Nette\Application\UI\Presenter
) {
$parentName = $providers->uiControl->findLayoutTemplateFile();
}
// back compatiblity
$params = $template->getParameters();
if (empty($providers->uiControl) && isset($params['_control'])) {
trigger_error('Replace template variable $_control with provider: $latte->addProvider("uiControl", ...)', E_USER_DEPRECATED);
$providers->uiControl = $params['_control'];
}
if (empty($providers->uiPresenter) && isset($params['_presenter'])) {
trigger_error('Replace template variable $_presenter with provider: $latte->addProvider("uiPresenter", ...)', E_USER_DEPRECATED);
$providers->uiPresenter = $params['_presenter'];
}
}
}
@@ -0,0 +1,158 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\ApplicationTracy;
use Nette;
use Nette\Application\Routers;
use Nette\Application\UI\Presenter;
use Tracy;
use Tracy\Dumper;
/**
* Routing debugger for Debug Bar.
*/
class RoutingPanel implements Tracy\IBarPanel
{
use Nette\SmartObject;
/** @var Nette\Application\IRouter */
private $router;
/** @var Nette\Http\IRequest */
private $httpRequest;
/** @var Nette\Application\IPresenterFactory */
private $presenterFactory;
/** @var array */
private $routers = [];
/** @var Nette\Application\Request */
private $request;
/** @var \ReflectionClass|\ReflectionMethod */
private $source;
public static function initializePanel(Nette\Application\Application $application)
{
Tracy\Debugger::getBlueScreen()->addPanel(function ($e) use ($application) {
return $e ? null : [
'tab' => 'Nette Application',
'panel' => '<h3>Requests</h3>' . Dumper::toHtml($application->getRequests(), [Dumper::LIVE => true])
. '<h3>Presenter</h3>' . Dumper::toHtml($application->getPresenter(), [Dumper::LIVE => true]),
];
});
}
public function __construct(Nette\Application\IRouter $router, Nette\Http\IRequest $httpRequest, Nette\Application\IPresenterFactory $presenterFactory)
{
$this->router = $router;
$this->httpRequest = $httpRequest;
$this->presenterFactory = $presenterFactory;
}
/**
* Renders tab.
* @return string
*/
public function getTab()
{
$this->analyse($this->router);
ob_start(function () {});
$request = $this->request;
require __DIR__ . '/templates/RoutingPanel.tab.phtml';
return ob_get_clean();
}
/**
* Renders panel.
* @return string
*/
public function getPanel()
{
ob_start(function () {});
$request = $this->request;
$routers = $this->routers;
$source = $this->source;
$hasModule = (bool) array_filter($routers, function ($rq) { return $rq['module']; });
$url = $this->httpRequest->getUrl();
$method = $this->httpRequest->getMethod();
require __DIR__ . '/templates/RoutingPanel.panel.phtml';
return ob_get_clean();
}
/**
* Analyses simple route.
* @param Nette\Application\IRouter
* @return void
*/
private function analyse($router, $module = '')
{
if ($router instanceof Routers\RouteList) {
foreach ($router as $subRouter) {
$this->analyse($subRouter, $module . $router->getModule());
}
return;
}
$matched = 'no';
$request = $router->match($this->httpRequest);
if ($request) {
$request->setPresenterName($module . $request->getPresenterName());
$matched = 'may';
if (empty($this->request)) {
$this->request = $request;
$this->findSource();
$matched = 'yes';
}
}
$this->routers[] = [
'matched' => $matched,
'class' => get_class($router),
'defaults' => $router instanceof Routers\Route || $router instanceof Routers\SimpleRouter ? $router->getDefaults() : [],
'mask' => $router instanceof Routers\Route ? $router->getMask() : null,
'request' => $request,
'module' => rtrim($module, ':'),
];
}
private function findSource()
{
$request = $this->request;
$presenter = $request->getPresenterName();
try {
$class = $this->presenterFactory->getPresenterClass($presenter);
} catch (Nette\Application\InvalidPresenterException $e) {
return;
}
$rc = new \ReflectionClass($class);
if ($rc->isSubclassOf(Nette\Application\UI\Presenter::class)) {
if ($request->getParameter(Presenter::SIGNAL_KEY)) {
$method = $class::formatSignalMethod($request->getParameter(Presenter::SIGNAL_KEY));
} elseif ($request->getParameter(Presenter::ACTION_KEY)) {
$action = $request->getParameter(Presenter::ACTION_KEY);
$method = $class::formatActionMethod($action);
if (!$rc->hasMethod($method)) {
$method = $class::formatRenderMethod($action);
}
}
}
$this->source = isset($method) && $rc->hasMethod($method) ? $rc->getMethod($method) : $rc;
}
}
@@ -0,0 +1,95 @@
<?php
namespace Nette\Bridges\ApplicationTracy;
use Nette\Application\UI\Presenter;
use Tracy;
use Tracy\Dumper;
?>
<style class="tracy-debug">
#tracy-debug .nette-RoutingPanel table {
font: 9pt/1.5 Consolas, monospace;
}
#tracy-debug .nette-RoutingPanel .yes td {
background: #BDE678 !important;
}
#tracy-debug .nette-RoutingPanel .may td {
background: #C1D3FF !important;
}
#tracy-debug .nette-RoutingPanel pre, #tracy-debug .nette-RoutingPanel code {
display: inline;
background: transparent;
}
#tracy-debug .nette-RoutingPanel-rel {
background: #eee;
white-space: nowrap
}
</style>
<h1>
<?php if (empty($request)): ?>
no route
<?php else: ?>
<?= htmlspecialchars($request->getPresenterName() . ':' . (isset($request->parameters[Presenter::ACTION_KEY]) ? $request->parameters[Presenter::ACTION_KEY] : Presenter::DEFAULT_ACTION) . (isset($request->parameters[Presenter::SIGNAL_KEY]) ? " {$request->parameters[Presenter::SIGNAL_KEY]}!" : ''), ENT_NOQUOTES, 'UTF-8') ?>
<?php endif ?>
</h1>
<div class="tracy-inner nette-RoutingPanel">
<div class="tracy-inner-container">
<?php if (empty($routers)): ?>
<p>No routers defined.</p>
<?php else: ?>
<table>
<thead>
<tr>
<th></th>
<th>Mask / Class</th>
<th>Defaults</th>
<?php if ($hasModule): ?><th>Module</th><?php endif ?>
<th>Matched as</th>
</tr>
</thead>
<tbody>
<?php foreach ($routers as $router): ?>
<tr class="<?= $router['matched'] ?>">
<td><?= $router['matched'] === 'yes' ? '✓' : ($router['matched'] === 'may' ? '≈' : '') ?></td>
<td><code title="<?= htmlspecialchars($router['class'], ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars(isset($router['mask']) ? $router['mask'] : $router['class'], ENT_NOQUOTES, 'UTF-8') ?></code></td>
<td><code>
<?php foreach ($router['defaults'] as $key => $value): ?>
<?= htmlspecialchars($key, ENT_IGNORE, 'UTF-8'), '&nbsp;=&nbsp;', is_string($value) ? htmlspecialchars($value, ENT_IGNORE, 'UTF-8') . '<br />' : Dumper::toHtml($value, [Dumper::COLLAPSE => true, Dumper::LIVE => true]) ?>
<?php endforeach ?>
</code></td>
<?php if ($hasModule): ?><td><code><?= htmlspecialchars($router['module'], ENT_NOQUOTES, 'UTF-8') ?></code></td><?php endif ?>
<td><?php if ($router['request']): ?><code>
<?php $params = $router['request']->getParameters(); ?>
<strong><?= htmlspecialchars($router['request']->getPresenterName() . ':' . (isset($params[Presenter::ACTION_KEY]) ? $params[Presenter::ACTION_KEY] : Presenter::DEFAULT_ACTION), ENT_NOQUOTES, 'UTF-8') ?></strong><br />
<?php unset($params[Presenter::ACTION_KEY]) ?>
<?php foreach ($params as $key => $value): ?>
<?= htmlspecialchars($key, ENT_IGNORE, 'UTF-8'), '&nbsp;=&nbsp;', is_string($value) ? htmlspecialchars($value, ENT_IGNORE, 'UTF-8') . '<br />' : Dumper::toHtml($value, [Dumper::COLLAPSE => true, Dumper::LIVE => true]) ?>
<?php endforeach ?>
</code><?php endif ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php endif ?>
<p><code><?= htmlspecialchars($method, ENT_IGNORE, 'UTF-8') ?></code>
<code><?= htmlspecialchars($url->getBaseUrl(), ENT_IGNORE, 'UTF-8') ?><span class="nette-RoutingPanel-rel"><?= htmlspecialchars($url->getRelativeUrl(), ENT_IGNORE, 'UTF-8') ?></span></code></p>
<?php if ($source): ?>
<p><a href="<?= htmlspecialchars(Tracy\Helpers::editorUri($source->getFileName(), $source->getStartLine()), ENT_QUOTES, 'UTF-8') ?>"><?= $source instanceof \ReflectionClass ? $source->getName() : $source->getDeclaringClass()->getName() . '::' . $source->getName() . '()' ?></a></p>
<?php endif ?>
</div>
</div>
@@ -0,0 +1,13 @@
<?php
namespace Nette\Bridges\ApplicationTracy;
use Nette\Application\UI\Presenter;
?>
<span title="Router">
<svg viewBox="0 0 2048 2048">
<path fill="#d86b01" d="m1559.7 1024c0 17-6 32-19 45l-670 694.48c-13 13-28 19-45 19s-32-6-45-19-19-28-19-45v-306.48h-438.52c-17 0-32-6-45-19s-19-28-19-45v-642c0-17 6-32 19-45s28-19 45-19h438.52v-309.41c0-17 6-32 19-45s28-19 45-19 32 6 45 19l670 691.41c13 13 19 28 19 45z"/>
<path d="m1914.7 1505c0 79-31 147-87 204-56 56-124 85-203 85h-320c-9 0-16-3-22-9-14-23-21-90 3-110 5-4 12-6 21-6h320c44 0 82-16 113-47s47-69 47-113v-962c0-44-16-82-47-113s-69-47-113-47h-312c-11 0-21-3-30-9-15-25-21-90 3-110 5-4 12-6 21-6h320c79 0 147 28 204 85 56 56 82 124 82 204-9 272 9 649 0 954z" fill-opacity=".5" fill="#d86b01"/>
</svg><span class="tracy-label"><?php if (empty($request)): ?>no route<?php else: echo htmlspecialchars($request->getPresenterName() . ':' . (isset($request->parameters[Presenter::ACTION_KEY]) ? $request->parameters[Presenter::ACTION_KEY] : Presenter::DEFAULT_ACTION) . (isset($request->parameters[Presenter::SIGNAL_KEY]) ? " {$request->parameters[Presenter::SIGNAL_KEY]}!" : ''), ENT_NOQUOTES, 'UTF-8'); endif ?></span>
</span>
+9
View File
@@ -0,0 +1,9 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
class_alias('Nette\Application\UI\Component', 'Nette\Application\UI\PresenterComponent');
class_alias('Nette\Application\UI\ComponentReflection', 'Nette\Application\UI\PresenterComponentReflection');