All changes as implemented over the last 3 years.

Pushes Administration into a workable state.
This commit is contained in:
Abel Hoogeveen 2023-02-08 12:57:10 +01:00
parent 50608c18e7
commit 63ea659d5b
38 changed files with 2396 additions and 1232 deletions

6
Dockerfile Normal file → Executable file
View File

@ -1,10 +1,6 @@
FROM php:7.3-apache FROM registry.i15.nl/i15/fuzephp:8.1-apache
MAINTAINER i15 <abel@i15.nl> MAINTAINER i15 <abel@i15.nl>
# Install PHP Deps
# First PDO and MySQL
RUN docker-php-ext-install mysqli pdo_mysql opcache
# Write application to image # Write application to image
ENV APACHE_DOCUMENT_ROOT /usr/src/admin/www ENV APACHE_DOCUMENT_ROOT /usr/src/admin/www
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf

2
LICENSE Normal file → Executable file
View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2013-2020 i15 Copyright (c) 2013-2023 i15
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

22
composer.json Normal file → Executable file
View File

@ -1,7 +1,6 @@
{ {
"name": "fuzeworks/administration", "name": "fuzeworks/administration",
"description": "description", "description": "Administration interface for FuzeWorks",
"minimum-stability": "stable",
"license": ["MIT"], "license": ["MIT"],
"authors": [ "authors": [
{ {
@ -10,19 +9,16 @@
} }
], ],
"require": { "require": {
"php": ">=7.2.0", "php": ">=8.1.0",
"fuzeworks/core": "~1.2", "fuzeworks/webcomponent": "~1.3.0",
"fuzeworks/mvcr": "~1.2", "fuzeworks/authentication": "~1.3.0",
"fuzeworks/webcomponent": "~1.2", "fuzeworks/layout": "~1.3.0",
"fuzeworks/layout": "~1.2", "latte/latte": "~2.5",
"fuzeworks/database": "~1.2", "almasaeed2010/adminlte": "^3"
"latte/latte": "^2",
"almasaeed2010/adminlte": "^3",
"phpdocumentor/reflection-docblock": "^5"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9", "fuzeworks/tracycomponent": "~1.3.0",
"fuzeworks/tracycomponent": "~1.2" "phpunit/phpunit": "^9"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

0
config.admin.php Normal file → Executable file
View File

View File

@ -1,10 +1,10 @@
<?php <?php
/** /**
* FuzeWorks Framework Administration Plugin. * i15Site.
* *
* The FuzeWorks PHP FrameWork * The FuzeWorks PHP FrameWork
* *
* Copyright (C) 2013-2020 i15 * Copyright (C) 2013-2022 i15
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -25,18 +25,18 @@
* SOFTWARE. * SOFTWARE.
* *
* @author i15 * @author i15
* @copyright Copyright (c) 2013 - 2020, i15. (https://i15.nl) * @copyright Copyright (c) 2013 - 2022, i15. (http://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License * @license https://opensource.org/licenses/MIT MIT License
* *
* @since Version 1.3.0 * @link https://i15.nl
* @since Version 2.0.0
* *
* @version Version 1.3.0 * @version Version 2.0.0
*/ */
namespace Application\Controller; namespace Application\Controller;
use FuzeWorks\WebController; use FuzeWorks\WebController;
class IndexController extends WebController class AsyncController extends WebController
{ {
} }

View File

@ -0,0 +1,36 @@
<?php
namespace Application\Controller;
use FuzeWorks\Factory;
use FuzeWorks\ObjectStorage\ObjectStorageComponent;
use FuzeWorks\WebController;
class CacheController extends WebController
{
public function getCacheItems(int $index = 1, int $pageSize = 50): ?array
{
// Get storage
/** @var ObjectStorageComponent $storage */
$storage = Factory::getInstance("storage");
$store = $storage->getStorage();
// First retrieve the index
$objectIndex = $store->getIndex();
$out = [];
for ($i = $index; $i < ($index + $pageSize); $i++)
{
if (!isset($objectIndex[$i]))
continue;
$out[] = $store->getItem($objectIndex[$i]);
//$out[] = $store->getItemMeta($objectIndex[$i]);
}
return $out;
}
}

View File

@ -33,23 +33,18 @@
* @version Version 1.3.0 * @version Version 1.3.0
*/ */
namespace Application\View; namespace Application\Controller;
use FuzeWorks\Administration\AdminView; use FuzeWorks\Administration\Attributes\DashboardAttribute;
use FuzeWorks\Administration\PageFinder;
use FuzeWorks\WebController;
class IndexAdminView extends AdminView class DashboardController extends WebController
{ {
/** public function populateDashboard()
* Hello!
*
* This does a lot of things
*
* @display Dashboard
* @icon tachometer-alt
* @priority Priority::HIGHEST
*/
public function index()
{ {
$finder = new PageFinder();
$views = $finder->findViewsWithAttribute(DashboardAttribute::class);
} }
} }

View File

@ -34,7 +34,9 @@
*/ */
namespace Application\Controller; namespace Application\Controller;
use FuzeWorks\Core; use FuzeWorks\Core;
use FuzeWorks\Exception\NotFoundException;
use FuzeWorks\Priority; use FuzeWorks\Priority;
use FuzeWorks\WebController; use FuzeWorks\WebController;
@ -48,24 +50,22 @@ class SettingsController extends WebController
for ($i = Priority::getHighestPriority(); $i <= Priority::getLowestPriority(); $i++) for ($i = Priority::getHighestPriority(); $i <= Priority::getLowestPriority(); $i++)
$paths[$i] = $this->config->getComponentPaths($i); $paths[$i] = $this->config->getComponentPaths($i);
// And add the CorePath
$paths[Priority::getLowestPriority()][] = Core::$coreDir . DS . 'Config';
// Then go over every individual paths // Then go over every individual paths
$configFiles = []; $configFiles = [];
foreach ($paths as $priority => $foldersArray) foreach ($paths as $priority => $foldersArray) {
{ foreach ($foldersArray as $folder) {
foreach ($foldersArray as $folder)
{
// Get the contents from the folder // Get the contents from the folder
$contents = array_diff(scandir($folder), array('..', '.')); $contents = array_diff(scandir($folder), array('..', '.'));
// Then go over the folders, and see which ones are an admin view // Then go over the folders, and see which ones are an admin view
foreach ($contents as $file) foreach ($contents as $file) {
{
// If the file matches the expected filename, add it to the list // If the file matches the expected filename, add it to the list
if (substr($file, 0, 7) === 'config.' && !isset($configFiles[$file])) if (str_starts_with($file, 'config.')) {
$configFiles[$file] = ['folder' => $folder, 'priority' => substr(Priority::getPriority($priority), 10)]; if (!isset($configFiles[$file]))
$configFiles[$file] = [];
$configFiles[$file][] = ['folder' => $folder, 'priority' => substr(Priority::getPriority($priority), 10)];
}
} }
} }
} }
@ -77,4 +77,54 @@ class SettingsController extends WebController
return $configFiles; return $configFiles;
} }
/**
* Fetch a file
*
* @param string $fileName
* @return array
* @throws NotFoundException
*/
public function fetchFile(string $fileName, int $selector): array
{
// Fetch files
$files = $this->findSettingsFiles();
// Check if the file exists
if (!isset($files[$fileName]))
throw new NotFoundException("The requested settings file could not be found.");
// Select correct data
$meta = $files[$fileName][$selector];
// Find the file
$file = $meta['folder'] . DS . $fileName;
if (!file_exists($file))
throw new NotFoundException("The requested settings file could not be found.");
// Load the raw data
$raw = file_get_contents($file);
// And try to load the editable data
$data = include($file);
// Prepare output variable
return ['name' => $fileName, 'directory' => $meta['folder'], 'raw' => $raw, 'data' => $data];
}
protected function verifyFileData(array $fileData): bool
{
foreach ($fileData as $key => $val) {
switch (gettype($val)) {
case 'array':
case 'object':
case 'resource':
case 'unknown type':
return false;
break;
}
}
return true;
}
} }

