MenuHelper.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. <?php
  2. namespace common\modules\rbac\components;
  3. use common\modules\rbac\models\Menu;
  4. use Yii;
  5. use yii\caching\TagDependency;
  6. /**
  7. * MenuHelper used to generate menu depend of user role.
  8. * Usage.
  9. *
  10. * ~~~
  11. * use common\modules\rbac\components\MenuHelper;
  12. * use yii\bootstrap\Nav;
  13. *
  14. * echo Nav::widget([
  15. * 'items' => MenuHelper::getAssignedMenu(Yii::$app->user->id)
  16. * ]);
  17. * ~~~
  18. *
  19. * To reformat returned, provide callback to method.
  20. *
  21. * ~~~
  22. * $callback = function ($menu) {
  23. * $data = eval($menu['data']);
  24. * return [
  25. * 'label' => $menu['name'],
  26. * 'url' => [$menu['route']],
  27. * 'options' => $data,
  28. * 'items' => $menu['children']
  29. * ]
  30. * ]
  31. * }
  32. *
  33. * $items = MenuHelper::getAssignedMenu(Yii::$app->user->id, null, $callback);
  34. * ~~~
  35. *
  36. * @author Misbahul D Munir <misbahuldmunir@gmail.com>
  37. *
  38. * @since 1.0
  39. */
  40. class MenuHelper
  41. {
  42. const CACHE_TAG = 'mdm.admin.menu';
  43. /**
  44. * Use to get assigned menu of user.
  45. *
  46. * @param mixed $userId
  47. * @param int $root
  48. * @param \Closure $callback use to reformat output.
  49. * callback should have format like
  50. *
  51. * ~~~
  52. * function ($menu) {
  53. * return [
  54. * 'label' => $menu['name'],
  55. * 'url' => [$menu['route']],
  56. * 'options' => $data,
  57. * 'items' => $menu['children']
  58. * ]
  59. * ]
  60. * }
  61. * ~~~
  62. * @param bool $refresh
  63. *
  64. * @return array
  65. */
  66. public static function getAssignedMenu($userId, $root = null, $callback = null, $refresh = false)
  67. {
  68. $config = Configs::instance();
  69. /* @var $manager \yii\rbac\BaseManager */
  70. $manager = Yii::$app->getAuthManager();
  71. $menus = Menu::find()->asArray()->indexBy('id')->all();
  72. $key = [__METHOD__, $userId, $manager->defaultRoles];
  73. $cache = $config->cache;
  74. if ($refresh || $cache === null || ($assigned = $cache->get($key)) === false) {
  75. $routes = $filter1 = $filter2 = [];
  76. if ($userId !== null) {
  77. foreach ($manager->getPermissionsByUser($userId) as $name => $value) {
  78. if ($name[0] === '/') {
  79. if (substr($name, -2) === '/*') {
  80. $name = substr($name, 0, -1);
  81. }
  82. $routes[] = $name;
  83. }
  84. }
  85. }
  86. foreach ($manager->defaultRoles as $role) {
  87. foreach ($manager->getPermissionsByRole($role) as $name => $value) {
  88. if ($name[0] === '/') {
  89. if (substr($name, -2) === '/*') {
  90. $name = substr($name, 0, -1);
  91. }
  92. $routes[] = $name;
  93. }
  94. }
  95. }
  96. $routes = array_unique($routes);
  97. sort($routes);
  98. $prefix = '\\';
  99. foreach ($routes as $route) {
  100. if (strpos($route, $prefix) !== 0) {
  101. if (substr($route, -1) === '/') {
  102. $prefix = $route;
  103. $filter1[] = $route.'%';
  104. } else {
  105. $filter2[] = $route;
  106. }
  107. }
  108. }
  109. $assigned = [];
  110. $query = Menu::find()->select(['id'])->asArray();
  111. if (count($filter2)) {
  112. $assigned = $query->where(['route' => $filter2])->column();
  113. }
  114. if (count($filter1)) {
  115. $query->where('route like :filter');
  116. foreach ($filter1 as $filter) {
  117. $assigned = array_merge($assigned, $query->params([':filter' => $filter])->column());
  118. }
  119. }
  120. $assigned = static::requiredParent($assigned, $menus);
  121. if ($cache !== null) {
  122. $cache->set($key, $assigned, $config->cacheDuration, new TagDependency([
  123. 'tags' => self::CACHE_TAG,
  124. ]));
  125. }
  126. }
  127. $key = [__METHOD__, $assigned, $root];
  128. if ($refresh || $callback !== null || $cache === null || (($result = $cache->get($key)) === false)) {
  129. $result = static::normalizeMenu($assigned, $menus, $callback, $root);
  130. if ($cache !== null && $callback === null) {
  131. $cache->set($key, $result, $config->cacheDuration, new TagDependency([
  132. 'tags' => self::CACHE_TAG,
  133. ]));
  134. }
  135. }
  136. return $result;
  137. }
  138. /**
  139. * 获取navs中第一个有url的nav.
  140. *
  141. * @param unknown $navs
  142. */
  143. public static function getFirstMenu($navs)
  144. {
  145. foreach ($navs as $nav) {
  146. if (array_key_exists('url', $nav) && $nav['url'] != '#') {
  147. return $nav;
  148. }
  149. if (array_key_exists('items', $nav) && count($nav['items']) > 0) {
  150. $nav = static::getFirstMenu($nav['items']);
  151. if ($nav != null) {
  152. return $nav;
  153. }
  154. }
  155. }
  156. return;
  157. }
  158. public static function getSiblingsMenu($label, $navs)
  159. {
  160. foreach ($navs as $nav) {
  161. if($nav['label'] == $label) {
  162. return $navs;
  163. }
  164. if (array_key_exists('items', $nav) && count($nav['items']) > 0) {
  165. $n = self::getSiblingsMenu($label, $nav['items']);
  166. if (!empty($n)) {
  167. return $n;
  168. }
  169. }
  170. }
  171. return [];
  172. }
  173. public static function getRootMenu($nav)
  174. {
  175. if ($nav['parent'] == null) {
  176. return $nav;
  177. }
  178. $parent = Menu::find()->where(['id' => $nav['parent']])->one();
  179. if ($parent['parent'] != null) {
  180. return static::getRootMenu($parent);
  181. } else {
  182. return $parent;
  183. }
  184. }
  185. /**
  186. * Ensure all item menu has parent.
  187. *
  188. * @param array $assigned
  189. * @param array $menus
  190. *
  191. * @return array
  192. */
  193. private static function requiredParent($assigned, &$menus)
  194. {
  195. $l = count($assigned);
  196. for ($i = 0; $i < $l; ++$i) {
  197. $id = $assigned[$i];
  198. $parent_id = $menus[$id]['parent'];
  199. if ($parent_id !== null && !in_array($parent_id, $assigned)) {
  200. $assigned[$l++] = $parent_id;
  201. }
  202. }
  203. return $assigned;
  204. }
  205. /**
  206. * Parse route.
  207. *
  208. * @param string $route
  209. *
  210. * @return mixed
  211. */
  212. public static function parseRoute($route)
  213. {
  214. if (!empty($route)) {
  215. $url = [];
  216. $r = explode('&', $route);
  217. $url[0] = $r[0];
  218. unset($r[0]);
  219. foreach ($r as $part) {
  220. $part = explode('=', $part);
  221. $url[$part[0]] = isset($part[1]) ? $part[1] : '';
  222. }
  223. return $url;
  224. }
  225. return '#';
  226. }
  227. /**
  228. * Normalize menu.
  229. *
  230. * @param array $assigned
  231. * @param array $menus
  232. * @param Closure $callback
  233. * @param int $parent
  234. *
  235. * @return array
  236. */
  237. private static function normalizeMenu(&$assigned, &$menus, $callback, $parent = null)
  238. {
  239. $result = [];
  240. $order = [];
  241. foreach ($assigned as $id) {
  242. if(!isset($menus[$id])) {
  243. continue;
  244. }
  245. $menu = $menus[$id];
  246. if ($menu['parent'] == $parent) {
  247. $menu['children'] = static::normalizeMenu($assigned, $menus, $callback, $id);
  248. if ($callback !== null) {
  249. $item = call_user_func($callback, $menu);
  250. } else {
  251. $item = [
  252. 'label' => $menu['name'],
  253. 'url' => static::parseRoute($menu['route']),
  254. 'icon' => $menu['icon']
  255. ];
  256. if ($menu['children'] != []) {
  257. $item['items'] = $menu['children'];
  258. }
  259. }
  260. $result[] = $item;
  261. $order[] = $menu['order'];
  262. }
  263. }
  264. if ($result != []) {
  265. array_multisort($order, $result);
  266. }
  267. return $result;
  268. }
  269. /**
  270. * Use to invalidate cache.
  271. */
  272. public static function invalidate()
  273. {
  274. if (Configs::instance()->cache !== null) {
  275. TagDependency::invalidate(Configs::instance()->cache, self::CACHE_TAG);
  276. }
  277. }
  278. }