summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Lange <code@nerdmind.de>2017-02-24 21:27:59 +0100
committerThomas Lange <code@nerdmind.de>2017-02-24 21:27:59 +0100
commit52b077a48c743ba4d08ac00520a0bf1ef6deef5f (patch)
treeb4205c194167e0e03e273957cdd0aab3be9fdf01
downloadblog-1.0.tar.gz
blog-1.0.tar.xz
blog-1.0.zip
Initial commit.v1.0
-rw-r--r--.gitignore2
-rw-r--r--.htaccess42
-rw-r--r--admin/auth.php86
-rw-r--r--admin/database.php49
-rw-r--r--admin/index.php88
-rw-r--r--admin/page/delete.php72
-rw-r--r--admin/page/index.php76
-rw-r--r--admin/page/insert.php86
-rw-r--r--admin/page/update.php96
-rw-r--r--admin/post/delete.php72
-rw-r--r--admin/post/index.php76
-rw-r--r--admin/post/insert.php86
-rw-r--r--admin/post/update.php96
-rw-r--r--admin/user/delete.php72
-rw-r--r--admin/user/index.php72
-rw-r--r--admin/user/insert.php78
-rw-r--r--admin/user/update.php89
-rw-r--r--core/application.php136
-rw-r--r--core/configuration-example.php103
-rw-r--r--core/functions.php322
-rw-r--r--core/language/de.php194
-rw-r--r--core/language/en.php194
-rw-r--r--core/namespace/Application.php148
-rw-r--r--core/namespace/Attribute.php74
-rw-r--r--core/namespace/AttributeInterface.php7
-rw-r--r--core/namespace/Database.php7
-rw-r--r--core/namespace/ExceptionHandler.php8
-rw-r--r--core/namespace/Factory.php19
-rw-r--r--core/namespace/FactoryInterface.php5
-rw-r--r--core/namespace/HTTP.php234
-rw-r--r--core/namespace/Item.php150
-rw-r--r--core/namespace/ItemFactory.php12
-rw-r--r--core/namespace/ItemInterface.php5
-rw-r--r--core/namespace/Language.php48
-rw-r--r--core/namespace/Page/Attribute.php22
-rw-r--r--core/namespace/Page/Exception.php5
-rw-r--r--core/namespace/Page/Factory.php9
-rw-r--r--core/namespace/Page/Item.php29
-rw-r--r--core/namespace/Parsedown.php1548
-rw-r--r--core/namespace/Post/Attribute.php22
-rw-r--r--core/namespace/Post/Exception.php5
-rw-r--r--core/namespace/Post/Factory.php9
-rw-r--r--core/namespace/Post/Item.php50
-rw-r--r--core/namespace/Template/Exception.php5
-rw-r--r--core/namespace/Template/Factory.php18
-rw-r--r--core/namespace/Template/Template.php72
-rw-r--r--core/namespace/User/Attribute.php24
-rw-r--r--core/namespace/User/Exception.php5
-rw-r--r--core/namespace/User/Factory.php13
-rw-r--r--core/namespace/User/Item.php36
-rw-r--r--database.sql66
-rw-r--r--index.php54
-rw-r--r--readme.md20
-rw-r--r--rsrc/image/content/computer-guy-public-domain.svg242
-rw-r--r--system/403.php29
-rw-r--r--system/404.php29
-rw-r--r--system/feed/main.php76
-rw-r--r--system/page/list.php64
-rw-r--r--system/page/main.php89
-rw-r--r--system/post/list.php64
-rw-r--r--system/post/main.php89
-rw-r--r--system/search/main.php89
-rw-r--r--system/user/list.php60
-rw-r--r--system/user/main.php97
-rw-r--r--template/admin/html/403.php2
-rw-r--r--template/admin/html/404.php2
-rw-r--r--template/admin/html/auth.php30
-rw-r--r--template/admin/html/database.php26
-rw-r--r--template/admin/html/home.php34
-rw-r--r--template/admin/html/main.php52
-rw-r--r--template/admin/html/page/delete.php4
-rw-r--r--template/admin/html/page/form.php91
-rw-r--r--template/admin/html/page/index.php10
-rw-r--r--template/admin/html/page/insert.php4
-rw-r--r--template/admin/html/page/item.php16
-rw-r--r--template/admin/html/page/update.php4
-rw-r--r--template/admin/html/pagination.php45
-rw-r--r--template/admin/html/post/delete.php4
-rw-r--r--template/admin/html/post/form.php91
-rw-r--r--template/admin/html/post/index.php10
-rw-r--r--template/admin/html/post/insert.php4
-rw-r--r--template/admin/html/post/item.php16
-rw-r--r--template/admin/html/post/update.php4
-rw-r--r--template/admin/html/user/delete.php6
-rw-r--r--template/admin/html/user/form.php97
-rw-r--r--template/admin/html/user/index.php10
-rw-r--r--template/admin/html/user/insert.php4
-rw-r--r--template/admin/html/user/item.php15
-rw-r--r--template/admin/html/user/update.php4
-rw-r--r--template/admin/lang/de.php118
-rw-r--r--template/admin/lang/en.php118
-rw-r--r--template/admin/rsrc/background.pngbin0 -> 1245 bytes
-rw-r--r--template/admin/rsrc/font/font-awesome.woff2bin0 -> 77160 bytes
-rw-r--r--template/admin/rsrc/font/kadwa-n-400.woff2bin0 -> 18108 bytes
-rw-r--r--template/admin/rsrc/font/ruda-n-400.woff2bin0 -> 9120 bytes
-rw-r--r--template/admin/rsrc/font/ruda-n-700.woff2bin0 -> 9116 bytes
-rw-r--r--template/admin/rsrc/icon-public-domain.svg1
-rw-r--r--template/admin/rsrc/main.css275
-rw-r--r--template/admin/rsrc/main.js94
-rw-r--r--template/standard/html/403.php11
-rw-r--r--template/standard/html/404.php11
-rw-r--r--template/standard/html/feed/item_page.php25
-rw-r--r--template/standard/html/feed/item_post.php26
-rw-r--r--template/standard/html/feed/main.php50
-rw-r--r--template/standard/html/home.php19
-rw-r--r--template/standard/html/main.php92
-rw-r--r--template/standard/html/page/item.php20
-rw-r--r--template/standard/html/page/list.php19
-rw-r--r--template/standard/html/page/main.php46
-rw-r--r--template/standard/html/pagination.php54
-rw-r--r--template/standard/html/post/item.php20
-rw-r--r--template/standard/html/post/list.php19
-rw-r--r--template/standard/html/post/main.php46
-rw-r--r--template/standard/html/search/main.php34
-rw-r--r--template/standard/html/search/result.php36
-rw-r--r--template/standard/html/user/item.php20
-rw-r--r--template/standard/html/user/list.php19
-rw-r--r--template/standard/html/user/main.php43
-rw-r--r--template/standard/lang/de.php51
-rw-r--r--template/standard/lang/en.php51
-rw-r--r--template/standard/rsrc/font-awesome.min.css4
-rw-r--r--template/standard/rsrc/font/font-awesome.woff2bin0 -> 77160 bytes
-rw-r--r--template/standard/rsrc/font/ruda-n-400.woff2bin0 -> 9120 bytes
-rw-r--r--template/standard/rsrc/font/ruda-n-700.woff2bin0 -> 9116 bytes
-rw-r--r--template/standard/rsrc/logo.pngbin0 -> 2595 bytes
-rw-r--r--template/standard/rsrc/main.css202
126 files changed, 7953 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c0cee83
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.idea/
+core/configuration.php \ No newline at end of file
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..c354b0b
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,42 @@
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Apache configuration rules [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# This file contains rewrite rules for Apache. It's recommended to include the #
+# rules directly into your Apache configuration for a better performance. Also #
+# disable the AllowOverride directive within your Apache virtualhost (if it is #
+# not already globally disabled for performance and security reasons both). #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+
+#===============================================================================
+# Enable RewriteEngine and define RewriteBase
+#===============================================================================
+RewriteEngine On
+RewriteBase /
+
+#===============================================================================
+# ErrorDocument handler
+#===============================================================================
+ErrorDocument 403 /system/403.php
+ErrorDocument 404 /system/404.php
+
+#===============================================================================
+# Forbidden directories
+#===============================================================================
+RewriteRule ^core|template/(.*)/(html|lang)/ - [F]
+
+#===============================================================================
+# Main rules
+#===============================================================================
+RewriteRule ^(page|post|user)/([^/]+)/$ system/$1/main.php?param=$2 [L]
+RewriteRule ^(page|post|user)/$ system/$1/list.php [L]
+RewriteRule ^feed/(page|post)/$ system/feed/main.php?item=$1 [L]
+RewriteRule ^(feed|search)/$ system/$1/main.php [L]
+
+#===============================================================================
+# Trailing slashes
+#===============================================================================
+RewriteRule ^(page|post|user)/([^/]+)$ $1/$2/ [R,L]
+RewriteRule ^(page|post|user|feed|search)$ $1/ [R,L]
+RewriteRule ^feed/(post|page)$ feed/$1/ [R,L] \ No newline at end of file
diff --git a/admin/auth.php b/admin/auth.php
new file mode 100644
index 0000000..dbdd3ef
--- /dev/null
+++ b/admin/auth.php
@@ -0,0 +1,86 @@
+<?php
+#===============================================================================
+# DEFINE: Administration
+#===============================================================================
+define('ADMINISTRATION', TRUE);
+
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../core/application.php';
+
+#===============================================================================
+# IF: Already authenticated
+#===============================================================================
+if(Application::isAuthenticated()) {
+ #===============================================================================
+ # IF: Logout action
+ #===============================================================================
+ if(HTTP::issetGET(['token' => Application::getSecurityToken(), ['action' => 'logout']])) {
+ session_destroy();
+ HTTP::redirect(Application::getAdminURL('auth.php'));
+ }
+
+ HTTP::redirect(Application::getAdminURL());
+}
+
+#===============================================================================
+# ELSE: Not authenticated
+#===============================================================================
+else {
+ #===============================================================================
+ # IF: Login action
+ #===============================================================================
+ if(HTTP::issetPOST(['token' => Application::getSecurityToken()], 'username', 'password')) {
+ try {
+ $User = User\Factory::buildByUsername(HTTP::POST('username'));
+
+ if($User->comparePassword(HTTP::POST('password'))) {
+ $_SESSION['auth'] = $User->getID();
+ HTTP::redirect(Application::getAdminURL());
+ }
+
+ else {
+ $messages[] = $Language->text('authentication_failure');
+ }
+ } catch(User\Exception $Exception){
+ $fake_hash = '$2y$10$xpnwDU2HumOgGQhVpMOP9uataEF82YXizniFhSUhYjUiXF8aoDk0C';
+ $fake_pass = HTTP::POST('password');
+
+ password_verify($fake_pass, $fake_hash);
+
+ $messages[] = $Language->text('authentication_failure');
+ }
+ }
+}
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ $AuthTemplate = Template\Factory::build('auth');
+ $AuthTemplate->set('FORM', [
+ 'INFO' => [
+ 'LIST' => $messages ?? [],
+ ],
+ 'DATA' => [
+ 'USERNAME' => HTTP::POST('username'),
+ 'PASSWORD' => HTTP::POST('password'),
+ ],
+ 'TOKEN' => Application::getSecurityToken()
+ ]);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('NAME', 'Authentication');
+ $MainTemplate->set('HTML', $AuthTemplate);
+ echo $MainTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?>
+
diff --git a/admin/database.php b/admin/database.php
new file mode 100644
index 0000000..47ffd29
--- /dev/null
+++ b/admin/database.php
@@ -0,0 +1,49 @@
+<?php
+#===============================================================================
+# DEFINE: Administration
+#===============================================================================
+define('ADMINISTRATION', TRUE);
+define('AUTHENTICATION', TRUE);
+
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../core/application.php';
+
+#===============================================================================
+# Execute database command(s)
+#===============================================================================
+if(HTTP::issetPOST(['token' => Application::getSecurityToken()], 'command')) {
+ try {
+ $Statement = $Database->query(HTTP::POST('command'));
+ $result = print_r($Statement->fetchAll(), TRUE);
+ } catch(PDOException $Exception) {
+ $messages[] = $Exception->getMessage();
+ }
+}
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ $DatabaseTemplate = Template\Factory::build('database');
+ $DatabaseTemplate->set('FORM', [
+ 'INFO' => $messages ?? [],
+ 'TOKEN' => Application::getSecurityToken(),
+ 'RESULT' => $result ?? NULL,
+ 'COMMAND' => HTTP::POST('command'),
+ ]);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('NAME', 'SQL');
+ $MainTemplate->set('HTML', $DatabaseTemplate);
+ echo $MainTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?> \ No newline at end of file
diff --git a/admin/index.php b/admin/index.php
new file mode 100644
index 0000000..c2ef0e0
--- /dev/null
+++ b/admin/index.php
@@ -0,0 +1,88 @@
+<?php
+#===============================================================================
+# DEFINE: Administration
+#===============================================================================
+define('ADMINISTRATION', TRUE);
+define('AUTHENTICATION', TRUE);
+
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../core/application.php';
+
+#===============================================================================
+# TRY: PDOException
+#===============================================================================
+try {
+ $LastPageStatement = $Database->query(sprintf('SELECT id FROM %s ORDER BY time_insert DESC LIMIT 1', Page\Attribute::TABLE));
+ $LastPostStatement = $Database->query(sprintf('SELECT id FROM %s ORDER BY time_insert DESC LIMIT 1', Post\Attribute::TABLE));
+ $LastUserStatement = $Database->query(sprintf('SELECT id FROM %s ORDER BY time_insert DESC LIMIT 1', User\Attribute::TABLE));
+
+ $PageCountStatement = $Database->query(sprintf('SELECT COUNT(*) FROM %s', Page\Attribute::TABLE));
+ $PostCountStatement = $Database->query(sprintf('SELECT COUNT(*) FROM %s', Post\Attribute::TABLE));
+ $UserCountStatement = $Database->query(sprintf('SELECT COUNT(*) FROM %s', User\Attribute::TABLE));
+}
+
+#===============================================================================
+# CATCH: PDOException
+#===============================================================================
+catch(PDOException $Exception) {
+ exit($Exception->getMessage());
+}
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ try {
+ $LastPage = Page\Factory::build($LastPageStatement->fetchColumn());
+ $LastPageUser = User\Factory::build($LastPage->attr('user'));
+
+ $PageItemTemplate = generatePageItemTemplate($LastPage, $LastPageUser);
+ }
+
+ catch(Page\Exception $Exception){}
+ catch(User\Exception $Exception){}
+
+ try {
+ $LastPost = Post\Factory::build($LastPostStatement->fetchColumn());
+ $LastPostUser = User\Factory::build($LastPost->attr('user'));
+
+ $PostItemTemplate = generatePostItemTemplate($LastPost, $LastPostUser);
+ }
+
+ catch(Post\Exception $Exception){}
+ catch(User\Exception $Exception){}
+
+ try {
+ $LastUser = User\Factory::build($LastUserStatement->fetchColumn());
+ $UserItemTemplate = generateUserItemTemplate($LastUser);
+ } catch(User\Exception $Exception){}
+
+ $HomeTemplate = Template\Factory::build('home');
+ $HomeTemplate->set('LAST', [
+ 'PAGE' => $PageItemTemplate ?? FALSE,
+ 'POST' => $PostItemTemplate ?? FALSE,
+ 'USER' => $UserItemTemplate ?? FALSE,
+
+ ]);
+
+ $HomeTemplate->set('COUNT', [
+ 'PAGE' => $PageCountStatement->fetchColumn(),
+ 'POST' => $PostCountStatement->fetchColumn(),
+ 'USER' => $UserCountStatement->fetchColumn(),
+ ]);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('NAME', 'Dashboard');
+ $MainTemplate->set('HTML', $HomeTemplate);
+ echo $MainTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?> \ No newline at end of file
diff --git a/admin/page/delete.php b/admin/page/delete.php
new file mode 100644
index 0000000..163bbdb
--- /dev/null
+++ b/admin/page/delete.php
@@ -0,0 +1,72 @@
+<?php
+#===============================================================================
+# DEFINE: Administration
+#===============================================================================
+define('ADMINISTRATION', TRUE);
+define('AUTHENTICATION', TRUE);
+
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+#===============================================================================
+# TRY: Page\Exception
+#===============================================================================
+try {
+ $Page = Page\Factory::build(HTTP::GET('id'));
+ $Attribute = $Page->getAttribute();
+
+ if(HTTP::issetPOST(['token' => Application::getSecurityToken()], 'delete')) {
+ try {
+ if($Attribute->databaseDELETE($Database)) {
+ HTTP::redirect(Application::getAdminURL('page/'));
+ }
+ } catch(PDOException $Exception) {
+ $messages[] = $Exception->getMessage();
+ }
+ }
+
+ #===============================================================================
+ # TRY: Template\Exception
+ #===============================================================================
+ try {
+ $FormTemplate = Template\Factory::build('page/form');
+ $FormTemplate->set('HTML', $Page->getHTML());
+ $FormTemplate->set('FORM', [
+ 'TYPE' => 'DELETE',
+ 'INFO' => $messages ?? [],
+ 'DATA' => [
+ 'ID' => $Attribute->get('id'),
+ 'BODY' => $Attribute->get('body'),
+ 'TIME_INSERT' => $Attribute->get('time_insert'),
+ 'TIME_UPDATE' => $Attribute->get('time_update'),
+ ],
+ 'TOKEN' => Application::getSecurityToken()
+ ]);
+
+ $DeleteTemplate = Template\Factory::build('page/delete');
+ $DeleteTemplate->set('HTML', $FormTemplate);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('NAME', $Language->text('title_page_delete'));
+ $MainTemplate->set('HTML', $DeleteTemplate);
+ echo $MainTemplate;
+ }
+
+ #===============================================================================
+ # CATCH: Template\Exception
+ #===============================================================================
+ catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+ }
+}
+
+#===============================================================================
+# CATCH: Page\Exception
+#===============================================================================
+catch(Page\Exception $Exception) {
+ Application::exit(404);
+}
+?>
+
diff --git a/admin/page/index.php b/admin/page/index.php
new file mode 100644
index 0000000..56233a9
--- /dev/null
+++ b/admin/page/index.php
@@ -0,0 +1,76 @@
+<?php
+#===============================================================================
+# DEFINE: Administration
+#===============================================================================
+define('ADMINISTRATION', TRUE);
+define('AUTHENTICATION', TRUE);
+
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+$site_size = Application::get('PAGE.LIST_SIZE');
+$site_sort = Application::get('PAGE.LIST_SORT');
+
+$lastSite = ceil($Database->query(sprintf('SELECT COUNT(id) FROM %s', Page\Attribute::TABLE))->fetchColumn() / $site_size);
+
+$currentSite = HTTP::GET('site') ?? 1;
+$currentSite = abs(intval($currentSite));
+
+if($currentSite < 1 OR ($currentSite > $lastSite AND $lastSite > 0)) {
+ Application::exit(404);
+}
+
+#===============================================================================
+# Fetch page IDs from database
+#===============================================================================
+$execSQL = "SELECT id FROM %s ORDER BY {$site_sort} LIMIT ".(($currentSite-1) * $site_size).", {$site_size}";
+$pageIDs = $Database->query(sprintf($execSQL, Page\Attribute::TABLE))->fetchAll($Database::FETCH_COLUMN);
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ foreach($pageIDs as $pageID) {
+ try {
+ $Page = Page\Factory::build($pageID);
+ $User = User\Factory::build($Page->attr('user'));
+
+ $ItemTemplate = generatePageItemTemplate($Page, $User);
+
+ $pages[] = $ItemTemplate;
+ }
+ catch(Page\Exception $Exception){}
+ catch(User\Exception $Exception){}
+ }
+
+ $PaginationTemplate = Template\Factory::build('pagination');
+ $PaginationTemplate->set('THIS', $currentSite);
+ $PaginationTemplate->set('LAST', $lastSite);
+ $PaginationTemplate->set('HREF', Application::getAdminURL('page/?site=%d'));
+
+ $ListTemplate = Template\Factory::build('page/index');
+ $ListTemplate->set('LIST', [
+ 'PAGES' => $pages ?? []
+ ]);
+
+ $ListTemplate->set('PAGINATION', [
+ 'THIS' => $currentSite,
+ 'LAST' => $lastSite,
+ 'HTML' => $PaginationTemplate
+ ]);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('NAME', $Language->text('title_page_overview', $currentSite));
+ $MainTemplate->set('HTML', $ListTemplate);
+ echo $MainTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?> \ No newline at end of file
diff --git a/admin/page/insert.php b/admin/page/insert.php
new file mode 100644
index 0000000..0f421f6
--- /dev/null
+++ b/admin/page/insert.php
@@ -0,0 +1,86 @@
+<?php
+#===============================================================================
+# DEFINE: Administration
+#===============================================================================
+define('ADMINISTRATION', TRUE);
+define('AUTHENTICATION', TRUE);
+
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+$Attribute = new Page\Attribute();
+
+if(HTTP::issetPOST('id', 'user', 'slug', 'name', 'body', 'time_insert', 'time_update', 'insert')) {
+ $Attribute->set('id', HTTP::POST('id') ? HTTP::POST('id') : FALSE);
+ $Attribute->set('user', HTTP::POST('user'));
+ $Attribute->set('slug', HTTP::POST('slug') ? HTTP::POST('slug') : makeSlugURL(HTTP::POST('name')));
+ $Attribute->set('name', HTTP::POST('name') ? HTTP::POST('name') : NULL);
+ $Attribute->set('body', HTTP::POST('body') ? HTTP::POST('body') : FALSE);
+ $Attribute->set('time_insert', HTTP::POST('time_insert') ? HTTP::POST('time_insert') : date('Y-m-d H:i:s'));
+ $Attribute->set('time_update', HTTP::POST('time_update') ? HTTP::POST('time_update') : date('Y-m-d H:i:s'));
+
+ if(HTTP::issetPOST(['token' => Application::getSecurityToken()])) {
+ try {
+ if($Attribute->databaseINSERT($Database)) {
+ HTTP::redirect(Application::getAdminURL('page/'));
+ }
+ } catch(PDOException $Exception) {
+ $messages[] = $Exception->getMessage();
+ }
+ }
+
+ else {
+ $messages[] = $Language->text('error_security_csrf');
+ }
+}
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ $userIDs = $Database->query(sprintf('SELECT id FROM %s ORDER BY fullname ASC', User\Attribute::TABLE));
+
+ foreach($userIDs->fetchAll(PDO::FETCH_COLUMN) as $userID) {
+ $User = User\Factory::build($userID);
+ $userAttributes[] = [
+ 'ID' => $User->attr('id'),
+ 'FULLNAME' => $User->attr('fullname'),
+ 'USERNAME' => $User->attr('username'),
+ ];
+ }
+
+ $FormTemplate = Template\Factory::build('page/form');
+ $FormTemplate->set('FORM', [
+ 'TYPE' => 'INSERT',
+ 'INFO' => $messages ?? [],
+ 'DATA' => [
+ 'ID' => $Attribute->get('id'),
+ 'USER' => $Attribute->get('user'),
+ 'SLUG' => $Attribute->get('slug'),
+ 'NAME' => $Attribute->get('name'),
+ 'BODY' => $Attribute->get('body'),
+ 'TIME_INSERT' => $Attribute->get('time_insert'),
+ 'TIME_UPDATE' => $Attribute->get('time_update'),
+ ],
+ 'USER_LIST' => $userAttributes ?? [],
+ 'TOKEN' => Application::getSecurityToken()
+ ]);
+
+ $InsertTemplate = Template\Factory::build('page/insert');
+ $InsertTemplate->set('HTML', $FormTemplate);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('NAME', $Language->text('title_page_insert'));
+ $MainTemplate->set('HTML', $InsertTemplate);
+ echo $MainTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?> \ No newline at end of file
diff --git a/admin/page/update.php b/admin/page/update.php
new file mode 100644
index 0000000..0489406
--- /dev/null
+++ b/admin/page/update.php
@@ -0,0 +1,96 @@
+<?php
+#===============================================================================
+# DEFINE: Administration
+#===============================================================================
+define('ADMINISTRATION', TRUE);
+define('AUTHENTICATION', TRUE);
+
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+#===============================================================================
+# TRY: Page\Exception
+#===============================================================================
+try {
+ $Page = Page\Factory::build(HTTP::GET('id'));
+ $Attribute = $Page->getAttribute();
+
+ if(HTTP::issetPOST('user', 'slug', 'name', 'body', 'time_insert', 'time_update', 'update')) {
+ $Attribute->set('user', HTTP::POST('user'));
+ $Attribute->set('slug', HTTP::POST('slug') ? HTTP::POST('slug') : makeSlugURL(HTTP::POST('name')));
+ $Attribute->set('name', HTTP::POST('name') ? HTTP::POST('name') : NULL);
+ $Attribute->set('body', HTTP::POST('body') ? HTTP::POST('body') : FALSE);
+ $Attribute->set('time_insert', HTTP::POST('time_insert') ? HTTP::POST('time_insert') : date('Y-m-d H:i:s'));
+ $Attribute->set('time_update', HTTP::POST('time_update') ? HTTP::POST('time_update') : date('Y-m-d H:i:s'));
+
+ if(HTTP::issetPOST(['token' => Application::getSecurityToken()])) {
+ try {
+ $Attribute->databaseUPDATE($Database);
+ } catch(PDOException $Exception) {
+ $messages[] = $Exception->getMessage();
+ }
+ }
+
+ else {
+ $messages[] = $Language->text('error_security_csrf');
+ }
+ }
+
+ #===============================================================================
+ # TRY: Template\Exception
+ #===============================================================================
+ try {
+ $userIDs = $Database->query(sprintf('SELECT id FROM %s ORDER BY fullname ASC', User\Attribute::TABLE));
+
+ foreach($userIDs->fetchAll(PDO::FETCH_COLUMN) as $userID) {
+ $User = User\Factory::build($userID);
+ $userAttributes[] = [
+ 'ID' => $User->attr('id'),
+ 'FULLNAME' => $User->attr('fullname'),
+ 'USERNAME' => $User->attr('username'),
+ ];
+ }
+
+ $FormTemplate = Template\Factory::build('page/form');
+ $FormTemplate->set('FORM', [
+ 'TYPE' => 'UPDATE',
+ 'INFO' => $messages ?? [],
+ 'DATA' => [
+ 'ID' => $Attribute->get('id'),
+ 'USER' => $Attribute->get('user'),
+ 'SLUG' => $Attribute->get('slug'),
+ 'NAME' => $Attribute->get('name'),
+ 'BODY' => $Attribute->get('body'),
+ 'TIME_INSERT' => $Attribute->get('time_insert'),
+ 'TIME_UPDATE' => $Attribute->get('time_update'),
+ ],
+ 'USER_LIST' => $userAttributes ?? [],
+ 'TOKEN' => Application::getSecurityToken()
+ ]);
+
+ $PageUpdateTemplate = Template\Factory::build('page/update');
+ $PageUpdateTemplate->set('HTML', $FormTemplate);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('NAME', $Language->text('title_page_update'));
+ $MainTemplate->set('HTML', $PageUpdateTemplate);
+ echo $MainTemplate;
+ }
+
+ #===============================================================================
+ # CATCH: Template\Exception
+ #===============================================================================
+ catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+ }
+}
+
+#===============================================================================
+# CATCH: Page\Exception
+#===============================================================================
+catch(Page\Exception $Exception) {
+ Application::exit(404);
+}
+?> \ No newline at end of file
diff --git a/admin/post/delete.php b/admin/post/delete.php
new file mode 100644
index 0000000..82e71da
--- /dev/null
+++ b/admin/post/delete.php
@@ -0,0 +1,72 @@
+<?php
+#===============================================================================
+# DEFINE: Administration
+#===============================================================================
+define('ADMINISTRATION', TRUE);
+define('AUTHENTICATION', TRUE);
+
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+#===============================================================================
+# TRY: Post\Exception
+#===============================================================================
+try {
+ $Post = Post\Factory::build(HTTP::GET('id'));
+ $Attribute = $Post->getAttribute();
+
+ if(HTTP::issetPOST(['token' => Application::getSecurityToken()], 'delete')) {
+ try {
+ if($Attribute->databaseDELETE($Database)) {
+ HTTP::redirect(Application::getAdminURL('post/'));
+ }
+ } catch(PDOException $Exception) {
+ $messages[] = $Exception->getMessage();
+ }
+ }
+
+ #===============================================================================
+ # TRY: Template\Exception
+ #===============================================================================
+ try {
+ $FormTemplate = Template\Factory::build('post/form');
+ $FormTemplate->set('HTML', $Post->getHTML());
+ $FormTemplate->set('FORM', [
+ 'TYPE' => 'DELETE',
+ 'INFO' => $messages ?? [],
+ 'DATA' => [
+ 'ID' => $Attribute->get('id'),
+ 'BODY' => $Attribute->get('body'),
+ 'TIME_INSERT' => $Attribute->get('time_insert'),
+ 'TIME_UPDATE' => $Attribute->get('time_update'),
+ ],
+ 'TOKEN' => Application::getSecurityToken()
+ ]);
+
+ $DeleteTemplate = Template\Factory::build('post/delete');
+ $DeleteTemplate->set('HTML', $FormTemplate);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('NAME', $Language->text('title_post_delete'));
+ $MainTemplate->set('HTML', $DeleteTemplate);
+ echo $MainTemplate;
+ }
+
+ #===============================================================================
+ # CATCH: Template\Exception
+ #===============================================================================
+ catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+ }
+}
+
+#===============================================================================
+# CATCH: Post\Exception
+#===============================================================================
+catch(Post\Exception $Exception) {
+ Application::exit(404);
+}
+?>
+
diff --git a/admin/post/index.php b/admin/post/index.php
new file mode 100644
index 0000000..bf7afdf
--- /dev/null
+++ b/admin/post/index.php
@@ -0,0 +1,76 @@
+<?php
+#===============================================================================
+# DEFINE: Administration
+#===============================================================================
+define('ADMINISTRATION', TRUE);
+define('AUTHENTICATION', TRUE);
+
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+$site_size = Application::get('POST.LIST_SIZE');
+$site_sort = Application::get('POST.LIST_SORT');
+
+$lastSite = ceil($Database->query(sprintf('SELECT COUNT(id) FROM %s', Post\Attribute::TABLE))->fetchColumn() / $site_size);
+
+$currentSite = HTTP::GET('site') ?? 1;
+$currentSite = abs(intval($currentSite));
+
+if($currentSite < 1 OR ($currentSite > $lastSite AND $lastSite > 0)) {
+ Application::exit(404);
+}
+
+#===============================================================================
+# Fetch post IDs from database
+#===============================================================================
+$execSQL = "SELECT id FROM %s ORDER BY {$site_sort} LIMIT ".(($currentSite-1) * $site_size).", {$site_size}";
+$postIDs = $Database->query(sprintf($execSQL, Post\Attribute::TABLE))->fetchAll($Database::FETCH_COLUMN);
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ foreach($postIDs as $postID) {
+ try {
+ $Post = Post\Factory::build($postID);
+ $User = User\Factory::build($Post->attr('user'));
+
+ $ItemTemplate = generatePostItemTemplate($Post, $User);
+
+ $posts[] = $ItemTemplate;
+ }
+ catch(Post\Exception $Exception){}
+ catch(User\Exception $Exception){}
+ }
+
+ $PaginationTemplate = Template\Factory::build('pagination');
+ $PaginationTemplate->set('THIS', $currentSite);
+ $PaginationTemplate->set('LAST', $lastSite);
+ $PaginationTemplate->set('HREF', Application::getAdminURL('post/?site=%d'));
+
+ $ListTemplate = Template\Factory::build('post/index');
+ $ListTemplate->set('LIST', [
+ 'POSTS' => $posts ?? []
+ ]);
+
+ $ListTemplate->set('PAGINATION', [
+ 'THIS' => $currentSite,
+ 'LAST' => $lastSite,
+ 'HTML' => $PaginationTemplate
+ ]);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('NAME', $Language->text('title_post_overview', $currentSite));
+ $MainTemplate->set('HTML', $ListTemplate);
+ echo $MainTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?> \ No newline at end of file
diff --git a/admin/post/insert.php b/admin/post/insert.php
new file mode 100644
index 0000000..ab6bb87
--- /dev/null
+++ b/admin/post/insert.php
@@ -0,0 +1,86 @@
+<?php
+#===============================================================================
+# DEFINE: Administration
+#===============================================================================
+define('ADMINISTRATION', TRUE);
+define('AUTHENTICATION', TRUE);
+
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+$Attribute = new Post\Attribute();
+
+if(HTTP::issetPOST('id', 'user', 'slug', 'name', 'body', 'time_insert', 'time_update', 'insert')) {
+ $Attribute->set('id', HTTP::POST('id') ? HTTP::POST('id') : FALSE);
+ $Attribute->set('user', HTTP::POST('user'));
+ $Attribute->set('slug', HTTP::POST('slug') ? HTTP::POST('slug') : makeSlugURL(HTTP::POST('name')));
+ $Attribute->set('name', HTTP::POST('name') ? HTTP::POST('name') : NULL);
+ $Attribute->set('body', HTTP::POST('body') ? HTTP::POST('body') : NULL);
+ $Attribute->set('time_insert', HTTP::POST('time_insert') ? HTTP::POST('time_insert') : date('Y-m-d H:i:s'));
+ $Attribute->set('time_update', HTTP::POST('time_update') ? HTTP::POST('time_update') : date('Y-m-d H:i:s'));
+
+ if(HTTP::issetPOST(['token' => Application::getSecurityToken()])) {
+ try {
+ if($Attribute->databaseINSERT($Database)) {
+ HTTP::redirect(Application::getAdminURL('post/'));
+ }
+ } catch(PDOException $Exception) {
+ $messages[] = $Exception->getMessage();
+ }
+ }
+
+ else {
+ $messages[] = $Language->text('error_security_csrf');
+ }
+}
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ $userIDs = $Database->query(sprintf('SELECT id FROM %s ORDER BY fullname ASC', User\Attribute::TABLE));
+
+ foreach($userIDs->fetchAll(PDO::FETCH_COLUMN) as $userID) {
+ $User = User\Factory::build($userID);
+ $userAttributes[] = [
+ 'ID' => $User->attr('id'),
+ 'FULLNAME' => $User->attr('fullname'),
+ 'USERNAME' => $User->attr('username'),
+ ];
+ }
+
+ $FormTemplate = Template\Factory::build('post/form');
+ $FormTemplate->set('FORM', [
+ 'TYPE' => 'INSERT',
+ 'INFO' => $messages ?? [],
+ 'DATA' => [
+ 'ID' => $Attribute->get('id'),
+ 'USER' => $Attribute->get('user'),
+ 'SLUG' => $Attribute->get('slug'),
+ 'NAME' => $Attribute->get('name'),
+ 'BODY' => $Attribute->get('body'),
+ 'TIME_INSERT' => $Attribute->get('time_insert'),
+ 'TIME_UPDATE' => $Attribute->get('time_update'),
+ ],
+ 'USER_LIST' => $userAttributes ?? [],
+ 'TOKEN' => Application::getSecurityToken()
+ ]);
+
+ $InsertTemplate = Template\Factory::build('post/insert');
+ $InsertTemplate->set('HTML', $FormTemplate);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('NAME', $Language->text('title_post_insert'));
+ $MainTemplate->set('HTML', $InsertTemplate);
+ echo $MainTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?> \ No newline at end of file
diff --git a/admin/post/update.php b/admin/post/update.php
new file mode 100644
index 0000000..875d947
--- /dev/null
+++ b/admin/post/update.php
@@ -0,0 +1,96 @@
+<?php
+#===============================================================================
+# DEFINE: Administration
+#===============================================================================
+define('ADMINISTRATION', TRUE);
+define('AUTHENTICATION', TRUE);
+
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+#===============================================================================
+# TRY: Post\Exception
+#===============================================================================
+try {
+ $Post = Post\Factory::build(HTTP::GET('id'));
+ $Attribute = $Post->getAttribute();
+
+ if(HTTP::issetPOST('user', 'slug', 'name', 'body', 'time_insert', 'time_update', 'update')) {
+ $Attribute->set('user', HTTP::POST('user'));
+ $Attribute->set('slug', HTTP::POST('slug') ? HTTP::POST('slug') : makeSlugURL(HTTP::POST('name')));
+ $Attribute->set('name', HTTP::POST('name') ? HTTP::POST('name') : NULL);
+ $Attribute->set('body', HTTP::POST('body') ? HTTP::POST('body') : FALSE);
+ $Attribute->set('time_insert', HTTP::POST('time_insert') ? HTTP::POST('time_insert') : date('Y-m-d H:i:s'));
+ $Attribute->set('time_update', HTTP::POST('time_update') ? HTTP::POST('time_update') : date('Y-m-d H:i:s'));
+
+ if(HTTP::issetPOST(['token' => Application::getSecurityToken()])) {
+ try {
+ $Attribute->databaseUPDATE($Database);
+ } catch(PDOException $Exception) {
+ $messages[] = $Exception->getMessage();
+ }
+ }
+
+ else {
+ $messages[] = $Language->text('error_security_csrf');
+ }
+ }
+
+ #===============================================================================
+ # TRY: Template\Exception
+ #===============================================================================
+ try {
+ $userIDs = $Database->query(sprintf('SELECT id FROM %s ORDER BY fullname ASC', User\Attribute::TABLE));
+
+ foreach($userIDs->fetchAll(PDO::FETCH_COLUMN) as $userID) {
+ $User = User\Factory::build($userID);
+ $userAttributes[] = [
+ 'ID' => $User->attr('id'),
+ 'FULLNAME' => $User->attr('fullname'),
+ 'USERNAME' => $User->attr('username'),
+ ];
+ }
+
+ $FormTemplate = Template\Factory::build('post/form');
+ $FormTemplate->set('FORM', [
+ 'TYPE' => 'UPDATE',
+ 'INFO' => $messages ?? [],
+ 'DATA' => [
+ 'ID' => $Attribute->get('id'),
+ 'USER' => $Attribute->get('user'),
+ 'SLUG' => $Attribute->get('slug'),
+ 'NAME' => $Attribute->get('name'),
+ 'BODY' => $Attribute->get('body'),
+ 'TIME_INSERT' => $Attribute->get('time_insert'),
+ 'TIME_UPDATE' => $Attribute->get('time_update'),
+ ],
+ 'USER_LIST' => $userAttributes ?? [],
+ 'TOKEN' => Application::getSecurityToken()
+ ]);
+
+ $PostUpdateTemplate = Template\Factory::build('post/update');
+ $PostUpdateTemplate->set('HTML', $FormTemplate);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('NAME', $Language->text('title_post_update'));
+ $MainTemplate->set('HTML', $PostUpdateTemplate);
+ echo $MainTemplate;
+ }
+
+ #===============================================================================
+ # CATCH: Template\Exception
+ #===============================================================================
+ catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+ }
+}
+
+#===============================================================================
+# CATCH: Post\Exception
+#===============================================================================
+catch(Post\Exception $Exception) {
+ Application::exit(404);
+}
+?> \ No newline at end of file
diff --git a/admin/user/delete.php b/admin/user/delete.php
new file mode 100644
index 0000000..ed8f925
--- /dev/null
+++ b/admin/user/delete.php
@@ -0,0 +1,72 @@
+<?php
+#===============================================================================
+# DEFINE: Administration
+#===============================================================================
+define('ADMINISTRATION', TRUE);
+define('AUTHENTICATION', TRUE);
+
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+#===============================================================================
+# TRY: User\Exception
+#===============================================================================
+try {
+ $User = User\Factory::build(HTTP::GET('id'));
+ $Attribute = $User->getAttribute();
+
+ if(HTTP::issetPOST(['token' => Application::getSecurityToken()], 'delete')) {
+ try {
+ if($Attribute->databaseDELETE($Database)) {
+ HTTP::redirect(Application::getAdminURL('user/'));
+ }
+ } catch(PDOException $Exception) {
+ $messages[] = $Exception->getMessage();
+ }
+ }
+
+ #===============================================================================
+ # TRY: Template\Exception
+ #===============================================================================
+ try {
+ $FormTemplate = Template\Factory::build('user/form');
+ $FormTemplate->set('HTML', $User->getHTML());
+ $FormTemplate->set('FORM', [
+ 'TYPE' => 'DELETE',
+ 'INFO' => $messages ?? [],
+ 'DATA' => [
+ 'ID' => $Attribute->get('id'),
+ 'BODY' => $Attribute->get('body'),
+ 'TIME_INSERT' => $Attribute->get('time_insert'),
+ 'TIME_UPDATE' => $Attribute->get('time_update'),
+ ],
+ 'TOKEN' => Application::getSecurityToken()
+ ]);
+
+ $DeleteTemplate = Template\Factory::build('user/delete');
+ $DeleteTemplate->set('HTML', $FormTemplate);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('NAME', $Language->text('title_user_delete'));
+ $MainTemplate->set('HTML', $DeleteTemplate);
+ echo $MainTemplate;
+ }
+
+ #===============================================================================
+ # CATCH: Template\Exception
+ #===============================================================================
+ catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+ }
+}
+
+#===============================================================================
+# CATCH: User\Exception
+#===============================================================================
+catch(User\Exception $Exception) {
+ Application::exit(404);
+}
+?>
+
diff --git a/admin/user/index.php b/admin/user/index.php
new file mode 100644
index 0000000..3dca93a
--- /dev/null
+++ b/admin/user/index.php
@@ -0,0 +1,72 @@
+<?php
+#===============================================================================
+# DEFINE: Administration
+#===============================================================================
+define('ADMINISTRATION', TRUE);
+define('AUTHENTICATION', TRUE);
+
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+$site_size = Application::get('POST.LIST_SIZE');
+$site_sort = Application::get('POST.LIST_SORT');
+
+$lastSite = ceil($Database->query(sprintf('SELECT COUNT(id) FROM %s', User\Attribute::TABLE))->fetchColumn() / $site_size);
+
+$currentSite = HTTP::GET('site') ?? 1;
+$currentSite = abs(intval($currentSite));
+
+if($currentSite < 1 OR ($currentSite > $lastSite AND $lastSite > 0)) {
+ Application::exit(404);
+}
+
+#===============================================================================
+# Fetch user IDs from database
+#===============================================================================
+$execSQL = "SELECT id FROM %s ORDER BY {$site_sort} LIMIT ".(($currentSite-1) * $site_size).", {$site_size}";
+$userIDs = $Database->query(sprintf($execSQL, User\Attribute::TABLE))->fetchAll($Database::FETCH_COLUMN);
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ foreach($userIDs as $userID) {
+ try {
+ $User = User\Factory::build($userID);
+ $ItemTemplate = generateUserItemTemplate($User);
+
+ $users[] = $ItemTemplate;
+ } catch(User\Exception $Exception){}
+ }
+
+ $PaginationTemplate = Template\Factory::build('pagination');
+ $PaginationTemplate->set('THIS', $currentSite);
+ $PaginationTemplate->set('LAST', $lastSite);
+ $PaginationTemplate->set('HREF', Application::getAdminURL('user/?site=%d'));
+
+ $ListTemplate = Template\Factory::build('user/index');
+ $ListTemplate->set('LIST', [
+ 'USERS' => $users ?? []
+ ]);
+
+ $ListTemplate->set('PAGINATION', [
+ 'THIS' => $currentSite,
+ 'LAST' => $lastSite,
+ 'HTML' => $PaginationTemplate
+ ]);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('NAME', $Language->text('title_user_overview', $currentSite));
+ $MainTemplate->set('HTML', $ListTemplate);
+ echo $MainTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?> \ No newline at end of file
diff --git a/admin/user/insert.php b/admin/user/insert.php
new file mode 100644
index 0000000..7c892d4
--- /dev/null
+++ b/admin/user/insert.php
@@ -0,0 +1,78 @@
+<?php
+#===============================================================================
+# DEFINE: Administration
+#===============================================================================
+define('ADMINISTRATION', TRUE);
+define('AUTHENTICATION', TRUE);
+
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+$Attribute = new User\Attribute();
+
+if(HTTP::issetPOST('id', 'slug', 'username', 'password', 'fullname', 'mailaddr', 'body', 'time_insert', 'time_update', 'insert')) {
+ $Attribute->set('id', HTTP::POST('id') ? HTTP::POST('id') : FALSE);
+ $Attribute->set('slug', HTTP::POST('slug') ? HTTP::POST('slug') : makeSlugURL(HTTP::POST('username')));
+ $Attribute->set('username', HTTP::POST('username') ? HTTP::POST('username') : NULL);
+ $Attribute->set('password', HTTP::POST('password') ? password_hash(HTTP::POST('password'), PASSWORD_BCRYPT, ['cost' => 10]) : FALSE);
+ $Attribute->set('fullname', HTTP::POST('fullname') ? HTTP::POST('fullname') : NULL);
+ $Attribute->set('mailaddr', HTTP::POST('mailaddr') ? HTTP::POST('mailaddr') : NULL);
+ $Attribute->set('body', HTTP::POST('body') ? HTTP::POST('body') : NULL);
+ $Attribute->set('time_insert', HTTP::POST('time_insert') ? HTTP::POST('time_insert') : date('Y-m-d H:i:s'));
+ $Attribute->set('time_update', HTTP::POST('time_update') ? HTTP::POST('time_update') : date('Y-m-d H:i:s'));
+
+ if(HTTP::issetPOST(['token' => Application::getSecurityToken()])) {
+ try {
+ if($Attribute->databaseINSERT($Database)) {
+ HTTP::redirect(Application::getAdminURL('user/'));
+ }
+ } catch(PDOException $Exception) {
+ $messages[] = $Exception->getMessage();
+ }
+ }
+
+ else {
+ $messages[] = $Language->text('error_security_csrf');
+ }
+}
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ $FormTemplate = Template\Factory::build('user/form');
+ $FormTemplate->set('FORM', [
+ 'TYPE' => 'INSERT',
+ 'INFO' => $messages ?? [],
+ 'DATA' => [
+ 'ID' => $Attribute->get('id'),
+ 'SLUG' => $Attribute->get('slug'),
+ 'USERNAME' => $Attribute->get('username'),
+ 'PASSWORD' => NULL,
+ 'FULLNAME' => $Attribute->get('fullname'),
+ 'MAILADDR' => $Attribute->get('mailaddr'),
+ 'BODY' => $Attribute->get('body'),
+ 'TIME_INSERT' => $Attribute->get('time_insert'),
+ 'TIME_UPDATE' => $Attribute->get('time_update'),
+ ],
+ 'TOKEN' => Application::getSecurityToken()
+ ]);
+
+ $InsertTemplate = Template\Factory::build('user/insert');
+ $InsertTemplate->set('HTML', $FormTemplate);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('NAME', $Language->text('title_user_insert'));
+ $MainTemplate->set('HTML', $InsertTemplate);
+ echo $MainTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?> \ No newline at end of file
diff --git a/admin/user/update.php b/admin/user/update.php
new file mode 100644
index 0000000..cab582e
--- /dev/null
+++ b/admin/user/update.php
@@ -0,0 +1,89 @@
+<?php
+#===============================================================================
+# DEFINE: Administration
+#===============================================================================
+define('ADMINISTRATION', TRUE);
+define('AUTHENTICATION', TRUE);
+
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+#===============================================================================
+# TRY: User\Exception
+#===============================================================================
+try {
+ $User = User\Factory::build(HTTP::GET('id'));
+ $Attribute = $User->getAttribute();
+
+ if(HTTP::issetPOST('slug', 'username', 'password', 'fullname', 'mailaddr', 'body', 'time_insert', 'time_update', 'update')) {
+ $Attribute->set('slug', HTTP::POST('slug') ? HTTP::POST('slug') : makeSlugURL(HTTP::POST('username')));
+ $Attribute->set('username', HTTP::POST('username') ? HTTP::POST('username') : NULL);
+ $Attribute->set('password', HTTP::POST('password') ? password_hash(HTTP::POST('password'), PASSWORD_BCRYPT, ['cost' => 10]) : FALSE);
+ $Attribute->set('fullname', HTTP::POST('fullname') ? HTTP::POST('fullname') : NULL);
+ $Attribute->set('mailaddr', HTTP::POST('mailaddr') ? HTTP::POST('mailaddr') : NULL);
+ $Attribute->set('body', HTTP::POST('body') ? HTTP::POST('body') : NULL);
+ $Attribute->set('time_insert', HTTP::POST('time_insert') ? HTTP::POST('time_insert') : date('Y-m-d H:i:s'));
+ $Attribute->set('time_update', HTTP::POST('time_update') ? HTTP::POST('time_update') : date('Y-m-d H:i:s'));
+
+ if(HTTP::issetPOST(['token' => Application::getSecurityToken()])) {
+ try {
+ if($Attribute->databaseUPDATE($Database)) {
+ }
+ } catch(PDOException $Exception) {
+ $messages[] = $Exception->getMessage();
+ }
+ }
+
+ else {
+ $messages[] = $Language->text('error_security_csrf');
+ }
+ }
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+ try {
+ $FormTemplate = Template\Factory::build('user/form');
+ $FormTemplate->set('FORM', [
+ 'TYPE' => 'UPDATE',
+ 'INFO' => $messages ?? [],
+ 'DATA' => [
+ 'ID' => $Attribute->get('id'),
+ 'SLUG' => $Attribute->get('slug'),
+ 'USERNAME' => $Attribute->get('username'),
+ 'PASSWORD' => NULL,
+ 'FULLNAME' => $Attribute->get('fullname'),
+ 'MAILADDR' => $Attribute->get('mailaddr'),
+ 'BODY' => $Attribute->get('body'),
+ 'TIME_INSERT' => $Attribute->get('time_insert'),
+ 'TIME_UPDATE' => $Attribute->get('time_update'),
+ ],
+ 'TOKEN' => Application::getSecurityToken()
+ ]);
+
+ $InsertTemplate = Template\Factory::build('user/update');
+ $InsertTemplate->set('HTML', $FormTemplate);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('NAME', $Language->text('title_user_update'));
+ $MainTemplate->set('HTML', $InsertTemplate);
+ echo $MainTemplate;
+ }
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+ catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+ }
+}
+
+#===============================================================================
+# CATCH: User\Exception
+#===============================================================================
+catch(User\Exception $Exception) {
+ Application::exit(404);
+}
+?> \ No newline at end of file
diff --git a/core/application.php b/core/application.php
new file mode 100644
index 0000000..58dd728
--- /dev/null
+++ b/core/application.php
@@ -0,0 +1,136 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Application initialization [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# This file is included by each file from the system or admin directory. #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+
+#===============================================================================
+# Document root
+#===============================================================================
+define('ROOT', dirname(__DIR__).'/');
+
+#===============================================================================
+# Autoload register for classes
+#===============================================================================
+spl_autoload_register(function($classname) {
+ $classname = str_replace('\\', '/', $classname);
+ require_once "namespace/{$classname}.php";
+});
+
+#===============================================================================
+# Exception handler for non-caught exceptions
+#===============================================================================
+set_exception_handler(function($Exception) {
+ http_response_code(503);
+ exit($Exception->getMessage());
+});
+
+#===============================================================================
+# Initialize HTTP class and remove all arrays from $_GET and $_POST
+#===============================================================================
+HTTP::init($_GET, $_POST, $_FILES, TRUE);
+
+#===============================================================================
+# Include configuration
+#===============================================================================
+require_once 'configuration.php';
+
+#===============================================================================
+# Overwrite configuration if admin
+#===============================================================================
+if(defined('ADMINISTRATION') AND ADMINISTRATION === TRUE) {
+
+ #===========================================================================
+ # Enable sessions
+ #===========================================================================
+ session_start();
+
+ #===========================================================================
+ # Authentication check
+ #===========================================================================
+ if(defined('AUTHENTICATION') AND !Application::isAuthenticated()) {
+ HTTP::redirect(Application::getAdminURL('auth.php'));
+ }
+
+ #===========================================================================
+ # Overwrite configuration
+ #===========================================================================
+ Application::set('CORE.LANGUAGE', Application::get('ADMIN.LANGUAGE'));
+ Application::set('TEMPLATE.NAME', Application::get('ADMIN.TEMPLATE'));
+ Application::set('TEMPLATE.LANG', Application::get('ADMIN.LANGUAGE'));
+}
+
+#===============================================================================
+# Include functions
+#===============================================================================
+require_once 'functions.php';
+
+#===============================================================================
+# TRY: PDOException
+#===============================================================================
+try {
+ $Language = Application::getLanguage();
+ $Database = Application::getDatabase();
+
+ $Database->setAttribute($Database::ATTR_DEFAULT_FETCH_MODE, $Database::FETCH_OBJ);
+ $Database->setAttribute($Database::ATTR_ERRMODE, $Database::ERRMODE_EXCEPTION);
+}
+
+#===============================================================================
+# CATCH: PDOException
+#===============================================================================
+catch(PDOException $Exception) {
+ http_response_code(503);
+ exit("PDO database connection error: {$Exception->getMessage()}");
+}
+
+#===============================================================================
+# Check if "304 Not Modified" and ETag header should be send
+#===============================================================================
+if(Application::get('CORE.SEND_304') === TRUE AND !defined('ADMINISTRATION')) {
+ #===========================================================================
+ # Select edit time from last edited items (page, post, user)
+ #===========================================================================
+ $execute = 'SELECT time_update FROM %s ORDER BY time_update DESC LIMIT 1';
+
+ $PageStatement = $Database->query(sprintf($execute, Page\Attribute::TABLE));
+ $PostStatement = $Database->query(sprintf($execute, Post\Attribute::TABLE));
+ $UserStatement = $Database->query(sprintf($execute, User\Attribute::TABLE));
+
+ #===========================================================================
+ # Define HTTP ETag header identifier
+ #===========================================================================
+ $HTTP_ETAG_IDENTIFIER = sha1(implode(NULL, [
+ serialize(Application::getConfiguration()),
+ $PageStatement->fetchColumn(),
+ $PostStatement->fetchColumn(),
+ $UserStatement->fetchColumn(),
+ 'CUSTOM-STRING-0123456789'
+ ]));
+
+ #===========================================================================
+ # Send ETag header within the HTTP response
+ #===========================================================================
+ HTTP::responseHeader(HTTP::HEADER_ETAG, "\"{$HTTP_ETAG_IDENTIFIER}\"");
+
+ #===========================================================================
+ # Validate ETag header from the HTTP request
+ #===========================================================================
+ if(isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
+ $HTTP_IF_NONE_MATCH = $_SERVER['HTTP_IF_NONE_MATCH'];
+ $HTTP_IF_NONE_MATCH = trim($HTTP_IF_NONE_MATCH, '"');
+
+ # If the server adds the extensions to the response header
+ $HTTP_IF_NONE_MATCH = rtrim($HTTP_IF_NONE_MATCH, '-br');
+ $HTTP_IF_NONE_MATCH = rtrim($HTTP_IF_NONE_MATCH, '-gzip');
+
+ if($HTTP_IF_NONE_MATCH === $HTTP_ETAG_IDENTIFIER) {
+ http_response_code(304);
+ exit();
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/core/configuration-example.php b/core/configuration-example.php
new file mode 100644
index 0000000..80192cf
--- /dev/null
+++ b/core/configuration-example.php
@@ -0,0 +1,103 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Application configuration [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+
+#===============================================================================
+# Core configuration
+#===============================================================================
+Application::set('CORE.LANGUAGE', 'en');
+Application::set('CORE.SEND_304', FALSE);
+
+#===============================================================================
+# Blog configuration
+#===============================================================================
+Application::set('BLOGMETA.NAME', 'My Techblog');
+Application::set('BLOGMETA.DESC', '[a creative description]');
+Application::set('BLOGMETA.HOME', 'Home');
+Application::set('BLOGMETA.MAIL', 'mail@example.org');
+Application::set('BLOGMETA.LANG', 'en');
+
+#===============================================================================
+# Settings for database connection
+#===============================================================================
+Application::set('DATABASE.HOSTNAME', 'localhost');
+Application::set('DATABASE.BASENAME', 'blog');
+Application::set('DATABASE.USERNAME', '');
+Application::set('DATABASE.PASSWORD', '');
+
+#===============================================================================
+# Backend configuration
+#===============================================================================
+Application::set('ADMIN.TEMPLATE', 'admin');
+Application::set('ADMIN.LANGUAGE', Application::get('CORE.LANGUAGE'));
+
+#===============================================================================
+# Settings for template configuration
+#===============================================================================
+Application::set('TEMPLATE.NAME', 'standard');
+Application::set('TEMPLATE.LANG', Application::get('CORE.LANGUAGE'));
+
+#===============================================================================
+# Protocol, hostname and path for this installation
+#===============================================================================
+Application::set('PATHINFO.PROT', isset($_SERVER['HTTPS']) ? 'https' : 'http');
+Application::set('PATHINFO.HOST', $_SERVER['HTTP_HOST']);
+Application::set('PATHINFO.BASE', '');
+
+#===============================================================================
+# Enable or disable the use of slug URLs for item permalinks
+#===============================================================================
+Application::set('PAGE.SLUG_URLS', TRUE);
+Application::set('POST.SLUG_URLS', TRUE);
+Application::set('USER.SLUG_URLS', TRUE);
+
+#===============================================================================
+# Number of items to display on feed and overview sites
+#===============================================================================
+Application::set('PAGE.LIST_SIZE', 10);
+Application::set('POST.LIST_SIZE', 10);
+Application::set('USER.LIST_SIZE', 10);
+Application::set('PAGE.FEED_SIZE', 25);
+Application::set('POST.FEED_SIZE', 25);
+
+#===============================================================================
+# Settings for item URL generation (you have to change the .htaccess as well!)
+#===============================================================================
+Application::set('PAGE.DIRECTORY', 'page');
+Application::set('POST.DIRECTORY', 'post');
+Application::set('USER.DIRECTORY', 'user');
+
+#===============================================================================
+# Enable or disable the use of emoticons for item content
+#===============================================================================
+Application::set('PAGE.EMOTICONS', TRUE);
+Application::set('POST.EMOTICONS', TRUE);
+Application::set('USER.EMOTICONS', TRUE);
+
+#===============================================================================
+# Number of characters to display in the items <meta> description
+#===============================================================================
+Application::set('PAGE.DESCRIPTION_SIZE', 200);
+Application::set('POST.DESCRIPTION_SIZE', 200);
+Application::set('USER.DESCRIPTION_SIZE', 200);
+
+#===============================================================================
+# "ORDER BY" clause for item sorting on feed and overview sites
+#===============================================================================
+Application::set('PAGE.LIST_SORT', 'time_insert DESC');
+Application::set('POST.LIST_SORT', 'time_insert DESC');
+Application::set('USER.LIST_SORT', 'time_insert DESC');
+Application::set('PAGE.FEED_SORT', 'time_insert DESC');
+Application::set('POST.FEED_SORT', 'time_insert DESC');
+
+#===============================================================================
+# Item attributes used to generate the <guid> hash for feed items
+#===============================================================================
+Application::set('PAGE.FEED_GUID', ['id', 'time_insert']);
+Application::set('POST.FEED_GUID', ['id', 'time_insert']);
+?> \ No newline at end of file
diff --git a/core/functions.php b/core/functions.php
new file mode 100644
index 0000000..726e96f
--- /dev/null
+++ b/core/functions.php
@@ -0,0 +1,322 @@
+<?php
+#===============================================================================
+# Helper function to reduce duplicate code
+#===============================================================================
+function generatePageNaviTemplate($currentSite): Template\Template {
+ $Database = Application::getDatabase();
+ $Statement = $Database->query(sprintf('SELECT COUNT(id) FROM %s', Page\Attribute::TABLE));
+
+ $lastSite = ceil($Statement->fetchColumn() / Application::get('PAGE.LIST_SIZE'));
+
+ $PaginationTemplate = Template\Factory::build('pagination');
+ $PaginationTemplate->set('THIS', $currentSite);
+ $PaginationTemplate->set('LAST', $lastSite);
+ $PaginationTemplate->set('HREF', Application::getPageURL('?site=%d'));
+
+ return $PaginationTemplate;
+}
+
+#===============================================================================
+# Helper function to reduce duplicate code
+#===============================================================================
+function generatePostNaviTemplate($currentSite): Template\Template {
+ $Database = Application::getDatabase();
+ $Statement = $Database->query(sprintf('SELECT COUNT(id) FROM %s', Post\Attribute::TABLE));
+
+ $lastSite = ceil($Statement->fetchColumn() / Application::get('POST.LIST_SIZE'));
+
+ $PaginationTemplate = Template\Factory::build('pagination');
+ $PaginationTemplate->set('THIS', $currentSite);
+ $PaginationTemplate->set('LAST', $lastSite);
+ $PaginationTemplate->set('HREF', Application::getPostURL('?site=%d'));
+
+ return $PaginationTemplate;
+}
+
+#===============================================================================
+# Helper function to reduce duplicate code
+#===============================================================================
+function generateUserNaviTemplate($currentSite): Template\Template {
+ $Database = Application::getDatabase();
+ $Statement = $Database->query(sprintf('SELECT COUNT(id) FROM %s', User\Attribute::TABLE));
+
+ $lastSite = ceil($Statement->fetchColumn() / Application::get('USER.LIST_SIZE'));
+
+ $PaginationTemplate = Template\Factory::build('pagination');
+ $PaginationTemplate->set('THIS', $currentSite);
+ $PaginationTemplate->set('LAST', $lastSite);
+ $PaginationTemplate->set('HREF', Application::getUserURL('?site=%d'));
+
+ return $PaginationTemplate;
+}
+
+#===============================================================================
+# Helper function to reduce duplicate code
+#===============================================================================
+function generatePageItemTemplate(Page\Item $Page, User\Item $User): Template\Template {
+ $Template = Template\Factory::build('page/item');
+ $Template->set('PAGE', generatePageItemData($Page));
+ $Template->set('USER', generateUserItemData($User));
+
+ return $Template;
+}
+
+#===============================================================================
+# Helper function to reduce duplicate code
+#===============================================================================
+function generatePostItemTemplate(Post\Item $Post, User\Item $User): Template\Template {
+ $Template = Template\Factory::build('post/item');
+ $Template->set('POST', generatePostItemData($Post));
+ $Template->set('USER', generateUserItemData($User));
+
+ return $Template;
+}
+
+#===============================================================================
+# Helper function to reduce duplicate code
+#===============================================================================
+function generateUserItemTemplate(User\Item $User): Template\Template {
+ $Template = Template\Factory::build('user/item');
+ $Template->set('USER', generateUserItemData($User));
+
+ return $Template;
+}
+
+#===============================================================================
+# Helper function to reduce duplicate code
+#===============================================================================
+function generateItemData(Item $Item): array {
+ return [
+ 'ID' => $Item->getID(),
+ 'URL' => $Item->getURL(),
+ 'GUID' => $Item->getGUID(),
+
+ 'PREV' => FALSE,
+ 'NEXT' => FALSE,
+
+ 'FILE' => [
+ 'LIST' => $Item->getFiles()
+ ],
+
+ 'BODY' => [
+ 'TEXT' => $Item->getBody(),
+ 'HTML' => $Item->getHTML()
+ ],
+
+ 'ATTR' => [
+ 'USER' => $Item->attr('user'),
+ 'SLUG' => $Item->attr('slug'),
+ 'NAME' => $Item->attr('name'),
+ 'BODY' => $Item->attr('body'),
+ 'TIME_INSERT' => $Item->attr('time_insert'),
+ 'TIME_UPDATE' => $Item->attr('time_update')
+ ]
+ ];
+}
+
+#===============================================================================
+# Helper function to reduce duplicate code
+#===============================================================================
+function generatePageItemData(Page\Item $Page): array {
+ return generateItemData($Page);
+}
+
+#===============================================================================
+# Helper function to reduce duplicate code
+#===============================================================================
+function generatePostItemData(Post\Item $Post): array {
+ return generateItemData($Post);
+}
+
+#===============================================================================
+# Helper function to reduce duplicate code
+#===============================================================================
+function generateUserItemData(User\Item $User): array {
+ return [
+ 'ID' => $User->getID(),
+ 'URL' => $User->getURL(),
+ 'GUID' => $User->getGUID(),
+
+ 'PREV' => FALSE,
+ 'NEXT' => FALSE,
+
+ 'FILE' => [
+ 'LIST' => $User->getFiles()
+ ],
+
+ 'BODY' => [
+ 'TEXT' => $User->getBody(),
+ 'HTML' => $User->getHTML()
+ ],
+
+ 'ATTR' => [
+ 'SLUG' => $User->attr('slug'),
+ 'BODY' => $User->attr('body'),
+ 'USERNAME' => $User->attr('username'),
+ 'FULLNAME' => $User->attr('fullname'),
+ 'MAILADDR' => $User->attr('mailaddr'),
+ 'TIME_INSERT' => $User->attr('time_insert'),
+ 'TIME_UPDATE' => $User->attr('time_update')
+ ]
+ ];
+}
+
+#===============================================================================
+# Parser for datetime formatted strings [YYYY-MM-DD HH:II:SS]
+#===============================================================================
+function parseDatetime($datetime, $format): string {
+ $Language = Application::getLanguage();
+
+ list($date, $time) = explode(' ', $datetime);
+
+ list($DATE['Y'], $DATE['M'], $DATE['D']) = explode('-', $date);
+ list($TIME['H'], $TIME['M'], $TIME['S']) = explode(':', $time);
+
+ $M_LIST = [
+ '01' => $Language->text('month_01'),
+ '02' => $Language->text('month_02'),
+ '03' => $Language->text('month_03'),
+ '04' => $Language->text('month_04'),
+ '05' => $Language->text('month_05'),
+ '06' => $Language->text('month_06'),
+ '07' => $Language->text('month_07'),
+ '08' => $Language->text('month_08'),
+ '09' => $Language->text('month_09'),
+ '10' => $Language->text('month_10'),
+ '11' => $Language->text('month_11'),
+ '12' => $Language->text('month_12'),
+ ];
+
+ $D_LIST = [
+ 0 => $Language->text('day_6'),
+ 1 => $Language->text('day_0'),
+ 2 => $Language->text('day_1'),
+ 3 => $Language->text('day_2'),
+ 4 => $Language->text('day_3'),
+ 5 => $Language->text('day_4'),
+ 6 => $Language->text('day_5'),
+ ];
+
+ return strtr($format, [
+ '[Y]' => $DATE['Y'],
+ '[M]' => $DATE['M'],
+ '[D]' => $DATE['D'],
+ '[H]' => $TIME['H'],
+ '[I]' => $TIME['M'],
+ '[S]' => $TIME['S'],
+ '[W]' => $D_LIST[date('w', strtotime($datetime))],
+ '[F]' => $M_LIST[date('m', strtotime($datetime))],
+ '[DATE]' => $date,
+ '[TIME]' => $time,
+ '[RFC2822]' => date('r', strtotime($datetime))
+ ]);
+}
+
+#===============================================================================
+# Get emoticons with unicode characters and description
+#===============================================================================
+function getEmoticons(): array {
+ $Language = Application::getLanguage();
+
+ return [
+ ':)' => ['&#x1F60A;', $Language->text('emoticon_1F60A')],
+ ':(' => ['&#x1F61E;', $Language->text('emoticon_1F61E')],
+ ':D' => ['&#x1F603;', $Language->text('emoticon_1F603')],
+ ':P' => ['&#x1F61B;', $Language->text('emoticon_1F61B')],
+ ':O' => ['&#x1F632;', $Language->text('emoticon_1F632')],
+ ';)' => ['&#x1F609;', $Language->text('emoticon_1F609')],
+ ';(' => ['&#x1F622;', $Language->text('emoticon_1F622')],
+ ':|' => ['&#x1F610;', $Language->text('emoticon_1F610')],
+ ':X' => ['&#x1F635;', $Language->text('emoticon_1F635')],
+ ':/' => ['&#x1F612;', $Language->text('emoticon_1F612')],
+ '8)' => ['&#x1F60E;', $Language->text('emoticon_1F60E')],
+ ':S' => ['&#x1F61F;', $Language->text('emoticon_1F61F')],
+ 'xD' => ['&#x1F602;', $Language->text('emoticon_1F602')],
+ '^^' => ['&#x1F604;', $Language->text('emoticon_1F604')],
+ ];
+}
+
+#===============================================================================
+# Parse emoticons to HTML encoded unicode characters
+#===============================================================================
+function parseEmoticons($string): string {
+ foreach(getEmoticons() as $emoticon => $data) {
+ $pattern = '#(^|\s)'.preg_quote($emoticon).'#';
+ $replace = " <span title=\"{$data[1]}\">{$data[0]}</span>";
+
+ $string = preg_replace($pattern, $replace, $string);
+ }
+
+ return $string;
+}
+
+#===============================================================================
+# Wrapper function for htmlspecialchars()
+#===============================================================================
+function escapeHTML($string): string {
+ return htmlspecialchars($string);
+}
+
+#===============================================================================
+# Wrapper function for strip_tags()
+#===============================================================================
+function removeHTML($string): string {
+ return strip_tags($string);
+}
+
+#===============================================================================
+# Remove all double line breaks from string
+#===============================================================================
+function removeDoubleLineBreaks($string): string {
+ return preg_replace('#(\r?\n){2,}#', "\n\n", $string);
+}
+
+#===============================================================================
+# Remove line breaks and tabs from a string
+#===============================================================================
+function removeLineBreaksAndTabs($string, $replace = ''): string {
+ return str_replace(["\r\n", "\r", "\n", "\t"], $replace, $string);
+}
+
+#===============================================================================
+# Return pseudo-random (hex converted) string
+#===============================================================================
+function getRandomValue($length = 40): string {
+ return strtoupper(bin2hex(random_bytes(ceil($length / 2))));
+}
+
+#===============================================================================
+# Return cutted string
+#===============================================================================
+function cut($string, $length, $replace = ' […]') {
+ if(mb_strlen($string) > $length) {
+ return preg_replace("/^(.{1,{$length}}\\b).*/su", "\\1{$replace}", $string);
+ }
+
+ return $string;
+}
+
+#===============================================================================
+# Return excerpt content
+#===============================================================================
+function excerpt($string, $length = 500, $replace = ' […]') {
+ $string = removeHTML($string);
+ $string = removeDoubleLineBreaks($string);
+ $string = cut($string, $length, $replace);
+ $string = nl2br($string);
+
+ return $string;
+}
+
+#====================================================================================================
+# Generate a valid slug URL part from a string
+#====================================================================================================
+function makeSlugURL($string) {
+ $string = strtolower($string);
+ $string = str_replace(['ä', 'ö', 'ü', 'ß'], ['ae', 'oe', 'ue', 'ss'], $string);
+ $string = preg_replace('/[^a-zA-Z0-9\-]/', '-', $string);
+ $string = preg_replace('/-+/', '-', $string);
+
+ return trim($string, '-');
+}
+?>
diff --git a/core/language/de.php b/core/language/de.php
new file mode 100644
index 0000000..dc6e924
--- /dev/null
+++ b/core/language/de.php
@@ -0,0 +1,194 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Internationalization [DE] [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# This file contains core internationalization strings for the DE language. If #
+# you are a translator, please only use the original EN language file for your #
+# translation and open a pull request on GitHub or send your language file via #
+# email back to <code@nerdmind.de>. #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+
+#===============================================================================
+# Date element names
+#===============================================================================
+$LANGUAGE['date_d'] = 'Tag';
+$LANGUAGE['date_m'] = 'Monat';
+$LANGUAGE['date_y'] = 'Jahr';
+
+#===============================================================================
+# Time element names
+#===============================================================================
+$LANGUAGE['time_h'] = 'Stunde';
+$LANGUAGE['time_m'] = 'Minute';
+$LANGUAGE['time_s'] = 'Sekunde';
+
+#===============================================================================
+# Day names
+#===============================================================================
+$LANGUAGE['day_0'] = 'Montag';
+$LANGUAGE['day_1'] = 'Dienstag';
+$LANGUAGE['day_2'] = 'Mittwoch';
+$LANGUAGE['day_3'] = 'Donnerstag';
+$LANGUAGE['day_4'] = 'Freitag';
+$LANGUAGE['day_5'] = 'Samstag';
+$LANGUAGE['day_6'] = 'Sonntag';
+
+#===============================================================================
+# Month names
+#===============================================================================
+$LANGUAGE['month_01'] = 'Januar';
+$LANGUAGE['month_02'] = 'Februar';
+$LANGUAGE['month_03'] = 'März';
+$LANGUAGE['month_04'] = 'April';
+$LANGUAGE['month_05'] = 'Mai';
+$LANGUAGE['month_06'] = 'Juni';
+$LANGUAGE['month_07'] = 'Juli';
+$LANGUAGE['month_08'] = 'August';
+$LANGUAGE['month_09'] = 'September';
+$LANGUAGE['month_10'] = 'Oktober';
+$LANGUAGE['month_11'] = 'November';
+$LANGUAGE['month_12'] = 'Dezember';
+
+#===============================================================================
+# Emoticon explanations
+#===============================================================================
+$LANGUAGE['emoticon_1F60A'] = 'Lächelndes Gesicht mit lächelnden Augen';
+$LANGUAGE['emoticon_1F61E'] = 'Enttäuschtes Gesicht';
+$LANGUAGE['emoticon_1F603'] = 'Lächelndes Gesicht mit offenem Mund';
+$LANGUAGE['emoticon_1F61B'] = 'Gesicht mit herausgestreckter Zunge';
+$LANGUAGE['emoticon_1F632'] = 'Erstauntes Gesicht';
+$LANGUAGE['emoticon_1F609'] = 'Zwinkerndes Gesicht';
+$LANGUAGE['emoticon_1F622'] = 'Weinendes Gesicht';
+$LANGUAGE['emoticon_1F610'] = 'Neutrales Gesicht';
+$LANGUAGE['emoticon_1F635'] = 'Schwindeliges Gesicht';
+$LANGUAGE['emoticon_1F612'] = 'Frustriertes Gesicht';
+$LANGUAGE['emoticon_1F60E'] = 'Lächelndes Gesicht mit Sonnenbrille';
+$LANGUAGE['emoticon_1F61F'] = 'Besorgtes Gesicht';
+$LANGUAGE['emoticon_1F602'] = 'Gesicht mit Freudentränen';
+$LANGUAGE['emoticon_1F604'] = 'Lächelndes Gesicht mit offenem Mund und lachenden Augen';
+
+#===============================================================================
+# Error messages
+#===============================================================================
+$LANGUAGE['error_security_csrf'] = 'Der Sicherheitstoken stimmt nicht mit dem Sicherheitstoken des Servers überein.';
+$LANGUAGE['error_database_exec'] = 'Es ist ein unerwarteter Fehler bei der Kommunikation mit der Datenbank aufgetreten.';
+
+#===============================================================================
+# Fulltext search
+#===============================================================================
+$LANGUAGE['search_no_results'] = 'Entschuldigung, es wurden keine Ergebnisse für "%s" gefunden.';
+
+#===============================================================================
+# Authentication
+#===============================================================================
+$LANGUAGE['authentication_failure'] = 'Der Benutzername oder das Passwort ist nicht korrekt.';
+
+#===============================================================================
+# Items [singular]
+#===============================================================================
+$LANGUAGE['page'] = 'Seite';
+$LANGUAGE['post'] = 'Beitrag';
+$LANGUAGE['user'] = 'Benutzer';
+
+#===============================================================================
+# Items [plural]
+#===============================================================================
+$LANGUAGE['pages'] = 'Seiten';
+$LANGUAGE['posts'] = 'Beiträge';
+$LANGUAGE['users'] = 'Benutzer';
+
+#===============================================================================
+# Actions
+#===============================================================================
+$LANGUAGE['select'] = 'Anzeigen';
+$LANGUAGE['insert'] = 'Erstellen';
+$LANGUAGE['update'] = 'Bearbeiten';
+$LANGUAGE['delete'] = 'Löschen';
+$LANGUAGE['search'] = 'Suchen';
+$LANGUAGE['remove'] = 'Entfernen';
+
+#===============================================================================
+# Previous items
+#===============================================================================
+$LANGUAGE['prev_page'] = 'Vorherige Seite';
+$LANGUAGE['prev_post'] = 'Vorheriger Beitrag';
+$LANGUAGE['prev_user'] = 'Vorheriger Benutzer';
+
+#===============================================================================
+# Next items
+#===============================================================================
+$LANGUAGE['next_page'] = 'Nächste Seite';
+$LANGUAGE['next_post'] = 'Nächster Beitrag';
+$LANGUAGE['next_user'] = 'Nächster Benutzer';
+
+#===============================================================================
+# Item overview
+#===============================================================================
+$LANGUAGE['page_overview'] = 'Seitenübersicht';
+$LANGUAGE['post_overview'] = 'Beitragübersicht';
+$LANGUAGE['user_overview'] = 'Benutzerübersicht';
+
+#===============================================================================
+# Items select
+#===============================================================================
+$LANGUAGE['select_page'] = 'Seite anzeigen';
+$LANGUAGE['select_post'] = 'Beitrag anzeigen';
+$LANGUAGE['select_user'] = 'Benutzer anzeigen';
+
+#===============================================================================
+# Items insert
+#===============================================================================
+$LANGUAGE['insert_page'] = 'Seite erstellen';
+$LANGUAGE['insert_post'] = 'Beitrag erstellen';
+$LANGUAGE['insert_user'] = 'Benutzer erstellen';
+
+#===============================================================================
+# Items update
+#===============================================================================
+$LANGUAGE['update_page'] = 'Seite bearbeiten';
+$LANGUAGE['update_post'] = 'Beitrag bearbeiten';
+$LANGUAGE['update_user'] = 'Benutzer bearbeiten';
+
+#===============================================================================
+# Items delete
+#===============================================================================
+$LANGUAGE['delete_page'] = 'Seite löschen';
+$LANGUAGE['delete_post'] = 'Beitrag löschen';
+$LANGUAGE['delete_user'] = 'Benutzer löschen';
+
+#===============================================================================
+# Item insert titles
+#===============================================================================
+$LANGUAGE['title_page_insert'] = $LANGUAGE['insert_page'];
+$LANGUAGE['title_post_insert'] = $LANGUAGE['insert_post'];
+$LANGUAGE['title_user_insert'] = $LANGUAGE['insert_user'];
+
+#===============================================================================
+# Item update titles
+#===============================================================================
+$LANGUAGE['title_page_update'] = $LANGUAGE['update_page'];
+$LANGUAGE['title_post_update'] = $LANGUAGE['update_post'];
+$LANGUAGE['title_user_update'] = $LANGUAGE['update_user'];
+
+#===============================================================================
+# Item delete titles
+#===============================================================================
+$LANGUAGE['title_page_delete'] = $LANGUAGE['delete_page'];
+$LANGUAGE['title_post_delete'] = $LANGUAGE['delete_post'];
+$LANGUAGE['title_user_delete'] = $LANGUAGE['delete_user'];
+
+#===============================================================================
+# Item overview titles
+#===============================================================================
+$LANGUAGE['title_page_overview'] = "{$LANGUAGE['page_overview']} [%d]";
+$LANGUAGE['title_post_overview'] = "{$LANGUAGE['post_overview']} [%d]";
+$LANGUAGE['title_user_overview'] = "{$LANGUAGE['user_overview']} [%d]";
+
+#===============================================================================
+# Search titles
+#===============================================================================
+$LANGUAGE['title_search_request'] = 'Volltextsuche';
+$LANGUAGE['title_search_results'] = 'Ergebnisse für "%s"';
+?> \ No newline at end of file
diff --git a/core/language/en.php b/core/language/en.php
new file mode 100644
index 0000000..3e48191
--- /dev/null
+++ b/core/language/en.php
@@ -0,0 +1,194 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Internationalization [EN] [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# This file contains core internationalization strings for the EN language. If #
+# you are a translator, please only use the original EN language file for your #
+# translation and open a pull request on GitHub or send your language file via #
+# email back to <code@nerdmind.de>. #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+
+#===============================================================================
+# Date element names
+#===============================================================================
+$LANGUAGE['date_d'] = 'Day';
+$LANGUAGE['date_m'] = 'Month';
+$LANGUAGE['date_y'] = 'Year';
+
+#===============================================================================
+# Time element names
+#===============================================================================
+$LANGUAGE['time_h'] = 'Hour';
+$LANGUAGE['time_m'] = 'Minute';
+$LANGUAGE['time_s'] = 'Second';
+
+#===============================================================================
+# Day names
+#===============================================================================
+$LANGUAGE['day_0'] = 'Monday';
+$LANGUAGE['day_1'] = 'Tuesday';
+$LANGUAGE['day_2'] = 'Wednesday';
+$LANGUAGE['day_3'] = 'Thursday';
+$LANGUAGE['day_4'] = 'Friday';
+$LANGUAGE['day_5'] = 'Saturday';
+$LANGUAGE['day_6'] = 'Sunday';
+
+#===============================================================================
+# Month names
+#===============================================================================
+$LANGUAGE['month_01'] = 'January';
+$LANGUAGE['month_02'] = 'February';
+$LANGUAGE['month_03'] = 'March';
+$LANGUAGE['month_04'] = 'April';
+$LANGUAGE['month_05'] = 'May';
+$LANGUAGE['month_06'] = 'June';
+$LANGUAGE['month_07'] = 'July';
+$LANGUAGE['month_08'] = 'August';
+$LANGUAGE['month_09'] = 'September';
+$LANGUAGE['month_10'] = 'October';
+$LANGUAGE['month_11'] = 'November';
+$LANGUAGE['month_12'] = 'December';
+
+#===============================================================================
+# Emoticon explanations
+#===============================================================================
+$LANGUAGE['emoticon_1F60A'] = 'Smiling face with smiling eyes';
+$LANGUAGE['emoticon_1F61E'] = 'Disappointed face';
+$LANGUAGE['emoticon_1F603'] = 'Smiling face with open mouth';
+$LANGUAGE['emoticon_1F61B'] = 'Face with stuck-out tongue';
+$LANGUAGE['emoticon_1F632'] = 'Astonished face';
+$LANGUAGE['emoticon_1F609'] = 'Winking face';
+$LANGUAGE['emoticon_1F622'] = 'Crying face';
+$LANGUAGE['emoticon_1F610'] = 'Neutral face';
+$LANGUAGE['emoticon_1F635'] = 'Dizzy face';
+$LANGUAGE['emoticon_1F612'] = 'Unamused face';
+$LANGUAGE['emoticon_1F60E'] = 'Smiling face with sunglasses';
+$LANGUAGE['emoticon_1F61F'] = 'Worried face';
+$LANGUAGE['emoticon_1F602'] = 'Face with tears of joy';
+$LANGUAGE['emoticon_1F604'] = 'Smiling face with open mouth and smiling eyes';
+
+#===============================================================================
+# Error messages
+#===============================================================================
+$LANGUAGE['error_security_csrf'] = 'The security token does not matches the security token at server.';
+$LANGUAGE['error_database_exec'] = 'An unexpected error occurred while communicating with the database.';
+
+#===============================================================================
+# Fulltext search
+#===============================================================================
+$LANGUAGE['search_no_results'] = 'Sorry, there are no search results for "%s".';
+
+#===============================================================================
+# Authentication
+#===============================================================================
+$LANGUAGE['authentication_failure'] = 'The username or password is incorrect.';
+
+#===============================================================================
+# Items [singular]
+#===============================================================================
+$LANGUAGE['page'] = 'Page';
+$LANGUAGE['post'] = 'Post';
+$LANGUAGE['user'] = 'User';
+
+#===============================================================================
+# Items [plural]
+#===============================================================================
+$LANGUAGE['pages'] = 'Pages';
+$LANGUAGE['posts'] = 'Posts';
+$LANGUAGE['users'] = 'Users';
+
+#===============================================================================
+# Actions
+#===============================================================================
+$LANGUAGE['select'] = 'Show';
+$LANGUAGE['insert'] = 'Create';
+$LANGUAGE['update'] = 'Edit';
+$LANGUAGE['delete'] = 'Delete';
+$LANGUAGE['search'] = 'Search';
+$LANGUAGE['remove'] = 'Remove';
+
+#===============================================================================
+# Previous items
+#===============================================================================
+$LANGUAGE['prev_page'] = 'Previous page';
+$LANGUAGE['prev_post'] = 'Previous post';
+$LANGUAGE['prev_user'] = 'Previous user';
+
+#===============================================================================
+# Next items
+#===============================================================================
+$LANGUAGE['next_page'] = 'Next page';
+$LANGUAGE['next_post'] = 'Next post';
+$LANGUAGE['next_user'] = 'Next user';
+
+#===============================================================================
+# Item overview
+#===============================================================================
+$LANGUAGE['page_overview'] = 'Page overview';
+$LANGUAGE['post_overview'] = 'Post overview';
+$LANGUAGE['user_overview'] = 'User overview';
+
+#===============================================================================
+# Items select
+#===============================================================================
+$LANGUAGE['select_page'] = 'Show page';
+$LANGUAGE['select_post'] = 'Show post';
+$LANGUAGE['select_user'] = 'Show user';
+
+#===============================================================================
+# Items insert
+#===============================================================================
+$LANGUAGE['insert_page'] = 'Create page';
+$LANGUAGE['insert_post'] = 'Create post';
+$LANGUAGE['insert_user'] = 'Create user';
+
+#===============================================================================
+# Items update
+#===============================================================================
+$LANGUAGE['update_page'] = 'Edit page';
+$LANGUAGE['update_post'] = 'Edit post';
+$LANGUAGE['update_user'] = 'Edit user';
+
+#===============================================================================
+# Items delete
+#===============================================================================
+$LANGUAGE['delete_page'] = 'Delete page';
+$LANGUAGE['delete_post'] = 'Delete post';
+$LANGUAGE['delete_user'] = 'Delete user';
+
+#===============================================================================
+# Item insert titles
+#===============================================================================
+$LANGUAGE['title_page_insert'] = $LANGUAGE['insert_page'];
+$LANGUAGE['title_post_insert'] = $LANGUAGE['insert_post'];
+$LANGUAGE['title_user_insert'] = $LANGUAGE['insert_user'];
+
+#===============================================================================
+# Item update titles
+#===============================================================================
+$LANGUAGE['title_page_update'] = $LANGUAGE['update_page'];
+$LANGUAGE['title_post_update'] = $LANGUAGE['update_post'];
+$LANGUAGE['title_user_update'] = $LANGUAGE['update_user'];
+
+#===============================================================================
+# Item delete titles
+#===============================================================================
+$LANGUAGE['title_page_delete'] = $LANGUAGE['delete_page'];
+$LANGUAGE['title_post_delete'] = $LANGUAGE['delete_post'];
+$LANGUAGE['title_user_delete'] = $LANGUAGE['delete_user'];
+
+#===============================================================================
+# Item overview titles
+#===============================================================================
+$LANGUAGE['title_page_overview'] = "{$LANGUAGE['page_overview']} [%d]";
+$LANGUAGE['title_post_overview'] = "{$LANGUAGE['post_overview']} [%d]";
+$LANGUAGE['title_user_overview'] = "{$LANGUAGE['user_overview']} [%d]";
+
+#===============================================================================
+# Search titles
+#===============================================================================
+$LANGUAGE['title_search_request'] = 'Fulltext search';
+$LANGUAGE['title_search_results'] = 'Results for "%s"';
+?> \ No newline at end of file
diff --git a/core/namespace/Application.php b/core/namespace/Application.php
new file mode 100644
index 0000000..39cb522
--- /dev/null
+++ b/core/namespace/Application.php
@@ -0,0 +1,148 @@
+<?php
+class Application {
+
+ #===============================================================================
+ # Singleton instances
+ #===============================================================================
+ private static $Database;
+ private static $Language;
+
+ #===============================================================================
+ # Configuration array
+ #===============================================================================
+ private static $configuration = [];
+
+ #===============================================================================
+ # Set configuration value
+ #===============================================================================
+ public static function set($config, $value) {
+ return self::$configuration[$config] = $value;
+ }
+
+ #===============================================================================
+ # Get configuration value
+ #===============================================================================
+ public static function get($config) {
+ return self::$configuration[$config] ?? "{$config}";
+ }
+
+ #===============================================================================
+ # Get configuration
+ #===============================================================================
+ public static function getConfiguration(): array {
+ return self::$configuration;
+ }
+
+ #===============================================================================
+ # Return singleton PDO database instance
+ #===============================================================================
+ public static function getDatabase($force = FALSE): Database {
+ if(!self::$Database instanceof Database OR $force === TRUE) {
+ $hostname = self::get('DATABASE.HOSTNAME');
+ $basename = self::get('DATABASE.BASENAME');
+ $username = self::get('DATABASE.USERNAME');
+ $password = self::get('DATABASE.PASSWORD');
+
+ self::set('DATABASE.PASSWORD', NULL);
+
+ self::$Database = new Database($hostname, $basename, $username, $password);
+ }
+
+ return self::$Database;
+ }
+
+ #===============================================================================
+ # Return singleton Language instance
+ #===============================================================================
+ public static function getLanguage($force = FALSE): Language {
+ if(!self::$Language instanceof Language OR $force === TRUE) {
+ $Language = new Language(self::get('CORE.LANGUAGE'));
+ $Language->loadLanguage(ROOT.'template/'.self::get('TEMPLATE.NAME').'/lang/'.self::get('TEMPLATE.LANG').'.php');
+ self::$Language = $Language;
+ }
+
+ return self::$Language;
+ }
+
+ #===============================================================================
+ # Return unique CSRF token for the current session
+ #===============================================================================
+ public static function getSecurityToken(): string {
+ if(!isset($_SESSION['token'])) {
+ $_SESSION['token'] = getRandomValue();
+ }
+
+ return $_SESSION['token'];
+ }
+
+ #===============================================================================
+ # Return boolean if successfully authenticated
+ #===============================================================================
+ public static function isAuthenticated(): bool {
+ return isset($_SESSION['auth']);
+ }
+
+ #===============================================================================
+ # Return absolute base URL
+ #===============================================================================
+ public static function getURL($more = ''): string {
+ $prot = self::get('PATHINFO.PROT');
+ $host = self::get('PATHINFO.HOST');
+ $base = self::get('PATHINFO.BASE');
+
+ return "{$prot}://{$host}/{$base}{$more}";
+ }
+
+ #===============================================================================
+ # Return absolute root URL
+ #===============================================================================
+ public static function getAdminURL($more = ''): string {
+ return self::getURL("admin/{$more}");
+ }
+
+ #===============================================================================
+ # Return absolute post URL
+ #===============================================================================
+ public static function getPostURL($more = ''): string {
+ return self::getURL(self::get('POST.DIRECTORY')."/{$more}");
+ }
+
+ #===============================================================================
+ # Return absolute page URL
+ #===============================================================================
+ public static function getPageURL($more = ''): string {
+ return self::getURL(self::get('PAGE.DIRECTORY')."/{$more}");
+ }
+
+ #===============================================================================
+ # Return absolute user URL
+ #===============================================================================
+ public static function getUserURL($more = ''): string {
+ return self::getURL(self::get('USER.DIRECTORY')."/{$more}");
+ }
+
+ #===============================================================================
+ # Return absolute file URL
+ #===============================================================================
+ public static function getFileURL($more = ''): string {
+ return self::getURL("rsrc/{$more}");
+ }
+
+ #===============================================================================
+ # Return absolute template URL
+ #===============================================================================
+ public static function getTemplateURL($more = ''): string {
+ $template = self::get('TEMPLATE.NAME');
+ return Application::getURL("template/{$template}/{$more}");
+ }
+
+ #===============================================================================
+ # Exit application with
+ #===============================================================================
+ public static function exit($code = 500) {
+ http_response_code($code);
+ $code === 404 AND require_once(ROOT."system/404.php");
+ exit();
+ }
+}
+?> \ No newline at end of file
diff --git a/core/namespace/Attribute.php b/core/namespace/Attribute.php
new file mode 100644
index 0000000..69998e0
--- /dev/null
+++ b/core/namespace/Attribute.php
@@ -0,0 +1,74 @@
+<?php
+abstract class Attribute implements AttributeInterface {
+
+ #===============================================================================
+ # Set attribute
+ #===============================================================================
+ public function set($attribute, $value) {
+ return $this->{$attribute} = $value;
+ }
+
+ #===============================================================================
+ # Get attribute
+ #===============================================================================
+ public function get($attribute) {
+ return $this->{$attribute} ?? NULL;
+ }
+
+ #===============================================================================
+ # Get array with not FALSE attributes
+ #===============================================================================
+ protected function getFilteredAttributes(): array {
+ return array_filter(get_object_vars($this), function($value) {
+ return $value !== FALSE;
+ });
+ }
+
+ #===============================================================================
+ # Insert database item
+ #===============================================================================
+ public function databaseINSERT(\Database $Database): bool {
+ $part[0] = '';
+ $part[1] = '';
+
+ $attributes = $this->getFilteredAttributes();
+
+ foreach($attributes as $column => $value) {
+ $part[0] .= "{$column},";
+ $part[1] .= '?,';
+ }
+
+ $part[0] = rtrim($part[0], ',');
+ $part[1] = rtrim($part[1], ',');
+
+ $Statement = $Database->prepare('INSERT INTO '.static::TABLE." ({$part[0]}) VALUES ({$part[1]})");
+ return $Statement->execute(array_values($attributes));
+ }
+
+ #===============================================================================
+ # Update database item
+ #===============================================================================
+ public function databaseUPDATE(\Database $Database): bool {
+ $part = '';
+
+ $attributes = $this->getFilteredAttributes();
+
+ foreach($attributes as $column => $value) {
+ $part .= "{$column} = ?,";
+ }
+
+ $part = rtrim($part, ',');
+
+ $Statement = $Database->prepare('UPDATE '.static::TABLE.' SET '.$part.' WHERE id = '.(int) $this->get('id'));
+ return $Statement->execute(array_values($attributes));
+ }
+
+ #===============================================================================
+ # Delete database item
+ #===============================================================================
+ public function databaseDELETE(\Database $Database): bool {
+ $Statement = $Database->prepare('DELETE FROM '.static::TABLE.' WHERE id = ?');
+ return $Statement->execute([$this->get('id')]);
+ }
+}
+?> \ No newline at end of file
diff --git a/core/namespace/AttributeInterface.php b/core/namespace/AttributeInterface.php
new file mode 100644
index 0000000..74cd1f1
--- /dev/null
+++ b/core/namespace/AttributeInterface.php
@@ -0,0 +1,7 @@
+<?php
+interface AttributeInterface {
+ public function databaseINSERT(\Database $Database);
+ public function databaseUPDATE(\Database $Database);
+ public function databaseDELETE(\Database $Database);
+}
+?> \ No newline at end of file
diff --git a/core/namespace/Database.php b/core/namespace/Database.php
new file mode 100644
index 0000000..ae233f4
--- /dev/null
+++ b/core/namespace/Database.php
@@ -0,0 +1,7 @@
+<?php
+class Database extends \PDO {
+ public function __construct($hostname, $basename, $username, $password) {
+ parent::__construct("mysql:host={$hostname};dbname={$basename};charset=utf8mb4;", $username, $password);
+ }
+}
+?> \ No newline at end of file
diff --git a/core/namespace/ExceptionHandler.php b/core/namespace/ExceptionHandler.php
new file mode 100644
index 0000000..d64d74c
--- /dev/null
+++ b/core/namespace/ExceptionHandler.php
@@ -0,0 +1,8 @@
+<?php
+abstract class ExceptionHandler extends Exception {
+ public function defaultHandler($code = 503) {
+ http_response_code(503);
+ exit(parent::getMessage());
+ }
+}
+?> \ No newline at end of file
diff --git a/core/namespace/Factory.php b/core/namespace/Factory.php
new file mode 100644
index 0000000..38be666
--- /dev/null
+++ b/core/namespace/Factory.php
@@ -0,0 +1,19 @@
+<?php
+abstract class Factory implements FactoryInterface {
+ public static $storage = [];
+
+ #===============================================================================
+ # Adds an instance of a class to the runtime instance cache
+ #===============================================================================
+ protected static function storeInstance($identifier, $instance) {
+ return self::$storage[get_called_class()][$identifier] = $instance;
+ }
+
+ #===============================================================================
+ # Gets an instance of a class from the runtime instance cache
+ #===============================================================================
+ protected static function fetchInstance($identifier) {
+ return self::$storage[get_called_class()][$identifier] ?? FALSE;
+ }
+}
+?> \ No newline at end of file
diff --git a/core/namespace/FactoryInterface.php b/core/namespace/FactoryInterface.php
new file mode 100644
index 0000000..54a115b
--- /dev/null
+++ b/core/namespace/FactoryInterface.php
@@ -0,0 +1,5 @@
+<?php
+interface FactoryInterface {
+ public static function build($identifier);
+}
+?> \ No newline at end of file
diff --git a/core/namespace/HTTP.php b/core/namespace/HTTP.php
new file mode 100644
index 0000000..f9fbf01
--- /dev/null
+++ b/core/namespace/HTTP.php
@@ -0,0 +1,234 @@
+<?php
+class HTTP {
+ private static $GET = NULL;
+ private static $POST = NULL;
+ private static $FILE = NULL;
+
+ #===============================================================================
+ # HTTP protocol versions
+ #===============================================================================
+ const VERSION_1_0 = 'HTTP/1.0';
+ const VERSION_1_1 = 'HTTP/1.1';
+ const VERSION_2_0 = 'HTTP/2.0';
+
+ #===============================================================================
+ # HTTP header fields
+ #===============================================================================
+ const HEADER_ETAG = 'ETag';
+ const HEADER_CONTENT_TYPE = 'Content-Type';
+ const HEADER_TRANSFER_ENCODING = 'Transfer-Encoding';
+ const HEADER_ACCESS_CONTROL = 'Access-Control-Allow-Origin';
+
+ #===============================================================================
+ # Values for HTTP header fields
+ #===============================================================================
+ const CONTENT_TYPE_JSCRIPT = 'application/x-javascript; charset=UTF-8';
+ const CONTENT_TYPE_TEXT = 'text/plain; charset=UTF-8';
+ const CONTENT_TYPE_HTML = 'text/html; charset=UTF-8';
+ const CONTENT_TYPE_JSON = 'application/json; charset=UTF-8';
+ const CONTENT_TYPE_XML = 'text/xml; charset=UTF-8';
+
+ #===============================================================================
+ # HTTP status codes
+ #===============================================================================
+ const RESPONSE_CODE = [
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 307 => 'Temporary Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Time-out',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested range not satisfiable',
+ 417 => 'Expectation Failed',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Time-out'
+ ];
+
+ #===============================================================================
+ # Initialize $GET, $POST and $FILE
+ #===============================================================================
+ public static function init($GET, $POST, $FILE, $removeArrays = FALSE, $trimValues = TRUE) {
+ self::$GET = $GET;
+ self::$POST = $POST;
+ self::$FILE = $FILE;
+
+ $removeArrays AND self::removeArrays();
+
+ self::$GET = ($trimValues === TRUE ? self::trim(self::$GET) : self::$GET );
+ self::$POST = ($trimValues === TRUE ? self::trim(self::$POST) : self::$POST);
+ }
+
+ #===============================================================================
+ # Remove all arrays from $_GET and $_POST
+ #===============================================================================
+ private static function removeArrays() {
+ foreach(['GET', 'POST'] as $HTTP) {
+ foreach(self::$$HTTP as $name => $value) {
+ if(is_array(self::$$HTTP[$name])) {
+ unset(self::$$HTTP[$name]);
+ }
+ }
+ }
+ }
+
+ #===============================================================================
+ # Trim all strings in argument
+ #===============================================================================
+ private static function trim($mixed) {
+ if(is_array($mixed)) {
+ return array_map('self::trim', $mixed);
+ }
+
+ return trim($mixed);
+ }
+
+ #===============================================================================
+ # Checks if all elements of $arguments are set as key of $data
+ #===============================================================================
+ private static function issetData($data, $arguments) {
+ foreach($arguments as $key) {
+ if(is_array($key)) {
+ if(!isset($data[key($key)]) OR $data[key($key)] !== $key[key($key)]) {
+ return FALSE;
+ }
+ }
+
+ else if(!isset($data[$key]) OR !is_string($data[$key])) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ }
+
+ #===============================================================================
+ # Return null or the value (if set) from one of the three requests attributes
+ #===============================================================================
+ public static function returnKey($data, array $paths) {
+ $current = &$data;
+
+ foreach($paths as $path) {
+ if(!isset($current[$path])) {
+ return NULL;
+ }
+ $current = &$current[$path];
+ }
+
+ return $current;
+ }
+
+ #===============================================================================
+ # Return GET value
+ #===============================================================================
+ public static function GET() {
+ return self::returnKey(self::$GET, func_get_args());
+ }
+
+ #===============================================================================
+ # Return POST value
+ #===============================================================================
+ public static function POST() {
+ return self::returnKey(self::$POST, func_get_args());
+ }
+
+ #===============================================================================
+ # Return FILE value
+ #===============================================================================
+ public static function FILE() {
+ return self::returnKey(self::$FILE, func_get_args());
+ }
+
+ #===============================================================================
+ # Checks if all elements of func_get_args() are set key of self::$POST
+ #===============================================================================
+ public static function issetPOST() {
+ return self::issetData(self::$POST, func_get_args());
+ }
+
+ #===============================================================================
+ # Checks if all elements of func_get_args() are set key of self::$GET
+ #===============================================================================
+ public static function issetGET() {
+ return self::issetData(self::$GET, func_get_args());
+ }
+
+ #===============================================================================
+ # Checks if all elements of func_get_args() are set key of self::$FILE
+ #===============================================================================
+ public static function issetFILE() {
+ return self::issetData(self::$FILE, func_get_args());
+ }
+
+ #===============================================================================
+ # Return HTTP request method or check if request method equals with $method
+ #===============================================================================
+ public static function requestMethod($method = NULL) {
+ if(!empty($method)) {
+ return ($_SERVER['REQUEST_METHOD'] === $method);
+ }
+
+ return $_SERVER['REQUEST_METHOD'];
+ }
+
+ #===============================================================================
+ # Return REQUEST_URL
+ #===============================================================================
+ public static function requestURI() {
+ return $_SERVER['REQUEST_URL'] ?? FALSE;
+ }
+
+ #===============================================================================
+ # Sends a HTTP header line to the client
+ #===============================================================================
+ public static function responseHeader($field, $value) {
+ self::sendHeader("{$field}: {$value}");
+ }
+
+ #===============================================================================
+ # Sends a HTTP redirect to the client
+ #===============================================================================
+ public static function redirect($location, $code = 303, $exit = TRUE) {
+ http_response_code($code);
+ self::sendHeader("Location: {$location}");
+ $exit AND exit();
+ }
+
+ #===============================================================================
+ # Sends a new HTTP header line to the client if headers are not already sent
+ #===============================================================================
+ private static function sendHeader($header) {
+ if(!headers_sent()) {
+ header($header);
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/core/namespace/Item.php b/core/namespace/Item.php
new file mode 100644
index 0000000..6d17011
--- /dev/null
+++ b/core/namespace/Item.php
@@ -0,0 +1,150 @@
+<?php
+abstract class Item implements ItemInterface {
+ protected $Database = NULL;
+ protected $Attribute = NULL;
+ protected $Reflection = NULL;
+
+ abstract public function getURL();
+ abstract public function getGUID();
+
+ #===============================================================================
+ # Abstract item constructor
+ #===============================================================================
+ public final function __construct($itemID, \Database $Database) {
+ $this->Database = $Database;
+
+ $this->Reflection = new ReflectionObject($this);
+
+ $attribute = "{$this->Reflection->getNamespaceName()}\\Attribute";
+ $exception = "{$this->Reflection->getNamespaceName()}\\Exception";
+
+ #===============================================================================
+ # Checking if item in database exists
+ #===============================================================================
+ $Statement = $Database->prepare(sprintf('SELECT * FROM %s WHERE id = ?', $attribute::TABLE));
+ $Statement->execute([$itemID]);
+
+ #===============================================================================
+ # Checking if retrieving data failed
+ #===============================================================================
+ if(!$this->Attribute = $Statement->fetchObject($attribute)) {
+ throw new $exception(sprintf('%s\\Item with ID %s does not exists', $this->Reflection->getNamespaceName(), (int) $itemID));
+ }
+ }
+
+ #===============================================================================
+ # Return attribute by name (short hand wrapper)
+ #===============================================================================
+ public function attr($attribute) {
+ return $this->Attribute->get($attribute);
+ }
+
+ #===============================================================================
+ # Return Attribute object
+ #===============================================================================
+ public final function getAttribute(): Attribute {
+ return $this->Attribute;
+ }
+
+ #===============================================================================
+ # Return unique ID
+ #===============================================================================
+ public final function getID(): int {
+ return $this->Attribute->get('id');
+ }
+
+ #===============================================================================
+ # Return pre-parsed content
+ #===============================================================================
+ public function getBody(): string {
+ $content = preg_replace_callback('#\{(POST|PAGE|USER)\[([0-9]+)\]\}#', function($matches) {
+ $namespace = ucfirst(strtolower($matches[1])).'\\Factory';
+
+ try {
+ $Item = $namespace::build($matches[2]);
+ return $Item->getURL();
+ } catch(Exception $Exception) {
+ return '{undefined}';
+ }
+ }, $this->Attribute->get('body'));
+
+ $content = preg_replace('#\{BASE\[\"([^"]+)\"\]\}#', \Application::getURL('$1'), $content);
+ $content = preg_replace('#\{FILE\[\"([^"]+)\"\]\}#', \Application::getFileURL('$1'), $content);
+
+ return $content;
+ }
+
+ #===============================================================================
+ # Return parsed content
+ #===============================================================================
+ public function getHTML(): string {
+ $item = "{$this->Reflection->getNamespaceName()}\\Item";
+
+ $Parsedown = new Parsedown();
+ $content = $this->getBody();
+
+ if(\Application::get($item::CONFIGURATION.'.EMOTICONS') === TRUE) {
+ $content = parseEmoticons($content);
+ }
+
+ return $Parsedown->text($content);
+ }
+
+ #===============================================================================
+ # Return attached files
+ #===============================================================================
+ public function getFiles(): array {
+ if(preg_match_all('#\!\[(.*)\][ ]?(?:\n[ ]*)?\((.*)(\s[\'"](.*)[\'"])?\)#U', $this->getBody(), $matches)) {
+ return array_map('htmlentities', $matches[2]);
+ }
+
+ return [];
+ }
+
+ #===============================================================================
+ # Return previous item ID
+ #===============================================================================
+ public function getPrevID(): int {
+ $execute = 'SELECT id FROM %s WHERE DATE(time_insert) <= DATE(?) AND id < ? ORDER BY time_insert DESC, id DESC LIMIT 1';
+
+ $attribute = "{$this->Reflection->getNamespaceName()}\\Attribute";
+ $Statement = $this->Database->prepare(sprintf($execute, $attribute::TABLE));
+
+ if($Statement->execute([$this->Attribute->get('time_insert'), $this->Attribute->get('id')])) {
+ return $Statement->fetchColumn();
+ }
+
+ return 0;
+ }
+
+ #===============================================================================
+ # Return next item ID
+ #===============================================================================
+ public function getNextID(): int {
+ $execute = 'SELECT id FROM %s WHERE DATE(time_insert) >= DATE(?) AND id > ? ORDER BY time_insert ASC, id DESC LIMIT 1';
+
+ $attribute = "{$this->Reflection->getNamespaceName()}\\Attribute";
+ $Statement = $this->Database->prepare(sprintf($execute, $attribute::TABLE));
+
+ if($Statement->execute([$this->Attribute->get('time_insert'), $this->Attribute->get('id')])) {
+ return $Statement->fetchColumn();
+ }
+
+ return 0;
+ }
+
+ #===============================================================================
+ # Return unique ID based on specific field comparison with value
+ #===============================================================================
+ public static function getIDByField($field, $value, \Database $Database): int {
+ $attribute = (new ReflectionClass(get_called_class()))->getNamespaceName().'\\Attribute';
+ $Statement = $Database->prepare('SELECT id FROM '.$attribute::TABLE." WHERE {$field} = ?");
+
+ if($Statement->execute([$value])) {
+ return $Statement->fetchColumn();
+ }
+
+ return 0;
+ }
+}
+?> \ No newline at end of file
diff --git a/core/namespace/ItemFactory.php b/core/namespace/ItemFactory.php
new file mode 100644
index 0000000..e5793d4
--- /dev/null
+++ b/core/namespace/ItemFactory.php
@@ -0,0 +1,12 @@
+<?php
+abstract class ItemFactory extends Factory implements FactoryInterface {
+ public static function build($itemID): Item {
+ if(!$Instance = parent::fetchInstance($itemID)) {
+ $Item = (new ReflectionClass(get_called_class()))->getNamespaceName().'\\Item';
+ $Instance = parent::storeInstance($itemID, new $Item($itemID, \Application::getDatabase()));
+ }
+
+ return $Instance;
+ }
+}
+?> \ No newline at end of file
diff --git a/core/namespace/ItemInterface.php b/core/namespace/ItemInterface.php
new file mode 100644
index 0000000..e7ccb6a
--- /dev/null
+++ b/core/namespace/ItemInterface.php
@@ -0,0 +1,5 @@
+<?php
+interface ItemInterface {
+ public function __construct($itemID, \Database $Database);
+}
+?> \ No newline at end of file
diff --git a/core/namespace/Language.php b/core/namespace/Language.php
new file mode 100644
index 0000000..c8a018e
--- /dev/null
+++ b/core/namespace/Language.php
@@ -0,0 +1,48 @@
+<?php
+class Language {
+ private $language = [];
+ private $template = [];
+
+ public function __construct($lang) {
+ require ROOT."core/language/{$lang}.php";
+ $this->language = $LANGUAGE;
+ }
+
+ public function loadLanguage($filename) {
+ require $filename;
+ $this->template = $LANGUAGE;
+ }
+
+ public function template($name, $params = FALSE) {
+ if(isset($this->template[$name])) {
+ if($params) {
+ return vsprintf($this->template[$name], $params);
+ }
+
+ return $this->template[$name];
+ }
+
+ return "{{$name}}";
+ }
+
+ private function get($name, $params = FALSE) {
+ if(isset($this->language[$name])) {
+ if($params) {
+ return vsprintf($this->language[$name], $params);
+ }
+
+ return $this->language[$name];
+ }
+
+ return "{{$name}}";
+ }
+
+ public function text($name, $params = FALSE) {
+ return $this->get($name, $params);
+ }
+
+ public function set($name, $value) {
+ return $this->language[$name] = $value;
+ }
+}
+?> \ No newline at end of file
diff --git a/core/namespace/Page/Attribute.php b/core/namespace/Page/Attribute.php
new file mode 100644
index 0000000..c12a2c8
--- /dev/null
+++ b/core/namespace/Page/Attribute.php
@@ -0,0 +1,22 @@
+<?php
+namespace Page;
+
+class Attribute extends \Attribute {
+
+ #===============================================================================
+ # Pre-Define database table columns
+ #===============================================================================
+ protected $id = FALSE;
+ protected $user = FALSE;
+ protected $slug = FALSE;
+ protected $name = FALSE;
+ protected $body = FALSE;
+ protected $time_insert = FALSE;
+ protected $time_update = FALSE;
+
+ #===============================================================================
+ # Define database table name
+ #===============================================================================
+ const TABLE = 'page';
+}
+?> \ No newline at end of file
diff --git a/core/namespace/Page/Exception.php b/core/namespace/Page/Exception.php
new file mode 100644
index 0000000..d4794b7
--- /dev/null
+++ b/core/namespace/Page/Exception.php
@@ -0,0 +1,5 @@
+<?php
+namespace Page;
+
+class Exception extends \Exception {}
+?> \ No newline at end of file
diff --git a/core/namespace/Page/Factory.php b/core/namespace/Page/Factory.php
new file mode 100644
index 0000000..f53b35f
--- /dev/null
+++ b/core/namespace/Page/Factory.php
@@ -0,0 +1,9 @@
+<?php
+namespace Page;
+
+class Factory extends \ItemFactory {
+ public static function buildBySlug($slug): \Item {
+ return self::build(Item::getIDByField('slug', $slug, \Application::getDatabase()));
+ }
+}
+?> \ No newline at end of file
diff --git a/core/namespace/Page/Item.php b/core/namespace/Page/Item.php
new file mode 100644
index 0000000..c6cece7
--- /dev/null
+++ b/core/namespace/Page/Item.php
@@ -0,0 +1,29 @@
+<?php
+namespace Page;
+
+class Item extends \Item {
+ const CONFIGURATION = 'PAGE';
+
+ #===============================================================================
+ # Return absolute page URL
+ #===============================================================================
+ public function getURL(): string {
+ if(\Application::get('PAGE.SLUG_URLS')) {
+ return \Application::getPageURL("{$this->Attribute->get('slug')}/");
+ }
+
+ return \Application::getPageURL("{$this->Attribute->get('id')}/");
+ }
+
+ #===============================================================================
+ # Return unique pseudo GUID
+ #===============================================================================
+ public function getGUID(): string {
+ foreach(\Application::get('PAGE.FEED_GUID') as $attribute) {
+ $attributes[] = $this->Attribute->get($attribute);
+ }
+
+ return sha1(implode(NULL, $attributes));
+ }
+}
+?> \ No newline at end of file
diff --git a/core/namespace/Parsedown.php b/core/namespace/Parsedown.php
new file mode 100644
index 0000000..610658b
--- /dev/null
+++ b/core/namespace/Parsedown.php
@@ -0,0 +1,1548 @@
+<?php
+
+#
+#
+# Parsedown
+# http://parsedown.org
+#
+# (c) Emanuil Rusev
+# http://erusev.com
+#
+# For the full license information, view the LICENSE file that was distributed
+# with this source code.
+#
+#
+
+class Parsedown
+{
+ # ~
+
+ const version = '1.6.0';
+
+ # ~
+
+ function text($text)
+ {
+ # make sure no definitions are set
+ $this->DefinitionData = array();
+
+ # standardize line breaks
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+ # remove surrounding line breaks
+ $text = trim($text, "\n");
+
+ # split text into lines
+ $lines = explode("\n", $text);
+
+ # iterate through lines to identify blocks
+ $markup = $this->lines($lines);
+
+ # trim line breaks
+ $markup = trim($markup, "\n");
+
+ return $markup;
+ }
+
+ #
+ # Setters
+ #
+
+ function setBreaksEnabled($breaksEnabled)
+ {
+ $this->breaksEnabled = $breaksEnabled;
+
+ return $this;
+ }
+
+ protected $breaksEnabled;
+
+ function setMarkupEscaped($markupEscaped)
+ {
+ $this->markupEscaped = $markupEscaped;
+
+ return $this;
+ }
+
+ protected $markupEscaped;
+
+ function setUrlsLinked($urlsLinked)
+ {
+ $this->urlsLinked = $urlsLinked;
+
+ return $this;
+ }
+
+ protected $urlsLinked = true;
+
+ #
+ # Lines
+ #
+
+ protected $BlockTypes = array(
+ '#' => array('Header'),
+ '*' => array('Rule', 'List'),
+ '+' => array('List'),
+ '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
+ '0' => array('List'),
+ '1' => array('List'),
+ '2' => array('List'),
+ '3' => array('List'),
+ '4' => array('List'),
+ '5' => array('List'),
+ '6' => array('List'),
+ '7' => array('List'),
+ '8' => array('List'),
+ '9' => array('List'),
+ ':' => array('Table'),
+ '<' => array('Comment', 'Markup'),
+ '=' => array('SetextHeader'),
+ '>' => array('Quote'),
+ '[' => array('Reference'),
+ '_' => array('Rule'),
+ '`' => array('FencedCode'),
+ '|' => array('Table'),
+ '~' => array('FencedCode'),
+ );
+
+ # ~
+
+ protected $unmarkedBlockTypes = array(
+ 'Code',
+ );
+
+ #
+ # Blocks
+ #
+
+ protected function lines(array $lines)
+ {
+ $CurrentBlock = null;
+
+ foreach ($lines as $line)
+ {
+ if (chop($line) === '')
+ {
+ if (isset($CurrentBlock))
+ {
+ $CurrentBlock['interrupted'] = true;
+ }
+
+ continue;
+ }
+
+ if (strpos($line, "\t") !== false)
+ {
+ $parts = explode("\t", $line);
+
+ $line = $parts[0];
+
+ unset($parts[0]);
+
+ foreach ($parts as $part)
+ {
+ $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
+
+ $line .= str_repeat(' ', $shortage);
+ $line .= $part;
+ }
+ }
+
+ $indent = 0;
+
+ while (isset($line[$indent]) and $line[$indent] === ' ')
+ {
+ $indent ++;
+ }
+
+ $text = $indent > 0 ? substr($line, $indent) : $line;
+
+ # ~
+
+ $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
+
+ # ~
+
+ if (isset($CurrentBlock['continuable']))
+ {
+ $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
+
+ if (isset($Block))
+ {
+ $CurrentBlock = $Block;
+
+ continue;
+ }
+ else
+ {
+ if ($this->isBlockCompletable($CurrentBlock['type']))
+ {
+ $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
+ }
+ }
+ }
+
+ # ~
+
+ $marker = $text[0];
+
+ # ~
+
+ $blockTypes = $this->unmarkedBlockTypes;
+
+ if (isset($this->BlockTypes[$marker]))
+ {
+ foreach ($this->BlockTypes[$marker] as $blockType)
+ {
+ $blockTypes []= $blockType;
+ }
+ }
+
+ #
+ # ~
+
+ foreach ($blockTypes as $blockType)
+ {
+ $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
+
+ if (isset($Block))
+ {
+ $Block['type'] = $blockType;
+
+ if ( ! isset($Block['identified']))
+ {
+ $Blocks []= $CurrentBlock;
+
+ $Block['identified'] = true;
+ }
+
+ if ($this->isBlockContinuable($blockType))
+ {
+ $Block['continuable'] = true;
+ }
+
+ $CurrentBlock = $Block;
+
+ continue 2;
+ }
+ }
+
+ # ~
+
+ if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
+ {
+ $CurrentBlock['element']['text'] .= "\n".$text;
+ }
+ else
+ {
+ $Blocks []= $CurrentBlock;
+
+ $CurrentBlock = $this->paragraph($Line);
+
+ $CurrentBlock['identified'] = true;
+ }
+ }
+
+ # ~
+
+ if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
+ {
+ $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
+ }
+
+ # ~
+
+ $Blocks []= $CurrentBlock;
+
+ unset($Blocks[0]);
+
+ # ~
+
+ $markup = '';
+
+ foreach ($Blocks as $Block)
+ {
+ if (isset($Block['hidden']))
+ {
+ continue;
+ }
+
+ $markup .= "\n";
+ $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
+ }
+
+ $markup .= "\n";
+
+ # ~
+
+ return $markup;
+ }
+
+ protected function isBlockContinuable($Type)
+ {
+ return method_exists($this, 'block'.$Type.'Continue');
+ }
+
+ protected function isBlockCompletable($Type)
+ {
+ return method_exists($this, 'block'.$Type.'Complete');
+ }
+
+ #
+ # Code
+
+ protected function blockCode($Line, $Block = null)
+ {
+ if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if ($Line['indent'] >= 4)
+ {
+ $text = substr($Line['body'], 4);
+
+ $Block = array(
+ 'element' => array(
+ 'name' => 'pre',
+ 'handler' => 'element',
+ 'text' => array(
+ 'name' => 'code',
+ 'text' => $text,
+ ),
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockCodeContinue($Line, $Block)
+ {
+ if ($Line['indent'] >= 4)
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['text']['text'] .= "\n";
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['element']['text']['text'] .= "\n";
+
+ $text = substr($Line['body'], 4);
+
+ $Block['element']['text']['text'] .= $text;
+
+ return $Block;
+ }
+ }
+
+ protected function blockCodeComplete($Block)
+ {
+ $text = $Block['element']['text']['text'];
+
+ $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
+
+ $Block['element']['text']['text'] = $text;
+
+ return $Block;
+ }
+
+ #
+ # Comment
+
+ protected function blockComment($Line)
+ {
+ if ($this->markupEscaped)
+ {
+ return;
+ }
+
+ if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
+ {
+ $Block = array(
+ 'markup' => $Line['body'],
+ );
+
+ if (preg_match('/-->$/', $Line['text']))
+ {
+ $Block['closed'] = true;
+ }
+
+ return $Block;
+ }
+ }
+
+ protected function blockCommentContinue($Line, array $Block)
+ {
+ if (isset($Block['closed']))
+ {
+ return;
+ }
+
+ $Block['markup'] .= "\n" . $Line['body'];
+
+ if (preg_match('/-->$/', $Line['text']))
+ {
+ $Block['closed'] = true;
+ }
+
+ return $Block;
+ }
+
+ #
+ # Fenced Code
+
+ protected function blockFencedCode($Line)
+ {
+ if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
+ {
+ $Element = array(
+ 'name' => 'code',
+ 'text' => '',
+ );
+
+ if (isset($matches[1]))
+ {
+ $class = 'language-'.$matches[1];
+
+ $Element['attributes'] = array(
+ 'class' => $class,
+ );
+ }
+
+ $Block = array(
+ 'char' => $Line['text'][0],
+ 'element' => array(
+ 'name' => 'pre',
+ 'handler' => 'element',
+ 'text' => $Element,
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockFencedCodeContinue($Line, $Block)
+ {
+ if (isset($Block['complete']))
+ {
+ return;
+ }
+
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['text']['text'] .= "\n";
+
+ unset($Block['interrupted']);
+ }
+
+ if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
+ {
+ $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
+
+ $Block['complete'] = true;
+
+ return $Block;
+ }
+
+ $Block['element']['text']['text'] .= "\n".$Line['body'];;
+
+ return $Block;
+ }
+
+ protected function blockFencedCodeComplete($Block)
+ {
+ $text = $Block['element']['text']['text'];
+
+ $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
+
+ $Block['element']['text']['text'] = $text;
+
+ return $Block;
+ }
+
+ #
+ # Header
+
+ protected function blockHeader($Line)
+ {
+ if (isset($Line['text'][1]))
+ {
+ $level = 1;
+
+ while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
+ {
+ $level ++;
+ }
+
+ if ($level > 6)
+ {
+ return;
+ }
+
+ $text = trim($Line['text'], '# ');
+
+ $Block = array(
+ 'element' => array(
+ 'name' => 'h' . min(6, $level),
+ 'text' => $text,
+ 'handler' => 'line',
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # List
+
+ protected function blockList($Line)
+ {
+ list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
+
+ if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
+ {
+ $Block = array(
+ 'indent' => $Line['indent'],
+ 'pattern' => $pattern,
+ 'element' => array(
+ 'name' => $name,
+ 'handler' => 'elements',
+ ),
+ );
+
+ if($name === 'ol')
+ {
+ $listStart = stristr($matches[0], '.', true);
+
+ if($listStart !== '1')
+ {
+ $Block['element']['attributes'] = array('start' => $listStart);
+ }
+ }
+
+ $Block['li'] = array(
+ 'name' => 'li',
+ 'handler' => 'li',
+ 'text' => array(
+ $matches[2],
+ ),
+ );
+
+ $Block['element']['text'] []= & $Block['li'];
+
+ return $Block;
+ }
+ }
+
+ protected function blockListContinue($Line, array $Block)
+ {
+ if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['li']['text'] []= '';
+
+ unset($Block['interrupted']);
+ }
+
+ unset($Block['li']);
+
+ $text = isset($matches[1]) ? $matches[1] : '';
+
+ $Block['li'] = array(
+ 'name' => 'li',
+ 'handler' => 'li',
+ 'text' => array(
+ $text,
+ ),
+ );
+
+ $Block['element']['text'] []= & $Block['li'];
+
+ return $Block;
+ }
+
+ if ($Line['text'][0] === '[' and $this->blockReference($Line))
+ {
+ return $Block;
+ }
+
+ if ( ! isset($Block['interrupted']))
+ {
+ $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
+
+ $Block['li']['text'] []= $text;
+
+ return $Block;
+ }
+
+ if ($Line['indent'] > 0)
+ {
+ $Block['li']['text'] []= '';
+
+ $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
+
+ $Block['li']['text'] []= $text;
+
+ unset($Block['interrupted']);
+
+ return $Block;
+ }
+ }
+
+ #
+ # Quote
+
+ protected function blockQuote($Line)
+ {
+ if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'blockquote',
+ 'handler' => 'lines',
+ 'text' => (array) $matches[1],
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockQuoteContinue($Line, array $Block)
+ {
+ if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['text'] []= '';
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['element']['text'] []= $matches[1];
+
+ return $Block;
+ }
+
+ if ( ! isset($Block['interrupted']))
+ {
+ $Block['element']['text'] []= $Line['text'];
+
+ return $Block;
+ }
+ }
+
+ #
+ # Rule
+
+ protected function blockRule($Line)
+ {
+ if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'hr'
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # Setext
+
+ protected function blockSetextHeader($Line, array $Block = null)
+ {
+ if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if (chop($Line['text'], $Line['text'][0]) === '')
+ {
+ $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
+
+ return $Block;
+ }
+ }
+
+ #
+ # Markup
+
+ protected function blockMarkup($Line)
+ {
+ if ($this->markupEscaped)
+ {
+ return;
+ }
+
+ if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
+ {
+ $element = strtolower($matches[1]);
+
+ if (in_array($element, $this->textLevelElements))
+ {
+ return;
+ }
+
+ $Block = array(
+ 'name' => $matches[1],
+ 'depth' => 0,
+ 'markup' => $Line['text'],
+ );
+
+ $length = strlen($matches[0]);
+
+ $remainder = substr($Line['text'], $length);
+
+ if (trim($remainder) === '')
+ {
+ if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
+ {
+ $Block['closed'] = true;
+
+ $Block['void'] = true;
+ }
+ }
+ else
+ {
+ if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
+ {
+ return;
+ }
+
+ if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
+ {
+ $Block['closed'] = true;
+ }
+ }
+
+ return $Block;
+ }
+ }
+
+ protected function blockMarkupContinue($Line, array $Block)
+ {
+ if (isset($Block['closed']))
+ {
+ return;
+ }
+
+ if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
+ {
+ $Block['depth'] ++;
+ }
+
+ if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
+ {
+ if ($Block['depth'] > 0)
+ {
+ $Block['depth'] --;
+ }
+ else
+ {
+ $Block['closed'] = true;
+ }
+ }
+
+ if (isset($Block['interrupted']))
+ {
+ $Block['markup'] .= "\n";
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['markup'] .= "\n".$Line['body'];
+
+ return $Block;
+ }
+
+ #
+ # Reference
+
+ protected function blockReference($Line)
+ {
+ if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
+ {
+ $id = strtolower($matches[1]);
+
+ $Data = array(
+ 'url' => $matches[2],
+ 'title' => null,
+ );
+
+ if (isset($matches[3]))
+ {
+ $Data['title'] = $matches[3];
+ }
+
+ $this->DefinitionData['Reference'][$id] = $Data;
+
+ $Block = array(
+ 'hidden' => true,
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # Table
+
+ protected function blockTable($Line, array $Block = null)
+ {
+ if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
+ {
+ $alignments = array();
+
+ $divider = $Line['text'];
+
+ $divider = trim($divider);
+ $divider = trim($divider, '|');
+
+ $dividerCells = explode('|', $divider);
+
+ foreach ($dividerCells as $dividerCell)
+ {
+ $dividerCell = trim($dividerCell);
+
+ if ($dividerCell === '')
+ {
+ continue;
+ }
+
+ $alignment = null;
+
+ if ($dividerCell[0] === ':')
+ {
+ $alignment = 'left';
+ }
+
+ if (substr($dividerCell, - 1) === ':')
+ {
+ $alignment = $alignment === 'left' ? 'center' : 'right';
+ }
+
+ $alignments []= $alignment;
+ }
+
+ # ~
+
+ $HeaderElements = array();
+
+ $header = $Block['element']['text'];
+
+ $header = trim($header);
+ $header = trim($header, '|');
+
+ $headerCells = explode('|', $header);
+
+ foreach ($headerCells as $index => $headerCell)
+ {
+ $headerCell = trim($headerCell);
+
+ $HeaderElement = array(
+ 'name' => 'th',
+ 'text' => $headerCell,
+ 'handler' => 'line',
+ );
+
+ if (isset($alignments[$index]))
+ {
+ $alignment = $alignments[$index];
+
+ $HeaderElement['attributes'] = array(
+ 'style' => 'text-align: '.$alignment.';',
+ );
+ }
+
+ $HeaderElements []= $HeaderElement;
+ }
+
+ # ~
+
+ $Block = array(
+ 'alignments' => $alignments,
+ 'identified' => true,
+ 'element' => array(
+ 'name' => 'table',
+ 'handler' => 'elements',
+ ),
+ );
+
+ $Block['element']['text'] []= array(
+ 'name' => 'thead',
+ 'handler' => 'elements',
+ );
+
+ $Block['element']['text'] []= array(
+ 'name' => 'tbody',
+ 'handler' => 'elements',
+ 'text' => array(),
+ );
+
+ $Block['element']['text'][0]['text'] []= array(
+ 'name' => 'tr',
+ 'handler' => 'elements',
+ 'text' => $HeaderElements,
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockTableContinue($Line, array $Block)
+ {
+ if (isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
+ {
+ $Elements = array();
+
+ $row = $Line['text'];
+
+ $row = trim($row);
+ $row = trim($row, '|');
+
+ preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
+
+ foreach ($matches[0] as $index => $cell)
+ {
+ $cell = trim($cell);
+
+ $Element = array(
+ 'name' => 'td',
+ 'handler' => 'line',
+ 'text' => $cell,
+ );
+
+ if (isset($Block['alignments'][$index]))
+ {
+ $Element['attributes'] = array(
+ 'style' => 'text-align: '.$Block['alignments'][$index].';',
+ );
+ }
+
+ $Elements []= $Element;
+ }
+
+ $Element = array(
+ 'name' => 'tr',
+ 'handler' => 'elements',
+ 'text' => $Elements,
+ );
+
+ $Block['element']['text'][1]['text'] []= $Element;
+
+ return $Block;
+ }
+ }
+
+ #
+ # ~
+ #
+
+ protected function paragraph($Line)
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'p',
+ 'text' => $Line['text'],
+ 'handler' => 'line',
+ ),
+ );
+
+ return $Block;
+ }
+
+ #
+ # Inline Elements
+ #
+
+ protected $InlineTypes = array(
+ '"' => array('SpecialCharacter'),
+ '!' => array('Image'),
+ '&' => array('SpecialCharacter'),
+ '*' => array('Emphasis'),
+ ':' => array('Url'),
+ '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
+ '>' => array('SpecialCharacter'),
+ '[' => array('Link'),
+ '_' => array('Emphasis'),
+ '`' => array('Code'),
+ '~' => array('Strikethrough'),
+ '\\' => array('EscapeSequence'),
+ );
+
+ # ~
+
+ protected $inlineMarkerList = '!"*_&[:<>`~\\';
+
+ #
+ # ~
+ #
+
+ public function line($text)
+ {
+ $markup = '';
+
+ # $excerpt is based on the first occurrence of a marker
+
+ while ($excerpt = strpbrk($text, $this->inlineMarkerList))
+ {
+ $marker = $excerpt[0];
+
+ $markerPosition = strpos($text, $marker);
+
+ $Excerpt = array('text' => $excerpt, 'context' => $text);
+
+ foreach ($this->InlineTypes[$marker] as $inlineType)
+ {
+ $Inline = $this->{'inline'.$inlineType}($Excerpt);
+
+ if ( ! isset($Inline))
+ {
+ continue;
+ }
+
+ # makes sure that the inline belongs to "our" marker
+
+ if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
+ {
+ continue;
+ }
+
+ # sets a default inline position
+
+ if ( ! isset($Inline['position']))
+ {
+ $Inline['position'] = $markerPosition;
+ }
+
+ # the text that comes before the inline
+ $unmarkedText = substr($text, 0, $Inline['position']);
+
+ # compile the unmarked text
+ $markup .= $this->unmarkedText($unmarkedText);
+
+ # compile the inline
+ $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
+
+ # remove the examined text
+ $text = substr($text, $Inline['position'] + $Inline['extent']);
+
+ continue 2;
+ }
+
+ # the marker does not belong to an inline
+
+ $unmarkedText = substr($text, 0, $markerPosition + 1);
+
+ $markup .= $this->unmarkedText($unmarkedText);
+
+ $text = substr($text, $markerPosition + 1);
+ }
+
+ $markup .= $this->unmarkedText($text);
+
+ return $markup;
+ }
+
+ #
+ # ~
+ #
+
+ protected function inlineCode($Excerpt)
+ {
+ $marker = $Excerpt['text'][0];
+
+ if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
+ {
+ $text = $matches[2];
+ $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
+ $text = preg_replace("/[ ]*\n/", ' ', $text);
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'code',
+ 'text' => $text,
+ ),
+ );
+ }
+ }
+
+ protected function inlineEmailTag($Excerpt)
+ {
+ if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
+ {
+ $url = $matches[1];
+
+ if ( ! isset($matches[2]))
+ {
+ $url = 'mailto:' . $url;
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $matches[1],
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+ }
+ }
+
+ protected function inlineEmphasis($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]))
+ {
+ return;
+ }
+
+ $marker = $Excerpt['text'][0];
+
+ if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
+ {
+ $emphasis = 'strong';
+ }
+ elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
+ {
+ $emphasis = 'em';
+ }
+ else
+ {
+ return;
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => $emphasis,
+ 'handler' => 'line',
+ 'text' => $matches[1],
+ ),
+ );
+ }
+
+ protected function inlineEscapeSequence($Excerpt)
+ {
+ if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
+ {
+ return array(
+ 'markup' => $Excerpt['text'][1],
+ 'extent' => 2,
+ );
+ }
+ }
+
+ protected function inlineImage($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
+ {
+ return;
+ }
+
+ $Excerpt['text']= substr($Excerpt['text'], 1);
+
+ $Link = $this->inlineLink($Excerpt);
+
+ if ($Link === null)
+ {
+ return;
+ }
+
+ $Inline = array(
+ 'extent' => $Link['extent'] + 1,
+ 'element' => array(
+ 'name' => 'img',
+ 'attributes' => array(
+ 'src' => $Link['element']['attributes']['href'],
+ 'alt' => $Link['element']['text'],
+ ),
+ ),
+ );
+
+ $Inline['element']['attributes'] += $Link['element']['attributes'];
+
+ unset($Inline['element']['attributes']['href']);
+
+ return $Inline;
+ }
+
+ protected function inlineLink($Excerpt)
+ {
+ $Element = array(
+ 'name' => 'a',
+ 'handler' => 'line',
+ 'text' => null,
+ 'attributes' => array(
+ 'href' => null,
+ 'title' => null,
+ ),
+ );
+
+ $extent = 0;
+
+ $remainder = $Excerpt['text'];
+
+ if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches))
+ {
+ $Element['text'] = $matches[1];
+
+ $extent += strlen($matches[0]);
+
+ $remainder = substr($remainder, $extent);
+ }
+ else
+ {
+ return;
+ }
+
+ if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
+ {
+ $Element['attributes']['href'] = $matches[1];
+
+ if (isset($matches[2]))
+ {
+ $Element['attributes']['title'] = substr($matches[2], 1, - 1);
+ }
+
+ $extent += strlen($matches[0]);
+ }
+ else
+ {
+ if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
+ {
+ $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
+ $definition = strtolower($definition);
+
+ $extent += strlen($matches[0]);
+ }
+ else
+ {
+ $definition = strtolower($Element['text']);
+ }
+
+ if ( ! isset($this->DefinitionData['Reference'][$definition]))
+ {
+ return;
+ }
+
+ $Definition = $this->DefinitionData['Reference'][$definition];
+
+ $Element['attributes']['href'] = $Definition['url'];
+ $Element['attributes']['title'] = $Definition['title'];
+ }
+
+ $Element['attributes']['href'] = str_replace(array('&', '<'), array('&amp;', '&lt;'), $Element['attributes']['href']);
+
+ return array(
+ 'extent' => $extent,
+ 'element' => $Element,
+ );
+ }
+
+ protected function inlineMarkup($Excerpt)
+ {
+ if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false)
+ {
+ return;
+ }
+
+ if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'markup' => $matches[0],
+ 'extent' => strlen($matches[0]),
+ );
+ }
+
+ if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'markup' => $matches[0],
+ 'extent' => strlen($matches[0]),
+ );
+ }
+
+ if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'markup' => $matches[0],
+ 'extent' => strlen($matches[0]),
+ );
+ }
+ }
+
+ protected function inlineSpecialCharacter($Excerpt)
+ {
+ if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
+ {
+ return array(
+ 'markup' => '&amp;',
+ 'extent' => 1,
+ );
+ }
+
+ $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
+
+ if (isset($SpecialCharacter[$Excerpt['text'][0]]))
+ {
+ return array(
+ 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
+ 'extent' => 1,
+ );
+ }
+ }
+
+ protected function inlineStrikethrough($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]))
+ {
+ return;
+ }
+
+ if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'del',
+ 'text' => $matches[1],
+ 'handler' => 'line',
+ ),
+ );
+ }
+ }
+
+ protected function inlineUrl($Excerpt)
+ {
+ if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
+ {
+ return;
+ }
+
+ if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
+ {
+ $Inline = array(
+ 'extent' => strlen($matches[0][0]),
+ 'position' => $matches[0][1],
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $matches[0][0],
+ 'attributes' => array(
+ 'href' => $matches[0][0],
+ ),
+ ),
+ );
+
+ return $Inline;
+ }
+ }
+
+ protected function inlineUrlTag($Excerpt)
+ {
+ if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
+ {
+ $url = str_replace(array('&', '<'), array('&amp;', '&lt;'), $matches[1]);
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $url,
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+ }
+ }
+
+ # ~
+
+ protected function unmarkedText($text)
+ {
+ if ($this->breaksEnabled)
+ {
+ $text = preg_replace('/[ ]*\n/', "<br />\n", $text);
+ }
+ else
+ {
+ $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
+ $text = str_replace(" \n", "\n", $text);
+ }
+
+ return $text;
+ }
+
+ #
+ # Handlers
+ #
+
+ protected function element(array $Element)
+ {
+ $markup = '<'.$Element['name'];
+
+ if (isset($Element['attributes']))
+ {
+ foreach ($Element['attributes'] as $name => $value)
+ {
+ if ($value === null)
+ {
+ continue;
+ }
+
+ $markup .= ' '.$name.'="'.$value.'"';
+ }
+ }
+
+ if (isset($Element['text']))
+ {
+ $markup .= '>';
+
+ if (isset($Element['handler']))
+ {
+ $markup .= $this->{$Element['handler']}($Element['text']);
+ }
+ else
+ {
+ $markup .= $Element['text'];
+ }
+
+ $markup .= '</'.$Element['name'].'>';
+ }
+ else
+ {
+ $markup .= ' />';
+ }
+
+ return $markup;
+ }
+
+ protected function elements(array $Elements)
+ {
+ $markup = '';
+
+ foreach ($Elements as $Element)
+ {
+ $markup .= "\n" . $this->element($Element);
+ }
+
+ $markup .= "\n";
+
+ return $markup;
+ }
+
+ # ~
+
+ protected function li($lines)
+ {
+ $markup = $this->lines($lines);
+
+ $trimmedMarkup = trim($markup);
+
+ if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
+ {
+ $markup = $trimmedMarkup;
+ $markup = substr($markup, 3);
+
+ $position = strpos($markup, "</p>");
+
+ $markup = substr_replace($markup, '', $position, 4);
+ }
+
+ return $markup;
+ }
+
+ #
+ # Deprecated Methods
+ #
+
+ function parse($text)
+ {
+ $markup = $this->text($text);
+
+ return $markup;
+ }
+
+ #
+ # Static Methods
+ #
+
+ static function instance($name = 'default')
+ {
+ if (isset(self::$instances[$name]))
+ {
+ return self::$instances[$name];
+ }
+
+ $instance = new static();
+
+ self::$instances[$name] = $instance;
+
+ return $instance;
+ }
+
+ private static $instances = array();
+
+ #
+ # Fields
+ #
+
+ protected $DefinitionData;
+
+ #
+ # Read-Only
+
+ protected $specialCharacters = array(
+ '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
+ );
+
+ protected $StrongRegex = array(
+ '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
+ '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
+ );
+
+ protected $EmRegex = array(
+ '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
+ '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
+ );
+
+ protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
+
+ protected $voidElements = array(
+ 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
+ );
+
+ protected $textLevelElements = array(
+ 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
+ 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
+ 'i', 'rp', 'del', 'code', 'strike', 'marquee',
+ 'q', 'rt', 'ins', 'font', 'strong',
+ 's', 'tt', 'sub', 'mark',
+ 'u', 'xm', 'sup', 'nobr',
+ 'var', 'ruby',
+ 'wbr', 'span',
+ 'time',
+ );
+}
diff --git a/core/namespace/Post/Attribute.php b/core/namespace/Post/Attribute.php
new file mode 100644
index 0000000..6f20183
--- /dev/null
+++ b/core/namespace/Post/Attribute.php
@@ -0,0 +1,22 @@
+<?php
+namespace Post;
+
+class Attribute extends \Attribute {
+
+ #===============================================================================
+ # Pre-Define database table columns
+ #===============================================================================
+ protected $id = FALSE;
+ protected $user = FALSE;
+ protected $slug = FALSE;
+ protected $name = FALSE;
+ protected $body = FALSE;
+ protected $time_insert = FALSE;
+ protected $time_update = FALSE;
+
+ #===============================================================================
+ # Define database table name
+ #===============================================================================
+ const TABLE = 'post';
+}
+?> \ No newline at end of file
diff --git a/core/namespace/Post/Exception.php b/core/namespace/Post/Exception.php
new file mode 100644
index 0000000..516ddbe
--- /dev/null
+++ b/core/namespace/Post/Exception.php
@@ -0,0 +1,5 @@
+<?php
+namespace Post;
+
+class Exception extends \Exception {}
+?> \ No newline at end of file
diff --git a/core/namespace/Post/Factory.php b/core/namespace/Post/Factory.php
new file mode 100644
index 0000000..34aa5e4
--- /dev/null
+++ b/core/namespace/Post/Factory.php
@@ -0,0 +1,9 @@
+<?php
+namespace Post;
+
+class Factory extends \ItemFactory {
+ public static function buildBySlug($slug): \Item {
+ return self::build(Item::getIDByField('slug', $slug, \Application::getDatabase()));
+ }
+}
+?> \ No newline at end of file
diff --git a/core/namespace/Post/Item.php b/core/namespace/Post/Item.php
new file mode 100644
index 0000000..a269ce4
--- /dev/null
+++ b/core/namespace/Post/Item.php
@@ -0,0 +1,50 @@
+<?php
+namespace Post;
+
+class Item extends \Item {
+ const CONFIGURATION = 'POST';
+
+ #===============================================================================
+ # Return absolute post URL
+ #===============================================================================
+ public function getURL(): string {
+ if(\Application::get('POST.SLUG_URLS')) {
+ return \Application::getPostURL("{$this->Attribute->get('slug')}/");
+ }
+
+ return \Application::getPostURL("{$this->Attribute->get('id')}/");
+ }
+
+ #===============================================================================
+ # Return unique pseudo GUID
+ #===============================================================================
+ public function getGUID(): string {
+ foreach(\Application::get('POST.FEED_GUID') as $attribute) {
+ $attributes[] = $this->Attribute->get($attribute);
+ }
+
+ return sha1(implode(NULL, $attributes));
+ }
+
+ #===============================================================================
+ # Return unique post IDs for search results
+ #===============================================================================
+ public static function getSearchResultIDs($search, array $date, \Database $Database): array {
+ $D = ($D = intval($date[0])) !== 0 ? $D : 'NULL';
+ $M = ($M = intval($date[1])) !== 0 ? $M : 'NULL';
+ $Y = ($Y = intval($date[2])) !== 0 ? $Y : 'NULL';
+
+ $Statement = $Database->prepare(sprintf("SELECT id FROM %s WHERE
+ ({$Y} IS NULL OR YEAR(time_insert) = {$Y}) AND
+ ({$M} IS NULL OR MONTH(time_insert) = {$M}) AND
+ ({$D} IS NULL OR DAY(time_insert) = {$D}) AND
+ MATCH(name, body) AGAINST(? IN BOOLEAN MODE) LIMIT 20", Attribute::TABLE));
+
+ if($Statement->execute([$search])) {
+ return $Statement->fetchAll($Database::FETCH_COLUMN);
+ }
+
+ return [];
+ }
+}
+?> \ No newline at end of file
diff --git a/core/namespace/Template/Exception.php b/core/namespace/Template/Exception.php
new file mode 100644
index 0000000..52c522e
--- /dev/null
+++ b/core/namespace/Template/Exception.php
@@ -0,0 +1,5 @@
+<?php
+namespace Template;
+
+class Exception extends \ExceptionHandler {}
+?> \ No newline at end of file
diff --git a/core/namespace/Template/Factory.php b/core/namespace/Template/Factory.php
new file mode 100644
index 0000000..a90c61e
--- /dev/null
+++ b/core/namespace/Template/Factory.php
@@ -0,0 +1,18 @@
+<?php
+namespace Template;
+
+class Factory extends \Factory implements \FactoryInterface {
+ public static function build($template): Template {
+ $Template = new Template(ROOT.'template/'.\Application::get('TEMPLATE.NAME')."/html/{$template}.php");
+ $Template->set('Language', \Application::getLanguage());
+ $Template->set('BLOGMETA', [
+ 'NAME' => \Application::get('BLOGMETA.NAME'),
+ 'DESC' => \Application::get('BLOGMETA.DESC'),
+ 'MAIL' => \Application::get('BLOGMETA.MAIL'),
+ 'LANG' => \Application::get('BLOGMETA.LANG'),
+ ]);
+
+ return $Template;
+ }
+}
+?> \ No newline at end of file
diff --git a/core/namespace/Template/Template.php b/core/namespace/Template/Template.php
new file mode 100644
index 0000000..0c68f92
--- /dev/null
+++ b/core/namespace/Template/Template.php
@@ -0,0 +1,72 @@
+<?php
+namespace Template;
+
+class Template {
+ private $filename = '';
+ private $parameters = [];
+
+ #===============================================================================
+ # Create template instance
+ #===============================================================================
+ public function __construct($filename) {
+ $this->filename = $filename;
+
+ if(!file_exists($filename)) {
+ throw new Exception("Template {$filename} does not exists.");
+ }
+ }
+
+ #===============================================================================
+ # Set value to array path
+ #===============================================================================
+ public function set($name, $value) {
+ if(!is_array($name)) {
+ return $this->parameters[$name] = $value;
+ }
+
+ $current = &$this->parameters;
+
+ foreach($name as $path) {
+ if(!isset($current[$path])) {
+ $current[$path] = [];
+ }
+ $current = &$current[$path];
+ }
+
+ return $current = $value;
+ }
+
+ #===============================================================================
+ # Add value as item to array path
+ #===============================================================================
+ public function add($paths, $value) {
+ if(!is_array($paths)) {
+ return $this->parameters[$paths][] = $value;
+ }
+
+ $current = &$this->parameters;
+
+ foreach($paths as $path) {
+ if(!isset($current[$path])) {
+ $current[$path] = [];
+ }
+ $current = &$current[$path];
+ }
+
+ return $current[] = $value;
+ }
+
+ #===============================================================================
+ # Return parsed template content
+ #===============================================================================
+ public function __toString() {
+ foreach($this->parameters as $name => $value) {
+ ${$name} = $value;
+ }
+
+ ob_start();
+ require $this->filename;
+ return ob_get_clean();
+ }
+}
+?> \ No newline at end of file
diff --git a/core/namespace/User/Attribute.php b/core/namespace/User/Attribute.php
new file mode 100644
index 0000000..b161fa9
--- /dev/null
+++ b/core/namespace/User/Attribute.php
@@ -0,0 +1,24 @@
+<?php
+namespace User;
+
+class Attribute extends \Attribute {
+
+ #===============================================================================
+ # Pre-Define database table columns
+ #===============================================================================
+ protected $id = FALSE;
+ protected $slug = FALSE;
+ protected $username = FALSE;
+ protected $password = FALSE;
+ protected $fullname = FALSE;
+ protected $mailaddr = FALSE;
+ protected $body = FALSE;
+ protected $time_insert = FALSE;
+ protected $time_update = FALSE;
+
+ #===============================================================================
+ # Define database table name
+ #===============================================================================
+ const TABLE = 'user';
+}
+?> \ No newline at end of file
diff --git a/core/namespace/User/Exception.php b/core/namespace/User/Exception.php
new file mode 100644
index 0000000..b5bcad0
--- /dev/null
+++ b/core/namespace/User/Exception.php
@@ -0,0 +1,5 @@
+<?php
+namespace User;
+
+class Exception extends \Exception {}
+?> \ No newline at end of file
diff --git a/core/namespace/User/Factory.php b/core/namespace/User/Factory.php
new file mode 100644
index 0000000..80e6b49
--- /dev/null
+++ b/core/namespace/User/Factory.php
@@ -0,0 +1,13 @@
+<?php
+namespace User;
+
+class Factory extends \ItemFactory {
+ public static function buildBySlug($slug): \Item {
+ return self::build(Item::getIDByField('slug', $slug, \Application::getDatabase()));
+ }
+
+ public static function buildByUsername($username): \Item {
+ return self::build(Item::getIDByField('username', $username, \Application::getDatabase()));
+ }
+}
+?> \ No newline at end of file
diff --git a/core/namespace/User/Item.php b/core/namespace/User/Item.php
new file mode 100644
index 0000000..129d8f9
--- /dev/null
+++ b/core/namespace/User/Item.php
@@ -0,0 +1,36 @@
+<?php
+namespace User;
+
+class Item extends \Item {
+ const CONFIGURATION = 'USER';
+
+ #===============================================================================
+ # Return absolute user URL
+ #===============================================================================
+ public function getURL(): string {
+ if(\Application::get('USER.SLUG_URLS')) {
+ return \Application::getUserURL("{$this->Attribute->get('slug')}/");
+ }
+
+ return \Application::getUserURL("{$this->Attribute->get('id')}/");
+ }
+
+ #===============================================================================
+ # Return unique pseudo GUID
+ #===============================================================================
+ public function getGUID(): string {
+ foreach(['id', 'time_insert'] as $attribute) {
+ $attributes[] = $this->Attribute->get($attribute);
+ }
+
+ return sha1(implode(NULL, $attributes));
+ }
+
+ #===============================================================================
+ # Compare plaintext password with hashed password from database
+ #===============================================================================
+ public function comparePassword($password): bool {
+ return password_verify($password, $this->Attribute->get('password'));
+ }
+}
+?> \ No newline at end of file
diff --git a/database.sql b/database.sql
new file mode 100644
index 0000000..efcd044
--- /dev/null
+++ b/database.sql
@@ -0,0 +1,66 @@
+-- =============================================================================
+-- Table structure for page items
+-- =============================================================================
+CREATE TABLE `page` (
+ `id` smallint(6) NOT NULL,
+ `time_insert` datetime NOT NULL,
+ `time_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ `user` tinyint(4) NOT NULL,
+ `slug` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
+ `name` varchar(100) NOT NULL,
+ `body` text NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- =============================================================================
+-- Table structure for post items
+-- =============================================================================
+CREATE TABLE `post` (
+ `id` smallint(6) NOT NULL,
+ `time_insert` datetime NOT NULL,
+ `time_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ `user` tinyint(4) NOT NULL,
+ `slug` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
+ `name` varchar(100) NOT NULL,
+ `body` text NOT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+-- =============================================================================
+-- Table structure for user items
+-- =============================================================================
+CREATE TABLE `user` (
+ `id` tinyint(4) NOT NULL,
+ `time_insert` datetime NOT NULL,
+ `time_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ `slug` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
+ `username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
+ `password` char(64) CHARACTER SET latin1 DEFAULT NULL,
+ `fullname` varchar(40) NOT NULL,
+ `mailaddr` varchar(60) NOT NULL,
+ `body` text NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- =============================================================================
+-- Insert demo page, post and user
+-- =============================================================================
+INSERT INTO `page` (`id`, `time_insert`, `time_update`, `user`, `slug`, `name`, `body`) VALUES
+(1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 'example-page', 'Example Page', 'OK. You discovered that there is also a page functionality. But what is the difference between a **page** and a **post**? This is simple: There is not really much difference. But you can style posts and pages within the templates CSS completely independent from each other. For example, use **pages** for things like your imprint, your terms of use, your FAQ or other stuff. And **posts** for your main blog posts. A **page** (and also a **user**) has exactly the same functionality as already described within the [first post]({POST[1]})! 8)');
+INSERT INTO `post` (`id`, `time_insert`, `time_update`, `user`, `slug`, `name`, `body`) VALUES
+(1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 'hello-world', 'Hello World!', 'Hello! This is the automatically generated first post on your new blog installation. You can type [Markdown](https://daringfireball.net/projects/markdown/) plaintext into the editor to format your content as you want. In this post you can see several examples to [format your content with Markdown](https://daringfireball.net/projects/markdown/syntax) and with the special features provided by this blog application. After you are familiar with the text formatting and done with the exploration of your new blog application, you can delete this post and create your own. Have fun! :)\r\n\r\n![Demo image: Computer Guy (Public Domain)]({FILE[\"image/content/computer-guy-public-domain.svg\"]})\r\n\r\n## Parsing emoticons (if `POST.EMOTICONS` is `TRUE` within your `configuration.php`)\r\n> You can insert one or more of the following emoticons into your posts by typing the emoticon as simple ASCII text. The emoticon parser will convert your ASCII emoticon to the HTML multibyte unicode equivalent. Each emoticon comes with an further explanation if you just hold your mouse over a emoticons face: \r\n> :) :( :D :P :O ;) ;( :| :X :/ 8) :S xD ^^\r\n\r\n## Dynamic internal URLs for items\r\nIf you want to link an item, please don\'t put the URL to the item hardcoded into your content! What if you want to change your site address (or the base directory) in the future? Then you have to change all links in your content. This is not cool! Thus, you can use the following code **without spaces between the braces** by knowing the ID of an item to link it dynamically:\r\n\r\n1. Example: `{ POST[1] }` \r\n{POST[1]}\r\n\r\n2. Example: `{ PAGE[1] }` \r\n{PAGE[1]}\r\n\r\n3. Example: `{ USER[1] }` \r\n{USER[1]}\r\n\r\n## Dynamic internal URLs for other resources\r\nThis also applies to any other resource that exists in the blog system and that you want to link to! You can link any other resource dynamically either relative to your base directory or relative to your resource directory for static content:\r\n\r\n* Example: `{ BASE[\"foo/bar/\"] }` \r\n{BASE[\"foo/bar/\"]}\r\n\r\n* Example: `{ FILE[\"foo/bar/\"] }` \r\n{FILE[\"foo/bar/\"]}\r\n\r\n### Anywhere …\r\nYou can use these codes anywhere in your markdown plaintext. This codes will be pre-parsed before the markdown parser gets the content. If the markdown parser begins then all codes already have been converted into the URLs.');
+INSERT INTO `user` (`id`, `time_insert`, `time_update`, `slug`, `username`, `password`, `fullname`, `mailaddr`, `body`) VALUES
+(1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'change-me', 'ChangeMe', '$2y$10$jH48L1K1y9dB303aI2biN.ob0biZDuUbMxPKadi3wDqOIxj6yNT6K', 'John Doe', 'mail@example.org', 'Describe yourself.');
+
+-- =============================================================================
+-- Add keys for tables
+-- =============================================================================
+ALTER TABLE `page` ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `slug` (`slug`), ADD KEY `page_user` (`user`);
+ALTER TABLE `post` ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `slug` (`slug`), ADD KEY `post_user` (`user`);
+ALTER TABLE `post` ADD FULLTEXT KEY `body` (`body`);
+ALTER TABLE `user` ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `username` (`username`), ADD UNIQUE KEY `slug` (`slug`);
+
+-- =============================================================================
+-- Add AUTO_INCREMENT for tables
+-- =============================================================================
+ALTER TABLE `page` MODIFY `id` smallint(6) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
+ALTER TABLE `post` MODIFY `id` smallint(6) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
+ALTER TABLE `user` MODIFY `id` tinyint(4) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
+ALTER TABLE `page` ADD CONSTRAINT `page_user` FOREIGN KEY (`user`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; \ No newline at end of file
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..b07fee6
--- /dev/null
+++ b/index.php
@@ -0,0 +1,54 @@
+<?php
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once 'core/application.php';
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ $execSQL = 'SELECT id FROM %s ORDER BY '.Application::get('POST.LIST_SORT').' LIMIT '.Application::get('POST.LIST_SIZE');
+ $Statement = $Database->query(sprintf($execSQL, Post\Attribute::TABLE));
+
+ $postIDs = $Statement->fetchAll($Database::FETCH_COLUMN);
+
+ foreach($postIDs as $postID) {
+ try {
+ $Post = Post\Factory::build($postID);
+ $User = User\Factory::build($Post->attr('user'));
+
+ $ItemTemplate = generatePostItemTemplate($Post, $User);
+
+ $posts[] = $ItemTemplate;
+ }
+ catch(Post\Exception $Exception){}
+ catch(User\Exception $Exception){}
+ }
+
+ $HomeTemplate = Template\Factory::build('home');
+ $HomeTemplate->set('PAGINATION', [
+ 'HTML' => generatePostNaviTemplate(1)
+ ]);
+ $HomeTemplate->set('LIST', [
+ 'POSTS' => $posts ?? []
+ ]);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('HTML', $HomeTemplate);
+ $MainTemplate->set('HEAD', [
+ 'NAME' => Application::get('BLOGMETA.HOME'),
+ 'DESC' => Application::get('BLOGMETA.NAME').' – '.Application::get('BLOGMETA.DESC'),
+ 'PERM' => Application::getURL()
+ ]);
+
+ echo $MainTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?> \ No newline at end of file
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..c0bc0eb
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,20 @@
+# PHP7 blogging application
+Easy blogging application written with pure PHP7! The application comes with a fulltext search functionality for posts and with customizable templates and languages. You can build your own template if the standard template does not satisfy you! You can see the application in action with a custom template on my private blog at [blog.nerdmind.de](https://blog.nerdmind.de/)!
+
+## Minimalistic standard template
+![Standard template](https://nmnd.de/file/p/github-blog/standard-template.png)
+
+## Clean administration interface …
+![Administration interface](https://nmnd.de/file/p/github-blog/admin-template-2.png)
+
+## … with a nice content editor
+![Content editor](https://nmnd.de/file/p/github-blog/admin-template-1.png)
+
+## Installation
+1. Download the repository and extract it to the target directory where it should be installed.
+2. Create your MySQL database and import the `database.sql` file.
+3. Rename `core/configuration-example.php` to `core/configuration.php` and customize the configuration and set in any case the settings for the database connection.
+4. Navigate your browser to `/admin/auth.php` and authenticate with the default username `ChangeMe` and the password `changeme` (please note that the username is case-sensitive).
+
+## Wiki
+More information about the configuration and customization on the **wiki** of this repository! \ No newline at end of file
diff --git a/rsrc/image/content/computer-guy-public-domain.svg b/rsrc/image/content/computer-guy-public-domain.svg
new file mode 100644
index 0000000..44b6eef
--- /dev/null
+++ b/rsrc/image/content/computer-guy-public-domain.svg
@@ -0,0 +1,242 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="1718.6296"
+ height="992.72675"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="Computer Guy.svg">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.175"
+ inkscape:cx="1164.6886"
+ inkscape:cy="1182.375"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1920"
+ inkscape:window-height="1018"
+ inkscape:window-x="-8"
+ inkscape:window-y="-8"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(855.59918,-221.08653)">
+ <rect
+ style="fill:#929292;fill-opacity:1;stroke:none"
+ id="rect3808"
+ width="1718.6296"
+ height="992.72675"
+ x="-855.59918"
+ y="221.08653" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path3812"
+ d="m 844.48753,1213.9866 0,-92.934 -1701.09689,0 0,92.934 z"
+ style="fill:#ffffff;stroke:none" />
+ <path
+ style="fill:#cccccc;fill-opacity:1;stroke:none"
+ d="M 1700.0938 902.4375 C 1699.2367 902.62999 1388.3571 972.44607 1295.8125 992.90625 L 1698.9062 992.90625 L 1700.0938 928.90625 L 1700.0938 902.4375 z "
+ transform="translate(-855.59918,221.08653)"
+ id="path3881" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:12;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.80000000000000004"
+ d="m -856.60936,1121.0526 1701.09689,0 0,92.934"
+ id="path3784"
+ inkscape:connector-curvature="0" />
+ <path
+ style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;stroke:none;stroke-width:12;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
+ d="M 830.5,1121.0625 399.875,1214 l 49.41212,0 394.35311,-84.1133 -4.51523,-8.8242 z"
+ id="path3786"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccc" />
+ <path
+ style="fill:#cccccc;stroke:#000000;stroke-width:12;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.80000000000000004;fill-opacity:1"
+ d="m -486.38845,1077.6161 440.426509,0.505 88.388348,31.8198 1.010152,26.2639 -329.035739,0 -193.21313,0"
+ id="path2996"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:12;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.8"
+ d="m -46.467017,1079.1312 -1.500361,53.7115"
+ id="path3766"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:#ffffff;stroke:#000000;stroke-width:12;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.8"
+ d="m -741.95705,614.96613 118.69293,438.40617 476.792,-13.1319 34.37492,-37.1603 -109.26979,-532.76884 -485.88337,10e-6 -37.23207,34.54823 z"
+ id="path3768"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccc" />
+ <path
+ style="fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:12;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round;stroke-linecap:round"
+ d="M 634.34375 249.8125 L 598.9375 288.09375 L 537.84375 390.59375 L 631.78125 799.875 L 463.21875 805.59375 L 418.03125 827.15625 L 709.125 819.15625 L 743.5 782 L 634.34375 249.8125 z "
+ id="path3876"
+ transform="translate(-855.59918,221.08653)" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:12;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.80000000000000004"
+ d="m -745.49258,506.87981 488.91383,2.0203 108.08633,529.31989"
+ id="path3770"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:12;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.8"
+ d="m -223.57547,1021.0983 -94.62258,-408.15247 -423.759,2.0203"
+ id="path3772"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:12;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.80000000000000004"
+ d="m -319.2082,612.94583 60.60915,-103.03556"
+ id="path3774"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:12;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.8"
+ d="m -149.50258,1040.2404 -74.07289,-19.1421 -158.68061,5.6635"
+ id="path3776"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ <path
+ style="fill:#ffffff;stroke:#000000;stroke-width:12;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.8"
+ d="m -729.33014,1180.6516 c 0,0 67.68022,-67.6803 67.68022,-71.7209 0,-4.0406 47.47717,-387.89855 47.47717,-387.89855 l 234.35539,2.0203 31.31473,124.24877 -36.36549,244.45688 79.45543,63.7832 -93.59757,25.1103 z"
+ id="path3778"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csccccccc" />
+ <path
+ style="fill:#cccccc;fill-opacity:1;stroke:#000000;stroke-width:12;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.8"
+ d="M 476.46875,504.71875 473.75,506 l -45.4375,378.8125 27.90625,74.75 0.375,0 93.59073,-25.09454 -79.46573,-63.81171 36.375,-244.4375 z"
+ transform="translate(-855.59918,221.08653)"
+ id="path3780"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccccc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:12;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.80000000000000004"
+ d="m -385.87827,1093.7784 -40.4061,13.132 -234.35539,1.0102"
+ id="path3782"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.80000000000000004"
+ d="M 668.57143,1116.6479 C 667.3064,948.82442 611.21392,849.53526 550,756.6479"
+ id="path3788"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:#ffffff;stroke:#000000;stroke-width:16;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.80000000000000004"
+ d="m 678.57143,379.50504 c 56.43679,94.55379 36.09553,239.27879 -30,331.42857 -66.09553,92.14978 -207.19585,156.16048 -314.28572,148.57143 -107.08987,-7.58905 -190.79763,-97.19222 -230,-187.14286 -39.202368,-89.95064 -30.503036,-211.34455 21.42858,-291.42857 51.93162,-80.08402 148.60271,-126.88085 250,-127.14286 101.39729,-0.26201 246.42035,31.1605 302.85714,125.71429 z"
+ id="path3792"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="zzzzzzz" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:12;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.80000000000000004"
+ d="m 62.950705,547.90303 c 23.8631,3.59127 39.281545,8.06254 107.428955,8.36221 14.18411,0.0624 30.02176,-7.31677 30.75273,-17.32795"
+ id="path3794"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:12;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.80000000000000004"
+ d="m 88.893424,624.0575 c 29.883676,8.08122 72.605746,10.1533 90.138176,-6.96646 17.53242,-17.11977 6.83647,-59.45107 2.79586,-67.53229"
+ id="path3796"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="czc" />
+ <path
+ sodipodi:nodetypes="cscssc"
+ inkscape:connector-curvature="0"
+ id="path3798"
+ d="m 270.74483,540.65429 c -2.18308,21.86801 18.48989,22.65265 35.89104,20.21048 64.53995,-9.05788 113.26579,-21.88274 160.69401,-36.20576 22.95428,40.00861 4.33309,81.32286 -17.7128,96.30466 -36.26639,24.64567 -103.45992,25.50598 -138.00994,0.32708 -21.93334,-15.98428 -25.66515,-36.96758 -21.78571,-58.21428"
+ style="fill:none;stroke:#000000;stroke-width:12;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.80000000000000004" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#000000;stroke:none"
+ id="path3800"
+ sodipodi:cx="327.79449"
+ sodipodi:cy="590.21741"
+ sodipodi:rx="15.152288"
+ sodipodi:ry="15.152288"
+ d="m 342.94678,590.21741 a 15.152288,15.152288 0 1 1 -30.30457,0 15.152288,15.152288 0 1 1 30.30457,0 z"
+ transform="matrix(0.66666667,0,0,0.66666667,108.1934,199.23913)" />
+ <path
+ transform="matrix(0.66666667,0,0,0.66666667,-101.95422,193.2036)"
+ d="m 342.94678,590.21741 a 15.152288,15.152288 0 1 1 -30.30457,0 15.152288,15.152288 0 1 1 30.30457,0 z"
+ sodipodi:ry="15.152288"
+ sodipodi:rx="15.152288"
+ sodipodi:cy="590.21741"
+ sodipodi:cx="327.79449"
+ id="path3802"
+ style="fill:#000000;stroke:none"
+ sodipodi:type="arc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:6;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none"
+ d="m 205,713.07647 c 30.17416,-23.82272 56.73466,-19.5207 87.85714,2.14285"
+ id="path3804"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.8"
+ d="m 602.85714,848.07647 c 4.41243,91.87358 -31.51229,242.19833 -78.57143,290.00003 C 513.20117,1016.0626 430.33653,689.74051 230,689.50504 95.669285,689.34715 99.896446,965.87773 150.03663,1118.3268 c 8.07971,24.566 12.97971,20.6629 31.53748,10.5423 160.69875,-87.6384 229.53358,-198.26059 319.40252,-303.61532"
+ id="path3790"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccsssc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.80000000000000004"
+ d="m 1082.2982,315.91257 c 1.1064,5.85005 -1.0813,18.12486 -7.4281,21.60932"
+ id="path3832"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc"
+ transform="translate(-855.59918,221.08653)" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.80000000000000004"
+ d="m 1094.105,316.08588 c -0.8358,9.99141 1.7119,14.85835 7.089,22.56871"
+ id="path3834"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc"
+ transform="translate(-855.59918,221.08653)" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path3869"
+ d="m 234.65157,746.05436 c 8.44714,-4.85886 26.12317,-5.24102 35.45176,2.49256"
+ style="fill:none;stroke:#000000;stroke-width:6;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" />
+ <path
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:12;stroke-miterlimit:4;stroke-dasharray:none"
+ d="m 599.02046,289.66057 33.78318,-37.99711"
+ id="path3871"
+ inkscape:connector-curvature="0"
+ transform="translate(-855.59918,221.08653)"
+ sodipodi:nodetypes="cc" />
+ </g>
+</svg>
diff --git a/system/403.php b/system/403.php
new file mode 100644
index 0000000..08ef44d
--- /dev/null
+++ b/system/403.php
@@ -0,0 +1,29 @@
+<?php
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+if(!defined('ROOT')) {
+ require_once '../core/application.php';
+}
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('HEAD', [
+ 'NAME' => '403 Forbidden',
+ 'DESC' => "You don't have permission to access {$_SERVER['REQUEST_URI']} on this server."
+ ]);
+
+ $MainTemplate->set('HTML', Template\Factory::build('403'));
+ echo $MainTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?> \ No newline at end of file
diff --git a/system/404.php b/system/404.php
new file mode 100644
index 0000000..a92e9d6
--- /dev/null
+++ b/system/404.php
@@ -0,0 +1,29 @@
+<?php
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+if(!defined('ROOT')) {
+ require_once '../core/application.php';
+}
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('HEAD', [
+ 'NAME' => '404 Not Found',
+ 'DESC' => "The requested URL {$_SERVER['REQUEST_URI']} was not found on this server."
+ ]);
+
+ $MainTemplate->set('HTML', Template\Factory::build('404'));
+ echo $MainTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?> \ No newline at end of file
diff --git a/system/feed/main.php b/system/feed/main.php
new file mode 100644
index 0000000..00ac4b8
--- /dev/null
+++ b/system/feed/main.php
@@ -0,0 +1,76 @@
+<?php
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+#===============================================================================
+# HEADER: Content-Type for XML document
+#===============================================================================
+HTTP::responseHeader(HTTP::HEADER_CONTENT_TYPE, HTTP::CONTENT_TYPE_XML);
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ if(HTTP::GET('item') !== 'page') {
+ $execSQL = 'SELECT id FROM %s ORDER BY '.Application::get('POST.FEED_SORT').' LIMIT '.Application::get('POST.FEED_SIZE');
+ $postIDs = $Database->query(sprintf($execSQL, Post\Attribute::TABLE))->fetchAll($Database::FETCH_COLUMN);
+
+ foreach($postIDs as $postID) {
+ try {
+ $Post = Post\Factory::build($postID);
+ $User = User\Factory::build($Post->attr('user'));
+
+ $ItemTemplate = Template\Factory::build('feed/item_post');
+ $ItemTemplate->set('POST', generatePostItemData($Post));
+ $ItemTemplate->set('USER', generateUserItemData($User));
+
+ $posts[] = $ItemTemplate;
+ }
+
+ catch(Post\Exception $Exception){}
+ catch(User\Exception $Exception){}
+ }
+ }
+
+ if(HTTP::GET('item') !== 'post') {
+ $execSQL = 'SELECT id FROM %s ORDER BY '.Application::get('PAGE.FEED_SORT').' LIMIT '.Application::get('PAGE.FEED_SIZE');
+ $pageIDs = $Database->query(sprintf($execSQL, Page\Attribute::TABLE))->fetchAll($Database::FETCH_COLUMN);
+
+ foreach($pageIDs as $pageID) {
+ try {
+ $Page = Page\Factory::build($pageID);
+ $User = User\Factory::build($Page->attr('user'));
+
+ $ItemTemplate = Template\Factory::build('feed/item_page');
+ $ItemTemplate->set('PAGE', generatePageItemData($Page));
+ $ItemTemplate->set('USER', generateUserItemData($User));
+
+ $pages[] = $ItemTemplate;
+ }
+
+ catch(Page\Exception $Exception){}
+ catch(User\Exception $Exception){}
+ }
+ }
+
+ $FeedTemplate = Template\Factory::build('feed/main');
+ $FeedTemplate->set('FEED', [
+ 'TYPE' => HTTP::GET('item'),
+ 'LIST' => [
+ 'POSTS' => $posts ?? [],
+ 'PAGES' => $pages ?? [],
+ ]
+ ]);
+
+ echo $FeedTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?> \ No newline at end of file
diff --git a/system/page/list.php b/system/page/list.php
new file mode 100644
index 0000000..5d07d30
--- /dev/null
+++ b/system/page/list.php
@@ -0,0 +1,64 @@
+<?php
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+$site_size = Application::get('PAGE.LIST_SIZE');
+$site_sort = Application::get('PAGE.LIST_SORT');
+
+$lastSite = ceil($Database->query(sprintf('SELECT COUNT(id) FROM %s', Page\Attribute::TABLE))->fetchColumn() / $site_size);
+
+$currentSite = HTTP::GET('site') ?? 1;
+$currentSite = abs(intval($currentSite));
+
+if($currentSite < 1 OR ($currentSite > $lastSite AND $lastSite > 0)) {
+ Application::exit(404);
+}
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ $execSQL = "SELECT id FROM %s ORDER BY {$site_sort} LIMIT ".(($currentSite-1) * $site_size).", {$site_size}";
+ $pageIDs = $Database->query(sprintf($execSQL, Page\Attribute::TABLE))->fetchAll($Database::FETCH_COLUMN);
+
+ foreach($pageIDs as $pageID) {
+ try {
+ $Page = Page\Factory::build($pageID);
+ $User = User\Factory::build($Page->attr('user'));
+
+ $ItemTemplate = generatePageItemTemplate($Page, $User);
+
+ $pages[] = $ItemTemplate;
+ }
+ catch(Page\Exception $Exception){}
+ catch(User\Exception $Exception){}
+ }
+
+ $ListTemplate = Template\Factory::build('page/list');
+ $ListTemplate->set('PAGINATION', [
+ 'THIS' => $currentSite,
+ 'LAST' => $lastSite,
+ 'HTML' => generatePageNaviTemplate($currentSite)
+ ]);
+ $ListTemplate->set('LIST', [
+ 'PAGES' => $pages ?? []
+ ]);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('HTML', $ListTemplate);
+ $MainTemplate->set('HEAD', [
+ 'NAME' => $Language->text('title_page_overview', $currentSite)
+ ]);
+
+ echo $MainTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?> \ No newline at end of file
diff --git a/system/page/main.php b/system/page/main.php
new file mode 100644
index 0000000..fdd5399
--- /dev/null
+++ b/system/page/main.php
@@ -0,0 +1,89 @@
+<?php
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+#===============================================================================
+# TRY: Page\Exception
+#===============================================================================
+try {
+ if(Application::get('PAGE.SLUG_URLS')) {
+ $Page = Page\Factory::buildBySlug(HTTP::GET('param'));
+ }
+
+ else {
+ $Page = Page\Factory::build(HTTP::GET('param'));
+ }
+
+ $User = User\Factory::build($Page->attr('user'));
+
+ $page_data = generatePageItemData($Page);
+ $user_data = generateUserItemData($User);
+
+ #===============================================================================
+ # Add post data for previous and next post if exists
+ #===============================================================================
+ try {
+ $PrevPage = Page\Factory::build($Page->getPrevID());
+ $page_data['PREV'] = generatePageItemData($PrevPage);
+ } catch(Page\Exception $Exception){}
+
+ try {
+ $NextPage = Page\Factory::build($Page->getNextID());
+ $page_data['NEXT'] = generatePageItemData($NextPage);
+ } catch(Page\Exception $Exception){}
+
+ #===============================================================================
+ # TRY: Template\Exception
+ #===============================================================================
+ try {
+ $PageTemplate = Template\Factory::build('page/main');
+ $PageTemplate->set('PAGE', $page_data);
+ $PageTemplate->set('USER', $user_data);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('HTML', $PageTemplate);
+ $MainTemplate->set('HEAD', [
+ 'NAME' => $page_data['ATTR']['NAME'],
+ 'DESC' => cut(removeLineBreaksAndTabs(removeHTML($Page->getHTML()), ' '), Application::get('PAGE.DESCRIPTION_SIZE')),
+ 'PERM' => $page_data['URL'],
+ 'OG_IMAGES' => $page_data['FILE']['LIST']
+ ]);
+
+ echo $MainTemplate;
+ }
+
+ #===============================================================================
+ # CATCH: Template\Exception
+ #===============================================================================
+ catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+ }
+}
+
+#===============================================================================
+# CATCH: Page\Exception
+#===============================================================================
+catch(Page\Exception $Exception) {
+ try {
+ if(Application::get('PAGE.SLUG_URLS') === FALSE) {
+ $Page = Page\Factory::buildBySlug(HTTP::GET('param'));
+ } else {
+ $Page = Page\Factory::build(HTTP::GET('param'));
+ }
+
+ HTTP::redirect($Page->getURL());
+ }
+
+ catch(Page\Exception $Exception) {
+ Application::exit(404);
+ }
+}
+
+#===============================================================================
+# CATCH: User\Exception
+#===============================================================================
+catch(User\Exception $Exception) {
+ exit($Exception->getMessage());
+} \ No newline at end of file
diff --git a/system/post/list.php b/system/post/list.php
new file mode 100644
index 0000000..a7d6ce7
--- /dev/null
+++ b/system/post/list.php
@@ -0,0 +1,64 @@
+<?php
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+$site_size = Application::get('POST.LIST_SIZE');
+$site_sort = Application::get('POST.LIST_SORT');
+
+$lastSite = ceil($Database->query(sprintf('SELECT COUNT(id) FROM %s', Post\Attribute::TABLE))->fetchColumn() / $site_size);
+
+$currentSite = HTTP::GET('site') ?? 1;
+$currentSite = abs(intval($currentSite));
+
+if($currentSite < 1 OR ($currentSite > $lastSite AND $lastSite > 0)) {
+ Application::exit(404);
+}
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ $execSQL = "SELECT id FROM %s ORDER BY {$site_sort} LIMIT ".(($currentSite-1) * $site_size).", {$site_size}";
+ $postIDs = $Database->query(sprintf($execSQL, Post\Attribute::TABLE))->fetchAll($Database::FETCH_COLUMN);
+
+ foreach($postIDs as $postID) {
+ try {
+ $Post = Post\Factory::build($postID);
+ $User = User\Factory::build($Post->attr('user'));
+
+ $ItemTemplate = generatePostItemTemplate($Post, $User);
+
+ $posts[] = $ItemTemplate;
+ }
+ catch(Post\Exception $Exception){}
+ catch(User\Exception $Exception){}
+ }
+
+ $ListTemplate = Template\Factory::build('post/list');
+ $ListTemplate->set('PAGINATION', [
+ 'THIS' => $currentSite,
+ 'LAST' => $lastSite,
+ 'HTML' => generatePostNaviTemplate($currentSite)
+ ]);
+ $ListTemplate->set('LIST', [
+ 'POSTS' => $posts ?? []
+ ]);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('HTML', $ListTemplate);
+ $MainTemplate->set('HEAD', [
+ 'NAME' => $Language->text('title_post_overview', $currentSite)
+ ]);
+
+ echo $MainTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?> \ No newline at end of file
diff --git a/system/post/main.php b/system/post/main.php
new file mode 100644
index 0000000..4322f53
--- /dev/null
+++ b/system/post/main.php
@@ -0,0 +1,89 @@
+<?php
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+#===============================================================================
+# TRY: Post\Exception, User\Exception
+#===============================================================================
+try {
+ if(Application::get('POST.SLUG_URLS')) {
+ $Post = Post\Factory::buildBySlug(HTTP::GET('param'));
+ }
+
+ else {
+ $Post = Post\Factory::build(HTTP::GET('param'));
+ }
+
+ $User = User\Factory::build($Post->attr('user'));
+
+ $post_data = generatePostItemData($Post);
+ $user_data = generateUserItemData($User);
+
+ #===============================================================================
+ # Add post data for previous and next post if exists
+ #===============================================================================
+ try {
+ $PrevPost = Post\Factory::build($Post->getPrevID());
+ $post_data['PREV'] = generatePostItemData($PrevPost);
+ } catch(Post\Exception $Exception){}
+
+ try {
+ $NextPost = Post\Factory::build($Post->getNextID());
+ $post_data['NEXT'] = generatePostItemData($NextPost);
+ } catch(Post\Exception $Exception){}
+
+ #===============================================================================
+ # TRY: Template\Exception
+ #===============================================================================
+ try {
+ $PostTemplate = Template\Factory::build('post/main');
+ $PostTemplate->set('POST', $post_data);
+ $PostTemplate->set('USER', $user_data);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('HTML', $PostTemplate);
+ $MainTemplate->set('HEAD', [
+ 'NAME' => $post_data['ATTR']['NAME'],
+ 'DESC' => cut(removeLineBreaksAndTabs(removeHTML($post_data['BODY']['HTML']), ' '), Application::get('POST.DESCRIPTION_SIZE')),
+ 'PERM' => $post_data['URL'],
+ 'OG_IMAGES' => $post_data['FILE']['LIST']
+ ]);
+
+ echo $MainTemplate;
+ }
+
+ #===============================================================================
+ # CATCH: Template\Exception
+ #===============================================================================
+ catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+ }
+}
+
+#===============================================================================
+# CATCH: Post\Exception
+#===============================================================================
+catch(Post\Exception $Exception) {
+ try {
+ if(Application::get('POST.SLUG_URLS') === FALSE) {
+ $Post = Post\Factory::buildBySlug(HTTP::GET('param'));
+ } else {
+ $Post = Post\Factory::build(HTTP::GET('param'));
+ }
+
+ HTTP::redirect($Post->getURL());
+ }
+
+ catch(Post\Exception $Exception) {
+ Application::exit(404);
+ }
+}
+
+#===============================================================================
+# CATCH: User\Exception
+#===============================================================================
+catch(User\Exception $Exception) {
+ exit($Exception->getMessage());
+} \ No newline at end of file
diff --git a/system/search/main.php b/system/search/main.php
new file mode 100644
index 0000000..1c9921a
--- /dev/null
+++ b/system/search/main.php
@@ -0,0 +1,89 @@
+<?php
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+$SEARCH_SUCCESS = FALSE;
+$D_LIST = $Database->query(sprintf('SELECT DISTINCT DAY(time_insert) AS temp FROM %s ORDER BY temp', Post\Attribute::TABLE));
+$M_LIST = $Database->query(sprintf('SELECT DISTINCT MONTH(time_insert) AS temp FROM %s ORDER BY temp', Post\Attribute::TABLE));
+$Y_LIST = $Database->query(sprintf('SELECT DISTINCT YEAR(time_insert) AS temp FROM %s ORDER BY temp', Post\Attribute::TABLE));
+
+if($search = HTTP::GET('q')) {
+ if(!$postIDs = Post\Item::getSearchResultIDs($search, [HTTP::GET('d'), HTTP::GET('m'), HTTP::GET('y')], $Database)) {
+ $message = $Language->text('search_no_results', escapeHTML($search));
+ }
+}
+
+$form_data = [
+ 'SELECT' => [
+ 'D' => HTTP::GET('d'),
+ 'M' => HTTP::GET('m'),
+ 'Y' => HTTP::GET('y'),
+ ],
+ 'OPTIONS' => [
+ 'D' => $D_LIST->fetchAll(PDO::FETCH_COLUMN),
+ 'M' => $M_LIST->fetchAll(PDO::FETCH_COLUMN),
+ 'Y' => $Y_LIST->fetchAll(PDO::FETCH_COLUMN),
+ ]
+];
+
+$search_data = [
+ 'TEXT' => $search,
+ 'INFO' => isset($message) ? $message : FALSE,
+];
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ if(isset($postIDs) AND !empty($postIDs)) {
+ foreach($postIDs as $postID) {
+ try {
+ $Post = Post\Factory::build($postID);
+ $User = User\Factory::build($Post->attr('user'));
+
+ $posts[] = generatePostItemTemplate($Post, $User);
+ }
+ catch(Post\Exception $Exception){}
+ catch(User\Exception $Exception){}
+ }
+
+ $ResultTemplate = Template\Factory::build('search/result');
+ $ResultTemplate->set('FORM', $form_data);
+ $ResultTemplate->set('SEARCH', $search_data);
+ $ResultTemplate->set('RESULT', [
+ 'LIST' => $posts ?? []
+ ]);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('HTML', $ResultTemplate);
+ $MainTemplate->set('HEAD', [
+ 'NAME' => $Language->text('title_search_results', escapeHTML($search)),
+ 'PERM' => Application::getURL('search/')
+ ]);
+ }
+
+ else {
+ $SearchTemplate = Template\Factory::build('search/main');
+ $SearchTemplate->set('FORM', $form_data);
+ $SearchTemplate->set('SEARCH', $search_data);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('HTML', $SearchTemplate);
+ $MainTemplate->set('HEAD', [
+ 'NAME' => $Language->text('title_search_request'),
+ 'DESC' => 'Wenn du einen bestimmten Beitrag suchst, aber ihn nicht finden kannst, dann kann dir die Suchfunktion bestimmt weiterhelfen.',
+ 'PERM' => Application::getURL('search/')
+ ]);
+ }
+
+ echo $MainTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?> \ No newline at end of file
diff --git a/system/user/list.php b/system/user/list.php
new file mode 100644
index 0000000..1f29473
--- /dev/null
+++ b/system/user/list.php
@@ -0,0 +1,60 @@
+<?php
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+$site_size = Application::get('USER.LIST_SIZE');
+$site_sort = Application::get('USER.LIST_SORT');
+
+$lastSite = ceil($Database->query(sprintf('SELECT COUNT(id) FROM %s', User\Attribute::TABLE))->fetchColumn() / $site_size);
+
+$currentSite = HTTP::GET('site') ?? 1;
+$currentSite = abs(intval($currentSite));
+
+if($currentSite < 1 OR ($currentSite > $lastSite AND $lastSite > 0)) {
+ Application::exit(404);
+}
+
+#===============================================================================
+# TRY: Template\Exception
+#===============================================================================
+try {
+ $execSQL = "SELECT id FROM %s ORDER BY {$site_sort} LIMIT ".(($currentSite-1) * $site_size).", {$site_size}";
+ $userIDs = $Database->query(sprintf($execSQL, User\Attribute::TABLE))->fetchAll($Database::FETCH_COLUMN);
+
+ foreach($userIDs as $userID) {
+ try {
+ $User = User\Factory::build($userID);
+ $ItemTemplate = generateUserItemTemplate($User);
+
+ $users[] = $ItemTemplate;
+ } catch(User\Exception $Exception){}
+ }
+
+ $ListTemplate = Template\Factory::build('user/list');
+ $ListTemplate->set('PAGINATION', [
+ 'THIS' => $currentSite,
+ 'LAST' => $lastSite,
+ 'HTML' => generateUserNaviTemplate($currentSite)
+ ]);
+ $ListTemplate->set('LIST', [
+ 'USERS' => $users ?? []
+ ]);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('HTML', $ListTemplate);
+ $MainTemplate->set('HEAD', [
+ 'NAME' => $Language->text('title_user_overview', $currentSite)
+ ]);
+
+ echo $MainTemplate;
+}
+
+#===============================================================================
+# CATCH: Template\Exception
+#===============================================================================
+catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+}
+?> \ No newline at end of file
diff --git a/system/user/main.php b/system/user/main.php
new file mode 100644
index 0000000..d236b94
--- /dev/null
+++ b/system/user/main.php
@@ -0,0 +1,97 @@
+<?php
+#===============================================================================
+# INCLUDE: Main configuration
+#===============================================================================
+require_once '../../core/application.php';
+
+#===============================================================================
+# TRY: User\Exception
+#===============================================================================
+try {
+ if(Application::get('USER.SLUG_URLS')) {
+ $User = User\Factory::buildBySlug(HTTP::GET('param'));
+ }
+
+ else {
+ $User = User\Factory::build(HTTP::GET('param'));
+ }
+
+ $user_data = generateUserItemData($User);
+
+ #===============================================================================
+ # Add user data for previous and next user if exists
+ #===============================================================================
+ try {
+ $PrevUser = User\Factory::build($User->getPrevID());
+ $user_data['PREV'] = generateUserItemData($PrevUser);
+ } catch(User\Exception $Exception){}
+
+ try {
+ $NextUser = User\Factory::build($User->getNextID());
+ $user_data['NEXT'] = generateUserItemData($NextUser);
+ } catch(User\Exception $Exception){}
+
+ #===============================================================================
+ # TRY: Template\Exception
+ #===============================================================================
+ try {
+ #===============================================================================
+ # TRY: PDOException
+ #===============================================================================
+ try {
+ $PostCountStatement = $Database->query(sprintf('SELECT COUNT(*) FROM %s WHERE user = %d', Post\Attribute::TABLE, $User->getID()));
+ $PageCountStatement = $Database->query(sprintf('SELECT COUNT(*) FROM %s WHERE user = %d', Page\Attribute::TABLE, $User->getID()));
+ }
+
+ #===============================================================================
+ # CATCH: PDOException
+ #===============================================================================
+ catch(PDOException $Exception) {
+ exit($Exception->getMessage());
+ }
+
+ $UserTemplate = Template\Factory::build('user/main');
+ $UserTemplate->set('USER', $user_data);
+ $UserTemplate->set('COUNT', [
+ 'POST' => $PostCountStatement->fetchColumn(),
+ 'PAGE' => $PageCountStatement->fetchColumn()
+ ]);
+
+ $MainTemplate = Template\Factory::build('main');
+ $MainTemplate->set('HTML', $UserTemplate);
+ $MainTemplate->set('HEAD', [
+ 'NAME' => $user_data['ATTR']['FULLNAME'],
+ 'DESC' => cut(removeLineBreaksAndTabs(removeHTML($User->getHTML()), ' '), Application::get('USER.DESCRIPTION_SIZE')),
+ 'PERM' => $User->getURL(),
+ 'OG_IMAGES' => $User->getFiles()
+ ]);
+
+ echo $MainTemplate;
+ }
+
+ #===============================================================================
+ # CATCH: Template\Exception
+ #===============================================================================
+ catch(Template\Exception $Exception) {
+ $Exception->defaultHandler();
+ }
+}
+
+#===============================================================================
+# CATCH: User\Exception
+#===============================================================================
+catch(User\Exception $Exception) {
+ try {
+ if(Application::get('USER.SLUG_URLS') === FALSE) {
+ $User = User\Factory::buildBySlug(HTTP::GET('param'));
+ } else {
+ $User = User\Factory::build(HTTP::GET('param'));
+ }
+
+ HTTP::redirect($User->getURL());
+ }
+
+ catch(User\Exception $Exception) {
+ Application::exit(404);
+ }
+} \ No newline at end of file
diff --git a/template/admin/html/403.php b/template/admin/html/403.php
new file mode 100644
index 0000000..aebddb7
--- /dev/null
+++ b/template/admin/html/403.php
@@ -0,0 +1,2 @@
+<h1><i class="fa fa-exclamation-triangle"></i><?=$Language->template('403_heading_text')?></h1>
+<p><?=$Language->template('403_heading_desc')?></p>
diff --git a/template/admin/html/404.php b/template/admin/html/404.php
new file mode 100644
index 0000000..4f841b6
--- /dev/null
+++ b/template/admin/html/404.php
@@ -0,0 +1,2 @@
+<h1><i class="fa fa-exclamation-triangle"></i><?=$Language->template('404_heading_text')?></h1>
+<p><?=$Language->template('404_heading_desc')?></p> \ No newline at end of file
diff --git a/template/admin/html/auth.php b/template/admin/html/auth.php
new file mode 100644
index 0000000..a33be5d
--- /dev/null
+++ b/template/admin/html/auth.php
@@ -0,0 +1,30 @@
+<h1><?=$Language->template('authentication_text')?></h1>
+<p><?=$Language->template('authentication_desc')?></p>
+
+<?php if(isset($FORM['INFO']['LIST'])): ?>
+ <?php foreach($FORM['INFO']['LIST'] as $message): ?>
+ <div class="red"><?=$message?></div>
+ <?php endforeach; ?>
+<?php endif; ?>
+
+<form action="" method="POST">
+ <input type="hidden" name="token" value="<?=Application::getSecurityToken()?>" />
+
+ <section class="flex">
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-user-secret"></i></div>
+ <div class="form-label-flex"><label for="L_USERNAME"><?=$Language->template('LABEL_USERNAME')?></label></div>
+ <div class="form-field-flex"><input id="L_USERNAME" name="username" value="<?=escapeHTML($FORM['DATA']['USERNAME'])?>" /></div>
+ </section>
+ </section>
+ <section class="flex">
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-key"></i></div>
+ <div class="form-label-flex"><label for="L_PASSWORD"><?=$Language->template('LABEL_PASSWORD')?></label></div>
+ <div class="form-field-flex"><input type="password" id="L_PASSWORD" name="password" /></div>
+ </section>
+ </section>
+ <section class="flex flex-padding background">
+ <input type="submit" name="auth" value="Auth" />
+ </section>
+</form> \ No newline at end of file
diff --git a/template/admin/html/database.php b/template/admin/html/database.php
new file mode 100644
index 0000000..92c5103
--- /dev/null
+++ b/template/admin/html/database.php
@@ -0,0 +1,26 @@
+<h1><i class="fa fa-database"></i><?=$Language->template('overview_database_text')?></h1>
+<p><?=$Language->template('overview_database_desc')?></p>
+
+<?php if(isset($FORM['INFO'])): ?>
+ <?php foreach($FORM['INFO'] as $message): ?>
+ <div class="red"><?=$message?></div>
+ <?php endforeach; ?>
+<?php endif; ?>
+
+<form action="" method="POST">
+ <input type="hidden" name="token" value="<?=$FORM['TOKEN']?>" />
+
+ <section class="flex flex-padding">
+ <textarea id="content-editor" placeholder="<?=$Language->template('database_warning')?>" name="command"><?=escapeHTML($FORM['COMMAND'])?></textarea>
+ </section>
+
+<?php if(isset($FORM['RESULT'])): ?>
+ <section class="flex flex-padding background flex-direction-column">
+ <pre id="database-result"><?=escapeHTML($FORM['RESULT'])?></pre>
+ </section>
+<?php endif; ?>
+
+ <section class="flex flex-padding background">
+ <input type="submit" name="execute" value="Execute" />
+ </section>
+</form> \ No newline at end of file
diff --git a/template/admin/html/home.php b/template/admin/html/home.php
new file mode 100644
index 0000000..6701f35
--- /dev/null
+++ b/template/admin/html/home.php
@@ -0,0 +1,34 @@
+<h1><i class="fa fa-dashboard"></i><?=$Language->template('overview_dashboard_text')?></h1>
+<p><?=$Language->template('overview_dashboard_desc')?></p>
+
+<h2><i class="fa fa-newspaper-o"></i><?=$Language->template('last_post')?></h2>
+<p><strong><?=$Language->text('posts')?>:</strong> <?=$COUNT['POST']?> | <a href="<?=Application::getAdminURL('post/')?>"><?=$Language->text('post_overview')?></a> | <a href="<?=Application::getAdminURL('post/insert.php')?>"><?=$Language->text('insert')?></a></p>
+<?php if(!empty($LAST['POST'])): ?>
+ <ul class="item-list">
+ <?=$LAST['POST']?>
+ </ul>
+<?php else: ?>
+ <p><em><?=$Language->template('home_no_posts')?></em></p>
+<?php endif; ?>
+
+<h2><i class="fa fa-file-text-o"></i><?=$Language->template('last_page')?></h2>
+<p><strong><?=$Language->text('pages')?>:</strong> <?=$COUNT['PAGE']?> | <a href="<?=Application::getAdminURL('page/')?>"><?=$Language->text('page_overview')?></a> | <a href="<?=Application::getAdminURL('page/insert.php')?>"><?=$Language->text('insert')?></a></p>
+
+<?php if(!empty($LAST['PAGE'])): ?>
+ <ul class="item-list">
+ <?=$LAST['PAGE']?>
+ </ul>
+<?php else: ?>
+ <p><em><?=$Language->template('home_no_pages')?></em></p>
+<?php endif; ?>
+
+<h2><i class="fa fa-user"></i><?=$Language->template('last_user')?></h2>
+<p><strong><?=$Language->text('users')?>:</strong> <?=$COUNT['USER']?> | <a href="<?=Application::getAdminURL('user/')?>"><?=$Language->text('user_overview')?></a> | <a href="<?=Application::getAdminURL('user/insert.php')?>"><?=$Language->text('insert')?></a></p>
+
+<?php if(!empty($LAST['USER'])): ?>
+ <ul class="item-list">
+ <?=$LAST['USER']?>
+ </ul>
+<?php else: ?>
+ <p><em><?=$Language->template('home_no_users')?></em></p>
+<?php endif; ?>
diff --git a/template/admin/html/main.php b/template/admin/html/main.php
new file mode 100644
index 0000000..d2fba38
--- /dev/null
+++ b/template/admin/html/main.php
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html lang="<?=$BLOGMETA['LANG']?>">
+<head>
+ <meta charset="UTF-8" />
+ <meta name="referrer" content="origin-when-crossorigin" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <link rel="stylesheet" href="<?=Application::getTemplateURL('rsrc/main.css')?>" />
+ <script src="<?=Application::getTemplateURL('rsrc/main.js')?>"></script>
+ <title><?=escapeHTML($NAME)?> | Administration</title>
+</head>
+<body>
+ <header id="main-header">
+ <section class="header-line">
+ <div class="header-content">
+ <a href="<?=Application::getAdminURL()?>"><img id="header-logo" src="<?=Application::getTemplateURL('rsrc/icon-public-domain.svg')?>" alt="Administration" /></a>
+ <div id="header-text">Administration</div>
+ <div id="header-desc">PHP7 blogging application by <span>Nerdmind</span>!</div>
+ </div>
+ </section>
+ <section class="header-line">
+ <div class="header-content">
+ <nav id="main-navi">
+ <?php if(Application::isAuthenticated()): ?>
+ <ul>
+ <li><a href="<?=Application::getAdminURL()?>" title="<?=$Language->template('overview_dashboard_text')?>"><i class="fa fa-dashboard"></i><span>Dashboard</span></a></li>
+ <li><a href="<?=Application::getAdminURL('post/')?>" title="<?=$Language->text('post_overview')?>"><i class="fa fa-newspaper-o"></i><span><?=$Language->text('posts')?></span></a></li>
+ <li><a href="<?=Application::getAdminURL('page/')?>" title="<?=$Language->text('page_overview')?>"><i class="fa fa-file-text-o"></i><span><?=$Language->text('pages')?></span></a></li>
+ <li><a href="<?=Application::getAdminURL('user/')?>" title="<?=$Language->text('user_overview')?>"><i class="fa fa-user"></i><span><?=$Language->text('users')?></span></a></li>
+ <li><a href="<?=Application::getAdminURL('database.php')?>" title="<?=$Language->template('overview_database_text')?>"><i class="fa fa-database"></i><span><?=$Language->template('overview_database_text')?></span></a></li>
+ </ul>
+ <ul>
+ <li><a href="<?=Application::getAdminURL('auth.php?action=logout&amp;token='.Application::getSecurityToken())?>"><i class="fa fa-sign-out"></i><span>Logout</span></a></li>
+ </ul>
+ <?php else: ?>
+ <ul>
+ <li><a href="<?=Application::getAdminURL('auth.php')?>"><i class="fa fa-sign-in"></i><span>Login</span></a></li>
+ </ul>
+ <?php endif; ?>
+ </nav>
+ </div>
+ </section>
+ </header>
+ <section id="main-content">
+ <main>
+ <?=$HTML?>
+ </main>
+ <footer id="main-footer">
+ <i class="fa fa-bug"></i> Report bugs via email to <a href="mailto:Thomas Lange <code@nerdmind.de>">code@nerdmind.de</a>!
+ </footer>
+ </section>
+</body>
+</html> \ No newline at end of file
diff --git a/template/admin/html/page/delete.php b/template/admin/html/page/delete.php
new file mode 100644
index 0000000..95451d8
--- /dev/null
+++ b/template/admin/html/page/delete.php
@@ -0,0 +1,4 @@
+<h1><i class="fa fa-trash-o"></i><?=$Language->text('delete_page')?></h1>
+<p><?=$Language->template('delete_page_desc')?></p>
+
+<?=$HTML?> \ No newline at end of file
diff --git a/template/admin/html/page/form.php b/template/admin/html/page/form.php
new file mode 100644
index 0000000..cf87f82
--- /dev/null
+++ b/template/admin/html/page/form.php
@@ -0,0 +1,91 @@
+<?php if(isset($FORM['INFO'])): ?>
+ <?php foreach($FORM['INFO'] as $message): ?>
+ <div class="red"><?=$message?></div>
+ <?php endforeach; ?>
+<?php endif; ?>
+
+<form action="" method="POST">
+ <input type="hidden" name="token" value="<?=$FORM['TOKEN']?>" />
+
+<?php if($FORM['TYPE'] !== 'DELETE'): ?>
+ <section class="flex flex-responsive">
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-database"></i></div>
+ <div class="form-label-flex"><label for="L_ID">ID</label></div>
+ <div class="form-field-flex"><input<?=($FORM['TYPE'] === 'UPDATE') ? ' disabled="disabled"' : '';?> id="L_ID" name="id" placeholder="[AUTO_INCREMENT]" value="<?=escapeHTML($FORM['DATA']['ID'])?>" /></div>
+ </section>
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-user"></i></div>
+ <div class="form-label-flex"><label for="L_USER"><?=$Language->template('LABEL_USER')?></label></div>
+ <div class="form-field-flex">
+ <select id="L_USER" name="user">
+ <?php foreach($FORM['USER_LIST'] as $user): ?>
+ <option value="<?=$user['ID']?>"<?=($FORM['DATA']['USER'] === $user['ID']) ? ' selected' : '' ?>><?=escapeHTML($user['FULLNAME'])?> [<?=$user['USERNAME']?>]</option>
+ <?php endforeach; ?>
+ </select>
+ </div>
+ </section>
+ </section>
+ <section class="flex flex-responsive">
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-file-text-o"></i></div>
+ <div class="form-label-flex"><label for="L_NAME"><?=$Language->template('LABEL_NAME')?></label></div>
+ <div class="form-field-flex"><input id="L_NAME" name="name" value="<?=escapeHTML($FORM['DATA']['NAME'])?>" /></div>
+ </section>
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-link"></i></div>
+ <div class="form-label-flex"><label for="L_SLUG"><?=$Language->template('LABEL_SLUG')?></label></div>
+ <div class="form-field-flex"><input id="L_SLUG" name="slug" value="<?=escapeHTML($FORM['DATA']['SLUG'])?>" /></div>
+ </section>
+ </section>
+ <section class="flex flex-responsive">
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-clock-o"></i></div>
+ <div class="form-label-flex"><label for="L_TIME_INSERT"><?=$Language->template('LABEL_INSERT')?></label></div>
+ <div class="form-field-flex"><input id="L_TIME_INSERT" name="time_insert" placeholder="[YYYY-MM-DD HH:II:SS]" value="<?=escapeHTML($FORM['DATA']['TIME_INSERT'])?>" /></div>
+ </section>
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-clock-o"></i></div>
+ <div class="form-label-flex"><label for="L_TIME_UPDATE"><?=$Language->template('LABEL_UPDATE')?></label></div>
+ <div class="form-field-flex"><input id="L_TIME_UPDATE" name="time_update" placeholder="<?=escapeHTML($FORM['DATA']['TIME_UPDATE'] ? $FORM['DATA']['TIME_UPDATE'] : '[CURRENT_TIMESTAMP]')?>" value="" /></div>
+ </section>
+ </section>
+ <section class="flex flex-padding">
+ <textarea id="content-editor" name="body" placeholder="[…]"><?=escapeHTML($FORM['DATA']['BODY'])?></textarea>
+ </section>
+ <section class="flex flex-padding background">
+ <ul class="button-list emoticons">
+ <?php foreach(getEmoticons() as $emoticon => $data):?>
+ <li onmousedown="emoticonReplace('<?=$emoticon?>')" title="<?=$data[1]?>"><?=$data[0]?></li>
+ <?php endforeach; ?>
+ </ul>
+ </section>
+ <section class="flex flex-padding background">
+ <ul class="button-list markdown">
+ <li onmousedown="markdownReplace('bold');" class="fa fa-bold" title="Bold"></li>
+ <li onmousedown="markdownReplace('italic');" class="fa fa-italic" title="Italic"></li>
+ <li onmousedown="markdownReplace('header');" class="fa fa-header" title="Heading"></li>
+ <li onmousedown="markdownReplace('link');" class="fa fa-link" title="Link"></li>
+ <li onmousedown="markdownReplace('image');" class="fa fa-picture-o" title="Image"></li>
+ <li onmousedown="markdownReplace('code');" class="fa fa-code" title="Code"></li>
+ <li onmousedown="markdownReplace('quote');" class="fa fa-quote-right" title="Quote"></li>
+ <li onmousedown="markdownReplace('list_ul');" class="fa fa-list-ul" title="List [unordered]"></li>
+ <li onmousedown="markdownReplace('list_ol');" class="fa fa-list-ol" title="List [ordered]"></li>
+ </ul>
+ </section>
+<?php else: ?>
+ <section class="flex flex-padding background flex-direction-column">
+ <?=$HTML?>
+ </section>
+<?php endif; ?>
+
+ <section class="flex flex-padding background">
+ <?php if($FORM['TYPE'] === 'INSERT'): ?>
+ <input type="submit" name="insert" value="<?=$Language->text('insert')?>" />
+ <?php elseif($FORM['TYPE'] === 'UPDATE'): ?>
+ <input type="submit" name="update" value="<?=$Language->text('update')?>" />
+ <?php elseif($FORM['TYPE'] === 'DELETE'): ?>
+ <input type="submit" name="delete" value="<?=$Language->text('delete')?>" onclick="return confirm('<?=$Language->template('sure')?>')" />
+ <?php endif; ?>
+ </section>
+</form> \ No newline at end of file
diff --git a/template/admin/html/page/index.php b/template/admin/html/page/index.php
new file mode 100644
index 0000000..4d090ca
--- /dev/null
+++ b/template/admin/html/page/index.php
@@ -0,0 +1,10 @@
+<h1><i class="fa fa-file-text-o"></i><?=$Language->text('page_overview')?><a class="brackets" href="<?=Application::getAdminURL("page/insert.php")?>"><?=$Language->text('insert')?></a></h1>
+<p><?=$Language->template('overview_page_desc')?></p>
+
+<ul class="item-list">
+ <?php foreach($LIST['PAGES'] as $page): ?>
+ <?php echo $page; ?>
+ <?php endforeach; ?>
+</ul>
+
+<?=$PAGINATION['HTML']?> \ No newline at end of file
diff --git a/template/admin/html/page/insert.php b/template/admin/html/page/insert.php
new file mode 100644
index 0000000..d45d79c
--- /dev/null
+++ b/template/admin/html/page/insert.php
@@ -0,0 +1,4 @@
+<h1><i class="fa fa-file-text-o"></i><?=$Language->text('insert_page')?></h1>
+<p><?=$Language->template('insert_page_desc')?></p>
+
+<?=$HTML?> \ No newline at end of file
diff --git a/template/admin/html/page/item.php b/template/admin/html/page/item.php
new file mode 100644
index 0000000..e1e67b7
--- /dev/null
+++ b/template/admin/html/page/item.php
@@ -0,0 +1,16 @@
+<li class="content">
+ <header>
+ <h2><i class="fa fa-file-text-o"></i><?=escapeHTML($PAGE['ATTR']['NAME'])?><span>#<?=$PAGE['ID']?></span></h2>
+ <div><a class="brackets" href="<?=Application::getAdminURL("user/update.php?id={$USER['ID']}")?>"><?=escapeHTML($USER['ATTR']['FULLNAME'])?></a></div>
+ </header>
+ <article>
+ <p><?=excerpt($PAGE['BODY']['HTML'])?></p>
+ </article>
+ <footer>
+ <ul>
+ <li><a href="<?=$PAGE['URL']?>" target="_blank" title="<?=$Language->text('select_page')?>"><i class="fa fa-external-link"></i></a></li>
+ <li><a href="<?=Application::getAdminURL("page/update.php?id={$PAGE['ID']}")?>" title="<?=$Language->text('update_page')?>"><i class="fa fa-pencil-square-o"></i></a></li>
+ <li><a href="<?=Application::getAdminURL("page/delete.php?id={$PAGE['ID']}")?>" title="<?=$Language->text('delete_page')?>"><i class="fa fa-trash-o"></i></a></li>
+ </ul>
+ </footer>
+</li> \ No newline at end of file
diff --git a/template/admin/html/page/update.php b/template/admin/html/page/update.php
new file mode 100644
index 0000000..d15d784
--- /dev/null
+++ b/template/admin/html/page/update.php
@@ -0,0 +1,4 @@
+<h1><i class="fa fa-file-text-o"></i><?=$Language->text('update_page')?></h1>
+<p><?=$Language->template('update_page_desc')?></p>
+
+<?=$HTML?> \ No newline at end of file
diff --git a/template/admin/html/pagination.php b/template/admin/html/pagination.php
new file mode 100644
index 0000000..31b5378
--- /dev/null
+++ b/template/admin/html/pagination.php
@@ -0,0 +1,45 @@
+<section id="site-navi">
+ <?php if($THIS > 1): ?>
+ <div><a href="<?=sprintf($HREF, $THIS-1)?>"><i class="fa fa-arrow-left"></i></a></div>
+ <?php else: ?>
+ <div><a class="disabled"><i class="fa fa-arrow-left"></i></a></div>
+ <?php endif; ?>
+
+ <section>
+ <div>
+ <ol>
+ <?php for($currentItem = 1; $currentItem <= $LAST; ++$currentItem): ?>
+ <?php
+ $href = sprintf($HREF, $currentItem);
+ $class = NULL;
+ $currentItemHTML = $currentItem;
+ if($currentItem === $THIS) {
+ $class = ' class="active"';
+ }
+
+ echo '<li'.$class.'><a href="'.$href.'">'.$currentItemHTML.'</a></li>';
+ ?>
+
+ <?php endfor; ?>
+ </ol>
+ </div>
+ </section>
+
+ <?php if($THIS < $LAST): ?>
+ <div><a href="<?=sprintf($HREF, $THIS+1)?>"><i class="fa fa-arrow-right"></i></a></div>
+ <?php else: ?>
+ <div><a class="disabled"><i class="fa fa-arrow-right"></i></a></div>
+ <?php endif; ?>
+</section>
+
+<script>
+ var prevPageURL = <?php echo json_encode($THIS > 1 ? sprintf($HREF, $THIS-1) : FALSE); ?>;
+ var nextPageURL = <?php echo json_encode($THIS < $LAST ? sprintf($HREF, $THIS+1) : FALSE); ?>;
+
+ document.addEventListener('keyup', function(event) {
+ if(!event.ctrlKey && !event.shiftKey) {
+ (event.keyCode === 37 && prevPageURL) && (window.location.href = prevPageURL);
+ (event.keyCode === 39 && nextPageURL) && (window.location.href = nextPageURL);
+ }
+ }, false)
+</script> \ No newline at end of file
diff --git a/template/admin/html/post/delete.php b/template/admin/html/post/delete.php
new file mode 100644
index 0000000..9be4566
--- /dev/null
+++ b/template/admin/html/post/delete.php
@@ -0,0 +1,4 @@
+<h1><i class="fa fa-trash-o"></i><?=$Language->text('delete_post')?></h1>
+<p><?=$Language->template('delete_post_desc')?></p>
+
+<?=$HTML?> \ No newline at end of file
diff --git a/template/admin/html/post/form.php b/template/admin/html/post/form.php
new file mode 100644
index 0000000..cba4aa8
--- /dev/null
+++ b/template/admin/html/post/form.php
@@ -0,0 +1,91 @@
+<?php if(isset($FORM['INFO'])): ?>
+ <?php foreach($FORM['INFO'] as $message): ?>
+ <div class="red"><?=$message?></div>
+ <?php endforeach; ?>
+<?php endif; ?>
+
+<form action="" method="POST">
+ <input type="hidden" name="token" value="<?=$FORM['TOKEN']?>" />
+
+<?php if($FORM['TYPE'] !== 'DELETE'): ?>
+ <section class="flex flex-responsive">
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-database"></i></div>
+ <div class="form-label-flex"><label for="L_ID">ID</label></div>
+ <div class="form-field-flex"><input<?=($FORM['TYPE'] === 'UPDATE') ? ' disabled="disabled"' : '';?> id="L_ID" name="id" placeholder="[AUTO_INCREMENT]" value="<?=escapeHTML($FORM['DATA']['ID'])?>" /></div>
+ </section>
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-user"></i></div>
+ <div class="form-label-flex"><label for="L_USER"><?=$Language->template('LABEL_USER')?></label></div>
+ <div class="form-field-flex">
+ <select id="L_USER" name="user">
+ <?php foreach($FORM['USER_LIST'] as $user): ?>
+ <option value="<?=$user['ID']?>"<?=($FORM['DATA']['USER'] === $user['ID']) ? ' selected' : '' ?>><?=escapeHTML($user['FULLNAME'])?> [<?=$user['USERNAME']?>]</option>
+ <?php endforeach; ?>
+ </select>
+ </div>
+ </section>
+ </section>
+ <section class="flex flex-responsive">
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-newspaper-o"></i></div>
+ <div class="form-label-flex"><label for="L_NAME"><?=$Language->template('LABEL_NAME')?></label></div>
+ <div class="form-field-flex"><input id="L_NAME" name="name" value="<?=escapeHTML($FORM['DATA']['NAME'])?>" /></div>
+ </section>
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-link"></i></div>
+ <div class="form-label-flex"><label for="L_SLUG"><?=$Language->template('LABEL_SLUG')?></label></div>
+ <div class="form-field-flex"><input id="L_SLUG" name="slug" value="<?=escapeHTML($FORM['DATA']['SLUG'])?>" /></div>
+ </section>
+ </section>
+ <section class="flex flex-responsive">
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-clock-o"></i></div>
+ <div class="form-label-flex"><label for="L_TIME_INSERT"><?=$Language->template('LABEL_INSERT')?></label></div>
+ <div class="form-field-flex"><input id="L_TIME_INSERT" name="time_insert" placeholder="[YYYY-MM-DD HH:II:SS]" value="<?=escapeHTML($FORM['DATA']['TIME_INSERT'])?>" /></div>
+ </section>
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-clock-o"></i></div>
+ <div class="form-label-flex"><label for="L_TIME_UPDATE"><?=$Language->template('LABEL_UPDATE')?></label></div>
+ <div class="form-field-flex"><input id="L_TIME_UPDATE" name="time_update" placeholder="<?=escapeHTML($FORM['DATA']['TIME_UPDATE'] ? $FORM['DATA']['TIME_UPDATE'] : '[CURRENT_TIMESTAMP]')?>" value="" /></div>
+ </section>
+ </section>
+ <section class="flex flex-padding">
+ <textarea id="content-editor" name="body" placeholder="[…]"><?=escapeHTML($FORM['DATA']['BODY'])?></textarea>
+ </section>
+ <section class="flex flex-padding background flex-emoticons">
+ <ul class="button-list emoticons">
+ <?php foreach(getEmoticons() as $emoticon => $data):?>
+ <li onmousedown="emoticonReplace('<?=$emoticon?>')" title="<?=$data[1]?>"><?=$data[0]?></li>
+ <?php endforeach; ?>
+ </ul>
+ </section>
+ <section class="flex flex-padding background">
+ <ul class="button-list markdown">
+ <li onmousedown="markdownReplace('bold');" class="fa fa-bold" title="Bold"></li>
+ <li onmousedown="markdownReplace('italic');" class="fa fa-italic" title="Italic"></li>
+ <li onmousedown="markdownReplace('header');" class="fa fa-header" title="Heading"></li>
+ <li onmousedown="markdownReplace('link');" class="fa fa-link" title="Link"></li>
+ <li onmousedown="markdownReplace('image');" class="fa fa-picture-o" title="Image"></li>
+ <li onmousedown="markdownReplace('code');" class="fa fa-code" title="Code"></li>
+ <li onmousedown="markdownReplace('quote');" class="fa fa-quote-right" title="Quote"></li>
+ <li onmousedown="markdownReplace('list_ul');" class="fa fa-list-ul" title="List [unordered]"></li>
+ <li onmousedown="markdownReplace('list_ol');" class="fa fa-list-ol" title="List [ordered]"></li>
+ </ul>
+ </section>
+<?php else: ?>
+ <section class="flex flex-padding background flex-direction-column">
+ <?=$HTML?>
+ </section>
+<?php endif; ?>
+
+ <section class="flex flex-padding background">
+ <?php if($FORM['TYPE'] === 'INSERT'): ?>
+ <input type="submit" name="insert" value="<?=$Language->text('insert')?>" />
+ <?php elseif($FORM['TYPE'] === 'UPDATE'): ?>
+ <input type="submit" name="update" value="<?=$Language->text('update')?>" />
+ <?php elseif($FORM['TYPE'] === 'DELETE'): ?>
+ <input type="submit" name="delete" value="<?=$Language->text('delete')?>" onclick="return confirm('<?=$Language->template('sure')?>')" />
+ <?php endif; ?>
+ </section>
+</form> \ No newline at end of file
diff --git a/template/admin/html/post/index.php b/template/admin/html/post/index.php
new file mode 100644
index 0000000..481cafe
--- /dev/null
+++ b/template/admin/html/post/index.php
@@ -0,0 +1,10 @@
+<h1><i class="fa fa-newspaper-o"></i><?=$Language->text('post_overview')?><a class="brackets" href="<?=Application::getAdminURL("post/insert.php")?>"><?=$Language->text('insert')?></a></h1>
+<p><?=$Language->template('overview_post_desc')?></p>
+
+<ul class="item-list">
+ <?php foreach($LIST['POSTS'] as $post): ?>
+ <?php echo $post; ?>
+ <?php endforeach; ?>
+</ul>
+
+<?=$PAGINATION['HTML']?> \ No newline at end of file
diff --git a/template/admin/html/post/insert.php b/template/admin/html/post/insert.php
new file mode 100644
index 0000000..df4b26d
--- /dev/null
+++ b/template/admin/html/post/insert.php
@@ -0,0 +1,4 @@
+<h1><i class="fa fa-newspaper-o"></i><?=$Language->text('insert_post')?></h1>
+<p><?=$Language->template('insert_post_desc')?></p>
+
+<?=$HTML?> \ No newline at end of file
diff --git a/template/admin/html/post/item.php b/template/admin/html/post/item.php
new file mode 100644
index 0000000..1b942bd
--- /dev/null
+++ b/template/admin/html/post/item.php
@@ -0,0 +1,16 @@
+<li class="content">
+ <header>
+ <h2><i class="fa fa-newspaper-o"></i><?=escapeHTML($POST['ATTR']['NAME'])?><span>#<?=$POST['ID']?></span></h2>
+ <div><a class="brackets" href="<?=Application::getAdminURL("user/update.php?id={$USER['ID']}")?>"><?=escapeHTML($USER['ATTR']['FULLNAME'])?></a></div>
+ </header>
+ <article>
+ <p><?=excerpt($POST['BODY']['HTML'])?></p>
+ </article>
+ <footer>
+ <ul>
+ <li><a href="<?=$POST['URL']?>" target="_blank" title="<?=$Language->text('select_post')?>"><i class="fa fa-external-link"></i></a></li>
+ <li><a href="<?=Application::getAdminURL("post/update.php?id={$POST['ID']}")?>" title="<?=$Language->text('update_post')?>"><i class="fa fa-pencil-square-o"></i></a></li>
+ <li><a href="<?=Application::getAdminURL("post/delete.php?id={$POST['ID']}")?>" title="<?=$Language->text('delete_post')?>"><i class="fa fa-trash-o"></i></a></li>
+ </ul>
+ </footer>
+</li> \ No newline at end of file
diff --git a/template/admin/html/post/update.php b/template/admin/html/post/update.php
new file mode 100644
index 0000000..baa119e
--- /dev/null
+++ b/template/admin/html/post/update.php
@@ -0,0 +1,4 @@
+<h1><i class="fa fa-newspaper-o"></i><?=$Language->text('update_post')?></h1>
+<p><?=$Language->template('update_post_desc')?></p>
+
+<?=$HTML?> \ No newline at end of file
diff --git a/template/admin/html/user/delete.php b/template/admin/html/user/delete.php
new file mode 100644
index 0000000..dac4185
--- /dev/null
+++ b/template/admin/html/user/delete.php
@@ -0,0 +1,6 @@
+<h1><i class="fa fa-trash-o"></i><?=$Language->text('delete_user')?></h1>
+<p><?=$Language->template('delete_user_desc')?></p>
+
+<p class="red"><?=$Language->template('delete_user_warning')?></p>
+
+<?=$HTML?> \ No newline at end of file
diff --git a/template/admin/html/user/form.php b/template/admin/html/user/form.php
new file mode 100644
index 0000000..c284187
--- /dev/null
+++ b/template/admin/html/user/form.php
@@ -0,0 +1,97 @@
+<?php if(isset($FORM['INFO'])): ?>
+ <?php foreach($FORM['INFO'] as $message): ?>
+ <div class="red"><?=$message?></div>
+ <?php endforeach; ?>
+<?php endif; ?>
+
+<form action="" method="POST">
+ <input type="hidden" name="token" value="<?=$FORM['TOKEN']?>" />
+
+<?php if($FORM['TYPE'] !== 'DELETE'): ?>
+ <section class="flex flex-responsive">
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-database"></i></div>
+ <div class="form-label-flex"><label for="L_ID">ID</label></div>
+ <div class="form-field-flex"><input<?=($FORM['TYPE'] === 'UPDATE') ? ' disabled="disabled"' : '';?> id="L_ID" name="id" placeholder="[AUTO_INCREMENT]" value="<?=escapeHTML($FORM['DATA']['ID'])?>" /></div>
+ </section>
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-key"></i></div>
+ <div class="form-label-flex"><label for="L_PASSWORD"><?=$Language->template('LABEL_PASSWORD')?></label></div>
+ <div class="form-field-flex"><input id="L_PASSWORD" name="password" placeholder="[NO CHANGE]" value="<?=escapeHTML($FORM['DATA']['PASSWORD'])?>" type="password" /></div>
+ </section>
+ </section>
+ <section class="flex flex-responsive">
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-user"></i></div>
+ <div class="form-label-flex"><label for="L_FULLNAME"><?=$Language->template('LABEL_FULLNAME')?></label></div>
+ <div class="form-field-flex"><input id="L_FULLNAME" name="fullname" value="<?=escapeHTML($FORM['DATA']['FULLNAME'])?>" /></div>
+ </section>
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-envelope-o"></i></div>
+ <div class="form-label-flex"><label for="L_MAILADDR"><?=$Language->template('LABEL_MAILADDR')?></label></div>
+ <div class="form-field-flex"><input id="L_MAILADDR" name="mailaddr" value="<?=escapeHTML($FORM['DATA']['MAILADDR'])?>" /></div>
+ </section>
+ </section>
+ <section class="flex flex-responsive">
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-user-secret"></i></div>
+ <div class="form-label-flex"><label for="L_USERNAME"><?=$Language->template('LABEL_USERNAME')?></label></div>
+ <div class="form-field-flex"><input id="L_USERNAME" name="username" value="<?=escapeHTML($FORM['DATA']['USERNAME'])?>" /></div>
+ </section>
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-link"></i></div>
+ <div class="form-label-flex"><label for="L_SLUG"><?=$Language->template('LABEL_SLUG')?></label></div>
+ <div class="form-field-flex"><input id="L_SLUG" name="slug" value="<?=escapeHTML($FORM['DATA']['SLUG'])?>" /></div>
+ </section>
+ </section>
+ <section class="flex flex-responsive">
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-clock-o"></i></div>
+ <div class="form-label-flex"><label for="L_TIME_INSERT"><?=$Language->template('LABEL_INSERT')?></label></div>
+ <div class="form-field-flex"><input id="L_TIME_INSERT" name="time_insert" placeholder="[YYYY-MM-DD HH:II:SS]" value="<?=escapeHTML($FORM['DATA']['TIME_INSERT'])?>" /></div>
+ </section>
+ <section>
+ <div class="form-icon-flex"><i class="fa fa-clock-o"></i></div>
+ <div class="form-label-flex"><label for="L_TIME_UPDATE"><?=$Language->template('LABEL_UPDATE')?></label></div>
+ <div class="form-field-flex"><input id="L_TIME_UPDATE" name="time_update" placeholder="<?=escapeHTML($FORM['DATA']['TIME_UPDATE'] ? $FORM['DATA']['TIME_UPDATE'] : '[CURRENT_TIMESTAMP]')?>" value="" /></div>
+ </section>
+ </section>
+ <section class="flex flex-padding">
+ <textarea id="content-editor" name="body" placeholder="[…]"><?=escapeHTML($FORM['DATA']['BODY'])?></textarea>
+ </section>
+ <section class="flex flex-padding background">
+ <ul class="button-list emoticons">
+ <?php foreach(getEmoticons() as $emoticon => $data):?>
+ <li onmousedown="emoticonReplace('<?=$emoticon?>')" title="<?=$data[1]?>"><?=$data[0]?></li>
+ <?php endforeach; ?>
+ </ul>
+ </section>
+ <section class="flex flex-padding background">
+ <ul class="button-list markdown">
+ <li onmousedown="markdownReplace('bold');" class="fa fa-bold" title="Bold"></li>
+ <li onmousedown="markdownReplace('italic');" class="fa fa-italic" title="Italic"></li>
+ <li onmousedown="markdownReplace('header');" class="fa fa-header" title="Heading"></li>
+ <li onmousedown="markdownReplace('link');" class="fa fa-link" title="Link"></li>
+ <li onmousedown="markdownReplace('image');" class="fa fa-picture-o" title="Image"></li>
+ <li onmousedown="markdownReplace('code');" class="fa fa-code" title="Code"></li>
+ <li onmousedown="markdownReplace('quote');" class="fa fa-quote-right" title="Quote"></li>
+ <li onmousedown="markdownReplace('list_ul');" class="fa fa-list-ul" title="List [unordered]"></li>
+ <li onmousedown="markdownReplace('list_ol');" class="fa fa-list-ol" title="List [ordered]"></li>
+ </ul>
+ </section>
+<?php else: ?>
+ <section class="flex flex-padding background flex-direction-column">
+ <?=$HTML?>
+ </section>
+<?php endif; ?>
+
+ <section class="flex flex-padding background">
+ <?php if($FORM['TYPE'] === 'INSERT'): ?>
+ <input type="submit" name="insert" value="<?=$Language->text('insert')?>" />
+ <?php elseif($FORM['TYPE'] === 'UPDATE'): ?>
+ <input type="submit" name="update" value="<?=$Language->text('update')?>" />
+ <?php elseif($FORM['TYPE'] === 'DELETE'): ?>
+ <input type="submit" name="delete" value="<?=$Language->text('delete')?>" onclick="return confirm('<?=$Language->template('sure')?>')" />
+ <?php endif; ?>
+ </section>
+</form> \ No newline at end of file
diff --git a/template/admin/html/user/index.php b/template/admin/html/user/index.php
new file mode 100644
index 0000000..6b23536
--- /dev/null
+++ b/template/admin/html/user/index.php
@@ -0,0 +1,10 @@
+<h1><i class="fa fa-user"></i><?=$Language->text('user_overview')?><a class="brackets" href="<?=Application::getAdminURL("user/insert.php")?>"><?=$Language->text('insert')?></a></h1>
+<p><?=$Language->template('overview_user_desc')?></p>
+
+<ul class="item-list">
+ <?php foreach($LIST['USERS'] as $user): ?>
+ <?php echo $user; ?>
+ <?php endforeach; ?>
+</ul>
+
+<?=$PAGINATION['HTML']?> \ No newline at end of file
diff --git a/template/admin/html/user/insert.php b/template/admin/html/user/insert.php
new file mode 100644
index 0000000..54598ee
--- /dev/null
+++ b/template/admin/html/user/insert.php
@@ -0,0 +1,4 @@
+<h1><i class="fa fa-user"></i><?=$Language->text('insert_user')?></h1>
+<p><?=$Language->template('insert_user_desc')?></p>
+
+<?=$HTML?> \ No newline at end of file
diff --git a/template/admin/html/user/item.php b/template/admin/html/user/item.php
new file mode 100644
index 0000000..df8e5b1
--- /dev/null
+++ b/template/admin/html/user/item.php
@@ -0,0 +1,15 @@
+<li class="content">
+ <header>
+ <h2><i class="fa fa-user"></i><?=escapeHTML($USER['ATTR']['FULLNAME'])?><span>#<?=$USER['ID']?></span></h2>
+ </header>
+ <article>
+ <p><?=excerpt($USER['BODY']['HTML'])?></p>
+ </article>
+ <footer>
+ <ul>
+ <li><a href="<?=$USER['URL']?>" target="_blank" title="<?=$Language->text('select_user')?>"><i class="fa fa-external-link"></i></a></li>
+ <li><a href="<?=Application::getAdminURL("user/update.php?id={$USER['ID']}")?>" title="<?=$Language->text('update_user')?>"><i class="fa fa-pencil-square-o"></i></a></li>
+ <li><a href="<?=Application::getAdminURL("user/delete.php?id={$USER['ID']}")?>" title="<?=$Language->text('delete_user')?>"><i class="fa fa-trash-o"></i></a></li>
+ </ul>
+ </footer>
+</li> \ No newline at end of file
diff --git a/template/admin/html/user/update.php b/template/admin/html/user/update.php
new file mode 100644
index 0000000..6f7ddae
--- /dev/null
+++ b/template/admin/html/user/update.php
@@ -0,0 +1,4 @@
+<h1><i class="fa fa-user"></i><?=$Language->text('update_user')?></h1>
+<p><?=$Language->template('update_user_desc')?></p>
+
+<?=$HTML?> \ No newline at end of file
diff --git a/template/admin/lang/de.php b/template/admin/lang/de.php
new file mode 100644
index 0000000..d9966ae
--- /dev/null
+++ b/template/admin/lang/de.php
@@ -0,0 +1,118 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Admin: Internationalization [DE] [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# This file contains template internationalization strings for the DE language #
+# and is completely independend from the core internationalization strings. #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+
+#===============================================================================
+# Item last text
+#===============================================================================
+$LANGUAGE['last_post'] = 'Letzter Post';
+$LANGUAGE['last_page'] = 'Letzte Seite';
+$LANGUAGE['last_user'] = 'Letzter Benutzer';
+
+#===============================================================================
+# Insert item description
+#===============================================================================
+$LANGUAGE['insert_page_desc'] = 'Hier kannst du eine neue Seite erstellen und veröffentlichen.';
+$LANGUAGE['insert_post_desc'] = 'Hier kannst du einen neuen Beitrag erstellen und veröffentlichen.';
+$LANGUAGE['insert_user_desc'] = 'Hier kannst du einen neuen Benutzer erstellen und veröffentlichen.';
+
+#===============================================================================
+# Update item description
+#===============================================================================
+$LANGUAGE['update_page_desc'] = 'Hier kannst du eine vorhandene Seite bearbeiten und die Änderungen abspeichern.';
+$LANGUAGE['update_post_desc'] = 'Hier kannst du einen vorhandenen Beitrag bearbeiten und die Änderungen abspeichern.';
+$LANGUAGE['update_user_desc'] = 'Hier kannst du deinen vorhandenen Benutzer bearbeiten und die Änderungen abspeichern.';
+
+#===============================================================================
+# Delete item description
+#===============================================================================
+$LANGUAGE['delete_page_desc'] = 'Falls du diese Seite nicht mehr benötigst kannst du sie über den folgenden Button permanent löschen.';
+$LANGUAGE['delete_post_desc'] = 'Falls du diesen Beitrag nicht mehr benötigst kannst du ihn über den folgenden Button permanent löschen.';
+$LANGUAGE['delete_user_desc'] = 'Falls du diesen Benutzer nicht mehr benötigst kannst du ihn über den folgenden Button permanent löschen.';
+
+#===============================================================================
+# Item overview description
+#===============================================================================
+$LANGUAGE['overview_page_desc'] = 'Hier siehst du alle vorhandenen Seiten.';
+$LANGUAGE['overview_post_desc'] = 'Hier siehst du alle vorhandenen Beiträge.';
+$LANGUAGE['overview_user_desc'] = 'Hier siehst du alle vorhandenen Benutzer.';
+
+#===============================================================================
+# Dashboard overview text
+#===============================================================================
+$LANGUAGE['overview_dashboard_text'] = 'Dashboard';
+
+#===============================================================================
+# Dashboard overview text
+#===============================================================================
+$LANGUAGE['overview_dashboard_desc'] = 'Willkommen im Administrationsbereich. Hier kannst du deine Inhalte verwalten.';
+
+#===============================================================================
+# Database overview text
+#===============================================================================
+$LANGUAGE['overview_database_text'] = 'Datenbank';
+
+#===============================================================================
+# Database overview text
+#===============================================================================
+$LANGUAGE['overview_database_desc'] = 'Datenbankoperationen mit SQL-Befehlen durchführen.';
+
+#===============================================================================
+# Authentication
+#===============================================================================
+$LANGUAGE['authentication_text'] = 'Authentifizierung';
+$LANGUAGE['authentication_desc'] = 'Um deine Inhalte zu verwalten musst du dich zuerst authentifizieren.';
+
+#===============================================================================
+# No items exists
+#===============================================================================
+$LANGUAGE['home_no_pages'] = 'Es gibt keine letzte Seite zum anzeigen hier. Du musst zuerst eine erstellen.';
+$LANGUAGE['home_no_posts'] = 'Es gibt keinen letzten Beitrag zum anzeigen hier. Du musst zuerst einen erstellen.';
+$LANGUAGE['home_no_users'] = 'Es gibt keinen letzten Benutzer zum anzeigen hier. Du musst zuerst einen erstellen.';
+
+#===============================================================================
+# Delete user warning
+#===============================================================================
+$LANGUAGE['delete_user_warning'] = '<strong>WARNUNG</strong>: Wenn du diesen Benutzer löschst werden alle ihm zugehörigen Beiträge und Seiten ebenfalls gelöscht!';
+
+#===============================================================================
+# Database warning
+#===============================================================================
+$LANGUAGE['database_warning'] = 'Manche Befehle können gefährliche Auswirkungen haben, wenn du nicht weißt, was du tust!';
+
+#===============================================================================
+# Error 403
+#===============================================================================
+$LANGUAGE['403_heading_text'] = 'Zugriff verweigert';
+$LANGUAGE['403_heading_desc'] = 'Der Zugriff auf diese Ressource des Servers wurde dir verweigert, da du die dafür notwendigen Berechtigungen nicht besitzt.';
+
+#===============================================================================
+# Error 404
+#===============================================================================
+$LANGUAGE['404_heading_text'] = 'Nicht gefunden';
+$LANGUAGE['404_heading_desc'] = 'Die angeforderte Ressource konnte auf diesem Server nicht gefunden werden.';
+
+#===============================================================================
+# "Are you sure?" question
+#===============================================================================
+$LANGUAGE['sure'] = 'Bist du sicher?';
+
+#===============================================================================
+# Labels
+#===============================================================================
+$LANGUAGE['LABEL_SLUG'] = 'Slug';
+$LANGUAGE['LABEL_USER'] = 'Benutzer';
+$LANGUAGE['LABEL_NAME'] = 'Titel';
+$LANGUAGE['LABEL_INSERT'] = 'Erstellt';
+$LANGUAGE['LABEL_UPDATE'] = 'Bearbeitet';
+$LANGUAGE['LABEL_FULLNAME'] = 'Name';
+$LANGUAGE['LABEL_MAILADDR'] = 'E-Mail';
+$LANGUAGE['LABEL_USERNAME'] = 'Username';
+$LANGUAGE['LABEL_PASSWORD'] = 'Passwort';
+?> \ No newline at end of file
diff --git a/template/admin/lang/en.php b/template/admin/lang/en.php
new file mode 100644
index 0000000..d98ea44
--- /dev/null
+++ b/template/admin/lang/en.php
@@ -0,0 +1,118 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Admin: Internationalization [EN] [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# This file contains template internationalization strings for the EN language #
+# and is completely independend from the core internationalization strings. #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+
+#===============================================================================
+# Item last text
+#===============================================================================
+$LANGUAGE['last_post'] = 'Last post';
+$LANGUAGE['last_page'] = 'Last page';
+$LANGUAGE['last_user'] = 'Last user';
+
+#===============================================================================
+# Insert item description
+#===============================================================================
+$LANGUAGE['insert_page_desc'] = 'Here you can create and publish a new page.';
+$LANGUAGE['insert_post_desc'] = 'Here you can create and publish a new post.';
+$LANGUAGE['insert_user_desc'] = 'Here you can create and publish a new user.';
+
+#===============================================================================
+# Update item description
+#===============================================================================
+$LANGUAGE['update_page_desc'] = 'Here you can edit an existing page and save the changes.';
+$LANGUAGE['update_post_desc'] = 'Here you can edit an existing post and save the changes.';
+$LANGUAGE['update_user_desc'] = 'Here you can edit an existing user and save the changes.';
+
+#===============================================================================
+# Delete item description
+#===============================================================================
+$LANGUAGE['delete_page_desc'] = 'If you do not need this page anymore, you can permanently delete it by clicking the following button.';
+$LANGUAGE['delete_post_desc'] = 'If you do not need this post anymore, you can permanently delete it by clicking the following button.';
+$LANGUAGE['delete_user_desc'] = 'If you do not need this user anymore, you can permanently delete it by clicking the following button.';
+
+#===============================================================================
+# Item overview description
+#===============================================================================
+$LANGUAGE['overview_page_desc'] = 'Here you can see all existing pages.';
+$LANGUAGE['overview_post_desc'] = 'Here you can see all existing posts.';
+$LANGUAGE['overview_user_desc'] = 'Here you can see all existing users.';
+
+#===============================================================================
+# Dashboard overview text
+#===============================================================================
+$LANGUAGE['overview_dashboard_text'] = 'Dashboard';
+
+#===============================================================================
+# Dashboard overview text
+#===============================================================================
+$LANGUAGE['overview_dashboard_desc'] = 'Welcome to the administration area. Here you can manage your content.';
+
+#===============================================================================
+# Database overview text
+#===============================================================================
+$LANGUAGE['overview_database_text'] = 'Database';
+
+#===============================================================================
+# Database overview text
+#===============================================================================
+$LANGUAGE['overview_database_desc'] = 'Perform database operations with SQL commands.';
+
+#===============================================================================
+# Authentication
+#===============================================================================
+$LANGUAGE['authentication_text'] = 'Authentication';
+$LANGUAGE['authentication_desc'] = 'To manage your content, you have to authenticate yourself first.';
+
+#===============================================================================
+# No items exists
+#===============================================================================
+$LANGUAGE['home_no_pages'] = 'There is no last page to display here. You have to insert a new page first.';
+$LANGUAGE['home_no_posts'] = 'There is no last post to display here. You have to insert a new post first.';
+$LANGUAGE['home_no_users'] = 'There is no last user to display here. You have to insert a new user first.';
+
+#===============================================================================
+# Delete user warning
+#===============================================================================
+$LANGUAGE['delete_user_warning'] = '<strong>WARNING</strong>: If you delete this user, all posts and pages belonging to this user will also be deleted!';
+
+#===============================================================================
+# Database warning
+#===============================================================================
+$LANGUAGE['database_warning'] = 'Some commands can have dangerous effects if you do not know what you are doing!';
+
+#===============================================================================
+# Error 403
+#===============================================================================
+$LANGUAGE['403_heading_text'] = 'Access denied';
+$LANGUAGE['403_heading_desc'] = 'You are denied to access this resource because you do not have the necessary permissions.';
+
+#===============================================================================
+# Error 404
+#===============================================================================
+$LANGUAGE['404_heading_text'] = 'Not found';
+$LANGUAGE['404_heading_desc'] = 'The requested resource could not be found on this server.';
+
+#===============================================================================
+# "Are you sure?" question
+#===============================================================================
+$LANGUAGE['sure'] = 'Are you sure?';
+
+#===============================================================================
+# Labels
+#===============================================================================
+$LANGUAGE['LABEL_SLUG'] = 'Slug';
+$LANGUAGE['LABEL_USER'] = 'User';
+$LANGUAGE['LABEL_NAME'] = 'Title';
+$LANGUAGE['LABEL_INSERT'] = 'Created';
+$LANGUAGE['LABEL_UPDATE'] = 'Updated';
+$LANGUAGE['LABEL_FULLNAME'] = 'Name';
+$LANGUAGE['LABEL_MAILADDR'] = 'Email';
+$LANGUAGE['LABEL_USERNAME'] = 'Username';
+$LANGUAGE['LABEL_PASSWORD'] = 'Password';
+?> \ No newline at end of file
diff --git a/template/admin/rsrc/background.png b/template/admin/rsrc/background.png
new file mode 100644
index 0000000..f018e81
--- /dev/null
+++ b/template/admin/rsrc/background.png
Binary files differ
diff --git a/template/admin/rsrc/font/font-awesome.woff2 b/template/admin/rsrc/font/font-awesome.woff2
new file mode 100644
index 0000000..4d13fc6
--- /dev/null
+++ b/template/admin/rsrc/font/font-awesome.woff2
Binary files differ
diff --git a/template/admin/rsrc/font/kadwa-n-400.woff2 b/template/admin/rsrc/font/kadwa-n-400.woff2
new file mode 100644
index 0000000..1566426
--- /dev/null
+++ b/template/admin/rsrc/font/kadwa-n-400.woff2
Binary files differ
diff --git a/template/admin/rsrc/font/ruda-n-400.woff2 b/template/admin/rsrc/font/ruda-n-400.woff2
new file mode 100644
index 0000000..6435a0d
--- /dev/null
+++ b/template/admin/rsrc/font/ruda-n-400.woff2
Binary files differ
diff --git a/template/admin/rsrc/font/ruda-n-700.woff2 b/template/admin/rsrc/font/ruda-n-700.woff2
new file mode 100644
index 0000000..0066431
--- /dev/null
+++ b/template/admin/rsrc/font/ruda-n-700.woff2
Binary files differ
diff --git a/template/admin/rsrc/icon-public-domain.svg b/template/admin/rsrc/icon-public-domain.svg
new file mode 100644
index 0000000..e0f0b0f
--- /dev/null
+++ b/template/admin/rsrc/icon-public-domain.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 53.869999 53.509998" version="1" width="53.87" height="53.51"><rect rx="2" width="50.745" y="1.563" x="1.563" height="50.385" fill="#b3b3b3" fill-rule="evenodd" stroke="#333" stroke-width="3.125" stroke-linejoin="round"/><g transform="translate(-3.116 -3.554)" fill-rule="evenodd" stroke="#333" stroke-linecap="round" stroke-linejoin="round"><path d="M25.96 22.992c-2.403 1.28-3.964 3.706-4.993 6.316h18.198c-.944-2.547-2.39-4.947-4.593-6.316-2.478-.108-5.595 0-8.612 0z" fill="#7f7f7f" stroke-width="1.87471319"/><circle transform="matrix(.6206 0 0 .6206 17.702 3.554)" cx="19.524" cy="22.301" r="12.326" fill="#bfbfbf" stroke-width="3.021"/></g><path d="M12.76 27.584c-.84 0-1.513.674-1.513 1.512v18.116h31.72V29.096c0-.838-.674-1.512-1.51-1.512H12.758z" fill="#999" fill-rule="evenodd" stroke="#333" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M18.55 29.705l-9.617-13.27 5.385-2.694" fill="none" stroke="#333" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/><circle cx="13.934" cy="13.741" r="2.308" fill="#7f7f7f" fill-rule="evenodd" stroke="#333" stroke-width="1.25"/><rect rx="2.267" height="4.616" width="36.929" y="24.705" x="8.548" fill="#4c4c4c" fill-rule="evenodd" stroke="#333" stroke-width="2.5" stroke-linejoin="round"/><path d="M18.29 45.03c2.917-1.836 6.143-2.33 7.4-2.043.14-1.242 2.07-3.303 5.54-5.486-4.107 1.048-4.96 1.32-6.74 3.745-3.04.3-3.913 1.192-6.2 3.785zM25.543 36.904c2.02-1.27 4.253-1.612 5.122-1.415.098-.86 1.434-2.287 3.835-3.797-2.842.724-3.433.912-4.664 2.59-2.105.208-2.71.826-4.293 2.62z" fill="#4c4c4c" fill-rule="evenodd" stroke="#333" stroke-width=".625" stroke-linecap="round" stroke-linejoin="round"/></svg> \ No newline at end of file
diff --git a/template/admin/rsrc/main.css b/template/admin/rsrc/main.css
new file mode 100644
index 0000000..2466b31
--- /dev/null
+++ b/template/admin/rsrc/main.css
@@ -0,0 +1,275 @@
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Selection
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+::-moz-selection{background:#BBB;color:#000;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Hyperlinks
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+a{color:#0060A0;text-decoration:none;}a:focus{background:#CCC;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Paragraphs
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+h1+p,h2+p,h3+p,h4+p,h5+p,h6+p{margin-top:0;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Icons
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+h1 .fa, h2 .fa, h3 .fa, h4 .fa, h5 .fa, h6 .fa{margin-right:0.25rem;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Headings
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+h1 > a{float:right;font-size:0.7rem;font-weight:normal;}
+h1,h2,h3,h4,h5,h6{margin:0;text-transform:uppercase;}
+h1{font-size:0.80rem;}h2{font-size:0.70rem;}
+h3{font-size:0.65rem;}h4{font-size:0.60rem;}
+h5{font-size:0.55rem;}h6{font-size:0.50rem;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Document
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+html,body{margin:0;padding:0;}
+main{padding:1rem;}
+html{font-size:1.25rem;color:#333;background:url(background.png) fixed center #CCC;-webkit-hyphens:auto;hyphens:auto;}
+body{font-family:Ruda,sans-serif;font-size:0.7rem;line-height:1.2rem;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Main content
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+#main-content{background:#FFF;border:0.05rem solid #AAA;border-top:none;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Width
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+#main-content,.header-content{max-width:50rem;margin:0 auto;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Header
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+#main-header{font-size:0.6rem;}
+#header-text,#header-desc{text-shadow: 0 -1px #4E718F, 1px 0 #4E718F, 0 1px #4E718F, -1px 0 #4E718F;}
+#header-text{font-size:0.8rem;font-weight:700;text-transform:uppercase;}
+#header-desc{font-size:0.6rem;font-weight:700;line-height:1rem;}
+#header-desc span{color:#CCC;}
+#header-logo{display:block;max-height:5rem;float:left;margin-right:0.5rem;}
+.header-line{padding:0.5rem 1rem;overflow:hidden;}
+.header-line:first-child{background:#5E819F;}
+.header-line:last-child{background:#EEE;border:0.05rem solid #AAA;border-left:none;border-right:none;padding:0.25rem 1rem;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Main Navigation
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+#main-navi ul{list-style:none;margin:0;padding:0;float:left;}
+#main-navi li{display:inline;}
+#main-navi ul+ul{float:right;}
+#main-navi li .fa{margin-right:0.25rem;}
+#main-navi a{padding:0.1rem 0.3rem;background:#DDD;border:0.05rem solid #AAA;color:inherit;text-decoration:none;text-align:center;display:inline-block;}
+#main-navi a:hover, #main-navi a:focus{text-decoration:none;background:#CCC;}
+#main-navi a:focus{background:#CCC;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Footer
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+#main-footer{font-size:0.6rem;background:#EEE;border-top:0.05rem solid #AAA;padding:0.75rem;text-align:center;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Content containers
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+.content{border:0.05rem solid #AAA;margin-bottom:1rem;clear:both;}
+.content{background:#FFF;border:0.05rem solid #AAA;}
+
+.content > header{padding:0.5rem 1rem;}
+
+.content > header,
+.content > footer{background:#EEE;overflow:hidden;}
+
+.content > article{padding:1rem;}
+.content > header{border-bottom:0.05rem solid #AAA;}
+.content > footer{border-top:0.05rem solid #AAA;}
+.content > footer > ul{margin:0;padding:0;list-style:none;}
+.content > footer > ul > li{display:inline-block;float:left;}
+.content > footer > ul > li:last-child{float:right;}
+.content > footer > ul > li > a{color:inherit;display:inline-block;padding:0.25rem 2rem;}
+.content > footer > ul > li > a:hover,
+.content > footer > ul > li > a:active{background:#DDD;}
+
+.content h2 > span{float:right;}
+
+.item-list{margin:0;padding:0;list-style:none;}
+.item-list > li{display:block;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Site Navigation
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+#site-navi{clear:both;display:flex;box-sizing:border-box;justify-content:space-between;}
+#site-navi > div{display:flex;align-items:center;border:0.05rem solid #AAA;background:#EEE;}
+#site-navi > div > a{display:block;}
+#site-navi > section{display:flex;overflow:hidden;align-items:center;}
+#site-navi > section > div{border:0.05rem solid #AAA;background:#EEE;}
+
+#site-navi .disabled{pointer-events:none;color:#AAA;}
+#site-navi .active a{background:#CCC !important;font-weight:600;pointer-events: none;}
+
+#site-navi ol{list-style:none;margin:0;padding:0;}
+#site-navi li{float:left;display:inline-block;}
+#site-navi li+li{border-left:0.05rem solid #AAA;}
+#site-navi a{padding:0 0.5rem;text-decoration:none;color:inherit;display:inline-block;}
+#site-navi a:hover,#site-navi a:focus{background:#CCC;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Elements
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+pre{margin-bottom:1rem;overflow:auto;-moz-tab-size:4;tab-size:4;}
+code,pre{font-family:monospace;color:#B03060;}
+p{margin-top:0;}
+img{border:none;max-width:100%;}
+.red{color:#B03060;}
+.blue{color:#40779A;}
+.green{color:#008B45;}
+.right{float:right;}
+#database-result{color:inherit;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Table elements
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+table{width:100%;margin:0 0 1rem;}td{vertical-align:middle;}
+table,td{border-spacing:0;border-collapse:collapse;padding:0.5rem;border:0.05rem solid #000;}
+thead,tr:nth-child(even){background:#EEE;}
+thead > tr, th{font-weight:700;font-style:italic;}
+thead > tr > td, th > td{text-align:center;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Brackets
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+.brackets a,a.brackets{text-decoration:none;}
+.brackets:after{content:"]"}
+.brackets:before{content:"["}
+a.brackets:before,a.brackets:after{color:#222;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Form flex-box
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+form{border:0.05rem solid #AAA;}
+
+.flex{display:flex;width:100%;justify-content:center;overflow:hidden;box-sizing:border-box;}
+.flex + section{border-top:0.05rem solid #AAA;}
+.flex > section {display:flex;box-sizing:padding-box;width:100%;}
+.flex > section > div{display:flex;align-items:center;padding:0.5rem;box-sizing:border-box;}
+.flex > section > div + div{border-left:0.05rem solid #AAA;}
+.flex.flex-responsive > section{width:50%;}
+
+.form-icon-flex{background:#DDD;width:10%;justify-content:center;}
+.form-label-flex{background:#DDD;width:30%;border-left:none !important;}
+.form-field-flex{width:60%;}
+
+.fa + label{margin-left:0.25rem;}
+.background{background:#DDD;}
+.flex-padding{padding:0.5rem;}
+.flex-direction-column{flex-direction:column;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Form buttons
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+.button-list {margin:0;padding:0;list-style:none;}
+.button-list > li{display:inline-block;background:#EEE;padding:0.5rem;border:0.05rem solid #AAA;cursor:pointer;}
+.button-list.emoticons > li {padding:0.25rem 0.5rem;}
+.button-list > li:hover,
+.button-list > li:active{background:#CCC;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Form elements
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+input,select,textarea{width:100%;box-sizing:padding-box;background:#EEE;color:#404040;font-family:inherit;font-size:0.7rem;padding:0.25rem;border:0.05rem solid #AAA;}
+textarea{font-family:Kadwa,sans-serif;box-sizing:border-box;display:inline-block;resize:vertical;min-height:15rem;line-height:1.4rem;padding:0.75rem;}
+input[type="submit"]{text-transform:uppercase;}
+input:disabled{background:#DDD;color:#888;}
+input:disabled:hover{cursor:not-allowed;}
+label{text-transform:uppercase;font-weight:normal;}
+label:after{content:":";}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* FontAwesome Main
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+.fa{display:inline-block;font:normal normal normal 14px/1 "FontAwesome";font-size:inherit;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* FontAwesome Icons
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+.fa-rss:before{content:"\f09e"}
+.fa-bug:before{content:"\f188"}
+.fa-key:before{content:"\f084"}
+.fa-link:before{content:"\f0c1"}
+.fa-bold:before{content:"\f032"}
+.fa-code:before{content:"\f121"}
+.fa-bars:before{content:"\f0c9"}
+.fa-user:before{content:"\f007"}
+.fa-home:before{content:"\f015"}
+.fa-plus:before{content:"\f067"}
+.fa-italic:before{content:"\f033"}
+.fa-header:before{content:"\f1dc"}
+.fa-search:before{content:"\f002"}
+.fa-trash-o:before{content:"\f014"}
+.fa-sign-in:before{content:"\f090"}
+.fa-clock-o:before{content:"\f017"}
+.fa-list-ul:before{content:"\f0ca"}
+.fa-list-ol:before{content:"\f0cb"}
+.fa-smile-o:before{content:"\f118"}
+.fa-database:before{content:"\f1c0"}
+.fa-sign-out:before{content:"\f08b"}
+.fa-dashboard:before{content:"\f0e4"}
+.fa-picture-o:before{content:"\f03e"}
+.fa-envelope-o:before{content:"\f003"}
+.fa-eyedropper:before{content:"\f1fb"}
+.fa-rss-square:before{content:"\f143"}
+.fa-arrow-left:before{content:"\f060"}
+.fa-quote-right:before{content:"\f10e"}
+.fa-user-secret:before{content:"\f21b"}
+.fa-file-text-o:before{content:"\f0f6"}
+.fa-newspaper-o:before{content:"\f1ea"}
+.fa-arrow-right:before{content:"\f061"}
+.fa-external-link:before{content:"\f08e"}
+.fa-pencil-square-o:before{content:"\f044"}
+.fa-question-circle:before{content:"\f059"}
+.fa-pencil-square-o:before{content:"\f044"}
+.fa-exclamation-triangle:before{content:"\f071"}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Responsive Level #1
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+@media only screen and (max-width:50rem) {
+ html{font-size:1.125rem;/*18px*/background-image:none !important;}
+}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Responsive Level #2
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+@media only screen and (max-width:37.5rem) {
+ .flex.flex-responsive{display:block;width:auto;}
+ .flex.flex-responsive > section {width:100%;}
+ .flex.flex-responsive > section + section{border-top:0.05rem solid #AAA;}
+
+ #main-navi{font-size:1rem;}
+ #main-navi li span{display:none;}
+ #main-navi li .fa{margin-right:0;}
+ #main-navi a{padding:0.5rem;}
+
+ .flex-emoticons{display:none !important;}
+}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Font "Font Awesome" [4.7.0]: SIL Open Font License (OFL)
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+@font-face{font-family:"FontAwesome";font-weight:400;src:url("font/font-awesome.woff2") format("woff2");}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Font "Kadwa": SIL Open Font License (OFL)
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+@font-face{font-family:Kadwa;font-weight:400;src:url("font/kadwa-n-400.woff2") format("woff2");}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Font "Ruda": SIL Open Font License (OFL)
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+@font-face{font-family:Ruda;font-weight:400;src:url("font/ruda-n-400.woff2") format("woff2");}
+@font-face{font-family:Ruda;font-weight:700;src:url("font/ruda-n-700.woff2") format("woff2");} \ No newline at end of file
diff --git a/template/admin/rsrc/main.js b/template/admin/rsrc/main.js
new file mode 100644
index 0000000..3d5546f
--- /dev/null
+++ b/template/admin/rsrc/main.js
@@ -0,0 +1,94 @@
+//==============================================================================
+// Markdown tags to replace
+//==============================================================================
+var markdownTags = {
+ "bold": ["**", "**"],
+ "italic": ["*", "*"],
+ "header": ["## ", "\n"],
+ "link": ["[", "](href)"],
+ "image": ["![", "](href)"],
+ "code": ["\n~~~\n", "\n~~~\n"],
+ "quote": ["\n> ", ""],
+ "list_ul": ["* ", ""],
+ "list_ol": ["1. ", ""]
+};
+
+//==============================================================================
+// Set caret position in editor
+//==============================================================================
+function setCaretPosition(position) {
+ window.setTimeout(function() {
+ document.getElementById("content-editor").focus();
+ document.getElementById("content-editor").setSelectionRange(position, position);
+ }, 50);
+
+}
+
+//==============================================================================
+// Insert markdown around text in editor
+//==============================================================================
+function markdownReplace(tagname) {
+ var element = document.activeElement;
+
+ if(element.nodeName === 'TEXTAREA') {
+ var selectionStart = element.selectionStart;
+ var selectionEnd = element.selectionEnd;
+
+ var selectedText = element.value.substring(selectionStart, selectionEnd);
+
+ var content = element.value;
+ element.value = content.slice(0, selectionStart) + markdownTags[tagname][0] + selectedText + markdownTags[tagname][1] + content.slice(selectionEnd);
+
+ setCaretPosition(selectionStart + markdownTags[tagname][0].length + selectedText.length + markdownTags[tagname][1].length);
+ }
+}
+
+//==============================================================================
+// Insert emoticon after cursor in editor
+//==============================================================================
+function emoticonReplace(emoticon) {
+ var element = document.activeElement;
+
+ if(element.nodeName === 'TEXTAREA') {
+ var selectionStart = element.selectionStart;
+ var selectionEnd = element.selectionEnd;
+
+ var content = element.value;
+ element.value = content.slice(0, selectionStart) + emoticon + content.slice(selectionEnd);
+
+ setCaretPosition(selectionStart + emoticon.length);
+ }
+}
+
+//==============================================================================
+// Keep server-side session active if the user is writing a long text
+//==============================================================================
+addEventListener("DOMContentLoaded", function() {
+ setInterval(function() {
+ var Request = new XMLHttpRequest();
+ Request.open("HEAD", "", true);
+ Request.send();
+ }, 300000);
+}, false);
+
+//==============================================================================
+// Insert tab indent into editor if <tab> is pressed
+//==============================================================================
+addEventListener("DOMContentLoaded", function() {
+ if(document.getElementById("content-editor")) {
+ var element = document.getElementById("content-editor");
+ element.addEventListener('keydown', function(e) {
+ if(e.keyCode === 9 && !e.ctrlKey && !e.shiftKey) {
+ var selectionStart = element.selectionStart;
+ var selectionEnd = element.selectionEnd;
+
+ var content = element.value;
+
+ element.value = content.substring(0, selectionStart) + "\t" + content.substring(selectionEnd);
+
+ setCaretPosition(selectionStart + 1);
+ e.preventDefault();
+ }
+ }, false);
+ }
+}, false); \ No newline at end of file
diff --git a/template/standard/html/403.php b/template/standard/html/403.php
new file mode 100644
index 0000000..5039678
--- /dev/null
+++ b/template/standard/html/403.php
@@ -0,0 +1,11 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: 403 Template [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+?>
+<h1><i class="fa fa-exclamation-triangle"></i><?=$Language->template('403_heading_text')?></h1>
+<p><?=$Language->template('403_heading_desc')?></p> \ No newline at end of file
diff --git a/template/standard/html/404.php b/template/standard/html/404.php
new file mode 100644
index 0000000..acb7ab9
--- /dev/null
+++ b/template/standard/html/404.php
@@ -0,0 +1,11 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: 404 Template [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+?>
+<h1><i class="fa fa-exclamation-triangle"></i><?=$Language->template('404_heading_text')?></h1>
+<p><?=$Language->template('404_heading_desc')?></p> \ No newline at end of file
diff --git a/template/standard/html/feed/item_page.php b/template/standard/html/feed/item_page.php
new file mode 100644
index 0000000..9defc25
--- /dev/null
+++ b/template/standard/html/feed/item_page.php
@@ -0,0 +1,25 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: Feed Item Template [page] [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+?>
+<item>
+ <title><?=escapeHTML($PAGE['ATTR']['NAME'])?></title>
+ <link><?=$PAGE['URL']?></link>
+ <guid isPermaLink="false"><?=$PAGE['GUID']?></guid>
+ <pubDate><?=parseDatetime($PAGE['ATTR']['TIME_INSERT'], '[RFC2822]')?></pubDate>
+ <dc:creator><?=escapeHTML($USER['ATTR']['FULLNAME'])?></dc:creator>
+ <description><?=escapeHTML(cut(removeLineBreaksAndTabs(removeHTML($PAGE['BODY']['HTML'])), 400))?></description>
+ <content:encoded>
+ <![CDATA[
+ <?=$PAGE['BODY']['HTML']?>
+ ]]>
+ </content:encoded>
+ <?php foreach($PAGE['FILE']['LIST'] as $fileURL): ?>
+ <media:content url="<?=$fileURL?>" medium="image"></media:content>
+ <?php endforeach; ?>
+</item> \ No newline at end of file
diff --git a/template/standard/html/feed/item_post.php b/template/standard/html/feed/item_post.php
new file mode 100644
index 0000000..f004316
--- /dev/null
+++ b/template/standard/html/feed/item_post.php
@@ -0,0 +1,26 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: Feed Item Template [post] [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+?>
+<item>
+ <title><?=escapeHTML($POST['ATTR']['NAME'])?></title>
+ <link><?=$POST['URL']?></link>
+ <guid isPermaLink="false"><?=$POST['GUID']?></guid>
+ <pubDate><?=parseDatetime($POST['ATTR']['TIME_INSERT'], '[RFC2822]')?></pubDate>
+ <dc:creator><?=escapeHTML($USER['ATTR']['FULLNAME'])?></dc:creator>
+ <description><?=escapeHTML(cut(removeLineBreaksAndTabs(removeHTML($POST['BODY']['HTML'])), 400))?></description>
+ <content:encoded>
+ <![CDATA[
+ <?=$POST['BODY']['HTML']?>
+ <p><small><strong>Kommentare:</strong> [<a href="https://keybase.io/nerdmind">0x33EB32A2</a>] blog&#64;nerdmind.de</small></p>
+ ]]>
+ </content:encoded>
+ <?php foreach($POST['FILE']['LIST'] as $fileURL): ?>
+ <media:content url="<?=$fileURL?>" medium="image"></media:content>
+ <?php endforeach; ?>
+</item> \ No newline at end of file
diff --git a/template/standard/html/feed/main.php b/template/standard/html/feed/main.php
new file mode 100644
index 0000000..174a841
--- /dev/null
+++ b/template/standard/html/feed/main.php
@@ -0,0 +1,50 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: Feed Template [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+
+switch($FEED['TYPE']) {
+ case 'post':
+ $title = escapeHTML($BLOGMETA['NAME']).' ['.$Language->template('feed_only_posts').']';
+ $self = Application::getURL('feed/post/');
+ break;
+ case 'page':
+ $title = escapeHTML($BLOGMETA['NAME']).' ['.$Language->template('feed_only_pages').']';
+ $self = Application::getURL('feed/page/');
+ break;
+ default:
+ $title = escapeHTML($BLOGMETA['NAME']);
+ $self = Application::getURL('feed/');
+}
+?>
+<?='<?xml version="1.0" encoding="UTF-8" ?>'?>
+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/">
+ <channel>
+ <title><?=$title?></title>
+ <link><?=$self?></link>
+ <language><?=$BLOGMETA['LANG']?></language>
+ <description><?=escapeHTML($BLOGMETA['DESC'])?></description>
+
+ <atom:link href="<?=$self?>" rel="self" type="application/rss+xml" />
+
+ <image>
+ <title><?=escapeHTML($BLOGMETA['NAME'])?></title>
+ <url><?=Application::getTemplateURL('rsrc/logo.png')?></url>
+ <link><?=$self?></link>
+ </image>
+
+ <!-- Feed items of type "post" -->
+ <?php foreach($FEED['LIST']['POSTS'] as $item): ?>
+ <?php echo $item ?>
+ <?php endforeach; ?>
+
+ <!-- Feed items of type "page" -->
+ <?php foreach($FEED['LIST']['PAGES'] as $item): ?>
+ <?php echo $item ?>
+ <?php endforeach; ?>
+ </channel>
+</rss> \ No newline at end of file
diff --git a/template/standard/html/home.php b/template/standard/html/home.php
new file mode 100644
index 0000000..42fc169
--- /dev/null
+++ b/template/standard/html/home.php
@@ -0,0 +1,19 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: Home Template [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+?>
+<h1><i class="fa fa-home"></i><?=$Language->template('home_heading_text', escapeHTML(Application::get('BLOGMETA.NAME')))?><span class="head-link brackets"><i class="fa fa-rss"></i><a href="<?=Application::getURL('feed/')?>" title="Alle Inhalte">Feed</a></span></h1>
+<p><?=$Language->template('home_heading_desc', Application::get('POST.LIST_SIZE'))?></p>
+
+<ul class="item-list post">
+<?php foreach($LIST['POSTS'] as $post): ?>
+ <?php echo $post; ?>
+<?php endforeach; ?>
+</ul>
+
+<?=$PAGINATION['HTML']?> \ No newline at end of file
diff --git a/template/standard/html/main.php b/template/standard/html/main.php
new file mode 100644
index 0000000..19d3b29
--- /dev/null
+++ b/template/standard/html/main.php
@@ -0,0 +1,92 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: Main Template [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+?>
+<!DOCTYPE html>
+<html lang="<?=$BLOGMETA['LANG']?>">
+<head>
+ <meta charset="UTF-8" />
+ <meta name="referrer" content="origin-when-crossorigin" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+
+<?php if(isset($HEAD['DESC'])): ?>
+ <meta name="description" content="<?=escapeHTML($HEAD['DESC'])?>" />
+<?php endif; ?>
+
+<?php if(isset($HEAD['PERM'])): ?>
+ <link rel="canonical" href="<?=$HEAD['PERM']?>" />
+<?php endif; ?>
+
+ <meta property="og:site_name" content="<?=escapeHTML($BLOGMETA['NAME'])?>" />
+ <meta property="og:title" content="<?=escapeHTML($HEAD['NAME'])?>" />
+ <meta property="og:image" content="<?=Application::getTemplateURL('rsrc/logo.png')?>" />
+
+<?php if(isset($HEAD['OG_IMAGES'])): ?>
+ <?php foreach($HEAD['OG_IMAGES'] as $imageURL): ?>
+ <meta property="og:image" content="<?=$imageURL?>" />
+ <?php endforeach; ?>
+<?php endif; ?>
+
+ <link rel="icon" href="<?=Application::getURL('favicon.ico')?>" />
+ <link rel="stylesheet" href="<?=Application::getTemplateURL('rsrc/main.css')?>" />
+
+ <link rel="alternate" type="application/rss+xml" href="<?=Application::getURL('feed/')?>" title="<?=escapeHTML($BLOGMETA['NAME'])?>" />
+ <link rel="alternate" type="application/rss+xml" href="<?=Application::getURL('feed/post/')?>" title="<?=escapeHTML($BLOGMETA['NAME'])?> [<?=$Language->template('feed_only_posts')?>]" />
+ <link rel="alternate" type="application/rss+xml" href="<?=Application::getURL('feed/page/')?>" title="<?=escapeHTML($BLOGMETA['NAME'])?> [<?=$Language->template('feed_only_pages')?>]" />
+
+ <title><?=escapeHTML("{$HEAD['NAME']} | {$BLOGMETA['NAME']} {$BLOGMETA['DESC']}")?></title>
+</head>
+<body>
+ <section id="container">
+ <header id="main-header">
+ <section>
+ <a href="<?=Application::getURL()?>" title="<?=escapeHTML("{$BLOGMETA['NAME']} {$BLOGMETA['DESC']}")?>">
+ <img id="main-logo" src="<?=Application::getTemplateURL('rsrc/logo.png')?>" alt="<?=escapeHTML($BLOGMETA['NAME'])?>" />
+ </a>
+ </section>
+ <nav id="main-navi">
+ <label for="toogle-nav" id="toogle-nav-label" class="fa fa-bars"></label>
+ <input type="checkbox" id="toogle-nav" />
+ <ul>
+ <li>
+ <a href="<?=Application::getURL()?>" title="<?=$Language->template('navigation_home_desc', escapeHTML($BLOGMETA['NAME']))?>">
+ <i class="fa fa-home"></i><?=$Language->template('navigation_home_text')?>
+ </a>
+ </li>
+ <li>
+ <a href="<?=Application::getPostURL()?>" title="<?=$Language->text('post_overview')?>">
+ <i class="fa fa-newspaper-o"></i><?=$Language->text('posts')?>
+ </a>
+ </li>
+ <li>
+ <a href="<?=Application::getPageURL()?>" title="<?=$Language->text('page_overview')?>">
+ <i class="fa fa-file-text-o"></i><?=$Language->text('pages')?>
+ </a>
+ </li>
+ <li>
+ <a href="<?=Application::getUserURL()?>" title="<?=$Language->text('user_overview')?>">
+ <i class="fa fa-user"></i><?=$Language->text('users')?>
+ </a>
+ </li>
+ <li>
+ <a href="<?=Application::getURL('search/')?>" title="<?=$Language->template('navigation_search_desc')?>">
+ <i class="fa fa-search"></i><?=$Language->template('navigation_search_text')?>
+ </a>
+ </li>
+ </ul>
+ </nav>
+ </header>
+ <main>
+ <?=$HTML?>
+ </main>
+ <footer id="main-footer">
+ &copy; <?=escapeHTML($BLOGMETA['NAME'])?>
+ </footer>
+ </section>
+</body>
+</html> \ No newline at end of file
diff --git a/template/standard/html/page/item.php b/template/standard/html/page/item.php
new file mode 100644
index 0000000..29ab9cf
--- /dev/null
+++ b/template/standard/html/page/item.php
@@ -0,0 +1,20 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: Page Item Template [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+?>
+<li class="item-list-li page">
+ <header>
+ <h2>
+ <a href="<?=$PAGE['URL']?>"><?=escapeHTML($PAGE['ATTR']['NAME'])."\n"?></a>
+ <time class="brackets info" datetime="<?=$PAGE['ATTR']['TIME_INSERT']?>"><?=parseDatetime($PAGE['ATTR']['TIME_INSERT'], $Language->template('date_format'))?></time>
+ </h2>
+ </header>
+ <article>
+ <p><?=excerpt($PAGE['BODY']['HTML'], 600)?></p>
+ </article>
+</li> \ No newline at end of file
diff --git a/template/standard/html/page/list.php b/template/standard/html/page/list.php
new file mode 100644
index 0000000..b6a112d
--- /dev/null
+++ b/template/standard/html/page/list.php
@@ -0,0 +1,19 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: Page List Template [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+?>
+<h1><i class="fa fa-file-text-o"></i><?=$Language->text('page_overview')?><span class="head-link brackets"><i class="fa fa-rss"></i><a href="<?=Application::getURL('feed/page/')?>" title="Nur Seiten">Feed</a></span></h1>
+<p><?=$Language->template('page_base_heading_desc', $PAGINATION['THIS'])?></p>
+
+<ul class="item-list page">
+ <?php foreach($LIST['PAGES'] as $page): ?>
+ <?php echo $page; ?>
+ <?php endforeach; ?>
+</ul>
+
+<?=$PAGINATION['HTML']?> \ No newline at end of file
diff --git a/template/standard/html/page/main.php b/template/standard/html/page/main.php
new file mode 100644
index 0000000..b5e6a6d
--- /dev/null
+++ b/template/standard/html/page/main.php
@@ -0,0 +1,46 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: Page Main Template [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+
+$user = "<a href=\"{$USER['URL']}\" title=\"alias »{$USER['ATTR']['USERNAME']}«\">{$USER['ATTR']['FULLNAME']}</a>";
+$time = "<time datetime=\"{$PAGE['ATTR']['TIME_INSERT']}\" title=\"".parseDatetime($PAGE['ATTR']['TIME_INSERT'], '[W]')."\">".parseDatetime($PAGE['ATTR']['TIME_INSERT'], $Language->template('date_format'))."</time>";
+?>
+<h1><i class="fa fa-file-text-o"></i><?=escapeHTML($Language->template('page_main_heading_text', $PAGE['ATTR']['NAME']))?></h1>
+<p><?=$Language->template('page_main_heading_desc', [$user, $time])?></p>
+
+<section id="content" class="page">
+ <?=$PAGE['BODY']['HTML']?>
+</section>
+
+<section id="site-navi">
+
+ <?php if($PAGE['PREV']): ?>
+ <div><a href="<?=$PAGE['PREV']['URL']?>" title="<?=$Language->text('prev_page')?> »<?=escapeHTML($PAGE['PREV']['ATTR']['NAME'])?>«"><i class="fa fa-arrow-left"></i></a></div>
+ <?php else: ?>
+ <div><a class="disabled"><i class="fa fa-arrow-left"></i></a></div>
+ <?php endif; ?>
+
+ <?php if($PAGE['NEXT']): ?>
+ <div><a href="<?=$PAGE['NEXT']['URL']?>" title="<?=$Language->text('next_page')?> »<?=escapeHTML($PAGE['NEXT']['ATTR']['NAME'])?>«"><i class="fa fa-arrow-right"></i></a></div>
+ <?php else: ?>
+ <div><a class="disabled"><i class="fa fa-arrow-right"></i></a></div>
+ <?php endif; ?>
+
+</section>
+
+<script>
+ var prevPageURL = <?php echo json_encode($PAGE['PREV'] ? $PAGE['PREV']['URL'] : FALSE); ?>;
+ var nextPageURL = <?php echo json_encode($PAGE['NEXT'] ? $PAGE['NEXT']['URL'] : FALSE); ?>;
+
+ document.addEventListener('keyup', function(event) {
+ if(!event.ctrlKey && !event.shiftKey) {
+ (event.keyCode === 37 && prevPageURL) && (window.location.href = prevPageURL);
+ (event.keyCode === 39 && nextPageURL) && (window.location.href = nextPageURL);
+ }
+ }, false)
+</script> \ No newline at end of file
diff --git a/template/standard/html/pagination.php b/template/standard/html/pagination.php
new file mode 100644
index 0000000..221530e
--- /dev/null
+++ b/template/standard/html/pagination.php
@@ -0,0 +1,54 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: Pagination Template [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+?>
+<section id="site-navi">
+ <?php if($THIS > 1): ?>
+ <div><a href="<?=sprintf($HREF, $THIS-1)?>"><i class="fa fa-arrow-left"></i></a></div>
+ <?php else: ?>
+ <div><a class="disabled"><i class="fa fa-arrow-left"></i></a></div>
+ <?php endif; ?>
+
+ <section>
+ <div>
+ <ol>
+ <?php for($currentItem = 1; $currentItem <= $LAST; ++$currentItem): ?>
+ <?php
+ $href = sprintf($HREF, $currentItem);
+ $class = NULL;
+ $currentItemHTML = $currentItem;
+ if($currentItem === $THIS) {
+ $class = ' class="active"';
+ }
+
+ echo '<li'.$class.'><a href="'.$href.'">'.$currentItemHTML.'</a></li>';
+ ?>
+
+ <?php endfor; ?>
+ </ol>
+ </div>
+ </section>
+
+ <?php if($THIS < $LAST): ?>
+ <div><a href="<?=sprintf($HREF, $THIS+1)?>"><i class="fa fa-arrow-right"></i></a></div>
+ <?php else: ?>
+ <div><a class="disabled"><i class="fa fa-arrow-right"></i></a></div>
+ <?php endif; ?>
+</section>
+
+<script>
+ var prevPageURL = <?php echo json_encode($THIS > 1 ? sprintf($HREF, $THIS-1) : FALSE); ?>;
+ var nextPageURL = <?php echo json_encode($THIS < $LAST ? sprintf($HREF, $THIS+1) : FALSE); ?>;
+
+ document.addEventListener('keyup', function(event) {
+ if(!event.ctrlKey && !event.shiftKey) {
+ (event.keyCode === 37 && prevPageURL) && (window.location.href = prevPageURL);
+ (event.keyCode === 39 && nextPageURL) && (window.location.href = nextPageURL);
+ }
+ }, false)
+</script> \ No newline at end of file
diff --git a/template/standard/html/post/item.php b/template/standard/html/post/item.php
new file mode 100644
index 0000000..ab00624
--- /dev/null
+++ b/template/standard/html/post/item.php
@@ -0,0 +1,20 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: Post Item Template [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+?>
+<li class="item-list-li post">
+ <header>
+ <h2>
+ <a href="<?=$POST['URL']?>"><?=escapeHTML($POST['ATTR']['NAME'])."\n"?></a>
+ <time class="brackets info" datetime="<?=$POST['ATTR']['TIME_INSERT']?>"><?=parseDatetime($POST['ATTR']['TIME_INSERT'], $Language->template('date_format'))?></time>
+ </h2>
+ </header>
+ <article>
+ <?=$POST['BODY']['HTML']?>
+ </article>
+</li> \ No newline at end of file
diff --git a/template/standard/html/post/list.php b/template/standard/html/post/list.php
new file mode 100644
index 0000000..457c1df
--- /dev/null
+++ b/template/standard/html/post/list.php
@@ -0,0 +1,19 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: Post List Template [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+?>
+<h1><i class="fa fa-newspaper-o"></i><?=$Language->text('post_overview')?><span class="head-link brackets"><i class="fa fa-rss"></i><a href="<?=Application::getURL('feed/post/')?>" title="Nur Beiträge">Feed</a></span></h1>
+<p><?=$Language->template('post_base_heading_desc', $PAGINATION['THIS'])?></p>
+
+<ul class="item-list post">
+ <?php foreach($LIST['POSTS'] as $post): ?>
+ <?php echo $post; ?>
+ <?php endforeach; ?>
+</ul>
+
+<?=$PAGINATION['HTML']?> \ No newline at end of file
diff --git a/template/standard/html/post/main.php b/template/standard/html/post/main.php
new file mode 100644
index 0000000..31d7102
--- /dev/null
+++ b/template/standard/html/post/main.php
@@ -0,0 +1,46 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: Post Main Template [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+
+$user = "<a href=\"{$USER['URL']}\" title=\"alias »{$USER['ATTR']['USERNAME']}«\">{$USER['ATTR']['FULLNAME']}</a>";
+$time = "<time datetime=\"{$POST['ATTR']['TIME_INSERT']}\" title=\"".parseDatetime($POST['ATTR']['TIME_INSERT'], '[W]')."\">".parseDatetime($POST['ATTR']['TIME_INSERT'], $Language->template('date_format'))."</time>";
+?>
+<h1><i class="fa fa-newspaper-o"></i><?=escapeHTML($Language->template('post_main_heading_text', $POST['ATTR']['NAME']))?></h1>
+<p><?=$Language->template('post_main_heading_desc', [$user, $time])?></p>
+
+<section id="content" class="post">
+ <?=$POST['BODY']['HTML']?>
+</section>
+
+<section id="site-navi">
+
+ <?php if($POST['PREV']): ?>
+ <div><a href="<?=$POST['PREV']['URL']?>" title="<?=$Language->text('prev_post')?> »<?=escapeHTML($POST['PREV']['ATTR']['NAME'])?>«"><i class="fa fa-arrow-left"></i></a></div>
+ <?php else: ?>
+ <div><a class="disabled"><i class="fa fa-arrow-left"></i></a></div>
+ <?php endif; ?>
+
+ <?php if($POST['NEXT']): ?>
+ <div><a href="<?=$POST['NEXT']['URL']?>" title="<?=$Language->text('next_post')?> »<?=escapeHTML($POST['NEXT']['ATTR']['NAME'])?>«"><i class="fa fa-arrow-right"></i></a></div>
+ <?php else: ?>
+ <div><a class="disabled"><i class="fa fa-arrow-right"></i></a></div>
+ <?php endif; ?>
+
+</section>
+
+<script>
+ var prevPageURL = <?php echo json_encode($POST['PREV'] ? $POST['PREV']['URL'] : FALSE); ?>;
+ var nextPageURL = <?php echo json_encode($POST['NEXT'] ? $POST['NEXT']['URL'] : FALSE); ?>;
+
+ document.addEventListener('keyup', function(event) {
+ if(!event.ctrlKey && !event.shiftKey) {
+ (event.keyCode === 37 && prevPageURL) && (window.location.href = prevPageURL);
+ (event.keyCode === 39 && nextPageURL) && (window.location.href = nextPageURL);
+ }
+ }, false)
+</script> \ No newline at end of file
diff --git a/template/standard/html/search/main.php b/template/standard/html/search/main.php
new file mode 100644
index 0000000..b749517
--- /dev/null
+++ b/template/standard/html/search/main.php
@@ -0,0 +1,34 @@
+<h1><i class="fa fa-search"></i><?=$Language->template('search_base_heading_text')?></h1>
+<p><?=$Language->template('search_base_heading_desc')?></p>
+
+<?php if($SEARCH['INFO']): ?>
+ <div class="red"><?=$SEARCH['INFO']?></div>
+<?php endif; ?>
+
+<form action="" method="GET">
+ <input type="search" name="q" placeholder="<?=$Language->template('search_form_placeholder')?>" value="<?=escapeHTML($SEARCH['TEXT'])?>" />
+
+ <select name="d">
+ <option value=""><?=$Language->text('date_d')?></option>
+
+ <?php foreach($FORM['OPTIONS']['D'] as $option): ?>
+ <option value="<?=$option?>"<?=($FORM['SELECT']['D'] === $option) ? ' selected' : '' ?>><?=$option?></option>
+ <?php endforeach; ?>
+ </select>
+ <select name="m">
+ <option value=""><?=$Language->text('date_m')?></option>
+
+ <?php foreach($FORM['OPTIONS']['M'] as $option): ?>
+ <option value="<?=$option?>"<?=($FORM['SELECT']['M'] === $option) ? ' selected' : '' ?>><?=$option?></option>
+ <?php endforeach; ?>
+ </select>
+ <select name="y">
+ <option value=""><?=$Language->text('date_y')?></option>
+
+ <?php foreach($FORM['OPTIONS']['Y'] as $option): ?>
+ <option value="<?=$option?>"<?=($FORM['SELECT']['Y'] === $option) ? ' selected' : '' ?>><?=$option?></option>
+ <?php endforeach; ?>
+ </select>
+
+ <input type="submit" value="<?=$Language->text('search')?>" />
+</form> \ No newline at end of file
diff --git a/template/standard/html/search/result.php b/template/standard/html/search/result.php
new file mode 100644
index 0000000..e79eb98
--- /dev/null
+++ b/template/standard/html/search/result.php
@@ -0,0 +1,36 @@
+<h1><?=$Language->template('search_result_heading_text', escapeHTML($SEARCH['TEXT']))?></h1>
+<p><?=$Language->template('search_result_heading_desc')?></p>
+
+<form action="" method="GET">
+ <input type="search" name="q" placeholder="<?=$Language->template('search_form_placeholder')?>" value="<?=escapeHTML($SEARCH['TEXT'])?>" />
+
+ <select name="d">
+ <option value=""><?=$Language->text('date_d')?></option>
+
+ <?php foreach($FORM['OPTIONS']['D'] as $option): ?>
+ <option value="<?=$option?>"<?=($FORM['SELECT']['D'] === $option) ? ' selected' : '' ?>><?=$option?></option>
+ <?php endforeach; ?>
+ </select>
+ <select name="m">
+ <option value=""><?=$Language->text('date_m')?></option>
+
+ <?php foreach($FORM['OPTIONS']['M'] as $option): ?>
+ <option value="<?=$option?>"<?=($FORM['SELECT']['M'] === $option) ? ' selected' : '' ?>><?=$option?></option>
+ <?php endforeach; ?>
+ </select>
+ <select name="y">
+ <option value=""><?=$Language->text('date_y')?></option>
+
+ <?php foreach($FORM['OPTIONS']['Y'] as $option): ?>
+ <option value="<?=$option?>"<?=($FORM['SELECT']['Y'] === $option) ? ' selected' : '' ?>><?=$option?></option>
+ <?php endforeach; ?>
+ </select>
+
+ <input type="submit" value="<?=$Language->text('search')?>" />
+</form>
+
+<ul class="item-list post">
+ <?php foreach($RESULT['LIST'] as $post): ?>
+ <?php echo $post; ?>
+ <?php endforeach; ?>
+</ul> \ No newline at end of file
diff --git a/template/standard/html/user/item.php b/template/standard/html/user/item.php
new file mode 100644
index 0000000..78a757d
--- /dev/null
+++ b/template/standard/html/user/item.php
@@ -0,0 +1,20 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: User Item Template [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+?>
+<li class="item-list-li user">
+ <header>
+ <h2>
+ <?=escapeHTML($USER['ATTR']['FULLNAME'])."\n"?>
+ <a class="brackets info" href="<?=$USER['URL']?>"><?=escapeHTML($USER['ATTR']['USERNAME'])?></a>
+ </h2>
+ </header>
+ <article>
+ <?=$USER['BODY']['HTML']?>
+ </article>
+</li> \ No newline at end of file
diff --git a/template/standard/html/user/list.php b/template/standard/html/user/list.php
new file mode 100644
index 0000000..922a3a4
--- /dev/null
+++ b/template/standard/html/user/list.php
@@ -0,0 +1,19 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: User List Template [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+?>
+<h1><i class="fa fa-user"></i><?=$Language->text('user_overview')?></h1>
+<p><?=$Language->template('user_base_heading_desc', $PAGINATION['THIS'])?></p>
+
+<ul class="item-list user">
+ <?php foreach($LIST['USERS'] as $user): ?>
+ <?php echo $user; ?>
+ <?php endforeach; ?>
+</ul>
+
+<?=$PAGINATION['HTML']?> \ No newline at end of file
diff --git a/template/standard/html/user/main.php b/template/standard/html/user/main.php
new file mode 100644
index 0000000..adc0534
--- /dev/null
+++ b/template/standard/html/user/main.php
@@ -0,0 +1,43 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Standard: User Main Template [Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# [see documentation] #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+?>
+<h1><i class="fa fa-user"></i><?=$Language->template('user_heading_text', [escapeHTML($USER['ATTR']['FULLNAME']), $USER['ATTR']['USERNAME']])?></h1>
+<p><em><?=$Language->template('user_heading_desc', [escapeHTML($USER['ATTR']['FULLNAME']), $COUNT['POST'], $COUNT['PAGE']])?></em></p>
+
+<section id="content" class="user">
+ <?=$USER['BODY']['HTML']?>
+</section>
+
+<section id="site-navi">
+
+ <?php if($USER['PREV']): ?>
+ <div><a href="<?=$USER['PREV']['URL']?>" title="<?=$Language->text('prev_user')?> »<?=escapeHTML($USER['PREV']['ATTR']['NAME'])?>«"><i class="fa fa-arrow-left"></i></a></div>
+ <?php else: ?>
+ <div><a class="disabled"><i class="fa fa-arrow-left"></i></a></div>
+ <?php endif; ?>
+
+ <?php if($USER['NEXT']): ?>
+ <div><a href="<?=$USER['NEXT']['URL']?>" title="<?=$Language->text('next_user')?> »<?=escapeHTML($USER['NEXT']['ATTR']['NAME'])?>«"><i class="fa fa-arrow-right"></i></a></div>
+ <?php else: ?>
+ <div><a class="disabled"><i class="fa fa-arrow-right"></i></a></div>
+ <?php endif; ?>
+
+</section>
+
+<script>
+ var prevPageURL = <?php echo json_encode($USER['PREV'] ? $USER['PREV']['URL'] : FALSE); ?>;
+ var nextPageURL = <?php echo json_encode($USER['NEXT'] ? $USER['NEXT']['URL'] : FALSE); ?>;
+
+ document.addEventListener('keyup', function(event) {
+ if(!event.ctrlKey && !event.shiftKey) {
+ (event.keyCode === 37 && prevPageURL) && (window.location.href = prevPageURL);
+ (event.keyCode === 39 && nextPageURL) && (window.location.href = nextPageURL);
+ }
+ }, false)
+</script> \ No newline at end of file
diff --git a/template/standard/lang/de.php b/template/standard/lang/de.php
new file mode 100644
index 0000000..333aab2
--- /dev/null
+++ b/template/standard/lang/de.php
@@ -0,0 +1,51 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Nerdmind: Internationalization [DE] Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# This file contains template internationalization strings for the DE language #
+# and is completely independend from the core internationalization strings. #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+
+$LANGUAGE['date_format'] = '[D].[M].[Y] [H]:[I]';
+
+$LANGUAGE['feed_only_posts'] = 'nur Beiträge';
+$LANGUAGE['feed_only_pages'] = 'nur Seiten';
+
+$LANGUAGE['navigation_home_text'] = 'Home';
+$LANGUAGE['navigation_home_desc'] = '%s';
+
+$LANGUAGE['navigation_search_text'] = 'Suche';
+$LANGUAGE['navigation_search_desc'] = 'Volltextsuche';
+
+$LANGUAGE['home_heading_text'] = 'Willkommen bei %s';
+$LANGUAGE['home_heading_desc'] = 'Hallo! Hier siehst du erst einmal die letzten %d veröffentlichten Beiträge. Viel Spaß!';
+
+$LANGUAGE['user_heading_text'] = '%s <code>[<a href="">%s</a>]</code>';
+$LANGUAGE['user_heading_desc'] = 'Bisher wurden von %s insgesamt <b>%d</b> Beiträge und <b>%d</b> Seiten veröffentlicht.';
+
+$LANGUAGE['post_base_heading_desc'] = '[Seite: <b>%d</b>] Hier siehst du alle veröffentlichten <strong>Beiträge</strong> nach dem Zeitpunkt der Veröffentlchung sortiert.';
+$LANGUAGE['page_base_heading_desc'] = '[Seite: <b>%d</b>] Hier siehst du alle veröffentlichten <strong>Seiten</strong> nach dem Zeitpunkt der Veröffentlchung sortiert.';
+$LANGUAGE['user_base_heading_desc'] = '[Seite: <b>%d</b>] Hier siehst du alle vorhandenen <strong>Benutzer</strong> nach dem Zeitpunkt der Erstellung sortiert.';
+
+$LANGUAGE['post_main_heading_text'] = '%s';
+$LANGUAGE['post_main_heading_desc'] = 'Von: %s (veröffentlicht am: <em>%s</em>)';
+
+$LANGUAGE['page_main_heading_text'] = '%s';
+$LANGUAGE['page_main_heading_desc'] = 'Von: %s (veröffentlicht am: <em>%s</em>)';
+
+$LANGUAGE['403_heading_text'] = 'Zugriff verweigert';
+$LANGUAGE['403_heading_desc'] = 'Der Zugriff auf diese Ressource des Servers wurde dir verweigert, da du die dafür notwendigen Berechtigungen nicht besitzt.';
+
+$LANGUAGE['404_heading_text'] = 'Nicht gefunden';
+$LANGUAGE['404_heading_desc'] = 'Die angeforderte Ressource konnte auf diesem Server nicht gefunden werden.';
+
+$LANGUAGE['search_base_heading_text'] = 'Volltextsuche';
+$LANGUAGE['search_base_heading_desc'] = 'Wenn du einen bestimmten <strong>Beitrag</strong> suchst, dann kann dir die <a href="https://dev.mysql.com/doc/refman/5.5/en/fulltext-boolean.html" target="_blank">Volltext-Suchfunktion</a> der MySQL-Datenbank bestimmt weiterhelfen.';
+
+$LANGUAGE['search_result_heading_text'] = 'Suchergebnisse für <code>%s</code>';
+$LANGUAGE['search_result_heading_desc'] = 'Herzlichen Glückwunsch, deine Suchanfrage scheint erfolgreich gewesen zu sein!';
+
+$LANGUAGE['search_form_placeholder'] = 'Suchbegriff eingeben …';
+?> \ No newline at end of file
diff --git a/template/standard/lang/en.php b/template/standard/lang/en.php
new file mode 100644
index 0000000..66e2a67
--- /dev/null
+++ b/template/standard/lang/en.php
@@ -0,0 +1,51 @@
+<?php
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# Nerdmind: Internationalization [EN] Thomas Lange <code@nerdmind.de>] #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+# #
+# This file contains template internationalization strings for the EN language #
+# and is completely independend from the core internationalization strings. #
+# #
+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
+
+$LANGUAGE['date_format'] = '[Y]-[M]-[D] [H]:[I]';
+
+$LANGUAGE['feed_only_posts'] = 'only posts';
+$LANGUAGE['feed_only_pages'] = 'only pages';
+
+$LANGUAGE['navigation_home_text'] = 'Home';
+$LANGUAGE['navigation_home_desc'] = '%s';
+
+$LANGUAGE['navigation_search_text'] = 'Search';
+$LANGUAGE['navigation_search_desc'] = 'Fulltext search';
+
+$LANGUAGE['home_heading_text'] = 'Welcome to %s';
+$LANGUAGE['home_heading_desc'] = 'Here you can see the last 10 published posts. Have fun!';
+
+$LANGUAGE['user_heading_text'] = '%s <code>[<a href="">%s</a>]</code>';
+$LANGUAGE['user_heading_desc'] = '%s has published a total count of <b>%d</b> posts and <b>%d</b> pages.';
+
+$LANGUAGE['post_base_heading_desc'] = '[Page: <b>%d</b>] Here you can see all published <strong>posts</strong> ordered by the date of publication.';
+$LANGUAGE['page_base_heading_desc'] = '[Page: <b>%d</b>] Here you can see all published <strong>pages</strong> ordered by the date of publication.';
+$LANGUAGE['user_base_heading_desc'] = '[Page: <b>%d</b>] Here you can see all existing <strong>users</strong> ordered by the date of creation.';
+
+$LANGUAGE['post_main_heading_text'] = '%s';
+$LANGUAGE['post_main_heading_desc'] = 'By: %s (published on: <em>%s</em>)';
+
+$LANGUAGE['page_main_heading_text'] = '%s';
+$LANGUAGE['page_main_heading_desc'] = 'By: %s (published on: <em>%s</em>)';
+
+$LANGUAGE['403_heading_text'] = 'Access denied';
+$LANGUAGE['403_heading_desc'] = 'You are denied to access this resource because you do not have the necessary permissions.';
+
+$LANGUAGE['404_heading_text'] = 'Not found';
+$LANGUAGE['404_heading_desc'] = 'The requested resource could not be found on this server.';
+
+$LANGUAGE['search_base_heading_text'] = 'Fulltext search';
+$LANGUAGE['search_base_heading_desc'] = 'If you are looking for a specific <strong>post</strong>, then the <a href="https://dev.mysql.com/doc/refman/5.5/en/fulltext-boolean.html" target="_blank">full-text search function</a> of the MySQL database could help you.';
+
+$LANGUAGE['search_result_heading_text'] = 'Search results for <code>%s</code>';
+$LANGUAGE['search_result_heading_desc'] = 'Congratulations, your search request seems to have been successful!';
+
+$LANGUAGE['search_form_placeholder'] = 'Enter search term …';
+?> \ No newline at end of file
diff --git a/template/standard/rsrc/font-awesome.min.css b/template/standard/rsrc/font-awesome.min.css
new file mode 100644
index 0000000..540440c
--- /dev/null
+++ b/template/standard/rsrc/font-awesome.min.css
@@ -0,0 +1,4 @@
+/*!
+ * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
+ * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}
diff --git a/template/standard/rsrc/font/font-awesome.woff2 b/template/standard/rsrc/font/font-awesome.woff2
new file mode 100644
index 0000000..4d13fc6
--- /dev/null
+++ b/template/standard/rsrc/font/font-awesome.woff2
Binary files differ
diff --git a/template/standard/rsrc/font/ruda-n-400.woff2 b/template/standard/rsrc/font/ruda-n-400.woff2
new file mode 100644
index 0000000..6435a0d
--- /dev/null
+++ b/template/standard/rsrc/font/ruda-n-400.woff2
Binary files differ
diff --git a/template/standard/rsrc/font/ruda-n-700.woff2 b/template/standard/rsrc/font/ruda-n-700.woff2
new file mode 100644
index 0000000..0066431
--- /dev/null
+++ b/template/standard/rsrc/font/ruda-n-700.woff2
Binary files differ
diff --git a/template/standard/rsrc/logo.png b/template/standard/rsrc/logo.png
new file mode 100644
index 0000000..7a62549
--- /dev/null
+++ b/template/standard/rsrc/logo.png
Binary files differ
diff --git a/template/standard/rsrc/main.css b/template/standard/rsrc/main.css
new file mode 100644
index 0000000..44547ec
--- /dev/null
+++ b/template/standard/rsrc/main.css
@@ -0,0 +1,202 @@
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Hyperlinks
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+a{color:#0060A0;text-decoration:none;}a:focus{background:#CCC;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Paragraphs
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+h1+p,h2+p,h3+p,h4+p,h5+p,h6+p{margin-top:0;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Headings
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+h1,h2,h3,h4,h5,h6{margin:0;font-weight:600;}
+h1{font-size:0.80rem;}h2{font-size:0.70rem;}
+h3{font-size:0.65rem;}h4{font-size:0.60rem;}
+h5{font-size:0.55rem;}h6{font-size:0.50rem;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Document
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+html,body{margin:0;padding:0;}
+html{font-size:1.25rem;color:#333;background:#CCC;-webkit-hyphens:auto;hyphens:auto;}
+body{font-family:Ruda,sans-serif;font-size:0.7rem;line-height:1.2rem;}
+
+#container{max-width:45rem;margin:1rem auto;border:0.05rem solid #AAA;background:#FFF;}
+
+main,#main-header > section{padding:1rem;box-sizing:border-box;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Header
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+#main-header > section{overflow:hidden;}
+#main-logo{height:1.75rem;display:block;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Footer
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+#main-footer{background:#EEE;border-top:0.05rem solid #AAA;padding:0.25rem 1rem;text-align:center;font-size:0.6rem;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Main Navigation
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+#main-navi{font-size:0.6rem;background:#EEE;border:0.05rem solid #AAA;border-left:none;border-right:none;padding:0 1rem;}
+#main-navi ul{list-style:none;margin:0;padding:0;}
+#main-navi li{display:inline;}
+#main-navi li .fa, h1 > .fa, h2 > .fa{margin-right:0.25rem;}
+#main-navi a{padding:0.25rem 0.3rem;color:inherit;text-decoration:none;text-align:center;display:inline-block;border:0.05rem solid transparent;border-top:none;border-bottom:none;}
+#main-navi a:hover, #main-navi a:focus{text-decoration:none;background:#DDD;border:0.05rem solid #AAA;border-top:none;border-bottom:none;}
+#main-navi li:last-child{float:right;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Site Navigation
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+#site-navi{clear:both;display:flex;box-sizing:border-box;justify-content:space-between;}
+#site-navi > div{display:flex;align-items:center;border:0.05rem solid #AAA;background:#EEE;}
+#site-navi > div > a{display:block;}
+#site-navi > section{display:flex;overflow:hidden;align-items:center;}
+#site-navi > section > div{border:0.05rem solid #AAA;background:#EEE;}
+
+#site-navi .disabled{pointer-events:none;color:#AAA;}
+#site-navi .active a{background:#CCC !important;font-weight:600;pointer-events: none;}
+
+#site-navi ol{list-style:none;margin:0;padding:0;}
+#site-navi li{float:left;display:inline-block;}
+#site-navi li+li{border-left:0.05rem solid #AAA;}
+#site-navi a{padding:0 0.5rem;text-decoration:none;color:inherit;display:inline-block;}
+#site-navi a:hover,#site-navi a:focus{background:#CCC;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Elements
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+pre{font-family:monospace;margin-bottom:1rem;overflow:auto;-moz-tab-size:4;tab-size:4;}
+code,pre{font-family:monospace;color:#008B45;}
+strong,label{font-weight:600;}
+img{border:none;max-width:100%;}
+main img{border:0.05rem solid #000;border-radius:0.15rem;}
+table img{border:none;border-radius:0;}
+.red{color:#B03060;}
+.head-link{font-size:0.6rem;float:right;}
+.head-link .fa{margin-right:0.125rem;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Brackets
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+.brackets a,a.brackets{text-decoration:none;}
+.brackets:after{content:"]"}
+.brackets:before{content:"["}
+a.brackets:before,a.brackets:after{color:#222;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Item List <ul>
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+.item-list{list-style:none;padding:0;}
+.item-list.page{}
+.item-list.post{}
+.item-list.user{}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Item List <li>
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+.item-list-li{background:#EEE;border:0.05rem solid #AAA;overflow:hidden;margin:0.5rem 0;}
+.item-list-li > header{padding:0.25rem 1rem;border-bottom:0.05rem solid #AAA;overflow:hidden;}
+.item-list-li > header h2{border:none !important;font-size:inherit;text-transform:uppercase;}
+.item-list-li > header h2 .info{float:right;font-size:0.7rem;font-weight:400;}
+.item-list-li > header a{color:inherit;}
+.item-list-li > article{padding:0 1rem;}
+.item-list-li > article img{display:block;}
+.item-list-li.page{}
+.item-list-li.post{}
+.item-list-li.user{}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Item content on main sites
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+#content{background:#EEE;border:0.05rem solid #AAA;margin:0.5rem 0;padding:0 1rem;}
+#content img{display:block;}
+#content.page{} /* different rules for page item */
+#content.post{} /* different rules for page item */
+#content.user{} /* different rules for page item */
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Responsive
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+#toogle-nav{display:none;clear:both;}
+#toogle-nav-label{display:none;cursor:pointer;font-size:1.25rem;text-align:center;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Form elements
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+input,select{background:#EEE;color:inherit;padding:0.2rem;border:0.1rem solid #AAA;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Table elements
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+table{width:100%;margin:0 0 1rem;}td{vertical-align:middle;}
+table,td{border-spacing:0;border-collapse:collapse;padding:0.5rem;border:0.05rem solid #000;}
+thead,tr:nth-child(even){background:#EEE;}
+thead > tr, th{font-weight:600;font-style:italic;}
+thead > tr > td, th > td{text-align:center;}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Responsive Level #1
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+@media only screen and (max-width:50rem) {
+ html{font-size:1.125rem;/*18px*/background-image:none !important;}
+ body{line-height:1.2rem;}
+ #container{margin:0;border-right:none;border-left:none;}
+}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Responsive Level #2
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+@media only screen and (max-width:37.5rem) {
+ main,#main-header > section{padding:1rem;}
+ #main-navi{padding:0.25rem 1rem;}
+ #main-navi a{border:0.05rem solid transparent;}
+ #main-navi a:hover, #main-navi a:focus{border:0.05rem solid #BBB;}
+ #main-navi > ul{display:none;float:none;}
+ #main-navi > ul > li a{display:block;text-align:left;padding:0 0.25rem;}
+ #main-navi{overflow:hidden;}
+ h2{border-bottom:0.05rem solid #888;}
+ #toogle-nav-label{display:block;}
+ #toogle-nav:checked + ul{display:block;}
+ #main-navi li:last-child{float:none;}
+
+ .item-list-li > header > h2{text-align:center;}
+ .item-list-li > header > h2 > .info{float:none;display:block;}
+}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* FontAwesome Main
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+.fa{display:inline-block;font:normal normal normal 14px/1 "FontAwesome";font-size:inherit;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* FontAwesome Icons
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+.fa-rss:before{content:"\f09e"}
+.fa-key:before{content:"\f084"}
+.fa-bars:before{content:"\f0c9"}
+.fa-user:before{content:"\f007"}
+.fa-home:before{content:"\f015"}
+.fa-search:before{content:"\f002"}
+.fa-rss-square:before{content:"\f143"}
+.fa-arrow-left:before{content:"\f060"}
+.fa-user-secret:before{content:"\f21b"}
+.fa-file-text-o:before{content:"\f0f6"}
+.fa-newspaper-o:before{content:"\f1ea"}
+.fa-arrow-right:before{content:"\f061"}
+.fa-exclamation-triangle:before{content:"\f071"}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Font "Font Awesome" [4.7.0]: SIL Open Font License (OFL)
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+@font-face{font-family:"FontAwesome";font-weight:400;src:url("font/font-awesome.woff2") format("woff2");}
+
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+/* Font "Ruda": SIL Open Font License (OFL)
+/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
+@font-face{font-family:Ruda;font-weight:400;src:url("font/ruda-n-400.woff2") format("woff2");}
+@font-face{font-family:Ruda;font-weight:700;src:url("font/ruda-n-700.woff2") format("woff2");} \ No newline at end of file