View File

19
layouts/components/settings/layout.overview.latte Normal file → Executable file
View File

@ -1,5 +1,5 @@
<div class="row"> <div class="row">
<div class="col-xl-12"> <div class="col-12">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3 class="card-title">Configuration Files</h3> <h3 class="card-title">Configuration Files</h3>
@ -18,14 +18,16 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{var $it = 0}
{foreach $files as $fileName => $data} {foreach $files as $fileName => $data}
<tr> <tr n:foreach="$data as $entry">
<td>{$iterator->getCounter()}</td> {var $it = $it + 1}
<td>{$it}</td>
<td>{$fileName}</td> <td>{$fileName}</td>
<td>{$data['folder']}</td> <td>{$entry['folder']}</td>
<td>{$data['priority']}</td> <td>{$entry['priority']}</td>
<td><button type="button" class="btn btn-block btn-success">VIEW</button></td> <td><a href="/{$adminKey}/settings/view/{$fileName}/{$iterator->counter0}"><button type="button" class="btn btn-block btn-success">VIEW</button></a></td>
<td><button type="button" class="btn btn-block btn-primary">EDIT</button></td> <td><a href="/{$adminKey}/settings/modify/{$fileName}/{$iterator->counter0}"><button type="button" class="btn btn-block btn-primary">EDIT</button></a></td>
</tr> </tr>
{/foreach} {/foreach}
</tbody> </tbody>
@ -35,6 +37,3 @@
</div> </div>
</div> </div>
</div> </div>
</div>

View File

@ -0,0 +1,63 @@
{varType FuzeWorks\Forms\Form $form}
{varType FuzeWorks\Forms\Field $field}
<div class="row">
<div class="col-12">
<!-- Custom Tabs -->
<div class="card">
<div class="card-header d-flex p-0">
<h3 class="card-title p-3">{$config['name']}</h3>
<ul class="nav nav-pills ml-auto p-2">
{ifset $config['data']}
<li class="nav-item">
<a class="nav-link active" href="#data" data-toggle="tab">Data</a>
</li>
{/ifset}
<li class="nav-item"><a class="nav-link {$config['data'] ? '' : 'active'}" href="#raw" data-toggle="tab">Raw</a></li>
</ul>
</div><!-- /.card-header -->
<div class="card-body">
<div class="tab-content">
{foreach $form->getFields() as $field}
{switch get_class($field)}
{case "FuzeWorks\Forms\Fields\HiddenField", "FuzeWorks\Forms\Fields\SubmitField"}
{$field|noescape}
{case "FuzeWorks\Forms\Fields\CheckboxField"}
<div class="form-check">
{$field->addClass("form-check-input")|noescape}
<label for="{$field->getId()}" class="form-check-label">{$field->getLabel()}</label>
{if $field->isValidated() && !$field->isValid()}
<small class="text-danger">{$field->getErrors()|implode}</small>
{/if}
</div>
{default}
<div class="form-group">
<label for="{$field->getId()}">{$field->getLabel()}</label>
{if $field->isValidated() && !$field->isValid()}
{$field->class(["form-control", "is-invalid"])|noescape}
<small class="text-danger">{$field->getErrors()|implode}</small>
{elseif $field->isValidated() && $field->isValid()}
{$field->class(["form-control", "is-valid"])|noescape}
{else}
{$field->addClass("form-control")|noescape}
{/if}
</div>
{/switch}
{/foreach}
<div class="tab-pane {$config['data'] ? '' : 'active'}" id="raw">
<textarea disabled style="min-width: 100%; height: 100%" oninput='resizeRawArea(this)'>{$config['raw']}</textarea>
</div>
<!-- /.tab-pane -->
</div>
<!-- /.tab-content -->
</div><!-- /.card-body -->
</div>
<!-- ./card -->
</div>
<!-- /.col -->
</div>
<script>
function resizeRawArea(jq_in) {
jq_in.style.height = "";
jq_in.style.height = jq_in.scrollHeight + 3 + "px";
}
</script>

View File

@ -0,0 +1,12 @@
{varType FuzeWorks\Forms\Form $form}
<div class="col-md-6">
<div class="card card-primary">
<div class="card card-header">
<h3 class="card-title">{$form->getLabel()}</h3>
</div>
<div class="card-body">
{$form|noescape}
</div>
</div>
</div>

View File

@ -0,0 +1,13 @@
<div class="error-page">
<h2 class="headline text-danger">403</h2>
<div class="error-content">
<h3><i class="fas fa-exclamation-triangle text-danger"></i> Unauthorized! </h3>
<p>
You do not have permission to view that page.
Meanwhile, you may <a href="/{$adminKey}">return to dashboard</a>.
</p>
</div>
</div>
<!-- /.error-page -->

View File

@ -0,0 +1,14 @@
<div class="error-page">
<h2 class="headline text-warning"> 404</h2>
<div class="error-content">
<h3><i class="fas fa-exclamation-triangle text-warning"></i> Oops! Page not found.</h3>
<p>
We could not find the page you were looking for.
Meanwhile, you may <a href="/{$adminKey}">return to dashboard</a>.
</p>
</div>
<!-- /.error-content -->
</div>
<!-- /.error-page -->

View File

@ -0,0 +1,13 @@
<div class="error-page">
<h2 class="headline text-danger">500</h2>
<div class="error-content">
<h3><i class="fas fa-exclamation-triangle text-danger"></i> Oops! Something went wrong.</h3>
<p>
We will work on fixing that right away.
Meanwhile, you may <a href="/{$adminKey}">return to dashboard</a>.
</p>
</div>
</div>
<!-- /.error-page -->

212
layouts/main/layout.panel.latte Normal file → Executable file
View File

