From d56dded458bfa5ede31fa97c05533835322b984c Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Thu, 7 Nov 2019 11:59:00 -0600 Subject: [PATCH] Restore middleware support in backend controllers. Reverts https://github.com/octobercms/october/commit/f73d8e6d498a211192976a525016b4a961707e3f. While there are other ways to achieve some of the same end results, this code existed in the code base for 8 months without issues and is included in the official docs. This means that there could be devs that are depending on this behavior. Additionally, while this may make the internal logic to the BackendController class more complex, it simplifies the developer experience by bringing the Backend\Classes\Controller base class more in line with the standard Laravel controller class. --- modules/backend/classes/BackendController.php | 106 ++++++++++++++++-- modules/backend/classes/Controller.php | 36 +++++- 2 files changed, 131 insertions(+), 11 deletions(-) diff --git a/modules/backend/classes/BackendController.php b/modules/backend/classes/BackendController.php index 57ac5dff2..dcf590be2 100644 --- a/modules/backend/classes/BackendController.php +++ b/modules/backend/classes/BackendController.php @@ -55,6 +55,44 @@ class BackendController extends ControllerBase */ public function __construct() { + $this->middleware(function ($request, $next) { + // Process the request before retrieving controller middleware, to allow for the session and auth data + // to be made available to the controller's constructor. + $response = $next($request); + + // Find requested controller to determine if any middleware has been attached + $pathParts = explode('/', str_replace(Request::root() . '/', '', Request::url())); + if (count($pathParts)) { + // Drop off preceding backend URL part if needed + if (!empty(Config::get('cms.backendUri', 'backend'))) { + array_shift($pathParts); + } + $path = implode('/', $pathParts); + + $requestedController = $this->getRequestedController($path); + if ( + !is_null($requestedController) + && is_array($requestedController) + && count($requestedController['controller']->getMiddleware()) + ) { + $action = $requestedController['action']; + + // Collect applicable middleware and insert middleware into pipeline + $controllerMiddleware = collect($requestedController['controller']->getMiddleware()) + ->reject(function ($data) use ($action) { + return static::methodExcludedByOptions($action, $data['options']); + }) + ->pluck('middleware'); + + foreach ($controllerMiddleware as $middleware) { + $middleware->call($requestedController['controller'], $request, $response); + } + } + } + + return $response; + }); + $this->extendableConstruct(); } @@ -113,6 +151,34 @@ class BackendController extends ControllerBase : $this->passToCmsController($url); } + $controllerRequest = $this->getRequestedController($url); + if (!is_null($controllerRequest)) { + return $controllerRequest['controller']->run( + $controllerRequest['action'], + $controllerRequest['params'] + ); + } + + /* + * Fall back on Cms controller + */ + return $this->passToCmsController($url); + } + + /** + * Determines the controller and action to load in the backend via a provided URL. + * + * If a suitable controller is found, this will return an array with the controller class name as a string, the + * action to call as a string and an array of parameters. If a suitable controller and action cannot be found, + * this method will return null. + * + * @param string $url A URL to determine the requested controller and action for + * @return array|null A suitable controller, action and parameters in an array if found, otherwise null. + */ + protected function getRequestedController($url) + { + $params = RouterHelper::segmentizeUrl($url); + /* * Look for a Module controller */ @@ -126,7 +192,11 @@ class BackendController extends ControllerBase $action, base_path().'/modules' )) { - return $controllerObj->run($action, $controllerParams); + return [ + 'controller' => $controllerObj, + 'action' => $action, + 'params' => $controllerParams + ]; } /* @@ -149,14 +219,15 @@ class BackendController extends ControllerBase $action, plugins_path() )) { - return $controllerObj->run($action, $controllerParams); + return [ + 'controller' => $controllerObj, + 'action' => $action, + 'params' => $controllerParams + ]; } } - /* - * Fall back on Cms controller - */ - return $this->passToCmsController($url); + return null; } /** @@ -169,6 +240,10 @@ class BackendController extends ControllerBase */ protected function findController($controller, $action, $inPath) { + if (isset($this->requestedController)) { + return $this->requestedController; + } + /* * Workaround: Composer does not support case insensitivity. */ @@ -181,16 +256,16 @@ class BackendController extends ControllerBase } if (!class_exists($controller)) { - return false; + return $this->requestedController = null; } $controllerObj = App::make($controller); if ($controllerObj->actionExists($action)) { - return $controllerObj; + return $this->requestedController = $controllerObj; } - return false; + return $this->requestedController = null; } /** @@ -206,4 +281,17 @@ class BackendController extends ControllerBase return $actionName; } + + /** + * Determine if the given options exclude a particular method. + * + * @param string $method + * @param array $options + * @return bool + */ + protected static function methodExcludedByOptions($method, array $options) + { + return (isset($options['only']) && !in_array($method, (array) $options['only'])) || + (!empty($options['except']) && in_array($method, (array) $options['except'])); + } } diff --git a/modules/backend/classes/Controller.php b/modules/backend/classes/Controller.php index ff6177320..9d4dc7995 100644 --- a/modules/backend/classes/Controller.php +++ b/modules/backend/classes/Controller.php @@ -19,9 +19,9 @@ use October\Rain\Exception\AjaxException; use October\Rain\Exception\SystemException; use October\Rain\Exception\ValidationException; use October\Rain\Exception\ApplicationException; -use October\Rain\Extension\Extendable; use Illuminate\Database\Eloquent\MassAssignmentException; use Illuminate\Http\RedirectResponse; +use Illuminate\Routing\Controller as ControllerBase; /** * The Backend base controller class, used by Backend controllers. @@ -30,7 +30,7 @@ use Illuminate\Http\RedirectResponse; * @package october\backend * @author Alexey Bobkov, Samuel Georges */ -class Controller extends Extendable +class Controller extends ControllerBase { use \System\Traits\ViewMaker; use \System\Traits\AssetMaker; @@ -40,6 +40,12 @@ class Controller extends Extendable use \System\Traits\SecurityController; use \Backend\Traits\ErrorMaker; use \Backend\Traits\WidgetMaker; + use \October\Rain\Extension\ExtendableTrait; + + /** + * @var array Behaviors implemented by this controller. + */ + public $implement; /** * @var object Reference the logged in admin user. @@ -153,6 +159,32 @@ class Controller extends Extendable $manager = new MediaManager($this, 'ocmediamanager'); $manager->bindToController(); } + + $this->extendableConstruct(); + } + + /** + * Extend this object properties upon construction. + */ + public static function extend(Closure $callback) + { + self::extendableExtendCallback($callback); + } + public function __get($name) + { + return $this->extendableGet($name); + } + public function __set($name, $value) + { + $this->extendableSet($name, $value); + } + public function __call($name, $params) + { + return $this->extendableCall($name, $params); + } + public static function __callStatic($name, $params) + { + return self::extendableCallStatic($name, $params); } /**