diff --git a/.gitignore b/.gitignore index 4f38912..7720681 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ .idea +assets/img +assets/app* +assets/mix-manifest.json vendor composer.lock diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4fbce92 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.0.0] - 2021-11-05 +### Added +- PHP 7.4 and PHP 8.0 support. + +### Changed +- The location of the Horizon assets has been changed. The Horizon assets are published into the plugin directory itself (`plugins/vdlp/horizon/assets`). Please note that you need to re-publish the assets when you are deploying your October CMS website or application using the `php artisan horizon:assets` command otherwise the Horizon Dashboard will not be available. +- The `horizon:assets` command can now be used to (re-)publish the Horizon Assets required for the Horizon Dashboard. +- Renamed `PushExampleJobs` to `PushExampleJobsCommand`. + +### Removed +- Removed the dependency of the October CMS module (for headless applications). +- Support for PHP 7.1 + +## [1.0.0] - 2021-06-22 + +- First release of `vdlp/oc-horizon-plugin`. diff --git a/Plugin.php b/Plugin.php index 17491fa..7fade00 100644 --- a/Plugin.php +++ b/Plugin.php @@ -11,15 +11,13 @@ use Illuminate\Notifications\NotificationServiceProvider; use Laravel\Horizon\Horizon; use System\Classes\PluginBase; -use Vdlp\Horizon\Console\PushExampleJobs; +use Vdlp\Horizon\Console\InstallCommand; +use Vdlp\Horizon\Console\PushExampleJobsCommand; use Vdlp\Horizon\ServiceProviders\HorizonServiceProvider; final class Plugin extends PluginBase { - /** - * @var Backend - */ - private $backend; + private Backend $backend; public function __construct($app) { @@ -32,7 +30,7 @@ public function pluginDetails(): array { return [ 'name' => 'Horizon', - 'description' => 'Laravel Horizon integration for OctoberCMS', + 'description' => 'Laravel Horizon integration for October CMS', 'author' => 'Van der Let & Partners', 'icon' => 'icon-area-chart', 'homepage' => 'https://octobercms.com/plugin/vdlp-horizon', @@ -43,8 +41,8 @@ public function boot(): void { config()->set('app.env', $this->app->environment()); - Horizon::auth(static function () { - /** @var User $user */ + Horizon::auth(static function (): bool { + /** @var ?User $user */ $user = AuthManager::instance()->getUser(); if ($user === null) { @@ -60,8 +58,10 @@ public function boot(): void $this->bootNotificationSettings(); if (config('app.debug') === true) { - $this->registerConsoleCommand(PushExampleJobs::class, PushExampleJobs::class); + $this->registerConsoleCommand(PushExampleJobsCommand::class, PushExampleJobsCommand::class); } + + $this->registerConsoleCommand(InstallCommand::class, InstallCommand::class); } public function register(): void @@ -79,32 +79,26 @@ public function registerSchedule($schedule): void public function registerPermissions(): array { - return array_merge( - (array) parent::registerPermissions(), - [ - 'vdlp.horizon.access_dashboard' => [ - 'tab' => 'Horizon', - 'label' => 'Access to the Horizon dashboard', - 'roles' => ['developer'], - ], - ] - ); + return [ + 'vdlp.horizon.access_dashboard' => [ + 'tab' => 'Horizon', + 'label' => 'Access to the Horizon dashboard', + 'roles' => ['developer'], + ], + ]; } public function registerNavigation(): array { - return array_merge( - (array) parent::registerNavigation(), - [ - 'dashboard' => [ - 'label' => 'Horizon', - 'url' => $this->backend->url('vdlp/horizon/dashboard'), - 'iconSvg' => '/plugins/vdlp/horizon/assets/icons/horizon.svg', - 'permissions' => ['vdlp.horizon.access_dashboard'], - 'order' => 500, - ], - ] - ); + return [ + 'dashboard' => [ + 'label' => 'Horizon', + 'url' => $this->backend->url('vdlp/horizon/dashboard'), + 'iconSvg' => '/plugins/vdlp/horizon/assets/icons/horizon.svg', + 'permissions' => ['vdlp.horizon.access_dashboard'], + 'order' => 500, + ], + ]; } public function registerMailTemplates(): array @@ -116,20 +110,20 @@ public function registerMailTemplates(): array private function bootNotificationSettings(): void { - if (config('vdlp.horizon::mail_notifications_enabled', false)) { + if ((bool) config('vdlp.horizon::mail_notifications_enabled', false)) { Horizon::routeMailNotificationsTo( config('vdlp.horizon::mail_notifications_to') ); } - if (config('vdlp.horizon::slack_notifications_enabled', false)) { + if ((bool) config('vdlp.horizon::slack_notifications_enabled', false)) { Horizon::routeSlackNotificationsTo( config('vdlp.horizon::slack_notifications_webhook_url'), config('vdlp.horizon::slack_notifications_channel') ); } - if (config('vdlp.horizon::sms_notifications_enabled', false)) { + if ((bool) config('vdlp.horizon::sms_notifications_enabled', false)) { Horizon::routeSmsNotificationsTo( config('vdlp.horizon::sms_notifications_to') ); diff --git a/README.md b/README.md index d4ad487..53ba141 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ Horizon is 100% open source, so you're free to dig through the source to see exa ## Requirements -* October CMS 1.0 -* Due to its usage of async process signals, Horizon requires **PHP 7.1+**. +* October CMS 1.0 or higher +* PHP 7.4 or PHP 8.0+ * PHP extensions: `ext-pcntl`, `ext-posix` and `ext-redis`. * Supervisor, see [Laravel 6.x supervisor configuration](https://laravel.com/docs/6.x/queues#supervisor-configuration). @@ -53,22 +53,22 @@ You should add the `dont-discover` option to your projects `composer.json` file > IMPORTANT: Make sure the `composer.json` is deployed to your hosting site. This will be parsed by te framework to determine which service providers should be ignored. -## Assets - -* Run the command to publish assets for the Horizon dashboard: +## Assets & Configuration ``` -php artisan vendor:publish --tag horizon-assets --force +php artisan horizon:install ``` -## Configuration +## Update Horizon Assets -* Run the command to publish configuration file `config/horizon.php`: +To update the Horizon Assets you can use the following command: ``` -php artisan vendor:publish --tag horizon-config --force +php artisan horizon:assets ``` +> IMPORTANT: Add the above command to your deployment logic. This way the assets will always be up to date on your staging or production environment. + * Configure Laravel Horizon settings file at `config/horizon.php`, please make sure `use` contains `horizon` (see the configuration snippet below). ``` diff --git a/classes/PathHelper.php b/classes/PathHelper.php index 7cda5a8..75eab04 100644 --- a/classes/PathHelper.php +++ b/classes/PathHelper.php @@ -4,39 +4,24 @@ namespace Vdlp\Horizon\Classes; -use Cms\Classes\Theme; -use Cms\Facades\Cms; +use Illuminate\Contracts\Routing\UrlGenerator; +use Illuminate\Filesystem\Filesystem; +use Throwable; final class PathHelper { - /** - * @var Theme|null - */ - private $theme; + private UrlGenerator $urlGenerator; + private Filesystem $filesystem; - public function __construct() + public function __construct(UrlGenerator $urlGenerator, Filesystem $filesystem) { - $this->theme = Theme::getActiveTheme(); - } - - private function hasActiveTheme(): bool - { - return $this->theme !== null; + $this->urlGenerator = $urlGenerator; + $this->filesystem = $filesystem; } public function getAssetsPath(?string $path = null): string { - if (!$this->hasActiveTheme()) { - return (string) $path; - } - - $assetsPath = $this->theme->getPath( - $this->theme->getDirName() - . DIRECTORY_SEPARATOR - . 'assets' - . DIRECTORY_SEPARATOR - . 'horizon' - ); + $assetsPath = plugins_path('vdlp/horizon/assets'); if ($path !== null) { $assetsPath .= DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR); @@ -47,11 +32,7 @@ public function getAssetsPath(?string $path = null): string public function getAssetsUrlPath(?string $path = null): string { - if (!$this->hasActiveTheme()) { - return (string) $path; - } - - $assetsUrlPath = Cms::url('/themes/' . $this->theme->getDirName() . '/assets/horizon'); + $assetsUrlPath = $this->urlGenerator->asset('plugins/vdlp/horizon/assets'); if ($path !== null) { $assetsUrlPath .= '/' . ltrim($path, '/'); @@ -59,4 +40,16 @@ public function getAssetsUrlPath(?string $path = null): string return $assetsUrlPath; } + + public function assetsAreCurrent(): bool + { + $publishedPath = $this->getAssetsPath('mix-manifest.json'); + $vendorPath = base_path('vendor/laravel/horizon/public/mix-manifest.json'); + + try { + return $this->filesystem->get($publishedPath) === $this->filesystem->get($vendorPath); + } catch (Throwable $exception) { + return false; + } + } } diff --git a/composer.json b/composer.json index 66a3e3c..3550ee3 100644 --- a/composer.json +++ b/composer.json @@ -20,13 +20,12 @@ ], "license": "GPL-2.0-only", "require": { - "php": ">=7.1", + "php": "^7.4 || ^8.0", "ext-pcntl": "*", "ext-posix": "*", "ext-redis": "*", "laravel/horizon": "^3.0", "composer/installers": "^1.0" - }, "archive": { "exclude": [ diff --git a/console/InstallCommand.php b/console/InstallCommand.php new file mode 100644 index 0000000..d861d02 --- /dev/null +++ b/console/InstallCommand.php @@ -0,0 +1,29 @@ +signature = 'horizon:install'; + $this->description = 'Install all of the Horizon resources'; + + parent::__construct(); + } + + public function handle(): void + { + $this->comment('Publishing Horizon Assets...'); + $this->callSilent('vendor:publish', ['--tag' => 'horizon-assets']); + + $this->comment('Publishing Horizon Configuration...'); + $this->callSilent('vendor:publish', ['--tag' => 'horizon-config']); + + $this->info('Horizon scaffolding installed successfully.'); + } +} diff --git a/console/PushExampleJobs.php b/console/PushExampleJobsCommand.php similarity index 91% rename from console/PushExampleJobs.php rename to console/PushExampleJobsCommand.php index 0871ad6..48c99f6 100644 --- a/console/PushExampleJobs.php +++ b/console/PushExampleJobsCommand.php @@ -8,7 +8,7 @@ use October\Rain\Support\Str; use Vdlp\Horizon\Jobs\Example; -final class PushExampleJobs extends Command +final class PushExampleJobsCommand extends Command { public function __construct() { diff --git a/jobs/Example.php b/jobs/Example.php index 5d137a4..95f13a0 100644 --- a/jobs/Example.php +++ b/jobs/Example.php @@ -12,8 +12,10 @@ final class Example implements ShouldQueue { - use Dispatchable, Queueable; - private $fooBar; + use Dispatchable; + use Queueable; + + private string $fooBar; public function __construct(string $fooBar) { @@ -21,7 +23,6 @@ public function __construct(string $fooBar) } /** - * @param LoggerInterface $log * @throws Exception */ public function handle(LoggerInterface $log): void diff --git a/listeners/SendNotification.php b/listeners/SendNotification.php index 9c4f6a9..465e026 100644 --- a/listeners/SendNotification.php +++ b/listeners/SendNotification.php @@ -11,15 +11,8 @@ final class SendNotification { - /** - * @var Horizon\Lock - */ - private $lock; - - /** - * @var Mailer - */ - private $mailer; + private Horizon\Lock $lock; + private Mailer $mailer; public function __construct(Horizon\Lock $lock, Mailer $mailer) { @@ -46,11 +39,14 @@ public function handle(Horizon\Events\LongWaitDetected $event): void 'seconds' => $notification->seconds, ]; - $this->mailer->send('vdlp.horizon::mail.long-wait-detected', $data, static function (Message $message) { - $message - ->to(Horizon\Horizon::$email) - ->subject(config('app.name') . ': Long Queue Wait Detected'); - }); + $this->mailer->send( + 'vdlp.horizon::mail.long-wait-detected', + $data, + static function (Message $message): void { + $message + ->to(Horizon\Horizon::$email) + ->subject(config('app.name') . ': Long Queue Wait Detected'); + }); } } } diff --git a/models/Settings.php b/models/Settings.php index c1b8cb1..af7f499 100644 --- a/models/Settings.php +++ b/models/Settings.php @@ -13,8 +13,8 @@ final class Settings extends Model { public $implement = ['System.Behaviors.SettingsModel']; - public $settingsCode = 'vdlp_horizon_settings'; - public $settingsFields = 'fields.yaml'; + public string $settingsCode = 'vdlp_horizon_settings'; + public string $settingsFields = 'fields.yaml'; public function isMailNotificationEnabled(): bool { diff --git a/routes.php b/routes.php index bb5d000..b636b3a 100644 --- a/routes.php +++ b/routes.php @@ -2,18 +2,42 @@ declare(strict_types=1); +use Illuminate\Contracts\Routing\ResponseFactory; +use Illuminate\Http\Request; use Illuminate\Routing\Router; +use Symfony\Component\HttpFoundation\BinaryFileResponse; use Vdlp\Horizon\Classes\PathHelper; /** @var Router $router */ $router = resolve(Router::class); -$router->group(['middleware' => ['web']], static function () use ($router) { - $router->get('/vendor/horizon/img/horizon.svg', static function () { - $helper = new PathHelper; +/** @var PathHelper $pathHelper */ +$pathHelper = resolve(PathHelper::class); - return response()->download($helper->getAssetsPath('img/horizon.svg'), 'horizon.svg', [ +$router->group(['middleware' => ['web']], static function () use ($router, $pathHelper): void { + $router->get('/vendor/horizon/img/horizon.svg', static function () use ($pathHelper): BinaryFileResponse { + /** @var ResponseFactory $factory */ + $factory = resolve(ResponseFactory::class); + + return $factory->download($pathHelper->getAssetsPath('img/horizon.svg'), 'horizon.svg', [ 'Content-Type' => 'image/svg+xml', ]); }); }); + +if (!$pathHelper->assetsAreCurrent()) { + $router->group([ + 'domain' => config('horizon.domain'), + 'prefix' => config('horizon.path'), + 'middleware' => config('horizon.middleware', 'web'), + ], static function () use ($router): void { + $router->get('/{anything?}', static function (Request $request): string { + if (Laravel\Horizon\Horizon::check($request)) { + return 'The published Horizon assets are not up-to-date with the installed version. ' + . 'To update, run:
php artisan horizon:assets'; + } + + abort(403); + })->where('anything', '(.*)'); + }); +} diff --git a/serviceproviders/HorizonServiceProvider.php b/serviceproviders/HorizonServiceProvider.php index 2556490..a880ab6 100644 --- a/serviceproviders/HorizonServiceProvider.php +++ b/serviceproviders/HorizonServiceProvider.php @@ -4,7 +4,6 @@ namespace Vdlp\Horizon\ServiceProviders; -use Cms\Classes\Theme; use Laravel\Horizon; use Laravel\Horizon\HorizonServiceProvider as HorizonServiceProviderBase; use Vdlp\Horizon\Listeners\SendNotification; @@ -14,7 +13,7 @@ final class HorizonServiceProvider extends HorizonServiceProviderBase public function defineAssetPublishing(): void { $this->publishes([ - HORIZON_PATH . '/public' => $this->getAssetPath(), + HORIZON_PATH . '/public' => plugins_path('vdlp/horizon/assets'), ], 'horizon-assets'); } @@ -31,20 +30,4 @@ protected function registerResources(): void { $this->loadViewsFrom(plugins_path('vdlp/horizon/views'), 'horizon'); } - - private function getAssetPath(): string - { - /** @var Theme $theme */ - $theme = Theme::getActiveTheme(); - - if ($theme === null) { - return ''; - } - - return $theme->getPath(implode(DIRECTORY_SEPARATOR, [ - $theme->getDirName(), - 'assets', - 'horizon', - ])); - } } diff --git a/updates/version.yaml b/updates/version.yaml index d1a96ae..12d7820 100644 --- a/updates/version.yaml +++ b/updates/version.yaml @@ -1 +1,2 @@ 1.0.0: "First version of Vdlp.Horizon" +2.0.0: "Removed the dependency of the October CMS module. Moved Horizon Assets. See CHANGELOG for more details." diff --git a/views/layout.blade.php b/views/layout.blade.php index 5dac5eb..b412e11 100644 --- a/views/layout.blade.php +++ b/views/layout.blade.php @@ -1,4 +1,4 @@ - +