pluginPath = dirname(__DIR__, 3); // Load the admin configuration $this->config = Factory::getInstance('config'); $this->config->addComponentPath($this->pluginPath, Priority::LOWEST); $adminCFG = $this->config->getConfig('admin'); // If admin is not enabled, stop here if (!$adminCFG->get('admin_enabled')) return; // Register the event Events::addListener([$this, 'routeWebRequestEventListener'], 'routeWebRequestEvent', Priority::NORMAL); // Now load the pluginKey $this->pluginKey = $adminCFG->get('admin_url'); // Settle admin URL $webURL = $this->config->getConfig("web")->get("base_url"); $this->adminURL = $webURL . "/" . $this->pluginKey; // Determine routing paths $this->webRoute = '^' . $this->pluginKey . '(|\/(?P.*?)(|\/(?P.*?)(|\/(?P.*?))))'; $this->apiRoute = '^' . $this->pluginKey . '(|\/(?P.*?)(|\/(?P.*?)(|\/(?P.*?)))).json'; // Load the dependencies $this->router = Factory::getInstance('router'); $this->models = Factory::getInstance('models'); $this->views = Factory::getInstance('views'); $this->controllers = Factory::getInstance('controllers'); } /** * @param RouteWebRequestEvent $event * @return void * @throws FactoryException * @throws WebException */ public function routeWebRequestEventListener(RouteWebRequestEvent $event): void { Logger::log("Administration: observed web request. Activating plugin."); // If it does, register everything /** @var Resources $resources */ $resources = Factory::getInstance('resources'); // Serve the AdminLTE distribution files $adminLTE = dirname(Core::$coreDir, 4) . DS . 'vendor' . DS . 'almasaeed2010' . DS . 'adminlte'; $resources->registerResources('admin/lte_dist', $adminLTE . DS . 'dist'); $resources->registerResources('admin/lte_plugins', $adminLTE . DS . 'plugins'); $resources->registerResources("admin/admin_dist", $this->pluginPath . DS . 'www' . DS . 'dist'); // @todo TEMPORARY!! //$resources->registerResources("lte_top", $adminLTE); // And serve the actual pages $this->router->addRoute('^' . $this->pluginKey, ['viewName' => 'dashboard', 'callable' => [$this, 'adminCallable']], Priority::HIGHEST); $this->router->addRoute($this->apiRoute, ['callable' => [$this, 'adminCallable']], Priority::HIGH); $this->router->addRoute($this->webRoute, ['callable' => [$this, 'adminCallable']], Priority::HIGH); } /** * @param array $matches * @param array $routeData * @param string $routeString * @return string|null * @throws EventException * @throws FactoryException * @throws LayoutException * @throws OutputException */ public function adminCallable(array $matches, array $routeData, string $routeString): ?string { Logger::log("AdminCallable called. Loading admin page."); // Add componentPaths for models, views, controllers $this->models->addComponentPath($this->pluginPath . DS . 'models' . DS . 'main', Priority::LOW); $this->views->addComponentPath($this->pluginPath . DS . 'views' . DS . 'main', Priority::LOW); $this->controllers->addComponentPath($this->pluginPath . DS . 'controllers' . DS . 'main', Priority::LOW); // Load layouts and assign componentPath, in case the loaded view needs access to it /** @var Layout $layouts */ $layouts = Factory::getInstance('layouts'); $layouts->addComponentPath($this->pluginPath . DS . 'layouts', Priority::LOW); // And add a layoutLoadEventListener, to add global administration variables Events::addListener([$this, 'layoutLoadEventListener'], 'layoutLoadEvent', Priority::HIGH); Events::addListener([$this, 'verifyViewPermissions'], 'routerCallViewEvent', Priority::HIGH); Events::addListener([$this, 'logViewNameAndIcon'], 'routerCallViewEvent', Priority::NORMAL); if (class_exists("\FuzeWorks\Async\Tasks")) { // Mark async to be loaded $this->loadAsync = true; // Add componentPaths for models, views, controllers $this->models->addComponentPath($this->pluginPath . DS . 'models' . DS . 'async', Priority::LOW); $this->views->addComponentPath($this->pluginPath . DS . 'views' . DS . 'async', Priority::LOW); $this->controllers->addComponentPath($this->pluginPath . DS . 'controllers' . DS . 'async', Priority::LOW); } // Load the current session /** @var Output $output */ $this->authPlugin = Factory::getInstance("plugins")->get('auth'); $output = Factory::getInstance("output"); // Redirect the user to login if they're not logged in $this->session = $this->authPlugin->sessions->start(); if ($this->session->user->id === "0") { $output->setHeader("Location: " . $this->authPlugin->getAuthenticationURL() . "/login?location=" . $this->getAdminURL()); return ""; } // Let's generate the sidebar $finder = new PageFinder(); $sidebar = $finder->generateSidebar($this->session); $footer = ""; // Afterwards, pass on to the admin view and its contents Logger::log("Forwarding request to Router::defaultCallable()."); // Determine viewType if ($routeString === $this->apiRoute) $routeData['viewType'] = 'AdminAPI'; else $routeData['viewType'] = 'admin'; try { // Fetch content from requested view $content = $this->router->defaultCallable($matches, $routeData, $routeString); // If content is false, nothing is found and should return 404 if (is_bool($content) && $content === false) { $output->setStatusHeader(404); $content = $layouts->get("main/error404"); $this->selectedMethodName = "Not found"; } // And check for footer code if (!empty($this->footerMethod)) { if (!method_exists($this->selectedView, $this->footerMethod)) throw new Exception("Could not load view. Requested footerMethod not found."); $footer = call_user_func_array([$this->selectedView, $this->footerMethod], []); } } catch (HaltException $e) { $output->setStatusHeader(403); $content = $layouts->get("main/error403"); $this->selectedMethodName = "Unauthorized"; } catch (Exception|\Throwable $e) { Logger::exceptionHandler($e, false); $output->setStatusHeader(500); $content = $layouts->get("main/error500"); $this->selectedMethodName = "Fatal error"; } // If an api is requested, return the content as a json object if ($routeData['viewType'] === 'AdminAPI') { $output->setContentType('json'); return json_encode($content); } // Otherwise, load the panel and return that Logger::log("Generating panel wrapper."); $layouts->reset(false); $layouts->assign("pageTitle", $this->selectedMethodName); $layouts->assign("pageIcon", $this->selectedMethodIcon); $layouts->assign('sidebar', $sidebar); $layouts->assign('content', $content); $layouts->assign("footer", $footer); return $layouts->get('main/panel'); } public function getAdminURL(): string { return $this->adminURL; } /** * Listener for LayoutLoadEvent * * Assigns variables to Layout that this plugin's layouts may require. * * @param LayoutLoadEvent $event */ public function layoutLoadEventListener(LayoutLoadEvent $event) { $event->assign('adminKey', $this->pluginKey); $event->assign('session', $this->session); $event->assign('authURL', $this->authPlugin->getAuthenticationURL()); $event->assign("loadAsync", $this->loadAsync); } /** * Listener for RouterCallViewEvent * * Verifies that the current user has access to the requested methods. * * @param RouterCallViewEvent $event * @return RouterCallViewEvent * @throws ReflectionException * @throws HaltException */ public function verifyViewPermissions(RouterCallViewEvent $event): RouterCallViewEvent { // Load reflector $reflector = new ReflectionClass(get_class($event->view)); // Pass over each requested method for ($i = Priority::getHighestPriority(); $i <= Priority::getLowestPriority(); $i++) { if (!isset($event->viewMethods[$i])) continue; foreach ($event->viewMethods[$i] as $key => $method) { // If the method doesn't exist, skip it in general if (!method_exists($event->view, $method)) { unset($event->viewMethods[$i][$key]); continue; } // If the method exists, verify permission $methodReflector = $reflector->getMethod($method); $permissionAttributes = $methodReflector->getAttributes(PermissionAttribute::class); if (!empty($permissionAttributes)) { // Fetch nodes $nodes = $permissionAttributes[0]->newInstance()->getValue(); // Check if any of the nodes are permitted $found = false; foreach ($nodes as $node) if ($this->session->hasPermission($node)) $found = true; // If not, skip if (!$found) { Logger::logWarning("Current user does not have permission for the requested method. Blocking."); $event->setCancelled(true); } } } } return $event; } protected string $selectedMethodName = ""; protected string $selectedMethodIcon = ""; protected string $footerMethod = ""; protected View $selectedView; public function logViewNameAndIcon(RouterCallViewEvent $event): RouterCallViewEvent { $reflector = new ReflectionClass(get_class($event->view)); $this->selectedView = $event->view; for ($i = Priority::getHighestPriority(); $i <= Priority::getLowestPriority(); $i++) { if (!isset($event->viewMethods[$i])) continue; foreach ($event->viewMethods[$i] as $key => $method) { $methodReflector = $reflector->getMethod($method); // Check for display attribute. $displayAttributes = $methodReflector->getAttributes(DisplayAttribute::class); $iconAttributes = $methodReflector->getAttributes(IconAttribute::class); $footerAttributes = $methodReflector->getAttributes(FooterCodeMethodAttribute::class); $this->selectedMethodName = !empty($displayAttributes) ? $displayAttributes[0]->newInstance()->getValue() : ucfirst($methodReflector->getName()); $this->selectedMethodIcon = !empty($iconAttributes) ? $iconAttributes[0]->newInstance()->getValue() : ""; $this->footerMethod = !empty($footerAttributes) ? $footerAttributes[0]->newInstance()->getvalue() : ""; } } return $event; } /** * @inheritDoc */ public function getName(): string { return 'admin'; } /** * @inheritDoc */ public function getClassesPrefix(): ?string { return null; } /** * @inheritDoc */ public function getSourceDirectory(): ?string { return null; } /** * @inheritDoc */ public function getPluginClass(): ?string { return null; } }