diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php index 5d49631..3c693dc 100644 --- a/src/Controller/AppController.php +++ b/src/Controller/AppController.php @@ -40,6 +40,7 @@ class AppController extends Controller public $isRest = null; public $restResponsePayload = null; public $user = null; + public $breadcrumb = []; /** * Initialization hook method. @@ -74,6 +75,9 @@ class AppController extends Controller 'request' => $this->request, 'Authentication' => $this->Authentication ]); + $this->loadComponent('Navigation', [ + 'request' => $this->request, + ]); if (Configure::read('debug')) { Configure::write('DebugKit.panels', ['DebugKit.Packages' => true]); Configure::write('DebugKit.forceEnable', true); @@ -122,6 +126,7 @@ class AppController extends Controller $this->ACL->checkAccess(); $this->set('menu', $this->ACL->getMenu()); + $this->set('breadcrumb', $this->Navigation->getBreadcrumb()); $this->set('ajax', $this->request->is('ajax')); $this->request->getParam('prefix'); $this->set('darkMode', !empty(Configure::read('Cerebrate.dark'))); diff --git a/src/Controller/Component/ACLComponent.php b/src/Controller/Component/ACLComponent.php index cfbaa69..58c19fc 100644 --- a/src/Controller/Component/ACLComponent.php +++ b/src/Controller/Component/ACLComponent.php @@ -17,6 +17,7 @@ class ACLComponent extends Component { private $user = null; + protected $components = ['Navigation']; public function initialize(array $config): void { @@ -454,464 +455,7 @@ class ACLComponent extends Component public function getMenu() { - $open = Configure::read('Cerebrate.open'); - $menu = [ - 'ContactDB' => [ - 'Individuals' => [ - 'label' => __('Individuals'), - 'icon' => 'address-book', - 'url' => '/individuals/index', - 'children' => [ - 'index' => [ - 'url' => '/individuals/index', - 'label' => __('List individuals') - ], - 'add' => [ - 'url' => '/individuals/add', - 'label' => __('Add individual'), - 'popup' => 1 - ], - 'view' => [ - 'url' => '/individuals/view/{{id}}', - 'label' => __('View individual'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1 - ], - 'edit' => [ - 'url' => '/individuals/edit/{{id}}', - 'label' => __('Edit individual'), - 'actions' => ['edit', 'delete', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ], - 'delete' => [ - 'url' => '/individuals/delete/{{id}}', - 'label' => __('Delete individual'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ] - ] - ], - 'Organisations' => [ - 'label' => __('Organisations'), - 'icon' => 'building', - 'url' => '/organisations/index', - 'children' => [ - 'index' => [ - 'url' => '/organisations/index', - 'label' => __('List organisations') - ], - 'add' => [ - 'url' => '/organisations/add', - 'label' => __('Add organisation'), - 'popup' => 1 - ], - 'view' => [ - 'url' => '/organisations/view/{{id}}', - 'label' => __('View organisation'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1 - ], - 'edit' => [ - 'url' => '/organisations/edit/{{id}}', - 'label' => __('Edit organisation'), - 'actions' => ['edit', 'delete', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ], - 'delete' => [ - 'url' => '/organisations/delete/{{id}}', - 'label' => __('Delete organisation'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ] - ] - ], - 'EncryptionKeys' => [ - 'label' => __('Encryption keys'), - 'icon' => 'key', - 'url' => '/encryptionKeys/index', - 'children' => [ - 'index' => [ - 'url' => '/encryptionKeys/index', - 'label' => __('List encryption keys') - ], - 'add' => [ - 'url' => '/encryptionKeys/add', - 'label' => __('Add encryption key'), - 'popup' => 1 - ], - 'edit' => [ - 'url' => '/encryptionKeys/edit/{{id}}', - 'label' => __('Edit organisation'), - 'actions' => ['edit'], - 'skipTopMenu' => 1, - 'popup' => 1 - ] - ] - ] - ], - 'Trust Circles' => [ - 'SharingGroups' => [ - 'label' => __('Sharing Groups'), - 'icon' => 'user-friends', - 'url' => '/sharingGroups/index', - 'children' => [ - 'index' => [ - 'url' => '/sharingGroups/index', - 'label' => __('List sharing groups') - ], - 'add' => [ - 'url' => '/SharingGroups/add', - 'label' => __('Add sharing group'), - 'popup' => 1 - ], - 'edit' => [ - 'url' => '/SharingGroups/edit/{{id}}', - 'label' => __('Edit sharing group'), - 'actions' => ['edit', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ], - 'delete' => [ - 'url' => '/SharingGroups/delete/{{id}}', - 'label' => __('Delete sharing group'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ] - ] - ] - ], - 'Sync' => [ - 'Broods' => [ - 'label' => __('Broods'), - 'icon' => 'network-wired', - 'url' => '/broods/index', - 'children' => [ - 'index' => [ - 'url' => '/broods/index', - 'label' => __('List broods') - ], - 'add' => [ - 'url' => '/broods/add', - 'label' => __('Add brood'), - 'popup' => 1 - ], - 'view' => [ - 'url' => '/broods/view/{{id}}', - 'label' => __('View brood'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1 - ], - 'edit' => [ - 'url' => '/broods/edit/{{id}}', - 'label' => __('Edit brood'), - 'actions' => ['edit', 'delete', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ], - 'delete' => [ - 'url' => '/broods/delete/{{id}}', - 'label' => __('Delete brood'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ] - ] - ] - ], - 'Administration' => [ - 'Roles' => [ - 'label' => __('Roles'), - 'icon' => 'id-badge', - 'url' => '/roles/index', - 'children' => [ - 'index' => [ - 'url' => '/roles/index', - 'label' => __('List roles') - ], - 'add' => [ - 'url' => '/roles/add', - 'label' => __('Add role'), - 'popup' => 1 - ], - 'view' => [ - 'url' => '/roles/view/{{id}}', - 'label' => __('View role'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1 - ], - 'edit' => [ - 'url' => '/roles/edit/{{id}}', - 'label' => __('Edit role'), - 'actions' => ['edit', 'delete', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ], - 'delete' => [ - 'url' => '/roles/delete/{{id}}', - 'label' => __('Delete role'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ] - ] - ], - 'Users' => [ - 'label' => __('Users'), - 'icon' => 'users', - 'url' => '/users/index', - 'children' => [ - 'index' => [ - 'url' => '/users/index', - 'label' => __('List users') - ], - 'add' => [ - 'url' => '/users/add', - 'label' => __('Add user'), - 'popup' => 1 - ], - 'view' => [ - 'url' => '/users/view/{{id}}', - 'label' => __('View user'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1 - ], - 'edit' => [ - 'url' => '/users/edit/{{id}}', - 'label' => __('Edit user'), - 'actions' => ['edit', 'delete', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ], - 'delete' => [ - 'url' => '/users/delete/{{id}}', - 'label' => __('Delete user'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ] - ] - ], - 'Inbox' => [ - 'label' => __('Inbox'), - 'icon' => 'inbox', - 'url' => '/inbox/index', - 'children' => [ - 'index' => [ - 'url' => '/inbox/index', - 'label' => __('Inbox') - ], - 'outbox' => [ - 'url' => '/outbox/index', - 'label' => __('Outbox') - ], - 'view' => [ - 'url' => '/inbox/view/{{id}}', - 'label' => __('View Message'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1 - ], - 'delete' => [ - 'url' => '/inbox/delete/{{id}}', - 'label' => __('Delete Message'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ], - 'listProcessors' => [ - 'url' => '/inbox/listProcessors', - 'label' => __('List Inbox Processors'), - 'skipTopMenu' => 1 - ] - ] - ], - 'Outbox' => [ - 'label' => __('Outbox'), - 'icon' => 'inbox', - 'url' => '/outbox/index', - 'children' => [ - 'index' => [ - 'url' => '/outbox/index', - 'label' => __('Outbox'), - 'skipTopMenu' => 1 - ], - 'view' => [ - 'url' => '/outbox/view/{{id}}', - 'label' => __('View Message'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1 - ], - 'delete' => [ - 'url' => '/outbox/delete/{{id}}', - 'label' => __('Delete Message'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ], - 'listProcessors' => [ - 'url' => '/outbox/listProcessors', - 'label' => __('List Outbox Processors'), - 'skipTopMenu' => 1 - ] - ] - ], - 'MetaTemplates' => [ - 'label' => __('Meta Field Templates'), - 'icon' => 'object-group', - 'url' => '/metaTemplates/index', - 'children' => [ - 'index' => [ - 'url' => '/metaTemplates/index', - 'label' => __('List Meta Templates') - ], - 'view' => [ - 'url' => '/metaTemplates/view/{{id}}', - 'label' => __('View Meta Template'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1 - ], - 'delete' => [ - 'url' => '/metaTemplates/delete/{{id}}', - 'label' => __('Delete Meta Template'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ], - 'update' => [ - 'url' => '/metaTemplates/update', - 'label' => __('Update Meta Templates'), - 'actions' => ['index', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ] - ] - ], - 'LocalTools' => [ - 'label' => __('Local Tools'), - 'icon' => 'tools', - 'url' => '/localTools/index', - 'children' => [ - 'index' => [ - 'url' => '/localTools/index', - 'label' => __('List Connectors') - ], - 'viewConnector' => [ - 'url' => '/localTools/viewConnector/{{connector}}', - 'label' => __('View Connector'), - 'actions' => ['view'], - 'skipTopMenu' => 1 - ], - 'add' => [ - 'url' => '/localTools/add/{{connector}}', - 'label' => __('Add connection'), - 'actions' => ['viewConnector'], - 'skipTopMenu' => 1 - ], - 'view' => [ - 'url' => '/localTools/view/{{id}}', - 'label' => __('View Connection'), - 'actions' => ['view'], - 'skipTopMenu' => 1 - ] - ] - ], - 'Instance' => [ - __('Instance'), - 'url' => '/instance/home', - 'icon' => 'database', - 'children' => [ - 'migration' => [ - 'url' => '/instance/migrationIndex', - 'label' => __('Database migration') - ] - ] - ], - ], - 'Cerebrate' => [ - 'Roles' => [ - 'label' => __('Roles'), - 'icon' => 'id-badge', - 'url' => '/roles/index', - 'children' => [ - 'index' => [ - 'url' => '/roles/index', - 'label' => __('List roles') - ], - 'view' => [ - 'url' => '/roles/view/{{id}}', - 'label' => __('View role'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1 - ], - 'delete' => [ - 'url' => '/roles/delete/{{id}}', - 'label' => __('Delete Role'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ] - ] - ], - 'Instance' => [ - __('Instance'), - 'url' => '/instance/home', - 'icon' => 'home', - 'children' => [ - 'home' => [ - 'url' => '/instance/home', - 'label' => __('Home') - ], - ] - ], - 'Users' => [ - __('My Profile'), - 'icon' => 'user-circle', - 'children' => [ - 'View My Profile' => [ - 'url' => '/users/view', - 'label' => __('View My Profile') - ], - 'Edit My Profile' => [ - 'url' => '/users/edit', - 'label' => __('Edit My Profile'), - 'actions' => ['delete', 'edit', 'view'], - 'skipTopMenu' => 1, - 'popup' => 1 - ] - ] - ] - ], - 'Open' => [ - 'Organisations' => [ - 'label' => __('Organisations'), - 'icon' => 'buildings', - 'url' => '/open/organisations/index', - 'children' => [ - 'index' => [ - 'url' => '/open/organisations/index', - 'label' => __('List organisations') - ], - ], - 'open' => in_array('organisations', Configure::read('Cerebrate.open')) - ], - 'Individuals' => [ - 'label' => __('Individuals'), - 'icon' => 'address-book', - 'url' => '/open/individuals/index', - 'children' => [ - 'index' => [ - 'url' => '/open/individuals/index', - 'label' => __('List individuals') - ], - ], - 'open' => in_array('individuals', Configure::read('Cerebrate.open')) - ] - ] - ]; + $menu = $this->Navigation->getSideMenu(); foreach ($menu as $group => $subMenu) { foreach ($subMenu as $subMenuElementName => $subMenuElement) { if (!empty($subMenuElement['url']) && !$this->checkAccessUrl($subMenuElement['url'], true) === true) { diff --git a/src/Controller/Component/NavigationComponent.php b/src/Controller/Component/NavigationComponent.php new file mode 100644 index 0000000..bbebdf0 --- /dev/null +++ b/src/Controller/Component/NavigationComponent.php @@ -0,0 +1,324 @@ +request = $config['request']; + $this->fullBreadcrumb = $this->genBreadcrumb(); + $this->breadcrumb = $this->getBreadcrumb(); + } + + public function getSideMenu(): array + { + return [ + 'ContactDB' => [ + 'Individuals' => [ + 'label' => __('Individuals'), + 'icon' => 'address-book', + 'url' => '/individuals/index', + ], + 'Organisations' => [ + 'label' => __('Organisations'), + 'icon' => 'building', + 'url' => '/organisations/index', + ], + 'EncryptionKeys' => [ + 'label' => __('Encryption keys'), + 'icon' => 'key', + 'url' => '/encryptionKeys/index', + ] + ], + 'Trust Circles' => [ + 'SharingGroups' => [ + 'label' => __('Sharing Groups'), + 'icon' => 'user-friends', + 'url' => '/sharingGroups/index', + ] + ], + 'Sync' => [ + 'Broods' => [ + 'label' => __('Broods'), + 'icon' => 'network-wired', + 'url' => '/broods/index', + ] + ], + 'Administration' => [ + 'Roles' => [ + 'label' => __('Roles'), + 'icon' => 'id-badge', + 'url' => '/roles/index', + ], + 'Users' => [ + 'label' => __('Users'), + 'icon' => 'users', + 'url' => '/users/index', + ], + 'Messages' => [ + 'label' => __('Messages'), + 'icon' => 'inbox', + 'url' => '/inbox/index', + 'children' => [ + 'index' => [ + 'url' => '/inbox/index', + 'label' => __('Inbox') + ], + 'outbox' => [ + 'url' => '/outbox/index', + 'label' => __('Outbox') + ], + ] + ], + 'Add-ons' => [ + 'label' => __('Add-ons'), + 'icon' => 'puzzle-piece', + 'children' => [ + 'MetaTemplates.index' => [ + 'label' => __('Meta Field Templates'), + 'icon' => 'object-group', + 'url' => '/metaTemplates/index', + ], + 'LocalTools.index' => [ + 'label' => __('Local Tools'), + 'icon' => 'tools', + 'url' => '/localTools/index', + ] + ] + ], + 'Instance' => [ + 'label' => __('Instance'), + 'icon' => 'server', + 'children' => [ + 'Database' => [ + 'label' => __('Database'), + 'url' => '/instance/migrationIndex', + 'icon' => 'database', + ] + ] + ], + ], + 'Open' => [ + 'Organisations' => [ + 'label' => __('Organisations'), + 'icon' => 'buildings', + 'url' => '/open/organisations/index', + 'children' => [ + 'index' => [ + 'url' => '/open/organisations/index', + 'label' => __('List organisations') + ], + ], + 'open' => in_array('organisations', Configure::read('Cerebrate.open')) + ], + 'Individuals' => [ + 'label' => __('Individuals'), + 'icon' => 'address-book', + 'url' => '/open/individuals/index', + 'children' => [ + 'index' => [ + 'url' => '/open/individuals/index', + 'label' => __('List individuals') + ], + ], + 'open' => in_array('individuals', Configure::read('Cerebrate.open')) + ] + ] + ]; + } + + public function getBreadcrumb(): array + { + $controller = Inflector::underscore($this->request->getParam('controller')); + $action = Inflector::underscore($this->request->getParam('action')); + if (empty($this->fullBreadcrumb[$controller]['routes']["{$controller}:{$action}"])) { + return []; // no breadcrumb defined for this endpoint + } + $currentRoute = $this->fullBreadcrumb[$controller]['routes']["{$controller}:{$action}"]; + $breadcrumbPath = $this->getBreadcrumbPath("{$controller}:{$action}", $currentRoute); + return $breadcrumbPath['objects']; + } + + public function getBreadcrumbPath(string $startRoute, array $currentRoute): array + { + $route = $startRoute; + $path = [ + 'routes' => [], + 'objects' => [], + ]; + $visited = []; + while (empty($visited[$route])) { + $visited[$route] = true; + $path['routes'][] = $route; + $path['objects'][] = $currentRoute; + if (!empty($currentRoute['after'])) { + $route = $currentRoute['after']; + $split = explode(':', $currentRoute['after']); + $currentRoute = $this->fullBreadcrumb[$split[0]]['routes'][$currentRoute['after']]; + } + } + $path['routes'] = array_reverse($path['routes']); + $path['objects'] = array_reverse($path['objects']); + return $path; + } + + private function insertInheritance(array $config, array $fullConfig): array + { + if (!empty($config['routes'])) { + foreach ($config['routes'] as $routeName => $value) { + $config['routes'][$routeName]['route_path'] = $routeName; + if (!empty($value['inherit'])) { + $default = $config['defaults'][$value['inherit']] ?? []; + $config['routes'][$routeName] = array_merge($config['routes'][$routeName], $default); + unset($config['routes'][$routeName]['inherit']); + } + } + } + return $config; + } + + private function insertRelated(array $config, array $fullConfig): array + { + if (!empty($config['routes'])) { + foreach ($config['routes'] as $routeName => $value) { + if (!empty($value['links'])) { + foreach ($value['links'] as $i => $linkedRoute) { + $split = explode(':', $linkedRoute); + if (!empty($fullConfig[$split[0]]['routes'][$linkedRoute])) { + $linkedRouteObject = $fullConfig[$split[0]]['routes'][$linkedRoute]; + if (!empty($linkedRouteObject)) { + $config['routes'][$routeName]['links'][$i] = $linkedRouteObject; + continue; + } + } + unset($config['routes'][$routeName]['links'][$i]); + } + } + if (!empty($value['actions'])) { + foreach ($value['actions'] as $i => $linkedRoute) { + $split = explode(':', $linkedRoute); + if (!empty($fullConfig[$split[0]]['routes'][$linkedRoute])) { + $linkedRouteObject = $fullConfig[$split[0]]['routes'][$linkedRoute]; + if (!empty($linkedRouteObject)) { + $config['routes'][$routeName]['actions'][$i] = $linkedRouteObject; + continue; + } + } + unset($config['routes'][$routeName]['actions'][$i]); + } + } + } + } + return $config; + } + + public function genBreadcrumb(): array + { + $fullConfig = [ + 'broods' => [ + 'defaults' => [ + 'depth-1' => [ + 'after' => 'broods:index', + 'textGetter' => 'name', + 'links' => [ + 'broods:view', + 'broods:edit', + 'local_tools:brood_tools', + ], + 'actions' => [ + 'broods:delete', + ], + ] + ], + 'routes' => [ + 'broods:index' => [ + 'label' => __('Broods'), + 'icon' => 'network-wired', + 'url' => '/broods/index', + ], + 'broods:view' => [ + 'label' => __('View'), + 'inherit' => 'depth-1', + 'url' => '/broods/view/{{id}}', + 'url_vars' => ['id' => 'id'], + ], + 'broods:edit' => [ + 'label' => __('Edit'), + 'inherit' => 'depth-1', + 'url' => '/broods/edit/{{id}}', + 'url_vars' => ['id' => 'id'], + ], + 'broods:delete' => [ + 'label' => __('Delete'), + 'inherit' => 'depth-1', + 'url' => '/broods/delete/{{id}}', + 'url_vars' => ['id' => 'id'], + ], + ] + ], + 'local_tools' => [ + 'routes' => [ + 'local_tools:brood_tools' => [ + 'label' => __('Brood Tools'), + 'icon' => 'tools', + 'url' => '/localTools/broodTools/{{id}}', + 'url_vars' => ['id' => 'id'], + ] + ] + ] + ]; + foreach ($fullConfig as $controller => $config) { + $fullConfig[$controller] = $this->insertInheritance($config, $fullConfig); + } + foreach ($fullConfig as $controller => $config) { + $fullConfig[$controller] = $this->insertRelated($config, $fullConfig); + } + return $fullConfig; + + + // return [ + // 'Broods' => [ + // 'index' => [ + // 'label' => 'Broods', + // 'icon' => 'network-wired', + // 'url' => ['controller' => 'Broods', 'action' => 'index'], + // 'children' => [ + // 'view' => [ + // 'textGetter' => 'name', + // 'url' => ['controller' => 'Broods', 'action' => 'view', 'argsGetter' => ['id']], + // 'links' => [ + // 'view' => [ + // 'label' => __('View'), + // 'url' => ['controller' => 'Broods', 'action' => 'view', 'argsGetter' => ['id']], + // ], + // 'local_tools' => [ + // 'textGetter' => 'name', + // 'url' => ['controller' => 'Broods', 'action' => 'delete', 'argsGetter' => ['id']], + // ], + // ], + // 'actions' => [ + // 'edit' => [ + // 'label' => __('Edit'), + // 'url' => ['controller' => 'Broods', 'action' => 'edit', 'argsGetter' => ['id']], + // ], + // 'delete' => [ + // 'label' => __('Delete'), + // 'url' => ['controller' => 'Broods', 'action' => 'delete', 'argsGetter' => ['id']], + // ], + // ], + // ], + // ], + // ], + // ], + // ]; + } +} \ No newline at end of file diff --git a/src/View/Helper/DataFromPathHelper.php b/src/View/Helper/DataFromPathHelper.php index 16a4209..8df0cb0 100644 --- a/src/View/Helper/DataFromPathHelper.php +++ b/src/View/Helper/DataFromPathHelper.php @@ -49,7 +49,7 @@ class DataFromPathHelper extends Helper if (empty($strArg['function'])) { $varValue = $options['sanitize'] ? h($varValue) : $varValue; } - $extractedVars[] = $varValue; + $extractedVars[$i] = $varValue; } foreach ($extractedVars as $i => $value) { $value = $options['highlight'] ? "${value}" : $value; diff --git a/templates/element/layouts/header.php b/templates/element/layouts/header.php index 86eaffc..399c4db 100644 --- a/templates/element/layouts/header.php +++ b/templates/element/layouts/header.php @@ -1,6 +1,8 @@