@ -1,29 +1,17 @@
{varType FuzeWorks\Authentication\Model\Session $session}
{varType bool $loadAsync}
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>AdminLTE 3 | Dashboard</title> <title>{$serverName} | {$pageTitle}</title>
<!-- Tell the browser to be responsive to screen width --> <!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Font Awesome --> <!-- Font Awesome -->
<link rel="stylesheet" href="/{$adminKey}/plugins/fontawesome-free/css/all.min.css"> <link rel="stylesheet" href="/{$adminKey}/lte_plugins/fontawesome-free/css/all.min.css">
<!-- Ionicons -->
<link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
<!-- Tempusdominus Bbootstrap 4 -->
<link rel="stylesheet" href="/{$adminKey}/plugins/tempusdominus-bootstrap-4/css/tempusdominus-bootstrap-4.min.css">
<!-- iCheck -->
<link rel="stylesheet" href="/{$adminKey}/plugins/icheck-bootstrap/icheck-bootstrap.min.css">
<!-- JQVMap -->
<link rel="stylesheet" href="/{$adminKey}/plugins/jqvmap/jqvmap.min.css">
<!-- Theme style --> <!-- Theme style -->
<link rel="stylesheet" href="/{$adminKey}/dist/css/adminlte.min.css"> <link rel="stylesheet" href="/{$adminKey}/lte_dist/css/adminlte.min.css">
<!-- overlayScrollbars -->
<link rel="stylesheet" href="/{$adminKey}/plugins/overlayScrollbars/css/OverlayScrollbars.min.css">
<!-- Daterange picker -->
<link rel="stylesheet" href="/{$adminKey}/plugins/daterangepicker/daterangepicker.css">
<!-- summernote -->
<link rel="stylesheet" href="/{$adminKey}/plugins/summernote/summernote-bs4.css">
<!-- Google Font: Source Sans Pro --> <!-- Google Font: Source Sans Pro -->
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet">
</head> </head>
@ -38,113 +26,46 @@
<a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a> <a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a>
</li> </li>
<li class="nav-item d-none d-sm-inline-block"> <li class="nav-item d-none d-sm-inline-block">
<a href="/" class="nav-link">Home</a> <a href="/{$adminKey}" class="nav-link">Dashboard</a>
</li> </li>
</ul> </ul>
<!-- SEARCH FORM -->
<form class="form-inline ml-3">
<div class="input-group input-group-sm">
<input class="form-control form-control-navbar" type="search" placeholder="Search" aria-label="Search">
<div class="input-group-append">
<button class="btn btn-navbar" type="submit">
<i class="fas fa-search"></i>
</button>
</div>
</div>
</form>
<!-- Right navbar links --> <!-- Right navbar links -->
<ul class="navbar-nav ml-auto"> <ul class="navbar-nav ml-auto">
<!-- Messages Dropdown Menu --> {if $loadAsync}
<li class="nav-item dropdown"> <li id="asyncWidget" class="nav-item dropdown">
<a class="nav-link" data-toggle="dropdown" href="#"> <a class="nav-link" data-toggle="dropdown" href="#">
<i class="far fa-comments"></i> <i class="fa fa-list"></i>
<span class="badge badge-danger navbar-badge">3</span> <span class="badge badge-warning navbar-badge">0</span>
</a> </a>
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-right"> <div class="dropdown-menu dropdown-menu-lg dropdown-menu-right">
<a href="#" class="dropdown-item"> <span class="dropdown-item dropdown-header">Asynchronous tasks</span>
<!-- Message Start -->
<div class="media">
<img src="/{$adminKey}/dist/img/user1-128x128.jpg" alt="User Avatar" class="img-size-50 mr-3 img-circle">
<div class="media-body">
<h3 class="dropdown-item-title">
Brad Diesel
<span class="float-right text-sm text-danger"><i class="fas fa-star"></i></span>
</h3>
<p class="text-sm">Call me whenever you can...</p>
<p class="text-sm text-muted"><i class="far fa-clock mr-1"></i> 4 Hours Ago</p>
</div>
</div>
<!-- Message End -->
</a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a href="#" class="dropdown-item">
<!-- Message Start -->
<div class="media">
<img src="/{$adminKey}/dist/img/user8-128x128.jpg" alt="User Avatar" class="img-size-50 img-circle mr-3">
<div class="media-body">
<h3 class="dropdown-item-title">
John Pierce
<span class="float-right text-sm text-muted"><i class="fas fa-star"></i></span>
</h3>
<p class="text-sm">I got your message bro</p>
<p class="text-sm text-muted"><i class="far fa-clock mr-1"></i> 4 Hours Ago</p>
</div>
</div>
<!-- Message End -->
</a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a href="#" class="dropdown-item"> <a href="/{$adminKey}/async" class="dropdown-item dropdown-footer">See all historic tasks</a>
<!-- Message Start -->
<div class="media">
<img src="/{$adminKey}/dist/img/user3-128x128.jpg" alt="User Avatar" class="img-size-50 img-circle mr-3">
<div class="media-body">
<h3 class="dropdown-item-title">
Nora Silvester
<span class="float-right text-sm text-warning"><i class="fas fa-star"></i></span>
</h3>
<p class="text-sm">The subject goes here</p>
<p class="text-sm text-muted"><i class="far fa-clock mr-1"></i> 4 Hours Ago</p>
</div>
</div>
<!-- Message End -->
</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item dropdown-footer">See All Messages</a>
</div> </div>
</li> </li>
<!-- Notifications Dropdown Menu --> {/if}
<li class="nav-item dropdown"> <li class="nav-item dropdown user-menu">
<a class="nav-link" data-toggle="dropdown" href="#"> <a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown">
<i class="far fa-bell"></i> <img src="/{$adminKey}/lte_dist/img/user2-160x160.jpg" class="user-image img-circle elevation-2" alt="User Image">
<span class="badge badge-warning navbar-badge">15</span> <span class="d-none d-md-inline">{$session->user->primaryEmail}</span>
</a> </a>
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-right"> <ul class="dropdown-menu dropdown-menu-lg dropdown-menu-right">
<span class="dropdown-item dropdown-header">15 Notifications</span> <!-- User image -->
<div class="dropdown-divider"></div> <li class="user-header bg-primary">
<a href="#" class="dropdown-item"> <img src="/{$adminKey}/lte_dist/img/user2-160x160.jpg" class="img-circle elevation-2" alt="User Image">
<i class="fas fa-envelope mr-2"></i> 4 new messages <p>
<span class="float-right text-muted text-sm">3 mins</span> {$session->user->primaryEmail}
</a> <small>Member since -</small>
<div class="dropdown-divider"></div> </p>
<a href="#" class="dropdown-item">
<i class="fas fa-users mr-2"></i> 8 friend requests
<span class="float-right text-muted text-sm">12 hours</span>
</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item">
<i class="fas fa-file mr-2"></i> 3 new reports
<span class="float-right text-muted text-sm">2 days</span>
</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item dropdown-footer">See All Notifications</a>
</div>
</li> </li>
<li class="nav-item"> <!-- Menu Footer-->
<a class="nav-link" data-widget="control-sidebar" data-slide="true" href="#" role="button"> <li class="user-footer">
<i class="fas fa-th-large"></i> <a href="/{$adminKey}/profile" class="btn btn-default btn-flat">Profile</a>
</a> <a href="{$authURL}/logout" class="btn btn-default btn-flat float-right">Sign out</a>
</li>
</ul>
</li> </li>
</ul> </ul>
</nav> </nav>
@ -153,24 +74,14 @@
<!-- Main Sidebar Container --> <!-- Main Sidebar Container -->
<aside class="main-sidebar sidebar-dark-primary elevation-4"> <aside class="main-sidebar sidebar-dark-primary elevation-4">
<!-- Brand Logo --> <!-- Brand Logo -->
<a href="index3.html" class="brand-link"> <a href="/" class="brand-link">
<img src="/{$adminKey}/dist/img/AdminLTELogo.png" alt="AdminLTE Logo" class="brand-image img-circle elevation-3" <img src="/{$adminKey}/lte_dist/img/AdminLTELogo.png" alt="AdminLTE Logo" class="brand-image img-circle elevation-3"
style="opacity: .8"> style="opacity: .8">
<span class="brand-text font-weight-light">AdminLTE 3</span> <span class="brand-text font-weight-light">{$serverName}</span>
</a> </a>
<!-- Sidebar --> <!-- Sidebar -->
<div class="sidebar"> <div class="sidebar">
<!-- Sidebar user panel (optional) -->
<div class="user-panel mt-3 pb-3 mb-3 d-flex">
<div class="image">
<img src="/{$adminKey}/dist/img/user2-160x160.jpg" class="img-circle elevation-2" alt="User Image">
</div>
<div class="info">
<a href="#" class="d-block">Juris LLM</a>
</div>
</div>
<!-- Sidebar Menu --> <!-- Sidebar Menu -->
<nav class="mt-2"> <nav class="mt-2">
<ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false"> <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
@ -196,12 +107,12 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row mb-2"> <div class="row mb-2">
<div class="col-sm-6"> <div class="col-sm-6">
<h1 class="m-0 text-dark">Dashboard</h1> <h1 class="m-0 text-dark">{$pageTitle}</h1>
</div><!-- /.col --> </div><!-- /.col -->
<div class="col-sm-6"> <div class="col-sm-6">
<ol class="breadcrumb float-sm-right"> <ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="#">Home</a></li> <li class="breadcrumb-item"><a href="/{$adminKey}">Home</a></li>
<li class="breadcrumb-item active">Dashboard v1</li> <li class="breadcrumb-item active">{$pageTitle}</li>
</ol> </ol>
</div><!-- /.col --> </div><!-- /.col -->
</div><!-- /.row --> </div><!-- /.row -->
@ -219,50 +130,29 @@
</div> </div>
<!-- /.content-wrapper --> <!-- /.content-wrapper -->
<footer class="main-footer"> <footer class="main-footer">
<strong>Copyright &copy; 2013-2020 <a href="https://i15.nl">i15.nl</a>.</strong> <strong>Copyright &copy; 2013-{date('Y')} <a href="https://i15.nl">i15.nl</a>.</strong>
All rights reserved. All rights reserved.
<div class="float-right d-none d-sm-inline-block"> <div class="float-right d-none d-sm-inline-block">
<b>Version</b> 1.3.0 <b>Version</b> 1.3.0
</div> </div>
</footer> </footer>
<!-- Control Sidebar -->
<aside class="control-sidebar control-sidebar-dark">
<!-- Control sidebar content goes here -->
</aside>
<!-- /.control-sidebar -->
</div> </div>
<!-- ./wrapper --> <!-- ./wrapper -->
<!-- jQuery --> <!-- jQuery -->
<script src="/{$adminKey}/plugins/jquery/jquery.min.js"></script> <script src="/{$adminKey}/lte_plugins/jquery/jquery.min.js"></script>
<!-- jQuery UI 1.11.4 -->
<script src="/{$adminKey}/plugins/jquery-ui/jquery-ui.min.js"></script>
<!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip -->
<script>
$.widget.bridge('uibutton', $.ui.button)
</script>
<!-- Bootstrap 4 --> <!-- Bootstrap 4 -->
<script src="/{$adminKey}/plugins/bootstrap/js/bootstrap.bundle.min.js"></script> <script src="/{$adminKey}/lte_plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- ChartJS -->
<script src="/{$adminKey}/plugins/chart.js/Chart.min.js"></script>
<!-- Sparkline -->
<script src="/{$adminKey}/plugins/sparklines/sparkline.js"></script>
<!-- JQVMap -->
<script src="/{$adminKey}/plugins/jqvmap/jquery.vmap.min.js"></script>
<script src="/{$adminKey}/plugins/jqvmap/maps/jquery.vmap.usa.js"></script>
<!-- jQuery Knob Chart -->
<script src="/{$adminKey}/plugins/jquery-knob/jquery.knob.min.js"></script>
<!-- daterangepicker -->
<script src="/{$adminKey}/plugins/moment/moment.min.js"></script>
<script src="/{$adminKey}/plugins/daterangepicker/daterangepicker.js"></script>
<!-- Tempusdominus Bootstrap 4 -->
<script src="/{$adminKey}/plugins/tempusdominus-bootstrap-4/js/tempusdominus-bootstrap-4.min.js"></script>
<!-- Summernote -->
<script src="/{$adminKey}/plugins/summernote/summernote-bs4.min.js"></script>
<!-- overlayScrollbars -->
<script src="/{$adminKey}/plugins/overlayScrollbars/js/jquery.overlayScrollbars.min.js"></script>
<!-- AdminLTE App --> <!-- AdminLTE App -->
<script src="/{$adminKey}/dist/js/adminlte.js"></script> <script src="/{$adminKey}/lte_dist/js/adminlte.js"></script>
{if $loadAsync}
<script src="/{$adminKey}/admin_dist/async.ts"></script>
{/if}
<script>
$.widget.bridge('uibutton', $.ui.button);
</script>
{ifset $footer}
{$footer|noescape}
{/ifset}
</body> </body>
</html> </html>

