Laravel maintenance mode on specific subdomain - laravel

I know that you can except some URIs of your main app like if you want to except example.com/page, you can just simply add it to the CheckForMaintenanceMode.php, like this:
In app/Http/Middleware/CheckForMaintenanceMode.php
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode as Middleware;
class CheckForMaintenanceMode extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* #var array
*/
protected $except = [
'/page'
];
}
Now, my app has a couple subdomains using one app; I have a subdomain for my main app: app.example.com, a subdomain for my API endpoints: api.example.com and the main website: www.example.com
How can I possibly except the specific subdomain instead of URI for maintenance mode? Like having api.example.com and app.example.com in maintenance mode but not the main website www.example.com?
I'm trying to figure out it on my own and even make my own middleware just to do this, but is it possible to do this using the built-in maintenance mode of laravel with php artisan:down?
Something like:
// app.example.com and api.example.com is in maintenance mode except:
protected $except = [
'example.com'
'www.example.com'
];

See the Illuminate\Foundation\Http\Middleware\CheckMaintenanceMode middleware class:
It checks the elements of the $except property using the function fullUrlIs() from the Illuminate\Http\Request class, which itself calls the Str::is() helper (also known as the str_is() function if you're using Laravel helper function globals):
protected function inExceptArray($request)
{
foreach ($this->except as $except) {
if ($except !== '/') {
$except = trim($except, '/');
}
if ($request->fullUrlIs($except) || $request->is($except)) {
return true;
}
}
return false;
}
See https://laravel.com/docs/7.x/helpers#method-str-is
You should then be able to check for an url like this, to exclude this domain from the maintenance mode (i.e. it will always be up):
protected $except = [
'https://www.example.com/*'
];

Suppose you have two domains. One is the main domain
and another one is a subdomain.
mydomain.com
admin.mydomain.com
You have a page name maintenance. The maintenance page is under the main domain. The URL of the maintenance page is mydomain.com/maintenance.
In the maintenance mode, you will have the route permission of mydomain.com/maintenance and admin.mydomain.com
Now work process.
Goto App\Http\Middleware the open the PreventRequestsDuringMaintenance middleware then add this code
protected $except = [
'maintenance*',
'http://admin.*',
'https://admin.*'
];
Then go to App\Exceptions open Handler file, inside render function add
if (App::isDownForMaintenance()) {
return redirect('/maintenance');
}
Now run php artisan down

Related

Laravel installation in sub-folder and horizon not working

I have installed the Laravel in sub-folder and is trying to install the horizon. After routing to "test.com/sub-folder/horizon", all the design in broken and also the internal links are pointing to main domain instead of main-domain-without-subfolder.
After the search, it seems to be the known issue which is already reported in github issue
Has there is any work around to make horizon work when Laravel is installed in sub-folder?
I have a solution that only involves PHP.
The issue, as pointed out by #Isaiahiroko, is the basePath defined for Horizon's interface. That code is in Laravel\Horizon\Http\Controllers\HomeController::index(). The idea is this: we are going to pass to Laravel's service container our own implementation of that controller that will override the basePath definition passed to Horizon's interface.
Create a new controller with code like this:
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Str;
use Illuminate\View\View;
use Laravel\Horizon\Horizon;
use Laravel\Horizon\Http\Controllers\HomeController;
class HorizonHomeController extends HomeController
{
/**
* Overrides default horizon route to support subdirectory hosting.
*/
public function index ()
{
// We use a plain request to check for the base url.
$request = request();
// Set up our base path.
$base_path = Str::substr($request->getBasePath(), 1);
if (!empty($base_path)) {
$base_path .= '/';
}
// Patch default horizon variables with our own base path.
$variables = Horizon::scriptVariables();
$variables['path'] = $base_path . config('horizon.path');
// Render horizon's home view.
return view('horizon::layout', [
'assetsAreCurrent' => Horizon::assetsAreCurrent(),
'horizonScriptVariables' => $variables,
'cssFile' => Horizon::$useDarkTheme ? 'app-dark.css' : 'app.css',
'isDownForMaintenance' => App::isDownForMaintenance(),
]);
}
}
What's left is telling Laravel's service container that when Horizon's HomeController is requested, it should provide our HorizonHomeController class. In your AppServiceProvider, at the end of the register() method, set this up:
// [...]
class AppServiceProvider extends ServiceProvider
{
// [...]
/**
* Register any application services.
*
* #return void
* #throws InvalidConfiguration
*/
public function register()
{
// [...]
// Horizon's subdirectory hack
$this->app->bind(
Laravel\Horizon\Http\Controllers\HomeController::class,
App\Http\Controllers\HorizonHomeController::class
);
}
// [...]
}
After that, you should be able to browse to http(s)://<your-host>/<your-sub-dir>/horizon normally.
Considerations:
To me this feels cleaner that patching a compiled js, which also has the downside that needs to be re-applied every time Horizon is updated (this can be mitigated with a post-update script in composer, tho). Also, for additional points, this solution is only overriding the method that renders the view, but not the route, which means all of Horizon's authentication mechanisms (middlewares and gates) are working exactly as described in the documentation.
If you desperately need to do this, here is a hack:
In public\vendor\horizon\app.js, search for window.Horizon.basePath
replace window.Horizon.basePath="/"+window.Horizon.path; with window.Horizon.basePath="/[you sub-directoy]/"+window.Horizon.path;
It should work...until you run update one day and it mysteriously stop working.

email/verify with VusJS SPA and Laravel

I'm currently building a new web application with VueJS SPA, VueJS Router, and Laravel, users should be able to access pages as guests (non-authenticated) or logged-in (authenticated)!
So $this->middleware('auth') is commented from my SpaContoller to give guests access to pages but with some view limitation of course!
I've added basic user authentication by using
php artisan make:auth
and the problem I'm facing is that after registration user gets redirected to the Home page and can access any pages rather than seeing the 'Verify Email' page only!
When I include $this->middleware('auth') for the SpaController it works fine but then guests can't access any pages.
So not sure now to get a proper solution for that?
I`m a beginner in Laravel and Vue-Js. I have done my website with JWT auth. I manage the access to pages using routes as follows.
routes: [
{ path: "/profile", component: profile, meta: { requireAuth: true } },
// this can be access only by registered users
{ path: "/home", component: home }, //this route can be access by anyone
router.beforeEach((to, from, next) => {
//console.log(Store.getters.role);
if (to.meta.requireAuth) {
next();
}
}
);
A Solution but I'm not sure if it would be the best one if you want to force the users to see only 'Verify Email' is to create a middleware and add it to SpaController:
class ForceRedirectToVerifyEmail extends Middleware
{
/**
*
* #param \Illuminate\Http\Request $request
* #return string
*/
protected function redirectTo($request)
{
if (auth()->check() && !auth()->user()->hasVerifiedEmail()) {
return url('verify-email');//Or what ever need to redirect them as normly it would be handled in VueJS or ReactJs themselves.
}
}
}

Laravel nova - redirect from Dashboard

I would like to remove dashboard from my Laravel Nova app.
I found it easy to remove it from sidebar-menu - simply comment /views/dashboard/navigation.blade.php code.
However, I want to add a redirection logic (landing page depends on user role) so when navigating to / user will be redirected to a resource or tool which corresponds him.
(I have already implemented a redirection after login (https://stackoverflow.com/a/54345123/1039488)
I tried to do it with cards, but looks like this is not the right solution.
Any idea where can I place the redirection logic?
Nova 4; You can override the initialPath like so:
class NovaServiceProvider extends NovaApplicationServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
parent::boot();
Nova::initialPath('/resources/users');
}
// ...
}
This way, you get redirected to the Users resource upon logging in.
Pre nova 4 method:
To NovaServiceProvider.php add to boot method:
Nova::script('menuFix', __DIR__.'/../../resources/js/fixMenu.js');
Create file fixMenu.js with following:
if (location.pathname == '/' || location.pathname == '/dashboards/main'){
location.href = '/whereToRedirect'
}
A cleaner and safe way for Nova 3.x or below:
Copy vendor/laravel/nova/resources/views/layout.blade.php to resources/views/vendor/nova/
Now open resources/views/vendor/nova/layout.blade.php and edit it
Replace this line with the code below window.Nova = new CreateNova(config);
window.Nova = new CreateNova(config);
window.Nova.booting((Vue, router, store) => {
/** This fixes showing an empty dashboard. */
router.beforeEach((to, from, next) => {
if (to.name === 'dashboard.custom') {
next({ name: 'index', params: { resourceName: 'users'}});
}
next();
});
});
Replace users with your entity name's plural like products
Now save the file and refresh the nova dashboard and you should see the new page.
The solution was taken from here with clear steps.
The solution may also work for 4.x, but I haven't checked it yet.
Happy Coding :)
Just figured this out myself. In your Routes/web.php file, add a redirect route:
Route::redirect('/','/resources/{resource_name}');
where {resource_name} is the plural form of the resource. For example, '/resources/posts'.
In your case, you may want to redirect to your own control file, where the redirect logic can be placed.
Route::get('/', 'YourController#rootRedirectLogic');
Then in the controller YourController, add the method:
public function rootRedirectLogic(Request $request) {
// some logic here
return redirect()->route('YourRoute');
}
where 'YourRoute' is the name of the route you want to send the user to.
(Found clues to this solution in a comment by dillingham here: https://github.com/laravel/nova-issues/issues/393)
i came across this link : Laravel Nova - Point Nova path to resource page
Not sure it's a permanent solution but editing LoginController.php will do.
public function redirectPath()
{
return Nova::path().'/resources/***<resource_name>***;
}
**change to your own resource name

Laravel 5.3 SubstituteBindings middleware with withoutMiddleware issue

Since Laravel 5.3, the route implicit binding works as middleware called SubstituteBindings. I used to work with Laravel 5.2 and upgraded to 5.3.
I have some custom middlewares in my application and in my tests I need to disable them. So, until now I used $this->withoutMiddleware() in the test methods. But since the update to Laravel 5.3, withoutMiddleware stops the route implicit binding, and all my tests fails.
I don't know if this should be considered as bug, but it is a huge problem for me.
Is there any way to set the SubstituteBindings middleware as mandatory middleware? How can I still use implicit binding and test my tests without other middlewares?
Building on my comment above I had a look at registering a custom router which always adds SubstituteBindings to the list of middleware if middleware was disabled. You can achieve it by registering a custom RoutingServiceProvider and registering your own Router class. Unfortunately since the route is created fairly early on in the app bootstrap process you also need to create a custom App class and use that in bootstrap/app.php too.
RoutingServiceProvider
<?php namespace App\Extensions\Providers;
use Illuminate\Routing\RoutingServiceProvider as IlluminateRoutingServiceProvider;
use App\Extensions\ExtendedRouter;
class RoutingServiceProvider extends IlluminateRoutingServiceProvider
{
protected function registerRouter()
{
$this->app['router'] = $this->app->share(function ($app) {
return new ExtendedRouter($app['events'], $app);
});
}
}
Custom router
This adds the middleware, it just extends the default router but overrides the runRouteWithinStack method and, instead of returning an empty array if $this->container->make('middleware.disable') is true, it returns an array containing the SubstituteBindings class.
<?php namespace App\Extensions;
use Illuminate\Routing\Router;
use Illuminate\Routing\Route;
use Illuminate\Routing\Pipeline;
use Illuminate\Http\Request;
class ExtendedRouter extends Router {
protected function runRouteWithinStack(Route $route, Request $request)
{
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
// Make sure SubstituteBindings is always used as middleware
$middleware = $shouldSkipMiddleware ? [
\Illuminate\Routing\Middleware\SubstituteBindings::class
] : $this->gatherRouteMiddleware($route);
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run($request)
);
});
}
}
Custom App Class
<?php namespace App;
use App\Extensions\Providers\RoutingServiceProvider;
class MyCustomApp extends Application
{
protected function registerBaseServiceProviders()
{
parent::registerBaseServiceProviders();
$this->register(new RoutingServiceProvider($this));
}
Using the custom app class
In bootstrap/app.php change the line where the app is instantiated to:
$app = new App\MyCustomApp(
realpath(__DIR__.'/../')
);
--
Warning! I haven't fully tested this, my app loads and my tests pass but there could be issues that I haven't discovered. It's also quite brittle since if the Laravel base Router class changes you might find things break randomly on future upgrades.
--
You might also want to refactor this so the list of middleware in the custom router always contains the SubstituteBindings class so there isn't so much of a difference in behaviour if middleware is disabled.

Laravel 3 - set routes dyncamically

I am using Laravel 3 with two sets of login controllers - the main domain goes to login, all subdomains should route to portal/login#index
I am using the following code in my routes.php:
Route::filter('before', function()
{
$server = explode('.', Request::server('HTTP_HOST'));
if (count($server) == 3)
{
$account = Account::where('subdomain', '=', $server[0])->first();
Session::put('account_id', $account->id);
Route::get('login', 'portal.login#index');
Route::post('login', 'portal.login#index');
Route::get('logout/(:any)', 'portal.login#logout');
}
else
{
// some other stuff - no routing calls in here
}
}
This code works fine for capturing the subdomain & doing the other tasks (such as setting the $account_id), but seem to have no affect on the routing
test.mydomain.com/login should go to portal/login, but instead goes to the main login controller.
I've searched through to be sure there are no filters affecting this (it is an inherited app)
Is this the correct way to set this up, and if so, what else might be affecting this?
TIA!
It's because when you are inside
if (count($server) == 3)
{
// Here
}
The registering of new routes using get/post is not going to respond because the system has already done the route matching, in this case you can forward the request to a new route using
Route::forward($method, $uri);
Which is in laravel/routing/route.php file as given nelow
/**
* Calls the specified route and returns its response.
*
* #param string $method
* #param string $uri
* #return Response
*/
public static function forward($method, $uri)
{
return Router::route(strtoupper($method), $uri)->call();
}
So, if you want to create a request similar to Route::get('login', 'portal.login#index'); then you can do it as
Route::forward('GET', 'login');
In this case, you have keep this route registered just normally you register a route. So, register/add the requests in the routes.php that you want to create dynamically and use Route::forward() method inside
if (count($server) == 3)
{
Route::forward('GET', 'login'); // for a get request
Route::forward('POST', 'login'); // for a post request
}
That's it.

Resources