420 lines
16 KiB
PHP
Executable File
420 lines
16 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* FuzeWorks Framework Administration Plugin.
|
|
*
|
|
* The FuzeWorks PHP FrameWork
|
|
*
|
|
* Copyright (C) 2013-2020 i15
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*
|
|
* @author i15
|
|
* @copyright Copyright (c) 2013 - 2020, i15. (https://i15.nl)
|
|
* @license https://opensource.org/licenses/MIT MIT License
|
|
*
|
|
* @since Version 1.3.0
|
|
*
|
|
* @version Version 1.3.0
|
|
*/
|
|
|
|
namespace FuzeWorks\Administration;
|
|
use FuzeWorks\Administration\Attributes\DisplayAttribute;
|
|
use FuzeWorks\Administration\Attributes\FooterCodeMethodAttribute;
|
|
use FuzeWorks\Administration\Attributes\IconAttribute;
|
|
use FuzeWorks\Administration\Attributes\PermissionAttribute;
|
|
use FuzeWorks\Authentication\AuthenticationPlugin;
|
|
use FuzeWorks\Authentication\Model\Session;
|
|
use FuzeWorks\Config;
|
|
use FuzeWorks\ConfigORM\ConfigORM;
|
|
use FuzeWorks\Controllers;
|
|
use FuzeWorks\Core;
|
|
use FuzeWorks\Event\LayoutLoadEvent;
|
|
use FuzeWorks\Event\RouterCallViewEvent;
|
|
use FuzeWorks\Event\RouteWebRequestEvent;
|
|
use FuzeWorks\Events;
|
|
use FuzeWorks\Exception\ConfigException;
|
|
use FuzeWorks\Exception\EventException;
|
|
use FuzeWorks\Exception\Exception;
|
|
use FuzeWorks\Exception\FactoryException;
|
|
use FuzeWorks\Exception\HaltException;
|
|
use FuzeWorks\Exception\LayoutException;
|
|
use FuzeWorks\Exception\OutputException;
|
|
use FuzeWorks\Exception\WebException;
|
|
use FuzeWorks\Factory;
|
|
use FuzeWorks\iPluginHeader;
|
|
use FuzeWorks\Layout;
|
|
use FuzeWorks\Logger;
|
|
use FuzeWorks\Models;
|
|
use FuzeWorks\Output;
|
|
use FuzeWorks\Priority;
|
|
use FuzeWorks\Resources;
|
|
use FuzeWorks\Router;
|
|
use FuzeWorks\View;
|
|
use FuzeWorks\Views;
|
|
use ReflectionClass;
|
|
use ReflectionException;
|
|
|
|
class AdminPlugin implements iPluginHeader
|
|
{
|
|
|
|
private Config $config;
|
|
private Router $router;
|
|
private Models $models;
|
|
private Views $views;
|
|
private Controllers $controllers;
|
|
private AuthenticationPlugin $authPlugin;
|
|
|
|
private string $pluginKey = 'admin';
|
|
private string $pluginPath;
|
|
private string $adminURL;
|
|
private string $apiRoute;
|
|
private string $webRoute;
|
|
|
|
private ?Session $session = null;
|
|
private bool $loadAsync = false;
|
|
|
|
/**
|
|
* @inheritDoc
|
|
* @throws EventException
|
|
* @throws FactoryException
|
|
* @throws ConfigException
|
|
*/
|
|
public function init()
|
|
{
|
|
// Make a listener for a web request
|
|
$this->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<viewName>.*?)(|\/(?P<viewMethod>.*?)(|\/(?P<viewParameters>.*?))))';
|
|
$this->apiRoute = '^' . $this->pluginKey . '(|\/(?P<viewName>.*?)(|\/(?P<viewMethod>.*?)(|\/(?P<viewParameters>.*?)))).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;
|
|
}
|
|
} |