From 48298cb2891f728ae93e59f75802607f0ed910f5 Mon Sep 17 00:00:00 2001 From: firefox747 Date: Wed, 4 Oct 2017 04:54:53 +0200 Subject: [PATCH] Added multi app (frontend/backend) support. (#309) * - Added multi app (frontend/backend) support. * Bugfix * Bugfix: Expanded cache key by Yii::$app->id to differenciate between apps (frontend/backend). I did not notice, that $module->getUniqueId() returns an empty string when $module is the app itself. Therefore the cache could not differenciate between frontend and backend. Thus resulting in the same routes for all indicated apps. * Filter out empty parent items. Sometimes a menu item is defined by url = # and is used as a container for sub-items. If all the subitems are forbidden then this patch removes the parent entry as well. * Bugfix: Debug bar disappeared. Thanks to yongtiger (see https://github.com/mdmsoft/yii2-admin/pull/309#issuecomment-280197343) --- components/Configs.php | 25 ++++++++----- components/Helper.php | 37 +++++++++++++------ models/Route.php | 83 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 118 insertions(+), 27 deletions(-) diff --git a/components/Configs.php b/components/Configs.php index 17a9b2c7..b6cf6ce0 100644 --- a/components/Configs.php +++ b/components/Configs.php @@ -3,19 +3,19 @@ namespace mdm\admin\components; use Yii; -use yii\db\Connection; use yii\caching\Cache; -use yii\rbac\ManagerInterface; -use yii\helpers\ArrayHelper; +use yii\db\Connection; use yii\di\Instance; +use yii\helpers\ArrayHelper; +use yii\rbac\ManagerInterface; /** * Configs - * Used for configure some value. To set config you can use [[\yii\base\Application::$params]] - * + * Used to configure some values. To set config you can use [[\yii\base\Application::$params]] + * * ``` * return [ - * + * * 'mdm.admin.configs' => [ * 'db' => 'customDb', * 'menuTable' => '{{%admin_menu}}', @@ -26,9 +26,9 @@ * ] * ]; * ``` - * + * * or use [[\Yii::$container]] - * + * * ``` * Yii::$container->set('mdm\admin\components\Configs',[ * 'db' => 'customDb', @@ -94,10 +94,15 @@ class Configs extends \yii\base\Object public $strict = true; /** - * @var array + * @var array */ public $options; + /** + * @var array|false + */ + public $advanced; + /** * @var self Instance of self */ @@ -106,7 +111,7 @@ class Configs extends \yii\base\Object 'db' => 'yii\db\Connection', 'userDb' => 'yii\db\Connection', 'cache' => 'yii\caching\Cache', - 'authManager' => 'yii\rbac\ManagerInterface' + 'authManager' => 'yii\rbac\ManagerInterface', ]; /** diff --git a/components/Helper.php b/components/Helper.php index 16952a45..1e72b588 100644 --- a/components/Helper.php +++ b/components/Helper.php @@ -2,10 +2,11 @@ namespace mdm\admin\components; +use mdm\admin\models\Route; use Yii; -use yii\web\User; -use yii\helpers\ArrayHelper; use yii\caching\TagDependency; +use yii\helpers\ArrayHelper; +use yii\web\User; /** * Description of Helper @@ -57,7 +58,7 @@ protected static function getDefaultRoutes() } if ($cache) { $cache->set($roles, self::$_defaultRoutes, Configs::cacheDuration(), new TagDependency([ - 'tags' => Configs::CACHE_TAG + 'tags' => Configs::CACHE_TAG, ])); } } @@ -87,7 +88,7 @@ public static function getRoutesByUser($userId) self::$_userRoutes[$userId] = $routes; if ($cache) { $cache->set([__METHOD__, $userId], $routes, Configs::cacheDuration(), new TagDependency([ - 'tags' => Configs::CACHE_TAG + 'tags' => Configs::CACHE_TAG, ])); } } @@ -104,7 +105,7 @@ public static function getRoutesByUser($userId) public static function checkRoute($route, $params = [], $user = null) { $config = Configs::instance(); - $r = static::normalizeRoute($route); + $r = static::normalizeRoute($route, $config->advanced); if ($config->onlyRegisteredRoute && !isset(static::getRegisteredRoutes()[$r])) { return true; } @@ -140,18 +141,30 @@ public static function checkRoute($route, $params = [], $user = null) } } - protected static function normalizeRoute($route) + /** + * Normalize route + * @param string $route Plain route string + * @param boolean|array $advanced Array containing the advanced configuration. Defaults to false. + * @return string Normalized route string + */ + protected static function normalizeRoute($route, $advanced = false) { if ($route === '') { - return '/' . Yii::$app->controller->getRoute(); + $normalized = '/' . Yii::$app->controller->getRoute(); } elseif (strncmp($route, '/', 1) === 0) { - return $route; + $normalized = $route; } elseif (strpos($route, '/') === false) { - return '/' . Yii::$app->controller->getUniqueId() . '/' . $route; + $normalized = '/' . Yii::$app->controller->getUniqueId() . '/' . $route; } elseif (($mid = Yii::$app->controller->module->getUniqueId()) !== '') { - return '/' . $mid . '/' . $route; + $normalized = '/' . $mid . '/' . $route; + } else { + $normalized = '/' . $route; + } + // Prefix @app-id to route. + if ($advanced) { + $normalized = Route::PREFIX_ADVANCED . Yii::$app->id . $normalized; } - return '/' . $route; + return $normalized; } /** @@ -187,7 +200,7 @@ protected static function filterRecursive($items, $user) } $item['items'] = $subItems; } - if ($allow) { + if ($allow && !($url == '#' && empty($item['items']))) { $result[$i] = $item; } } diff --git a/models/Route.php b/models/Route.php index afe939ee..ccc160c9 100644 --- a/models/Route.php +++ b/models/Route.php @@ -20,6 +20,11 @@ class Route extends \yii\base\Object { const CACHE_TAG = 'mdm.admin.route'; + const PREFIX_ADVANCED = '@'; + const PREFIX_BASIC = '/'; + + private $_routePrefix; + /** * Assign or remove items * @param array $routes @@ -31,7 +36,7 @@ public function addNew($routes) foreach ($routes as $route) { try { $r = explode('&', $route); - $item = $manager->createPermission('/' . trim($route, '/')); + $item = $manager->createPermission($this->getPermissionName($route)); if (count($r) > 1) { $action = '/' . trim($r[0], '/'); if (($itemAction = $manager->getPermission($action)) === null) { @@ -67,7 +72,7 @@ public function remove($routes) $manager = Configs::authManager(); foreach ($routes as $route) { try { - $item = $manager->createPermission('/' . trim($route, '/')); + $item = $manager->createPermission($this->getPermissionName($route)); $manager->remove($item); } catch (Exception $exc) { Yii::error($exc->getMessage(), __METHOD__); @@ -76,6 +81,32 @@ public function remove($routes) Helper::invalidate(); } + /** + * Returns route prefix depending on the configuration. + * @return string Route prefix + */ + public function getRoutePrefix() + { + if (!$this->_routePrefix) { + $this->_routePrefix = Configs::instance()->advanced ? self::PREFIX_ADVANCED : self::PREFIX_BASIC; + } + return $this->_routePrefix; + } + + /** + * Returns the correct permission name depending on the configuration. + * @param string $route Route + * @return string Permission name + */ + public function getPermissionName($route) + { + if (self::PREFIX_BASIC == $this->routePrefix) { + return self::PREFIX_BASIC . trim($route, self::PREFIX_BASIC); + } else { + return self::PREFIX_ADVANCED . ltrim(trim($route, self::PREFIX_BASIC), self::PREFIX_ADVANCED); + } + } + /** * Get available and assigned routes * @return array @@ -83,10 +114,52 @@ public function remove($routes) public function getRoutes() { $manager = Configs::authManager(); - $routes = $this->getAppRoutes(); + // Get advanced configuration + $advanced = Configs::instance()->advanced; + if ($advanced) { + // Use advanced route scheme. + // Set advanced route prefix. + $this->_routePrefix = self::PREFIX_ADVANCED; + // Create empty routes array. + $routes = []; + // Save original app. + $yiiApp = Yii::$app; + // Step through each configured application + foreach ($advanced as $id => $configPaths) { + // Force correct id string. + $id = $this->routePrefix . ltrim(trim($id), $this->routePrefix); + // Create empty config array. + $config = []; + // Assemble configuration for current app. + foreach ($configPaths as $configPath) { + // Merge every new configuration with the old config array. + $config = yii\helpers\ArrayHelper::merge($config, require (Yii::getAlias($configPath))); + } + // Create new app using the config array. + unset($config['bootstrap']); + $app = new yii\web\Application($config); + // Get all the routes of the newly created app. + $r = $this->getAppRoutes($app); + // Dump new app + unset($app); + // Prepend the app id to all routes. + foreach ($r as $route) { + $routes[$id . $route] = $id . $route; + } + } + // Switch back to original app. + Yii::$app = $yiiApp; + unset($yiiApp); + } else { + // Use basic route scheme. + // Set basic route prefix + $this->_routePrefix = self::PREFIX_BASIC; + // Get basic app routes. + $routes = $this->getAppRoutes(); + } $exists = []; foreach (array_keys($manager->getPermissions()) as $name) { - if ($name[0] !== '/') { + if ($name[0] !== $this->routePrefix) { continue; } $exists[] = $name; @@ -109,7 +182,7 @@ public function getAppRoutes($module = null) } elseif (is_string($module)) { $module = Yii::$app->getModule($module); } - $key = [__METHOD__, $module->getUniqueId()]; + $key = [__METHOD__, Yii::$app->id, $module->getUniqueId()]; $cache = Configs::instance()->cache; if ($cache === null || ($result = $cache->get($key)) === false) { $result = [];