diff --git a/.github/funding.yml b/.github/funding.yml new file mode 100644 index 000000000..25adc9520 --- /dev/null +++ b/.github/funding.yml @@ -0,0 +1,2 @@ +github: dg +custom: "https://nette.org/donate" diff --git a/.travis.yml b/.travis.yml index 1b1266b7f..edf25e2c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,7 +67,7 @@ jobs: - stage: Code Coverage -sudo: false +dist: xenial cache: directories: diff --git a/readme.md b/readme.md index d37599fb6..ba652fff5 100644 --- a/readme.md +++ b/readme.md @@ -9,6 +9,6 @@ Nette Application MVC 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). +Please, [see documentation](https://doc.nette.org/application). If you like Nette, **[please make a donation now](https://nette.org/donate)**. Thank you! diff --git a/src/Application/Application.php b/src/Application/Application.php index fdb64b8b7..4a593a3fc 100644 --- a/src/Application/Application.php +++ b/src/Application/Application.php @@ -112,6 +112,8 @@ public function createInitialRequest(): Request throw new BadRequestException('No route for HTTP request.'); } elseif (!is_string($presenter)) { throw new Nette\InvalidStateException('Missing presenter in route definition.'); + } elseif (Nette\Utils\Strings::startsWith($presenter, 'Nette:') && $presenter !== 'Nette:Micro') { + throw new BadRequestException('Invalid request. Presenter is not achievable.'); } unset($params[UI\Presenter::PRESENTER_KEY]); diff --git a/src/Application/LinkGenerator.php b/src/Application/LinkGenerator.php index 803300779..7738e1404 100644 --- a/src/Application/LinkGenerator.php +++ b/src/Application/LinkGenerator.php @@ -10,6 +10,7 @@ namespace Nette\Application; use Nette; +use Nette\Http\UrlScript; use Nette\Routing\Router; @@ -23,14 +24,14 @@ final class LinkGenerator /** @var Router */ private $router; - /** @var Nette\Http\UrlScript */ + /** @var UrlScript */ private $refUrl; /** @var IPresenterFactory|null */ private $presenterFactory; - public function __construct(Router $router, Nette\Http\UrlScript $refUrl, IPresenterFactory $presenterFactory = null) + public function __construct(Router $router, UrlScript $refUrl, IPresenterFactory $presenterFactory = null) { $this->router = $router; $this->refUrl = $refUrl; @@ -88,4 +89,14 @@ public function link(string $dest, array $params = []): string } return $url . $frag; } + + + public function withReferenceUrl(string $url): self + { + return new self( + $this->router, + new UrlScript($url), + $this->presenterFactory + ); + } } diff --git a/src/Application/MicroPresenter.php b/src/Application/MicroPresenter.php index 62f17a20e..0beb1a46f 100644 --- a/src/Application/MicroPresenter.php +++ b/src/Application/MicroPresenter.php @@ -67,11 +67,11 @@ public function run(Application\Request $request): Application\IResponse } $params = $request->getParameters(); - if (!isset($params['callback'])) { - throw new Nette\InvalidStateException('Parameter callback is missing.'); + $callback = $params['callback'] ?? null; + if (!$callback instanceof \Closure) { + throw new Nette\InvalidStateException('Parameter callback is not a valid closure.'); } - $callback = $params['callback']; - $reflection = Nette\Utils\Callback::toReflection(Nette\Utils\Callback::check($callback)); + $reflection = new \ReflectionFunction($callback); if ($this->context) { foreach ($reflection->getParameters() as $param) { diff --git a/src/Application/UI/Component.php b/src/Application/UI/Component.php index f0108eba4..4723692ad 100644 --- a/src/Application/UI/Component.php +++ b/src/Application/UI/Component.php @@ -35,6 +35,7 @@ abstract class Component extends Nette\ComponentModel\Container implements ISign /** * Returns the presenter where this component belongs to. + * @return Presenter */ public function getPresenter(): ?Presenter { diff --git a/src/Application/UI/ComponentReflection.php b/src/Application/UI/ComponentReflection.php index 4c472fd5e..e0a2d19e0 100644 --- a/src/Application/UI/ComponentReflection.php +++ b/src/Application/UI/ComponentReflection.php @@ -198,6 +198,9 @@ public static function convertType(&$val, string $type): bool if (empty($builtin[$type])) { return $val instanceof $type; + } elseif ($type === 'object') { + return is_object($val); + } elseif ($type === 'callable') { return false; diff --git a/src/Bridges/ApplicationTracy/RoutingPanel.php b/src/Bridges/ApplicationTracy/RoutingPanel.php index 444b4970c..6518b00c0 100644 --- a/src/Bridges/ApplicationTracy/RoutingPanel.php +++ b/src/Bridges/ApplicationTracy/RoutingPanel.php @@ -99,10 +99,14 @@ public function getPanel(): string private function analyse(Routing\Router $router, string $module = '', bool $parentMatches = true, int $level = -1): void { if ($router instanceof Routing\RouteList) { - $parentMatches = $parentMatches && $router->match($this->httpRequest) !== null; + try { + $parentMatches = $parentMatches && $router->match($this->httpRequest) !== null; + } catch (\Exception $e) { + } $next = count($this->routers); + $parentModule = $module . ($router instanceof Nette\Application\Routers\RouteList ? $router->getModule() : ''); foreach ($router->getRouters() as $subRouter) { - $this->analyse($subRouter, $module . $router->getModule(), $parentMatches, $level + 1); + $this->analyse($subRouter, $parentModule, $parentMatches, $level + 1); } if ($info = $this->routers[$next] ?? null) { @@ -119,6 +123,7 @@ private function analyse(Routing\Router $router, string $module = '', bool $pare try { $params = $parentMatches ? $router->match($this->httpRequest) : null; } catch (\Exception $e) { + $matched = 'error'; } if ($params !== null) { if ($module) { diff --git a/src/Bridges/ApplicationTracy/templates/RoutingPanel.panel.phtml b/src/Bridges/ApplicationTracy/templates/RoutingPanel.panel.phtml index ae5e37d49..2a256fa08 100644 --- a/src/Bridges/ApplicationTracy/templates/RoutingPanel.panel.phtml +++ b/src/Bridges/ApplicationTracy/templates/RoutingPanel.panel.phtml @@ -26,6 +26,14 @@ use Tracy\Helpers; background: #C1D3FF !important; } + #tracy-debug .nette-RoutingPanel .error td { + background: #ffd2c3 !important; + } + + #tracy-debug .nette-RoutingPanel td.symbol { + text-align: center; + } + #tracy-debug .nette-RoutingPanel td:first-child { width: 20px; } @@ -73,7 +81,7 @@ use Tracy\Helpers; - matched === 'yes' ? '✓' : ($router->matched === 'may' ? '≈' : '') ?> + '✓', 'may' => '≈', 'no' => '', 'error' => '❌'][$router->matched] ?> mask) ? str_replace(['/', '-'], ['/', '-'], Helpers::escapeHtml($router->mask)) : str_replace('\\', '\\', Helpers::escapeHtml($router->class)) ?> diff --git a/tests/Application/MicroPresenter.invoke.phpt b/tests/Application/MicroPresenter.invoke.phpt index ca764c9f9..ea13ff0e1 100644 --- a/tests/Application/MicroPresenter.invoke.phpt +++ b/tests/Application/MicroPresenter.invoke.phpt @@ -13,15 +13,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -class Invokable -{ - public function __invoke($page, $id, NetteModule\MicroPresenter $presenter) - { - $this->log[] = 'Callback id ' . $id . ' page ' . $page; - } -} - - test(function () { $presenter = $p = new NetteModule\MicroPresenter; @@ -39,21 +30,6 @@ test(function () { }); -test(function () { - $presenter = new NetteModule\MicroPresenter; - - $presenter->run(new Request('Nette:Micro', 'GET', [ - 'callback' => $invokable = new Invokable, - 'id' => 1, - 'page' => 2, - ])); - Assert::same([ - 'Callback id 1 page 2', - ], $invokable->log); -}); - - - test(function () { $container = Mockery::mock(Nette\DI\Container::class) ->shouldReceive('getByType')->with('stdClass', false)->once()->andReturn(new stdClass) diff --git a/tests/Routers/LinkGenerator.phpt b/tests/Routers/LinkGenerator.phpt index e3f1787c9..cb53526ce 100644 --- a/tests/Routers/LinkGenerator.phpt +++ b/tests/Routers/LinkGenerator.phpt @@ -89,6 +89,14 @@ namespace { 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')); + Assert::same('http://nette.org/en/?action=default&presenter=Homepage', $generator->link('Homepage:default')); }); + + test(function () { + $generator = new LinkGenerator(new Routers\SimpleRouter, new Http\UrlScript('http://nette.org/en/')); + $generator2 = $generator->withReferenceUrl('http://nette.org/cs/'); + Assert::same('http://nette.org/en/?action=default&presenter=Homepage', $generator->link('Homepage:default')); + Assert::same('http://nette.org/cs/?action=default&presenter=Homepage', $generator2->link('Homepage:default')); + }); } diff --git a/tests/UI/ComponentReflection.convertType.phpt b/tests/UI/ComponentReflection.convertType.phpt index 0f3229ffd..9c91ae340 100644 --- a/tests/UI/ComponentReflection.convertType.phpt +++ b/tests/UI/ComponentReflection.convertType.phpt @@ -192,6 +192,23 @@ testIt('callable', 1); testIt('callable', 1.0); testIt('callable', 1.2); +testIt('object', null); +testIt('object', []); +testIt('object', $obj, $obj); +testIt('object', $var = function () {}, $var); +testIt('object', ''); +testIt('object', 'a'); +testIt('object', '1'); +testIt('object', '1.0'); +testIt('object', '1.1'); +testIt('object', '1a'); +testIt('object', true); +testIt('object', false); +testIt('object', 0); +testIt('object', 1); +testIt('object', 1.0); +testIt('object', 1.2); + testIt('stdClass', null); testIt('stdClass', []); testIt('stdClass', $obj, $obj);