Namespace and ClassPath rewrites
This commit is contained in:
9
Core/Elements/Body.class.php
Normal file
9
Core/Elements/Body.class.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Elements;
|
||||
|
||||
abstract class Body extends View {
|
||||
public function __construct($document) {
|
||||
parent::__construct($document);
|
||||
}
|
||||
}
|
||||
152
Core/Elements/Document.class.php
Normal file
152
Core/Elements/Document.class.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Elements;
|
||||
|
||||
use Core\Configuration\Settings;
|
||||
use Core\Driver\Logger\Logger;
|
||||
use Core\Driver\SQL\SQL;
|
||||
use Core\Objects\Context;
|
||||
use Core\Objects\lang\LanguageModule;
|
||||
use Core\Objects\Router\DocumentRoute;
|
||||
use Core\Objects\Router\Router;
|
||||
use Core\Objects\DatabaseEntity\User;
|
||||
use Core\Objects\Search\Searchable;
|
||||
use Core\Objects\Search\SearchQuery;
|
||||
use Core\Objects\Search\SearchResult;
|
||||
|
||||
abstract class Document {
|
||||
|
||||
protected Router $router;
|
||||
private Logger $logger;
|
||||
protected bool $databaseRequired;
|
||||
private bool $cspEnabled;
|
||||
private ?string $cspNonce;
|
||||
private array $cspWhitelist;
|
||||
private string $domain;
|
||||
protected bool $searchable;
|
||||
|
||||
public function __construct(Router $router) {
|
||||
$this->router = $router;
|
||||
$this->cspEnabled = false;
|
||||
$this->cspNonce = null;
|
||||
$this->databaseRequired = true;
|
||||
$this->cspWhitelist = [];
|
||||
$this->domain = $this->getSettings()->getBaseUrl();
|
||||
$this->logger = new Logger("Document", $this->getSQL());
|
||||
$this->searchable = false;
|
||||
}
|
||||
|
||||
public abstract function getTitle(): string;
|
||||
|
||||
public function isSearchable(): bool {
|
||||
return $this->searchable;
|
||||
}
|
||||
|
||||
public function getLogger(): Logger {
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
public function getUser(): ?User {
|
||||
return $this->getContext()->getUser();
|
||||
}
|
||||
|
||||
public function getContext(): Context {
|
||||
return $this->router->getContext();
|
||||
}
|
||||
|
||||
public function getSQL(): ?SQL {
|
||||
return $this->getContext()->getSQL();
|
||||
}
|
||||
|
||||
public function getSettings(): Settings {
|
||||
return $this->getContext()->getSettings();
|
||||
}
|
||||
|
||||
public function getCSPNonce(): ?string {
|
||||
return $this->cspNonce;
|
||||
}
|
||||
|
||||
public function isCSPEnabled(): bool {
|
||||
return $this->cspEnabled;
|
||||
}
|
||||
|
||||
public function enableCSP() {
|
||||
$this->cspEnabled = true;
|
||||
$this->cspNonce = generateRandomString(16, "base62");
|
||||
}
|
||||
|
||||
public function getRouter(): Router {
|
||||
return $this->router;
|
||||
}
|
||||
|
||||
public function addCSPWhitelist(string $path) {
|
||||
$urlParts = parse_url($path);
|
||||
if (!$urlParts || !isset($urlParts["host"])) {
|
||||
$this->cspWhitelist[] = $this->domain . $path;
|
||||
} else {
|
||||
$this->cspWhitelist[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
public function loadLanguageModule(LanguageModule|string $module) {
|
||||
$language = $this->getContext()->getLanguage();
|
||||
$language->loadModule($module);
|
||||
}
|
||||
|
||||
public function sendHeaders() {
|
||||
if ($this->cspEnabled) {
|
||||
$cspWhiteList = implode(" ", $this->cspWhitelist);
|
||||
$csp = [
|
||||
"default-src $cspWhiteList 'self'",
|
||||
"object-src 'none'",
|
||||
"base-uri 'self'",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"img-src 'self' 'unsafe-inline' data: https:;",
|
||||
"script-src $cspWhiteList 'nonce-$this->cspNonce'"
|
||||
];
|
||||
if ($this->getSettings()->isRecaptchaEnabled()) {
|
||||
$csp[] = "frame-src https://www.google.com/ 'self'";
|
||||
}
|
||||
|
||||
$compiledCSP = implode("; ", $csp);
|
||||
header("Content-Security-Policy: $compiledCSP;");
|
||||
}
|
||||
}
|
||||
|
||||
public abstract function getCode(array $params = []);
|
||||
|
||||
public function load(array $params = []): string {
|
||||
|
||||
if ($this->databaseRequired) {
|
||||
$sql = $this->getSQL();
|
||||
if (is_null($sql)) {
|
||||
return "Database is not configured yet.";
|
||||
} else if (!$sql->isConnected()) {
|
||||
return "Database is not connected: " . $sql->getLastError();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$code = $this->getCode($params);
|
||||
$this->sendHeaders();
|
||||
return $code;
|
||||
}
|
||||
|
||||
public function doSearch(SearchQuery $query, DocumentRoute $route): array {
|
||||
$code = $this->getCode();
|
||||
$results = Searchable::searchHtml($code, $query);
|
||||
return array_map(function ($res) use ($route) {
|
||||
return new SearchResult($route->getUrl(), $this->getTitle(), $res["text"]);
|
||||
}, $results);
|
||||
}
|
||||
|
||||
public function createScript($type, $src, $content = ""): Script {
|
||||
$script = new Script($type, $src, $content);
|
||||
|
||||
if ($this->isCSPEnabled()) {
|
||||
$script->setNonce($this->getCSPNonce());
|
||||
}
|
||||
|
||||
return $script;
|
||||
}
|
||||
}
|
||||
26
Core/Elements/EmptyHead.class.php
Normal file
26
Core/Elements/EmptyHead.class.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Elements;
|
||||
|
||||
class EmptyHead extends Head {
|
||||
|
||||
public function __construct($document) {
|
||||
parent::__construct($document);
|
||||
}
|
||||
|
||||
protected function initSources() {
|
||||
}
|
||||
|
||||
protected function initMetas(): array {
|
||||
return array(
|
||||
);
|
||||
}
|
||||
|
||||
protected function initRawFields(): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function initTitle(): string {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
110
Core/Elements/Head.class.php
Normal file
110
Core/Elements/Head.class.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Elements;
|
||||
|
||||
abstract class Head extends View {
|
||||
|
||||
protected array $sources;
|
||||
protected string $title;
|
||||
protected array $metas;
|
||||
protected array $rawFields;
|
||||
protected array $keywords;
|
||||
protected string $description;
|
||||
protected string $baseUrl;
|
||||
|
||||
function __construct($document) {
|
||||
parent::__construct($document);
|
||||
$this->sources = array();
|
||||
$this->searchable = false;
|
||||
$this->metas = $this->initMetas();
|
||||
$this->rawFields = $this->initRawFields();
|
||||
$this->title = $this->initTitle();
|
||||
$this->initSources();
|
||||
$this->init();
|
||||
}
|
||||
|
||||
protected abstract function initSources();
|
||||
protected abstract function initMetas(): array;
|
||||
protected abstract function initRawFields(): array;
|
||||
protected abstract function initTitle(): string;
|
||||
|
||||
protected function init() {
|
||||
$this->keywords = array();
|
||||
$this->description = "";
|
||||
$this->baseUrl = "";
|
||||
}
|
||||
|
||||
public function setBaseUrl($baseUrl) { $this->baseUrl = $baseUrl; }
|
||||
public function setDescription($description) { $this->description = $description; }
|
||||
public function setKeywords($keywords) { $this->keywords = $keywords; }
|
||||
public function setTitle($title) { $this->title = $title; }
|
||||
public function getSources(): array { return $this->sources; }
|
||||
public function addScript($type, $url, $js = '') { $this->sources[] = new Script($type, $url, $js); }
|
||||
public function addRawField($rawField) { $this->rawFields[] = $rawField; }
|
||||
public function addMeta($aMeta) { $this->metas[] = $aMeta; }
|
||||
public function addLink($rel, $href, $type = "") { $this->sources[] = new Link($rel, $href, $type); }
|
||||
public function addKeywords($keywords) { $this->keywords = array_merge($this->keywords, $keywords); }
|
||||
public function getTitle(): string { return $this->title; }
|
||||
|
||||
public function addCSS($href, $type = Link::MIME_TEXT_CSS) { $this->sources[] = new Link(Link::STYLESHEET, $href, $type); }
|
||||
public function addStyle($style) { $this->sources[] = new Style($style); }
|
||||
public function addJS($url) { $this->sources[] = new Script(Script::MIME_TEXT_JAVASCRIPT, $url, ""); }
|
||||
public function addJSCode($code) { $this->sources[] = new Script(Script::MIME_TEXT_JAVASCRIPT, "", $code); }
|
||||
|
||||
public function loadFontawesome() {
|
||||
$this->addCSS(Link::FONTAWESOME);
|
||||
}
|
||||
|
||||
public function loadGoogleRecaptcha($siteKey) {
|
||||
$this->addJS("https://www.google.com/recaptcha/api.js?render=$siteKey");
|
||||
}
|
||||
|
||||
public function loadJQuery() {
|
||||
$this->addJS(Script::JQUERY);
|
||||
}
|
||||
|
||||
public function loadBootstrap() {
|
||||
$this->addCSS(Link::BOOTSTRAP);
|
||||
$this->addJS(Script::BOOTSTRAP);
|
||||
}
|
||||
|
||||
public function getCode(): string {
|
||||
$content = [];
|
||||
|
||||
// meta tags
|
||||
foreach($this->metas as $meta) {
|
||||
$content[] = html_tag_short("meta", $meta);
|
||||
}
|
||||
|
||||
// description
|
||||
if(!empty($this->description)) {
|
||||
$content[] = html_tag_short("meta", ["name" => "description", "content" => $this->description]);
|
||||
}
|
||||
|
||||
// keywords
|
||||
if(!empty($this->keywords)) {
|
||||
$keywords = implode(", ", $this->keywords);
|
||||
$content[] = html_tag_short("meta", ["name" => "keywords", "content" => $keywords]);
|
||||
}
|
||||
|
||||
// base tag
|
||||
if(!empty($this->baseUrl)) {
|
||||
$content[] = html_tag_short("base", ["href" => $this->baseUrl]);
|
||||
}
|
||||
|
||||
// title
|
||||
$content[] = html_tag("title", [], $this->title);
|
||||
|
||||
// src tags
|
||||
foreach($this->sources as $src) {
|
||||
$content[] = $src->getCode();
|
||||
}
|
||||
|
||||
//
|
||||
foreach ($this->rawFields as $raw) {
|
||||
$content[] = $raw;
|
||||
}
|
||||
|
||||
return html_tag("head", [], $content, false);
|
||||
}
|
||||
}
|
||||
71
Core/Elements/HtmlDocument.class.php
Normal file
71
Core/Elements/HtmlDocument.class.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Elements;
|
||||
|
||||
use Core\Objects\Router\Router;
|
||||
|
||||
class HtmlDocument extends Document {
|
||||
|
||||
protected ?Head $head;
|
||||
protected ?Body $body;
|
||||
private ?string $activeView;
|
||||
|
||||
public function __construct(Router $router, $headClass, $bodyClass, ?string $view = NULL) {
|
||||
parent::__construct($router);
|
||||
$this->head = $headClass ? new $headClass($this) : null;
|
||||
$this->body = $bodyClass ? new $bodyClass($this) : null;
|
||||
$this->activeView = $view;
|
||||
}
|
||||
|
||||
public function getHead(): ?Head { return $this->head; }
|
||||
public function getBody(): ?Body { return $this->body; }
|
||||
|
||||
public function getView() : ?View {
|
||||
|
||||
if ($this->activeView === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$view = parseClass($this->activeView);
|
||||
$file = getClassPath($view);
|
||||
if (!file_exists($file) || !is_subclass_of($view, View::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new $view($this);
|
||||
}
|
||||
|
||||
public function getRequestedView(): string {
|
||||
return $this->activeView;
|
||||
}
|
||||
|
||||
function getCode(array $params = []): string {
|
||||
// generate body first, so we can modify head
|
||||
$body = $this->body->getCode();
|
||||
|
||||
if ($this->isCSPEnabled()) {
|
||||
foreach ($this->head->getSources() as $element) {
|
||||
if ($element instanceof Script || $element instanceof Link) {
|
||||
$element->setNonce($this->getCSPNonce());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$head = $this->head->getCode();
|
||||
$lang = $this->getContext()->getLanguage();
|
||||
|
||||
$code = "<!DOCTYPE html>";
|
||||
$code .= html_tag("html", ["lang" => $lang->getShortCode()], $head . $body, false);
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
|
||||
public function getTitle(): string {
|
||||
if ($this->head !== null) {
|
||||
return $this->head->getTitle();
|
||||
}
|
||||
|
||||
return "Untitled Document";
|
||||
}
|
||||
}
|
||||
43
Core/Elements/Link.class.php
Normal file
43
Core/Elements/Link.class.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Elements;
|
||||
|
||||
class Link extends StaticView {
|
||||
|
||||
const STYLESHEET = "stylesheet";
|
||||
const MIME_TEXT_CSS = "text/css";
|
||||
|
||||
const FONTAWESOME = "/css/fontawesome.min.css";
|
||||
const BOOTSTRAP = "/css/bootstrap.min.css";
|
||||
const CORE = "/css/style.css";
|
||||
const ACCOUNT = "/css/account.css";
|
||||
|
||||
private string $type;
|
||||
private string $rel;
|
||||
private string $href;
|
||||
private ?string $nonce;
|
||||
|
||||
function __construct($rel, $href, $type = "") {
|
||||
$this->href = $href;
|
||||
$this->type = $type;
|
||||
$this->rel = $rel;
|
||||
$this->nonce = null;
|
||||
}
|
||||
|
||||
function getCode(): string {
|
||||
$attributes = ["rel" => $this->rel, "href" => $this->href];
|
||||
|
||||
if (!empty($this->type)) {
|
||||
$attributes["type"] = $this->type;
|
||||
}
|
||||
if (!empty($this->nonce)) {
|
||||
$attributes["nonce"] = $this->nonce;
|
||||
}
|
||||
|
||||
return html_tag_short("link", $attributes);
|
||||
}
|
||||
|
||||
public function setNonce(string $nonce) {
|
||||
$this->nonce = $nonce;
|
||||
}
|
||||
}
|
||||
45
Core/Elements/Script.class.php
Normal file
45
Core/Elements/Script.class.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Elements;
|
||||
|
||||
class Script extends StaticView {
|
||||
|
||||
const MIME_TEXT_JAVASCRIPT = "text/javascript";
|
||||
|
||||
const CORE = "/js/script.js";
|
||||
const JQUERY = "/js/jquery.min.js";
|
||||
const INSTALL = "/js/install.js";
|
||||
const BOOTSTRAP = "/js/bootstrap.bundle.min.js";
|
||||
const ACCOUNT = "/js/account.js";
|
||||
const FONTAWESOME = "/js/fontawesome-all.min.js";
|
||||
|
||||
private string $type;
|
||||
private string $content;
|
||||
private string $src;
|
||||
private ?string $nonce;
|
||||
|
||||
function __construct($type, $src, $content = "") {
|
||||
$this->src = $src;
|
||||
$this->type = $type;
|
||||
$this->content = $content;
|
||||
$this->nonce = null;
|
||||
}
|
||||
|
||||
function getCode(): string {
|
||||
$attributes = ["type" => $this->type];
|
||||
if (!empty($this->src)) {
|
||||
$attributes["src"] = $this->src;
|
||||
}
|
||||
|
||||
if (!empty($this->nonce)) {
|
||||
$attributes["nonce"] = $this->nonce;
|
||||
}
|
||||
|
||||
// TODO: do we need to escape the content here?
|
||||
return html_tag("script", $attributes, $this->content, false);
|
||||
}
|
||||
|
||||
public function setNonce(string $nonce) {
|
||||
$this->nonce = $nonce;
|
||||
}
|
||||
}
|
||||
17
Core/Elements/SimpleBody.class.php
Normal file
17
Core/Elements/SimpleBody.class.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Elements;
|
||||
|
||||
abstract class SimpleBody extends Body {
|
||||
|
||||
public function __construct($document) {
|
||||
parent::__construct($document);
|
||||
}
|
||||
|
||||
public function getCode(): string {
|
||||
$content = $this->getContent();
|
||||
return html_tag("body", [], $content, false);
|
||||
}
|
||||
|
||||
protected abstract function getContent(): string;
|
||||
}
|
||||
13
Core/Elements/StaticView.class.php
Normal file
13
Core/Elements/StaticView.class.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Elements;
|
||||
|
||||
abstract class StaticView {
|
||||
|
||||
public abstract function getCode();
|
||||
|
||||
public function __toString() {
|
||||
return $this->getCode();
|
||||
}
|
||||
|
||||
}
|
||||
17
Core/Elements/Style.class.php
Normal file
17
Core/Elements/Style.class.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Elements;
|
||||
|
||||
class Style extends StaticView {
|
||||
|
||||
private string $style;
|
||||
|
||||
function __construct($style) {
|
||||
$this->style = $style;
|
||||
}
|
||||
|
||||
function getCode(): string {
|
||||
// TODO: do we need to escape the content here?
|
||||
return html_tag("style", [], $this->style, false);
|
||||
}
|
||||
}
|
||||
124
Core/Elements/TemplateDocument.class.php
Normal file
124
Core/Elements/TemplateDocument.class.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Elements;
|
||||
|
||||
use Core\Objects\CustomTwigFunctions;
|
||||
use Core\Objects\Router\DocumentRoute;
|
||||
use Core\Objects\Router\Router;
|
||||
use Core\Objects\Search\Searchable;
|
||||
use Core\Objects\Search\SearchQuery;
|
||||
use Core\Objects\Search\SearchResult;
|
||||
use Twig\Environment;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
|
||||
class TemplateDocument extends Document {
|
||||
|
||||
const TEMPLATE_PATH = WEBROOT . '/Core/Templates';
|
||||
|
||||
private string $templateName;
|
||||
protected array $parameters;
|
||||
private Environment $twigEnvironment;
|
||||
private FilesystemLoader $twigLoader;
|
||||
protected string $title;
|
||||
|
||||
public function __construct(Router $router, string $templateName, array $params = []) {
|
||||
parent::__construct($router);
|
||||
$this->title = "Untitled Document";
|
||||
$this->templateName = $templateName;
|
||||
$this->parameters = $params;
|
||||
$this->twigLoader = new FilesystemLoader(self::TEMPLATE_PATH);
|
||||
$this->twigEnvironment = new Environment($this->twigLoader, [
|
||||
'cache' => WEBROOT . '/core/Cache/Templates/',
|
||||
'auto_reload' => true
|
||||
]);
|
||||
$this->twigEnvironment->addExtension(new CustomTwigFunctions());
|
||||
}
|
||||
|
||||
public function getTitle(): string {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
protected function getTemplateName(): string {
|
||||
return $this->templateName;
|
||||
}
|
||||
|
||||
protected function loadParameters() {
|
||||
|
||||
}
|
||||
|
||||
protected function setTemplate(string $file) {
|
||||
$this->templateName = $file;
|
||||
}
|
||||
|
||||
public function getCode(array $params = []): string {
|
||||
$this->loadParameters();
|
||||
return $this->renderTemplate($this->templateName, $this->parameters);
|
||||
}
|
||||
|
||||
public function renderTemplate(string $name, array $params = []): string {
|
||||
try {
|
||||
|
||||
$context = $this->getContext();
|
||||
$session = $context->getSession();
|
||||
$settings = $this->getSettings();
|
||||
$language = $context->getLanguage();
|
||||
|
||||
$urlParts = parse_url($this->getRouter()->getRequestedUri());
|
||||
|
||||
$params = array_replace_recursive([
|
||||
"user" => [
|
||||
"lang" => $language->getShortCode(),
|
||||
"loggedIn" => $session !== null,
|
||||
"session" => ($session ? [
|
||||
"csrfToken" => $session->getCsrfToken()
|
||||
] : null)
|
||||
],
|
||||
"site" => [
|
||||
"name" => $settings->getSiteName(),
|
||||
"url" => [
|
||||
"base" => $settings->getBaseUrl(),
|
||||
"path" => $urlParts["path"],
|
||||
"query" => $urlParts["query"] ?? "",
|
||||
"fragment" => $urlParts["fragment"] ?? ""
|
||||
],
|
||||
"lastModified" => date(L('Y-m-d H:i:s'), @filemtime(implode(DIRECTORY_SEPARATOR, [self::TEMPLATE_PATH, $name]))),
|
||||
"registrationEnabled" => $settings->isRegistrationAllowed(),
|
||||
"title" => $this->title,
|
||||
"recaptcha" => [
|
||||
"key" => $settings->isRecaptchaEnabled() ? $settings->getRecaptchaSiteKey() : null,
|
||||
"enabled" => $settings->isRecaptchaEnabled(),
|
||||
],
|
||||
"csp" => [
|
||||
"nonce" => $this->getCSPNonce(),
|
||||
"enabled" => $this->isCSPEnabled()
|
||||
]
|
||||
]
|
||||
], $params);
|
||||
return $this->twigEnvironment->render($name, $params);
|
||||
} catch (LoaderError | RuntimeError | SyntaxError $e) {
|
||||
return "<b>Error rendering twig template: " . htmlspecialchars($e->getMessage()) . "</b>";
|
||||
}
|
||||
}
|
||||
|
||||
protected function loadView(string $class): array {
|
||||
$view = new $class($this);
|
||||
$view->loadParameters($this->parameters);
|
||||
if ($view->getTitle()) {
|
||||
$this->title = $view->getTitle();
|
||||
}
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
public function doSearch(SearchQuery $query, DocumentRoute $route): array {
|
||||
$this->loadParameters();
|
||||
$viewParams = $this->parameters["view"] ?? [];
|
||||
$siteTitle = $this->parameters["site"]["title"] ?? $this->title;
|
||||
$results = Searchable::searchArray($viewParams, $query);
|
||||
return array_map(function ($res) use ($siteTitle, $route) {
|
||||
return new SearchResult($route->getUrl(), $siteTitle, $res["text"]);
|
||||
}, $results);
|
||||
}
|
||||
}
|
||||
39
Core/Elements/TemplateView.class.php
Normal file
39
Core/Elements/TemplateView.class.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Elements;
|
||||
|
||||
abstract class TemplateView extends View {
|
||||
|
||||
protected array $keywords = [];
|
||||
protected string $description = "";
|
||||
|
||||
public function __construct(TemplateDocument $document) {
|
||||
parent::__construct($document);
|
||||
$this->title = "";
|
||||
}
|
||||
|
||||
protected function getParameters(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function loadParameters(array &$parameters) {
|
||||
|
||||
$siteParameters = [
|
||||
"title" => $this->title,
|
||||
"description" => $this->description,
|
||||
"keywords" => $this->keywords
|
||||
];
|
||||
|
||||
foreach ($siteParameters as $key => $value) {
|
||||
if ($value) {
|
||||
$parameters["site"][$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$parameters["view"] = $this->getParameters();
|
||||
}
|
||||
|
||||
public function getCode(): string {
|
||||
return $this->getDocument()->getCode();
|
||||
}
|
||||
}
|
||||
146
Core/Elements/View.class.php
Normal file
146
Core/Elements/View.class.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Elements;
|
||||
|
||||
abstract class View extends StaticView {
|
||||
|
||||
private Document $document;
|
||||
private bool $loadView;
|
||||
protected string $title;
|
||||
protected array $langModules;
|
||||
|
||||
public function __construct(Document $document, bool $loadView = true) {
|
||||
$this->document = $document;
|
||||
$this->title = "Untitled View";
|
||||
$this->langModules = array();
|
||||
$this->loadView = $loadView;
|
||||
}
|
||||
|
||||
public function getTitle(): string { return $this->title; }
|
||||
public function getDocument(): Document { return $this->document; }
|
||||
|
||||
public function getSiteName(): string {
|
||||
return $this->getDocument()->getSettings()->getSiteName();
|
||||
}
|
||||
|
||||
protected function load(string $viewClass) : string {
|
||||
try {
|
||||
$reflectionClass = new \ReflectionClass($viewClass);
|
||||
if ($reflectionClass->isSubclassOf(View::class) && $reflectionClass->isInstantiable()) {
|
||||
$view = $reflectionClass->newInstanceArgs(array($this->getDocument()));
|
||||
$view->loadView();
|
||||
return $view;
|
||||
}
|
||||
} catch(\ReflectionException $e) {
|
||||
$this->document->getLogger()->error("Error loading view: '$viewClass': " . $e->getMessage());
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private function loadLanguageModules() {
|
||||
$lang = $this->document->getContext()->getLanguage();
|
||||
foreach ($this->langModules as $langModule) {
|
||||
$lang->loadModule($langModule);
|
||||
}
|
||||
}
|
||||
|
||||
// Virtual Methods
|
||||
public function loadView() { }
|
||||
|
||||
public function getCode(): string {
|
||||
|
||||
// Load translations
|
||||
$this->loadLanguageModules();
|
||||
|
||||
// Load metadata + head (title, scripts, includes, ...)
|
||||
if($this->loadView) {
|
||||
$this->loadView();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// UI Functions
|
||||
private function createList(array $items, string $tag, array $classes = []): string {
|
||||
|
||||
$attributes = [];
|
||||
if (!empty($classes)) {
|
||||
$attributes["class"] = implode(" ", $classes);
|
||||
}
|
||||
|
||||
$content = array_map(function ($item) { return html_tag("li", [], $item, false); }, $items);
|
||||
return html_tag_ex($tag, $attributes, $content, false);
|
||||
}
|
||||
|
||||
public function createOrderedList(array $items=[], array $classes=[]): string {
|
||||
return $this->createList($items, "ol", $classes);
|
||||
}
|
||||
|
||||
public function createUnorderedList(array $items=[], array $classes=[]): string {
|
||||
return $this->createList($items, "ul", $classes);
|
||||
}
|
||||
|
||||
protected function createLink(string $link, $title=null, array $classes=[], bool $escapeTitle=true): string {
|
||||
$attrs = ["href" => $link];
|
||||
if (!empty($classes)) {
|
||||
$attrs["class"] = implode(" ", $classes);
|
||||
}
|
||||
|
||||
return html_tag("a", $attrs, $title ?? $link, $escapeTitle);
|
||||
}
|
||||
|
||||
protected function createExternalLink(string $link, $title=null, bool $escapeTitle=true): string {
|
||||
$attrs = ["href" => $link, "target" => "_blank", "rel" => "noopener noreferrer", "class" => "external"];
|
||||
return html_tag("a", $attrs, $title ?? $link, $escapeTitle);
|
||||
}
|
||||
|
||||
protected function createIcon($icon, $type="fas", $classes = []): string {
|
||||
$classes = array_merge($classes, [$type, "fa-$icon"]);
|
||||
if ($icon === "spinner" || $icon === "circle-notch") {
|
||||
$classes[] = "fa-spin";
|
||||
}
|
||||
|
||||
return html_tag("i", ["class" => implode(" ", $classes)]);
|
||||
}
|
||||
|
||||
protected function createErrorText($text, $id="", $hidden=false): string {
|
||||
return $this->createStatusText("danger", $text, $id, $hidden);
|
||||
}
|
||||
|
||||
protected function createWarningText($text, $id="", $hidden=false): string {
|
||||
return $this->createStatusText("warning", $text, $id, $hidden);
|
||||
}
|
||||
|
||||
protected function createSuccessText($text, $id="", $hidden=false): string {
|
||||
return $this->createStatusText("success", $text, $id, $hidden);
|
||||
}
|
||||
|
||||
protected function createSecondaryText($text, $id="", $hidden=false): string {
|
||||
return $this->createStatusText("secondary", $text, $id, $hidden);
|
||||
}
|
||||
|
||||
protected function createInfoText($text, $id="", $hidden=false): string {
|
||||
return $this->createStatusText("info", $text, $id, $hidden);
|
||||
}
|
||||
|
||||
protected function createStatusText(string $type, $text, string $id="", bool $hidden=false, array $classes=[]): string {
|
||||
$classes[] = "alert";
|
||||
$classes[] = "alert-$type";
|
||||
|
||||
if ($hidden) {
|
||||
$classes[] = "hidden";
|
||||
}
|
||||
|
||||
$attributes = [
|
||||
"class" => implode(" ", $classes),
|
||||
"role" => "alert"
|
||||
];
|
||||
|
||||
if (!empty($id)) {
|
||||
$attributes["id"] = $id;
|
||||
}
|
||||
|
||||
return html_tag("div", $attributes, $text, false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user