54 |
55 | @endif
--------------------------------------------------------------------------------
/src/MisterPhilip/MaintenanceMode/Console/Commands/StartMaintenanceCommand.php:
--------------------------------------------------------------------------------
1 | verifyViewExists($this->option('view'))) {
42 | $message = "Aborting due to missing view. Your application will remain ";
43 | $message .= (file_exists($this->laravel->storagePath().'/framework/down')) ? "down from a previous down command." : "up.";
44 | $this->info($message);
45 | return false;
46 | }
47 |
48 | // Call the original method, writing to the down file & console output
49 | parent::handle();
50 |
51 | // Fire an event
52 | $payload = $this->getDownFilePayload();
53 | Event::dispatch(new MaintenanceModeEnabled($payload['time'], $payload['message'], $payload['view'], $payload['retry'], $payload['allowed']));
54 | return true;
55 | }
56 |
57 | /**
58 | * Get the payload to be placed in the "down" file.
59 | *
60 | * @return array
61 | */
62 | protected function getDownFilePayload()
63 | {
64 | // Get the Laravel file data & add ours (selected view)
65 | $data = parent::getDownFilePayload();
66 | $data['view'] = $this->option('view');
67 |
68 | if (!isset($data['allowed'])) {
69 | $data['allowed'] = $this->option('allow');
70 | }
71 | return $data;
72 | }
73 |
74 | /**
75 | * Verify the view exists, and if not then prompt if they want to continue with the default
76 | *
77 | * @param $view
78 | * @return bool
79 | */
80 | protected function verifyViewExists($view)
81 | {
82 | // Verify the user passed us a correct view
83 | if ($view && !$this->laravel->view->exists($view)) {
84 | $this->laravel->config->get("maintenancemode.view");
85 | $this->error("The view \"{$view}\" does not exist. If you continue, the view from the configuration file \"{$this->laravel->config->get("maintenancemode.view")}\" will be used until \"{$view}\" is found.");
86 | return $this->confirm('Do you wish to continue? [y|N]');
87 | }
88 | return true;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/config/maintenancemode.php:
--------------------------------------------------------------------------------
1 | 'maintenancemode::app-down',
11 |
12 | /**
13 | * Include CSS styles with the optional notification view
14 | *
15 | * @var boolean
16 | */
17 | 'notification-styles' => true,
18 |
19 | /**
20 | * Configuration values for injecting variables into the views
21 | *
22 | * Variables available:
23 | * [prefix]Enabled - If maintenance mode is currently enabled
24 | * [prefix]Timestamp - The timestamp of when maintenance mode was enabled
25 | * [prefix]Message - The message passed in with the command, if applicable
26 | *
27 | * @var array
28 | */
29 | 'inject' => [
30 |
31 | /**
32 | * Make variables accessible in all views
33 | *
34 | * If set to false, only the maintenance page will have access to these variables
35 | *
36 | * @var boolean
37 | */
38 | 'global' => true,
39 |
40 | /**
41 | * Prefix the variables to prevent name collisions
42 | *
43 | * @var string
44 | */
45 | 'prefix' => 'MaintenanceMode',
46 | ],
47 |
48 | /**
49 | * The path to the language file to use
50 | *
51 | * @var string
52 | */
53 | 'language-path' => 'maintenancemode::defaults',
54 |
55 | /**
56 | * An array of IP address that will never see the maintenance page
57 | *
58 | * To be used in conjunction with the IPWhitelist exemption class
59 | *
60 | * @var array
61 | */
62 | 'exempt-ips' => ['127.0.0.1'],
63 |
64 | /**
65 | * Use proxies to get the user's IP address
66 | *
67 | * See: http://symfony.com/doc/current/components/http_foundation/trusting_proxies.html
68 | *
69 | * @var boolean
70 | */
71 | 'exempt-ips-proxy' => false,
72 |
73 | /**
74 | * An array of environments that will never show the maintenance page
75 | *
76 | * To be used in conjunction with the EnvironmentWhitelist exemption class
77 | *
78 | * @var array
79 | */
80 | 'exempt-environments' => ['local'],
81 |
82 | /**
83 | * A list of exemption classes to execute
84 | *
85 | * Each of these classes should extend
86 | * \MisterPhilip\MaintenanceMode\Exemptions\MaintenanceModeExemption
87 | * and contain an "isExempt" method that returns a boolean value on
88 | * if the user should or should not see the maintenance page.
89 | *
90 | * To create your own, simply add it to the list below. For more information and examples, see:
91 | * https://github.com/MisterPhilip/maintenance-mode/blob/master/examples/exemptions
92 | *
93 | * @var array
94 | */
95 | 'exemptions' => [
96 |
97 | /*
98 | * IPWhitelist exempts users with the IPs matched in the "exempt-ips" config
99 | */
100 | MisterPhilip\MaintenanceMode\Exemptions\IPWhitelist::class,
101 |
102 | /*
103 | * EnvironmentWhitelist exempts installations with environments matched in the "exempt-environments" config
104 | */
105 | MisterPhilip\MaintenanceMode\Exemptions\EnvironmentWhitelist::class,
106 | ],
107 | ];
108 |
--------------------------------------------------------------------------------
/src/MisterPhilip/MaintenanceMode/Http/Middleware/CheckForMaintenanceMode.php:
--------------------------------------------------------------------------------
1 | inject = $this->app['config']->get('maintenancemode.inject.globally', true);
60 | $this->prefix = $this->app['config']->get('maintenancemode.inject.prefix', 'MaintenanceMode');
61 | $this->language = $this->app['config']->get('maintenancemode.language-path', 'maintenancemode::defaults');
62 |
63 | // Setup value array
64 | $info = [
65 | 'Enabled' => false,
66 | 'Timestamp' => time(),
67 | 'Message' => $this->app['translator']->get($this->language . '.message'),
68 | 'View' => null,
69 | 'Retry' => 60,
70 | ];
71 |
72 | // Are we down?
73 | if ($this->app->isDownForMaintenance()) {
74 | // Yes. :(
75 | Carbon::setLocale(App::getLocale());
76 |
77 | $info['Enabled'] = true;
78 |
79 | $data = ["message" => null, "view" => $this->app['config']->get('maintenancemode.view'), "retry" => null, "time" => Carbon::now()->getTimestamp()];
80 |
81 | $data = array_merge($data, json_decode(file_get_contents($this->app->storagePath().'/framework/down'), true));
82 |
83 | // Update the array with data from down file
84 | $info['Timestamp'] = Carbon::createFromTimestamp($data['time']);
85 |
86 | if (isset($data['message']) && $data['message']) {
87 | $info['Message'] = $data['message'];
88 | }
89 | if (isset($data['view']) && $data['view']) {
90 | $info['View'] = $data['view'];
91 | }
92 | if (isset($data['retry']) && intval($data['retry'], 10) !== 0) {
93 | $info['Retry'] = intval($data['retry'], 10);
94 | }
95 |
96 | // Inject the information into the views before the exception
97 | $this->injectIntoViews($info);
98 |
99 | if (!$this->isExempt($data, $request)) {
100 | // The user isn't exempt, so show them the error page
101 | throw new MaintenanceModeException($data['time'], $data['retry'], $data['message'], $data['view']);
102 | }
103 | } else {
104 | // Inject the default information into the views
105 | $this->injectIntoViews($info);
106 | }
107 |
108 | return $next($request);
109 | }
110 |
111 | /**
112 | * Inject the prefixed data into the views
113 | *
114 | * @param $info
115 | * @return null
116 | */
117 | protected function injectIntoViews($info)
118 | {
119 | if ($this->inject) {
120 | // Inject the information globally (to prevent the need of isset)
121 | foreach ($info as $key => $value) {
122 | $this->app['view']->share($this->prefix . $key, $value);
123 | }
124 | }
125 | }
126 |
127 | /**
128 | * Check if a user is exempt from the maintenance mode page
129 | *
130 | * @param $data
131 | * @param $request
132 | *
133 | * @return bool
134 | * @throws ExemptionDoesNotExist
135 | * @throws InvalidExemption
136 | */
137 | protected function isExempt($data, $request)
138 | {
139 | // Grab all of the exemption classes to create/execute against
140 | $exemptions = $this->app['config']->get('maintenancemode.exemptions', []);
141 | foreach ($exemptions as $className) {
142 | if (class_exists($className)) {
143 | $exemption = new $className($this->app);
144 | if ($exemption instanceof MaintenanceModeExemption) {
145 | // Run the exemption check
146 | if ($exemption->isExempt()) {
147 | return true;
148 | }
149 | } else {
150 | // Class doesn't match what we're looking for
151 | throw new InvalidExemption($this->app['translator']->get($this->language . '.exceptions.invalid', ['class' => $className]));
152 | }
153 | } else {
154 | // Where's Waldo?
155 | throw new ExemptionDoesNotExist($this->app['translator']->get($this->language . '.exceptions.missing', ['class' => $className]));
156 | }
157 | }
158 |
159 | // Check for IP via the "allow" option
160 | if (isset($data['allowed']) && IpUtils::checkIp($request->ip(), (array) $data['allowed'])) {
161 | return true;
162 | }
163 |
164 | return false;
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Enhanced Laravel Maintenance Mode
2 |
3 | This package is a drop-in replacement for Laravel's maintenance mode. The following versions are supported:
4 |
5 | * Laravel 5.0 - 5.2, please use the [1.0 branch](https://github.com/MisterPhilip/maintenance-mode/tree/1.0).
6 | * There are currently no plans to support Laravel 5.3 - 5.4.
7 | * Laravel 5.5 - 5.8, please use the [1.3 branch](https://github.com/MisterPhilip/maintenance-mode/tree/1.3)
8 | * Laravel 6.0, please use the [2.0 branch](https://github.com/MisterPhilip/maintenance-mode/tree/2.0). Note that 5.5+
9 | _can_ work with the 2.0 branch, but it is not guaranteed to be backwards compatible.
10 |
11 |
12 | Features include:
13 | - Allowing custom maintenance messages to be shown to users
14 | - Including a timestamp of when the application went down
15 | - Exempting select users via custom exemption classes
16 | - Firing an event for when the application goes down
17 | - Dynamically selecting the view to be shown on down command
18 |
19 | ## Table of Contents
20 |
21 | 1. [Installation](#installation)
22 | 1. [Changes from 1.0](#changes-from-1.0)
23 | 1. [Usage](#usage)
24 | 1. [Configuration](#configuration)
25 | 1. [Default Configuration](#default-configuration)
26 | 1. [Overriding Defaults](#overriding-defaults)
27 | 1. [Exemptions](#exemptions)
28 | 1. [Default Exemptions](#default-exemptions)
29 | 1. [IP Whitelist](#ip-whitelist)
30 | 1. [Environment Whitelist](#environment-whitelist)
31 | 1. [Creating a New Exemption](#creating-a-new-exemption)
32 | 1. [Views](#views)
33 | 1. [Application Down](#application-down)
34 | 1. [Maintenance Notification](#maintenance-notification)
35 | 1. [Events](#events)
36 |
37 | ## Installation
38 |
39 | Run the following command to install the latest:
40 | ```bash
41 | $ composer require "misterphilip/maintenance-mode:~2.0"
42 | ```
43 |
44 | Or, if you prefer installing it manually, within `composer.json` add the following line to the end of the `require` section:
45 |
46 | ```json
47 | "misterphilip/maintenance-mode": "~2.0"
48 | ```
49 |
50 | And then run the Composer install command:
51 |
52 | ```bash
53 | $ composer install
54 | ```
55 |
56 | Laravel _should_ automatically install the Service Providers, but verify they exist within the `config/app.php` file.
57 | If they do not, add `MisterPhilip\MaintenanceMode\MaintenanceModeServiceProvider::class,` and
58 | `MisterPhilip\MaintenanceMode\MaintenanceCommandServiceProvider::class,` to the end of the
59 | `$providers` array in your `config/app.php`:
60 |
61 | ```php
62 | 'providers' => [
63 |
64 | /*
65 | * Application Service Providers...
66 | */
67 | App\Providers\AppServiceProvider::class,
68 | App\Providers\EventServiceProvider::class,
69 | App\Providers\RouteServiceProvider::class,
70 |
71 | ...
72 |
73 | MisterPhilip\MaintenanceMode\MaintenanceModeServiceProvider::class,
74 | MisterPhilip\MaintenanceMode\MaintenanceCommandServiceProvider::class,
75 | ],
76 | ```
77 |
78 | Finally, in `app/Http/Kernel.php` replace the current MaintenanceMode Middleware
79 |
80 | ```php
81 | \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
82 | ```
83 |
84 | with
85 |
86 | ```php
87 | \MisterPhilip\MaintenanceMode\Http\Middleware\CheckForMaintenanceMode::class,
88 | ```
89 |
90 |
91 | ## Changes from 1.0
92 |
93 | Since Laravel 5.3, messages are now allowed in the default `artisan down` command, as well as adding an option for the
94 | `Retry-After` HTTP header. Because of this it should be noted that the syntax to call the `artisan down` command has
95 | changed from the 1.0 branch to better match Laravel's default command.
96 |
97 | Additionally, we've changed the
98 | `MaintenanceModeEnabled` event to no longer use the `info` property, but instead have separate properties for each piece
99 | of information. See more in the [events section](#events).
100 |
101 | ## Usage
102 |
103 | This package overwrites the default `artisan down` command, with more options:
104 |
105 | ```bash
106 | $ php artisan down [--message=MESSAGE] [--retry=RETRY] [--view=VIEW] [--allow*=ALLOW]
107 | ```
108 |
109 | For example,
110 |
111 | ```bash
112 | $ php artisan down --message="We're currently upgrading our system! Please check back later."
113 | ```
114 |
115 | would show users a message of "We're doing some routine maintenance! Be back soon!". If you don't pass in a
116 | message, the default "We're currently working on the site, please try again later" will
117 | display to the users. Of course this default is configurable via a language string.
118 |
119 | You can also change the view that is shown each time you run the `artisan down` command via the `--view=[VIEW]` option:
120 |
121 | ```bash
122 | $ php artisan down --message="Be back in a few!" --view="errors/maintenance"
123 | ```
124 |
125 | And of course you can use the default `--retry` option as well:
126 |
127 | ```bash
128 | $ php artisan down --message="Be back in a few!" --view="errors/maintenance" --retry=60
129 | ```
130 |
131 | To bring your application back online, run the normal app up command:
132 |
133 | ```bash
134 | $ php artisan up
135 | ```
136 |
137 | **NOTE:** by default, two [exemptions](#exemptions) enabled, which means that if you run
138 | your development server locally (127.0.0.1) _or_ you have `APP_ENV=local` in your `.env` file, you will _not_ see the
139 | maintenance page. You can remove these exemptions via the [configuration](#overriding-defaults).
140 |
141 | ## Configuration
142 |
143 | This package is a drop-in replacement for the default maintenance mode provided with Laravel 5. This means
144 | that you do not have to do any configuration out-of-the-box. However, if you'd like to tweak some of the
145 | settings, there are a number of configuration values that are available to make this package a better fit
146 | for your application.
147 |
148 | ### Default Configuration
149 |
150 | Below are the default configuration options and a short description on each. Don't worry, all of this
151 | information is within the configuration file too!
152 |
153 | - `view` (string)
154 | - The view to show to users when maintenance mode is currently enabled. This can be temporarily overridden when
155 | the command is called via the `--view` option
156 | - Defaults to `maintenancemode::app-down`
157 | - `notification-styles` (boolean)
158 | - Include CSS styling with the optional [maintenance notification](#maintenance-notification) view
159 | - Defaults to `true`
160 | - `inject.global` (boolean)
161 | - Enable or disable global visibility to maintenance mode variables (accessible in all views)
162 | - Defaults to `true`
163 | - `inject.prefix` (string)
164 | - Prefix the maintenance mode variables to prevent view variable name collisions
165 | - Defaults to `MaintenanceMode`
166 | - `language-path` (string)
167 | - The path to the maintenance mode language strings.
168 | - Defaults to `maintenancemode::defaults`
169 | - `exempt-ips` (string array)
170 | - An array of IP address that will always be exempt from the application down page
171 | - Defaults to `['127.0.0.1']`
172 | - `exempt-ips-proxy` (boolean)
173 | - Use [proxies](http://symfony.com/doc/current/components/http_foundation/trusting_proxies.html)
174 | to get the user's IP address
175 | - Defaults to `false`
176 | - `exempt-environments` (string array)
177 | - An array of environment names that will always be exempt from the application down page
178 | - Defaults to `['local']`
179 | - `exemptions` (string array)
180 | - A list of the exemption classes to execute. *See [Exemptions](#exemptions)*
181 | - Defaults to:
182 | ```php
183 | '\MisterPhilip\MaintenanceMode\Exemptions\IPWhitelist',
184 | '\MisterPhilip\MaintenanceMode\Exemptions\EnvironmentWhitelist',
185 | ```
186 |
187 | ### Overriding Defaults
188 |
189 | If you need to override the default configuration values, run the following command:
190 |
191 | ```bash
192 | $ php artisan vendor:publish --provider="MisterPhilip\MaintenanceMode\MaintenanceModeServiceProvider" --tag="config"
193 | ```
194 |
195 | Now you can edit the values at `config/maintenancemode.php`.
196 |
197 | ## Exemptions
198 |
199 | Exemptions allow for select users to continue to use the application like normal based on a specific
200 | set of rules. Each of these rule sets are defined via a class which is then executed against.
201 |
202 | ### Default Exemptions
203 |
204 | By default, an IP whitelist and an application environment whitelist are included with this package
205 | to get you off the ground running. Additionally, more examples are provided for various types of
206 | exemptions that might be useful to your application.
207 |
208 | ##### IP Whitelist
209 |
210 | This exemption allows you to check the user's IP address against a whitelist. This is useful for
211 | always including your office IP(s) so that your staff doesn't see the maintenance page. This is similar
212 | to the `allow` option that is offered in Laravel 5.6+ (and is backported to 5.5 with this package), however
213 | the `allow` option requires you to pass the IPs every time, vs. this method allows for you to have IPs stored
214 | in the configuration. Both methods can be used with each other, e.g. a static internal network should always be
215 | allowed via the config, while an additional IP for a vendor or remote employee can temporarily be added via the
216 | `allow` option.
217 |
218 | Configuration values included with this exemption are:
219 |
220 | - `exempt-ips` - An array of IP addresses that will not see the maintenance page
221 | - `exempt-ips-proxy` - Set to `true` if you have IP proxies setup
222 |
223 | ##### Environment Whitelist
224 |
225 | This exemption allows you to check if the current environment matches against a whitelist. This is
226 | useful for local development where you might not want to see the maintenance page.
227 |
228 | Configuration values included with this exemption are:
229 |
230 | - `exempt-environments` - An array of environments that will not display the maintenance page
231 |
232 | ### Creating a new exemption
233 |
234 | Setting up a new exemption is simple:
235 |
236 | 1. Create a new class and extend `MisterPhilip\MaintenanceMode\Exemptions\MaintenanceModeExemption`.
237 | You might consider creating these files in `app\Exemptions` or `app\Infrastructure\Maintenance`, but
238 | you're free to place them where you want.
239 | 2. This class must include an `isExempt` method. This method should return `true` if the user should
240 | not see the maintenance page. If this returns `false`, it indicates that the user does not match
241 | your ruleset and other exceptions should be checked.
242 | 3. Add the full class name to the `exemptions` array in the configuration file.
243 |
244 | Below is an template to use for a new exemption class `SampleExemption`:
245 |
246 | ```php
247 | [
335 | 'App\Listeners\UpdateStatusPageMaintenanceStarted',
336 | ],
337 | 'MisterPhilip\MaintenanceMode\Events\MaintenanceModeDisabled' => [
338 | 'App\Listeners\UpdateStatusPageMaintenanceEnded',
339 | ],
340 | // ..
341 | ];
342 | ```
343 |
344 | The original `message`, `time` the app went down, `retry` length, `view`, and `allowed` are all properties available on both events.
345 |
--------------------------------------------------------------------------------