diff --git a/CHANGELOG.md b/CHANGELOG.md index 90810fb..76faf73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +## 0.2.0 - 2017-03-22 + +ZE2 migration + +### Changed +* updated factories to PSR11 container and middleware to PSR15 +* refactored folder structure to follow modular application design a la ZE2 +* programmatic pipeline and routes +* restructured application to match ZE2 structure + +### Added +* Nothing + +### Deprecated +* Nothing + +### Removed +* Nothing + +### Fixed +* Nothing + + ## 0.1.1 - 2017-03-12 ### Added diff --git a/README.md b/README.md index c734ec5..eff492e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Dotkernel web starter package suitable for admin applications. Create a new project directory and change dir to it. Run the following composer command ```bash -$ composer create-project -s dev dotkernel/dot-admin . +$ composer create-project dotkernel/dot-admin . ``` ## Configuration diff --git a/bin/clear-config-cache.php b/bin/clear-config-cache.php index dd5516a..50a14ce 100644 --- a/bin/clear-config-cache.php +++ b/bin/clear-config-cache.php @@ -4,19 +4,23 @@ * * Can also be invoked as `composer clear-config-cache`. * - * @see https://github.com/dotkernel/dot-admin/ for the canonical source repository - * @copyright Copyright (c) 2017 Apidemia (https://www.apidemia.com) - * @license https://github.com/dotkernel/dot-admin/blob/master/LICENSE.md MIT License + * @see https://github.com/zendframework/zend-expressive-skeleton for the canonical source repository + * @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com) + * @license https://github.com/zendframework/zend-expressive-skeleton/blob/master/LICENSE.md New BSD License */ chdir(__DIR__ . '/../'); + require 'vendor/autoload.php'; + $config = include 'config/config.php'; -if (!isset($config['config_cache_path'])) { + +if (! isset($config['config_cache_path'])) { echo "No configuration cache path found" . PHP_EOL; exit(0); } -if (!file_exists($config['config_cache_path'])) { + +if (! file_exists($config['config_cache_path'])) { printf( "Configured config cache file '%s' not found%s", $config['config_cache_path'], @@ -24,6 +28,7 @@ ); exit(0); } + if (false === unlink($config['config_cache_path'])) { printf( "Error removing config cache file '%s'%s", @@ -32,6 +37,7 @@ ); exit(1); } + printf( "Removed configured config cache file '%s'%s", $config['config_cache_path'], diff --git a/composer.json b/composer.json index ef2be76..db93044 100644 --- a/composer.json +++ b/composer.json @@ -9,67 +9,70 @@ "email": "tibi@apidemia.com" } ], + "config": { + "sort-packages": true + }, "require": { "php": "^7.1", "roave/security-advisories": "dev-master", - "zendframework/zend-expressive": "^1.0", - "zendframework/zend-expressive-fastroute": "^1.0", - "zendframework/zend-servicemanager": "^3.0", - "zendframework/zend-expressive-twigrenderer": "^1.0", + "zendframework/zend-expressive": "^2.0.2", + "zendframework/zend-expressive-fastroute": "^2.0", + "zendframework/zend-expressive-twigrenderer": "^1.4", + "zendframework/zend-servicemanager": "^3.3.0", "zendframework/zend-db": "^2.8", "zendframework/zend-i18n": "^2.7", "zendframework/zend-i18n-resources": "^2.5", "zendframework/zend-captcha": "^2.6", "zendframework/zendservice-recaptcha": "^3.0", "zendframework/zend-text": "^2.6", - "zendframework/zend-stdlib": "^3.0", + "zendframework/zend-stdlib": "^3.1", "zendframework/zend-psr7bridge": "^0.2.2", "zendframework/zend-config": "^3.1", "zendframework/zend-config-aggregator": "^0.2.0", - "zendframework/zend-component-installer": "^0.6.0 || ~1.0", + "zendframework/zend-component-installer": "^1.0 || ^0.7.0", - "dotkernel/dot-annotated-services": "^1.0", - "dotkernel/dot-authentication-service": "^0.1", - "dotkernel/dot-authentication-web": "^0.1", - "dotkernel/dot-cache": "^1.0", - "dotkernel/dot-controller": "^0.1", - "dotkernel/dot-controller-plugin-flashmessenger": "^0.1", - "dotkernel/dot-controller-plugin-authentication": "^0.1", - "dotkernel/dot-controller-plugin-authorization": "^0.1", - "dotkernel/dot-controller-plugin-forms": "^0.1", + "dotkernel/dot-annotated-services": "^1.1", + "dotkernel/dot-authentication-service": "^0.2", + "dotkernel/dot-authentication-web": "^0.2", + "dotkernel/dot-cache": "^1.1", + "dotkernel/dot-controller": "^0.2", + "dotkernel/dot-controller-plugin-flashmessenger": "^0.2", + "dotkernel/dot-controller-plugin-authentication": "^0.2", + "dotkernel/dot-controller-plugin-authorization": "^0.2", + "dotkernel/dot-controller-plugin-forms": "^0.2", "dotkernel/dot-controller-plugin-mail": "^0.1", - "dotkernel/dot-controller-plugin-session": "^0.1", - "dotkernel/dot-mapper": "^0.1", - "dotkernel/dot-event": "^0.1", - "dotkernel/dot-filter": "^1.0", - "dotkernel/dot-flashmessenger": "^0.1", - "dotkernel/dot-form": "^1.0", - "dotkernel/dot-helpers": "^0.1", - "dotkernel/dot-hydrator": "^1.0", - "dotkernel/dot-inputfilter": "^1.0", - "dotkernel/dot-log": "^1.0", + "dotkernel/dot-controller-plugin-session": "^0.2", + "dotkernel/dot-mapper": "^0.3", + "dotkernel/dot-event": "^0.2", + "dotkernel/dot-filter": "^1.1", + "dotkernel/dot-flashmessenger": "^0.2", + "dotkernel/dot-form": "^1.1", + "dotkernel/dot-helpers": "^0.2", + "dotkernel/dot-hydrator": "^1.1", + "dotkernel/dot-inputfilter": "^1.1", + "dotkernel/dot-log": "^1.1", "dotkernel/dot-mail": "^0.1", - "dotkernel/dot-navigation": "^0.1", - "dotkernel/dot-paginator": "^1.0", - "dotkernel/dot-rbac": "^0.1", - "dotkernel/dot-rbac-guard": "^0.1", - "dotkernel/dot-session": "^1.0", - "dotkernel/dot-twigrenderer": "^0.1", - "dotkernel/dot-user": "^0.1", - "dotkernel/dot-validator": "^1.0" + "dotkernel/dot-navigation": "^0.2", + "dotkernel/dot-paginator": "^1.1", + "dotkernel/dot-rbac": "^0.2", + "dotkernel/dot-rbac-guard": "^0.2", + "dotkernel/dot-session": "^2.0", + "dotkernel/dot-twigrenderer": "^0.2", + "dotkernel/dot-user": "^0.2", + "dotkernel/dot-validator": "^1.1" }, "require-dev": { - "phpunit/phpunit": "^4.8", - "squizlabs/php_codesniffer": "^2.3", - "filp/whoops": "^1.1 || ^2.0", - "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": "^6.0.8 || ^5.7.15", + "squizlabs/php_codesniffer": "^2.8.1", + "zendframework/zend-expressive-tooling": "^0.3.2", + "filp/whoops": "^2.1.7", "zfcampus/zf-development-mode": "^3.1" }, "autoload": { "psr-4": { - "Admin\\App\\": "src/App/", - "Admin\\Admin\\": "src/Admin/", - "Admin\\User\\": "src/User/" + "Admin\\App\\": "src/App/src", + "Admin\\Admin\\": "src/Admin/src", + "Admin\\User\\": "src/User/src" } }, "autoload-dev": { @@ -78,24 +81,28 @@ } }, "scripts": { + "post-create-project-cmd": [ + "@development-enable" + ], "development-disable": "zf-development-mode disable", "development-enable": "zf-development-mode enable", "development-status": "zf-development-mode status", "check": [ - "@cs", + "@cs-check", "@test" ], "clear-config-cache": "php bin/clear-config-cache.php", "cs-check": "phpcs", "cs-fix": "phpcbf", - "serve": "php -S 0.0.0.0:8080 -t public/ public/index.php", + "serve": "php -S 0.0.0.0:8080 -t public public/index.php", "test": "phpunit --colors=always", "test-coverage": "phpunit --colors=always --coverage-clover clover.xml", "upload-coverage": "coveralls -v" }, "extra": { "branch-alias": { - "dev-master": "0.2-dev" + "dev-master": "0.2-dev", + "dev-develop": "0.3-dev" } } } diff --git a/config/autoload/authentication.global.php b/config/autoload/authentication.global.php index 859e9d6..4a00ff3 100644 --- a/config/autoload/authentication.global.php +++ b/config/autoload/authentication.global.php @@ -1,5 +1,9 @@ [ 'adapter' => [ @@ -7,15 +11,15 @@ 'options' => [ 'adapter' => 'database', - 'identity_prototype' => \Admin\Admin\Entity\AdminEntity::class, - 'identity_hydrator' => \Dot\Hydrator\ClassMethodsCamelCase::class, + 'identity_prototype' => AdminEntity::class, + 'identity_hydrator' => AdminHydrator::class, 'table' => 'admin', 'identity_columns' => ['username', 'email'], 'credential_column' => 'password', - 'callback_check' => \Dot\User\Service\PasswordCheck::class + 'callback_check' => PasswordCheck::class ] ], 'storage' => [ diff --git a/config/autoload/authorization-guards.global.php b/config/autoload/authorization-guards.global.php index ceaea95..e13825a 100644 --- a/config/autoload/authorization-guards.global.php +++ b/config/autoload/authorization-guards.global.php @@ -1,8 +1,10 @@ [ - 'protection_policy' => \Dot\Rbac\Guard\Guard\GuardInterface::POLICY_DENY, + 'protection_policy' => GuardInterface::POLICY_DENY, 'event_listeners' => [], @@ -35,7 +37,8 @@ 'confirm-account', 'opt-out' ], - 'roles' => ['guest'], + // no restriction on these actions because they handle the authentication check + 'roles' => ['*'], ] ] ] @@ -54,7 +57,7 @@ 'actions' => ['add', 'edit', 'delete'], 'permissions' => [ 'permissions' => ['superuser', 'admin-manager'], - 'condition' => \Dot\Rbac\Guard\Guard\GuardInterface::CONDITION_OR, + 'condition' => GuardInterface::CONDITION_OR, ], ], [ @@ -67,7 +70,7 @@ 'actions' => [], 'permissions' => [ 'permissions' => ['superuser', 'admin'], - 'condition' => \Dot\Rbac\Guard\Guard\GuardInterface::CONDITION_OR + 'condition' => GuardInterface::CONDITION_OR ] ], ] diff --git a/config/autoload/cache.global.php b/config/autoload/cache.global.php index 2d4a904..63b6d57 100644 --- a/config/autoload/cache.global.php +++ b/config/autoload/cache.global.php @@ -1,5 +1,8 @@ __DIR__ . '/../../data/cache/annotations', @@ -7,8 +10,7 @@ 'factories' => [ // used by dot-annotated-services to cache annotations // needs to return a cache instance from Doctrine\Common\Cache - \Dot\AnnotatedServices\Factory\AbstractAnnotatedFactory::CACHE_SERVICE => - \Admin\App\Factory\AnnotationsCacheFactory::class, + AbstractAnnotatedFactory::CACHE_SERVICE => AnnotationsCacheFactory::class, ] ], ]; diff --git a/config/autoload/controllers.global.php b/config/autoload/controllers.global.php deleted file mode 100644 index a702621..0000000 --- a/config/autoload/controllers.global.php +++ /dev/null @@ -1,11 +0,0 @@ - [], - - 'dot_controller' => [ - 'plugin_manager' => [], - - 'event_listeners' => [], - ], -]; diff --git a/config/autoload/dependencies.global.php b/config/autoload/dependencies.global.php index f2f2ae6..4777cd7 100644 --- a/config/autoload/dependencies.global.php +++ b/config/autoload/dependencies.global.php @@ -1,20 +1,39 @@ [ - 'factories' => [ - Application::class => ApplicationFactory::class, - Helper\UrlHelper::class => Helper\UrlHelperFactory::class, - Helper\ServerUrlHelper::class => \Zend\ServiceManager\Factory\InvokableFactory::class, + // Use 'aliases' to alias a service name to another service. The + // key is the alias name, the value is the service to which it points. + 'aliases' => [ + 'Zend\Expressive\Delegate\DefaultDelegate' => Delegate\NotFoundDelegate::class, ], + // Use 'invokables' for constructor-less services, or services that do + // not require arguments to the constructor. Map a service name to the + // class name. + 'invokables' => [ + // Fully\Qualified\InterfaceName::class => Fully\Qualified\ClassName::class, + Helper\ServerUrlHelper::class => Helper\ServerUrlHelper::class, + ], + // Use 'factories' for services provided by callbacks/factory classes. + 'factories' => [ + Application::class => Container\ApplicationFactory::class, + Delegate\NotFoundDelegate::class => Container\NotFoundDelegateFactory::class, + Helper\ServerUrlMiddleware::class => Helper\ServerUrlMiddlewareFactory::class, + Helper\UrlHelper::class => Helper\UrlHelperFactory::class, + Helper\UrlHelperMiddleware::class => Helper\UrlHelperMiddlewareFactory::class, - 'lazy_services' => [ - 'proxies_target_dir' => 'data/proxies', - 'proxies_namespace' => 'DotProxy', - ] + Zend\Stratigility\Middleware\ErrorHandler::class => Container\ErrorHandlerFactory::class, + Middleware\ErrorResponseGenerator::class => Container\ErrorResponseGeneratorFactory::class, + Middleware\NotFoundHandler::class => Container\NotFoundHandlerFactory::class, + ], ], ]; diff --git a/config/autoload/development.local.php.dist b/config/autoload/development.local.php.dist new file mode 100644 index 0000000..a3b3c0d --- /dev/null +++ b/config/autoload/development.local.php.dist @@ -0,0 +1,37 @@ + [ + 'invokables' => [ + ], + 'factories' => [ + ErrorResponseGenerator::class => Container\WhoopsErrorResponseGeneratorFactory::class, + 'Zend\Expressive\Whoops' => Container\WhoopsFactory::class, + 'Zend\Expressive\WhoopsPageHandler' => Container\WhoopsPageHandlerFactory::class, + ], + 'lazy_services' => [ + 'write_proxy_files' => false, + ] + ], + + 'whoops' => [ + 'json_exceptions' => [ + 'display' => true, + 'show_trace' => true, + 'ajax_only' => true, + ], + ], +]; diff --git a/config/autoload/ems.global.php b/config/autoload/ems.global.php deleted file mode 100644 index e9ec1c5..0000000 --- a/config/autoload/ems.global.php +++ /dev/null @@ -1,7 +0,0 @@ - [ - 'default_adapter' => 'database', - ] -]; diff --git a/config/autoload/errorhandler.local.php.dist b/config/autoload/errorhandler.local.php.dist deleted file mode 100644 index de70e4f..0000000 --- a/config/autoload/errorhandler.local.php.dist +++ /dev/null @@ -1,21 +0,0 @@ - [ - 'invokables' => [ - 'Zend\Expressive\Whoops' => Whoops\Run::class, - 'Zend\Expressive\WhoopsPageHandler' => Whoops\Handler\PrettyPageHandler::class, - ], - 'factories' => [ - 'Zend\Expressive\FinalHandler' => Zend\Expressive\Container\WhoopsErrorHandlerFactory::class, - ], - ], - - 'whoops' => [ - 'json_exceptions' => [ - 'display' => true, - 'show_trace' => true, - 'ajax_only' => true, - ], - ], -]; diff --git a/config/autoload/forms.global.php b/config/autoload/forms.global.php deleted file mode 100644 index e27fd9e..0000000 --- a/config/autoload/forms.global.php +++ /dev/null @@ -1,16 +0,0 @@ - [ - 'hydrator_manager' => [], - ], - - 'dot_input_filter' => [ - 'input_filter_manager' => [], - ], - - 'dot_form' => [ - 'form_manager' => [], - 'forms' => [], - ], -]; diff --git a/config/autoload/local.php.dist b/config/autoload/local.php.dist index 10483a0..8a14df0 100644 --- a/config/autoload/local.php.dist +++ b/config/autoload/local.php.dist @@ -1,7 +1,6 @@ [ 'adapters' => [ 'database' => [ @@ -15,9 +14,15 @@ return [ ] ], + 'dot_mapper' => [ + 'default_adapter' => 'database', + ], + 'dot_mail' => [ 'default' => [ 'smtp_options' => [ + 'host' => '', + 'port' => 587, 'connection_config' => [ 'username' => '', 'password' => '' diff --git a/config/autoload/log.global.php b/config/autoload/log.global.php index acbdba7..9dfd6c9 100644 --- a/config/autoload/log.global.php +++ b/config/autoload/log.global.php @@ -1,5 +1,7 @@ [ 'loggers' => [ @@ -7,7 +9,7 @@ 'writers' => [ [ 'name' => 'db', - 'priority' => \Zend\Log\Logger::INFO, + 'priority' => Logger::INFO, 'options' => [ 'db' => 'database', diff --git a/config/autoload/middleware-pipeline.global.php b/config/autoload/middleware-pipeline.global.php deleted file mode 100644 index 1735bf0..0000000 --- a/config/autoload/middleware-pipeline.global.php +++ /dev/null @@ -1,83 +0,0 @@ - [ - 'factories' => [ - Helper\ServerUrlMiddleware::class => Helper\ServerUrlMiddlewareFactory::class, - Helper\UrlHelperMiddleware::class => Helper\UrlHelperMiddlewareFactory::class, - ], - ], - // This can be used to seed pre- and/or post-routing middleware - 'middleware_pipeline' => [ - // An array of middleware to register. Each item is of the following - // specification: - // - // [ - // Required: - // 'middleware' => 'Name or array of names of middleware services and/or callables', - // Optional: - // 'path' => '/path/to/match', // string; literal path prefix to match - // // middleware will not execute - // // if path does not match! - // 'error' => true, // boolean; true for error middleware - // 'priority' => 1, // int; higher values == register early; - // // lower/negative == register last; - // // default is 1, if none is provided. - // ], - // - // While the ApplicationFactory ignores the keys associated with - // specifications, they can be used to allow merging related values - // defined in multiple configuration files/locations. This file defines - // some conventional keys for middleware to execute early, routing - // middleware, and error middleware. - 'always' => [ - 'middleware' => [ - // Add more middleware here that you want to execute on - // every request: - // - bootstrapping - // - pre-conditions - // - modifications to outgoing responses - Helper\ServerUrlMiddleware::class, - \Dot\Session\SessionMiddleware::class, - ], - 'priority' => 10000, - ], - - 'routing' => [ - 'middleware' => [ - ApplicationFactory::ROUTING_MIDDLEWARE, - Helper\UrlHelperMiddleware::class, - - \Admin\App\Middleware\AdminIndexMiddleware::class, - - //DK after-routing middleware - \Dot\Navigation\NavigationMiddleware::class, - \Dot\Rbac\Guard\Middleware\RbacGuardMiddleware::class, - - // Add more middleware here that needs to introspect the routing - // results; this might include: - // - route-based authentication - // - route-based validation - // - etc. - ApplicationFactory::DISPATCH_MIDDLEWARE, - ], - 'priority' => 1, - ], - - [ - 'middleware' => \Dot\Helpers\Middleware\NotFound::class, - 'priority' => -1 - ], - - 'error' => [ - 'middleware' => [ - // Add error middleware here. - ], - 'error' => true, - 'priority' => -10000, - ], - ], -]; diff --git a/config/autoload/navigation.global.php b/config/autoload/navigation.global.php index 6ae352e..f385711 100644 --- a/config/autoload/navigation.global.php +++ b/config/autoload/navigation.global.php @@ -14,24 +14,29 @@ [ 'options' => [ 'label' => 'Dashboard', - 'route' => 'dashboard', - 'params' => ['action' => ''], + 'route' => [ + 'route_name' => 'dashboard', + ], 'icon' => 'fa fa-tachometer', ] ], [ 'options' => [ 'label' => 'Manage admins', - 'route' => 'user', - 'params' => ['action' => 'manage'], + 'route' => [ + 'route_name' => 'user', + 'route_params' => ['action' => 'manage'] + ], 'icon' => 'fa fa-user-circle-o', ], ], [ 'options' => [ 'label' => 'Manage users', - 'route' => 'f_user', - 'params' => ['action' => 'manage'], + 'route' => [ + 'route_name' => 'f_user', + 'route_params' => ['action' => 'manage'] + ], 'icon' => 'fa fa-user-o', ], ], @@ -46,22 +51,28 @@ [ 'options' => [ 'label' => 'Profile', - 'route' => 'user', - 'params' => ['action' => 'account'], + 'route' => [ + 'route_name' => 'user', + 'route_params' => ['action' => 'account'] + ], 'icon' => 'fa fa-user', ] ], [ 'options' => [ 'label' => 'Settings', - 'route' => 'dashboard', + 'route' => [ + 'route_name' => 'dashboard', + ], 'icon' => 'fa fa-cog', ] ], [ 'options' => [ 'label' => 'Sign Out', - 'route' => 'logout', + 'route' => [ + 'route_name' => 'logout', + ], 'icon' => 'fa fa-sign-out', ] ] diff --git a/config/autoload/router.global.php b/config/autoload/router.global.php new file mode 100644 index 0000000..0505bee --- /dev/null +++ b/config/autoload/router.global.php @@ -0,0 +1,12 @@ + [ + 'invokables' => [ + RouterInterface::class => FastRouteRouter::class, + ], + ], +]; diff --git a/config/autoload/routes.global.php b/config/autoload/routes.global.php deleted file mode 100644 index d047c27..0000000 --- a/config/autoload/routes.global.php +++ /dev/null @@ -1,15 +0,0 @@ - [ - 'invokables' => [ - Zend\Expressive\Router\RouterInterface::class => Zend\Expressive\Router\FastRouteRouter::class, - ], - // Map middleware -> factories here - 'factories' => [], - ], - - 'routes' => [ - // see Admin\App\ConfigProvider or similar... - ], -]; diff --git a/config/autoload/templates.global.php b/config/autoload/templates.global.php index 92577f0..79bd6b1 100644 --- a/config/autoload/templates.global.php +++ b/config/autoload/templates.global.php @@ -1,37 +1,34 @@ [ 'factories' => [ - 'Zend\Expressive\FinalHandler' => - Zend\Expressive\Container\TemplatedErrorHandlerFactory::class, - - Zend\Expressive\Template\TemplateRendererInterface::class => - Zend\Expressive\Twig\TwigRendererFactory::class, - - Twig_Environment::class => - \Zend\Expressive\Twig\TwigEnvironmentFactory::class, + Twig_Environment::class => TwigEnvironmentFactory::class, + TemplateRendererInterface::class => TwigRendererFactory::class, ], ], 'templates' => [ 'extension' => 'html.twig', - 'paths' => [ - 'app' => [__DIR__ . '/../../templates/app'], - 'layout' => [__DIR__ . '/../../templates/layout'], - 'error' => [__DIR__ . '/../../templates/error'], - 'partial' => [__DIR__ . '/../../templates/partial'], - 'admin' => [__DIR__ . '/../../templates/app/admin'], - 'user' => [__DIR__ . '/../../templates/app/user'], - ], ], 'twig' => [ - 'cache_dir' => __DIR__ . '/../../data/cache/twig', - 'assets_url' => '/', + 'cache_dir' => 'data/cache/twig', + 'assets_url' => '/', 'assets_version' => null, - 'extensions' => [ + 'extensions' => [ // extension service names or instances ], + 'runtime_loaders' => [ + // runtime loader names or instances + ], + 'globals' => [ + // Variables to pass to all twig templates + ], + // 'timezone' => 'default timezone identifier; e.g. America/Chicago', ], ]; diff --git a/config/autoload/zend-expressive.global.php b/config/autoload/zend-expressive.global.php index 49d01bb..6673e4c 100644 --- a/config/autoload/zend-expressive.global.php +++ b/config/autoload/zend-expressive.global.php @@ -3,20 +3,28 @@ use Zend\ConfigAggregator\ConfigAggregator; return [ - // Enable debugging; typically used to provide debugging information within templates. - 'debug' => false, - // Toggle the configuration cache. Set this to boolean false, or remove the // directive, to disable configuration caching. Toggling development mode // will also disable it by default; clear the configuration cache using // `composer clear-config-cache`. ConfigAggregator::ENABLE_CACHE => true, + // Enable debugging; typically used to provide debugging information within templates. + 'debug' => false, + 'zend-expressive' => [ + // Enable exception-based error handling via standard middleware. + 'raise_throwables' => true, + + // Enable programmatic pipeline: Any `middleware_pipeline` or `routes` + // configuration will be ignored when creating the `Application` instance. + 'programmatic_pipeline' => true, + // Provide templates for the error handling middleware to use when // generating responses. 'error_handler' => [ - 'template_404' => 'error::404', + 'template_404' => 'error::404', + 'template_403' => 'error::403', 'template_error' => 'error::error', ], ], diff --git a/config/config.php b/config/config.php index 8c924ef..5042616 100644 --- a/config/config.php +++ b/config/config.php @@ -13,11 +13,15 @@ // Include cache configuration new ArrayProvider($cacheConfig), - //zend framework + //zend framework configs \Zend\Db\ConfigProvider::class, \Zend\Mail\ConfigProvider::class, - // dotkernel + // dotkernel components default configs + // some of these configs are overwriting and customizing the underlying zendframework configs + // in case the dotkernel package is heavily based on a zendframework package + // you should not include both zendframework config provider and dotkernel's config provider in this case + // e.g: dot-filter, dot-paginator, dot-session etc. \Dot\AnnotatedServices\ConfigProvider::class, \Dot\Authentication\ConfigProvider::class, \Dot\Authentication\Web\ConfigProvider::class, @@ -64,4 +68,5 @@ // Load development config if it exists new PhpFileProvider('config/development.config.php'), ], $cacheConfig['config_cache_path']); + return $aggregator->getMergedConfig(); diff --git a/config/development.config.php.dist b/config/development.config.php.dist index 30a6dfa..f9e594a 100644 --- a/config/development.config.php.dist +++ b/config/development.config.php.dist @@ -1,18 +1,24 @@ true, ConfigAggregator::ENABLE_CACHE => false, - - 'dependencies' => [ - 'lazy_services' => [ - 'write_proxy_files' => false, - ] - ] ]; diff --git a/config/pipeline.php b/config/pipeline.php new file mode 100644 index 0000000..a4c1ae9 --- /dev/null +++ b/config/pipeline.php @@ -0,0 +1,72 @@ +pipe(ErrorHandler::class); +$app->pipe(ServerUrlMiddleware::class); + +// starts the session and tracks session activity +$app->pipe(SessionMiddleware::class); + +// automatically login the user if it has a valid remember token +$app->pipe(AutoLogin::class); + +// Pipe more middleware here that you want to execute on every request: +// - bootstrapping +// - pre-conditions +// - modifications to outgoing responses + +// Register the routing middleware in the middleware pipeline +$app->pipeRoutingMiddleware(); + +// zend expressive middleware +$app->pipe(ImplicitHeadMiddleware::class); +$app->pipe(ImplicitOptionsMiddleware::class); +$app->pipe(UrlHelperMiddleware::class); + +// authentication and authorization error handlers +// this is piped here to have access to the route result +// it should be ok, as these particular errors are generated from below middleware or routed middleware +$app->pipe(ForbiddenHandler::class); +$app->pipe(UnauthorizedHandler::class); + +// Add more middleware here that needs to introspect the routing results; this +// ... + +// this middleware redirects to login or dashboard, based on authentication status, if `/` path is accessed +// this is just to make the UI more friendly, by by-passing the rbac guards in this particular case +$app->pipe(AdminIndexMiddleware::class); + +// navigation middleware makes sure the navigation service is injected the RouteResult +$app->pipe(NavigationMiddleware::class); + +// the RBAC guards protect chunks of the application(routes or controllers or controller actions) +// the authorization service can be used together with the guards for maximum security and finer control +$app->pipe(RbacGuardMiddleware::class); + +// Register the dispatch middleware in the middleware pipeline +$app->pipeDispatchMiddleware(); + +// At this point, if no Response is return by any middleware, the +// NotFoundHandler kicks in; alternately, you can provide other fallback +// middleware to execute. +$app->pipe(NotFoundHandler::class); diff --git a/config/routes.php b/config/routes.php new file mode 100644 index 0000000..2ac6996 --- /dev/null +++ b/config/routes.php @@ -0,0 +1,50 @@ +get('/', App\Action\HomePageAction::class, 'home'); + * $app->post('/album', App\Action\AlbumCreateAction::class, 'album.create'); + * $app->put('/album/:id', App\Action\AlbumUpdateAction::class, 'album.put'); + * $app->patch('/album/:id', App\Action\AlbumUpdateAction::class, 'album.patch'); + * $app->delete('/album/:id', App\Action\AlbumDeleteAction::class, 'album.delete'); + * + * Or with multiple request methods: + * + * $app->route('/contact', App\Action\ContactAction::class, ['GET', 'POST', ...], 'contact'); + * + * Or handling all request methods: + * + * $app->route('/contact', App\Action\ContactAction::class)->setName('contact'); + * + * or: + * + * $app->route( + * '/contact', + * App\Action\ContactAction::class, + * Zend\Expressive\Router\Route::HTTP_METHOD_ANY, + * 'contact' + * ); + */ + +/** @var \Zend\Expressive\Application $app */ +// Dashboard controller route +$app->route('/dashboard[/{action}]', DashboardController::class, ['GET', 'POST'], 'dashboard'); + +// following three routes are for user(in this case user refers to the admin user) authentication and management +$app->route('/admin/login', LoginAction::class, ['GET', 'POST'], 'login'); +$app->route('/admin/logout', LogoutAction::class, ['GET'], 'logout'); +$app->route('/admin[/{action}[/{id}]]', [AdminController::class, UserController::class], ['GET', 'POST'], 'user'); + +// this route is for the frontend user management(hence the f_ prefix) +// if this admin application is used without the frontend application(no frontend user management required) +// you can remove the Admin\User module together with any configuration related to it +// TODO: we'll offer the option to use admin package without frontend in future releases, as installation scripts +$app->route('/user[/{action}[/{id}]]', UserManagementController::class, ['GET', 'POST'], 'f_user'); diff --git a/public/index.php b/public/index.php index f84c136..0fad61d 100644 --- a/public/index.php +++ b/public/index.php @@ -10,9 +10,20 @@ chdir(dirname(__DIR__)); require 'vendor/autoload.php'; -/** @var \Interop\Container\ContainerInterface $container */ -$container = require 'config/container.php'; +/** + * Self-called anonymous function that creates its own scope and keep the global namespace clean. + */ +call_user_func(function () { + /** @var \Interop\Container\ContainerInterface $container */ + $container = require 'config/container.php'; -/** @var \Zend\Expressive\Application $app */ -$app = $container->get(\Zend\Expressive\Application::class); -$app->run(); + /** @var \Zend\Expressive\Application $app */ + $app = $container->get(\Zend\Expressive\Application::class); + + // Import programmatic/declarative middleware pipeline and routing + // configuration statements + require 'config/pipeline.php'; + require 'config/routes.php'; + + $app->run(); +}); diff --git a/src/Admin/Authentication/AuthenticationListener.php b/src/Admin/src/Authentication/AuthenticationListener.php similarity index 100% rename from src/Admin/Authentication/AuthenticationListener.php rename to src/Admin/src/Authentication/AuthenticationListener.php diff --git a/src/Admin/Authentication/UnauthorizedListener.php b/src/Admin/src/Authentication/UnauthorizedListener.php similarity index 84% rename from src/Admin/Authentication/UnauthorizedListener.php rename to src/Admin/src/Authentication/UnauthorizedListener.php index c1c3486..a5cc0f6 100644 --- a/src/Admin/Authentication/UnauthorizedListener.php +++ b/src/Admin/src/Authentication/UnauthorizedListener.php @@ -9,6 +9,7 @@ use Dot\Authentication\Web\Event\AbstractAuthenticationEventListener; use Dot\Authentication\Web\Event\AuthenticationEvent; +use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Zend\Diactoros\Response; use Zend\Expressive\Router\RouteResult; @@ -30,7 +31,7 @@ class UnauthorizedListener extends AbstractAuthenticationEventListener /** * @param AuthenticationEvent $e - * @return bool|Response\EmptyResponse + * @return ResponseInterface|null */ public function onUnauthorized(AuthenticationEvent $e) { @@ -38,17 +39,17 @@ public function onUnauthorized(AuthenticationEvent $e) $request = $e->getParam('request'); /** @var RouteResult $routeMatch */ $routeMatch = $request->getAttribute(RouteResult::class); - if ($routeMatch) { $routeName = $routeMatch->getMatchedRouteName(); $params = $routeMatch->getMatchedParams(); - $action = $params['action'] ?? ''; + $action = $params['action'] ?? 'index'; if (in_array($routeName, $this->routes) && in_array($action, $this->actions)) { return new Response\EmptyResponse(401); } } - - return true; + // just to make the IDE shut up about returning something + // only ResponseInterfaces return types are picked by the event trigger source + return null; } } diff --git a/src/Admin/ConfigProvider.php b/src/Admin/src/ConfigProvider.php similarity index 72% rename from src/Admin/ConfigProvider.php rename to src/Admin/src/ConfigProvider.php index 4682033..d709f95 100644 --- a/src/Admin/ConfigProvider.php +++ b/src/Admin/src/ConfigProvider.php @@ -10,6 +10,7 @@ namespace Admin\Admin; use Admin\Admin\Authentication\AuthenticationListener; +use Admin\Admin\Controller\AdminController; use Admin\Admin\Entity\AdminEntity; use Admin\Admin\Entity\RoleEntity; use Admin\Admin\Factory\AdminHydratorFactory; @@ -21,13 +22,13 @@ use Admin\Admin\Mapper\AdminDbMapper; use Admin\Admin\Mapper\RoleDbMapper; use Admin\Admin\Mapper\TokenDbMapper; -use Admin\App\Controller\AdminController; use Dot\Mapper\Factory\DbMapperFactory; use Dot\User\Entity\ConfirmTokenEntity; use Dot\User\Entity\RememberTokenEntity; use Dot\User\Entity\ResetTokenEntity; use Dot\User\Factory\UserDbMapperFactory; use Dot\User\Factory\UserFieldsetFactory; +use Dot\User\Options\MessagesOptions; use Zend\ServiceManager\Factory\InvokableFactory; /** @@ -39,22 +40,28 @@ class ConfigProvider public function __invoke(): array { return [ - 'dependencies' => $this->getDependenciesConfig(), + 'dependencies' => $this->getDependencies(), - 'dot_ems' => $this->getMapperConfig(), + 'templates' => $this->getTemplates(), - 'dot_authentication' => $this->getAuthenticationConfig(), + 'dot_mapper' => $this->getMappers(), - 'dot_form' => $this->getFormsConfig(), + 'dot_authentication' => $this->getAuthentication(), - 'dot_hydrator' => $this->getHydratorsConfig(), + 'dot_form' => $this->getForms(), + + 'dot_hydrator' => $this->getHydrators(), 'dot_user' => [ 'user_entity' => AdminEntity::class, 'role_entity' => RoleEntity::class, 'default_roles' => ['admin'], - 'route_default' => ['route_name' => 'dashboard', 'route_params' => ['action' => '']], + + 'route_default' => [ + 'route_name' => 'user', + 'route_params' => ['action' => 'account'] + ], 'enable_account_confirmation' => false, @@ -69,12 +76,13 @@ public function __invoke(): array 'enable_recovery' => false, ], 'template_options' => [ - 'login_template' => 'admin::login', - 'account_template' => 'admin::account', + 'login_template' => 'app::login', + 'account_template' => 'app::account', ], 'messages_options' => [ 'messages' => [ - + MessagesOptions::SIGN_OUT_FIRST => + 'Sign out first in order to access the requested content' ] ], @@ -87,14 +95,23 @@ public function __invoke(): array ]; } - public function getDependenciesConfig(): array + public function getDependencies(): array { return [ ]; } - public function getMapperConfig(): array + public function getTemplates(): array + { + return [ + 'paths' => [ + 'app' => [__DIR__ . '/../templates/app'] + ] + ]; + } + + public function getMappers(): array { return [ 'mapper_manager' => [ @@ -115,7 +132,7 @@ public function getMapperConfig(): array ]; } - public function getHydratorsConfig(): array + public function getHydrators(): array { return [ 'hydrator_manager' => [ @@ -129,7 +146,7 @@ public function getHydratorsConfig(): array ]; } - public function getFormsConfig(): array + public function getForms(): array { return [ 'form_manager' => [ @@ -137,24 +154,35 @@ public function getFormsConfig(): array AdminFieldset::class => UserFieldsetFactory::class, AdminForm::class => InvokableFactory::class, AccountForm::class => InvokableFactory::class, - //ChangePasswordForm::class => InvokableFactory::class, ], 'aliases' => [ 'UserFieldset' => AdminFieldset::class, 'AdminFieldset' => AdminFieldset::class, 'Admin' => AdminForm::class, 'Account' => AccountForm::class, - //'ChangePassword' => ChangePasswordForm::class, ] ] ]; } - public function getAuthenticationConfig(): array + public function getAuthentication(): array { return [ 'web' => [ - 'after_login_route' => ['route_name' => 'dashboard', 'route_params' => ['action' => '']], + 'login_route' => [ + 'route_name' => 'login', + ], + + 'logout_route' => [ + 'route_name' => 'logout', + ], + + 'after_logout_route' => [ + 'route_name' => 'login', + ], + 'after_login_route' => [ + 'route_name' => 'dashboard', + ], 'event_listeners' => [ [ diff --git a/src/App/Controller/AdminController.php b/src/Admin/src/Controller/AdminController.php similarity index 92% rename from src/App/Controller/AdminController.php rename to src/Admin/src/Controller/AdminController.php index 0222e59..0502a78 100644 --- a/src/App/Controller/AdminController.php +++ b/src/Admin/src/Controller/AdminController.php @@ -5,11 +5,12 @@ * @license https://github.com/dotkernel/dot-admin/blob/master/LICENSE.md MIT License */ -namespace Admin\App\Controller; +namespace Admin\Admin\Controller; use Admin\Admin\Entity\AdminEntity; use Admin\Admin\Form\AdminForm; use Admin\Admin\Service\AdminService; +use Admin\App\Controller\EntityManageBaseController; use Dot\AnnotatedServices\Annotation\Inject; use Dot\AnnotatedServices\Annotation\Service; use Dot\User\Event\UserControllerEvent; @@ -32,7 +33,7 @@ class AdminController extends EntityManageBaseController implements UserControll const ENTITY_NAME_SINGULAR = 'admin'; const ENTITY_NAME_PLURAL = 'admins'; const ENTITY_ROUTE_NAME = 'user'; - const ENTITY_TEMPLATE_NAME = 'admin::admin-table'; + const ENTITY_TEMPLATE_NAME = 'app::admin-table'; const ENTITY_FORM_NAME = 'Admin'; const ENTITY_DELETE_FORM_NAME = 'ConfirmDelete'; @@ -79,8 +80,8 @@ public function changePasswordAction(): ResponseInterface return new RedirectResponse($this->url('user', ['action' => 'account'])); } - $next = $this->getNext(); - return $next($this->getRequest(), $this->getResponse()); + $delegate = $this->getDelegate(); + return $delegate->process($this->getRequest()); } /** diff --git a/src/Admin/Entity/AdminEntity.php b/src/Admin/src/Entity/AdminEntity.php similarity index 100% rename from src/Admin/Entity/AdminEntity.php rename to src/Admin/src/Entity/AdminEntity.php diff --git a/src/Admin/Entity/RoleEntity.php b/src/Admin/src/Entity/RoleEntity.php similarity index 100% rename from src/Admin/Entity/RoleEntity.php rename to src/Admin/src/Entity/RoleEntity.php diff --git a/src/Admin/Factory/AdminHydratorFactory.php b/src/Admin/src/Factory/AdminHydratorFactory.php similarity index 94% rename from src/Admin/Factory/AdminHydratorFactory.php rename to src/Admin/src/Factory/AdminHydratorFactory.php index 29c37b2..171f511 100644 --- a/src/Admin/Factory/AdminHydratorFactory.php +++ b/src/Admin/src/Factory/AdminHydratorFactory.php @@ -11,7 +11,7 @@ use Admin\Admin\Hydrator\AdminHydrator; use Admin\Admin\Hydrator\RolesHydratingStrategy; -use Interop\Container\ContainerInterface; +use Psr\Container\ContainerInterface; /** * Class AdminHydratorFactory diff --git a/src/Admin/Form/AccountForm.php b/src/Admin/src/Form/AccountForm.php similarity index 98% rename from src/Admin/Form/AccountForm.php rename to src/Admin/src/Form/AccountForm.php index fb7df6a..83874c4 100644 --- a/src/Admin/Form/AccountForm.php +++ b/src/Admin/src/Form/AccountForm.php @@ -52,6 +52,7 @@ public function init() $this->add([ 'name' => 'submit', + 'type' => 'submit', 'attributes' => [ 'type' => 'submit', 'value' => 'Update account' diff --git a/src/Admin/Form/AdminFieldset.php b/src/Admin/src/Form/AdminFieldset.php similarity index 100% rename from src/Admin/Form/AdminFieldset.php rename to src/Admin/src/Form/AdminFieldset.php diff --git a/src/Admin/Form/AdminForm.php b/src/Admin/src/Form/AdminForm.php similarity index 100% rename from src/Admin/Form/AdminForm.php rename to src/Admin/src/Form/AdminForm.php diff --git a/src/Admin/Form/ChangePasswordForm.php b/src/Admin/src/Form/ChangePasswordForm.php similarity index 100% rename from src/Admin/Form/ChangePasswordForm.php rename to src/Admin/src/Form/ChangePasswordForm.php diff --git a/src/Admin/Hydrator/AdminHydrator.php b/src/Admin/src/Hydrator/AdminHydrator.php similarity index 100% rename from src/Admin/Hydrator/AdminHydrator.php rename to src/Admin/src/Hydrator/AdminHydrator.php diff --git a/src/Admin/Hydrator/RolesHydratingStrategy.php b/src/Admin/src/Hydrator/RolesHydratingStrategy.php similarity index 100% rename from src/Admin/Hydrator/RolesHydratingStrategy.php rename to src/Admin/src/Hydrator/RolesHydratingStrategy.php diff --git a/src/Admin/Mapper/AdminDbMapper.php b/src/Admin/src/Mapper/AdminDbMapper.php similarity index 100% rename from src/Admin/Mapper/AdminDbMapper.php rename to src/Admin/src/Mapper/AdminDbMapper.php diff --git a/src/Admin/Mapper/RoleDbMapper.php b/src/Admin/src/Mapper/RoleDbMapper.php similarity index 100% rename from src/Admin/Mapper/RoleDbMapper.php rename to src/Admin/src/Mapper/RoleDbMapper.php diff --git a/src/Admin/Mapper/TokenDbMapper.php b/src/Admin/src/Mapper/TokenDbMapper.php similarity index 100% rename from src/Admin/Mapper/TokenDbMapper.php rename to src/Admin/src/Mapper/TokenDbMapper.php diff --git a/src/Admin/Service/AdminService.php b/src/Admin/src/Service/AdminService.php similarity index 100% rename from src/Admin/Service/AdminService.php rename to src/Admin/src/Service/AdminService.php diff --git a/templates/app/admin/account.html.twig b/src/Admin/templates/app/account.html.twig similarity index 98% rename from templates/app/admin/account.html.twig rename to src/Admin/templates/app/account.html.twig index ee97f7c..58d6f25 100644 --- a/templates/app/admin/account.html.twig +++ b/src/Admin/templates/app/account.html.twig @@ -3,7 +3,7 @@ {% block title %}Account{% endblock %} {% block content %} - {{ messagesPartial('partial::alerts') }} + {{ messagesPartial('partial::alerts', {dismissible: true}) }}
We encountered a {{ status }} {{ reason }} error.
+
+ You don't have enough permissions to view this page. You can Sign out
+ and try with a more permissive user.
+ Another reason for this error could be that the page you want to access is not available for authenticated
+ users. You should Sign out before and try again.
+