diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 708879fd6..00b138de4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.1', '7.2', '7.3', '7.4'] + php: ['7.2', '7.3', '7.4'] fail-fast: false diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php index 00de74bd3..2b7559165 100644 --- a/.phpstorm.meta.php +++ b/.phpstorm.meta.php @@ -64,3 +64,5 @@ exitPoint(\Nette\Application\UI\Presenter::sendResponse()); exitPoint(\Nette\Application\UI\Presenter::sendTemplate()); exitPoint(\Nette\Application\UI\Presenter::terminate()); + +override(\Nette\Application\UI\Control::createTemplate(0), map(['' => '@'])); diff --git a/.travis.yml b/.travis.yml index 1b1266b7f..76bb2c84c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: php php: - - 7.1 - 7.2 - 7.3 - 7.4 diff --git a/composer.json b/composer.json index aadd179f5..b003a1541 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=7.1", + "php": ">=7.2", "nette/component-model": "^3.0", "nette/http": "^3.0.2", "nette/routing": "~3.0.0", @@ -31,7 +31,7 @@ "nette/forms": "^3.0", "nette/robot-loader": "^3.2", "nette/security": "^3.0", - "latte/latte": "^2.6.2", + "latte/latte": "^2.7", "tracy/tracy": "^2.6", "mockery/mockery": "^1.0", "phpstan/phpstan-nette": "^0.12" @@ -39,7 +39,7 @@ "conflict": { "nette/di": "<3.0-stable", "nette/forms": "<3.0", - "latte/latte": "<2.6", + "latte/latte": "<2.7.1", "tracy/tracy": "<2.5" }, "autoload": { @@ -52,7 +52,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } } } diff --git a/src/Application/LinkGenerator.php b/src/Application/LinkGenerator.php index 7738e1404..465c34b72 100644 --- a/src/Application/LinkGenerator.php +++ b/src/Application/LinkGenerator.php @@ -51,6 +51,13 @@ public function link(string $dest, array $params = []): string } [, $presenter, $action, $frag] = $m; + if ($presenter[0] === ':') { // absolute + $presenter = substr($presenter, 1); + if (!$presenter) { + throw new UI\InvalidLinkException("Missing presenter name in '$dest'."); + } + } + try { $class = $this->presenterFactory ? $this->presenterFactory->getPresenterClass($presenter) : null; } catch (InvalidPresenterException $e) { diff --git a/src/Application/MicroPresenter.php b/src/Application/MicroPresenter.php index 62f17a20e..2b2491cb4 100644 --- a/src/Application/MicroPresenter.php +++ b/src/Application/MicroPresenter.php @@ -114,7 +114,7 @@ public function run(Application\Request $request): Application\IResponse public function createTemplate(string $class = null, callable $latteFactory = null): Application\UI\ITemplate { $latte = $latteFactory ? $latteFactory() : $this->getContext()->getByType(Nette\Bridges\ApplicationLatte\ILatteFactory::class)->create(); - $template = $class ? new $class : new Nette\Bridges\ApplicationLatte\Template($latte); + $template = $class ? new $class : new Nette\Bridges\ApplicationLatte\DefaultTemplate($latte); $template->setParameters($this->request->getParameters()); $template->presenter = $this; diff --git a/src/Application/Routers/CliRouter.php b/src/Application/Routers/CliRouter.php index cd7216bb3..b334dbbc4 100644 --- a/src/Application/Routers/CliRouter.php +++ b/src/Application/Routers/CliRouter.php @@ -10,13 +10,12 @@ namespace Nette\Application\Routers; use Nette; -use Nette\Application; /** * The unidirectional router for CLI. (experimental) */ -final class CliRouter implements Application\IRouter +final class CliRouter implements Nette\Routing\Router { use Nette\SmartObject; diff --git a/src/Application/Routers/Route.php b/src/Application/Routers/Route.php index 5cca47530..578fd95bc 100644 --- a/src/Application/Routers/Route.php +++ b/src/Application/Routers/Route.php @@ -16,7 +16,7 @@ * The bidirectional route is responsible for mapping * HTTP request to an array for dispatch and vice-versa. */ -class Route extends Nette\Routing\Route implements Nette\Application\IRouter +class Route extends Nette\Routing\Route implements Nette\Routing\Router { private const PRESENTER_KEY = 'presenter', @@ -40,9 +40,6 @@ class Route extends Nette\Routing\Route implements Nette\Application\IRouter ], ]; - /** @deprecated */ - public static $styles = []; - /** @var int */ private $flags; @@ -69,12 +66,11 @@ public function __construct(string $mask, $metadata = [], int $flags = 0) ]; } - $this->defaultMeta = $this->defaultMeta + self::UI_META; - if (self::$styles) { - trigger_error('Route::$styles is deprecated.', E_USER_DEPRECATED); - array_replace_recursive($this->defaultMeta, self::$styles); + if ($flags) { + trigger_error(__METHOD__ . '() parameter $flags is deprecated, use RouteList::addRoute(..., ..., $flags) instead.', E_USER_DEPRECATED); } + $this->defaultMeta = $this->defaultMeta + self::UI_META; $this->flags = $flags; parent::__construct($mask, $metadata); } @@ -149,11 +145,10 @@ public function getConstantParameters(): array } - /** - * Returns flags. - */ + /** @deprecated */ public function getFlags(): int { + trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED); return $this->flags; } @@ -210,3 +205,6 @@ public static function path2presenter(string $s): string return $s; } } + + +interface_exists(Nette\Application\IRouter::class); diff --git a/src/Application/Routers/RouteList.php b/src/Application/Routers/RouteList.php index 5fdcfe93c..a2b769961 100644 --- a/src/Application/Routers/RouteList.php +++ b/src/Application/Routers/RouteList.php @@ -15,7 +15,7 @@ /** * The router broker. */ -class RouteList extends Nette\Routing\RouteList implements Nette\Application\IRouter, \ArrayAccess, \Countable, \IteratorAggregate +class RouteList extends Nette\Routing\RouteList implements Nette\Routing\Router, \ArrayAccess, \Countable, \IteratorAggregate { private const PRESENTER_KEY = 'presenter'; @@ -93,8 +93,10 @@ public function getModule(): ?string } + /** @deprecated */ public function count(): int { + trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED); return count($this->getRouters()); } @@ -132,7 +134,7 @@ public function offsetGet($index) */ public function offsetExists($index): bool { - return is_int($index) && $index >= 0 && $index < $this->count(); + return is_int($index) && $index >= 0 && $index < count($this->getRouters()); } @@ -149,8 +151,13 @@ public function offsetUnset($index): void } + /** @deprecated */ public function getIterator(): \ArrayIterator { + trigger_error(__METHOD__ . '() is deprecated, use getRouters().', E_USER_DEPRECATED); return new \ArrayIterator($this->getRouters()); } } + + +interface_exists(Nette\Application\IRouter::class); diff --git a/src/Application/Routers/SimpleRouter.php b/src/Application/Routers/SimpleRouter.php index 340858f04..9c63c68eb 100644 --- a/src/Application/Routers/SimpleRouter.php +++ b/src/Application/Routers/SimpleRouter.php @@ -16,15 +16,12 @@ /** * The bidirectional route for trivial routing via query parameters. */ -final class SimpleRouter extends Nette\Routing\SimpleRouter implements Nette\Application\IRouter +final class SimpleRouter extends Nette\Routing\SimpleRouter implements Nette\Routing\Router { private const PRESENTER_KEY = 'presenter', MODULE_KEY = 'module'; - /** @var string */ - private $module = ''; - /** @var int */ private $flags; @@ -43,9 +40,9 @@ public function __construct($defaults = [], int $flags = 0) } if (isset($defaults[self::MODULE_KEY])) { - trigger_error(__METHOD__ . '() parameter module is deprecated, use RouteList::withModule() instead.', E_USER_DEPRECATED); - $this->module = $defaults[self::MODULE_KEY] . ':'; - unset($defaults[self::MODULE_KEY]); + throw new Nette\DeprecatedException(__METHOD__ . '() parameter module is deprecated, use RouteList::withModule() instead.'); + } elseif ($flags) { + trigger_error(__METHOD__ . '() parameter $flags is deprecated, use RouteList::add(..., $flags) instead.', E_USER_DEPRECATED); } $this->flags = $flags; @@ -53,21 +50,6 @@ public function __construct($defaults = [], int $flags = 0) } - /** - * Maps HTTP request to an array. - */ - public function match(Nette\Http\IRequest $httpRequest): ?array - { - $params = parent::match($httpRequest); - $presenter = $params[self::PRESENTER_KEY] ?? null; - if (is_string($presenter)) { - $params[self::PRESENTER_KEY] = $this->module . $presenter; - } - - return $params; - } - - /** * Constructs absolute URL from array. */ @@ -76,20 +58,17 @@ public function constructUrl(array $params, Nette\Http\UrlScript $refUrl): ?stri if ($this->flags & self::ONE_WAY) { return null; } - - if (strncmp($params[self::PRESENTER_KEY], $this->module, strlen($this->module)) !== 0) { - return null; - } - $params[self::PRESENTER_KEY] = substr($params[self::PRESENTER_KEY], strlen($this->module)); return parent::constructUrl($params, $refUrl); } - /** - * Returns flags. - */ + /** @deprecated */ public function getFlags(): int { + trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED); return $this->flags; } } + + +interface_exists(Nette\Application\IRouter::class); diff --git a/src/Application/UI/Component.php b/src/Application/UI/Component.php index 4723692ad..af4f3b1c4 100644 --- a/src/Application/UI/Component.php +++ b/src/Application/UI/Component.php @@ -199,14 +199,6 @@ final public function getParameterId(string $name): string } - /** @deprecated */ - final public function getParam($name = null, $default = null) - { - trigger_error(__METHOD__ . '() is deprecated; use getParameter() or getParameters() instead.', E_USER_DEPRECATED); - return func_num_args() ? $this->getParameter($name, $default) : $this->getParameters(); - } - - /********************* interface ISignalReceiver ****************d*g**/ @@ -287,21 +279,11 @@ public function isLinkCurrent(string $destination = null, $args = []): bool * @param array|mixed $args * @throws Nette\Application\AbortException */ - public function redirect(/*int $code, string */$destination, $args = []): void + public function redirect($destination, $args = []): void { - if (is_numeric($destination)) { - trigger_error(__METHOD__ . '() first parameter $code is deprecated; use redirectPermanent() for 301 redirect.', E_USER_DEPRECATED); - [$code, $destination, $args] = func_get_args() + [null, null, []]; - if (func_num_args() > 3 || !is_array($args)) { - $args = array_slice(func_get_args(), 2); - } - } else { - $code = null; - $args = func_num_args() < 3 && is_array($args) ? $args : array_slice(func_get_args(), 1); - } - + $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'), $code); + $presenter->redirectUrl($presenter->createRequest($this, $destination, $args, 'redirect')); } diff --git a/src/Application/UI/Control.php b/src/Application/UI/Control.php index da20357ee..2c2d1c2ef 100644 --- a/src/Application/UI/Control.php +++ b/src/Application/UI/Control.php @@ -15,7 +15,7 @@ /** * Control is renderable Presenter component. * - * @property-read ITemplate|Nette\Bridges\ApplicationLatte\Template|\stdClass $template + * @property-read ITemplate|Nette\Bridges\ApplicationLatte\DefaultTemplate|\stdClass $template */ abstract class Control extends Component implements IRenderable { @@ -51,10 +51,31 @@ final public function getTemplate(): ITemplate } - protected function createTemplate(): ITemplate + /** + * @param string $class + */ + protected function createTemplate(/*string $class = null*/): ITemplate { + $class = func_num_args() ? func_get_arg(0) : $this->formatTemplateClass(); // back compatibility $templateFactory = $this->templateFactory ?: $this->getPresenter()->getTemplateFactory(); - return $templateFactory->createTemplate($this); + return $templateFactory->createTemplate($this, $class); + } + + + public function formatTemplateClass(): ?string + { + $class = preg_replace('#Presenter$|Control$#', 'Template', static::class); + if ($class === static::class || !class_exists($class)) { + return null; + } elseif (!is_a($class, ITemplate::class, true)) { + trigger_error(sprintf( + '%s: class %s was found but does not implement the %s, so it will not be used for the template.', + static::class, $class, ITemplate::class + ), E_USER_NOTICE); + return null; + } else { + return $class; + } } diff --git a/src/Application/UI/Link.php b/src/Application/UI/Link.php index 751dc999f..7236be96f 100644 --- a/src/Application/UI/Link.php +++ b/src/Application/UI/Link.php @@ -41,6 +41,15 @@ public function __construct(Component $component, string $destination, array $pa } + /** + * Returns link component. + */ + public function getComponent(): Component + { + return $this->component; + } + + /** * Returns link destination. */ @@ -80,6 +89,15 @@ public function getParameters(): array } + /** + * Determines whether this links to the current page. + */ + public function isLinkCurrent(): bool + { + return $this->component->isLinkCurrent($this->destination, $this->params); + } + + /** * Converts link to URL. */ diff --git a/src/Application/UI/Presenter.php b/src/Application/UI/Presenter.php index a5702b810..73aff23ad 100644 --- a/src/Application/UI/Presenter.php +++ b/src/Application/UI/Presenter.php @@ -242,9 +242,7 @@ public function run(Application\Request $request): Application\IResponse } // finish template rendering - if ($this->getTemplate()) { - $this->sendTemplate(); - } + $this->sendTemplate(); } catch (Application\AbortException $e) { } @@ -263,7 +261,7 @@ public function run(Application\Request $request): Application\IResponse } if ($this->hasFlashSession()) { - $this->getFlashSession()->setExpiration($this->response instanceof Responses\RedirectResponse ? '+ 30 seconds' : '+ 3 seconds'); + $this->getFlashSession()->setExpiration('30 seconds'); } if (!$this->response) { @@ -312,22 +310,6 @@ protected function shutdown(Application\IResponse $response) } - /** - * Checks authorization. - */ - public function checkRequirements($element): void - { - parent::checkRequirements($element); - $user = (array) ComponentReflection::parseAnnotation($element, 'User'); - if (in_array('loggedIn', $user, true)) { - trigger_error(__METHOD__ . '() annotation @User is deprecated', E_USER_DEPRECATED); - if (!$this->getUser()->isLoggedIn()) { - throw new Application\ForbiddenRequestException; - } - } - } - - /** * This method will be called when CSRF is detected. */ @@ -461,12 +443,11 @@ public function setLayout($layout) /** - * @throws Nette\Application\BadRequestException if no template found * @throws Nette\Application\AbortException */ - public function sendTemplate(): void + public function sendTemplate(ITemplate $template = null): void { - $template = $this->getTemplate(); + $template = $template ?? $this->getTemplate(); if (!$template->getFile()) { $files = $this->formatTemplateFiles(); foreach ($files as $file) { @@ -567,9 +548,13 @@ public static function formatRenderMethod(string $view): string } - protected function createTemplate(): ITemplate + /** + * @param string $class + */ + protected function createTemplate(/*string $class = null*/): ITemplate { - return $this->getTemplateFactory()->createTemplate($this); + $class = func_num_args() ? func_get_arg(0) : $this->formatTemplateClass(); // back compatibility + return $this->getTemplateFactory()->createTemplate($this, $class); } @@ -1317,11 +1302,12 @@ final public function injectPrimary(?Nette\DI\Container $context, ?Application\I * Gets the context. * @deprecated */ - final public function getContext(): Nette\DI\Container + public function getContext(): Nette\DI\Container { if (!$this->context) { throw new Nette\InvalidStateException('Context has not been set.'); } + trigger_error(__METHOD__ . '() is deprecated, use dependency injection.', E_USER_DEPRECATED); return $this->context; } diff --git a/src/Bridges/ApplicationDI/ApplicationExtension.php b/src/Bridges/ApplicationDI/ApplicationExtension.php index af975a456..2d09d1aae 100644 --- a/src/Bridges/ApplicationDI/ApplicationExtension.php +++ b/src/Bridges/ApplicationDI/ApplicationExtension.php @@ -52,7 +52,7 @@ public function getConfigSchema(): Nette\Schema\Schema return Expect::structure([ 'debugger' => Expect::bool(interface_exists(Tracy\IBarPanel::class)), 'errorPresenter' => Expect::string('Nette:Error')->dynamic(), - 'catchExceptions' => Expect::bool(!$this->debugMode)->dynamic(), + 'catchExceptions' => Expect::bool()->dynamic(), 'mapping' => Expect::arrayOf('string|array'), 'scanDirs' => Expect::anyOf(Expect::arrayOf('string'), false)->default($this->scanDirs), 'scanComposer' => Expect::bool(class_exists(ClassLoader::class)), @@ -74,7 +74,7 @@ public function loadConfiguration() $application = $builder->addDefinition($this->prefix('application')) ->setFactory(Nette\Application\Application::class) - ->addSetup('$catchExceptions', [$config->catchExceptions]) + ->addSetup('$catchExceptions', [$this->debugMode ? $config->catchExceptions : true]) ->addSetup('$errorPresenter', [$config->errorPresenter]); if ($config->debugger) { diff --git a/src/Bridges/ApplicationDI/RoutingExtension.php b/src/Bridges/ApplicationDI/RoutingExtension.php index deae3c536..a2f25623f 100644 --- a/src/Bridges/ApplicationDI/RoutingExtension.php +++ b/src/Bridges/ApplicationDI/RoutingExtension.php @@ -33,7 +33,7 @@ public function __construct(bool $debugMode = false) /** @var string[] */ public $routes = []; /** @var ?string */ - public $routeClass = Nette\Application\Routers\Route::class; + public $routeClass; /** @var bool */ public $cache = false; }; @@ -43,14 +43,24 @@ public function __construct(bool $debugMode = false) public function loadConfiguration() { + if (!$this->config->routes) { + return; + } + $builder = $this->getContainerBuilder(); $router = $builder->addDefinition($this->prefix('router')) - ->setType(Nette\Application\IRouter::class) ->setFactory(Nette\Application\Routers\RouteList::class); - foreach ($this->config->routes as $mask => $action) { - $router->addSetup('$service[] = new ' . $this->config->routeClass . '(?, ?)', [$mask, $action]); + if ($this->config->routeClass) { + trigger_error('Option routing.routeClass is deprecated.', E_USER_DEPRECATED); + foreach ($this->config->routes as $mask => $action) { + $router->addSetup('$service[] = new ' . $this->config->routeClass . '(?, ?)', [$mask, $action]); + } + } else { + foreach ($this->config->routes as $mask => $action) { + $router->addSetup('$service->addRoute(?, ?)', [$mask, $action]); + } } if ($this->name === 'routing') { @@ -73,13 +83,22 @@ public function beforeCompile() new Definitions\Statement(Nette\Bridges\ApplicationTracy\RoutingPanel::class), ]); } + + if (!$builder->getByType(Nette\Routing\Router::class)) { + $builder->addDefinition($this->prefix('router')) + ->setType(Nette\Routing\Router::class) + ->setFactory(Nette\Routing\SimpleRouter::class); + $builder->addAlias('router', $this->prefix('router')); + } } public function afterCompile(Nette\PhpGenerator\ClassType $class) { if ($this->config->cache) { - $method = $class->getMethod(Nette\DI\Container::getMethodName($this->prefix('router'))); + $builder = $this->getContainerBuilder(); + $def = $builder->getDefinitionByType(Nette\Routing\Router::class); + $method = $class->getMethod(Nette\DI\Container::getMethodName($def->getName())); try { $router = eval($method->getBody()); if ($router instanceof Nette\Application\Routers\RouteList) { diff --git a/src/Bridges/ApplicationLatte/DefaultTemplate.php b/src/Bridges/ApplicationLatte/DefaultTemplate.php new file mode 100644 index 000000000..d20a576fa --- /dev/null +++ b/src/Bridges/ApplicationLatte/DefaultTemplate.php @@ -0,0 +1,64 @@ +$name = $value; + return $this; + } + + + /** + * Sets all parameters. + * @return static + */ + public function setParameters(array $params) + { + return Nette\Utils\Arrays::toObject($params, $this); + } +} diff --git a/src/Bridges/ApplicationLatte/Template.php b/src/Bridges/ApplicationLatte/Template.php index 76d586523..57b8ae62c 100644 --- a/src/Bridges/ApplicationLatte/Template.php +++ b/src/Bridges/ApplicationLatte/Template.php @@ -18,17 +18,12 @@ */ 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) { @@ -47,7 +42,8 @@ final public function getLatte(): Latte\Engine */ public function render(string $file = null, array $params = []): void { - $this->latte->render($file ?: $this->file, $params + $this->params); + Nette\Utils\Arrays::toObject($params, $this); + $this->latte->render($file ?: $this->file, $this); } @@ -56,7 +52,8 @@ public function render(string $file = null, array $params = []): void */ public function renderToString(string $file = null, array $params = []): string { - return $this->latte->renderToString($file ?: $this->file, $params + $this->params); + Nette\Utils\Arrays::toObject($params, $this); + return $this->latte->renderToString($file ?: $this->file, $this); } @@ -67,7 +64,7 @@ public function renderToString(string $file = null, array $params = []): string public function __toString(): string { try { - return $this->latte->renderToString($this->file, $this->params); + return $this->latte->renderToString($this->file, $this->getParameters()); } catch (\Throwable $e) { if (func_num_args() || PHP_VERSION_ID >= 70400) { throw $e; @@ -136,78 +133,18 @@ final public function getFile(): ?string } - /** - * Adds new template parameter. - * @return static - */ - public function add(string $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. - * @return static - */ - public function setParameters(array $params) - { - $this->params = $params + $this->params; - return $this; - } - - /** * Returns array of all parameters. */ final public function getParameters(): array { - return $this->params; - } - - - /** - * Sets a template parameter. Do not call directly. - */ - public function __set($name, $value): void - { - $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); + $res = []; + foreach ((new \ReflectionObject($this))->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) { + if (PHP_VERSION_ID < 70400 || $prop->isInitialized($this)) { + $res[$prop->getName()] = $prop->getValue($this); + } } - - return $this->params[$name]; - } - - - /** - * Determines whether parameter is defined. Do not call directly. - */ - public function __isset($name) - { - return isset($this->params[$name]); - } - - - /** - * Removes a template parameter. Do not call directly. - */ - public function __unset(string $name): void - { - unset($this->params[$name]); + return $res; } diff --git a/src/Bridges/ApplicationLatte/TemplateFactory.php b/src/Bridges/ApplicationLatte/TemplateFactory.php index a2ddf66c2..bb0743bff 100644 --- a/src/Bridges/ApplicationLatte/TemplateFactory.php +++ b/src/Bridges/ApplicationLatte/TemplateFactory.php @@ -21,7 +21,7 @@ class TemplateFactory implements UI\ITemplateFactory { use Nette\SmartObject; - /** @var callable[]&(callable(Template $template): void)[]; Occurs when a new template is created */ + /** @var callable[]&(callable(UI\ITemplate $template): void)[]; Occurs when a new template is created */ public $onCreate; /** @var ILatteFactory */ @@ -47,17 +47,22 @@ public function __construct(ILatteFactory $latteFactory, Nette\Http\IRequest $ht $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.'); + if ($templateClass && (!class_exists($templateClass) || !is_a($templateClass, UI\ITemplate::class, true))) { + throw new Nette\InvalidArgumentException("Class $templateClass does not implement " . UI\ITemplate::class . ' or it does not exist.'); } - $this->templateClass = $templateClass ?: Template::class; + $this->templateClass = $templateClass ?: DefaultTemplate::class; } - public function createTemplate(UI\Control $control = null): UI\ITemplate + public function createTemplate(UI\Control $control = null, string $class = null): UI\ITemplate { + $class = $class ?? $this->templateClass; + if (!is_a($class, UI\ITemplate::class, true)) { + throw new Nette\InvalidArgumentException("Class $class does not implement " . UI\ITemplate::class . ' or it does not exist.'); + } + $latte = $this->latteFactory->create(); - $template = new $this->templateClass($latte); + $template = new $class($latte); $presenter = $control ? $control->getPresenterIfExists() : null; if ($latte->onCompile instanceof \Traversable) { @@ -77,19 +82,6 @@ public function createTemplate(UI\Control $control = null): UI\ITemplate } }); - $latte->addFilter('url', function (string $s): string { - trigger_error('Filter |url is deprecated, use |escapeUrl.', E_USER_DEPRECATED); - return rawurlencode($s); - }); - foreach (['normalize', 'toAscii'] as $name) { - $latte->addFilter($name, function (string $s) use ($name): string { - trigger_error("Filter |$name is deprecated.", E_USER_DEPRECATED); - return [Nette\Utils\Strings::class, $name]($s); - }); - } - $latte->addFilter('null', function (): void { - trigger_error('Filter |null is deprecated.', E_USER_DEPRECATED); - }); $latte->addFilter('modifyDate', function ($time, $delta, $unit = null) { return $time == null ? null : Nette\Utils\DateTime::from($time)->modify($delta . $unit); // intentionally == }); @@ -106,13 +98,27 @@ public function createTemplate(UI\Control $control = null): UI\ITemplate } // default parameters - $template->user = $this->user; - $template->baseUrl = $this->httpRequest ? rtrim($this->httpRequest->getUrl()->withoutUserInfo()->getBaseUrl(), '/') : null; - $template->basePath = preg_replace('#https?://[^/]+#A', '', $template->baseUrl); - $template->flashes = []; + $baseUrl = $this->httpRequest ? rtrim($this->httpRequest->getUrl()->withoutUserInfo()->getBaseUrl(), '/') : null; + $flashes = $presenter instanceof UI\Presenter && $presenter->hasFlashSession() + ? (array) $presenter->getFlashSession()->{$control->getParameterId('flash')} + : []; + + $params = [ + 'user' => $this->user, + 'baseUrl' => $baseUrl, + 'basePath' => $baseUrl ? preg_replace('#https?://[^/]+#A', '', $baseUrl) : null, + 'flashes' => $flashes, + 'control' => $control, + 'presenter' => $presenter, + ]; + + foreach ($params as $key => $value) { + if ($value !== null && property_exists($template, $key)) { + $template->$key = $value; + } + } + 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)); @@ -125,11 +131,6 @@ public function createTemplate(UI\Control $control = null): UI\ITemplate } $latte->addProvider('cacheStorage', $this->cacheStorage); - if ($presenter instanceof UI\Presenter && $presenter->hasFlashSession()) { - $id = $control->getParameterId('flash'); - $template->flashes = (array) $presenter->getFlashSession()->$id; - } - $this->onCreate($template); return $template; diff --git a/src/Bridges/ApplicationLatte/UIMacros.php b/src/Bridges/ApplicationLatte/UIMacros.php index c0222de3d..cc3a04047 100644 --- a/src/Bridges/ApplicationLatte/UIMacros.php +++ b/src/Bridges/ApplicationLatte/UIMacros.php @@ -29,6 +29,9 @@ final class UIMacros extends Latte\Macros\MacroSet /** @var bool|string */ private $extends; + /** @var string|null */ + private $printTemplate; + public static function install(Latte\Compiler $compiler): void { @@ -44,6 +47,7 @@ public static function install(Latte\Compiler $compiler): void $me->addMacro('extends', [$me, 'macroExtends']); $me->addMacro('layout', [$me, 'macroExtends']); $me->addMacro('nonce', null, null, 'echo $this->global->uiNonce ? " nonce=\"{$this->global->uiNonce}\"" : "";'); + $me->addMacro('templatePrint', [$me, 'macroTemplatePrint'], null, null, self::ALLOWED_IN_HEAD); } @@ -62,6 +66,9 @@ public function initialize(): void */ public function finalize() { + if ($this->printTemplate) { + return ["Nette\\Bridges\\ApplicationLatte\\UIRuntime::printClass(\$this, $this->printTemplate); exit;"]; + } return [$this->extends . 'Nette\Bridges\ApplicationLatte\UIRuntime::initialize($this, $this->parentName, $this->blocks);']; } @@ -74,6 +81,9 @@ public function finalize() */ public function macroControl(MacroNode $node, PhpWriter $writer) { + if ($node->modifiers) { + trigger_error('Modifiers are deprecated in ' . $node->getNotation(), E_USER_DEPRECATED); + } $words = $node->tokenizer->fetchWords(); if (!$words) { throw new CompileException('Missing control name in {control}'); @@ -113,6 +123,9 @@ public function macroControl(MacroNode $node, PhpWriter $writer) */ public function macroLink(MacroNode $node, PhpWriter $writer) { + if ($node->modifiers) { + trigger_error('Modifiers are deprecated in ' . $node->getNotation(), E_USER_DEPRECATED); + } $node->modifiers = preg_replace('#\|safeurl\s*(?=\||$)#Di', '', $node->modifiers); return $writer->using($node, $this->getCompiler()) ->write('echo %escape(%modify(' @@ -147,4 +160,16 @@ public function macroExtends(MacroNode $node, PhpWriter $writer) } $this->extends = $writer->write('$this->parentName = $this->global->uiPresenter->findLayoutTemplateFile();'); } + + + /** + * {templatePrint [parentClass | default]} + */ + public function macroTemplatePrint(MacroNode $node) + { + if ($node->modifiers) { + throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); + } + $this->printTemplate = var_export($node->tokenizer->fetchWord() ?: null, true); + } } diff --git a/src/Bridges/ApplicationLatte/UIRuntime.php b/src/Bridges/ApplicationLatte/UIRuntime.php index 0c4aadab6..8a16745c7 100644 --- a/src/Bridges/ApplicationLatte/UIRuntime.php +++ b/src/Bridges/ApplicationLatte/UIRuntime.php @@ -11,6 +11,8 @@ use Latte; use Nette; +use Nette\Application\UI\Presenter; +use Nette\PhpGenerator as Php; /** @@ -34,4 +36,47 @@ public static function initialize(Latte\Runtime\Template $template, &$parentName $parentName = $providers->uiControl->findLayoutTemplateFile(); } } + + + public static function printClass(Latte\Runtime\Template $template, string $parent = null): void + { + $name = 'Template'; + $parent = $parent === 'default' + ? DefaultTemplate::class + : ($parent ?: Template::class); + + $params = $template->getParameters(); + $control = $params['control'] ?? $params['presenter'] ?? null; + if ($control) { + $name = preg_replace('#(Control|Presenter)$#', '', get_class($control)) . 'Template'; + unset($params[$control instanceof Presenter ? 'control' : 'presenter']); + } + + if (class_exists($parent)) { + get_class_vars($parent); + $params = array_diff_key($params, get_class_vars($parent)); + } + + $funcs = (array) $template->global->fn; + unset($funcs['isLinkCurrent'], $funcs['isModuleCurrent']); + + $namespace = new Php\PhpNamespace(Php\Helpers::extractNamespace($name)); + $class = $namespace->addClass(Php\Helpers::extractShortName($name)); + $class->setExtends($parent); + $class->addTrait(Nette\SmartObject::class); + + $blueprint = new Latte\Runtime\Blueprint; + $blueprint->addProperties($class, $params, true); + $blueprint->addFunctions($class, $funcs); + + $end = $blueprint->printCanvas(); + $blueprint->printHeader('Native types'); + $blueprint->printCode((string) $namespace); + + $blueprint->addProperties($class, $params, false); + + $blueprint->printHeader('phpDoc types'); + $blueprint->printCode((string) $namespace); + echo $end; + } } diff --git a/src/Application/IRouter.php b/src/compatibility-intf.php similarity index 50% rename from src/Application/IRouter.php rename to src/compatibility-intf.php index 5c427a138..8682fbd8a 100644 --- a/src/Application/IRouter.php +++ b/src/compatibility-intf.php @@ -9,12 +9,11 @@ namespace Nette\Application; -use Nette; - - -/** - * @deprecated use Nette\Routing\Router - */ -interface IRouter extends Nette\Routing\Router -{ +if (false) { + /** @deprecated use Nette\Routing\Router */ + interface IRouter + { + } +} elseif (!interface_exists(IRouter::class)) { + class_alias(\Nette\Routing\Router::class, IRouter::class); } diff --git a/tests/Application/Presenter.twoDomains.phpt b/tests/Application/Presenter.twoDomains.phpt index 06fcda2a9..ba8b8ce68 100644 --- a/tests/Application/Presenter.twoDomains.phpt +++ b/tests/Application/Presenter.twoDomains.phpt @@ -16,7 +16,7 @@ require __DIR__ . '/../bootstrap.php'; class TestPresenter extends Application\UI\Presenter { - protected function createTemplate($class = null): Application\UI\ITemplate + protected function createTemplate(string $class = null): Application\UI\ITemplate { } } diff --git a/tests/Bridges.DI/RoutingExtension.basic.phpt b/tests/Bridges.DI/RoutingExtension.basic.phpt index 5b6dd36ea..01172f7d4 100644 --- a/tests/Bridges.DI/RoutingExtension.basic.phpt +++ b/tests/Bridges.DI/RoutingExtension.basic.phpt @@ -36,7 +36,7 @@ test(function () { $container = new Container1; $router = $container->getService('router'); Assert::type(Nette\Application\Routers\RouteList::class, $router); - Assert::count(2, $router); + @Assert::count(2, $router); // @ is deprecated Assert::same('index.php', $router[0]->getMask()); Assert::same('item/', $router[1]->getMask()); @@ -57,7 +57,7 @@ test(function () { $compiler = new DI\Compiler; $compiler->addExtension('routing', new RoutingExtension(false)); - $code = $compiler->addConfig($config)->setClassName('Container2')->compile(); + $code = @$compiler->addConfig($config)->setClassName('Container2')->compile(); // @ routingClass is deprecated eval($code); $container = new Container2; diff --git a/tests/Bridges.Latte/TemplateFactory.customTemplate.phpt b/tests/Bridges.Latte/TemplateFactory.customTemplate.phpt index f23c1145f..5ac57c824 100644 --- a/tests/Bridges.Latte/TemplateFactory.customTemplate.phpt +++ b/tests/Bridges.Latte/TemplateFactory.customTemplate.phpt @@ -51,7 +51,7 @@ test(function () { Assert::exception(function () { $factory = new TemplateFactory(Mockery::mock(ILatteFactory::class), null, null, null, stdClass::class); -}, \Nette\InvalidArgumentException::class, 'Class stdClass does not extend Nette\Bridges\ApplicationLatte\Template or it does not exist.'); +}, \Nette\InvalidArgumentException::class, 'Class stdClass does not implement Nette\Application\UI\ITemplate or it does not exist.'); test(function () { @@ -61,7 +61,6 @@ test(function () { $template = $factory->createTemplate(); Assert::type(TemplateMock::class, $template); Assert::type(UI\ITemplate::class, $template); - Assert::same([], $template->flashes); ob_start(); $template->render(); Assert::same('ok', ob_get_clean()); diff --git a/tests/Bridges.Latte/TemplateFactory.filters.phpt b/tests/Bridges.Latte/TemplateFactory.filters.phpt index 7ace90c1a..aa377e2f0 100644 --- a/tests/Bridges.Latte/TemplateFactory.filters.phpt +++ b/tests/Bridges.Latte/TemplateFactory.filters.phpt @@ -42,8 +42,3 @@ Assert::same('1978-01-24 11:40:00', (string) $latte->invokeFilter('modifyDate', Assert::same('1978-05-06 00:00:00', (string) $latte->invokeFilter('modifyDate', ['1978-05-05', '+1 day'])); Assert::same('1978-05-06 00:00:00', (string) $latte->invokeFilter('modifyDate', [new DateTime('1978-05-05'), '1day'])); Assert::same('1978-01-22 11:40:00', (string) $latte->invokeFilter('modifyDate', [254400000, -1, 'day'])); - - -Assert::same('%25', @$latte->invokeFilter('url', ['%'])); // @ is deprecated -Assert::null(@$latte->invokeFilter('null', ['x'])); // @ is deprecated -Assert::same('', @$latte->invokeFilter('normalize', [' '])); // @ is deprecated diff --git a/tests/Bridges.Latte/UIMacros.control.phpt b/tests/Bridges.Latte/UIMacros.control.phpt index 388fa8d15..33cfdc4b3 100644 --- a/tests/Bridges.Latte/UIMacros.control.phpt +++ b/tests/Bridges.Latte/UIMacros.control.phpt @@ -18,7 +18,7 @@ UIMacros::install($compiler); // {control ...} Assert::match('global->uiControl->getComponent("form"); %a%->render(); ?>', $compiler->expandMacro('control', 'form', '')->openingCode); -Assert::match('global->uiControl->getComponent("form"); %a%->render(); echo ($this->filters->filter)(%a%); ?>', $compiler->expandMacro('control', 'form', 'filter')->openingCode); +@Assert::match('global->uiControl->getComponent("form"); %a%->render(); echo ($this->filters->filter)(%a%); ?>', $compiler->expandMacro('control', 'form', 'filter')->openingCode); // @ deprecated Assert::match('global->uiControl->getComponent($form); %a%->render(); ?>', $compiler->expandMacro('control', '$form', '')->openingCode); Assert::match('global->uiControl->getComponent("form"); %a%->renderType(); ?>', $compiler->expandMacro('control', 'form:type', '')->openingCode); Assert::match('global->uiControl->getComponent("form"); %a%->{"render$type"}(); ?>', $compiler->expandMacro('control', 'form:$type', '')->openingCode); @@ -26,4 +26,4 @@ Assert::match('global->uiControl->getComponent("form"); %a%->re Assert::match('global->uiControl->getComponent("form"); %a%->render(array_merge([], $params, [])); ?>', $compiler->expandMacro('control', 'form (expand) $params', '')->openingCode); Assert::match('global->uiControl->getComponent("form"); %a%->renderType([\'param\' => 123]); ?>', $compiler->expandMacro('control', 'form:type param => 123', '')->openingCode); Assert::match('global->uiControl->getComponent("form"); %a%->renderType([\'param\' => 123]); ?>', $compiler->expandMacro('control', 'form:type, param => 123', '')->openingCode); -Assert::match('global->uiControl->getComponent("form"); %a%->render(); echo ($this->filters->striptags)(%a%); ?>', $compiler->expandMacro('control', 'form', 'striptags')->openingCode); +@Assert::match('global->uiControl->getComponent("form"); %a%->render(); echo ($this->filters->striptags)(%a%); ?>', $compiler->expandMacro('control', 'form', 'striptags')->openingCode); // @ deprecated diff --git a/tests/Bridges.Latte/UIMacros.link.phpt b/tests/Bridges.Latte/UIMacros.link.phpt index 3ae06bfec..c6eae6ae3 100644 --- a/tests/Bridges.Latte/UIMacros.link.phpt +++ b/tests/Bridges.Latte/UIMacros.link.phpt @@ -19,7 +19,7 @@ UIMacros::install($compiler); // {link ...} Assert::same('global->uiControl->link("p") ?>', $compiler->expandMacro('link', 'p', '')->openingCode); -Assert::same('filters->filter)($this->global->uiControl->link("p")) ?>', $compiler->expandMacro('link', 'p', 'filter')->openingCode); +@Assert::same('filters->filter)($this->global->uiControl->link("p")) ?>', $compiler->expandMacro('link', 'p', 'filter')->openingCode); // @ deprecated Assert::same('global->uiControl->link("p:a") ?>', $compiler->expandMacro('link', 'p:a', '')->openingCode); Assert::same('global->uiControl->link($dest) ?>', $compiler->expandMacro('link', '$dest', '')->openingCode); Assert::same('global->uiControl->link($p:$a) ?>', $compiler->expandMacro('link', '$p:$a', '')->openingCode); diff --git a/tests/Routers/LinkGenerator.phpt b/tests/Routers/LinkGenerator.phpt index cb53526ce..678a60255 100644 --- a/tests/Routers/LinkGenerator.phpt +++ b/tests/Routers/LinkGenerator.phpt @@ -52,9 +52,13 @@ namespace { test(function () use ($pf) { $generator = new LinkGenerator(new Routers\SimpleRouter, new Http\UrlScript('http://nette.org/en/'), $pf); Assert::same('http://nette.org/en/?action=default&presenter=Homepage', $generator->link('Homepage:default')); + Assert::same('http://nette.org/en/?action=default&presenter=Homepage', $generator->link(':Homepage:default')); Assert::same('http://nette.org/en/?action=default&presenter=Module%3AMy', $generator->link('Module:My:default')); + Assert::same('http://nette.org/en/?action=default&presenter=Module%3AMy', $generator->link(':Module:My:default')); Assert::same('http://nette.org/en/?presenter=Module%3AMy', $generator->link('Module:My:')); + Assert::same('http://nette.org/en/?presenter=Module%3AMy', $generator->link(':Module:My:')); Assert::same('http://nette.org/en/?action=default&presenter=Homepage', $generator->link('Homepage:')); + Assert::same('http://nette.org/en/?action=default&presenter=Homepage', $generator->link(':Homepage:')); Assert::same('http://nette.org/en/?a=10&action=default&presenter=Homepage', $generator->link('Homepage:', [10])); Assert::same('http://nette.org/en/?id=20&b=10&action=detail&presenter=Homepage', $generator->link('Homepage:detail', [10, 'id' => 20])); Assert::same('http://nette.org/en/?action=default&presenter=Homepage#frag:ment', $generator->link('Homepage:#frag:ment')); @@ -68,6 +72,18 @@ namespace { }, Nette\Application\UI\InvalidLinkException::class, "Invalid link destination 'default'."); + Assert::exception(function () use ($pf) { + $generator = new LinkGenerator(new Routers\Route('/', 'Homepage:'), new Http\UrlScript('http://nette.org/en/'), $pf); + $generator->link(':'); + }, Nette\Application\UI\InvalidLinkException::class, 'Invalid link destination \':\'.'); + + + Assert::exception(function () use ($pf) { + $generator = new LinkGenerator(new Routers\Route('/', 'Homepage:'), new Http\UrlScript('http://nette.org/en/'), $pf); + $generator->link('::'); + }, Nette\Application\UI\InvalidLinkException::class, 'Missing presenter name in \'::\'.'); + + Assert::exception(function () use ($pf) { $generator = new LinkGenerator(new Routers\Route('/', 'Product:'), new Http\UrlScript('http://nette.org/en/'), $pf); $generator->link('Homepage:default', ['id' => 10]); @@ -83,9 +99,13 @@ namespace { test(function () { $generator = new LinkGenerator(new Routers\SimpleRouter, new Http\UrlScript('http://nette.org/en/')); Assert::same('http://nette.org/en/?action=default&presenter=Homepage', $generator->link('Homepage:default')); + Assert::same('http://nette.org/en/?action=default&presenter=Homepage', $generator->link(':Homepage:default')); Assert::same('http://nette.org/en/?action=default&presenter=Module%3AMy', $generator->link('Module:My:default')); + Assert::same('http://nette.org/en/?action=default&presenter=Module%3AMy', $generator->link(':Module:My:default')); Assert::same('http://nette.org/en/?presenter=Module%3AMy', $generator->link('Module:My:')); + Assert::same('http://nette.org/en/?presenter=Module%3AMy', $generator->link(':Module:My:')); Assert::same('http://nette.org/en/?presenter=Homepage', $generator->link('Homepage:')); + Assert::same('http://nette.org/en/?presenter=Homepage', $generator->link(':Homepage:')); Assert::same('http://nette.org/en/?0=10&presenter=Homepage', $generator->link('Homepage:', [10])); Assert::same('http://nette.org/en/?0=10&id=20&action=detail&presenter=Homepage', $generator->link('Homepage:detail', [10, 'id' => 20])); Assert::same('http://nette.org/en/?presenter=Homepage#frag:ment', $generator->link('Homepage:#frag:ment')); diff --git a/tests/Routers/Route.oneWay.phpt b/tests/Routers/Route.oneWay.phpt index 2203ea080..c9578d790 100644 --- a/tests/Routers/Route.oneWay.phpt +++ b/tests/Routers/Route.oneWay.phpt @@ -14,7 +14,7 @@ require __DIR__ . '/../bootstrap.php'; require __DIR__ . '/Route.php'; -$route = new Route('/', [ +@$route = new Route('/', [ // @ is deprecated 'presenter' => 'Default', 'action' => 'default', ], Route::ONE_WAY); diff --git a/tests/Routers/SimpleRouter.module.phpt b/tests/Routers/SimpleRouter.module.phpt deleted file mode 100644 index 81a02ceea..000000000 --- a/tests/Routers/SimpleRouter.module.phpt +++ /dev/null @@ -1,34 +0,0 @@ - 'main:sub', -]); - -$url = new Http\Url('http://nette.org/file.php', '/file.php'); -$url->setQuery([ - 'presenter' => 'myPresenter', -]); -$httpRequest = new Http\Request(new Http\UrlScript($url)); - -$req = $router->match($httpRequest); -Assert::same('main:sub:myPresenter', $req['presenter']); - -$url = $router->constructUrl($req, $httpRequest->getUrl()); -Assert::same('http://nette.org/file.php?presenter=myPresenter', $url); - -$url = $router->constructUrl(['presenter' => 'othermodule:presenter'], $httpRequest->getUrl()); -Assert::null($url); diff --git a/tests/UI/Component.redirect().phpt b/tests/UI/Component.redirect().phpt index 43721a787..e796c937b 100644 --- a/tests/UI/Component.redirect().phpt +++ b/tests/UI/Component.redirect().phpt @@ -66,22 +66,6 @@ test(function () use ($presenter) { }); -test(function () use ($presenter) { - @$presenter->redirect(301, 'foo', ['arg' => 1]); // @ is deprecated - Assert::type(Nette\Application\Responses\RedirectResponse::class, $presenter->response); - Assert::same(301, $presenter->response->getCode()); - Assert::same('http://localhost/?arg=1&action=foo&presenter=test', $presenter->response->getUrl()); -}); - - -test(function () use ($presenter) { - @$presenter->redirect(301, 'foo', 2); // @ is deprecated - Assert::type(Nette\Application\Responses\RedirectResponse::class, $presenter->response); - Assert::same(301, $presenter->response->getCode()); - Assert::same('http://localhost/?val=2&action=foo&presenter=test', $presenter->response->getUrl()); -}); - - test(function () use ($presenter) { $presenter->redirectPermanent('foo', 2); Assert::type(Nette\Application\Responses\RedirectResponse::class, $presenter->response);