From 50608c18e728911472e56ba26dc81d33974b65cb Mon Sep 17 00:00:00 2001 From: Abel Hoogeveen Date: Sun, 2 Aug 2020 11:08:21 +0200 Subject: [PATCH] Initial commit --- .gitattributes | 4 + .gitignore | 6 + Dockerfile | 16 ++ LICENSE | 21 ++ composer.json | 32 +++ config.admin.php | 38 +++ controllers/controller.index.php | 42 +++ controllers/controller.settings.php | 80 ++++++ .../components/settings/layout.overview.latte | 40 +++ layouts/main/layout.panel.latte | 268 ++++++++++++++++++ src/FuzeWorks/Administration/AdminPlugin.php | 212 ++++++++++++++ src/FuzeWorks/Administration/AdminView.php | 61 ++++ src/FuzeWorks/Administration/PageFinder.php | 230 +++++++++++++++ test/bootstrap.php | 63 ++++ views/view.admin.index.php | 55 ++++ views/view.admin.settings.php | 78 +++++ www/.htaccess | 4 + www/index.php | 41 +++ 18 files changed, 1291 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 composer.json create mode 100644 config.admin.php create mode 100644 controllers/controller.index.php create mode 100644 controllers/controller.settings.php create mode 100644 layouts/components/settings/layout.overview.latte create mode 100644 layouts/main/layout.panel.latte create mode 100644 src/FuzeWorks/Administration/AdminPlugin.php create mode 100644 src/FuzeWorks/Administration/AdminView.php create mode 100644 src/FuzeWorks/Administration/PageFinder.php create mode 100644 test/bootstrap.php create mode 100644 views/view.admin.index.php create mode 100644 views/view.admin.settings.php create mode 100644 www/.htaccess create mode 100644 www/index.php diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d6966d7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +.gitattributes export-ignore +.gitignore export-ignore +.drone.yml export-ignore +test/ export-ignore \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6f4d6e --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +composer.lock +composer.phar +.idea/ +build/ +test/temp/ +vendor/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1a588c7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM php:7.3-apache +MAINTAINER i15 + +# Install PHP Deps +# First PDO and MySQL +RUN docker-php-ext-install mysqli pdo_mysql opcache + +# Write application to image +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/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf +RUN a2enmod rewrite + +# Amend permissions +COPY --chown=www-data . /usr/src/admin +RUN chmod -R 777 /usr/src/admin/test/temp \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a325d0e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +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. \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d1b1abe --- /dev/null +++ b/composer.json @@ -0,0 +1,32 @@ +{ + "name": "fuzeworks/administration", + "description": "description", + "minimum-stability": "stable", + "license": ["MIT"], + "authors": [ + { + "name": "Abel Hoogeveen", + "email": "abel@i15.nl" + } + ], + "require": { + "php": ">=7.2.0", + "fuzeworks/core": "~1.2", + "fuzeworks/mvcr": "~1.2", + "fuzeworks/webcomponent": "~1.2", + "fuzeworks/layout": "~1.2", + "fuzeworks/database": "~1.2", + "latte/latte": "^2", + "almasaeed2010/adminlte": "^3", + "phpdocumentor/reflection-docblock": "^5" + }, + "require-dev": { + "phpunit/phpunit": "^9", + "fuzeworks/tracycomponent": "~1.2" + }, + "autoload": { + "psr-4": { + "FuzeWorks\\Administration\\": "src/FuzeWorks/Administration/" + } + } +} \ No newline at end of file diff --git a/config.admin.php b/config.admin.php new file mode 100644 index 0000000..8ce351b --- /dev/null +++ b/config.admin.php @@ -0,0 +1,38 @@ + true, + 'admin_url' => 'admin' +]; \ No newline at end of file diff --git a/controllers/controller.index.php b/controllers/controller.index.php new file mode 100644 index 0000000..2f52536 --- /dev/null +++ b/controllers/controller.index.php @@ -0,0 +1,42 @@ +config->getComponentPaths($i); + + // And add the CorePath + $paths[Priority::getLowestPriority()][] = Core::$coreDir . DS . 'Config'; + + // Then go over every individual paths + $configFiles = []; + foreach ($paths as $priority => $foldersArray) + { + foreach ($foldersArray as $folder) + { + // Get the contents from the folder + $contents = array_diff(scandir($folder), array('..', '.')); + + // Then go over the folders, and see which ones are an admin view + foreach ($contents as $file) + { + // If the file matches the expected filename, add it to the list + if (substr($file, 0, 7) === 'config.' && !isset($configFiles[$file])) + $configFiles[$file] = ['folder' => $folder, 'priority' => substr(Priority::getPriority($priority), 10)]; + } + } + } + + // At the end, sort the files + ksort($configFiles); + + // And return them + return $configFiles; + } + +} \ No newline at end of file diff --git a/layouts/components/settings/layout.overview.latte b/layouts/components/settings/layout.overview.latte new file mode 100644 index 0000000..ff736db --- /dev/null +++ b/layouts/components/settings/layout.overview.latte @@ -0,0 +1,40 @@ +
+
+
+
+

Configuration Files

+
+ +
+ + + + + + + + + + + + + {foreach $files as $fileName => $data} + + + + + + + + + {/foreach} + +
#NameFolderPriorityViewEdit
{$iterator->getCounter()}{$fileName}{$data['folder']}{$data['priority']}
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/layouts/main/layout.panel.latte b/layouts/main/layout.panel.latte new file mode 100644 index 0000000..00bee3a --- /dev/null +++ b/layouts/main/layout.panel.latte @@ -0,0 +1,268 @@ + + + + + + AdminLTE 3 | Dashboard + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+
+
+
+

Dashboard

+
+
+ +
+
+
+
+ + + +
+
+ {$content|noescape} +
+
+ +
+ +
+ Copyright © 2013-2020 i15.nl. + All rights reserved. +
+ Version 1.3.0 +
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/FuzeWorks/Administration/AdminPlugin.php b/src/FuzeWorks/Administration/AdminPlugin.php new file mode 100644 index 0000000..6344d04 --- /dev/null +++ b/src/FuzeWorks/Administration/AdminPlugin.php @@ -0,0 +1,212 @@ +pluginPath = dirname(__DIR__, 3); + } + + public function routeWebRequestEventListener(RouteWebRequestEvent $event) + { + // If this request has nothing to do with the admin interface, don't bother routing the request + if (substr($event->uriString, 0, strlen($this->pluginKey)) !== $this->pluginKey) + return; + + Logger::log("Administration: observed admin request. Activating plugin."); + + + // Load the dependencies + $this->config = Factory::getInstance('config'); + $this->router = Factory::getInstance('router'); + $this->models = Factory::getInstance('models'); + $this->views = Factory::getInstance('views'); + $this->controllers = Factory::getInstance('controllers'); + + // Load the admin configuration + $this->config->addComponentPath($this->pluginPath, Priority::LOWEST); + $this->adminCFG = $this->config->getConfig('admin'); + + // If admin is not enabled, stop here + if (!$this->adminCFG->get('admin_enabled')) + return; + + // Now load the pluginKey + $this->pluginKey = $this->adminCFG->get('admin_url'); + + // If it does, register everything + /** @var Resources $resources */ + $resources = Factory::getInstance('resources'); + + // Serve the AdminLTE distribution files + $adminLTE = $this->pluginPath . DS . 'vendor' . DS . 'almasaeed2010' . DS . 'adminlte'; + $resources->registerResources('admin/dist', $adminLTE . DS . 'dist'); + $resources->registerResources('admin/plugins', $adminLTE . DS . 'plugins'); + + // And serve the actual pages + $routeString = '^' . $this->pluginKey . '(|\/(?P.*?)(|\/(?P.*?)(|\/(?P.*?))))'; + $this->router->addRoute($routeString, ['callable' => [$this, 'adminCallable']], Priority::HIGHEST); + + // 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) + { + Logger::log("AdminCallable called. Loading admin page."); + + // 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::NORMAL); + + // Let's generate the sidebar + $finder = new PageFinder(); + $sidebar = $finder->generateSidebar(); + + // Afterwards, pass on to the admin view and its contents + Logger::log("Forwarding request to Router::defaultCallable()."); + $matches['viewType'] = 'admin'; + $content = $this->router->defaultCallable($matches, $routeString); + + // Reset and assign + Logger::log("Generating panel wrapper."); + $layouts->reset(false); + $layouts->assign('sidebar', $sidebar); + $layouts->assign('content', $content); + + // And load the page + Logger::log("Forwarding output back to Router."); + $page = $layouts->get('main/panel'); + return $page; + } + + /** + * 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); + } + + /** + * @inheritDoc + */ + public function getName(): string + { + return 'FuzeWorksAdministration'; + } + + /** + * @inheritDoc + */ + public function getClassesPrefix(): ?string + { + return null; + } + + /** + * @inheritDoc + */ + public function getSourceDirectory(): ?string + { + return null; + } + + /** + * @inheritDoc + */ + public function getPluginClass(): ?string + { + return null; + } +} \ No newline at end of file diff --git a/src/FuzeWorks/Administration/AdminView.php b/src/FuzeWorks/Administration/AdminView.php new file mode 100644 index 0000000..371eb1f --- /dev/null +++ b/src/FuzeWorks/Administration/AdminView.php @@ -0,0 +1,61 @@ +layouts = Factory::getInstance('layouts'); + } +} \ No newline at end of file diff --git a/src/FuzeWorks/Administration/PageFinder.php b/src/FuzeWorks/Administration/PageFinder.php new file mode 100644 index 0000000..ce5bd46 --- /dev/null +++ b/src/FuzeWorks/Administration/PageFinder.php @@ -0,0 +1,230 @@ +isCancelled()) + return []; + + // First find all views + $viewFileList = $this->findViews(); + Logger::log("Found " . count($viewFileList) . ' admin views.'); + + // Then sort all those views into methods in the sidebar + $sorted = $this->sort($viewFileList); + + // Log the result and return + Logger::log("Found " . count($sorted) . ' entries for sidebar.'); + return $sorted; + } + + /** + * Parse over every view component path and find all admin views. + * + * Return a file list. + * + * @return array + */ + protected function findViews(): array + { + /** @var Views $views */ + $views = Factory::getInstance('views'); + + // First collect all the known paths + $paths = []; + for ($i = Priority::getHighestPriority(); $i<=Priority::getLowestPriority(); $i++) + $paths[$i] = $views->getComponentPaths($i); + + // Then go over every individual paths + $viewFiles = []; + foreach ($paths as $priority => $foldersArray) + { + foreach ($foldersArray as $folder) + { + // Get the contents from the folder + $contents = array_diff(scandir($folder), array('..', '.')); + + // Then go over the folders, and see which ones are an admin view + foreach ($contents as $file) + { + // If the file matches the expected filename, add it to the list + if (substr($file, 0, 11) === 'view.admin.' && !isset($viewFiles[$file])) + $viewFiles[$file] = $folder . DS . $file; + } + } + } + + return $viewFiles; + } + + protected function sort(array $viewFileList): array + { + // Reflection docblock factory + $factory = DocBlockFactory::createInstance(); + + // Start sorting all methods + $entries = []; + foreach ($viewFileList as $viewId => $viewFile) + { + // 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 { + $reflector = new ReflectionClass($className); + } catch (ReflectionException $e) { + // If that doesn't work, simply ignore it. Don't drag the whole interface down. + continue; + } + + // Check if this class actually inherits AdminView + $parent = $reflector->getParentClass(); + if ($parent === false || $parent->getName() !== 'FuzeWorks\Administration\AdminView') + continue; + + // Select all methods + $methods = $reflector->getMethods(ReflectionMethod::IS_PUBLIC); + + // And pass over all of them + foreach ($methods as $method) { + // Prepare the entry output + $entry = [ + 'url' => ($id !== 'index' ? $id : '') . ($method->getName() !== 'index' ? '/' . $method->getName() : ''), + 'priority' => Priority::NORMAL, + 'display' => $reflector->getShortName() . '/' . $method->getName(), + 'icon' => 'round' + ]; + + // Only allow the methods in the actual class. Not from the parent method. + if ($method->class !== $className) + continue; + + // Then fetch the docComments. + $docComment = $method->getDocComment(); + if ($docComment !== false) + { + $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; + + // Then test for display + if (!empty($display)) + $entry['display'] = (string) $display[0]; + + // Then test for icon + if (!empty($icon)) + $entry['icon'] = (string) $icon[0]; + + // Then test for priority + if (!empty($priority)) + { + $priority = (string) $priority[0]; + switch ($priority) { + case 'Priority::LOWEST': + $entry['priority'] = 5; + break; + case 'Priority::LOW': + $entry['priority'] = 4; + break; + case 'Priority::NORMAL': + $entry['priority'] = 3; + break; + case 'Priority::HIGH': + $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; + } + } + } + + // Write to entries + $entries[] = $entry; + } + } + + // And finally return those + return $entries; + } + +} \ No newline at end of file diff --git a/test/bootstrap.php b/test/bootstrap.php new file mode 100644 index 0000000..d42ac15 --- /dev/null +++ b/test/bootstrap.php @@ -0,0 +1,63 @@ +setTempDirectory(__DIR__ . '/temp'); +$configurator->setLogDirectory(__DIR__ . '/temp'); + +// Other values +$configurator->setTimeZone('Europe/Amsterdam'); + +// Add components and plugins +$webComponent = new \FuzeWorks\WebComponent(); +$webComponent->enableComponent(); +$configurator->addComponent($webComponent); + +// Add LayoutComponent +$configurator->addComponent(new \FuzeWorks\LayoutComponent()); + +// Add TracyComponent +$configurator->addComponent(new \FuzeWorks\TracyComponent()); + +// Debug related +$configurator->enableDebugMode(); +$configurator->setDebugAddress('ALL'); + +$container = $configurator->createContainer(); +$container->plugins->addPlugin(new \FuzeWorks\Administration\AdminPlugin()); +return $container; \ No newline at end of file diff --git a/views/view.admin.index.php b/views/view.admin.index.php new file mode 100644 index 0000000..5fe494e --- /dev/null +++ b/views/view.admin.index.php @@ -0,0 +1,55 @@ +controller->findSettingsFiles(); + $this->layouts->assign('files', $files); + return $this->layouts->get('components/settings/overview'); + } + + /** + * @hidden + */ + public function view() + { + + } + + /** + * @hidden + */ + public function modify() + { + + } + +} \ No newline at end of file diff --git a/www/.htaccess b/www/.htaccess new file mode 100644 index 0000000..4e6cbbe --- /dev/null +++ b/www/.htaccess @@ -0,0 +1,4 @@ +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule (.*) index.php?path=$1 [QSA,L] \ No newline at end of file diff --git a/www/index.php b/www/index.php new file mode 100644 index 0000000..28a1ee6 --- /dev/null +++ b/www/index.php @@ -0,0 +1,41 @@ +web; +$web->routeWebRequest(); \ No newline at end of file