├── .gitignore ├── examples ├── screenshots │ ├── maintenance-mode-page.png │ └── maintenance-mode-notification.png ├── exemptions │ ├── LoggedOut.php │ └── UserGroupWhitelist.php └── events │ ├── LogMaintenanceEnded.php │ ├── LogMaintenanceStarted.php │ ├── UpdateStatusPageMaintenanceEnded.php │ └── UpdateStatusPageMaintenanceStarted.php ├── src ├── MisterPhilip │ └── MaintenanceMode │ │ ├── Events │ │ ├── MaintenanceModeEnabled.php │ │ ├── MaintenanceModeDisabled.php │ │ └── MaintenanceModeChanged.php │ │ ├── Exceptions │ │ ├── ExemptionDoesNotExist.php │ │ ├── InvalidExemption.php │ │ └── MaintenanceModeException.php │ │ ├── Exemptions │ │ ├── MaintenanceModeExemption.php │ │ ├── EnvironmentWhitelist.php │ │ └── IPWhitelist.php │ │ ├── MaintenanceCommandServiceProvider.php │ │ ├── Console │ │ └── Commands │ │ │ ├── EndMaintenanceCommand.php │ │ │ └── StartMaintenanceCommand.php │ │ ├── MaintenanceModeServiceProvider.php │ │ └── Http │ │ └── Middleware │ │ └── CheckForMaintenanceMode.php ├── lang │ ├── en │ │ └── defaults.php │ ├── nl │ │ └── defaults.php │ └── fa │ │ └── defaults.php ├── views │ ├── app-down.blade.php │ └── notification.blade.php └── config │ └── maintenancemode.php ├── composer.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store -------------------------------------------------------------------------------- /examples/screenshots/maintenance-mode-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awjudd/maintenance-mode/HEAD/examples/screenshots/maintenance-mode-page.png -------------------------------------------------------------------------------- /examples/screenshots/maintenance-mode-notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awjudd/maintenance-mode/HEAD/examples/screenshots/maintenance-mode-notification.png -------------------------------------------------------------------------------- /src/MisterPhilip/MaintenanceMode/Events/MaintenanceModeEnabled.php: -------------------------------------------------------------------------------- 1 | app['auth']->guest(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/events/LogMaintenanceEnded.php: -------------------------------------------------------------------------------- 1 | time; 19 | Log::notice("Maintenance Mode Disabled, total downtime was " . Carbon::now()->diffForHumans($startingTime, true, true, 6)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/events/LogMaintenanceStarted.php: -------------------------------------------------------------------------------- 1 | message) && $maintenanceMode->message !== "") { 19 | $logMessage .= " with a custom message: \"" . $maintenanceMode->message . "\""; 20 | } 21 | Log::alert($logMessage); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/MisterPhilip/MaintenanceMode/Exemptions/MaintenanceModeExemption.php: -------------------------------------------------------------------------------- 1 | app = $app; 25 | } 26 | 27 | /** 28 | * Check to see if the user is exempt from the maintenance page 29 | * 30 | * @return bool True is the user should not see the exemption page 31 | */ 32 | abstract public function isExempt(); 33 | } 34 | -------------------------------------------------------------------------------- /src/MisterPhilip/MaintenanceMode/Exemptions/EnvironmentWhitelist.php: -------------------------------------------------------------------------------- 1 | app['config']->get('maintenancemode.exempt-environments', []); 24 | 25 | if (is_array($ignoreEnvs) && in_array($this->app->environment(), $ignoreEnvs)) { 26 | return true; 27 | } 28 | 29 | // We did not have a match 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/lang/en/defaults.php: -------------------------------------------------------------------------------- 1 | 'Maintenance Mode', 10 | 11 | /** 12 | * Default application down message, shown on the maintenance page 13 | * 14 | * @var string 15 | */ 16 | 'message' => 'We\'re currently working on the site, please try again later!', 17 | 18 | /** 19 | * Last updated string, shown on the maintenance page 20 | * 21 | * @var string 22 | */ 23 | 'last-updated' => 'This message was last updated :timestamp', 24 | 25 | /** 26 | * Exception messages 27 | * 28 | * @var array 29 | */ 30 | 'exceptions' => [ 31 | 'invalid' => 'Class :class does not extend \MisterPhilip\MaintenanceMode\Exemptions\MaintenanceModeExemption', 32 | 'missing' => 'Class :class does not exist', 33 | ] 34 | ]; 35 | -------------------------------------------------------------------------------- /src/lang/nl/defaults.php: -------------------------------------------------------------------------------- 1 | 'Onderhoudsmodus', 10 | 11 | /** 12 | * Default application down message, shown on the maintenance page 13 | * 14 | * @var string 15 | */ 16 | 'message' => 'Op dit moment wordt aan de website gewerkt, probeer het a.u.b. straks nog eens', 17 | 18 | /** 19 | * Last updated string, shown on the maintenance page 20 | * 21 | * @var string 22 | */ 23 | 'last-updated' => 'Laatste update was :timestamp', 24 | 25 | /** 26 | * Exception messages 27 | * 28 | * @var array 29 | */ 30 | 'exceptions' => [ 31 | 'invalid' => 'Klasse :class extend \MisterPhilip\MaintenanceMode\Exemptions\MaintenanceModeExemption niet', 32 | 'missing' => 'Klasse :class bestaat niet', 33 | ] 34 | ]; 35 | -------------------------------------------------------------------------------- /src/lang/fa/defaults.php: -------------------------------------------------------------------------------- 1 | 'حالت تعمیر و نگهداری', 10 | 11 | /** 12 | * Default application down message, shown on the maintenance page 13 | * 14 | * @var string 15 | */ 16 | 'message' => 'ما در حال کاربر روی سایت هستیم، خواهشمند است کمی دیگر مراجعه فرمایید.', 17 | 18 | /** 19 | * Last updated string, shown on the maintenance page 20 | * 21 | * @var string 22 | */ 23 | 'last-updated' => 'آخرین زمان بروز رسانی :timestamp می باشد.', 24 | 25 | /** 26 | * Exception messages 27 | * 28 | * @var array 29 | */ 30 | 'exceptions' => [ 31 | 'invalid' => 'کلاس :class بر روی کلاس \MisterPhilip\MaintenanceMode\Exemptions\MaintenanceModeExemption توسعه داده نشده است.', 32 | 'missing' => 'کلاس :class وجود ندارد.', 33 | ] 34 | ]; 35 | -------------------------------------------------------------------------------- /src/MisterPhilip/MaintenanceMode/Exemptions/IPWhitelist.php: -------------------------------------------------------------------------------- 1 | app['config']->get('maintenancemode.exempt-ips', []); 25 | $useProxy = $this->app['config']->get('maintenancemode.exempt-ips-proxy', false); 26 | $userIP = $this->app['request']->getClientIp($useProxy); 27 | 28 | if (is_array($authorizedIPs) && in_array($userIP, $authorizedIPs)) { 29 | return true; 30 | } 31 | 32 | // We did not have a match 33 | return false; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "misterphilip/maintenance-mode", 3 | "description": "An enhanced drop-in replacement for Laravel's maintenance mode", 4 | "keywords": ["laravel", "laravel6", "l6", "laravel5" ,"l5", "maintenance"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Philip Lawrence", 9 | "email": "philip@misterphilip.com", 10 | "homepage": "http://misterphilip.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=7.0.0", 15 | "illuminate/support": ">=5.5", 16 | "nesbot/carbon": "^1.24 || ^2.0" 17 | }, 18 | "autoload": { 19 | "psr-0": { 20 | "MisterPhilip\\MaintenanceMode": "src/" 21 | } 22 | }, 23 | "extra": { 24 | "laravel": { 25 | "providers": [ 26 | "MisterPhilip\\MaintenanceMode\\MaintenanceModeServiceProvider", 27 | "MisterPhilip\\MaintenanceMode\\MaintenanceCommandServiceProvider" 28 | ] 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Philip Lawrence 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /examples/events/UpdateStatusPageMaintenanceEnded.php: -------------------------------------------------------------------------------- 1 | config('statuspage.key'), 25 | ]); 26 | 27 | StatusPage\Component::on($server) 28 | ->findById(config('statuspage.component')) 29 | ->setStatus(Component::OPERATIONAL) 30 | ->update(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/events/UpdateStatusPageMaintenanceStarted.php: -------------------------------------------------------------------------------- 1 | config('statuspage.key'), 25 | ]); 26 | 27 | StatusPage\Component::on($server) 28 | ->findById(config('statuspage.component')) 29 | ->setStatus(Component::UNDER_MAINTENANCE) 30 | ->update(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/MisterPhilip/MaintenanceMode/MaintenanceCommandServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton('command.down', function ($app) { 27 | return new StartMaintenanceCommand(); 28 | }); 29 | 30 | 31 | $this->app->singleton('command.up', function ($app) { 32 | return new EndMaintenanceCommand(); 33 | }); 34 | $this->commands(['command.down', 'command.up']); 35 | } 36 | 37 | /** 38 | * Get the services provided by the provider. 39 | * 40 | * @return array 41 | */ 42 | public function provides() 43 | { 44 | return ['command.down', 'command.up']; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/MisterPhilip/MaintenanceMode/Events/MaintenanceModeChanged.php: -------------------------------------------------------------------------------- 1 | time = Carbon::createFromTimestamp($time); 57 | $this->message = $message; 58 | $this->view = $view; 59 | $this->retry = $retry; 60 | $this->allowed = $allowed; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/views/app-down.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ Lang::get(Config::get('maintenancemode.language-path', 'maintenancemode::defaults.') . '.title') }} 9 | 38 | 39 | 40 |
41 |

