diff --git a/Dockerfile b/Dockerfile old mode 100644 new mode 100755 index 1a588c7..f8f44a0 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,12 @@ -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 +FROM registry.i15.nl/i15/fuzephp:8.1-apache +MAINTAINER i15 + +# 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 old mode 100644 new mode 100755 index a325d0e..fae3185 --- a/LICENSE +++ b/LICENSE @@ -1,21 +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 +MIT License + +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. \ No newline at end of file diff --git a/composer.json b/composer.json old mode 100644 new mode 100755 index d1b1abe..e41099c --- a/composer.json +++ b/composer.json @@ -1,32 +1,28 @@ -{ - "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/" - } - } +{ + "name": "fuzeworks/administration", + "description": "Administration interface for FuzeWorks", + "license": ["MIT"], + "authors": [ + { + "name": "Abel Hoogeveen", + "email": "abel@i15.nl" + } + ], + "require": { + "php": ">=8.1.0", + "fuzeworks/webcomponent": "~1.3.0", + "fuzeworks/authentication": "~1.3.0", + "fuzeworks/layout": "~1.3.0", + "latte/latte": "~2.5", + "almasaeed2010/adminlte": "^3" + }, + "require-dev": { + "fuzeworks/tracycomponent": "~1.3.0", + "phpunit/phpunit": "^9" + }, + "autoload": { + "psr-4": { + "FuzeWorks\\Administration\\": "src/FuzeWorks/Administration/" + } + } } \ No newline at end of file diff --git a/config.admin.php b/config.admin.php old mode 100644 new mode 100755 index 8ce351b..d09428e --- a/config.admin.php +++ b/config.admin.php @@ -1,38 +1,38 @@ - true, - 'admin_url' => 'admin' + true, + 'admin_url' => 'admin' ]; \ No newline at end of file diff --git a/controllers/controller.index.php b/controllers/async/controller.async.php similarity index 84% rename from controllers/controller.index.php rename to controllers/async/controller.async.php index 2f52536..88b80e4 100644 --- a/controllers/controller.index.php +++ b/controllers/async/controller.async.php @@ -1,10 +1,10 @@ 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; + } + +} \ No newline at end of file diff --git a/views/view.admin.index.php b/controllers/main/controller.dashboard.php old mode 100644 new mode 100755 similarity index 77% rename from views/view.admin.index.php rename to controllers/main/controller.dashboard.php index 5fe494e..580a574 --- a/views/view.admin.index.php +++ b/controllers/main/controller.dashboard.php @@ -1,55 +1,50 @@ -findViewsWithAttribute(DashboardAttribute::class); + } + } \ No newline at end of file diff --git a/controllers/controller.settings.php b/controllers/main/controller.settings.php old mode 100644 new mode 100755 similarity index 51% rename from controllers/controller.settings.php rename to controllers/main/controller.settings.php index e69a841..9bf5cb5 --- a/controllers/controller.settings.php +++ b/controllers/main/controller.settings.php @@ -1,80 +1,130 @@ -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; - } - +config->getComponentPaths($i); + + // 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 (str_starts_with($file, 'config.')) { + if (!isset($configFiles[$file])) + $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; + } + + /** + * 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; + } + } \ No newline at end of file diff --git a/layouts/components/cache/layout.overview.latte b/layouts/components/cache/layout.overview.latte new file mode 100644 index 0000000..e69de29 diff --git a/layouts/components/settings/layout.overview.latte b/layouts/components/settings/layout.overview.latte old mode 100644 new mode 100755 index ff736db..bfdb126 --- a/layouts/components/settings/layout.overview.latte +++ b/layouts/components/settings/layout.overview.latte @@ -1,40 +1,39 @@ -
-
-
-
-

Configuration Files

-
- -
- - - - - - - - - - - - - {foreach $files as $fileName => $data} - - - - - - - - - {/foreach} - -
#NameFolderPriorityViewEdit
{$iterator->getCounter()}{$fileName}{$data['folder']}{$data['priority']}
-
- -
-
-
- - +
+
+
+
+

Configuration Files

+
+ +
+ + + + + + + + + + + + + {var $it = 0} + {foreach $files as $fileName => $data} + + {var $it = $it + 1} + + + + + + + + {/foreach} + +
#NameFolderPriorityViewEdit
{$it}{$fileName}{$entry['folder']}{$entry['priority']}
+
+ +
+
\ No newline at end of file diff --git a/layouts/components/settings/layout.view.latte b/layouts/components/settings/layout.view.latte new file mode 100755 index 0000000..8257295 --- /dev/null +++ b/layouts/components/settings/layout.view.latte @@ -0,0 +1,63 @@ +{varType FuzeWorks\Forms\Form $form} +{varType FuzeWorks\Forms\Field $field} +
+
+ +
+
+

{$config['name']}

+ +
+
+
+ {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"} +
+ {$field->addClass("form-check-input")|noescape} + + {if $field->isValidated() && !$field->isValid()} + {$field->getErrors()|implode} + {/if} +
+ {default} +
+ + {if $field->isValidated() && !$field->isValid()} + {$field->class(["form-control", "is-invalid"])|noescape} + {$field->getErrors()|implode} + {elseif $field->isValidated() && $field->isValid()} + {$field->class(["form-control", "is-valid"])|noescape} + {else} + {$field->addClass("form-control")|noescape} + {/if} +
+ {/switch} + {/foreach} +
+ +
+ +
+ +
+
+ +
+ +
+ \ No newline at end of file diff --git a/layouts/components/test/layout.home.latte b/layouts/components/test/layout.home.latte new file mode 100755 index 0000000..d039df6 --- /dev/null +++ b/layouts/components/test/layout.home.latte @@ -0,0 +1,12 @@ +{varType FuzeWorks\Forms\Form $form} +
+
+
+

{$form->getLabel()}

+
+
+ {$form|noescape} +
+
+
+ diff --git a/layouts/main/layout.error403.latte b/layouts/main/layout.error403.latte new file mode 100644 index 0000000..1a90bbf --- /dev/null +++ b/layouts/main/layout.error403.latte @@ -0,0 +1,13 @@ +
+

403

+ +
+

Unauthorized!

+ +

+ You do not have permission to view that page. + Meanwhile, you may return to dashboard. +

+
+
+ \ No newline at end of file diff --git a/layouts/main/layout.error404.latte b/layouts/main/layout.error404.latte new file mode 100644 index 0000000..c9b0c86 --- /dev/null +++ b/layouts/main/layout.error404.latte @@ -0,0 +1,14 @@ +
+

404

+ +
+

Oops! Page not found.

+ +

+ We could not find the page you were looking for. + Meanwhile, you may return to dashboard. +

+
+ +
+ \ No newline at end of file diff --git a/layouts/main/layout.error500.latte b/layouts/main/layout.error500.latte new file mode 100644 index 0000000..a0ef165 --- /dev/null +++ b/layouts/main/layout.error500.latte @@ -0,0 +1,13 @@ +
+

500

+ +
+

Oops! Something went wrong.

+ +

+ We will work on fixing that right away. + Meanwhile, you may return to dashboard. +

+
+
+ \ No newline at end of file diff --git a/layouts/main/layout.panel.latte b/layouts/main/layout.panel.latte old mode 100644 new mode 100755 index 00bee3a..3fc9ec7 --- a/layouts/main/layout.panel.latte +++ b/layouts/main/layout.panel.latte @@ -1,268 +1,158 @@ - - - - - - AdminLTE 3 | Dashboard - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - -
- -
-
-
-
-

Dashboard

-
-
- -
-
-
-
- - - -
-
- {$content|noescape} -
-
- -
- -
- Copyright © 2013-2020 i15.nl. - All rights reserved. -
- Version 1.3.0 -
-
- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +{varType FuzeWorks\Authentication\Model\Session $session} +{varType bool $loadAsync} + + + + + + {$serverName} | {$pageTitle} + + + + + + + + + + +
+ + + + + + + + + +
+ +
+
+
+
+

{$pageTitle}

+
+
+ +
+
+
+
+ + + +
+
+ {$content|noescape} +
+
+ +
+ +
+ Copyright © 2013-{date('Y')} i15.nl. + All rights reserved. +
+ Version 1.3.0 +
+
+
+ + + + + + + + +{if $loadAsync} + +{/if} + +{ifset $footer} + {$footer|noescape} +{/ifset} + + diff --git a/src/FuzeWorks/Administration/AdminPlugin.php b/src/FuzeWorks/Administration/AdminPlugin.php old mode 100644 new mode 100755 index 6344d04..7bc3ad8 --- a/src/FuzeWorks/Administration/AdminPlugin.php +++ b/src/FuzeWorks/Administration/AdminPlugin.php @@ -1,212 +1,420 @@ -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; - } +pluginPath = dirname(__DIR__, 3); + + // Load the admin configuration + $this->config = Factory::getInstance('config'); + $this->config->addComponentPath($this->pluginPath, Priority::LOWEST); + $adminCFG = $this->config->getConfig('admin'); + + // If admin is not enabled, stop here + if (!$adminCFG->get('admin_enabled')) + return; + + // Register the event + Events::addListener([$this, 'routeWebRequestEventListener'], 'routeWebRequestEvent', Priority::NORMAL); + + // Now load the pluginKey + $this->pluginKey = $adminCFG->get('admin_url'); + + // Settle admin URL + $webURL = $this->config->getConfig("web")->get("base_url"); + $this->adminURL = $webURL . "/" . $this->pluginKey; + + // Determine routing paths + $this->webRoute = '^' . $this->pluginKey . '(|\/(?P.*?)(|\/(?P.*?)(|\/(?P.*?))))'; + $this->apiRoute = '^' . $this->pluginKey . '(|\/(?P.*?)(|\/(?P.*?)(|\/(?P.*?)))).json'; + + // Load the dependencies + $this->router = Factory::getInstance('router'); + $this->models = Factory::getInstance('models'); + $this->views = Factory::getInstance('views'); + $this->controllers = Factory::getInstance('controllers'); + } + + /** + * @param RouteWebRequestEvent $event + * @return void + * @throws FactoryException + * @throws WebException + */ + public function routeWebRequestEventListener(RouteWebRequestEvent $event): void + { + Logger::log("Administration: observed web request. Activating plugin."); + + // If it does, register everything + /** @var Resources $resources */ + $resources = Factory::getInstance('resources'); + + // Serve the AdminLTE distribution files + $adminLTE = dirname(Core::$coreDir, 4) . DS . 'vendor' . DS . 'almasaeed2010' . DS . 'adminlte'; + $resources->registerResources('admin/lte_dist', $adminLTE . DS . 'dist'); + $resources->registerResources('admin/lte_plugins', $adminLTE . DS . 'plugins'); + $resources->registerResources("admin/admin_dist", $this->pluginPath . DS . 'www' . DS . 'dist'); + + // @todo TEMPORARY!! + //$resources->registerResources("lte_top", $adminLTE); + + // And serve the actual pages + $this->router->addRoute('^' . $this->pluginKey, ['viewName' => 'dashboard', 'callable' => [$this, 'adminCallable']], Priority::HIGHEST); + $this->router->addRoute($this->apiRoute, ['callable' => [$this, 'adminCallable']], Priority::HIGH); + $this->router->addRoute($this->webRoute, ['callable' => [$this, 'adminCallable']], Priority::HIGH); + } + + /** + * @param array $matches + * @param array $routeData + * @param string $routeString + * @return string|null + * @throws EventException + * @throws FactoryException + * @throws LayoutException + * @throws OutputException + */ + public function adminCallable(array $matches, array $routeData, string $routeString): ?string + { + Logger::log("AdminCallable called. Loading admin page."); + + // Add componentPaths for models, views, controllers + $this->models->addComponentPath($this->pluginPath . DS . 'models' . DS . 'main', Priority::LOW); + $this->views->addComponentPath($this->pluginPath . DS . 'views' . DS . 'main', Priority::LOW); + $this->controllers->addComponentPath($this->pluginPath . DS . 'controllers' . DS . 'main', Priority::LOW); + + // Load layouts and assign componentPath, in case the loaded view needs access to it + /** @var Layout $layouts */ + $layouts = Factory::getInstance('layouts'); + $layouts->addComponentPath($this->pluginPath . DS . 'layouts', Priority::LOW); + + // And add a layoutLoadEventListener, to add global administration variables + Events::addListener([$this, 'layoutLoadEventListener'], 'layoutLoadEvent', Priority::HIGH); + Events::addListener([$this, 'verifyViewPermissions'], 'routerCallViewEvent', Priority::HIGH); + Events::addListener([$this, 'logViewNameAndIcon'], 'routerCallViewEvent', Priority::NORMAL); + + if (class_exists("\FuzeWorks\Async\Tasks")) + { + // Mark async to be loaded + $this->loadAsync = true; + + // Add componentPaths for models, views, controllers + $this->models->addComponentPath($this->pluginPath . DS . 'models' . DS . 'async', Priority::LOW); + $this->views->addComponentPath($this->pluginPath . DS . 'views' . DS . 'async', Priority::LOW); + $this->controllers->addComponentPath($this->pluginPath . DS . 'controllers' . DS . 'async', Priority::LOW); + } + + // Load the current session + /** @var Output $output */ + $this->authPlugin = Factory::getInstance("plugins")->get('auth'); + $output = Factory::getInstance("output"); + + // Redirect the user to login if they're not logged in + $this->session = $this->authPlugin->sessions->start(); + if ($this->session->user->id === "0") + { + $output->setHeader("Location: " . $this->authPlugin->getAuthenticationURL() . "/login?location=" . $this->getAdminURL()); + return ""; + } + + // Let's generate the sidebar + $finder = new PageFinder(); + $sidebar = $finder->generateSidebar($this->session); + $footer = ""; + + // Afterwards, pass on to the admin view and its contents + Logger::log("Forwarding request to Router::defaultCallable()."); + + // Determine viewType + if ($routeString === $this->apiRoute) + $routeData['viewType'] = 'AdminAPI'; + else + $routeData['viewType'] = 'admin'; + + try { + // Fetch content from requested view + $content = $this->router->defaultCallable($matches, $routeData, $routeString); + + // If content is false, nothing is found and should return 404 + if (is_bool($content) && $content === false) + { + $output->setStatusHeader(404); + $content = $layouts->get("main/error404"); + $this->selectedMethodName = "Not found"; + } + + // And check for footer code + if (!empty($this->footerMethod)) + { + if (!method_exists($this->selectedView, $this->footerMethod)) + throw new Exception("Could not load view. Requested footerMethod not found."); + + $footer = call_user_func_array([$this->selectedView, $this->footerMethod], []); + } + + } catch (HaltException $e) { + $output->setStatusHeader(403); + $content = $layouts->get("main/error403"); + $this->selectedMethodName = "Unauthorized"; + } catch (Exception|\Throwable $e) { + Logger::exceptionHandler($e, false); + $output->setStatusHeader(500); + $content = $layouts->get("main/error500"); + $this->selectedMethodName = "Fatal error"; + } + + // If an api is requested, return the content as a json object + if ($routeData['viewType'] === 'AdminAPI') + { + $output->setContentType('json'); + return json_encode($content); + } + + // Otherwise, load the panel and return that + Logger::log("Generating panel wrapper."); + $layouts->reset(false); + $layouts->assign("pageTitle", $this->selectedMethodName); + $layouts->assign("pageIcon", $this->selectedMethodIcon); + $layouts->assign('sidebar', $sidebar); + $layouts->assign('content', $content); + $layouts->assign("footer", $footer); + + return $layouts->get('main/panel'); + } + + public function getAdminURL(): string + { + return $this->adminURL; + } + + /** + * Listener for LayoutLoadEvent + * + * Assigns variables to Layout that this plugin's layouts may require. + * + * @param LayoutLoadEvent $event + */ + public function layoutLoadEventListener(LayoutLoadEvent $event) + { + $event->assign('adminKey', $this->pluginKey); + $event->assign('session', $this->session); + $event->assign('authURL', $this->authPlugin->getAuthenticationURL()); + $event->assign("loadAsync", $this->loadAsync); + } + + + /** + * Listener for RouterCallViewEvent + * + * Verifies that the current user has access to the requested methods. + * + * @param RouterCallViewEvent $event + * @return RouterCallViewEvent + * @throws ReflectionException + * @throws HaltException + */ + public function verifyViewPermissions(RouterCallViewEvent $event): RouterCallViewEvent + { + // Load reflector + $reflector = new ReflectionClass(get_class($event->view)); + + // Pass over each requested method + for ($i = Priority::getHighestPriority(); $i <= Priority::getLowestPriority(); $i++) { + if (!isset($event->viewMethods[$i])) + continue; + + foreach ($event->viewMethods[$i] as $key => $method) + { + // If the method doesn't exist, skip it in general + if (!method_exists($event->view, $method)) + { + unset($event->viewMethods[$i][$key]); + continue; + } + + // If the method exists, verify permission + $methodReflector = $reflector->getMethod($method); + $permissionAttributes = $methodReflector->getAttributes(PermissionAttribute::class); + if (!empty($permissionAttributes)) + { + // Fetch nodes + $nodes = $permissionAttributes[0]->newInstance()->getValue(); + + // Check if any of the nodes are permitted + $found = false; + foreach ($nodes as $node) + if ($this->session->hasPermission($node)) + $found = true; + + // If not, skip + if (!$found) + { + + Logger::logWarning("Current user does not have permission for the requested method. Blocking."); + $event->setCancelled(true); + } + } + } + } + + return $event; + } + + protected string $selectedMethodName = ""; + protected string $selectedMethodIcon = ""; + protected string $footerMethod = ""; + protected View $selectedView; + + public function logViewNameAndIcon(RouterCallViewEvent $event): RouterCallViewEvent + { + $reflector = new ReflectionClass(get_class($event->view)); + $this->selectedView = $event->view; + for ($i = Priority::getHighestPriority(); $i <= Priority::getLowestPriority(); $i++) + { + if (!isset($event->viewMethods[$i])) + continue; + + foreach ($event->viewMethods[$i] as $key => $method) + { + $methodReflector = $reflector->getMethod($method); + + // Check for display attribute. + $displayAttributes = $methodReflector->getAttributes(DisplayAttribute::class); + $iconAttributes = $methodReflector->getAttributes(IconAttribute::class); + $footerAttributes = $methodReflector->getAttributes(FooterCodeMethodAttribute::class); + $this->selectedMethodName = !empty($displayAttributes) ? $displayAttributes[0]->newInstance()->getValue() : ucfirst($methodReflector->getName()); + $this->selectedMethodIcon = !empty($iconAttributes) ? $iconAttributes[0]->newInstance()->getValue() : ""; + $this->footerMethod = !empty($footerAttributes) ? $footerAttributes[0]->newInstance()->getvalue() : ""; + } + } + + return $event; + } + + /** + * @inheritDoc + */ + public function getName(): string + { + return 'admin'; + } + + /** + * @inheritDoc + */ + public function getClassesPrefix(): ?string + { + return null; + } + + /** + * @inheritDoc + */ + public function getSourceDirectory(): ?string + { + return null; + } + + /** + * @inheritDoc + */ + public function getPluginClass(): ?string + { + return null; + } } \ No newline at end of file diff --git a/src/FuzeWorks/Administration/AdminView.php b/src/FuzeWorks/Administration/AdminView.php old mode 100644 new mode 100755 index 371eb1f..338965d --- a/src/FuzeWorks/Administration/AdminView.php +++ b/src/FuzeWorks/Administration/AdminView.php @@ -1,61 +1,70 @@ -layouts = Factory::getInstance('layouts'); - } +layouts = Factory::getInstance('layouts'); + + /** @var AuthenticationPlugin $plugin */ + $plugin = $this->plugins->get('auth'); + + // Set current session + $this->users = $plugin->users; + $this->session = $plugin->sessions->getCurrentSession(); + } } \ No newline at end of file diff --git a/src/FuzeWorks/Administration/Attributes/AdminAttribute.php b/src/FuzeWorks/Administration/Attributes/AdminAttribute.php new file mode 100755 index 0000000..04cbbd1 --- /dev/null +++ b/src/FuzeWorks/Administration/Attributes/AdminAttribute.php @@ -0,0 +1,41 @@ +value = $value; + } + + public function getValue(): string + { + return $this->value; + } + + + +} \ No newline at end of file diff --git a/src/FuzeWorks/Administration/Attributes/FooterCodeMethodAttribute.php b/src/FuzeWorks/Administration/Attributes/FooterCodeMethodAttribute.php new file mode 100644 index 0000000..939e196 --- /dev/null +++ b/src/FuzeWorks/Administration/Attributes/FooterCodeMethodAttribute.php @@ -0,0 +1,55 @@ +value = $value; + } + + public function getValue(): string + { + return $this->value; + } + +} \ No newline at end of file diff --git a/src/FuzeWorks/Administration/Attributes/HiddenAttribute.php b/src/FuzeWorks/Administration/Attributes/HiddenAttribute.php new file mode 100755 index 0000000..71ce9c6 --- /dev/null +++ b/src/FuzeWorks/Administration/Attributes/HiddenAttribute.php @@ -0,0 +1,44 @@ +value = $value; + } + + public function getValue(): string + { + return $this->value; + } + +} \ No newline at end of file diff --git a/src/FuzeWorks/Administration/Attributes/PermissionAttribute.php b/src/FuzeWorks/Administration/Attributes/PermissionAttribute.php new file mode 100644 index 0000000..c446c31 --- /dev/null +++ b/src/FuzeWorks/Administration/Attributes/PermissionAttribute.php @@ -0,0 +1,55 @@ +nodes = $nodes; + } + + public function getValue(): array + { + return $this->nodes; + } + +} \ No newline at end of file diff --git a/src/FuzeWorks/Administration/Attributes/PriorityAttribute.php b/src/FuzeWorks/Administration/Attributes/PriorityAttribute.php new file mode 100755 index 0000000..d24b4b9 --- /dev/null +++ b/src/FuzeWorks/Administration/Attributes/PriorityAttribute.php @@ -0,0 +1,54 @@ +priority = $value; + } + + public function getValue(): int + { + return $this->priority; + } + +} \ No newline at end of file diff --git a/src/FuzeWorks/Administration/Exceptions/AdminPluginException.php b/src/FuzeWorks/Administration/Exceptions/AdminPluginException.php new file mode 100644 index 0000000..10e5a0b --- /dev/null +++ b/src/FuzeWorks/Administration/Exceptions/AdminPluginException.php @@ -0,0 +1,44 @@ +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; - } - +session = $session; + + Logger::log("Generating sidebar..."); + $event = Events::fireEvent('adminGenerateSidebarEvent'); + if ($event->isCancelled()) + return []; + + // First find all views + $views = $this->findViews(); + Logger::log("Found " . count($views) . ' admin views.'); + + // Then sort all those views into methods in the sidebar + $sorted = $this->sortSidebar($views); + + // Log the result and return + Logger::log("Found " . count($sorted) . ' entries for sidebar.'); + 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. + * + * Return a file list. + * + * @return string[] + */ + 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 + $views = []; + 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 (!str_starts_with($file, 'view.admin.')) + 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 $views; + } + + /** + * 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 + { + $out = []; + 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 + $entries = []; + foreach ($views as $view) + { + // Try and reflect on this class. + try { + $reflector = new ReflectionClass($view); + } 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 + if (!$reflector->isSubclassOf("FuzeWorks\Administration\AdminView")) + continue; + + // Determine view Id + $id = strtolower(substr($reflector->getShortName(), 0, -9)); + + // 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 !== $view) + continue; + + // Read attributes + // First read hidden attribute. If present, skip this method + $hiddenAttributes = $method->getAttributes(HiddenAttribute::class); + if (!empty($hiddenAttributes)) + continue; + + // Then check for permissions + $permissionAttributes = $method->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) + continue; + } + + // 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 + $entries[$entry['priority']][] = $entry; + } + } + + // Sort by priority + $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; + } + } \ No newline at end of file diff --git a/test/bootstrap.php b/test/bootstrap.php old mode 100644 new mode 100755 index d42ac15..85d37bc --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -1,63 +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()); +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/async/view.admin.async.php b/views/async/view.admin.async.php new file mode 100644 index 0000000..902448f --- /dev/null +++ b/views/async/view.admin.async.php @@ -0,0 +1,76 @@ +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); + } + +} \ No newline at end of file diff --git a/views/async/view.adminapi.async.php b/views/async/view.adminapi.async.php new file mode 100644 index 0000000..b86383e --- /dev/null +++ b/views/async/view.adminapi.async.php @@ -0,0 +1,67 @@ +libraries->get("tasks"); + $this->tasks = $lib; + } + + public function index() + { + return ["Hello world"]; + } + +} \ No newline at end of file diff --git a/views/main/view.admin.cache.php b/views/main/view.admin.cache.php new file mode 100644 index 0000000..fc61735 --- /dev/null +++ b/views/main/view.admin.cache.php @@ -0,0 +1,32 @@ +controller->getCacheItems(); + return json_encode($items); + } + +} \ No newline at end of file diff --git a/views/view.admin.settings.php b/views/main/view.admin.dashboard.php old mode 100644 new mode 100755 similarity index 64% rename from views/view.admin.settings.php rename to views/main/view.admin.dashboard.php index d7d7f1e..b0708ce --- a/views/view.admin.settings.php +++ b/views/main/view.admin.dashboard.php @@ -1,78 +1,68 @@ -controller->findSettingsFiles(); - $this->layouts->assign('files', $files); - return $this->layouts->get('components/settings/overview'); - } - - /** - * @hidden - */ - public function view() - { - - } - - /** - * @hidden - */ - public function modify() - { - - } - +controller->populateDashboard(); + //$this->layouts->assign('content', $content); + } + + #[HiddenAttribute, DashboardAttribute("Hello!")] + public function test() + { + + } + } \ No newline at end of file diff --git a/views/main/view.admin.settings.php b/views/main/view.admin.settings.php new file mode 100755 index 0000000..81da0e5 --- /dev/null +++ b/views/main/view.admin.settings.php @@ -0,0 +1,124 @@ +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() + { + + } + +} \ No newline at end of file diff --git a/www/.htaccess b/www/.htaccess old mode 100644 new mode 100755 index 4e6cbbe..81396e1 --- a/www/.htaccess +++ b/www/.htaccess @@ -1,4 +1,4 @@ -RewriteEngine On -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d +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/dist/async.ts b/www/dist/async.ts new file mode 100644 index 0000000..c18a1e7 --- /dev/null +++ b/www/dist/async.ts @@ -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) { + + } +} + diff --git a/www/index.php b/www/index.php old mode 100644 new mode 100755 index 28a1ee6..0132049 --- a/www/index.php +++ b/www/index.php @@ -1,41 +1,41 @@ -web; +web; $web->routeWebRequest(); \ No newline at end of file