332
src/FuzeWorks/Administration/AdminPlugin.php Normal file → Executable file
View File

@ -34,136 +34,252 @@
*/ */
namespace FuzeWorks\Administration; 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\Config;
use FuzeWorks\ConfigORM\ConfigORM; use FuzeWorks\ConfigORM\ConfigORM;
use FuzeWorks\Controllers; use FuzeWorks\Controllers;
use FuzeWorks\Core;
use FuzeWorks\Event\LayoutLoadEvent; use FuzeWorks\Event\LayoutLoadEvent;
use FuzeWorks\Event\RouterCallViewEvent;
use FuzeWorks\Event\RouteWebRequestEvent; use FuzeWorks\Event\RouteWebRequestEvent;
use FuzeWorks\Events; 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\Factory;
use FuzeWorks\iPluginHeader;
use FuzeWorks\Layout; use FuzeWorks\Layout;
use FuzeWorks\Logger; use FuzeWorks\Logger;
use FuzeWorks\Models; use FuzeWorks\Models;
use FuzeWorks\Output;
use FuzeWorks\Priority; use FuzeWorks\Priority;
use FuzeWorks\Resources; use FuzeWorks\Resources;
use FuzeWorks\Router; use FuzeWorks\Router;
use FuzeWorks\View;
use FuzeWorks\Views; use FuzeWorks\Views;
use ReflectionClass;
use ReflectionException;
class AdminPlugin implements \FuzeWorks\iPluginHeader class AdminPlugin implements iPluginHeader
{ {
private $pluginKey = 'admin'; private Config $config;
private Router $router;
private Models $models;
private Views $views;
private Controllers $controllers;
private AuthenticationPlugin $authPlugin;
/** @var Config */ private string $pluginKey = 'admin';
private $config; private string $pluginPath;
private string $adminURL;
private string $apiRoute;
private string $webRoute;
/** @var Router */ private ?Session $session = null;
private $router; private bool $loadAsync = false;
/** @var Models */
private $models;
/** @var Views */
private $views;
/** @var Controllers */
private $controllers;
/** @var ConfigORM */
private $adminCFG;
/**
* @var string
*/
private $pluginPath;
/** /**
* @inheritDoc * @inheritDoc
* @throws EventException
* @throws FactoryException
* @throws ConfigException
*/ */
public function init() public function init()
{ {
// Make a listener for a web request // Make a listener for a web request
Events::addListener([$this, 'routeWebRequestEventListener'], 'routeWebRequestEvent', Priority::NORMAL);
$this->pluginPath = dirname(__DIR__, 3); $this->pluginPath = dirname(__DIR__, 3);
}
public function routeWebRequestEventListener(RouteWebRequestEvent $event) // Load the admin configuration
{ $this->config = Factory::getInstance('config');
// If this request has nothing to do with the admin interface, don't bother routing the request $this->config->addComponentPath($this->pluginPath, Priority::LOWEST);
if (substr($event->uriString, 0, strlen($this->pluginKey)) !== $this->pluginKey) $adminCFG = $this->config->getConfig('admin');
// If admin is not enabled, stop here
if (!$adminCFG->get('admin_enabled'))
return; return;
Logger::log("Administration: observed admin request. Activating plugin."); // 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 // Load the dependencies
$this->config = Factory::getInstance('config');
$this->router = Factory::getInstance('router'); $this->router = Factory::getInstance('router');
$this->models = Factory::getInstance('models'); $this->models = Factory::getInstance('models');
$this->views = Factory::getInstance('views'); $this->views = Factory::getInstance('views');
$this->controllers = Factory::getInstance('controllers'); $this->controllers = Factory::getInstance('controllers');
}
// Load the admin configuration /**
$this->config->addComponentPath($this->pluginPath, Priority::LOWEST); * @param RouteWebRequestEvent $event
$this->adminCFG = $this->config->getConfig('admin'); * @return void
* @throws FactoryException
// If admin is not enabled, stop here * @throws WebException
if (!$this->adminCFG->get('admin_enabled')) */
return; public function routeWebRequestEventListener(RouteWebRequestEvent $event): void
{
// Now load the pluginKey Logger::log("Administration: observed web request. Activating plugin.");
$this->pluginKey = $this->adminCFG->get('admin_url');
// If it does, register everything // If it does, register everything
/** @var Resources $resources */ /** @var Resources $resources */
$resources = Factory::getInstance('resources'); $resources = Factory::getInstance('resources');
// Serve the AdminLTE distribution files // Serve the AdminLTE distribution files
$adminLTE = $this->pluginPath . DS . 'vendor' . DS . 'almasaeed2010' . DS . 'adminlte'; $adminLTE = dirname(Core::$coreDir, 4) . DS . 'vendor' . DS . 'almasaeed2010' . DS . 'adminlte';
$resources->registerResources('admin/dist', $adminLTE . DS . 'dist'); $resources->registerResources('admin/lte_dist', $adminLTE . DS . 'dist');
$resources->registerResources('admin/plugins', $adminLTE . DS . 'plugins'); $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 // And serve the actual pages
$routeString = '^' . $this->pluginKey . '(|\/(?P<viewName>.*?)(|\/(?P<viewMethod>.*?)(|\/(?P<viewParameters>.*?))))'; $this->router->addRoute('^' . $this->pluginKey, ['viewName' => 'dashboard', 'callable' => [$this, 'adminCallable']], Priority::HIGHEST);
$this->router->addRoute($routeString, ['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);
// And add componentPaths for models, views, controllers
$this->models->addComponentPath($this->pluginPath . DS . 'models', Priority::LOW);
$this->views->addComponentPath($this->pluginPath . DS . 'views', Priority::LOW);
$this->controllers->addComponentPath($this->pluginPath . DS . 'controllers', Priority::LOW);
} }
public function adminCallable(array $matches, string $routeString) /**
* @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."); 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 // Load layouts and assign componentPath, in case the loaded view needs access to it
/** @var Layout $layouts */ /** @var Layout $layouts */
$layouts = Factory::getInstance('layouts'); $layouts = Factory::getInstance('layouts');
$layouts->addComponentPath($this->pluginPath . DS . 'layouts', Priority::LOW); $layouts->addComponentPath($this->pluginPath . DS . 'layouts', Priority::LOW);
// And add a layoutLoadEventListener, to add global administration variables // And add a layoutLoadEventListener, to add global administration variables
Events::addListener([$this, 'layoutLoadEventListener'], 'layoutLoadEvent', Priority::NORMAL); 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 // Let's generate the sidebar
$finder = new PageFinder(); $finder = new PageFinder();
$sidebar = $finder->generateSidebar(); $sidebar = $finder->generateSidebar($this->session);
$footer = "";
// Afterwards, pass on to the admin view and its contents // Afterwards, pass on to the admin view and its contents
Logger::log("Forwarding request to Router::defaultCallable()."); Logger::log("Forwarding request to Router::defaultCallable().");
$matches['viewType'] = 'admin';
$content = $this->router->defaultCallable($matches, $routeString);
// Reset and assign // 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."); Logger::log("Generating panel wrapper.");
$layouts->reset(false); $layouts->reset(false);
$layouts->assign("pageTitle", $this->selectedMethodName);
$layouts->assign("pageIcon", $this->selectedMethodIcon);
$layouts->assign('sidebar', $sidebar); $layouts->assign('sidebar', $sidebar);
$layouts->assign('content', $content); $layouts->assign('content', $content);
$layouts->assign("footer", $footer);
// And load the page return $layouts->get('main/panel');
Logger::log("Forwarding output back to Router."); }
$page = $layouts->get('main/panel');
return $page; public function getAdminURL(): string
{
return $this->adminURL;
} }
/** /**
@ -176,6 +292,98 @@ class AdminPlugin implements \FuzeWorks\iPluginHeader
public function layoutLoadEventListener(LayoutLoadEvent $event) public function layoutLoadEventListener(LayoutLoadEvent $event)
{ {
$event->assign('adminKey', $this->pluginKey); $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;
} }
/** /**
@ -183,7 +391,7 @@ class AdminPlugin implements \FuzeWorks\iPluginHeader
*/ */
public function getName(): string public function getName(): string
{ {
return 'FuzeWorksAdministration'; return 'admin';
} }
/** /**

21
src/FuzeWorks/Administration/AdminView.php Normal file → Executable file
View File

@ -34,6 +34,9 @@
*/ */
namespace FuzeWorks\Administration; namespace FuzeWorks\Administration;
use FuzeWorks\Authentication\AuthenticationPlugin;
use FuzeWorks\Authentication\Model\Session;
use FuzeWorks\Authentication\Model\Users;
use FuzeWorks\Factory; use FuzeWorks\Factory;
use FuzeWorks\Layout; use FuzeWorks\Layout;
use FuzeWorks\Priority; use FuzeWorks\Priority;
@ -41,21 +44,27 @@ use FuzeWorks\WebView;
abstract class AdminView extends WebView abstract class AdminView extends WebView
{ {
protected Layout $layouts;
/** @var Layout */ protected Session $session;
protected $layouts; protected Users $users;
/** /**
* The priority of this AdminView. * The priority of this AdminView.
*
* @var int
*/ */
protected $priority = Priority::NORMAL; protected int $priority = Priority::NORMAL;
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
// Set layouts
$this->layouts = Factory::getInstance('layouts'); $this->layouts = Factory::getInstance('layouts');
/** @var AuthenticationPlugin $plugin */
$plugin = $this->plugins->get('auth');
// Set current session
$this->users = $plugin->users;
$this->session = $plugin->sessions->getCurrentSession();
} }
} }

