Compare commits
11 Commits
fdd24d90da
...
2a8fa3f397
Author | SHA1 | Date |
---|---|---|
tracer | 2a8fa3f397 | |
tracer | f6a5e96576 | |
tracer | b731e2c0e1 | |
tracer | f15c2f9ff2 | |
tracer | 7d15313503 | |
tracer | 98e6c7fc64 | |
tracer | 0a334498df | |
tracer | 02c6154629 | |
tracer | 65e2836f23 | |
tracer | 983da7fe88 | |
tracer | 9b3a6b1f3a |
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) <year> <copyright holders>
|
Copyright (c) 2022 Micha Espey <tracer@24unix.net>
|
||||||
|
|
||||||
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:
|
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:
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"dbHost": "localhost",
|
||||||
|
"dbPort": 3306,
|
||||||
|
"dbDatabase": "tracer_addressbook",
|
||||||
|
"dbUser": "tracer_addressbook",
|
||||||
|
"dbPassword": "secret",
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
DirectoryIndex index.php
|
||||||
|
|
||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{REQUEST_URI}::$0 ^(/.+)/(.*)::\2$
|
||||||
|
RewriteRule .* - [E=BASE:%1]
|
||||||
|
RewriteCond %{HTTP:Authorization} .+
|
||||||
|
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]
|
||||||
|
RewriteCond %{ENV:REDIRECT_STATUS} =""
|
||||||
|
RewriteRule ^index\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L]
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteRule ^ %{ENV:BASE}/index.php [L]
|
||||||
|
</IfModule>
|
|
@ -3,12 +3,38 @@
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Service\Template;
|
use App\Service\Template;
|
||||||
use stdClass;
|
|
||||||
|
|
||||||
class AddressBook extends stdClass
|
class AddressBook
|
||||||
{
|
{
|
||||||
public function __construct(Template $template)
|
public function __construct(private readonly Template $template)
|
||||||
{
|
{
|
||||||
$template->render(templateName: 'index.tpl');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function main(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->template->render(templateName: 'index.tpl');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
die($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public function admin(string $command = '')
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->template->render(templateName: 'admin/index.tpl');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
die($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function login()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->template->render(templateName: 'admin/index.tpl');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
die($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -9,9 +9,10 @@ class User
|
||||||
private string $password,
|
private string $password,
|
||||||
private string $first = '',
|
private string $first = '',
|
||||||
private string $last = '',
|
private string $last = '',
|
||||||
private int $id = 0
|
private int $id = 0,
|
||||||
|
private bool $isAdmin = false
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// empty body
|
// empty body
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,255 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Service\Config;
|
||||||
|
use App\Service\DatabaseConnection;
|
||||||
|
use App\Entity\User;
|
||||||
|
use PDO;
|
||||||
|
use PDOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class DomainRepository
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly DatabaseConnection $databaseConnection,
|
||||||
|
private readonly Config $configController)
|
||||||
|
{
|
||||||
|
// empty body
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function findAll(): array
|
||||||
|
{
|
||||||
|
$users = [];
|
||||||
|
$sql = "
|
||||||
|
SELECT id, nick, first, last, is_admin
|
||||||
|
FROM " . DatabaseConnection::TABLE_USERS . "
|
||||||
|
ORDER BY name";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||||
|
$statement->execute();
|
||||||
|
while ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
|
||||||
|
$domain = new Domain(name: $result['name'], panel: $result['panel'], id: $result['id']);
|
||||||
|
$domains[] = $domain;
|
||||||
|
}
|
||||||
|
return $domains;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
exit($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $id
|
||||||
|
*
|
||||||
|
* @return bool|\App\Entity\Domain
|
||||||
|
*/
|
||||||
|
public function findByID(int $id): bool|Domain
|
||||||
|
{
|
||||||
|
$this->logger->debug(message: "findById($id)");
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT id, name, panel
|
||||||
|
FROM . " . DatabaseConnection::TABLE_DOMAINS . "
|
||||||
|
WHERE id = :id";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||||
|
$statement->bindParam(param: ':id', var: $id);
|
||||||
|
$statement->execute();
|
||||||
|
if ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
|
||||||
|
|
||||||
|
return new Domain(name: $result['name'], panel: $result['panel'], id: $result['id']);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
exit($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param String $name
|
||||||
|
*
|
||||||
|
* @return \App\Entity\Domain|bool
|
||||||
|
*/
|
||||||
|
public function findByName(string $name): Domain|bool
|
||||||
|
{
|
||||||
|
$this->logger->debug(message: "findByName($name)");
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT id, name, panel
|
||||||
|
FROM " . DatabaseConnection::TABLE_DOMAINS . "
|
||||||
|
WHERE name = :name";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||||
|
$statement->bindParam(param: ':name', var: $name);
|
||||||
|
$statement->execute();
|
||||||
|
if ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
|
||||||
|
return new Domain(name: $result['name'], panel: $result['panel'], id: $result['id']);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
exit($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $host
|
||||||
|
*
|
||||||
|
* @return \App\Entity\Domain|bool
|
||||||
|
*/
|
||||||
|
public function findByHost(string $host): Domain|bool
|
||||||
|
{
|
||||||
|
$this->logger->debug(message: "findByHost($host)");
|
||||||
|
|
||||||
|
$host = strtolower(string: trim(string: $host));
|
||||||
|
$count = substr_count(haystack: $host, needle: '.');
|
||||||
|
if ($count == 2) {
|
||||||
|
if (strlen(string: explode(separator: '.', string: $host)[1]) > 3) {
|
||||||
|
$host = explode(separator: '.', string: $host, limit: 2)[1];
|
||||||
|
}
|
||||||
|
} elseif ($count > 2) {
|
||||||
|
$host = $this->findByHost(host: explode(separator: '.', string: $host, limit: 2)[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($domain = $this->findByName(name: $host)) {
|
||||||
|
return $domain;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \App\Entity\Domain $domain
|
||||||
|
*
|
||||||
|
* @return string|false
|
||||||
|
*/
|
||||||
|
public function insert(Domain $domain): bool|string
|
||||||
|
{
|
||||||
|
$domainName = $domain->getName();
|
||||||
|
$this->logger->info(message: "insert($domainName)");
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
INSERT INTO " . DatabaseConnection::TABLE_DOMAINS . " (name, panel)
|
||||||
|
VALUES (:name, :panel)";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$name = $domain->getName();
|
||||||
|
$panel = $domain->getPanel();
|
||||||
|
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||||
|
$statement->bindParam(param: ':name', var: $name);
|
||||||
|
$statement->bindParam(param: ':panel', var: $panel);
|
||||||
|
$statement->execute();
|
||||||
|
|
||||||
|
return $this->databaseConnection->getConnection()->lastInsertId();
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
exit($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \App\Entity\Domain $domain
|
||||||
|
*
|
||||||
|
* @return false|int
|
||||||
|
*/
|
||||||
|
public function update(Domain $domain): bool|int
|
||||||
|
{
|
||||||
|
$domainName = $domain->getName();
|
||||||
|
$this->logger->debug(message: "update($domainName)");
|
||||||
|
|
||||||
|
$id = $domain->getId();
|
||||||
|
$current = $this->findByID(id: $id);
|
||||||
|
|
||||||
|
if (empty($domain->getName())) {
|
||||||
|
$name = $current->getName();
|
||||||
|
} else {
|
||||||
|
$name = $domain->getName();
|
||||||
|
}
|
||||||
|
if (empty($domain->getPanel())) {
|
||||||
|
$panel = $current->getPanel();
|
||||||
|
} else {
|
||||||
|
$panel = $domain->getPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
UPDATE " . DatabaseConnection::TABLE_DOMAINS . " SET
|
||||||
|
name = :name,
|
||||||
|
panel = :panel
|
||||||
|
WHERE id = :id";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||||
|
$statement->bindParam(param: 'id', var: $id);
|
||||||
|
$statement->bindParam(param: 'name', var: $name);
|
||||||
|
$statement->bindParam(param: 'panel', var: $panel);
|
||||||
|
$statement->execute();
|
||||||
|
|
||||||
|
return $statement->rowCount();
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo $e->getMessage();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \App\Entity\Domain $domain
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function delete(Domain $domain): int
|
||||||
|
{
|
||||||
|
$domainName = $domain->getName();
|
||||||
|
$this->logger->debug(message: "delete($domainName)");
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
DELETE FROM " . DatabaseConnection::TABLE_DOMAINS . "
|
||||||
|
WHERE id = :id";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||||
|
$id = $domain->getId();
|
||||||
|
$statement->bindParam(param: 'id', var: $id);
|
||||||
|
$statement->execute();
|
||||||
|
|
||||||
|
return $statement->rowCount();
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
exit($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param String $field
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getLongestEntry(string $field): int
|
||||||
|
{
|
||||||
|
$sql = "
|
||||||
|
SELECT MAX(LENGTH(" . $field . ")) as length FROM " . DatabaseConnection::TABLE_DOMAINS;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
|
||||||
|
$statement->execute();
|
||||||
|
$result = $statement->fetch();
|
||||||
|
return $result['length'];
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
exit($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Config
|
||||||
|
{
|
||||||
|
private array $config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$configFile = dirname(path: __DIR__, levels: 2) . "/config.json.local";
|
||||||
|
if (!file_exists(filename: $configFile)) {
|
||||||
|
$configFile = dirname(path: __DIR__, levels: 2) . "/config.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_exists(filename: $configFile)) {
|
||||||
|
throw new Exception(message: 'Missing config file');
|
||||||
|
}
|
||||||
|
$configJSON = file_get_contents(filename: $configFile);
|
||||||
|
|
||||||
|
if (json_decode(json: $configJSON) === null) {
|
||||||
|
throw new Exception(message: 'Config file is not valid JSON.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->config = json_decode(json: $configJSON, associative: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConfig(string $configKey): string
|
||||||
|
{
|
||||||
|
return $this->config[$configKey];
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,31 +3,30 @@
|
||||||
namespace App\Service;
|
namespace App\Service;
|
||||||
|
|
||||||
use App\Controller\AddressBook;
|
use App\Controller\AddressBook;
|
||||||
use Exception;
|
|
||||||
use stdClass;
|
|
||||||
|
|
||||||
class Container
|
class Container
|
||||||
{
|
{
|
||||||
// no autowiring yet, maybe later, but it might fit for a demo
|
// caveat: Classes are always instantiated
|
||||||
|
// No autowiring (yet, maybe later, but it might fit for a demo)
|
||||||
|
|
||||||
private Template $template;
|
private Template $template;
|
||||||
private AddressBook $addressBook;
|
private AddressBook $addressBook;
|
||||||
|
private Router $router;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->template = new Template(templateDir: dirname(path: __DIR__, levels: 2) . '/templates/');
|
$this->template = new Template(templateDir: dirname(path: __DIR__, levels: 2) . '/templates/');
|
||||||
$this->addressBook = new AddressBook(template: $this->template);
|
$this->addressBook = new AddressBook(template: $this->template);
|
||||||
|
$this->router = new Router();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function get(string $className): object
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function get(string $class): stdClass
|
|
||||||
{
|
{
|
||||||
return match($class) {
|
return match ($className) {
|
||||||
'App\Controller\AddressBook' => $this->addressBook,
|
'App\Controller\AddressBook' => $this->addressBook,
|
||||||
default => throw new Exception(message: "Missing class definition: $class")
|
'App\Service\Router' => $this->router,
|
||||||
|
//default => throw new Exception(message: "Missing class definition: $class")
|
||||||
|
default => die("Missing class definition: $className")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class DatabaseConnection
|
||||||
|
{
|
||||||
|
private PDO $dbConnection;
|
||||||
|
|
||||||
|
const TABLE_PREFIX = '';
|
||||||
|
const TABLE_USERS = self::TABLE_PREFIX . "users";
|
||||||
|
const TABLE_ADDRESSES = self::TABLE_PREFIX . "addresses";
|
||||||
|
|
||||||
|
public function __construct(private readonly Config $configController)
|
||||||
|
{
|
||||||
|
$dbHost = $this->configController->getConfig(configKey: 'dbHost');
|
||||||
|
$dbPort = $this->configController->getConfig(configKey: 'dbPort');
|
||||||
|
$dbDatabase = $this->configController->getConfig(configKey: 'dbDatabase');
|
||||||
|
$dbUser = $this->configController->getConfig(configKey: 'dbUser');
|
||||||
|
$dbPassword = $this->configController->getConfig(configKey: 'dbPassword');
|
||||||
|
|
||||||
|
$this->dbConnection = new PDO(
|
||||||
|
dsn: "mysql:host=$dbHost;port=$dbPort;charset=utf8mb4;dbname=$dbDatabase",
|
||||||
|
username: $dbUser,
|
||||||
|
password: $dbPassword
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConnection(): PDO
|
||||||
|
{
|
||||||
|
return $this->dbConnection;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
|
||||||
|
class Router
|
||||||
|
{
|
||||||
|
private array $uri;
|
||||||
|
private array $routes;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$uri = parse_url(url: $_SERVER['REQUEST_URI'], component: PHP_URL_PATH);
|
||||||
|
$this->uri = explode(separator: '/', string: $uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerRoute(string $route, \Closure $callback): void
|
||||||
|
{
|
||||||
|
$this->routes[$route] = $callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function matchRoute($url = '/users/tracer/posts/tracer', $method = 'GET')
|
||||||
|
{
|
||||||
|
$reqUrl = $url;
|
||||||
|
|
||||||
|
$reqUrl = rtrim(string: $reqUrl, characters: "/");
|
||||||
|
|
||||||
|
foreach ($this->routes as $route => $closure) {
|
||||||
|
// convert urls like '/users/:uid/posts/:pid' to regular expression
|
||||||
|
// $pattern = "@^" . preg_replace('/\\\:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', preg_quote($route['url'])) . "$@D";
|
||||||
|
$pattern = "@^" . preg_replace('\\/users/:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', $route) . "$@D";
|
||||||
|
// echo $pattern."\n";
|
||||||
|
$params = [];
|
||||||
|
// check if the current request params the expression
|
||||||
|
$match = preg_match($pattern, $reqUrl, $params);
|
||||||
|
if ($match) {
|
||||||
|
// remove the first match
|
||||||
|
array_shift($params);
|
||||||
|
// call the callback with the matched positions as params
|
||||||
|
// return call_user_func_array($route['callback'], $params);
|
||||||
|
return [$route, $params];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleRouting(): void
|
||||||
|
{
|
||||||
|
$foo = $this->matchRoute();
|
||||||
|
var_dump($foo);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,15 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
spl_autoload_register(callback: function($className) {
|
spl_autoload_register(callback: function ($className) {
|
||||||
$prefix = 'App';
|
$prefix = 'App';
|
||||||
$baseDir = __DIR__;
|
$baseDir = __DIR__;
|
||||||
|
|
||||||
$len = strlen(string: $prefix);
|
$prefixLen = strlen(string: $prefix);
|
||||||
if (strncmp(string1: $prefix, string2: $className, length: $len) !== 0) {
|
if (strncmp(string1: $prefix, string2: $className, length: $prefixLen) !== 0) {
|
||||||
return;
|
die("Invalid class: $className");
|
||||||
}
|
}
|
||||||
$realClassName = substr(string: $className, offset: $len);
|
$realClassNamePSRpath = substr(string: $className, offset: $prefixLen);
|
||||||
$classLocation = $baseDir . str_replace(search: '\\', replace: '/', subject: $realClassName) . '.php';
|
$classLocation = $baseDir . str_replace(search: '\\', replace: '/', subject: $realClassNamePSRpath) . '.php';
|
||||||
if (file_exists(filename: $classLocation)) {
|
if (file_exists(filename: $classLocation)) {
|
||||||
require $classLocation;
|
require $classLocation;
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue