removed notification + new react structure
This commit is contained in:
parent
303a5b69b5
commit
b1c4c9e976
@ -1,159 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Core\API {
|
|
||||||
|
|
||||||
use Core\Objects\Context;
|
|
||||||
|
|
||||||
abstract class NewsAPI extends Request {
|
|
||||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
|
||||||
parent::__construct($context, $externalCall, $params);
|
|
||||||
$this->loginRequired = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Core\API\News {
|
|
||||||
|
|
||||||
use Core\API\NewsAPI;
|
|
||||||
use Core\API\Parameter\Parameter;
|
|
||||||
use Core\API\Parameter\StringType;
|
|
||||||
use Core\Driver\SQL\Condition\Compare;
|
|
||||||
use Core\Objects\Context;
|
|
||||||
use Core\Objects\DatabaseEntity\Group;
|
|
||||||
use Core\Objects\DatabaseEntity\News;
|
|
||||||
|
|
||||||
class Get extends NewsAPI {
|
|
||||||
|
|
||||||
public function __construct(Context $context, bool $externalCall = false) {
|
|
||||||
parent::__construct($context, $externalCall, [
|
|
||||||
"since" => new Parameter("since", Parameter::TYPE_DATE_TIME, true, null),
|
|
||||||
"limit" => new Parameter("limit", Parameter::TYPE_INT, true, 10)
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->loginRequired = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function _execute(): bool {
|
|
||||||
$since = $this->getParam("since");
|
|
||||||
$limit = $this->getParam("limit");
|
|
||||||
if ($limit < 1 || $limit > 30) {
|
|
||||||
return $this->createError("Limit must be in range 1-30");
|
|
||||||
}
|
|
||||||
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$newsQuery = News::createBuilder($sql, false)
|
|
||||||
->limit($limit)
|
|
||||||
->orderBy("published_at")
|
|
||||||
->descending()
|
|
||||||
->fetchEntities();
|
|
||||||
|
|
||||||
if ($since) {
|
|
||||||
$newsQuery->where(new Compare("published_at", $since, ">="));
|
|
||||||
}
|
|
||||||
|
|
||||||
$newsArray = News::findBy($newsQuery);
|
|
||||||
$this->success = $newsArray !== null;
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
|
|
||||||
if ($this->success) {
|
|
||||||
$this->result["news"] = [];
|
|
||||||
foreach ($newsArray as $news) {
|
|
||||||
$newsId = $news->getId();
|
|
||||||
$this->result["news"][$newsId] = $news->jsonSerialize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Publish extends NewsAPI {
|
|
||||||
public function __construct(Context $context, bool $externalCall = false) {
|
|
||||||
parent::__construct($context, $externalCall, [
|
|
||||||
"title" => new StringType("title", 128),
|
|
||||||
"text" => new StringType("text", 1024)
|
|
||||||
]);
|
|
||||||
$this->loginRequired = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function _execute(): bool {
|
|
||||||
|
|
||||||
$news = new News();
|
|
||||||
$news->text = $this->getParam("text");
|
|
||||||
$news->title = $this->getParam("title");
|
|
||||||
$news->publishedBy = $this->context->getUser();
|
|
||||||
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$this->success = $news->save($sql);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
|
|
||||||
if ($this->success) {
|
|
||||||
$this->result["newsId"] = $news->getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Delete extends NewsAPI {
|
|
||||||
public function __construct(Context $context, bool $externalCall = false) {
|
|
||||||
parent::__construct($context, $externalCall, [
|
|
||||||
"id" => new Parameter("id", Parameter::TYPE_INT)
|
|
||||||
]);
|
|
||||||
$this->loginRequired = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function _execute(): bool {
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$currentUser = $this->context->getUser();
|
|
||||||
|
|
||||||
$news = News::find($sql, $this->getParam("id"));
|
|
||||||
$this->success = ($news !== false);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
if (!$this->success) {
|
|
||||||
return false;
|
|
||||||
} else if ($news === null) {
|
|
||||||
return $this->createError("News Post not found");
|
|
||||||
} else if ($news->publishedBy->getId() !== $currentUser->getId() && !$currentUser->hasGroup(Group::ADMIN)) {
|
|
||||||
return $this->createError("You do not have permissions to delete news post of other users.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->success = $news->delete($sql);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Edit extends NewsAPI {
|
|
||||||
public function __construct(Context $context, bool $externalCall = false) {
|
|
||||||
parent::__construct($context, $externalCall, [
|
|
||||||
"id" => new Parameter("id", Parameter::TYPE_INT),
|
|
||||||
"title" => new StringType("title", 128),
|
|
||||||
"text" => new StringType("text", 1024)
|
|
||||||
]);
|
|
||||||
$this->loginRequired = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function _execute(): bool {
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$currentUser = $this->context->getUser();
|
|
||||||
|
|
||||||
$news = News::find($sql, $this->getParam("id"));
|
|
||||||
$this->success = ($news !== false);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
if (!$this->success) {
|
|
||||||
return false;
|
|
||||||
} else if ($news === null) {
|
|
||||||
return $this->createError("News Post not found");
|
|
||||||
} else if ($news->publishedBy->getId() !== $currentUser->getId() && !$currentUser->hasGroup(Group::ADMIN)) {
|
|
||||||
return $this->createError("You do not have permissions to edit news post of other users.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$news->text = $this->getParam("text");
|
|
||||||
$news->title = $this->getParam("title");
|
|
||||||
$this->success = $news->save($sql);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,7 +5,6 @@ namespace Core\API;
|
|||||||
use Core\API\Parameter\StringType;
|
use Core\API\Parameter\StringType;
|
||||||
use Core\Objects\Context;
|
use Core\Objects\Context;
|
||||||
use Core\Objects\DatabaseEntity\Group;
|
use Core\Objects\DatabaseEntity\Group;
|
||||||
use Core\Objects\DatabaseEntity\User;
|
|
||||||
|
|
||||||
class Swagger extends Request {
|
class Swagger extends Request {
|
||||||
|
|
||||||
@ -26,7 +25,7 @@ class Swagger extends Request {
|
|||||||
$classes = [];
|
$classes = [];
|
||||||
$apiDirs = ["Core", "Site"];
|
$apiDirs = ["Core", "Site"];
|
||||||
foreach ($apiDirs as $apiDir) {
|
foreach ($apiDirs as $apiDir) {
|
||||||
$basePath = realpath(WEBROOT . "/$apiDir/Api/");
|
$basePath = realpath(WEBROOT . "/$apiDir/API/");
|
||||||
if (!$basePath) {
|
if (!$basePath) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -36,7 +35,7 @@ class Swagger extends Request {
|
|||||||
if (is_file($fullPath) && endsWith($fileName, ".class.php")) {
|
if (is_file($fullPath) && endsWith($fileName, ".class.php")) {
|
||||||
require_once $fullPath;
|
require_once $fullPath;
|
||||||
$apiName = explode(".", $fileName)[0];
|
$apiName = explode(".", $fileName)[0];
|
||||||
$className = "\\API\\$apiName";
|
$className = "\\$apiDir\\API\\$apiName";
|
||||||
if (!class_exists($className)) {
|
if (!class_exists($className)) {
|
||||||
var_dump("Class not exist: $className");
|
var_dump("Class not exist: $className");
|
||||||
continue;
|
continue;
|
||||||
@ -108,6 +107,7 @@ class Swagger extends Request {
|
|||||||
$settings = $this->context->getSettings();
|
$settings = $this->context->getSettings();
|
||||||
$siteName = $settings->getSiteName();
|
$siteName = $settings->getSiteName();
|
||||||
$domain = parse_url($settings->getBaseUrl(), PHP_URL_HOST);
|
$domain = parse_url($settings->getBaseUrl(), PHP_URL_HOST);
|
||||||
|
$protocol = getProtocol();
|
||||||
|
|
||||||
$permissions = $this->fetchPermissions();
|
$permissions = $this->fetchPermissions();
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ class Swagger extends Request {
|
|||||||
],
|
],
|
||||||
"host" => $domain,
|
"host" => $domain,
|
||||||
"basePath" => "/api",
|
"basePath" => "/api",
|
||||||
"schemes" => ["https"],
|
"schemes" => ["$protocol"],
|
||||||
"paths" => $paths,
|
"paths" => $paths,
|
||||||
"definitions" => $definitions
|
"definitions" => $definitions
|
||||||
];
|
];
|
||||||
|
@ -328,37 +328,13 @@ namespace Core\API\User {
|
|||||||
} else {
|
} else {
|
||||||
|
|
||||||
$queriedUser = $user->jsonSerialize();
|
$queriedUser = $user->jsonSerialize();
|
||||||
|
|
||||||
// either we are querying own info or we are support / admin
|
|
||||||
$currentUser = $this->context->getUser();
|
$currentUser = $this->context->getUser();
|
||||||
$canView = ($userId === $currentUser->getId() ||
|
|
||||||
$currentUser->hasGroup(Group::ADMIN) ||
|
|
||||||
$currentUser->hasGroup(Group::SUPPORT));
|
|
||||||
|
|
||||||
// full info only when we have administrative privileges, or we are querying ourselves
|
// full info only when we have administrative privileges, or we are querying ourselves
|
||||||
$fullInfo = ($userId === $currentUser->getId() ||
|
$fullInfo = ($userId === $currentUser->getId() ||
|
||||||
$currentUser->hasGroup(Group::ADMIN) ||
|
$currentUser->hasGroup(Group::ADMIN) ||
|
||||||
$currentUser->hasGroup(Group::SUPPORT));
|
$currentUser->hasGroup(Group::SUPPORT));
|
||||||
|
|
||||||
if (!$canView) {
|
|
||||||
|
|
||||||
// check if user posted something publicly
|
|
||||||
$res = $sql->select(new JsonArrayAgg(new Column("publishedBy"), "publisherIds"))
|
|
||||||
->from("News")
|
|
||||||
->execute();
|
|
||||||
$this->success = ($res !== false);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
if (!$this->success) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
$canView = in_array($userId, json_decode($res[0]["publisherIds"], true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$canView) {
|
|
||||||
return $this->createError("No permissions to access this user");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$fullInfo) {
|
if (!$fullInfo) {
|
||||||
if (!$queriedUser["confirmed"]) {
|
if (!$queriedUser["confirmed"]) {
|
||||||
return $this->createError("No permissions to access this user");
|
return $this->createError("No permissions to access this user");
|
||||||
|
@ -21,9 +21,9 @@ class CreateDatabase extends DatabaseScript {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$queries[] = Group::getHandler($sql)->getInsertQuery([
|
$queries[] = Group::getHandler($sql)->getInsertQuery([
|
||||||
new Group(Group::ADMIN, Group::GROUPS[Group::ADMIN], "#007bff"),
|
new Group(Group::ADMIN, Group::GROUPS[Group::ADMIN], "#dc3545"),
|
||||||
new Group(Group::MODERATOR, Group::GROUPS[Group::MODERATOR], "#28a745"),
|
new Group(Group::MODERATOR, Group::GROUPS[Group::MODERATOR], "#28a745"),
|
||||||
new Group(Group::SUPPORT, Group::GROUPS[Group::SUPPORT], "#dc3545"),
|
new Group(Group::SUPPORT, Group::GROUPS[Group::SUPPORT], "#007bff"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$queries[] = $sql->createTable("Visitor")
|
$queries[] = $sql->createTable("Visitor")
|
||||||
@ -84,6 +84,7 @@ class CreateDatabase extends DatabaseScript {
|
|||||||
->addRow("Mail/Sync", array(Group::SUPPORT, Group::ADMIN), "Allows users to synchronize mails with the database")
|
->addRow("Mail/Sync", array(Group::SUPPORT, Group::ADMIN), "Allows users to synchronize mails with the database")
|
||||||
->addRow("Settings/get", array(Group::ADMIN), "Allows users to fetch server settings")
|
->addRow("Settings/get", array(Group::ADMIN), "Allows users to fetch server settings")
|
||||||
->addRow("Settings/set", array(Group::ADMIN), "Allows users create, delete or modify server settings")
|
->addRow("Settings/set", array(Group::ADMIN), "Allows users create, delete or modify server settings")
|
||||||
|
->addRow("Settings/generateJWT", array(Group::ADMIN), "Allows users generate a new jwt key")
|
||||||
->addRow("Stats", array(Group::ADMIN, Group::SUPPORT), "Allows users to fetch server stats")
|
->addRow("Stats", array(Group::ADMIN, Group::SUPPORT), "Allows users to fetch server stats")
|
||||||
->addRow("User/create", array(Group::ADMIN), "Allows users to create a new user, email address does not need to be confirmed")
|
->addRow("User/create", array(Group::ADMIN), "Allows users to create a new user, email address does not need to be confirmed")
|
||||||
->addRow("User/fetch", array(Group::ADMIN, Group::SUPPORT), "Allows users to list all registered users")
|
->addRow("User/fetch", array(Group::ADMIN, Group::SUPPORT), "Allows users to list all registered users")
|
||||||
|
@ -47,15 +47,18 @@ class Settings {
|
|||||||
|
|
||||||
public function getJwtPublicKey(bool $allowPrivate = true): ?\Firebase\JWT\Key {
|
public function getJwtPublicKey(bool $allowPrivate = true): ?\Firebase\JWT\Key {
|
||||||
if (empty($this->jwtPublicKey)) {
|
if (empty($this->jwtPublicKey)) {
|
||||||
|
if ($allowPrivate && $this->jwtSecretKey) {
|
||||||
// we might have a symmetric key, should we instead return the private key?
|
// we might have a symmetric key, should we instead return the private key?
|
||||||
return $allowPrivate ? new \Firebase\JWT\Key($this->jwtSecretKey, $this->jwtAlgorithm) : null;
|
return new \Firebase\JWT\Key($this->jwtSecretKey, $this->jwtAlgorithm);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return new \Firebase\JWT\Key($this->jwtPublicKey, $this->jwtAlgorithm);
|
return new \Firebase\JWT\Key($this->jwtPublicKey, $this->jwtAlgorithm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getJwtSecretKey(): \Firebase\JWT\Key {
|
public function getJwtSecretKey(): ?\Firebase\JWT\Key {
|
||||||
return new \Firebase\JWT\Key($this->jwtSecretKey, $this->jwtAlgorithm);
|
return $this->jwtSecretKey ? new \Firebase\JWT\Key($this->jwtSecretKey, $this->jwtAlgorithm) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isInstalled(): bool {
|
public function isInstalled(): bool {
|
||||||
|
@ -198,15 +198,6 @@ namespace Documents\Install {
|
|||||||
$success = $req->execute(array("settings" => array("installation_completed" => "1")));
|
$success = $req->execute(array("settings" => array("installation_completed" => "1")));
|
||||||
if (!$success) {
|
if (!$success) {
|
||||||
$this->errorString = $req->getLastError();
|
$this->errorString = $req->getLastError();
|
||||||
} else {
|
|
||||||
$req = new \Core\API\Notifications\Create($context);
|
|
||||||
$req->execute(array(
|
|
||||||
"title" => "Welcome",
|
|
||||||
"message" => "Your Web-base was successfully installed. Check out the admin dashboard. Have fun!",
|
|
||||||
"groupId" => Group::ADMIN
|
|
||||||
)
|
|
||||||
);
|
|
||||||
$this->errorString = $req->getLastError();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,8 +103,9 @@ class Context {
|
|||||||
try {
|
try {
|
||||||
$token = $_COOKIE['session'];
|
$token = $_COOKIE['session'];
|
||||||
$settings = $this->configuration->getSettings();
|
$settings = $this->configuration->getSettings();
|
||||||
$decoded = (array)JWT::decode($token, $settings->getJwtSecretKey());
|
$jwtKey = $settings->getJwtSecretKey();
|
||||||
if (!is_null($decoded)) {
|
if ($jwtKey) {
|
||||||
|
$decoded = (array)JWT::decode($token, $jwtKey);
|
||||||
$userId = ($decoded['userId'] ?? NULL);
|
$userId = ($decoded['userId'] ?? NULL);
|
||||||
$sessionId = ($decoded['sessionId'] ?? NULL);
|
$sessionId = ($decoded['sessionId'] ?? NULL);
|
||||||
if (!is_null($userId) && !is_null($sessionId)) {
|
if (!is_null($userId) && !is_null($sessionId)) {
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Core\Objects\DatabaseEntity;
|
|
||||||
|
|
||||||
use Core\API\Parameter\Parameter;
|
|
||||||
use Core\Driver\SQL\Expression\CurrentTimeStamp;
|
|
||||||
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
|
||||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
|
||||||
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
|
||||||
|
|
||||||
class News extends DatabaseEntity {
|
|
||||||
|
|
||||||
public User $publishedBy;
|
|
||||||
#[DefaultValue(CurrentTimeStamp::class)] private \DateTime $publishedAt;
|
|
||||||
#[MaxLength(128)] public string $title;
|
|
||||||
#[MaxLength(1024)] public string $text;
|
|
||||||
|
|
||||||
public function __construct(?int $id = null) {
|
|
||||||
parent::__construct($id);
|
|
||||||
$this->publishedAt = new \DateTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function jsonSerialize(): array {
|
|
||||||
return [
|
|
||||||
"id" => $this->getId(),
|
|
||||||
"publishedBy" => $this->publishedBy->jsonSerialize(),
|
|
||||||
"publishedAt" => $this->publishedAt->format(Parameter::DATE_TIME_FORMAT),
|
|
||||||
"title" => $this->title,
|
|
||||||
"text" => $this->text
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
{% extends "account.twig" %}
|
{% extends "account/account_base.twig" %}
|
||||||
|
|
||||||
{% set view_title = 'Invitation' %}
|
{% set view_title = 'Invitation' %}
|
||||||
{% set view_icon = 'user-check' %}
|
{% set view_icon = 'user-check' %}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% extends "account.twig" %}
|
{% extends "account/account_base.twig" %}
|
||||||
|
|
||||||
{% set view_title = 'Confirm Email' %}
|
{% set view_title = 'Confirm Email' %}
|
||||||
{% set view_icon = 'user-check' %}
|
{% set view_icon = 'user-check' %}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% extends "account.twig" %}
|
{% extends "account/account_base.twig" %}
|
||||||
|
|
||||||
{% set view_title = 'Sign In' %}
|
{% set view_title = 'Sign In' %}
|
||||||
{% set view_icon = 'user-lock' %}
|
{% set view_icon = 'user-lock' %}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% extends "account.twig" %}
|
{% extends "account/account_base.twig" %}
|
||||||
|
|
||||||
{% set view_title = 'Registration' %}
|
{% set view_title = 'Registration' %}
|
||||||
{% set view_icon = 'user-plus' %}
|
{% set view_icon = 'user-plus' %}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% extends "account.twig" %}
|
{% extends "account/account_base.twig" %}
|
||||||
|
|
||||||
{% set view_title = 'Resend Confirm Email' %}
|
{% set view_title = 'Resend Confirm Email' %}
|
||||||
{% set view_icon = 'envelope' %}
|
{% set view_icon = 'envelope' %}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% extends "account.twig" %}
|
{% extends "account/account_base.twig" %}
|
||||||
|
|
||||||
{% set view_title = 'Reset Password' %}
|
{% set view_title = 'Reset Password' %}
|
||||||
{% set view_icon = 'user-lock' %}
|
{% set view_icon = 'user-lock' %}
|
||||||
|
@ -7,6 +7,6 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<noscript>You need Javascript enabled to run this app</noscript>
|
<noscript>You need Javascript enabled to run this app</noscript>
|
||||||
<div class="wrapper" id="root"></div>
|
<div class="wrapper" type="module" id="admin-panel"></div>
|
||||||
<script src="/js/admin.min.js" nonce="{{ site.csp.nonce }}"></script>
|
<script src="/js/admin-panel/index.js" nonce="{{ site.csp.nonce }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -10,7 +10,7 @@ if (is_file($autoLoad)) {
|
|||||||
require_once $autoLoad;
|
require_once $autoLoad;
|
||||||
}
|
}
|
||||||
|
|
||||||
define("WEBBASE_VERSION", "2.3.0");
|
const WEBBASE_VERSION = "2.3.0";
|
||||||
|
|
||||||
spl_autoload_extensions(".php");
|
spl_autoload_extensions(".php");
|
||||||
spl_autoload_register(function ($class) {
|
spl_autoload_register(function ($class) {
|
||||||
@ -81,7 +81,7 @@ function generateRandomString($length, $type = "ascii"): string {
|
|||||||
return $randomString;
|
return $randomString;
|
||||||
}
|
}
|
||||||
|
|
||||||
function base64url_decode($data) {
|
function base64url_decode($data): bool|string {
|
||||||
$base64 = strtr($data, '-_', '+/');
|
$base64 = strtr($data, '-_', '+/');
|
||||||
return base64_decode($base64);
|
return base64_decode($base64);
|
||||||
}
|
}
|
||||||
@ -247,7 +247,7 @@ function getClassName($class, bool $short = true): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createError($msg) {
|
function createError($msg): array {
|
||||||
return ["success" => false, "msg" => $msg];
|
return ["success" => false, "msg" => $msg];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": ["@babel/preset-env", "@babel/preset-react"]
|
|
||||||
}
|
|
@ -24,7 +24,7 @@ server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# deny access to specific directories
|
# deny access to specific directories
|
||||||
location ~ ^/(files/uploaded|adminPanel|docker|Site|Core|test)/.*$ {
|
location ~ ^/(files/uploaded|react|docker|Site|Core|test)/.*$ {
|
||||||
rewrite ^(.*)$ /index.php?site=$1;
|
rewrite ^(.*)$ /index.php?site=$1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
js/admin-panel/index.css
Normal file
2
js/admin-panel/index.css
Normal file
File diff suppressed because one or more lines are too long
1
js/admin-panel/index.css.map
Normal file
1
js/admin-panel/index.css.map
Normal file
File diff suppressed because one or more lines are too long
32
js/admin-panel/index.js
Normal file
32
js/admin-panel/index.js
Normal file
File diff suppressed because one or more lines are too long
1
js/admin-panel/index.js.map
Normal file
1
js/admin-panel/index.js.map
Normal file
File diff suppressed because one or more lines are too long
341
js/admin.min.js
vendored
341
js/admin.min.js
vendored
File diff suppressed because one or more lines are too long
157
react/.gitignore
vendored
Normal file
157
react/.gitignore
vendored
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/node,react
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=node,react
|
||||||
|
|
||||||
|
### Node ###
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
### Node Patch ###
|
||||||
|
# Serverless Webpack directories
|
||||||
|
.webpack/
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
# SvelteKit build / generate output
|
||||||
|
.svelte-kit
|
||||||
|
|
||||||
|
### react ###
|
||||||
|
.DS_*
|
||||||
|
**/*.backup.*
|
||||||
|
**/*.back.*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
*.sublime*
|
||||||
|
|
||||||
|
psd
|
||||||
|
thumb
|
||||||
|
sketch
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/node,react
|
15
react/admin-panel/config-overrides.js
Normal file
15
react/admin-panel/config-overrides.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const {
|
||||||
|
override,
|
||||||
|
removeModuleScopePlugin,
|
||||||
|
babelInclude,
|
||||||
|
} = require('customize-cra');
|
||||||
|
|
||||||
|
module.exports = override(
|
||||||
|
removeModuleScopePlugin(),
|
||||||
|
babelInclude([
|
||||||
|
path.resolve(path.join(__dirname, 'src')),
|
||||||
|
fs.realpathSync(path.join(__dirname, '../shared')),
|
||||||
|
]),
|
||||||
|
);
|
26
react/admin-panel/package.json
Normal file
26
react/admin-panel/package.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "admin-panel",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"shared": "link:../shared"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"debug": "react-app-rewired start"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"devDependencies": {},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
1
react/admin-panel/public/css
Symbolic link
1
react/admin-panel/public/css
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../css/
|
1
react/admin-panel/public/fonts
Symbolic link
1
react/admin-panel/public/fonts
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../fonts
|
9
react/admin-panel/public/index.html
Normal file
9
react/admin-panel/public/index.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/fontawesome.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="admin-panel"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
93
react/admin-panel/src/index.jsx
Normal file
93
react/admin-panel/src/index.jsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import './res/adminlte.min.css';
|
||||||
|
import './res/index.css';
|
||||||
|
import API from "shared/api";
|
||||||
|
import Icon from "shared/elements/icon";
|
||||||
|
|
||||||
|
class AdminDashboard extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.api = new API();
|
||||||
|
this.state = {
|
||||||
|
loaded: false,
|
||||||
|
dialog: { onClose: () => this.hideDialog() },
|
||||||
|
notifications: [ ],
|
||||||
|
contactRequests: [ ]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate() {
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialog(message, title, options=["Close"], onOption = null) {
|
||||||
|
const props = { show: true, message: message, title: title, options: options, onOption: onOption };
|
||||||
|
this.setState({ ...this.state, dialog: { ...this.state.dialog, ...props } });
|
||||||
|
}
|
||||||
|
|
||||||
|
hideDialog() {
|
||||||
|
this.setState({ ...this.state, dialog: { ...this.state.dialog, show: false } });
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.api.fetchUser().then(Success => {
|
||||||
|
if (!Success) {
|
||||||
|
document.location = "/admin";
|
||||||
|
} else {
|
||||||
|
this.fetchNotifications();
|
||||||
|
this.fetchContactRequests();
|
||||||
|
setInterval(this.onUpdate.bind(this), 60*1000);
|
||||||
|
this.setState({...this.state, loaded: true});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
if (!this.state.loaded) {
|
||||||
|
return <b>Loading… <Icon icon={"spinner"}/></b>
|
||||||
|
}
|
||||||
|
|
||||||
|
this.controlObj = {
|
||||||
|
showDialog: this.showDialog.bind(this),
|
||||||
|
api: this.api
|
||||||
|
};
|
||||||
|
|
||||||
|
return <b>test</b>
|
||||||
|
/*return <Router>
|
||||||
|
<Header {...this.controlObj} notifications={this.state.notifications} />
|
||||||
|
<Sidebar {...this.controlObj} notifications={this.state.notifications} contactRequests={this.state.contactRequests}/>
|
||||||
|
<div className={"content-wrapper p-2"}>
|
||||||
|
<section className={"content"}>
|
||||||
|
<Switch>
|
||||||
|
<Route path={"/admin/dashboard"}><Overview {...this.controlObj} notifications={this.state.notifications} /></Route>
|
||||||
|
<Route exact={true} path={"/admin/users"}><UserOverview {...this.controlObj} /></Route>
|
||||||
|
<Route path={"/admin/user/add"}><CreateUser {...this.controlObj} /></Route>
|
||||||
|
<Route path={"/admin/user/edit/:userId"} render={(props) => {
|
||||||
|
let newProps = {...props, ...this.controlObj};
|
||||||
|
return <EditUser {...newProps} />
|
||||||
|
}}/>
|
||||||
|
<Route path={"/admin/user/permissions"}><PermissionSettings {...this.controlObj}/></Route>
|
||||||
|
<Route path={"/admin/group/add"}><CreateGroup {...this.controlObj} /></Route>
|
||||||
|
<Route exact={true} path={"/admin/contact/"}><ContactRequestOverview {...this.controlObj} /></Route>
|
||||||
|
<Route path={"/admin/visitors"}><Visitors {...this.controlObj} /></Route>
|
||||||
|
<Route path={"/admin/logs"}><Logs {...this.controlObj} notifications={this.state.notifications} /></Route>
|
||||||
|
<Route path={"/admin/settings"}><Settings {...this.controlObj} /></Route>
|
||||||
|
<Route path={"/admin/pages"}><PageOverview {...this.controlObj} /></Route>
|
||||||
|
<Route path={"/admin/help"}><HelpPage {...this.controlObj} /></Route>
|
||||||
|
<Route path={"*"}><View404 /></Route>
|
||||||
|
</Switch>
|
||||||
|
<Dialog {...this.state.dialog}/>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<Footer />
|
||||||
|
</Router>
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<AdminDashboard />,
|
||||||
|
document.getElementById('admin-panel')
|
||||||
|
);
|
3
react/adminPanel.old/.babelrc
Normal file
3
react/adminPanel.old/.babelrc
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"presets": ["react"],
|
||||||
|
}
|
1
react/adminPanel.old/.htaccess
Normal file
1
react/adminPanel.old/.htaccess
Normal file
@ -0,0 +1 @@
|
|||||||
|
DENY FROM ALL
|
@ -25,9 +25,6 @@
|
|||||||
"build": "webpack --mode production && mv dist/main.js ../js/admin.min.js",
|
"build": "webpack --mode production && mv dist/main.js ../js/admin.min.js",
|
||||||
"debug": "react-scripts start"
|
"debug": "react-scripts start"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
|
||||||
"extends": "react-app"
|
|
||||||
},
|
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
">0.2%",
|
">0.2%",
|
12
react/adminPanel.old/src/include/adminlte.min.css
vendored
Normal file
12
react/adminPanel.old/src/include/adminlte.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
6
react/adminPanel.old/src/include/index.css
Normal file
6
react/adminPanel.old/src/include/index.css
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.page-link { color: #222629; }
|
||||||
|
.page-link:hover { color: black; }
|
||||||
|
|
||||||
|
.ReactCollapse--collapse {
|
||||||
|
transition: height 500ms;
|
||||||
|
}
|
1
react/adminPanel.old/src/include/select2.min.css
vendored
Normal file
1
react/adminPanel.old/src/include/select2.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
0
react/other-module/test.js
Normal file
0
react/other-module/test.js
Normal file
39
react/package.json
Normal file
39
react/package.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "web-base-frontend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"private": true,
|
||||||
|
"targets": {
|
||||||
|
"admin-panel": {
|
||||||
|
"source": "./admin-panel/src/index.jsx",
|
||||||
|
"distDir": "./dist/admin-panel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"workspaces": [
|
||||||
|
"admin-panel"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "npx parcel build",
|
||||||
|
"deploy": "cp -r dist/* ../js/",
|
||||||
|
"clean": "rm -rfd .parcel-cache dist/*"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.20.2",
|
||||||
|
"customize-cra": "^1.0.0",
|
||||||
|
"parcel": "^2.8.0",
|
||||||
|
"react-app-rewired": "^2.2.1",
|
||||||
|
"react-scripts": "^5.0.1"
|
||||||
|
},
|
||||||
|
"@parcel/bundler-default": {
|
||||||
|
"minBundles": 1,
|
||||||
|
"minBundleSize": 3000,
|
||||||
|
"maxParallelRequests": 1
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-router-dom": "^6.4.3"
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import 'babel-polyfill';
|
// import 'babel-polyfill';
|
||||||
|
|
||||||
export default class API {
|
export default class API {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -43,14 +43,6 @@ export default class API {
|
|||||||
return this.apiCall("user/logout");
|
return this.apiCall("user/logout");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNotifications(onlyNew = true) {
|
|
||||||
return this.apiCall("notifications/fetch", { new: onlyNew });
|
|
||||||
}
|
|
||||||
|
|
||||||
async markNotificationsSeen() {
|
|
||||||
return this.apiCall("notifications/seen");
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUser(id) {
|
async getUser(id) {
|
||||||
return this.apiCall("user/get", { id: id });
|
return this.apiCall("user/get", { id: id });
|
||||||
}
|
}
|
||||||
@ -118,16 +110,4 @@ export default class API {
|
|||||||
async getVisitors(type, date) {
|
async getVisitors(type, date) {
|
||||||
return this.apiCall("visitors/stats", { type: type, date: date });
|
return this.apiCall("visitors/stats", { type: type, date: date });
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchContactRequests() {
|
|
||||||
return this.apiCall('contact/fetch');
|
|
||||||
}
|
|
||||||
|
|
||||||
async getContactMessages(id) {
|
|
||||||
return this.apiCall('contact/get', { requestId: id });
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendContactMessage(id, message) {
|
|
||||||
return this.apiCall('contact/respond', { requestId: id, message: message });
|
|
||||||
}
|
|
||||||
};
|
};
|
24
react/shared/elements/icon.jsx
Normal file
24
react/shared/elements/icon.jsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function Icon(props) {
|
||||||
|
|
||||||
|
let classes = props.className || [];
|
||||||
|
classes = Array.isArray(classes) ? classes : classes.toString().split(" ");
|
||||||
|
let type = props.type || "fas";
|
||||||
|
let icon = props.icon;
|
||||||
|
|
||||||
|
classes.push(type);
|
||||||
|
classes.push("fa-" + icon);
|
||||||
|
|
||||||
|
if (icon === "spinner" || icon === "circle-notch") {
|
||||||
|
classes.push("fa-spin");
|
||||||
|
}
|
||||||
|
|
||||||
|
let newProps = {...props, className: classes.join(" ") };
|
||||||
|
delete newProps["type"];
|
||||||
|
delete newProps["icon"];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<i {...newProps} />
|
||||||
|
);
|
||||||
|
}
|
8
react/shared/package.json
Normal file
8
react/shared/package.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "shared",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"devDependencies": { },
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": ""
|
||||||
|
}
|
9759
react/yarn.lock
Normal file
9759
react/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user