View File

@ -0,0 +1,41 @@
<?php
/**
* FuzeWorksAdministration
*
* Copyright (C) 2013-2021 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 - 2021, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.0.0
*
* @version Version 1.0.0
*/
namespace FuzeWorks\Administration\Attributes;
interface AdminAttribute
{
public function getValue();
}

View File

@ -0,0 +1,51 @@
<?php
/**
* FuzeWorksAdministration
*
* Copyright (C) 2013-2023 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 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.0.0
*
* @version Version 1.0.0
*/
namespace FuzeWorks\Administration\Attributes;
use Attribute;
#[Attribute]
class DashboardAttribute implements AdminAttribute
{
public function __construct()
{
}
public function getValue()
{
}
}

View File

@ -0,0 +1,57 @@
<?php
/**
* FuzeWorksAdministration
*
* Copyright (C) 2013-2021 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 - 2021, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.0.0
*
* @version Version 1.0.0
*/
namespace FuzeWorks\Administration\Attributes;
use Attribute;
#[Attribute]
class DisplayAttribute implements AdminAttribute
{
const VALUE = '';
private string $value;
public function __construct(string $value = '')
{
$this->value = $value;
}
public function getValue(): string
{
return $this->value;
}
}

View File

@ -0,0 +1,55 @@
<?php
/**
* FuzeWorksAdministration
*
* Copyright (C) 2013-2021 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 - 2021, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.0.0
*
* @version Version 1.0.0
*/
namespace FuzeWorks\Administration\Attributes;
use Attribute;
#[Attribute]
class FooterCodeMethodAttribute implements AdminAttribute
{
const VALUE = '';
private string $value;
public function __construct(string $value = '')
{
$this->value = $value;
}
public function getValue(): string
{
return $this->value;
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* FuzeWorksAdministration
*
* Copyright (C) 2013-2021 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 - 2021, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.0.0
*
* @version Version 1.0.0
*/
namespace FuzeWorks\Administration\Attributes;
use Attribute;
#[Attribute]
class HiddenAttribute implements AdminAttribute
{
public function getValue(): bool
{
return true;
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* FuzeWorksAdministration
*
* Copyright (C) 2013-2021 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 - 2021, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.0.0
*
* @version Version 1.0.0
*/
namespace FuzeWorks\Administration\Attributes;
use Attribute;
#[Attribute]
class IconAttribute implements AdminAttribute
{
private string $value;
public function __construct(string $value = '')
{
$this->value = $value;
}
public function getValue(): string
{
return $this->value;
}
}

View File

@ -0,0 +1,55 @@
<?php
/**
* FuzeWorksWorkspace.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2022 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 - 2022, i15. (http://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link https://i15.nl
* @since Version 2.0.0
*
* @version Version 2.0.0
*/
namespace FuzeWorks\Administration\Attributes;
use Attribute;
#[Attribute]
class PermissionAttribute implements AdminAttribute
{
private array $nodes;
public function __construct(array $nodes = [])
{
$this->nodes = $nodes;
}
public function getValue(): array
{
return $this->nodes;
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* FuzeWorksAdministration
*
* Copyright (C) 2013-2021 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 - 2021, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.0.0
*
* @version Version 1.0.0
*/
namespace FuzeWorks\Administration\Attributes;
use Attribute;
use FuzeWorks\Priority;
#[Attribute]
class PriorityAttribute implements AdminAttribute
{
private int $priority;
public function __construct(int $value = Priority::NORMAL)
{
$this->priority = $value;
}
public function getValue(): int
{
return $this->priority;
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* FuzeWorksWorkspace.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2022 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 - 2022, i15. (http://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link https://i15.nl
* @since Version 2.0.0
*
* @version Version 2.0.0
*/
namespace FuzeWorks\Administration\Exceptions;
use FuzeWorks\Exception\PluginException;
class AdminPluginException extends PluginException
{
}

View File

@ -0,0 +1,42 @@
<?php
/**
* FuzeWorksWorkspace.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2022 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 - 2022, i15. (http://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link https://i15.nl
* @since Version 2.0.0
*
* @version Version 2.0.0
*/
namespace FuzeWorks\Administration\Exceptions;
class UnauthorizedException extends AdminPluginException
{
}

218
src/FuzeWorks/Administration/PageFinder.php Normal file → Executable file
View File

@ -35,13 +35,19 @@
*/ */
namespace FuzeWorks\Administration; namespace FuzeWorks\Administration;
use FuzeWorks\Administration\Attributes\DisplayAttribute;
use FuzeWorks\Administration\Attributes\HiddenAttribute;
use FuzeWorks\Administration\Attributes\IconAttribute;
use FuzeWorks\Administration\Attributes\PermissionAttribute;
use FuzeWorks\Administration\Attributes\PriorityAttribute;
use FuzeWorks\Administration\Events\AdminFindViewsEvent;
use FuzeWorks\Administration\Events\AdminGenerateSidebarEvent; use FuzeWorks\Administration\Events\AdminGenerateSidebarEvent;
use FuzeWorks\Authentication\Model\Session;
use FuzeWorks\Events; use FuzeWorks\Events;
use FuzeWorks\Factory; use FuzeWorks\Factory;
use FuzeWorks\Logger; use FuzeWorks\Logger;
use FuzeWorks\Priority; use FuzeWorks\Priority;
use FuzeWorks\Views; use FuzeWorks\Views;
use phpDocumentor\Reflection\DocBlockFactory;
use ReflectionClass; use ReflectionClass;
use ReflectionException; use ReflectionException;
use ReflectionMethod; use ReflectionMethod;
@ -50,39 +56,56 @@ class PageFinder
{ {
protected Session $session;
/** /**
* Generates an array of all sidebar elements. * Generates an array of all sidebar elements.
* *
* First searches for all the available AdminViews. Then parses over all their methods and populates the array * First searches for all the available AdminViews. Then parses over all their methods and populates the array
* using each method's individual docblock. * using each method's attributes.
* *
* @return array * @return array
*/ */
public function generateSidebar(): array public function generateSidebar(Session $session): array
{ {
$this->session = $session;
Logger::log("Generating sidebar..."); Logger::log("Generating sidebar...");
$event = Events::fireEvent('adminGenerateSidebarEvent'); $event = Events::fireEvent('adminGenerateSidebarEvent');
if ($event->isCancelled()) if ($event->isCancelled())
return []; return [];
// First find all views // First find all views
$viewFileList = $this->findViews(); $views = $this->findViews();
Logger::log("Found " . count($viewFileList) . ' admin views.'); Logger::log("Found " . count($views) . ' admin views.');
// Then sort all those views into methods in the sidebar // Then sort all those views into methods in the sidebar
$sorted = $this->sort($viewFileList); $sorted = $this->sortSidebar($views);
// Log the result and return // Log the result and return
Logger::log("Found " . count($sorted) . ' entries for sidebar.'); Logger::log("Found " . count($sorted) . ' entries for sidebar.');
return $sorted; return $sorted;
} }
/**
* Find all views that have methods with attributes of a specific kind.
*
* @param string $attributeClass
* @return string[]
*/
public function findViewsWithAttribute(string $attributeClass): array
{
// First find all admin views
$views = $this->findViews();
return $this->sortByAttribute($views, $attributeClass);
}
/** /**
* Parse over every view component path and find all admin views. * Parse over every view component path and find all admin views.
* *
* Return a file list. * Return a file list.
* *
* @return array * @return string[]
*/ */
protected function findViews(): array protected function findViews(): array
{ {
@ -95,7 +118,7 @@ class PageFinder
$paths[$i] = $views->getComponentPaths($i); $paths[$i] = $views->getComponentPaths($i);
// Then go over every individual paths // Then go over every individual paths
$viewFiles = []; $views = [];
foreach ($paths as $priority => $foldersArray) foreach ($paths as $priority => $foldersArray)
{ {
foreach ($foldersArray as $folder) foreach ($foldersArray as $folder)
@ -107,44 +130,79 @@ class PageFinder
foreach ($contents as $file) foreach ($contents as $file)
{ {
// If the file matches the expected filename, add it to the list // If the file matches the expected filename, add it to the list
if (substr($file, 0, 11) === 'view.admin.' && !isset($viewFiles[$file])) if (!str_starts_with($file, 'view.admin.'))
$viewFiles[$file] = $folder . DS . $file; continue;
// Attempt to load the view
$viewFile = $folder . DS . $file;
$id = substr($file, 11, -4);
$className = 'Application\View\\' . ucfirst($id) . 'AdminView';
require_once($viewFile);
if (!class_exists($className))
continue;
$views[] = $className;
} }
} }
} }
return $viewFiles; return $views;
} }
protected function sort(array $viewFileList): array /**
* Returns all views which have methods which have Attributes with the attributeClassName
*
* @param string[] $views
* @param string $attributeClassName
* @return string[]
*/
protected function sortByAttribute(array $views, string $attributeClassName): array
{ {
// Reflection docblock factory $out = [];
$factory = DocBlockFactory::createInstance(); foreach ($views as $view)
{
try {
$reflector = new ReflectionClass($view);
} catch (ReflectionException $e) {
// If reflector doesn't work, simply ignore it.
continue;
}
// Check if the class actually inherits AdminView
if (!$reflector->isSubclassOf(AdminView::class))
continue;
// Select all methods
$methods = $reflector->getMethods(ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method)
if (!empty($method->getAttributes($attributeClassName)))
$out[] = $view;
}
return $out;
}
protected function sortSidebar(array $views): array
{
// Start sorting all methods // Start sorting all methods
$entries = []; $entries = [];
foreach ($viewFileList as $viewId => $viewFile) foreach ($views as $view)
{ {
// Load the file
require_once $viewFile;
// Determine the className
$id = substr($viewId, 11, -4);
$className = 'Application\View\\' . ucfirst($id) . 'AdminView';
// Try and reflect on this class. // Try and reflect on this class.
try { try {
$reflector = new ReflectionClass($className); $reflector = new ReflectionClass($view);
} catch (ReflectionException $e) { } catch (ReflectionException $e) {
// If that doesn't work, simply ignore it. Don't drag the whole interface down. // If that doesn't work, simply ignore it. Don't drag the whole interface down.
continue; continue;
} }
// Check if this class actually inherits AdminView // Check if this class actually inherits AdminView
$parent = $reflector->getParentClass(); if (!$reflector->isSubclassOf("FuzeWorks\Administration\AdminView"))
if ($parent === false || $parent->getName() !== 'FuzeWorks\Administration\AdminView')
continue; continue;
// Determine view Id
$id = strtolower(substr($reflector->getShortName(), 0, -9));
// Select all methods // Select all methods
$methods = $reflector->getMethods(ReflectionMethod::IS_PUBLIC); $methods = $reflector->getMethods(ReflectionMethod::IS_PUBLIC);
@ -154,77 +212,75 @@ class PageFinder
$entry = [ $entry = [
'url' => ($id !== 'index' ? $id : '') . ($method->getName() !== 'index' ? '/' . $method->getName() : ''), 'url' => ($id !== 'index' ? $id : '') . ($method->getName() !== 'index' ? '/' . $method->getName() : ''),
'priority' => Priority::NORMAL, 'priority' => Priority::NORMAL,
'display' => $reflector->getShortName() . '/' . $method->getName(), 'display' => $reflector->getShortName() . '::' . $method->getName(),
'icon' => 'round' 'icon' => 'round'
]; ];
// Only allow the methods in the actual class. Not from the parent method. // Only allow the methods in the actual class. Not from the parent method.
if ($method->class !== $className) if ($method->class !== $view)
continue; continue;
// Then fetch the docComments. // Read attributes
$docComment = $method->getDocComment(); // First read hidden attribute. If present, skip this method
if ($docComment !== false) $hiddenAttributes = $method->getAttributes(HiddenAttribute::class);
{ if (!empty($hiddenAttributes))
$docBlock = $factory->create($docComment);
$docTags = $docBlock->getTags();
// Find known tags
$hidden = $docBlock->getTagsByName('hidden');
$display = $docBlock->getTagsByName('display');
$icon = $docBlock->getTagsByName('icon');
$priority = $docBlock->getTagsByName('priority');
// First test for hidden. If found, it should not be added to the sidebar
if (!empty($hidden))
continue; continue;
// Then test for display // Then check for permissions
if (!empty($display)) $permissionAttributes = $method->getAttributes(PermissionAttribute::class);
$entry['display'] = (string) $display[0]; if (!empty($permissionAttributes))
// Then test for icon
if (!empty($icon))
$entry['icon'] = (string) $icon[0];
// Then test for priority
if (!empty($priority))
{ {
$priority = (string) $priority[0]; // Fetch nodes
switch ($priority) { $nodes = $permissionAttributes[0]->newInstance()->getValue();
case 'Priority::LOWEST':
$entry['priority'] = 5; // Check if any of the nodes are permitted
break; $found = false;
case 'Priority::LOW': foreach ($nodes as $node)
$entry['priority'] = 4; if ($this->session->hasPermission($node))
break; $found = true;
case 'Priority::NORMAL':
$entry['priority'] = 3; // If not, skip
break; if (!$found)
case 'Priority::HIGH': continue;
$entry['priority'] = 2;
break;
case 'Priority::HIGHEST':
$entry['priority'] = 1;
break;
case 'Priority::MONITOR':
$entry['priority'] = 0;
break;
default:
Logger::logError("Method " . $reflector->getName() . '::' . $method->getName() . ' has invalid value for @priority.');
$entry['priority'] = 3;
break;
}
}
} }
// Then check for display attribute.
$displayAttributes = $method->getAttributes(DisplayAttribute::class);
if (!empty($displayAttributes))
$entry['display'] = $displayAttributes[0]->newInstance()->getValue();
// Then check for icon
$iconAttributes = $method->getAttributes(IconAttribute::class);
if (!empty($iconAttributes))
$entry['icon'] = $iconAttributes[0]->newInstance()->getValue();
// Then check for priority
$priorityAttributes = $method->getAttributes(PriorityAttribute::class);
if (!empty($priorityAttributes))
$entry['priority'] = $priorityAttributes[0]->newInstance()->getValue();
// Add priority
if (!isset($entries[$entry['priority']]))
$entries[$entry['priority']] = [];
// Write to entries // Write to entries
$entries[] = $entry; $entries[$entry['priority']][] = $entry;
} }
} }
// And finally return those // Sort by priority
return $entries; $out = [];
for ($i = Priority::getHighestPriority(); $i <= Priority::getLowestPriority(); $i++) {
if (!isset($entries[$i]))
continue;
$p = $entries[$i];
foreach ($p as $entry)
$out[] = $entry;
}
// And finally return the sidebar entries
return $out;
} }
} }

0
test/bootstrap.php Normal file → Executable file
View File

View File

@ -0,0 +1,76 @@
<?php
/**
* i15Site.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2022 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 - 2022, i15. (http://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link https://i15.nl
* @since Version 2.0.0
*
* @version Version 2.0.0
*/
namespace Application\View;
use Application\Controller\AsyncController;
use FuzeWorks\Administration\AdminView;
use FuzeWorks\Administration\Attributes\DisplayAttribute;
use FuzeWorks\Administration\Attributes\HiddenAttribute;
use FuzeWorks\Async\Tasks;
use FuzeWorks\Controller;
class AsyncAdminView extends AdminView
{
/**
* @var AsyncController
*/
protected Controller $controller;
protected Tasks $tasks;
#[HiddenAttribute]
public function __construct()
{
parent::__construct();
/** @var Tasks $lib */
$lib = $this->libraries->get("tasks");
$this->tasks = $lib;
}
#[HiddenAttribute]
#[DisplayAttribute("Overview")]
public function index()
{
// Read out all tasks
$storage = $this->tasks->getTaskStorage();
$tasks = $storage->readTasks();
// And assign them
$this->layouts->assign("tasks", $tasks);
return json_encode($tasks);
}
}

View File

@ -0,0 +1,67 @@
<?php
/**
* i15Site.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2022 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 - 2022, i15. (http://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link https://i15.nl
* @since Version 2.0.0
*
* @version Version 2.0.0
*/
namespace Application\View;
use Application\Controller\AsyncController;
use FuzeWorks\Administration\AdminView;
use FuzeWorks\Async\Tasks;
use FuzeWorks\Controller;
class AsyncAdminApiView extends AdminView
{
/**
* @var AsyncController
*/
protected Controller $controller;
protected Tasks $tasks;
public function __construct()
{
parent::__construct();
/** @var Tasks $lib */
$lib = $this->libraries->get("tasks");
$this->tasks = $lib;
}
public function index()
{
return ["Hello world"];
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace Application\View;
use Application\Controller\CacheController;
use FuzeWorks\Administration\AdminView;
use FuzeWorks\Administration\Attributes\DisplayAttribute;
use FuzeWorks\Administration\Attributes\HiddenAttribute;
use FuzeWorks\Administration\Attributes\IconAttribute;
use FuzeWorks\Administration\Attributes\PermissionAttribute;
use FuzeWorks\Administration\Attributes\PriorityAttribute;
use FuzeWorks\Controller;
use FuzeWorks\Factory;
use FuzeWorks\ObjectStorage\ObjectStorageComponent;
use FuzeWorks\Priority;
use Psr\SimpleCache\CacheInterface;
class CacheAdminView extends AdminView
{
/** @var CacheController $controller */
protected Controller $controller;
#[DisplayAttribute("ObjectStorage"), IconAttribute("server"), PriorityAttribute(Priority::LOW)]
#[HiddenAttribute]
#[PermissionAttribute(["ADMIN"])]
public function index()
{
$items = $this->controller->getCacheItems();
return json_encode($items);
}
}

View File

@ -34,43 +34,33 @@
*/ */
namespace Application\View; namespace Application\View;
use Application\Controller\SettingsController; use Application\Controller\DashboardController;
use FuzeWorks\Administration\AdminView; use FuzeWorks\Administration\AdminView;
use FuzeWorks\Administration\Attributes\DashboardAttribute;
use FuzeWorks\Administration\Attributes\DisplayAttribute;
use FuzeWorks\Administration\Attributes\HiddenAttribute;
use FuzeWorks\Administration\Attributes\IconAttribute;
use FuzeWorks\Administration\Attributes\PriorityAttribute;
use FuzeWorks\Controller;
use FuzeWorks\Priority;
class SettingsAdminView extends AdminView class DashboardAdminView extends AdminView
{ {
/** /**
* @var SettingsController * @var DashboardController
*/ */
protected $controller; protected Controller $controller;
/** #[DisplayAttribute("Dashboard"), IconAttribute("tachometer-alt"), PriorityAttribute(Priority::HIGHEST)]
* Displays a list of settings files
*
* @display Settings
* @icon cog
* @priority Priority::LOW
*/
public function index() public function index()
{ {
$files = $this->controller->findSettingsFiles(); //$content = $this->controller->populateDashboard();
$this->layouts->assign('files', $files); //$this->layouts->assign('content', $content);
return $this->layouts->get('components/settings/overview');
} }
/** #[HiddenAttribute, DashboardAttribute("Hello!")]
* @hidden public function test()
*/
public function view()
{
}
/**
* @hidden
*/
public function modify()
{ {
} }

View File

@ -0,0 +1,124 @@
<?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 Application\View;
use Application\Controller\SettingsController;
use FuzeWorks\Administration\AdminView;
use FuzeWorks\Administration\Attributes\DisplayAttribute;
use FuzeWorks\Administration\Attributes\HiddenAttribute;
use FuzeWorks\Administration\Attributes\IconAttribute;
use FuzeWorks\Administration\Attributes\PermissionAttribute;
use FuzeWorks\Administration\Attributes\PriorityAttribute;
use FuzeWorks\Controller;
use FuzeWorks\Exception\NotFoundException;
use FuzeWorks\Forms\Fields\CheckboxField;
use FuzeWorks\Forms\Fields\TextField;
use FuzeWorks\Forms\Forms;
use FuzeWorks\Priority;
class SettingsAdminView extends AdminView
{
/**
* @var SettingsController
*/
protected Controller $controller;
/**
* Displays a list of settings files
*/
#[DisplayAttribute("Config"), IconAttribute("cog"), PriorityAttribute(Priority::LOWEST)]
#[PermissionAttribute(["SUPER"])]
public function index()
{
$files = $this->controller->findSettingsFiles();
$this->layouts->assign('files', $files);
return $this->layouts->get('components/settings/overview');
}
/**
* @throws NotFoundException
*/
#[HiddenAttribute, DisplayAttribute("View config file")]
#[PermissionAttribute(["SUPER"])]
public function view(string $request)
{
// Determine parts
$parts = explode("/", $request);
$file = $parts[0];
$selector = intval($parts[1]);
// Select the file
$data = $this->controller->fetchFile($file, $selector);
// Create a form out of the data
/** @var Forms $forms */
$forms = $this->libraries->get("forms");
$form = $forms->getForm("ConfigEdit");
foreach ($data['data'] as $key => $value)
{
switch (gettype($value))
{
case "string":
case "integer":
$field = new TextField($key);
$field->setLabel($key)->setValue($value)->lock();
$form->field($field);
break;
case "boolean":
$field = new CheckboxField($key);
$field->setLabel($key)->setValue($value)->lock();
$form->field($field);
break;
default:
dump(gettype($value));
break;
}
}
// Assign and load layout
$this->layouts->assign('config', $data);
$this->layouts->assign('form', $form);
return $this->layouts->get('components/settings/view');
}
#[HiddenAttribute, DisplayAttribute("Edit config file")]
#[PermissionAttribute(["SUPER"])]
public function modify()
{
}
}

0
www/.htaccess Normal file → Executable file
View File

29
www/dist/async.ts vendored Normal file
View File

@ -0,0 +1,29 @@
class AsyncManager {
protected readonly url: string = "/admin/async";
constructor(url?: string) {
if (url !== undefined)
this.url = url;
}
public getTaskList(onSuccess: Function, onError?: Function): void {
let xhr = new XMLHttpRequest();
xhr.open("GET", this.url + ".json", true);
xhr.send();
// @todo Continue
}
}
let async: AsyncManager = new AsyncManager();
const url = "/admin/async";
let xhr = new XMLHttpRequest();
xhr.open("GET", url + ".json", true);
xhr.send();
xhr.onload = function () {
if (xhr.status === 200) {
}
}

0
www/index.php Normal file → Executable file
View File