{{ ${Config::get('maintenancemode.inject.prefix').'Message'} }}

42 |

{{ Lang::get(Config::get('maintenancemode.language-path', 'maintenancemode::defaults.') . '.last-updated', ['timestamp' => ${Config::get('maintenancemode.inject.prefix').'Timestamp'}->diffForHumans()]) }}

43 |
44 | 45 | -------------------------------------------------------------------------------- /src/MisterPhilip/MaintenanceMode/Console/Commands/EndMaintenanceCommand.php: -------------------------------------------------------------------------------- 1 | laravel->storagePath().'/framework/down')) { 26 | $this->info('Application is already live.'); 27 | return false; 28 | } 29 | 30 | // Grab the data 31 | $data = @json_decode(file_get_contents($this->laravel->storagePath().'/framework/down'), true); 32 | if (!isset($data) || !is_array($data)) { 33 | $data = []; 34 | } 35 | $data = array_merge(["time" => null, "message" => null, "view" => null, "retry" => null], $data); 36 | 37 | // Remove the down file 38 | @unlink(storage_path('framework/down')); 39 | 40 | // Show how long the application was down for 41 | $startingTime = Carbon::createFromTimestamp($data['time']); 42 | $this->info("The application is now live! Total downtime: " . Carbon::now()->diffForHumans($startingTime, true, true, 6)); 43 | 44 | // Fire the event 45 | Event::dispatch(new MaintenanceModeDisabled($data['time'], $data['message'], $data['view'], $data['retry'])); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/exemptions/UserGroupWhitelist.php: -------------------------------------------------------------------------------- 1 | [ 30 | * 'user-groups' => [ 1, 2, 3 ], 31 | * ], 32 | * ]; 33 | */ 34 | 35 | $exemptGroups = $this->app['config']->get('maintenancemode.exemptions.user-groups', []); 36 | $currentUser = $this->app['auth']->user(); 37 | if (is_array($exemptGroups) && $currentUser !== null) { 38 | // Grab the current user group IDs 39 | $currentUserGroups = $currentUser->groups->modelKeys(); 40 | 41 | // Check to see if the user is within this group 42 | if ((is_array($currentUserGroups) && count(array_intersect($currentUserGroups, $exemptGroups))) || 43 | !is_array($currentUserGroups) && in_array($currentUserGroups, $exemptGroups)) { 44 | // The user matched the whitelist, they will NOT see the maintenance page 45 | return true; 46 | } 47 | } 48 | 49 | // The current user did not match the whitelist, therefore they MIGHT see the maintenance page (depending on other exemptions) 50 | return false; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/MisterPhilip/MaintenanceMode/Exceptions/MaintenanceModeException.php: -------------------------------------------------------------------------------- 1 | view = $view; 32 | } 33 | 34 | /** 35 | * Build a response for Laravel to show 36 | * 37 | * @param \Illuminate\Http\Request $request 38 | * @return \Illuminate\Http\Response 39 | */ 40 | public function toResponse($request) 41 | { 42 | $headers = array(); 43 | if ($this->retryAfter) { 44 | $headers = array('Retry-After' => $this->retryAfter); 45 | } 46 | 47 | // Figure out what view to show them 48 | // @TODO: fallback to the default 503 page (laravel/framework/src/Illuminate/Foundation/Exceptions/views/503.blade.php) 49 | $view = view()->first([$this->view, config("maintenancemode.view"), "errors/503", "maintenancemode::app-down"], []); 50 | 51 | return response($view, 503)->withHeaders($headers); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/MisterPhilip/MaintenanceMode/MaintenanceModeServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadViews(); 26 | $this->loadTranslations(); 27 | $this->loadConfig(); 28 | } 29 | 30 | /** 31 | * Register the service provider. 32 | * 33 | * @return void 34 | */ 35 | public function register() 36 | { 37 | $this->mergeConfigFrom( 38 | $this->getRelativePath('config/maintenancemode.php'), 39 | 'maintenancemode' 40 | ); 41 | } 42 | 43 | /** 44 | * Register our view files 45 | * 46 | * @return void 47 | */ 48 | protected function loadViews() 49 | { 50 | $this->loadViewsFrom($this->getRelativePath('views'), 'maintenancemode'); 51 | 52 | $this->publishes([ 53 | $this->getRelativePath('views') => base_path('resources/views/vendor/maintenancemode'), 54 | ], 'views'); 55 | } 56 | 57 | /** 58 | * Register our translations 59 | * 60 | * @return void 61 | */ 62 | protected function loadTranslations() 63 | { 64 | $this->loadTranslationsFrom($this->getRelativePath('lang'), 'maintenancemode'); 65 | } 66 | 67 | /** 68 | * Register our config file 69 | * 70 | * @return void 71 | */ 72 | protected function loadConfig() 73 | { 74 | $this->publishes([ 75 | $this->getRelativePath('config/maintenancemode.php') => config_path('maintenancemode.php'), 76 | ], 'config'); 77 | } 78 | 79 | /** 80 | * Get the full path, relative to the package 81 | * 82 | * @param $path 83 | * 84 | * @return string 85 | */ 86 | protected function getRelativePath($path = '') 87 | { 88 | return __DIR__ . '/../../' . $path; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/views/notification.blade.php: -------------------------------------------------------------------------------- 1 | {{-- 2 | This is a simple notification bar to users who are exempt from the maintenance page, telling them that a 3 | maintenance period is going on. You should place this within your main template(s) via a call to: 4 | 5 | @include('maintenancemode::notification') 6 | --}} 7 | 8 | @if(isset(${Config::get('maintenancemode.inject.prefix').'Enabled'}) && 9 | ${Config::get('maintenancemode.inject.prefix').'Enabled'} == true) 10 | 11 | @if(Config::get('maintenancemode.notification-styles', true)) 12 | 31 | @endif 32 | 33 | 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 | --------------------------------------------------------------------------------