Riyyi
4 years ago
commit
a8056b66cc
67 changed files with 7759 additions and 0 deletions
@ -0,0 +1,31 @@
|
||||
# Non-project |
||||
.idea/ |
||||
.directory |
||||
|
||||
# Files |
||||
*.mwb.bak |
||||
config.php |
||||
composer.lock |
||||
syncconfig.sh |
||||
|
||||
# Directories |
||||
/public/* |
||||
!/public/css/ |
||||
/public/css/* |
||||
!/public/fonts/ |
||||
!/public/img/ |
||||
/public/img/* |
||||
!/public/js/ |
||||
/public/js/* |
||||
!/public/media/ |
||||
/public/media/* |
||||
/vendor/* |
||||
|
||||
# Unignore |
||||
!/public/.htaccess |
||||
!/public/css/style.css |
||||
!/public/img/favicon.png |
||||
!/public/index.php |
||||
!/public/js/app.js |
||||
!/public/js/site.js |
||||
!/public/media/.gitinclude |
@ -0,0 +1,29 @@
|
||||
<?php |
||||
|
||||
namespace App\Classes; |
||||
|
||||
class Config { |
||||
|
||||
protected static $config; |
||||
|
||||
//-------------------------------------// |
||||
|
||||
public static function load(): void |
||||
{ |
||||
if (file_exists('../config.php')) { |
||||
self::$config = require_once '../config.php'; |
||||
} |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
public static function c(string $index = ''): string |
||||
{ |
||||
if (_exists(self::$config, $index)) { |
||||
return self::$config[$index]; |
||||
} |
||||
|
||||
return ''; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,106 @@
|
||||
<?php |
||||
|
||||
namespace App\Classes; |
||||
|
||||
class Db { |
||||
|
||||
protected static $db; |
||||
|
||||
protected static $columns = []; |
||||
protected static $sections = []; |
||||
protected static $pages = []; |
||||
|
||||
//-------------------------------------// |
||||
|
||||
public static function load(): void { |
||||
try { |
||||
$host = Config::c('DB_HOST'); |
||||
$name = Config::c('DB_NAME'); |
||||
self::$db = new \PDO( |
||||
"mysql:host=$host;dbname=$name;charset=utf8mb4", |
||||
Config::c('DB_USERNAME'), |
||||
Config::c('DB_PASSWORD') |
||||
); |
||||
} catch (\PDOException $e) { |
||||
throw new \PDOException($e->getMessage(), (int)$e->getCode()); |
||||
} |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
/** |
||||
* Get the PDO connection object |
||||
* |
||||
* @return PDO |
||||
*/ |
||||
public static function get(): \PDO { |
||||
return self::$db; |
||||
} |
||||
|
||||
/** |
||||
* Get all columns |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function getColumns(): array |
||||
{ |
||||
return self::$columns; |
||||
} |
||||
|
||||
/** |
||||
* Get all sections |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function getSections(): array |
||||
{ |
||||
return self::$sections; |
||||
} |
||||
|
||||
/** |
||||
* Get all pages |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function getPages(): array |
||||
{ |
||||
return self::$pages; |
||||
} |
||||
|
||||
/** |
||||
* Store columns |
||||
* |
||||
* @param array $columns |
||||
* |
||||
* @return void |
||||
*/ |
||||
public static function setColumns(array $columns): void |
||||
{ |
||||
self::$columns = $columns; |
||||
} |
||||
|
||||
/** |
||||
* Store sections |
||||
* |
||||
* @param array $sections |
||||
* |
||||
* @return void |
||||
*/ |
||||
public static function setSections(array $sections): void |
||||
{ |
||||
self::$sections = $sections; |
||||
} |
||||
|
||||
/** |
||||
* Store pages |
||||
* |
||||
* @param array $pages |
||||
* |
||||
* @return void |
||||
*/ |
||||
public static function setPages(array $pages): void |
||||
{ |
||||
self::$pages = $pages; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,188 @@
|
||||
<?php |
||||
|
||||
namespace App\Classes; |
||||
|
||||
use \Klein\Klein; |
||||
|
||||
class Form { |
||||
|
||||
private $router; |
||||
|
||||
private $data = []; |
||||
private $resetLabel = ''; |
||||
private $submitLabel = 'Submit'; |
||||
private $errorKey = ''; |
||||
|
||||
public function __construct(Klein $router) |
||||
{ |
||||
$this->router = $router; |
||||
$this->router->service()->csrfToken = Session::token(); |
||||
$this->router->service()->form = $this; |
||||
$this->router->service()->injectView = '../app/views/form.php'; |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
public function addField(string $name, array $field): void |
||||
{ |
||||
// "name" => [ |
||||
// "label", |
||||
// "type", text, email, tel, password, radio, textarea, checkbox(?), comment |
||||
// "data", (for radio fields) [ 'value' => 'Label', 'value' => 'Label'] |
||||
// "rules", (server side rules) |
||||
// "message", (server side error message) |
||||
// "pattern", (client sided rule) |
||||
// "title", |
||||
// ], |
||||
|
||||
$this->data[$name] = [ |
||||
$field[0] ?? '', |
||||
$field[1] ?? '', |
||||
$field[2] ?? '', |
||||
$field[3] ?? '', |
||||
$field[4] ?? '', |
||||
$field[5] ?? '', |
||||
$field[6] ?? '', |
||||
]; |
||||
} |
||||
|
||||
public function validated(array $submit = []): bool |
||||
{ |
||||
$result = false; |
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') { |
||||
|
||||
$result = true; |
||||
|
||||
if (empty($submit)) { |
||||
$submit = $_POST; |
||||
} |
||||
|
||||
if (!Session::validateToken($submit)) { |
||||
$result = false; |
||||
} |
||||
|
||||
// Only check fields if CSRF token is valid |
||||
if ($result) { |
||||
// Check field rules |
||||
foreach ($this->data as $ruleName => $ruleValue) { |
||||
|
||||
$found = false; |
||||
$value = ''; |
||||
foreach ($submit as $submitName => $submitValue) { |
||||
|
||||
if ($ruleName == $submitName) { |
||||
$found = true; |
||||
$value = $submitValue; |
||||
break; |
||||
} |
||||
|
||||
if ($ruleValue[1] == 'comment') { |
||||
$found = true; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (!$found || !$this->matchRule($ruleName, $value)) { |
||||
$this->errorKey = $ruleName; |
||||
$result = false; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// If unsuccessful, remember the form fields |
||||
if (!$result) { |
||||
foreach (array_keys($this->data) as $name) { |
||||
if ($name == 'captcha') { |
||||
continue; |
||||
} |
||||
|
||||
$this->router->service()->{$name} = $submit[$name] ?? ''; |
||||
} |
||||
} |
||||
} |
||||
|
||||
Session::delete('captcha'); |
||||
|
||||
return $result; |
||||
} |
||||
|
||||
public function matchRule(string $key, string $value): bool |
||||
{ |
||||
// Get the rule(s) |
||||
$rule = $this->data[$key]; |
||||
$rules = explode('|', $rule[3]); |
||||
|
||||
if (array_search('required', $rules) !== false) { |
||||
if (empty($value)) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
if (array_search('email', $rules) !== false) { |
||||
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
if (array_search('tel', $rules) !== false) { |
||||
if (!filter_var($value, FILTER_VALIDATE_REGEXP, [ |
||||
'options' => [ |
||||
'regexp' => '/^([0-9]{2}-?[0-9]{8})|([0-9]{3}-?[0-9]{7})$/', |
||||
] |
||||
])) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
if (array_search('captcha', $rules) !== false) { |
||||
if (!Session::exists('captcha')) { |
||||
return false; |
||||
} |
||||
else if ($value != Session::get('captcha')) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
public function errorMessage(): string |
||||
{ |
||||
return $this->errorKey != '' ? $this->data[$this->errorKey][4] : ''; |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
public function getFields(): array |
||||
{ |
||||
return $this->data; |
||||
} |
||||
|
||||
public function getReset(): string |
||||
{ |
||||
return $this->resetLabel; |
||||
} |
||||
|
||||
public function getSubmit(): string |
||||
{ |
||||
return $this->submitLabel; |
||||
} |
||||
|
||||
public function setData(array $data): void |
||||
{ |
||||
$this->data = $data; |
||||
} |
||||
|
||||
public function setReset(string $resetLabel): void |
||||
{ |
||||
$this->resetLabel = $resetLabel; |
||||
} |
||||
|
||||
public function setSubmit(string $submitLabel): void |
||||
{ |
||||
$this->submitLabel = $submitLabel; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,72 @@
|
||||
<?php |
||||
|
||||
namespace App\Classes; |
||||
|
||||
use Tx\Mailer; |
||||
|
||||
class Mail { |
||||
|
||||
protected static $host; |
||||
protected static $port; |
||||
protected static $name; |
||||
protected static $username; |
||||
protected static $password; |
||||
protected static $to; |
||||
|
||||
public static function _init(): void { |
||||
self::$host = Config::c('MAIL_HOST'); |
||||
self::$port = Config::c('MAIL_PORT'); |
||||
self::$name = Config::c('MAIL_NAME'); |
||||
self::$username = Config::c('MAIL_USERNAME'); |
||||
self::$password = Config::c('MAIL_PASSWORD'); |
||||
self::$to = Config::c('MAIL_TO'); |
||||
} |
||||
|
||||
public static function send( |
||||
string $subject, string $message, string $to = '', string $from = ''): bool |
||||
{ |
||||
if ($to == '') { |
||||
$to = self::$to; |
||||
} |
||||
if ($from == '') { |
||||
$from = 'Website <'. self::$username . '>'; |
||||
} |
||||
|
||||
$headers = |
||||
'MIME-Version: 1.0' . "\r\n" . |
||||
'Content-type: text/html; charset=utf-8' . "\r\n" . |
||||
'From: ' . $from . "\r\n" . |
||||
'Reply-To: ' . $from . "\r\n" . |
||||
'X-Mailer: PHP/' . phpversion(); |
||||
|
||||
return mail($to, $subject, $message, $headers); |
||||
} |
||||
|
||||
public static function sendMail(string $subject, string $message, string $from = '', string $to = ''): bool |
||||
{ |
||||
if ($to == '') { |
||||
$to = self::$to; |
||||
} |
||||
if ($from == '') { |
||||
$from = self::$name; |
||||
} |
||||
if (empty(self::$host) || empty(self::$port) || |
||||
empty(self::$username) || empty(self::$password) || empty($to)) { |
||||
return false; |
||||
} |
||||
|
||||
$result = (new Mailer()) |
||||
->setServer(self::$host, self::$port, "tlsv1.2") |
||||
->setAuth(self::$username, self::$password) |
||||
->setFrom($from, self::$username) |
||||
->addTo('', $to) |
||||
->setSubject($subject) |
||||
->setBody($message) |
||||
->send(); |
||||
|
||||
return $result; |
||||
} |
||||
|
||||
} |
||||
|
||||
Mail::_init(); |
@ -0,0 +1,282 @@
|
||||
<?php |
||||
|
||||
namespace App\Classes; |
||||
|
||||
use App\Model\MediaModel; |
||||
|
||||
class Media { |
||||
|
||||
public static $pagination = 20; |
||||
public static $directory = 'media'; |
||||
|
||||
public static function errorMessage(int $errorCode): string |
||||
{ |
||||
// https://www.php.net/manual/en/features.file-upload.errors.php |
||||
$errorMessages = [ |
||||
0 => 'Uploaded with success.', |
||||
1 => 'Uploaded file exceeds the maximum upload size. (1)', // php.ini |
||||
2 => 'Uploaded file exceeds the maximum upload size. (2)', // HTML Form |
||||
3 => 'Uploaded file was only partially uploaded.', |
||||
4 => 'No file was uploaded.', |
||||
6 => 'Missing a temporary folder.', |
||||
7 => 'Failed to write file to disk.', |
||||
8 => 'A PHP extension stopped the file upload.', |
||||
9 => 'User was not logged in.', |
||||
10 => 'Missing media folder.', |
||||
11 => 'Uploaded file has invalid MIME type.', |
||||
12 => 'Uploaded file exceeds the maximum upload size. (3)', // Media.php |
||||
13 => 'Uploaded file already exists.', |
||||
14 => 'DB entry creation failed.', |
||||
15 => 'Moving file from temporary location failed.', |
||||
]; |
||||
|
||||
return $errorMessages[$errorCode]; |
||||
} |
||||
|
||||
public static function deleteMedia(int $id): bool |
||||
{ |
||||
if (!User::check()) { |
||||
return false; |
||||
} |
||||
|
||||
$media = MediaModel::find($id); |
||||
|
||||
if (!$media->exists()) { |
||||
return false; |
||||
} |
||||
|
||||
// Delete file |
||||
$file = self::$directory . '/' . $media->filename . '.' . $media->extension; |
||||
if (file_exists($file)) { |
||||
unlink($file); |
||||
} |
||||
|
||||
return $media->delete(); |
||||
} |
||||
|
||||
public static function uploadMedia(bool $overwrite = false): int |
||||
{ |
||||
// Check if User is logged in |
||||
if (!User::check()) { |
||||
return 9; |
||||
} |
||||
|
||||
// Check if "media" directory exists |
||||
if (!is_dir(self::$directory) && !mkdir(self::$directory, 0755)) { |
||||
return 10; |
||||
} |
||||
|
||||
$files = $_FILES['file']; |
||||
|
||||
// Check for file errors |
||||
foreach ($files['error'] as $error) { |
||||
if ($error != 0) { |
||||
return $error; |
||||
} |
||||
} |
||||
|
||||
if (!Media::checkMimeType($files['type'], $files['tmp_name'])) { |
||||
return 11; |
||||
} |
||||
|
||||
if (!Media::checkSize($files['size'])) { |
||||
return 12; |
||||
} |
||||
|
||||
// Append random string to filename that already exists |
||||
$nameExt = Media::duplicateName($files['name'], $overwrite); |
||||
|
||||
// Check if the file already exists |
||||
$hash = Media::hashExists($files['tmp_name']); |
||||
if (!$hash[0]) { |
||||
return 13; |
||||
} |
||||
|
||||
$count = count($files['name']); |
||||
for ($i = 0; $i < $count; $i++) { |
||||
$filename = $nameExt[0][$i]; |
||||
$extension = $nameExt[1][$i]; |
||||
$md5 = $hash[1][$i]; |
||||
$tmpName = $files['tmp_name'][$i]; |
||||
|
||||
// Store record |
||||
$media = MediaModel::create([ |
||||
'filename' => $filename, |
||||
'extension' => $extension, |
||||
'md5' => $md5, |
||||
]); |
||||
|
||||
if (!$media->exists()) { |
||||
return 14; |
||||
} |
||||
|
||||
// Store image |
||||
$name = self::$directory . '/'. $filename . '.' . $extension; |
||||
if (!move_uploaded_file($tmpName, $name)) { |
||||
return 15; |
||||
} |
||||
|
||||
// After storing successfully, remove old entries with duplicate names |
||||
if ($overwrite) { |
||||
Media::destroyDuplicates($filename, $extension); |
||||
} |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
private static function checkMimeType(array $fileTypes, array $fileTmpNames): bool |
||||
{ |
||||
$allowedMimeType = [ |
||||
// .tar.gz |
||||
'application/gzip', |
||||
'application/json', |
||||
'application/octet-stream', |
||||
'application/pdf', |
||||
'application/sql', |
||||
// .tar.xz |
||||
'application/x-xz', |
||||
'application/xml', |
||||
'application/zip', |
||||
'audio/mp3', |
||||
'audio/mpeg', |
||||
'audio/ogg', |
||||
'audio/wav', |
||||
'audio/webm', |
||||
'image/jpg', |
||||
'image/jpeg', |
||||
'image/png', |
||||
'image/gif', |
||||
'text/plain', |
||||
'text/csv', |
||||
'text/xml', |
||||
'video/mp4', |
||||
'video/webm', |
||||
// .doc |
||||
'application/msword', |
||||
// .xls |
||||
'application/vnd.ms-excel', |
||||
// .ppt |
||||
'application/vnd.ms-powerpoint', |
||||
// .docx |
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |
||||
// .xlsx |
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', |
||||
// .pptx |
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation', |
||||
]; |
||||
|
||||
// Files type check |
||||
$count = count($fileTypes); |
||||
for ($i = 0; $i < $count; $i++) { |
||||
if ($fileTypes[$i] != mime_content_type($fileTmpNames[$i]) || |
||||
!in_array($fileTypes[$i], $allowedMimeType)) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
private static function checkSize(array $fileSizes): bool |
||||
{ |
||||
// Files should not exceed 10MiB |
||||
foreach ($fileSizes as $fileSize) { |
||||
if ($fileSize > 10485760) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
private static function duplicateName(array $filenames, bool $overwrite): array |
||||
{ |
||||
// Split name from extension |
||||
$names = []; |
||||
$extensions = []; |
||||
foreach ($filenames as $name) { |
||||
$dotPos = strrpos($name, '.'); |
||||
$names[] = substr($name, 0, $dotPos); |
||||
$extensions[] = substr($name, $dotPos + 1); |
||||
} |
||||
|
||||
// Early return if names are specified to be overwritten |
||||
if ($overwrite) { |
||||
return [$names, $extensions]; |
||||
} |
||||
|
||||
// Get duplicate filenames |
||||
$in1 = str_repeat('?, ', count($names) - 1) . '?'; |
||||
$in2 = str_repeat('?, ', count($extensions) - 1) . '?'; |
||||
$data = array_merge($names, $extensions); |
||||
$duplicates = MediaModel::selectAll( |
||||
'*', "WHERE filename IN ($in1) AND extension IN ($in2)", $data, '?' |
||||
); |
||||
|
||||
foreach ($filenames as $key => $filename) { |
||||
$hasDuplicate = false; |
||||
foreach ($duplicates as $duplicate) { |
||||
if ($filename == $duplicate['filename'] . '.' . $duplicate['extension']) { |
||||
$hasDuplicate = true; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// Append to filename if there are duplicates |
||||
if ($hasDuplicate) { |
||||
$names[$key] = $names[$key] . '-' . _randomStr(5); |
||||
} |
||||
} |
||||
|
||||
return [$names, $extensions]; |
||||
} |
||||
|
||||
private static function hashExists(array $fileTmpNames): array |
||||
{ |
||||
$md5 = []; |
||||
foreach ($fileTmpNames as $tmpName) { |
||||
$md5[] = md5_file($tmpName); |
||||
} |
||||
|
||||
// If exact file already exists |
||||
$in = str_repeat('?, ', count($md5) - 1) . '?'; |
||||
$exists = MediaModel::selectAll( |
||||
'*', "WHERE md5 IN ($in)", $md5, '?' |
||||
); |
||||
|
||||
if (!empty($exists)) { |
||||
return [false]; |
||||
} |
||||
|
||||
return [true, $md5]; |
||||
} |
||||
|
||||
private static function destroyDuplicates(string $filename, string $extension): void |
||||
{ |
||||
$media = MediaModel::selectAll( |
||||
'*', 'WHERE filename = ? AND extension = ? ORDER BY id ASC', |
||||
[$filename, $extension], '?' |
||||
); |
||||
|
||||
if (!_exists($media)) { |
||||
return; |
||||
} |
||||
|
||||
foreach ($media as $key => $value) { |
||||
|
||||
// Dont delete the new entry |
||||
if ($key === array_key_last($media)) { |
||||
return; |
||||
} |
||||
|
||||
MediaModel::destroy($value['id']); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
// @Todo |
||||
// - If a file fails to store in the loop, destruct all files of that request, by tracking all IDs |
@ -0,0 +1,352 @@
|
||||
<?php |
||||
|
||||
namespace App\Classes; |
||||
|
||||
use Klein\Klein; |
||||
|
||||
use App\Classes\Db; |
||||
|
||||
use App\Model\SectionModel; |
||||
use App\Model\PageModel; |
||||
|
||||
class Router { |
||||
|
||||
protected static $router; |
||||
protected static $routes = []; |
||||
|
||||
public static function _init(): void { |
||||
self::$router = new Klein(); |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
/** |
||||
* Load all routes into the Klein object |
||||
* |
||||
* @return void |
||||
*/ |
||||
public static function fire(): void |
||||
{ |
||||
$path = parse_url($_SERVER['REQUEST_URI'])['path']; |
||||
$check = str_replace('.', '', $path); |
||||
|
||||
// If it's a dynamic file or the file doesn't exist, go through the router. |
||||
if ($path == $check || !file_exists(getcwd() . $path)) { |
||||
|
||||
Db::load(); |
||||
self::setDefaultLayout(); |
||||
self::loadConfigRoutes(); |
||||
self::loadDbRoutes(); |
||||
|
||||
// Process basic routes |
||||
foreach (self::$routes as $route) { |
||||
// If route does not match the base url |
||||
if ($route[0] != $path) { |
||||
continue; |
||||
} |
||||
|
||||
// ["/example/my-page", "ExampleController", "action", "" : ["view", "title", "description"]], |
||||
self::addBasicRoute(['GET', 'POST'], $route); |
||||
|
||||
break; |
||||
} |
||||
|
||||
self::createNavigation(); |
||||
self::setHttpError(); |
||||
|
||||
self::$router->dispatch(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Add CRUD routes |
||||
* Example usage: Router::resource('/example', 'CrudController'); |
||||
* |
||||
* @param string $route The URL location |
||||
* @param string $controller Controller to handle this route |
||||
* |
||||
* @return void |
||||
*/ |
||||
public static function resource(string $route, string $controller): void |
||||
{ |
||||
/* |
||||
* HTTP Verb Part (URL) Action (Method) |
||||
* |
||||
* GET /route indexAction |
||||
* GET /route/create createAction |
||||
* POST /route storeAction |
||||
* GET /route/{id} showAction |
||||
* GET /route/{id}/edit editAction |
||||
* PUT/PATCH /route/{id} updateAction |
||||
* DELETE /route/{id} destroyAction |
||||
*/ |
||||
|
||||
self::addRoute(['GET'], [$route, $controller, 'indexAction']); |
||||
self::addRoute(['GET'], [$route . '/create', $controller, 'createAction']); |
||||
self::addRoute(['POST'], [$route, $controller, 'storeAction']); |
||||
self::addRoute(['GET'], [$route . '/[i:id]', $controller, 'showAction', ['id']]); |
||||
self::addRoute(['GET'], [$route . '/[i:id]/edit', $controller, 'editAction', ['id']]); |
||||
self::addRoute(['PUT', 'PATCH'], [$route . '/[i:id]', $controller, 'updateAction', ['id']]); |
||||
self::addRoute(['DELETE'], [$route . '/[i:id]', $controller, 'destroyAction', ['id']]); |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
protected static function setDefaultLayout(): void |
||||
{ |
||||
self::$router->respond(function ($request, $response, $service) { |
||||
$service->layout('../app/views/layouts/default.php'); |
||||
}); |
||||
} |
||||
|
||||
protected static function loadConfigRoutes(): void |
||||
{ |
||||
if (file_exists('../route.php')) { |
||||
self::$routes = require_once '../route.php'; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Add all pages in the Db to self::$routes |
||||
* |
||||
* @return void |
||||
*/ |
||||
protected static function loadDbRoutes(): void |
||||
{ |
||||
// Load all sections from Db |
||||
$sections = SectionModel::selectAll('*', 'WHERE `active` = 1 ORDER BY `order` ASC'); |
||||
|
||||
// Return if no sections |
||||
if (!_exists($sections)) { |
||||
return; |
||||
} |
||||
|
||||
// Load all pages from Db |
||||
$pages = PageModel::selectAll('DISTINCT page.*', ' |
||||
LEFT JOIN page_has_content ON page_has_content.page_id = page.id |
||||
LEFT JOIN content ON content.id = page_has_content.content_id |
||||
WHERE page.active = 1 AND content.active = 1 |
||||
ORDER BY page.order ASC; |
||||
'); |
||||
|
||||
// Return if no pages |
||||
if (!_exists($pages)) { |
||||
return; |
||||
} |
||||
|
||||
// Select id column |
||||
$section = array_column($sections, 'section', 'id'); |
||||
|
||||
// Loop through all pages |
||||
foreach ($pages as $pageKey => $page) { |
||||
// Skip if section isn't created / active |
||||
if (!_exists($section, $page['section_id'])) { continue; } |
||||
|
||||
// url = /section/page |
||||
$url = '/' . $section[$page['section_id']] . '/' . $page['page']; |
||||
|
||||
// Add route |
||||
self::$routes[] = [$url, 'PageController', 'route', $page['id']]; |
||||
} |
||||
|
||||
// Cache sections and pages |
||||
Db::setSections($sections); |
||||
Db::setPages($pages); |
||||
} |
||||
|
||||
protected static function addRoute(array $method = [], array $data = []): void |
||||
{ |
||||
if (!_exists($method) || !_exists($data)) { |
||||
return; |
||||
} |
||||
|
||||
$route = $data[0] ?? ''; |
||||
$controller = $data[1] ?? ''; |
||||
$action = $data[2] ?? ''; |
||||
$param = $data[3] ?? []; |
||||
if ($route == '' || $controller == '' || $action == '') { |
||||
return; |
||||
} |
||||
|
||||
// Create Klein route |
||||
self::$router->respond($method, $route, function($request, $response, $service) |
||||
use($controller, $action, $param) { |
||||
|
||||
// Create new Controller object |
||||
$controller = '\App\Controllers\\' . $controller; |
||||
$controller = new $controller(self::$router); |
||||
|
||||
$stillValid = true; |
||||
|
||||
// If method does not exist in object |
||||
if (!method_exists($controller, $action)) { |
||||
$stillValid = false; |
||||
} |
||||
|
||||
// If no valid permissions |
||||
if ($controller->getAdminSection() && |
||||
$controller->getLoggedIn() == false) { |
||||
$stillValid = false; |
||||
} |
||||
|
||||
// Call Controller action |
||||
if ($stillValid) { |
||||
|
||||
// Loop through params |
||||
$params = []; |
||||
foreach ($param as $name) { |
||||
$params[] = $request->param($name); |
||||
} |
||||
|
||||
return $controller->{$action}(...$params); |
||||
} |
||||
else { |
||||
$controller->throw404(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
protected static function addBasicRoute(array $method = [], array $route = []): void |
||||
{ |
||||
if (!_exists($method) || !_exists($route)) { |
||||
return; |
||||
} |
||||
|
||||
// Create Klein route |
||||
self::$router->respond($method, $route[0], function() use($route) { |
||||
|
||||
// Create new Controller object |
||||
$controller = '\App\Controllers\\' . $route[1]; |
||||
$controller = new $controller(self::$router); |
||||
|
||||
// Complete action variable |
||||
if ($route[2] == '') { |
||||
$route[2] = 'indexAction'; |
||||
} |
||||
else { |
||||
$route[2] .= 'Action'; |
||||
} |
||||
|
||||
$stillValid = true; |
||||
|
||||
// If method does not exist in object |
||||
if (!method_exists($controller, $route[2])) { |
||||
$stillValid = false; |
||||
} |
||||
|
||||
// If no valid permissions |
||||
if ($controller->getAdminSection() && |
||||
$controller->getLoggedIn() == false) { |
||||
$stillValid = false; |
||||
} |
||||
|
||||
// Call Controller action |
||||
if ($stillValid) { |
||||
if (is_array($route[3])) { |
||||
return $controller->{$route[2]}( |
||||
$route[3][0] ?? '', |
||||
$route[3][1] ?? '', |
||||
$route[3][2] ?? '' |
||||
); |
||||
} |
||||
else if ($route[3] != '') { |
||||
return $controller->{$route[2]}($route[3]); |
||||
} |
||||
else { |
||||
return $controller->{$route[2]}(); |
||||
} |
||||
} |
||||
else { |
||||
$controller->throw404(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public static function createNavigation(): void |
||||
{ |
||||
// Pull from cache |
||||
$sections = Db::getSections(); |
||||
$pages = Db::getPages(); |
||||
|
||||
// [ |
||||
// [ |
||||
// 'section url', |
||||
// 'title', |
||||
// ['page url', 'title'], |
||||
// ['page url', 'titleOfPage2'], |
||||
// ], |
||||
// [], |
||||
// [], |
||||
// ] |
||||
|
||||
$navigation = []; |
||||
|
||||
// Generate sections |
||||
foreach ($sections as $section) { |
||||
// Skip hidden sections |
||||
if ($section['hide_navigation'] == '1') { |
||||
continue; |
||||
} |
||||
|
||||
// Add URL, title to ID |
||||
$navigation[$section['id']] = [ |
||||
$section['section'], $section['title'] |
||||
]; |
||||
} |
||||
|
||||
// Generate pages |
||||
foreach ($pages as $page) { |
||||
// Skip hidden sections |
||||
if (!_exists($navigation, $page['section_id'])) { |
||||
continue; |
||||
} |
||||
|
||||
// Skip hidden pages |
||||
if ($page['hide_navigation'] == '1') { |
||||
continue; |
||||
} |
||||
|
||||
// Add [URL, title] to ID |
||||
$navigation[$page['section_id']][] = [ |
||||
$page['page'], $page['title'] |
||||
]; |
||||
} |
||||
|
||||
self::$router->service()->navigation = $navigation; |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
protected static function setHttpError(): void |
||||
{ |
||||
self::$router->onHttpError(function($code) { |
||||
$service = self::$router->service(); |
||||
|
||||
switch ($code) { |
||||
case 404: |
||||
|
||||
$service->escape = function (?string $string) { |
||||
return htmlentities($string, ENT_QUOTES | ENT_HTML5, 'UTF-8'); |
||||
}; |
||||
|
||||
self::$router->response()->sendHeaders(true, true); |
||||
$service->pageTitle = 'Error 404 (Not Found)'; |
||||
$service->render('../app/views/errors/404.php'); |
||||
break; |
||||
} |
||||
}); |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
public static function getRoutes(): array |
||||
{ |
||||
return self::$routes; |
||||
} |
||||
|
||||
} |
||||
|
||||
Router::_init(); |
||||
|
||||
// @Todo |
||||
// - combine addRoute and addBasicroute functionality |
@ -0,0 +1,300 @@
|
||||
<?php |
||||
|
||||
namespace App\Classes; |
||||
|
||||
use stdClass; |
||||
|
||||
class Session { |
||||
|
||||
/** |
||||
* The session attributes. |
||||
* |
||||
* @var array |
||||
*/ |
||||
private static $attributes; |
||||
|
||||
/** |
||||
* Session store started status. |
||||
* |
||||
* @var bool |
||||
*/ |
||||
protected static $started = false; |
||||
|
||||
//-------------------------------------// |
||||
|
||||
/** |
||||
* Start the PHP session |
||||
* |
||||
* @return void |
||||
*/ |
||||
public static function start(): void |
||||
{ |
||||
if (self::$started) { |
||||
return; |
||||
} |
||||
|
||||
session_set_cookie_params(['secure' => true, 'httponly' => true, 'samesite' => 'Strict']); |
||||
session_start(); |
||||
|
||||
self::$attributes = &$_SESSION; |
||||
|
||||
if (!self::exists('_token')) { |
||||
self::regenerateToken(); |
||||
} |
||||
|
||||
self::$started = true; |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
/** |
||||
* Get all of the session data. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function all(): array |
||||
{ |
||||
return self::$attributes; |
||||
} |
||||
|
||||
/** |
||||
* Checks if a key exists. |
||||
* |
||||
* @param string|array $key |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public static function isset($key): bool |
||||
{ |
||||
$placeholder = new stdClass; |
||||
return self::get($key, $placeholder) !== $placeholder; |
||||
} |
||||
|
||||
/** |
||||
* Check if a key is present and not null. |
||||
* |
||||
* @param string|array $key |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public static function exists($key): bool |
||||
{ |
||||
return !is_null(self::get($key)); |
||||
} |
||||
|
||||
/** |
||||
* Get an item from the session. |
||||
* |
||||
* @param string $key |
||||
* @param mixed $default |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public static function get(string $key, $default = null) |
||||
{ |
||||
if (array_key_exists($key, self::$attributes)) { |
||||
return self::$attributes[$key]; |
||||
} |
||||
|
||||
if (strpos($key, '.') === false) { |
||||
return $default; |
||||
} |
||||
|
||||
// Get item using the "dot" notation |
||||
$array = self::$attributes; |
||||
foreach (explode('.', $key) as $segment) { |
||||
if (is_array($array) && array_key_exists($segment, $array)) { |
||||
$array = $array[$segment]; |
||||
} |
||||
else { |
||||
return $default; |
||||
} |
||||
} |
||||
|
||||
return $array; |
||||
} |
||||
|
||||
/** |
||||
* Put a key / value pair or array of key / value pairs in the session. |
||||
* |
||||
* @param string|array $key |
||||
* @param mixed $value |
||||
* @return void |
||||
*/ |
||||
public static function put($key, $value = null) |
||||
{ |
||||
if (!is_array($key)) { |
||||
$key = [$key => $value]; |
||||
} |
||||
|
||||
foreach ($key as $arrayKey => $arrayValue) { |
||||
|
||||
// Set array item to a given value using the "dot" notation |
||||
|
||||
$keys = explode('.', $arrayKey); |
||||
|
||||
$array = &self::$attributes; |
||||
foreach ($keys as $i => $key) { |
||||
if (count($keys) === 1) { |
||||
break; |
||||
} |
||||
|
||||
unset($keys[$i]); |
||||
|
||||
// If key doesnt exist at this depth, create empty array holder |
||||
if (!isset($array[$key]) || !is_array($array[$key])) { |
||||
$array[$key] = []; |
||||
} |
||||
|
||||
$array = &$array[$key]; |
||||
} |
||||
|
||||
$array[array_shift($keys)] = $arrayValue; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Get an item from the session, or store the default value. |
||||
* |
||||
* @param string $key |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public static function emplace(string $key) |
||||
{ |
||||
$value = self::get($key); |
||||
if (!is_null($value)) { |
||||
return $value; |
||||
} |
||||
|
||||
self::put($key, $value); |
||||
return $value; |
||||
} |
||||
|
||||
/** |
||||
* Push a value onto a session array. |
||||
* |
||||
* @param string $key |
||||
* @param mixed $value |
||||
* |
||||
* @return void |
||||
*/ |
||||
public static function push(string $key, $value): void |
||||
{ |
||||
$array = self::get($key, []); |
||||
$array[] = $value; |
||||
self::put($key, $array); |
||||
} |
||||
|
||||
/** |
||||
* Get the value of a given key and then remove it. |
||||
* |
||||
* @param string $keys |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public static function pull(string $key, $default = null) |
||||
{ |
||||
$result = self::get($key, $default); |
||||
self::delete($key); |
||||
|
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* Delete one or many items from the session. |
||||
* |
||||
* @param string|array $keys |
||||
* |
||||
* @return void |
||||
*/ |
||||
public static function delete($keys): void |
||||
{ |
||||
$start = &self::$attributes; |
||||
|
||||
$keys = (array)$keys; |
||||
|
||||
if (count($keys) === 0) { |
||||
return; |
||||
} |
||||
|
||||
foreach ($keys as $key) { |
||||
// Delete top-level if non-"dot" notation key |
||||
if (self::exists($key)) { |
||||
unset(self::$attributes[$key]); |
||||
|
||||
continue; |
||||
} |
||||
|
||||
// Delete using "dot" notation key |
||||
|
||||
$parts = explode('.', $key); |
||||
|
||||
// Move to the start of the array each pass |
||||
$array = &$start; |
||||
|
||||
// Traverse into the associative array |
||||
while (count($parts) > 1) { |
||||
$part = array_shift($parts); |
||||
|
||||
if (isset($array[$part]) && is_array($array[$part])) { |
||||
$array = &$array[$part]; |
||||
} |
||||
else { |
||||
continue 2; |
||||
} |
||||
} |
||||
|
||||
unset($array[array_shift($parts)]); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Remove all of the items from the session. |
||||
* |
||||
* @return void |
||||
*/ |
||||
public static function flush(): void |
||||
{ |
||||
self::$attributes = []; |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
/** |
||||
* Get the CSRF token value. |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function token() |
||||
{ |
||||
return self::get('_token'); |
||||
} |
||||
|
||||
/** |
||||
* Regenerate the CSRF token value. |
||||
* |
||||
* @return void |
||||
*/ |
||||
public static function regenerateToken() |
||||
{ |
||||
self::put('_token', _randomStr(40)); |
||||
} |
||||
|
||||
/** |
||||
* Validate the CSRF token value to the given value. |
||||
* |
||||
* @param array $array |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public static function validateToken(array $array = null): bool |
||||
{ |
||||
if (is_array($array) && array_key_exists('_token', $array) && $array['_token'] == self::token()) { |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,174 @@
|
||||
<?php |
||||
|
||||
namespace App\Classes; |
||||
|
||||
use App\Model\UserModel; |
||||
|
||||
class User { |
||||
|
||||
public static function check(): bool |
||||
{ |
||||
$success = false; |
||||
|
||||
// Session |
||||
if (Session::exists('user')) { |
||||
$success = true; |
||||
} |
||||
|
||||
// If cookie is set, try to login |
||||
if (!$success && |
||||
_exists($_COOKIE, 'id') && |
||||
_exists($_COOKIE, 'username') && |
||||
_exists($_COOKIE, 'salt') && |
||||
_exists($_COOKIE, 'toggle')) { |
||||
|
||||
$user = UserModel::find($_COOKIE['id']); |
||||
|
||||
if ($user->exists() && |
||||
$_COOKIE['username'] == $user->username && |
||||
$_COOKIE['salt'] == $user->salt) { |
||||
$success = true; |
||||
|
||||
self::setSession($_COOKIE['id'], $_COOKIE['username'], |
||||
$_COOKIE['salt'], $_COOKIE['toggle']); |
||||
} |
||||
} |
||||
|
||||
return $success; |
||||
} |
||||
|
||||
public static function login(string $username, string $password, string $rememberMe): bool |
||||
{ |
||||
$user = UserModel::search(['username' => $username]); |
||||
|
||||
$success = false; |
||||
if ($user->exists() && $user->failed_login_attempt <= 2) { |
||||
$saltPassword = $user->salt . $password; |
||||
if (password_verify($saltPassword, $user->password)) { |
||||
$success = true; |
||||
|
||||
// On successful login, set failed_login_attempt to 0 |
||||
if ($user->failed_login_attempt > 0) { |
||||
$user->failed_login_attempt = 0; |
||||
$user->save(); |
||||
} |
||||
} |
||||
else { |
||||
$user->failed_login_attempt++; |
||||
$user->save(); |
||||
} |
||||
} |
||||
|
||||
if (!$success) { |
||||
self::logout(); |
||||
|
||||
return false; |
||||
} |
||||
|
||||
// Set session |
||||
self::setSession($user->id, $user->username, $user->salt, 1); |
||||
|
||||
// Set cookie |
||||
if ($rememberMe == '1') { |
||||
$time = time() + (3600 * 24 * 7); |
||||
self::setCookie($time, $user->id, $user->username, $user->salt, 1); |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
public static function logout(): void |
||||
{ |
||||
Session::delete('user'); |
||||
|
||||
// Destroy user login cookie |
||||
$time = time() - 3600; |
||||
self::setCookie($time, 0, '', '', 0); |
||||
} |
||||
|
||||
public static function getUser(string $id = '', string $username = '', string $email = ''): UserModel |
||||
{ |
||||
if ($id == '' && $username == '' && $email == '' && self::check()) { |
||||
$id = Session::get('user.id'); |
||||
$username = Session::get('user.username'); |
||||
} |
||||
|
||||
return UserModel::search([ |
||||
'id' => $id, |
||||
'username' => $username, |
||||
'email' => $email, |
||||
], 'OR'); |
||||
} |
||||
|
||||
public static function toggle(): void |
||||
{ |
||||
if (self::check()) { |
||||
// Toggle session |
||||
Session::put('user.toggle', !Session::get('user.toggle')); |
||||
// Toggle cookie |
||||
self::setCookieToggle(Session::get('user.toggle')); |
||||
} |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
protected static function setSession( |
||||
int $id, string $username, string $salt, int $toggle): void |
||||
{ |
||||
Session::put('user', [ |
||||
'id' => $id, |
||||
'username' => $username, |
||||
'salt' => $salt, |
||||
'toggle' => $toggle, |
||||
]); |
||||
} |
||||
|
||||
protected static function setCookie( |
||||
int $time, int $id, string $username, string $salt, int $toggle): void |
||||
{ |
||||
if (_exists($_SERVER, 'HTTPS') && $_SERVER['HTTPS'] == 'on') { |
||||
$domain = Config::c('APP_NAME'); |
||||
$options = [ |
||||
'expires' => $time, |
||||
'path' => '/', |
||||
'domain' => $domain, |
||||
'secure' => true, |
||||
'httponly' => true, |
||||
'samesite' => 'Strict' |
||||
]; |
||||
setcookie('id', $id, $options); |
||||
setcookie('username', $username, $options); |
||||
setcookie('salt', $salt, $options); |
||||
setcookie('toggle', $toggle, $options); |
||||
} |
||||
} |
||||
|
||||
protected static function setCookieToggle(int $toggle): void |
||||
{ |
||||
if (_exists($_SERVER, 'HTTPS') && $_SERVER['HTTPS'] == 'on') { |
||||
$domain = Config::c('APP_NAME'); |
||||
$options = [ |
||||
'expires' => time() + (3600 * 24 * 7), |
||||
'path' => '/', |
||||
'domain' => $domain, |
||||
'secure' => true, |
||||
'httponly' => true, |
||||
'samesite' => 'Strict' |
||||
]; |
||||
setcookie('toggle', $toggle, $options); |
||||
} |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
public static function getToggle(): int |
||||
{ |
||||
return self::check() ? Session::get('user.toggle') : 0; |
||||
} |
||||
|
||||
public static function getSession(): array |
||||
{ |
||||
return self::check() ? Session::get('user') : []; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,24 @@
|
||||
<?php |
||||
|
||||
namespace App\Controllers; |
||||
|
||||
use App\Classes\User; |
||||
|
||||
class AdminController extends PageController { |
||||
|
||||
public function indexAction(): void { |
||||
$this->router->service()->user = User::getUser(); |
||||
|
||||
parent::view('', 'Admin'); |
||||
} |
||||
|
||||
public function toggleAction(): void { |
||||
User::toggle(); |
||||
echo User::getToggle() ? '1' : '0'; |
||||
} |
||||
|
||||
public function syntaxAction(): void { |
||||
parent::view(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,133 @@
|
||||
<?php |
||||
|
||||
namespace App\Controllers; |
||||
|
||||
use App\Classes\Config; |
||||
use App\Classes\Db; |
||||
use App\Classes\Session; |
||||
use App\Classes\User; |
||||
|
||||
class BaseController { |
||||
|
||||
protected $router; |
||||
protected $section; |
||||
protected $page; |
||||
protected $loggedIn; |
||||
protected $url; |
||||
protected $adminSection; |
||||
|
||||
//-------------------------------------// |
||||
|
||||
public function __construct(\Klein\Klein $router = null) |
||||
{ |
||||
$this->router = $router; |
||||
|
||||
$request = $this->router->request()->uri(); |
||||
$request = parse_url($request)['path']; |
||||
$request = explode("/", $request); |
||||
|
||||
if (array_key_exists(1, $request)) { |
||||
$this->section = $request[1]; |
||||
} |
||||
if (array_key_exists(2, $request)) { |
||||
$this->page = $request[2]; |
||||
} |
||||
|
||||
// Set login status |
||||
$this->loggedIn = User::check(); |
||||
$this->router->service()->loggedIn = $this->loggedIn; |
||||
|
||||
// Set url https://site.com/section/page |
||||
$this->url = Config::c('APP_URL'); |
||||
$this->url .= _exists([$this->section]) ? '/' . $this->section : ''; |
||||
$this->url .= _exists([$this->page]) ? '/' . $this->page : ''; |
||||
$this->router->service()->url = $this->url; |
||||
|
||||
// If Admin section |
||||
$this->adminSection = $this->section == 'admin'; |
||||
$this->router->service()->adminSection = $this->adminSection; |
||||
|
||||
// Clear alert |
||||
$this->setAlert('', ''); |
||||
// Load alert set on the previous page |
||||
$this->loadAlert(); |
||||
|
||||
// View helper method |
||||
$this->router->service()->escape = function (?string $string) { |
||||
return htmlentities($string, ENT_QUOTES | ENT_HTML5, 'UTF-8'); |
||||
}; |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
public function throw404(): void |
||||
{ |
||||
$this->router->response()->sendHeaders(true, true); |
||||
$service = $this->router->service(); |
||||
$service->pageTitle = 'Error 404 (Not Found)'; |
||||
$service->render('../app/views/errors/404.php'); |
||||
exit(); |
||||
} |
||||
|
||||
/** |
||||
* Set alert for the current page |
||||
* |
||||
* @param string $type Color of the message (success/danger/warning/info) |
||||
* @param string $message The message to display |
||||
* |
||||
* @return void |
||||
*/ |
||||
public function setAlert(string $type, string $message): void |
||||
{ |
||||
$this->router->service()->type = $type; |
||||
$this->router->service()->message = $message; |
||||
} |
||||
|
||||
/** |
||||
* Set alert for the next page |
||||
* |
||||
* @param string $type Color of the message (success/danger/warning/info) |
||||
* @param string $message The message to display |
||||
* |
||||
* @return void |
||||
*/ |
||||
public function setAlertNext(string $type, string $message): void |
||||
{ |
||||
Session::put('type', $type); |
||||
Session::put('message', $message); |
||||
} |
||||
|
||||
/** |
||||
* Load alert set on the previous page |
||||
* |
||||
* @return void |
||||
*/ |
||||
public function loadAlert(): void |
||||
{ |
||||
if (Session::exists('type') && Session::exists('message')) { |
||||
$this->setAlert(Session::get('type'), Session::get('message')); |
||||
Session::delete(['type', 'message']); |
||||
} |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
public function getSection(): string |
||||
{ |
||||
return $this->section; |
||||
} |
||||
|
||||
public function getLoggedIn(): bool |
||||
{ |
||||
return $this->loggedIn; |
||||
} |
||||
|
||||
public function getAdminSection(): bool |
||||
{ |
||||
return $this->adminSection; |
||||
} |
||||
|
||||
} |
||||
|
||||
// @Todo |
||||
// - Image lazy loading |
@ -0,0 +1,249 @@
|
||||
<?php |
||||
|
||||
namespace App\Controllers; |
||||
|
||||
use App\Classes\Session; |
||||
|
||||
class CrudController extends PageController { |
||||
|
||||
public static $pagination = 10; |
||||
|
||||
//-------------------------------------// |
||||
|
||||
/** |
||||
* Display a listing of the resource. |
||||
* |
||||
* @return void |
||||
*/ |
||||
public function indexAction(): void |
||||
{ |
||||
$modelName = $this->getModelName(); |
||||
$model = "\App\Model\\{$modelName}Model"; |
||||
$model = new $model; |
||||
|
||||
// ?page=x |
||||
$page = 1; |
||||
if (_exists($_GET, 'page') && is_numeric($_GET['page'])) { |
||||
$page = $_GET['page']; |
||||
} |
||||
|
||||
$rows = $model->all($page, self::$pagination); |
||||
|
||||
// Set empty value |
||||
if (!_exists($rows)) { |
||||
$rows = []; |
||||
} |
||||
|
||||
$this->router->service()->attributes = $model->getAttributesRules(); |
||||
$this->router->service()->csrfToken = Session::token(); |
||||
$this->router->service()->rows = $rows; |
||||
$this->router->service()->title = $modelName; |
||||
|
||||
// Set page variables |
||||
$this->router->service()->page = $page; |
||||
$pages = ceil($model->count() / self::$pagination); |
||||
$pages > 1 |
||||
? $this->router->service()->pages = $pages |
||||
: $this->router->service()->pages = 1; |
||||
|
||||
parent::view("{$this->section}/crud/index"); |
||||
} |
||||
|
||||
/** |
||||
* Show the form for creating a new resource. |
||||
* |
||||
* @return void |
||||
*/ |
||||
public function createAction(): void |
||||
{ |
||||
$model = "\App\Model\\{$this->getModelName()}Model"; |
||||
$model = new $model; |
||||
|
||||
$attributes = $model->getAttributesRules(); |
||||
|
||||
// Get dropdown data |
||||
$dropdownData = []; |
||||
foreach ($attributes as $key => $attribute) { |
||||
if ($attribute[1] == 'dropdown') { |
||||
$dropdownData[$key] = $model->getDropdownData($attribute[0]); |
||||
} |
||||
} |
||||
|
||||
$this->router->service()->attributes = $attributes; |
||||
$this->router->service()->csrfToken = Session::token(); |
||||
$this->router->service()->dropdownData = $dropdownData; |
||||
parent::view("{$this->section}/crud/create"); |
||||
} |
||||
|
||||
/** |
||||
* Store a newly created resource in storage. |
||||
* |
||||
* @return void |
||||
*/ |
||||
public function storeAction(): void |
||||
{ |
||||
$modelName = $this->getModelName(); |
||||
$model = "\App\Model\\{$modelName}Model"; |
||||
$model = new $model; |
||||
|
||||
$token = Session::validateToken($_POST); |
||||
|
||||
$token && $model->fill($_POST) && $model->save() |
||||
? $this->setAlertNext('success', "$modelName successfully created.") |
||||
: $this->setAlertNext('danger', "$modelName could not be created!"); |
||||
|
||||
$this->router->response()->redirect($this->url); |
||||
} |
||||
|
||||
/** |
||||
* Display the specified resource. |
||||
* |
||||
* @param int $id |
||||
* |
||||
* @return void |
||||
*/ |
||||
public function showAction(int $id): void |
||||
{ |
||||
$model = "\App\Model\\{$this->getModelName()}Model"; |
||||
$model = $model::find($id); |
||||
|
||||
if (!$model->exists()) { |
||||
parent::throw404(); |
||||
} |
||||
|
||||
$this->router->service()->model = $model; |
||||
$this->router->service()->attributes = $model->getAttributesRules(); |
||||
parent::view("{$this->section}/crud/show"); |
||||
} |
||||
|
||||
/** |
||||
* Show the form for editing the specified resource. |
||||
* |
||||
* @param int $id |
||||
* |
||||
* @return void |
||||
*/ |
||||
public function editAction(int $id): void |
||||
{ |
||||
$model = "\App\Model\\{$this->getModelName()}Model"; |
||||
$model = $model::find($id); |
||||
|
||||
if (!$model->exists()) { |
||||
parent::throw404(); |
||||
} |
||||
|
||||
$attributes = $model->getAttributesRules(); |
||||
|
||||
// Get dropdown data |
||||
$dropdownData = []; |
||||
foreach ($attributes as $key => $attribute) { |
||||
if ($attribute[1] == 'dropdown') { |
||||
$dropdownData[$key] = $model->getDropdownData($attribute[0]); |
||||
} |
||||
} |
||||
|
||||
$this->router->service()->attributes = $attributes; |
||||
$this->router->service()->csrfToken = Session::token(); |
||||
$this->router->service()->dropdownData = $dropdownData; |
||||
$this->router->service()->model = $model; |
||||
parent::view("{$this->section}/crud/edit"); |
||||
} |
||||
|
||||
/** |
||||
* Update the specified resource in storage. |
||||
* |
||||
* @param int $id |
||||
* |
||||
* @return void |
||||
*/ |
||||
public function updateAction(int $id): void |
||||
{ |
||||
$modelName = $this->getModelName(); |
||||
$model = "\App\Model\\{$modelName}Model"; |
||||
$model = $model::find($id); |
||||
|
||||
if (!$model->exists()) { |
||||
$this->setAlertNext('danger', "$modelName does not exist!"); |
||||
} |
||||
else { |
||||
// Read PUT request |
||||
$this->parsePhpInput($_PUT); |
||||
|
||||
$token = Session::validateToken($_PUT); |
||||
|
||||
$token && $model->fill($_PUT) && $model->save() |
||||
? $this->setAlertNext('success', "$modelName successfully updated.") |
||||
: $this->setAlertNext('danger', "$modelName could not be updated!"); |
||||
} |
||||
|
||||
echo $this->url; |
||||
} |
||||
|
||||
/** |
||||
* Remove the specified resource from storage. |
||||
* |
||||
* @param int $id |
||||
* |
||||
* @return void |
||||
*/ |
||||
public function destroyAction(int $id): void |
||||
{ |
||||
// Read DELETE request |
||||
$this->parsePhpInput($_DELETE); |
||||
|
||||
$token = Session::validateToken($_DELETE); |
||||
|
||||
$modelName = $this->getModelName(); |
||||
$model = "\App\Model\\{$modelName}Model"; |
||||
|
||||
$token && $model::destroy($id) |
||||
? $this->setAlertNext('success', "$modelName successfully deleted.") |
||||
: $this->setAlertNext('danger', "$modelName could not be deleted!"); |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
/** |
||||
* Generate the model name from the /section/page url |
||||
* |
||||
* @return string The model name |
||||
*/ |
||||
private function getModelName(): string |
||||
{ |
||||
$model = $this->page; |
||||
$model = str_replace('-', ' ', $model); |
||||
$model = str_replace('_', ' ', $model); |
||||
$model = ucwords($model); |
||||
$model = str_replace(' ', '', $model); |
||||
|
||||
return $model; |
||||
} |
||||
|
||||
/** |
||||
* PUT/DELETE requests aren't handled by PHP automatically yet. |
||||
* Parse them from php://input instead. |
||||
* |
||||
* @param ?array &$result Array filled with input data |
||||
* |
||||
* @return void |
||||
*/ |
||||
private function parsePhpInput(?array &$result): void |
||||
{ |
||||
// Parse json or form-encoded formatted data |
||||
if (strpos($_SERVER['CONTENT_TYPE'], "application/json") !== false) { |
||||
$result = json_decode(file_get_contents("php://input"), true); |
||||
} |
||||
else if (strpos($_SERVER['CONTENT_TYPE'], "application/x-www-form-urlencoded") !== false) { |
||||
parse_str(file_get_contents("php://input"), $result); |
||||
|
||||
// Cleanup & HTML entities |
||||
foreach ($result as $key => $value) { |
||||
unset($result[$key]); |
||||
$result[str_replace('amp;', '', $key)] = $value; |
||||
} |
||||
} |
||||
|
||||
$_REQUEST = array_merge($_REQUEST, $result); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,98 @@
|
||||
<?php |
||||
|
||||
namespace App\Controllers; |
||||
|
||||
use App\Classes\Config; |
||||
use App\Classes\Db; |
||||
use App\Classes\Router; |
||||
use App\Classes\Session; |
||||
|
||||
class IndexController extends PageController { |
||||
|
||||
public function indexAction(): void |
||||
{ |
||||
// Pull pages from cache |
||||
$pages = Db::getPages(); |
||||
|
||||
parent::routeAction( |
||||
array_search('home', array_column($pages, 'page', 'id'))); |
||||
} |
||||
|
||||
public function captchaAction(): void |
||||
{ |
||||
header('Content-type: image/jpeg'); |
||||
|
||||
if (!Session::exists('captcha')) { |
||||
Session::put('captcha', _randomStr(4, '0123456789')); |
||||
} |
||||
|
||||
$imageWidth = 151; |
||||
$imageHeight = 51; |
||||
|
||||
// Text |
||||
$textSize = 30; |
||||
$textFont = 'fonts/captcha.otf'; |
||||
$text = Session::get('captcha'); |
||||
|
||||
// Generate position |
||||
$randPosX = rand(0, 40); |
||||
$randPosY = rand(35, 45); |
||||
|
||||
// Calculate rotation from the position |
||||
$rotationFactorUp = 1.0 - (($randPosY - 40.0) / 5.0); |
||||
$rotationFactorDown = 1.0 - ((40.0 - $randPosY) / 5.0); |
||||
// Clamp between 0.0-1.0 |
||||
$rotationFactorUp = max(0.0, min(1.0, $rotationFactorUp)); |
||||
$rotationFactorDown = max(0.0, min(1.0, $rotationFactorDown)); |
||||
$rotation = rand(-8 * $rotationFactorUp, 8 * $rotationFactorDown); |
||||
|
||||
// Create image |
||||
$image = imagecreate($imageWidth, $imageHeight); |
||||
imagecolorallocate($image, 255, 255, 255); |
||||
|
||||
// Render number |
||||
$textColor = imagecolorallocate($image, 0, 0, 0); |
||||
imagettftext($image, $textSize, $rotation, $randPosX, $randPosY, |
||||
$textColor, $textFont, $text); |
||||
|
||||
// Render grid pattern |
||||
$lineColor = imagecolorallocate($image, 73, 106, 164); |
||||
$cord = 0; |
||||
for ($i = 1; $i <= 31; $i++) { |
||||
imageline($image, $cord, 0, $cord, 50, $lineColor); |
||||
imageline($image, 0, $cord, 150, $cord, $lineColor); |
||||
$cord = $cord + 5; |
||||
} |
||||
|
||||
imagejpeg($image); |
||||
exit(); |
||||
} |
||||
|
||||
public function sitemapAction(): void |
||||
{ |
||||
$xml = new \SimpleXMLElement('<urlset/>'); |
||||
|
||||
// Config routes |
||||
$routes = array_column(Router::getRoutes(), '0'); |
||||
|
||||
// Remove /admin and /test pages |
||||
foreach ($routes as $key => $route) { |
||||
if (strpos($route, '/admin') !== false || |
||||
strpos($route, '/test') !== false) { |
||||
|
||||
unset($routes[$key]); |
||||
} |
||||
} |
||||
|
||||
foreach ($routes as $route) { |
||||
$url = $xml->addChild('url'); |
||||
$loc = Config::c('APP_URL') . $route; |
||||
|
||||
$url->addChild('loc', $loc); |
||||
} |
||||
|
||||
Header('Content-type: text/xml'); |
||||
print($xml->asXML()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,232 @@
|
||||
<?php |
||||
|
||||
namespace App\Controllers; |
||||
|
||||
use App\Classes\Config; |
||||
use App\Classes\Db; |
||||
use App\Classes\Form; |
||||
use App\Classes\User; |
||||
use App\Classes\Mail; |
||||
|
||||
class LoginController extends PageController { |
||||
|
||||
public function loginAction(string $view, string $title): void { |
||||
$form = new Form($this->router); |
||||
$form->addField('username', [ |
||||
'Username*', |
||||
'text', |
||||
'', |
||||
'required', |
||||
'Username is required.', |
||||
]); |
||||
$form->addField('password', [ |
||||
'Password*', |
||||
'password', |
||||
'', |
||||
'required', |
||||
'Password is required.', |
||||
]); |
||||
$form->addField('rememberMe', [ |
||||
'', |
||||
'checkbox', |
||||
['1' => 'Remember me'], |
||||
]); |
||||
|
||||
$form->setSubmit('Sign in'); |
||||
|
||||
if ($form->validated()) { |
||||
if (User::login($_POST['username'], $_POST['password'], $_POST['rememberMe'])) { |
||||
$this->setAlert('success', 'Successfully signed in, redirecting...'); |
||||
|
||||
// Set delayed redirect URL |
||||
$this->router->service()->redirectURL = Config::c('APP_URL') . '/admin'; |
||||
} |
||||
else { |
||||
$user = User::getUser('', $_POST['username']); |
||||
if ($user->exists() && $user->failed_login_attempt >= 5) { |
||||
$this->setAlert('danger', 'User has been blocked.'); |
||||
} |
||||
else { |
||||
$this->setAlert('danger', 'Invalid username and password combination.'); |
||||
} |
||||
} |
||||
} |
||||
else if ($_SERVER['REQUEST_METHOD'] == 'POST') { |
||||
if(!_exists($_POST, 'username') || !_exists($_POST, 'password')) { |
||||
$this->setAlert('danger', 'Please fill out both fields.'); |
||||
} |
||||
else { |
||||
$this->setAlert('danger', 'Could not sign in.'); |
||||
} |
||||
} |
||||
|
||||
$this->router->service()->password = ""; |
||||
parent::view($view, $title); |
||||
} |
||||
|
||||
public function resetAction(string $view, string $title): void { |
||||
|
||||
// Send request |
||||
if (!_exists($_GET, 'uid') || !_exists($_GET, 'reset-key')) { |
||||
$this->requestResetForm(); |
||||
} |
||||
// Reset password |
||||
else { |
||||
$this->resetPasswordForm(); |
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] != 'POST') { |
||||
$this->router->service()->newPassword = true; |
||||
$user = User::getUser($_GET['uid']); |
||||
if (!$user->exists() || $_GET['reset-key'] != $user->reset_key) { |
||||
$this->setAlert('danger', 'Link expired.'); |
||||
} |
||||
} |
||||
} |
||||
|
||||
parent::view($view, $title); |
||||
} |
||||
|
||||
public function logoutAction(): void { |
||||
User::logout(); |
||||
|
||||
$this->router->response()->redirect('/'); |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
private function requestResetForm(): void { |
||||
// Only have required on 1 field |
||||
$usernameRule = !_exists($_POST, 'reset-password-email') ? 'required' : ''; |
||||
$emailRule = !_exists($_POST, 'reset-password-username') ? 'required|email' : ''; |
||||
|
||||
$form = new Form($this->router); |
||||
$form->addField('reset-password-username', [ |
||||
'Username', |
||||
'text', |
||||
'', |
||||
"$usernameRule", |
||||
'Username is required.', |
||||
]); |
||||
$form->addField('reset-password-email', [ |
||||
'Email', |
||||
'email', |
||||
'', |
||||
"$emailRule", |
||||
'Please enter a valid email address.', |
||||
'[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$', |
||||
'Valid email address' |
||||
]); |
||||
|
||||
$form->setSubmit('Send request'); |
||||
|
||||
if ($form->validated()) { |
||||
$this->requestResetSend(); |
||||
} |
||||
else if ($_SERVER['REQUEST_METHOD'] == 'POST') { |
||||
if (!_exists($_POST, 'reset-password-username') && |
||||
!_exists($_POST, 'reset-password-email')) { |
||||
$this->setAlert('danger', 'Please fill out one of the fields.'); |
||||
} |
||||
else { |
||||
$error = $form->errorMessage(); |
||||
$this->setAlert('danger', $error ? $error : 'Could not send request.'); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private function requestResetSend(): void { |
||||
$user = User::getUser('', $_POST['reset-password-username'], $_POST['reset-password-email']); |
||||
if ($user->exists()) { |
||||
|
||||
// Generate new reset key |
||||
$user->reset_key = _randomStr(25); |
||||
$user->save(); |
||||
|
||||
// Send reset mail |
||||
$subject = 'Password reset request - ' . Config::c('APP_NAME'); |
||||
|
||||
$resetUrl = Config::c('APP_URL') . "/reset-password?uid={$user->id}&reset-key={$user->reset_key}"; |
||||
$message = " |
||||
Click the link below to reset your password:<br> |
||||
<a href='$resetUrl'>$resetUrl</a> |
||||
"; |
||||
|
||||
if (Mail::send($subject, $message, $user->email)) { |
||||
$this->setAlert('success', 'Successfully requested password reset.'); |
||||
} |
||||
else { |
||||
$this->setAlert('danger', 'Password reset failed.'); |
||||
} |
||||
} |
||||
else { |
||||
$this->setAlert('danger', 'User was not found.'); |
||||
} |
||||
} |
||||
|
||||
private function resetPasswordForm(): void { |
||||
// Add _GETs to form post url |
||||
$uid = $_GET['uid']; |
||||
$resetKey = $_GET['reset-key']; |
||||
$this->router->service()->url .= "?uid=$uid&reset-key=$resetKey"; |
||||
|
||||
$form = new Form($this->router); |
||||
$form->addField('password', [ |
||||
'New password*', |
||||
'password', |
||||
'', |
||||
'required', |
||||
'New password is required.', |
||||
]); |
||||
$form->addField('password-again', [ |
||||
'New password again*', |
||||
'password', |
||||
'', |
||||
'required', |
||||
'New password again is required.', |
||||
]); |
||||
|
||||
$form->setSubmit('Reset password'); |
||||
|
||||
if ($form->validated()) { |
||||
$this->resetPasswordSend(); |
||||
} |
||||
else if ($_SERVER['REQUEST_METHOD'] == 'POST') { |
||||
if (!_exists($_POST, 'password') || |
||||
!_exists($_POST, 'password-again')) { |
||||
$this->setAlert('danger', 'Please fill out all fields.'); |
||||
} |
||||
else { |
||||
$error = $form->errorMessage(); |
||||
$this->setAlert('danger', $error ? $error : 'Could not reset password.'); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private function resetPasswordSend(): void |
||||
{ |
||||
$user = User::getUser($_GET['uid']); |
||||
if ($user->exists()) { |
||||
(bool)$notEmptyKey = $user->reset_key != ''; |
||||
(bool)$correctKey = $user->reset_key == $_GET['reset-key']; |
||||
(bool)$identicalPass = $_POST['password'] == $_POST['password-again']; |
||||
} |
||||
if ($notEmptyKey && $correctKey && $identicalPass) { |
||||
|
||||
$user->salt = _randomStr(15); |
||||
$user->password = password_hash($user->salt . $_POST['password'], PASSWORD_BCRYPT, ['cost', 12]); |
||||
$user->reset_key = ''; |
||||
$user->save(); |
||||
|
||||
$this->setAlert('success', 'Successfully changed password.'); |
||||
} |
||||
|
||||
// Display error message |
||||
if (!$user || !$notEmptyKey || !$correctKey) { |
||||
$this->setAlert('danger', 'Invalid password reset link.'); |
||||
} |
||||
else if (!$identicalPass) { |
||||
$this->setAlert('danger', 'Fields did not match.'); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,117 @@
|
||||
<?php |
||||
|
||||
namespace App\Controllers; |
||||
|
||||
use App\Classes\Config; |
||||
use App\Classes\Media; |
||||
|
||||
use App\Model\LogModel; |
||||
use App\Model\MediaModel; |
||||
use App\Model\UserModel; |
||||
|
||||
class MediaController extends PageController { |
||||
|
||||
/** |
||||
* Display a listing of the resource. |
||||
* |
||||
* @return void |
||||
*/ |
||||
public function indexAction(): void |
||||
{ |
||||
$this->mediaPage(); |
||||
parent::view(); |
||||
} |
||||
|
||||
/** |
||||
* Store a newly created resource in storage. |
||||
* |
||||
* @return void |
||||
*/ |
||||
public function storeAction(): void |
||||
{ |
||||
$name = ucfirst($this->page); |
||||
|
||||
$overwrite = _exists($_POST, 'overwrite'); |
||||
|
||||
$error = Media::uploadMedia($overwrite); |
||||
if (!$error) { |
||||
$this->setAlert('success', "$name successfully created."); |
||||
} |
||||
else { |
||||
$this->setAlert('danger', Media::errorMessage($error)); |
||||
} |
||||
|
||||
$this->mediaPage(); |
||||
parent::view(); |
||||
} |
||||
|
||||
/** |
||||
* Remove the specified resource from storage. |
||||
* |
||||
* @param int $id |
||||
* |
||||
* @return void |
||||
*/ |
||||
public function destroyAction(int $id): void |
||||
{ |
||||
$name = ucfirst($this->page); |
||||
|
||||
if (Media::deleteMedia($id)) { |
||||
$this->setAlertNext('success', "$name successfully deleted."); |
||||
} |
||||
else { |
||||
$this->setAlertNext('danger', "$name could not be deleted!"); |
||||
} |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
protected function mediaPage(): void { |
||||
// ?page=x |
||||
$page = 1; |
||||
if (_exists($_GET, 'page') && is_numeric($_GET['page'])) { |
||||
$page = $_GET['page']; |
||||
} |
||||
|
||||
$mediaModel = new MediaModel; |
||||
|
||||
// Get all Media of the page |
||||
$media = $mediaModel->all($page, Media::$pagination); |
||||
|
||||
// Get all the connected Logs |
||||
$log = null; |
||||
if (_exists($media)) { |
||||
$logId = array_column($media, 'log_id'); |
||||
$log = LogModel::findAll($logId); |
||||
} |
||||
|
||||
// Get all the connected Users |
||||
$user = null; |
||||
if (_exists($log)) { |
||||
$uploaderId = array_column($log, 'user_id'); |
||||
$user = UserModel::findAll($uploaderId); |
||||
} |
||||
|
||||
// Set empty values |
||||
if (!_exists($media) || !_exists($log) || !_exists($user)) { |
||||
$media = []; |
||||
$log = [["user_id" => "0"]]; |
||||
$user = [["username" => "Could not load users.."]]; |
||||
} |
||||
|
||||
// Set view Media variables |
||||
$this->router->service()->media = $media; |
||||
$this->router->service()->log = $log; |
||||
$this->router->service()->user = $user; |
||||
|
||||
// Set view Page variables |
||||
$this->router->service()->page = $page; |
||||
$pages = ceil($mediaModel->count() / Media::$pagination); |
||||
$pages > 1 |
||||
? $this->router->service()->pages = $pages |
||||
: $this->router->service()->pages = 1; |
||||
|
||||
$this->router->service()->fileUrl = Config::c('APP_URL') . '/' . Media::$directory . '/'; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,186 @@
|
||||
<?php |
||||
|
||||
namespace App\Controllers; |
||||
|
||||
use App\Classes\Db; |
||||
use App\Model\Model; |
||||
use App\Model\ContentModel; |
||||
use App\Model\PageHasContentModel; |
||||
use App\Model\SectionHasContentModel; |
||||
|
||||
class PageController extends BaseController { |
||||
|
||||
/** |
||||
* Path of the page view files. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $views = '../app/views/'; |
||||
|
||||
//-------------------------------------// |
||||
|
||||
/** |
||||
* Create a new page controller instance. |
||||
* |
||||
* @param Klein $router |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function __construct(\Klein\Klein $router = null) |
||||
{ |
||||
parent::__construct($router); |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
/** |
||||
* Handle page request with Db stored content. |
||||
* |
||||
* @param int $id |
||||
* |
||||
* @return void |
||||
*/ |
||||
public function routeAction(int $id): void |
||||
{ |
||||
// Pull pages from cache |
||||
$pages = Db::getPages(); |
||||
$page = array_search($id, array_column($pages, 'id')); |
||||
$page = $pages[$page]; |
||||
|
||||
$title = $page['title'] ?? ''; |
||||
$metaDescription = $page['meta_description'] ?? ''; |
||||
|
||||
// Load linked content |
||||
$pageHasContent = new PageHasContentModel; |
||||
$sectionHasContent = new SectionHasContentModel; |
||||
$contents = array_merge( |
||||
(array)$this->loadLinkedContent($pageHasContent, 'page', $id), |
||||
(array)$this->loadLinkedContent($sectionHasContent, 'section', $page['section_id'])); |
||||
|
||||
// Exit if nothing was found |
||||
if (!_exists($contents)) { |
||||
parent::throw404(); |
||||
} |
||||
|
||||
$sideContent = in_array('2', array_column($contents, 'type')); |
||||
|
||||
$this->router->service()->contents = $contents; |
||||
$this->router->service()->sideContent = $sideContent; |
||||
$this->view('content', $title, $metaDescription); |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
/** |
||||
* Load all content blocks linked to the provided Model. |
||||
* |
||||
* @param Model $model |
||||
* @param string $column |
||||
* @param int $id |
||||
* |
||||
* @return null|array |
||||
*/ |
||||
protected function loadLinkedContent(Model $model, string $column, int $id): ?array |
||||
{ |
||||
// Load all the Model <-> Content link data |
||||
$hasContent = $model::selectAll('*', " |
||||
WHERE {$column}_id = :id |
||||
ORDER BY {$model->getSort()} ASC", [ |
||||
[':id', $id, \PDO::PARAM_INT], |
||||
] |
||||
); |
||||
|
||||
// Exit if nothing was found |
||||
if (!_exists($hasContent)) { |
||||
return null; |
||||
} |
||||
|
||||
// Get all the content |
||||
$contentIds = array_column($hasContent, 'content_id'); |
||||
$contents = ContentModel::findAll($contentIds); |
||||
|
||||
// Exit if nothing was found |
||||
if (!_exists($contents)) { |
||||
return null; |
||||
} |
||||
|
||||
// Remove inactive content |
||||
foreach ($contents as $key => $content) { |
||||
if ($content['active'] == "0") { |
||||
unset($contents[$key]); |
||||
} |
||||
} |
||||
$contents = array_values($contents); |
||||
|
||||
return $contents; |
||||
} |
||||
|
||||
/** |
||||
* Render page view with title and meta description. |
||||
* |
||||
* @param string $view |
||||
* @param string $pageTitle |
||||
* @param string $metaDescription |
||||
* |
||||
* @return void |
||||
*/ |
||||
protected function view( |
||||
string $view = '', string $pageTitle = '', string $metaDescription = ''): void |
||||
{ |
||||
if ($view != '') { |
||||
$view = $this->fileExists($this->views . $view . '.php'); |
||||
} |
||||
|
||||
if ($this->page == null) { |
||||
if ($view == '' && $this->section == '') { |
||||
// / |
||||
$view = $this->fileExists($this->views . 'home.php'); |
||||
} |
||||
|
||||
if ($view == '') { |
||||
// /example.php |
||||
$view = $this->fileExists($this->views . $this->section . '.php'); |
||||
} |
||||
|
||||
if ($view == '') { |
||||
// /example/index.php |
||||
$view = $this->fileExists($this->views . $this->section . '/index.php'); |
||||
} |
||||
} |
||||
else if ($view == '') { |
||||
// /example/my-page.php |
||||
$view = $this->fileExists($this->views . $this->section . '/' . $this->page . '.php'); |
||||
} |
||||
|
||||
if ($view != '') { |
||||
$pageTitle != '' |
||||
? $this->router->service()->pageTitle = $pageTitle |
||||
: $this->router->service()->pageTitle = ucfirst(str_replace('-', ' ', $this->page)); |
||||
$this->router->service()->metaDescription = $metaDescription; |
||||
$this->router->service()->render($view); |
||||
} |
||||
else { |
||||
parent::throw404(); |
||||
} |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
/** |
||||
* Loop back filename if it exists, empty string otherwise. |
||||
* |
||||
* @param string $file |
||||
* |
||||
* @return string |
||||
*/ |
||||
private function fileExists(string $file): string |
||||
{ |
||||
return file_exists($file) ? $file : ''; |
||||
} |
||||
|
||||
} |
||||
|
||||
// @Todo |
||||
// - Fix line 32, breaks if no DB content! |
||||
// - Implement page.description (meta) |
||||
// - Use page.title instead of content.title (?) |
@ -0,0 +1,25 @@
|
||||
<?php |
||||
|
||||
namespace App\Controllers; |
||||
|
||||
use App\Classes\Config; |
||||
use App\Classes\Db; |
||||
use App\Classes\User; |
||||
use App\Classes\Mail; |
||||
use App\Classes\Media; |
||||
use App\Classes\Session; |
||||
|
||||
use App\Model\MediaModel; |
||||
use App\Model\Model; |
||||
use App\Model\SectionModel; |
||||
use App\Model\PageModel; |
||||
use App\Model\UserModel; |
||||
use App\Model\LogModel; |
||||
|
||||
class TestController extends PageController { |
||||
|
||||
public function indexAction(): void { |
||||
parent::throw404(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,90 @@
|
||||
<?php |
||||
|
||||
/** |
||||
* Check if array element exists |
||||
* |
||||
* @param array $array The array to check for element |
||||
* @param string $key The element to find |
||||
* |
||||
* @return bool True if element eixsts |
||||
*/ |
||||
function _exists(array $array = null, string $key = '0'): bool { |
||||
return isset($array[$key]) && !empty($array[$key]); |
||||
} |
||||
|
||||
/** |
||||
* Cut off string after the found character |
||||
* |
||||
* @param string $string The input string |
||||
* @param string $fromCharacter The string is cut after this character |
||||
* |
||||
* @return string The new cut string |
||||
*/ |
||||
function _trim(string $string, string $fromCharacter): string { |
||||
$position = strpos($string, $fromCharacter); |
||||
$end = strlen($string); |
||||
return substr($string, 0, $position !== false ? $position : $end); |
||||
} |
||||
|
||||
/** |
||||
* Generate a cryptographically secure random string |
||||
* |
||||
* @param int $length Length of the string |
||||
* @param string $keyspace Possible characters the string can have |
||||
* |
||||
* @return string The generated string |
||||
*/ |
||||
function _randomStr(int $length, string $keyspace = |
||||
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'): string { |
||||
|
||||
$pieces = []; |
||||
$max = mb_strlen($keyspace, '8bit') - 1; |
||||
|
||||
for ($i = 0; $i < $length; $i++) { |
||||
$pieces[] = $keyspace[random_int(0, $max)]; |
||||
} |
||||
|
||||
return implode('', $pieces); |
||||
} |
||||
|
||||
/** |
||||
* Print variable inside of a <pre> and exit |
||||
* |
||||
* @param mixed[] $output The variable (single/array) to print |
||||
* |
||||
* @return void Nothing |
||||
*/ |
||||
function _log($output): void { |
||||
echo '<pre>'; |
||||
var_dump($output); |
||||
echo '</pre>'; |
||||
die(); |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
if (!function_exists('session')) { |
||||
/** |
||||
* Get / set the specified session value. |
||||
* If key is an array, treat as a setter. |
||||
* |
||||
* @param string|array $key |
||||
* @param mixed $default |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
function session($key = null, $default = null) |
||||
{ |
||||
$session = "\App\Classes\Session"; |
||||
|
||||
if (is_null($key)) { |
||||
return $session; |
||||
} |
||||
|
||||
if (is_array($key)) { |
||||
return $session::put($key); |
||||
} |
||||
|
||||
return $session::get($key, $default); |
||||
} |
||||
} |
@ -0,0 +1,50 @@
|
||||
<?php |
||||
|
||||
namespace App\Model; |
||||
|
||||
use App\Traits\Log; |
||||
|
||||
class ContentModel extends Model { |
||||
|
||||
use Log { delete as deleteLog; } |
||||
|
||||
protected $table = 'content'; |
||||
protected $sort = 'title'; |
||||
|
||||
// Attribute rules |
||||
// Name | Type | Required | Filtered |
||||
public $rules = [ |
||||
["content", "textarea", 0, 0], |
||||
["title", "text", 0, 0], |
||||
["type", "dropdown", 1, 0], |
||||
["hide_title", "checkbox", 1, 0], |
||||
["hide_background", "checkbox", 1, 0], |
||||
["active", "checkbox", 1, 0], |
||||
["log_id", "text", 1, 1], |
||||
]; |
||||
|
||||
//-------------------------------------// |
||||
|
||||
// Generate the dropdown data |
||||
public function getDropdownData(string $type): array |
||||
{ |
||||
if ($type == 'type') { |
||||
return [0 => 'Select type', 1 => 'Page content', 2 => 'Side block']; |
||||
} |
||||
|
||||
return []; |
||||
} |
||||
|
||||
public function delete(): bool |
||||
{ |
||||
if (self::query( |
||||
"DELETE FROM `page_has_{$this->table}` WHERE `{$this->table}_$this->primaryKey` = :id", [ |
||||
[':id', $this->{$this->primaryKey}], |
||||
] |
||||
) === null) { |
||||
return false; |
||||
} |
||||
|
||||
return $this->deleteLog(); |
||||
} |
||||
} |
@ -0,0 +1,7 @@
|
||||
<?php |
||||
|
||||
namespace App\Model; |
||||
|
||||
class LogModel extends Model { |
||||
protected $table = 'log'; |
||||
} |
@ -0,0 +1,14 @@
|
||||
<?php |
||||
|
||||
namespace App\Model; |
||||
|
||||
use App\Traits\Log; |
||||
|
||||
class MediaModel extends Model { |
||||
|
||||
use Log; |
||||
|
||||
protected $table = 'media'; |
||||
protected $sort = 'filename'; |
||||
|
||||
} |
@ -0,0 +1,503 @@
|
||||
<?php |
||||
|
||||
namespace App\Model; |
||||
|
||||
use App\Classes\Db; |
||||
|
||||
abstract class Model { |
||||
|
||||
protected $table; |
||||
|
||||
protected $primaryKey = 'id'; |
||||
|
||||
protected $keyType = 'int'; |
||||
|
||||
protected $incrementing = true; |
||||
|
||||
protected $perPage = 20; |
||||
|
||||
protected $exists = false; |
||||
|
||||
protected $sort = 'id'; |
||||
|
||||
const CREATED_AT = 'created_at'; |
||||
|
||||
const UPDATED_AT = 'updated_at'; |
||||
|
||||
private $attributes = []; |
||||
|
||||
public function __construct() |
||||
{ |
||||
// Fill in table name |
||||
if (empty($this->table)) { |
||||
$class = strtolower(get_class($this)); |
||||
$pos = strrpos($class, '\\'); |
||||
$this->table = substr($class, $pos + 1); |
||||
} |
||||
|
||||
// Pull columns from cache |
||||
$columnsCache = Db::getColumns(); |
||||
|
||||
// If exists in cache |
||||
if (array_key_exists($this->table, $columnsCache)) { |
||||
$columns = $columnsCache[$this->table]; |
||||
} |
||||
// Otherwise query Db and add to cache |
||||
else { |
||||
$columns = self::query("SHOW COLUMNS FROM `$this->table`"); |
||||
$columns = array_column($columns, 'Field'); |
||||
$columnsCache[$this->table] = $columns; |
||||
Db::setColumns($columnsCache); |
||||
} |
||||
|
||||
// Create attribute placeholders |
||||
if (_exists($columns)) { |
||||
foreach ($columns as $column) { |
||||
if ($column != $this->primaryKey) { |
||||
$this->attributes[] = $column; |
||||
} |
||||
$this->{$column} = null; |
||||
} |
||||
} |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
protected static function query(string $query, array $parameters = [], |
||||
$type = ':'): ?array |
||||
{ |
||||
// Example |
||||
// $parameters = [ |
||||
// [':id', 1], |
||||
// [':number', 7, \PDO::PARAM_INT], |
||||
// [':string', 'A random string', \PDO::PARAM_STR], |
||||
// ]; |
||||
|
||||
// PDO::PARAM_BOOL |
||||
// PDO::PARAM_NULL |
||||
// PDO::PARAM_INT |
||||
// PDO::PARAM_STR |
||||
|
||||
if (substr_count($query, $type) != count($parameters)) { |
||||
return null; |
||||
} |
||||
|
||||
$query = Db::get()->prepare($query); |
||||
|
||||
$success = false; |
||||
if ($type == '?') { |
||||
$success = $query->execute($parameters); |
||||
} |
||||
else { |
||||
foreach ($parameters as $key => $parameter) { |
||||
if (count($parameter) == 2) { |
||||
$query->bindParam($parameter[0], $parameter[1]); |
||||
} |
||||
else if (count($parameter) == 3) { |
||||
$query->bindParam($parameter[0], $parameter[1], $parameter[2]); |
||||
} |
||||
} |
||||
$success = $query->execute(); |
||||
} |
||||
|
||||
if (!$success) { |
||||
return null; |
||||
} |
||||
|
||||
return $query->fetchAll(); |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
public function exists(): bool |
||||
{ |
||||
if ($this->exists == true) { |
||||
return $this->exists; |
||||
} |
||||
|
||||
$exists = self::query( |
||||
"SELECT id FROM `$this->table` WHERE `$this->primaryKey` = :id", [ |
||||
[':id', $this->{$this->primaryKey}], |
||||
] |
||||
); |
||||
|
||||
if (_exists($exists)) { |
||||
$this->exists = true; |
||||
} |
||||
|
||||
return $this->exists; |
||||
} |
||||
|
||||
public function save(): bool |
||||
{ |
||||
$parameters = []; |
||||
|
||||
$exists = $this->exists(); |
||||
// Insert new Model |
||||
if (!$exists) { |
||||
$query = "INSERT INTO `$this->table` "; |
||||
for ($i = 0; $i < 2; $i++) { |
||||
if ($i == 0) { |
||||
$query .= '('; |
||||
} |
||||
else { |
||||
$query .= 'VALUES ('; |
||||
} |
||||
|
||||
foreach ($this->attributes as $key => $attribute) { |
||||
if ($key != 0) { |
||||
$query .= ', '; |
||||
} |
||||
|
||||
if ($i == 0) { |
||||
$query .= "`$attribute`"; |
||||
} |
||||
else { |
||||
$query .= ":$attribute"; |
||||
$parameters[] = [":$attribute", $this->{$attribute}]; |
||||
} |
||||
} |
||||
$query .= ') '; |
||||
} |
||||
} |
||||
// Update existing Model |
||||
else { |
||||
$query = "UPDATE `$this->table` SET "; |
||||
foreach ($this->attributes as $key => $attribute) { |
||||
if ($key != 0) { |
||||
$query .= ', '; |
||||
} |
||||
|
||||
$query .= "`$attribute` = :$attribute"; |
||||
$parameters[] = [":$attribute", $this->{$attribute}]; |
||||
} |
||||
$query .= " WHERE `$this->primaryKey` = :id"; |
||||
$parameters[] = [':id', $this->{$this->primaryKey}]; |
||||
} |
||||
|
||||
if (self::query($query, $parameters) === null) { |
||||
return false; |
||||
} |
||||
|
||||
// Fill in primary key and exists for newly created Models |
||||
if (!$exists) { |
||||
$id = self::query("SELECT LAST_INSERT_ID() as `$this->primaryKey`"); |
||||
if (_exists($id)) { |
||||
$this->{$this->primaryKey} = $id[0][$this->primaryKey]; |
||||
$this->exists = true; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
public function delete(): bool |
||||
{ |
||||
return self::query( |
||||
"DELETE FROM `$this->table` WHERE `$this->primaryKey` = :id", [ |
||||
[':id', $this->{$this->primaryKey}], |
||||
] |
||||
) !== null; |
||||
} |
||||
|
||||
public function count(): int |
||||
{ |
||||
$total = self::query( |
||||
"SELECT COUNT({$this->primaryKey}) as total FROM `$this->table`"); |
||||
return $total[0]['total'] ?? 0; |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
public function fill(array $fill): bool |
||||
{ |
||||
if (!_exists([$fill])) { |
||||
return false; |
||||
} |
||||
|
||||
// Set primary key |
||||
if (_exists($fill, $this->primaryKey)) { |
||||
$this->{$this->primaryKey} = $fill[$this->primaryKey]; |
||||
} |
||||
|
||||
// Set other attributes |
||||
foreach ($this->getAttributes() as $attribute) { |
||||
if (_exists($fill, $attribute) || |
||||
(isset($fill[$attribute]) && $fill[$attribute] === '0')) { |
||||
// Escape sequences are only interpreted with double quotes! |
||||
$this->{$attribute} = preg_replace('/\r\n?/', "\n", $fill[$attribute]); |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
public function getPrimaryKey(): string |
||||
{ |
||||
return $this->primaryKey; |
||||
} |
||||
|
||||
public function getAttributes(): array |
||||
{ |
||||
return $this->attributes; |
||||
} |
||||
|
||||
public function getAttributesRules(): array |
||||
{ |
||||
|
||||
// Check if model has set rules |
||||
if (!_exists($this->rules) || !is_array($this->rules)) { |
||||
$rules = []; |
||||
} |
||||
else { |
||||
$rules = $this->rules; |
||||
} |
||||
|
||||
// Get first column (name) of every rule |
||||
$rulesSearch = array_column($rules, 0); |
||||
|
||||
// Loop through attributes |
||||
foreach ($this->attributes as $attribute) { |
||||
$found = array_search($attribute, $rulesSearch); |
||||
|
||||
if ($found === false) { |
||||
// Define default ruleset |
||||
$rules[] = [$attribute, "text", 0, 0]; |
||||
// Name | Type | Required | Filtered |
||||
// Type can be: |
||||
// - text |
||||
// - textarea |
||||
// - checkbox |
||||
// - dropdown // @Todo: store dropdown data |
||||
} |
||||
} |
||||
|
||||
return $rules; |
||||
} |
||||
|
||||
// Placeholder for generating the dropdown data |
||||
public function getDropdownData(string $type): array |
||||
{ |
||||
return []; |
||||
} |
||||
|
||||
public function getSort(): string |
||||
{ |
||||
return is_array($this->sort) |
||||
? '`' . implode('`, `', $this->sort) . '`' |
||||
: '`' . $this->sort . '`'; |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
public static function select(string $select = '*', string $filter = '', |
||||
array $parameters = [], $type = ':'): Model |
||||
{ |
||||
$class = get_called_class(); |
||||
$model = new $class; |
||||
|
||||
$select = self::query("SELECT $select FROM `$model->table` $filter", |
||||
$parameters, $type); |
||||
if (_exists($select)) { |
||||
$model->fill($select[0]); |
||||
} |
||||
|
||||
return $model; |
||||
} |
||||
|
||||
// $media = MediaModel::selectAll( |
||||
// '*', 'ORDER BY id DESC LIMIT :offset, :limit', [ |
||||
// [':offset', $offset, \PDO::PARAM_INT], |
||||
// [':limit', $limit, \PDO::PARAM_INT], |
||||
// ] |
||||
// ); |
||||
// |
||||
// $contents = ContentModel::selectAll( |
||||
// '*', 'WHERE id IN (?, ?, ?)', [1, 2, 3], '?' |
||||
// ); |
||||
public static function selectAll(string $select = '*', string $filter = '', |
||||
array $parameters = [], $type = ':'): ?array |
||||
{ |
||||
$class = get_called_class(); |
||||
$model = new $class; |
||||
|
||||
return self::query("SELECT $select FROM `$model->table` $filter", |
||||
$parameters, $type); |
||||
} |
||||
|
||||
public static function find(int $id): Model |
||||
{ |
||||
$class = get_called_class(); |
||||
$model = new $class; |
||||
|
||||
$find = self::query("SELECT * FROM `$model->table` WHERE `$model->primaryKey` = :id", [ |
||||
[':id', $id], |
||||
]); |
||||
if (_exists($find)) { |
||||
$model->fill($find[0]); |
||||
} |
||||
|
||||
return $model; |
||||
} |
||||
|
||||
// @Todo: add field name to query -> update Media.php md5 |
||||
public static function findAll(array $id = []): ?array |
||||
{ |
||||
if (_exists($id)) { |
||||
$id = array_values(array_unique($id)); |
||||
$in = str_repeat('?, ', count($id) - 1) . '?'; |
||||
array_push($id, ...$id); |
||||
return self::selectAll( |
||||
'*', "WHERE id IN ($in) ORDER BY FIELD(id, $in)", $id, '?'); |
||||
} |
||||
else { |
||||
return self::selectAll(); |
||||
} |
||||
} |
||||
|
||||
public static function findOrFail(int $id): Model |
||||
{ |
||||
$model = self::find($id); |
||||
if (!$model->exists()) { |
||||
throw new \Exception('Could not find Model!'); |
||||
} |
||||
|
||||
return $model; |
||||
} |
||||
|
||||
public static function search(array $constraints, string $delimiter = 'AND'): Model |
||||
{ |
||||
$parameters = []; |
||||
|
||||
$filter = "WHERE "; |
||||
foreach (array_keys($constraints) as $key => $constraint) { |
||||
if ($key != 0) { |
||||
$filter .= " $delimiter "; |
||||
} |
||||
|
||||
$filter .= "`$constraint` = :$constraint"; |
||||
$parameters[] = [":$constraint", $constraints[$constraint]]; |
||||
} |
||||
|
||||
return self::select('*', $filter, $parameters); |
||||
} |
||||
|
||||
public static function destroy(int $id): bool |
||||
{ |
||||
$model = self::find($id); |
||||
if ($model->exists()) { |
||||
return $model->delete(); |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Load Model data: all, with a limit or pagination |
||||
* |
||||
* @param int $limitOrPage Treated as page if $limit is provided, limit otherwise |
||||
* @param int $limit The amount to limit by |
||||
* |
||||
* @return array|null The found model data, or null |
||||
*/ |
||||
public static function all(int $limitOrPage = -1, int $limit = -1): ?array { |
||||
$class = get_called_class(); |
||||
$model = new $class; |
||||
|
||||
$filter = ''; |
||||
$parameters = []; |
||||
|
||||
// If the user wants to paginate |
||||
if ($limitOrPage >= 1 && $limit >= 0) { |
||||
// Pagination calculation |
||||
$page = ($limitOrPage - 1) * $limit; |
||||
|
||||
$filter = 'LIMIT :page, :limit'; |
||||
$parameters[] = [':page', $page, \PDO::PARAM_INT]; |
||||
$parameters[] = [':limit', $limit, \PDO::PARAM_INT]; |
||||
} |
||||
// If the user wants an offset |
||||
else if ($limitOrPage >= 0) { |
||||
$filter = 'LIMIT :limit'; |
||||
$parameters[] = [':limit', $limitOrPage, \PDO::PARAM_INT]; |
||||
} |
||||
|
||||
return $model->selectAll( |
||||
'*', "ORDER BY {$model->getSort()} ASC $filter", |
||||
$parameters |
||||
); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Retreive Model, or instantiate |
||||
* Usage: $model = \App\Model\Example::firstOrNew(['name' => 'Example name']); |
||||
* |
||||
* @param $search Retrieve by |
||||
* @param $data Instantiate with search plus data |
||||
* |
||||
* @return Model The Model |
||||
*/ |
||||
public static function firstOrNew(array $search, array $data = []): Model |
||||
{ |
||||
$model = self::search($search); |
||||
|
||||
if (!$model->exists()) { |
||||
$class = get_called_class(); |
||||
$model = new $class; |
||||
$model->fill($search); |
||||
$model->fill($data); |
||||
} |
||||
|
||||
return $model; |
||||
} |
||||
|
||||
/** |
||||
* Create new Model |
||||
* Usage: $model = \App\Model\Example::create(['name' => 'Example name']); |
||||
* |
||||
* @param $data Create with this data |
||||
* |
||||
* @return Model The Model |
||||
*/ |
||||
public static function create(array $data): Model { |
||||
$class = get_called_class(); |
||||
$model = new $class; |
||||
$model->fill($data); |
||||
$model->save(); |
||||
return $model; |
||||
} |
||||
|
||||
/** |
||||
* Retreive Model, or create |
||||
* Usage: $model = \App\Model\Example::firstOrCreate(['name' => 'Example name']); |
||||
* |
||||
* @param $search Retrieve by |
||||
* @param $data Instantiate with search plus data |
||||
* |
||||
* @return Model The Model |
||||
*/ |
||||
public static function firstOrCreate(array $search, array $data = []): Model { |
||||
$model = self::firstOrNew($search, $data); |
||||
|
||||
if (!$model->exists()) { |
||||
$model->save(); |
||||
} |
||||
|
||||
return $model; |
||||
} |
||||
|
||||
// // Update existing Model, or create it if doesn't exist |
||||
// public static function updateOrCreate(array $data, array $data): Model { |
||||
// // $flight = App\Flight::updateOrCreate( |
||||
// // ['departure' => 'Oakland', 'destination' => 'San Diego'], |
||||
// // ['price' => 99] |
||||
// // ); |
||||
// return new Model; |
||||
// } |
||||
|
||||
} |
||||
|
||||
// @Todo |
||||
// - Generate rules from database table |
||||
// - Make count work without 'this' context |
@ -0,0 +1,68 @@
|
||||
<?php |
||||
|
||||
namespace App\Model; |
||||
|
||||
use App\Classes\Db; |
||||
use App\Model\ContentModel; |
||||
use App\Model\PageModel; |
||||
|
||||
class PageHasContentModel extends Model { |
||||
|
||||
protected $table = 'page_has_content'; |
||||
protected $sort = ['page_id', 'order']; |
||||
|
||||
public $title = "PageHasContent"; |
||||
|
||||
// Attribute rules |
||||
// Name | Type | Required | Filtered |
||||
public $rules = [ |
||||
["order", "text", 1, 0], |
||||
["page_id", "dropdown", 1, 0], |
||||
["content_id", "dropdown", 1, 0], |
||||
]; |
||||
|
||||
//-------------------------------------// |
||||
|
||||
// Generate the dropdown data |
||||
public function getDropdownData(string $type): array |
||||
{ |
||||
if ($type == 'page_id') { |
||||
return $this->dropdownPage(); |
||||
} |
||||
else if ($type == 'content_id') { |
||||
return $this->dropdownContent(); |
||||
} |
||||
|
||||
return []; |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
protected function dropdownPage(): array |
||||
{ |
||||
$pages = PageModel::selectAll( |
||||
'*', "WHERE `active` = ? ORDER BY `title` ASC", [1], '?'); |
||||
|
||||
return [0 => 'Select page'] + array_combine( |
||||
array_column($pages, 'id'), |
||||
array_column($pages, 'title') |
||||
); |
||||
} |
||||
|
||||
protected function dropdownContent(): array |
||||
{ |
||||
$contents = ContentModel::selectAll( |
||||
'*', "WHERE `active` = ? ORDER BY `title` ASC", [1], '?'); |
||||
|
||||
// Exit if nothing was found |
||||
if (!_exists($contents)) { |
||||
return []; |
||||
} |
||||
|
||||
return [0 => 'Select content'] + array_combine( |
||||
array_column($contents, 'id'), |
||||
array_column($contents, 'title') |
||||
); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,76 @@
|
||||
<?php |
||||
|
||||
namespace App\Model; |
||||
|
||||
use App\Classes\Db; |
||||
use App\Traits\Log; |
||||
|
||||
class PageModel extends Model { |
||||
|
||||
use Log { delete as deleteLog; } |
||||
|
||||
protected $table = 'page'; |
||||
protected $sort = ['section_id', 'order']; |
||||
|
||||
// Attribute rules |
||||
// Name | Type | Required | Filtered |
||||
public $rules = [ |
||||
["page", "text", 1, 0], |
||||
["title", "text", 0, 0], |
||||
["title_url", "text", 0, 1], |
||||
["meta_description", "text", 0, 0], |
||||
["type", "text", 1, 1], |
||||
["order", "text", 1, 0], |
||||
["hide_navigation", "checkbox", 1, 0], |
||||
["active", "checkbox", 1, 0], |
||||
["section_id", "dropdown", 1, 0], |
||||
["log_id", "text", 1, 1], |
||||
]; |
||||
|
||||
//-------------------------------------// |
||||
|
||||
// Set default values |
||||
public function __construct() |
||||
{ |
||||
parent::__construct(); |
||||
|
||||
$this->type = 0; |
||||
} |
||||
|
||||
// Generate the dropdown data |
||||
public function getDropdownData(string $type): array |
||||
{ |
||||
if ($type == 'section_id') { |
||||
return $this->dropdownSection(); |
||||
} |
||||
|
||||
return []; |
||||
} |
||||
|
||||
public function delete(): bool |
||||
{ |
||||
if (self::query( |
||||
"DELETE FROM `{$this->table}_has_content` WHERE `{$this->table}_$this->primaryKey` = :id", [ |
||||
[':id', $this->{$this->primaryKey}], |
||||
] |
||||
) === null) { |
||||
return false; |
||||
} |
||||
|
||||
return $this->deleteLog(); |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
protected function dropdownSection(): array |
||||
{ |
||||
// Pull sections from cache |
||||
$sections = Db::getSections(); |
||||
|
||||
return [0 => 'Select section'] + array_combine( |
||||
array_column($sections, 'id'), |
||||
array_column($sections, 'title') |
||||
); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,68 @@
|
||||
<?php |
||||
|
||||
namespace App\Model; |
||||
|
||||
use App\Classes\Db; |
||||
use App\Model\ContentModel; |
||||
use App\Model\SectionModel; |
||||
|
||||
class SectionHasContentModel extends Model { |
||||
|
||||
protected $table = 'section_has_content'; |
||||
protected $sort = ['section_id', 'order']; |
||||
|
||||
public $title = "SectionHasContent"; |
||||
|
||||
// Attribute rules |
||||
// Name | Type | Required | Filtered |
||||
public $rules = [ |
||||
["order", "text", 1, 0], |
||||
["section_id", "dropdown", 1, 0], |
||||
["content_id", "dropdown", 1, 0], |
||||
]; |
||||
|
||||
//-------------------------------------// |
||||
|
||||
// Generate the dropdown data |
||||
public function getDropdownData(string $type): array |
||||
{ |
||||
if ($type == 'section_id') { |
||||
return $this->dropdownSection(); |
||||
} |
||||
else if ($type == 'content_id') { |
||||
return $this->dropdownContent(); |
||||
} |
||||
|
||||
return []; |
||||
} |
||||
|
||||
//-------------------------------------// |
||||
|
||||
protected function dropdownSection(): array |
||||
{ |
||||
$sections = SectionModel::selectAll( |
||||
'*', "WHERE `active` = ? ORDER BY `title` ASC", [1], '?'); |
||||
|
||||
return [0 => 'Select section'] + array_combine( |
||||
array_column($sections, 'id'), |
||||
array_column($sections, 'title') |
||||
); |
||||
} |
||||
|
||||
protected function dropdownContent(): array |
||||
{ |
||||
$contents = ContentModel::selectAll( |
||||
'*', "WHERE `active` = ? ORDER BY `title` ASC", [1], '?'); |
||||
|
||||
// Exit if nothing was found |
||||
if (!_exists($contents)) { |
||||
return []; |
||||
} |
||||
|
||||
return [0 => 'Select content'] + array_combine( |
||||
array_column($contents, 'id'), |
||||
array_column($contents, 'title') |
||||
); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,24 @@
|
||||
<?php |
||||
|
||||
namespace App\Model; |
||||
|
||||
use App\Traits\Log; |
||||
|
||||
class SectionModel extends Model { |
||||
|
||||
use Log; |
||||
|
||||
protected $table = 'section'; |
||||
protected $sort = 'order'; |
||||
|
||||
// Attribute rules |
||||
// Name | Type | Required | Filtered |
||||
public $rules = [ |
||||
["section", "text", 1, 0], |
||||
["title", "text", 0, 0], |
||||
["order", "text", 1, 0], |
||||
["hide_navigation", "checkbox", 1, 0], |
||||
["active", "checkbox", 1, 0], |
||||
["log_id", "text", 1, 1], |
||||
]; |
||||
} |
@ -0,0 +1,7 @@
|
||||
<?php |
||||
|
||||
namespace App\Model; |
||||
|
||||
class UserModel extends Model { |
||||
protected $table = 'user'; |
||||
} |
@ -0,0 +1,178 @@
|
||||
<?php |
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php'; |
||||
|
||||
use App\Classes\Config; |
||||
use App\Classes\Db; |
||||
use App\Classes\User; |
||||
|
||||
use App\Model\ContentModel; |
||||
use App\Model\PageModel; |
||||
use App\Model\PageHasContentModel; |
||||
use App\Model\SectionModel; |
||||
use App\Model\SectionHasContentModel; |
||||
use App\Model\UserModel; |
||||
|
||||
Config::load(); |
||||
Db::load(); |
||||
|
||||
// Drop db and reset auto increment |
||||
//-------------------------------------// |
||||
|
||||
$query = " |
||||
SET FOREIGN_KEY_CHECKS = 0; |
||||
TRUNCATE `page_has_content`; |
||||
TRUNCATE `section_has_content`; |
||||
TRUNCATE `content`; |
||||
TRUNCATE `page`; |
||||
TRUNCATE `section`; |
||||
TRUNCATE `media`; |
||||
TRUNCATE `log`; |
||||
TRUNCATE `user`; |
||||
SET FOREIGN_KEY_CHECKS = 1; |
||||
|
||||
ALTER TABLE `page_has_content` AUTO_INCREMENT = 1; |
||||
ALTER TABLE `section_has_content` AUTO_INCREMENT = 1; |
||||
ALTER TABLE `content` AUTO_INCREMENT = 1; |
||||
ALTER TABLE `page` AUTO_INCREMENT = 1; |
||||
ALTER TABLE `section` AUTO_INCREMENT = 1; |
||||
ALTER TABLE `media` AUTO_INCREMENT = 1; |
||||
ALTER TABLE `log` AUTO_INCREMENT = 1; |
||||
ALTER TABLE `user` AUTO_INCREMENT = 1; |
||||
"; |
||||
|
||||
if ($argc >= 2) { |
||||
$query = Db::get()->prepare($query); |
||||
$query->execute(); |
||||
die(); |
||||
} |
||||
|
||||
// Users |
||||
//-------------------------------------// |
||||
|
||||
$users = [ |
||||
['', '', '', '', '', '', '0', ''], // 1 |
||||
// ['', '', '', '', '', '', '', ''], |
||||
]; |
||||
|
||||
foreach ($users as $user) { |
||||
UserModel::firstOrCreate( |
||||
['username' => $user[0]], |
||||
[ |
||||
'email' => $user[1], |
||||
'first_name' => $user[2], |
||||
'last_name' => $user[3], |
||||
'salt' => $user[4], |
||||
'password' => $user[5], |
||||
'failed_login_attempt' => $user[6], |
||||
'reset_key' => $user[7], |
||||
] |
||||
); |
||||
} |
||||
|
||||
User::login($users[0][0], 'password', $users[0][6]); |
||||
|
||||
// Sections |
||||
//-------------------------------------// |
||||
|
||||
$sections = [ |
||||
['home', 'Homepage', '1', '1', '1'], // 1 |
||||
// ['', '', '', '', ''], |
||||
]; |
||||
|
||||
foreach ($sections as $section) { |
||||
SectionModel::firstOrCreate( |
||||
['section' => $section[0]], |
||||
[ |
||||
'title' => $section[1], |
||||
'order' => $section[2], |
||||
'hide_navigation' => $section[3], |
||||
'active' => $section[4], |
||||
], |
||||
); |
||||
} |
||||
|
||||
// Pages |
||||
//-------------------------------------// |
||||
|
||||
$pages = [ |
||||
['home', 'Homepage', '', '', '1', '1', '0', '1', '1'], // 1 |
||||
// ['', '', '', '', '', '', '', '', ''], |
||||
]; |
||||
|
||||
foreach ($pages as $page) { |
||||
PageModel::firstOrCreate( |
||||
['page' => $page[0]], |
||||
[ |
||||
'title' => $page[1], |
||||
'title_url' => $page[2], |
||||
'meta_description' => $page[3], |
||||
'type' => $page[4], |
||||
'order' => $page[5], |
||||
'hide_navigation' => $page[6], |
||||
'active' => $page[7], |
||||
'section_id' => $page[8], |
||||
], |
||||
); |
||||
} |
||||
|
||||
// Content |
||||
//-------------------------------------// |
||||
|
||||
$contents = [ |
||||
['Homepage', 'home', '1', '1', '0', '1'], // 1 |
||||
// ['', '', '', '', '', ''], |
||||
]; |
||||
|
||||
foreach ($contents as $content) { |
||||
ContentModel::firstOrCreate( |
||||
['title' => $content[0]], |
||||
[ |
||||
'content' => $content[1], |
||||
'type' => $content[2], |
||||
'hide_title' => $content[3], |
||||
'hide_background' => $content[4], |
||||
'active' => $content[5], |
||||
], |
||||
); |
||||
} |
||||
|
||||
// PageHasContent |
||||
//-------------------------------------// |
||||
|
||||
$pageLinks = [ |
||||
// id, order, page_id, content_id |
||||
[ '1', '1', '1', '1'], |
||||
// ['', '', '', ''], |
||||
]; |
||||
|
||||
foreach ($pageLinks as $pageLink) { |
||||
PageHasContentModel::firstOrCreate( |
||||
['id' => $pageLink[0]], |
||||
[ |
||||
'order' => $pageLink[1], |
||||
'page_id' => $pageLink[2], |
||||
'content_id' => $pageLink[3], |
||||
], |
||||
); |
||||
} |
||||
|
||||
// SectionHasContent |
||||
//-------------------------------------// |
||||
|
||||
$sectionLinks = [ |
||||
// id, order, section_id, content_id |
||||
// ['1', '1', '1' '1'], |
||||
// ['', '', '', ''], |
||||
]; |
||||
|
||||
foreach ($sectionLinks as $sectionLink) { |
||||
SectionHasContentModel::firstOrCreate( |
||||
['id' => $sectionLink[0]], |
||||
[ |
||||
'order' => $sectionLink[1], |
||||
'section_id' => $sectionLink[2], |
||||
'content_id' => $sectionLink[3], |
||||
], |
||||
); |
||||
} |
@ -0,0 +1,68 @@
|
||||
<?php |
||||
|
||||
namespace App\Traits; |
||||
|
||||
use App\Classes\User; |
||||
|
||||
use App\Model\LogModel; |
||||
|
||||
trait Log { |
||||
public function save(): bool |
||||
{ |
||||
// Get timestamp |
||||
$date = date('Y-m-d H:i:s'); |
||||
|
||||
// Create log |
||||
if (!_exists([$this->log_id])) { |
||||
$log = new LogModel; |
||||
$log->{self::CREATED_AT} = $date; |
||||
$log->{self::UPDATED_AT} = $date; |
||||
$log->user_id = User::getSession()['id']; |
||||
if (!$log->save()) { |
||||
return false; |
||||
} |
||||
|
||||
// Add log to new model |
||||
$this->log_id = $log->{$log->primaryKey}; |
||||
} |
||||
// Update log |
||||
else { |
||||
$log = LogModel::findOrFail($this->log_id); |
||||
$log->{self::UPDATED_AT} = $date; |
||||
} |
||||
|
||||
// Save model |
||||
if (!parent::save()) { |
||||
// Clean up the log if model creation failed |
||||
if (!_exists([$this->{$this->primaryKey}])) { |
||||
$log->delete(); |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
// Update table and user ID |
||||
$log->table_name = $this->table; |
||||
$log->table_id = $this->{$this->primaryKey}; |
||||
$log->user_id = User::getSession()['id']; |
||||
return $log->save(); |
||||
} |
||||
|
||||
public function delete(): bool |
||||
{ |
||||
// Exit if there is no log |
||||
if (!_exists([$this->log_id])) { |
||||
return false; |
||||
} |
||||
|
||||
$log = $this->log_id; |
||||
|
||||
// Delete model |
||||
if (!parent::delete()) { |
||||
return false; |
||||
} |
||||
|
||||
// Delete log |
||||
return LogModel::destroy($log); |
||||
} |
||||
} |
@ -0,0 +1,62 @@
|
||||
<div class="row"> |
||||
<div class="col-12"> |
||||
<div class="content shadow p-4 mb-4"> |
||||
<?= $this->partial('../app/views/partials/message.php'); ?> |
||||
|
||||
<h3>Create</h3> |
||||
|
||||
<form action="<?= $this->url; ?>" method="post">
|
||||
<?php foreach($this->attributes as $key => $attribute) { ?> |
||||
<?php |
||||
if ($attribute[3] == 1) { continue; } |
||||
if ($attribute[2] == 1) { $required = 'required'; } else { $required = ''; } |
||||
|
||||
$name = $attribute[0]; |
||||
$title = ucfirst($attribute[0]); |
||||
$title = str_replace('_', ' ', $title); |
||||
$autofocus = $key == 0 ? 'autofocus' : ''; |
||||
?> |
||||
<div class="form-group"> |
||||
<label for="<?= $name; ?>"><?= $title;?></label><br>
|
||||
<?php if ($attribute[1] == 'text') { ?> |
||||
|
||||
<input type="text" class="form-control" |
||||
<?= $autofocus; ?> |
||||
<?= $required; ?> |
||||
name="<?= $name; ?>"
|
||||
placeholder="<?= $title; ?>">
|
||||
|
||||
<?php } else if ($attribute[1] == 'textarea') { ?> |
||||
|
||||
<textarea id="summernote" rows="18" cols="1" class="form-control" |
||||
<?= $autofocus; ?> |
||||
<?= $required; ?> |
||||
name="<?= $name; ?>"
|
||||
placeholder="<?= $title; ?>"
|
||||
></textarea> |
||||
|
||||
<?php } else if ($attribute[1] == 'checkbox') { ?> |
||||
|
||||
<input type="hidden" name="<?= $name; ?>" value="0">
|
||||
<input type="checkbox" name="<?= $name; ?>" value="1">
|
||||
|
||||
<?php } else if ($attribute[1] == 'dropdown') { ?> |
||||
|
||||
<select name="<?= $name; ?>" class="custom-select">
|
||||
<?php foreach($this->dropdownData[$key] as $dropdownKey => $value) { ?> |
||||
<option value="<?= $dropdownKey; ?>"><?= $value; ?></option>
|
||||
<?php } ?> |
||||
</select> |
||||
|
||||
<?php } ?> |
||||
</div> |
||||
<?php } ?> |
||||
<button type="submit" class="btn btn-dark">Create</button> |
||||
|
||||
<input type="hidden" name="_token" value="<?= $this->csrfToken; ?>" />
|
||||
</form> |
||||
|
||||
<div class="pb-5"></div> |
||||
</div> |
||||
</div> |
||||
</div> |
@ -0,0 +1,66 @@
|
||||
<div class="row"> |
||||
<div class="col-12"> |
||||
<div class="content shadow p-4 mb-4"> |
||||
<?= $this->partial('../app/views/partials/message.php'); ?> |
||||
|
||||
<h3>Edit</h3> |
||||
|
||||
<form id="form-edit" action="<?= $this->url; ?>" method="post">
|
||||
<?php foreach($this->attributes as $key => $attribute) { ?> |
||||
<?php |
||||
if ($attribute[3] == 1) { continue; } |
||||
if ($attribute[2] == 1) { $required = 'required'; } else { $required = ''; } |
||||
|
||||
$name = $attribute[0]; |
||||
$title = ucfirst($attribute[0]); |
||||
$title = str_replace('_', ' ', $title); |
||||
$autofocus = $key == 0 ? 'autofocus' : ''; |
||||
?> |
||||
<div class="form-group"> |
||||
<label for="<?= $name; ?>"><?= $title;?></label><br>
|
||||
<?php if ($attribute[1] == 'text') { ?> |
||||
|
||||
<input type="text" class="form-control" |
||||
<?= $autofocus; ?> |
||||
<?= $required; ?> |
||||
name="<?= $name; ?>"
|
||||
placeholder="<?= $title; ?>"
|
||||
value="<?= ($this->escape)($this->model->$name); ?>">
|
||||
|
||||
<?php } else if ($attribute[1] == 'textarea') { ?> |
||||
|
||||
<textarea id="summernote" rows="18" cols="1" class="form-control" |
||||
<?= $autofocus; ?> |
||||
<?= $required; ?> |
||||
name="<?= $name; ?>"
|
||||
placeholder="<?= $title; ?>"
|
||||
><?= ($this->escape)($this->model->$name); ?></textarea>
|
||||
|
||||
<?php } else if ($attribute[1] == 'checkbox') { ?> |
||||
|
||||
<input type="hidden" name="<?= $name; ?>" value="0">
|
||||
<input type="checkbox" name="<?= $name; ?>" value="1"
|
||||
<?= $this->model->$name == '1' ? 'checked' : ''; ?>>
|
||||
|
||||
<?php } else if ($attribute[1] == 'dropdown') { ?> |
||||
|
||||
<select name="<?= $name; ?>" class="custom-select">
|
||||
<?php foreach($this->dropdownData[$key] as $dropdownKey => $value) { ?> |
||||
<option value="<?= $dropdownKey; ?>"
|
||||
<?= $this->model->$name == $dropdownKey ? 'selected' : ''; ?>>
|
||||
<?= $value; ?> |
||||
</option> |
||||
<?php } ?> |
||||
</select> |
||||
|
||||
<?php } ?> |
||||
</div> |
||||
<?php } ?> |
||||
<input type="hidden" name="_token" value="<?= $this->csrfToken; ?>" />
|
||||
</form> |
||||
<button class="js-edit btn btn-dark" href="<?= $this->url . '/' . $this->model->id; ?>">Edit</button>
|
||||
|
||||
<div class="pb-5"></div> |
||||
</div> |
||||
</div> |
||||
</div> |
@ -0,0 +1,71 @@
|
||||
<div class="row"> |
||||
<div class="col-12"> |
||||
<div class="content shadow p-4 mb-4"> |
||||
<?= $this->partial('../app/views/partials/message.php'); ?> |
||||
|
||||
<h3><?= $this->title; ?></h3>
|
||||
|
||||
<table class="table table-bordered table-striped"> |
||||
<thead> |
||||
<tr> |
||||
<th>#</th> |
||||
<?php foreach($this->attributes as $attribute) { ?> |
||||
<?php |
||||
if ($attribute[3] == 1) { continue; } |
||||
|
||||
$title = ucfirst($attribute[0]); |
||||
$title = str_replace('_', ' ', $title); |
||||
?> |
||||
<th><?= $title; ?></th>
|
||||
<?php } ?> |
||||
<th></th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
<?php foreach($this->rows as $key => $row) { ?> |
||||
<tr> |
||||
<td> |
||||
<a href="<?= $this->url . '/' . $row['id']; ?>">
|
||||
<?= $key + 1; ?> |
||||
</a> |
||||
</td> |
||||
<?php foreach($this->attributes as $attribute) { ?> |
||||
<?php |
||||
// Skip filtered |
||||
if ($attribute[3] == 1) { continue; } |
||||
?> |
||||
<td> |
||||
<?php $value = $row[$attribute[0]]; ?> |
||||
<?php if ($attribute[1] == 'checkbox' && is_numeric($value)) { ?> |
||||
<i class="fa <?= $value ? 'fa-check text-success' : 'fa-times text-danger'; ?>"></i>
|
||||
<?php } else { ?> |
||||
<?= ($this->escape)(substr($value, 0, 47)); ?> |
||||
<?= strlen($value) > 47 ? '...' : ''; ?> |
||||
<?php } ?> |
||||
</td> |
||||
<?php } ?> |
||||
<td> |
||||
<a href="<?= $this->url . '/' . $row['id']; ?>/edit">
|
||||
<i class="fa fa-pencil" aria-hidden="true"></i> |
||||
</a> |
||||
<a class="js-delete" href="<?= $this->url . '/' . $row['id']; ?>" data-token="<?= $this->csrfToken; ?>">
|
||||
<i class="fa fa-trash text-danger" aria-hidden="true"></i> |
||||
</a> |
||||
</td> |
||||
</tr> |
||||
<?php } ?> |
||||
</tbody> |
||||
</table> |
||||
|
||||
<?= $this->partial('../app/views/partials/pagination.php'); ?> |
||||
|
||||
<div class="row"> |
||||
<div class="col-12"> |
||||
<a class="btn btn-dark" href="<?= $this->url ?>/create">New <?= $this->title; ?></a>
|
||||
</div> |
||||
</div> |
||||
|
||||
<div class="pb-5"></div> |
||||
</div> |
||||
</div> |
||||
</div> |
@ -0,0 +1,40 @@
|
||||
<div class="row"> |
||||
<div class="col-12"> |
||||
<div class="content shadow p-4 mb-4"> |
||||
<h3><?= _exists([$this->model->title]) ? ($this->escape)($this->model->title) : 'Show'; ?></h3>
|
||||
|
||||
<table class="table table-bordered table-striped"> |
||||
<thead> |
||||
<tr class="d-flex"> |
||||
<th class="col-4">Column</th> |
||||
<th class="col-8">Value</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
<?php foreach ($this->attributes as $attribute) { ?> |
||||
<?php |
||||
// Skip filtered |
||||
if ($attribute[3] == 1) { continue; } |
||||
|
||||
$title = ucfirst($attribute[0]); |
||||
$title = str_replace('_', ' ', $title); |
||||
?> |
||||
<tr class="d-flex"> |
||||
<td class="col-4"><?= $title; ?></td>
|
||||
<td class="col-8"> |
||||
<?php $value = $this->model->{$attribute[0]}; ?> |
||||
<?php if ($attribute[1] == 'checkbox' && is_numeric($value)) { ?> |
||||
<i class="fa <?= $value ? 'fa-check text-success' : 'fa-times text-danger'; ?>"></i>
|
||||
<?php } else { ?> |
||||
<?= ($this->escape)($value); ?> |
||||
<?php } ?> |
||||
</td> |
||||
</tr> |
||||
<?php } ?> |
||||
</tbody> |
||||
</table> |
||||
|
||||
<div class="pb-5"></div> |
||||
</div> |
||||
</div> |
||||
</div> |
@ -0,0 +1,13 @@
|
||||
<div class="content shadow p-4 mb-4"> |
||||
<div id="home"> |
||||
<h3> |
||||
Welcome back, |
||||
</h3> |
||||
<h3> |
||||
<?= $this->user->first_name; ?> |
||||
<?= !empty($this->user->last_name) ? ' ' . $this->user->last_name : ''; ?> |
||||
</h3> |
||||
</div> |
||||
|
||||
<div class="pb-5"></div> |
||||
</div> |
@ -0,0 +1,90 @@
|
||||
<div class="row"> |
||||
<div class="col-12"> |
||||
<div class="content shadow p-4 mb-4"> |
||||
<?= $this->partial('../app/views/partials/message.php'); ?> |
||||
|
||||
<h3>Media file manager</h3> |
||||
|
||||
<table class="table table-bordered table-striped"> |
||||
<thead> |
||||
<th scope="col">Thumbnail</th> |
||||
<th scope="col">URL</th> |
||||
<th scope="col">Uploaded by</th> |
||||
<th scope="col">Modifier</th> |
||||
</thead> |
||||
<tbody> |
||||
|
||||
<?php foreach($this->media as $media) { ?> |
||||
<?php |
||||
$filename = $media['filename'] . '.' . $media['extension']; |
||||
?> |
||||
<tr> |
||||
<td> |
||||
<?php if (in_array($media['extension'], ['jpg', 'jpeg', 'png', 'gif'])) { ?> |
||||
<img src="<?= $this->fileUrl . $filename; ?>"
|
||||
title="<?= $filename; ?>" alt="<?= $filename; ?>"
|
||||
class="img-thumbnail" width="100px"> |
||||
<?php } else { ?> |
||||
No thumbnail available. |
||||
<?php } ?> |
||||
</td> |
||||
<td> |
||||
<a href="<?= $this->fileUrl . $filename; ?>" target="_blank">
|
||||
<?= $this->fileUrl . $filename; ?> |
||||
</a> |
||||
</td> |
||||
<td> |
||||
<?php |
||||
$idx = array_search( $media['log_id'], array_column($this->log, 'id')); |
||||
$idx = array_search($this->log[$idx]['user_id'], array_column($this->user, 'id')); |
||||
echo $this->user[$idx]['username']; |
||||
?> |
||||
</td> |
||||
<td> |
||||
<a class="js-delete" href="<?= $this->url . '/' . $media['id']; ?>">
|
||||
<button type="button" class="btn btn-danger"> |
||||
Delete |
||||
</button> |
||||
</a> |
||||
</td> |
||||
</tr> |
||||
<?php } ?> |
||||
<tr> |
||||
<form method="POST" action="<?= $this->url; ?>" enctype="multipart/form-data">
|
||||
<td colspan="2" class="js-upload p-0 middle"> |
||||
<input type="file" id="file" class="d-none" name="file[]" required multiple> |
||||
|
||||
<table class="table mb-0"> |
||||
<thead></thead> |
||||
<tbody> |
||||
<tr class="bg-transparent"> |
||||
<td class="border-0 middle text-center"> |
||||
<i class="fa fa-cloud-upload"></i> |
||||
<span class="">Drag files here</span> |
||||
</td> |
||||
<td class="border-0 middle text-center"> |
||||
<label for="file" class="btn btn-light border px-3 py-1 mb-0">Select files</label> |
||||
</td> |
||||
</tr> |
||||
</tbody> |
||||
</table> |
||||
</td> |
||||
<td> |
||||
<input type="hidden" name="overwrite" value="0"> |
||||
<input type="checkbox" name="overwrite" value="1"> Overwrite |
||||
</td> |
||||
<td> |
||||
<button type="submit" class="btn btn-success">Upload</button> |
||||
</td> |
||||
</form> |
||||
</tr> |
||||
|
||||
</tbody> |
||||
</table> |
||||
|
||||
<?= $this->partial('../app/views/partials/pagination.php'); ?> |
||||
|
||||
<div class="pb-5"></div> |
||||
</div> |
||||
</div> |
||||
</div> |
@ -0,0 +1,33 @@
|
||||
<div class="content shadow p-4 mb-4"> |
||||
<h3>Syntax Highlighting</h3> |
||||
|
||||
<div class="form-group"> |
||||
<label for="language">Select Language:</label><br> |
||||
<!-- https://prismjs.com/#languages-list --> |
||||
<select id="syntax-language" name="language"> |
||||
<option value="c">C</option> |
||||
<option value="cpp">C++</option> |
||||
<option value="css">CSS</option> |
||||
<option value="html">HTML</option> |
||||
<option value="javascript">JavaScript</option> |
||||
<option value="php">PHP</option> |
||||
<option value="python">Python</option> |
||||
<option value="shell">Shell</option> |
||||
</select> |
||||
</div> |
||||
|
||||
<div class="form-group"> |
||||
<label for="code">Code:</label><br> |
||||
<textarea id="syntax-code" name="code" rows="12" cols="1" class="form-control" autofocus></textarea> |
||||
</div> |
||||
|
||||
<div class="form-group"> |
||||
<a id="syntax-highlight" class="btn btn-dark" href="#">Highlight</a> |
||||
<a id="syntax-copy" class="btn btn-dark" href="#">Copy</a> |
||||
</div> |
||||
|
||||
<div id="syntax-parse"><pre class="line-numbers mb-4"><code class=""></code></pre></div> |
||||
<textarea id="syntax-parse-copy" class="admin-hidden"></textarea> |
||||
|
||||
<div class="pb-5"></div> |
||||
</div> |
@ -0,0 +1,44 @@
|
||||
<div class="row"> |
||||
<?php $size = $this->sideContent ? '8' : '12'; ?> |
||||
<div class="col-12 col-md-<?= $size; ?> col-lg-<?= $size; ?>">
|
||||
<?php foreach ($this->contents as $key => $content) { ?> |
||||
<?php if ($content['type'] == '1') { ?> |
||||
|
||||
<div class="<?= !$content['hide_background'] ? 'content shadow p-4' : ''; ?> mb-4">
|
||||
<?php if ($key === array_key_first($this->contents)) { ?> |
||||
<?= $this->partial('../app/views/partials/message.php'); ?> |
||||
<?php } ?> |
||||
|
||||
<?php if ($content['hide_title'] == 0) { ?> |
||||
<h1><?= ($this->escape)($content['title']); ?></h1>
|
||||
<?php } ?> |
||||
<?= $content['content']; ?> |
||||
|
||||
<?php if ($this->injectView != '') { ?> |
||||
<?= $this->partial($this->injectView); ?> |
||||
<?php } ?> |
||||
|
||||
<?php if ($key === array_key_last($this->contents)) { ?> |
||||
<div class="pb-5"></div> |
||||
<?php } ?> |
||||
</div> |
||||
|
||||
<?php } ?> |
||||
<?php } ?> |
||||
</div> |
||||
|
||||
<?php if ($this->sideContent) { ?> |
||||
<div class="col-8 col-md-4 col-lg-4"> |
||||
<?php foreach ($this->contents as $content) { ?> |
||||
<?php if ($content['type'] == '2') { ?> |
||||
|
||||
<div class="content content-side shadow p-3 mb-4"> |
||||
<h3><?= ($this->escape)($content['title']); ?></h3>
|
||||
<?= $content['content']; ?> |
||||
</div> |
||||
|
||||
<?php } ?> |
||||
<?php } ?> |
||||
</div> |
||||
<?php } ?> |
||||
</div> |
@ -0,0 +1,8 @@
|
||||
<div class="content shadow p-4 mb-4"> |
||||
<div class="navbar-padding"> |
||||
<p>Error <strong>404</strong>.</p> |
||||
<p>requested URL <?= $_SERVER["REQUEST_URI"] ?> was not found on this server.</p>
|
||||
</div> |
||||
|
||||
<div class="pb-5"></div> |
||||
</div> |
@ -0,0 +1,96 @@
|
||||
<form action="<?= $this->url; ?>" method="post">
|
||||
|
||||
<?php $count = 0; ?> |
||||
<?php foreach ($this->form->getFields() as $name => $field) { ?> |
||||
|
||||
<?php if ($field[1] == 'comment') { ?> |
||||
|
||||
<div class="form-group"> |
||||
<?= $field[0]; ?> |
||||
</div> |
||||
|
||||
<?php } else if ($field[1] == 'radio') { ?> |
||||
|
||||
<div class="form-group"> |
||||
<?php $radioCount = 0; ?> |
||||
|
||||
<?php if ($field[0] != '') { ?> |
||||
<label><?= $field[0]; ?></label><br>
|
||||
<?php } ?> |
||||
|
||||
<input type="hidden" name="<?= $name; ?>" value="">
|
||||
<?php foreach ($field[2] as $value => $label) { ?> |
||||
|
||||
<input type="radio" name="<?= $name; ?>" id="<?= $value; ?>"
|
||||
<?= strstr($field[3], 'required') ? 'required' : ''; ?> |
||||
<?= $this->{$name} == $value || (!isset($this->{$name}) && $radioCount == 0 && $field[0] == '') ? 'checked' : ''; ?> |
||||
<?= $count == 0 && $radioCount == 0 ? 'autofocus' : ''; ?> |
||||
value="<?= $value; ?>">
|
||||
<label for="<?= $value; ?>"><?= $label; ?> </label>
|
||||
<?= $field[0] != '' ? '<br>' : ''; ?> |
||||
|
||||
<?php $radioCount++; ?> |
||||
<?php } ?> |
||||
</div> |
||||
|
||||
<?php } else if ($field[1] == 'text' || $field[1] == 'email' || |
||||
$field[1] == 'tel' || $field[1] == 'password') { ?> |
||||
|
||||
<div class="form-group"> |
||||
<label for="<?= $name; ?>"><?= $field[0]; ?></label>
|
||||
<input type="<?= $field[1]; ?>" name="<?= $name; ?>" id="<?= $name; ?>" class="form-control"
|
||||
<?= strstr($field[3], 'required') ? 'required' : ''; ?> |
||||
<?= _exists($field, 5) ? "pattern='$field[5]'" : ''; ?> |
||||
<?= _exists($field, 6) ? "title='$field[6]'" : ''; ?> |
||||
<?= strstr($field[3], 'captcha') ? 'autocomplete="off"' : '' ?> |
||||
<?= $count == 0 ? 'autofocus' : ''; ?> |
||||
value="<?= $this->{$name}; ?>">
|
||||
|
||||
<?php if (strstr($field[3], 'captcha')) { ?> |
||||
<img src="/img/captcha.jpg" class="img-fluid pt-2"> |
||||
<?php } ?> |
||||
</div> |
||||
|
||||
<?php } else if ($field[1] == 'textarea') { ?> |
||||
|
||||
<div class="form-group"> |
||||
<label for="<?= $name; ?>"><?= $field[0]; ?></label>
|
||||
<textarea name="<?= $name; ?>" cols="1" rows="5" id="<?= $name; ?>" class="form-control"
|
||||
<?= strstr($field[3], 'required') ? 'required' : ''; ?> |
||||
<?= $count == 0 ? 'autofocus' : ''; ?> |
||||
><?= $this->{$name}; ?></textarea>
|
||||
</div> |
||||
|
||||
<?php } else if ($field[1] == 'checkbox') { ?> |
||||
|
||||
<div class="form-group form-check"> |
||||
|
||||
<?php $checkboxCount = 0; ?> |
||||
<input name="<?= $name; ?>" type="hidden" value="0">
|
||||
<?php foreach ($field[2] as $value => $label) { ?> |
||||
|
||||
<input type="checkbox" name="<?= $name; ?>" id="<?= $value; ?>" class="form-check-input"
|
||||
<?= strstr($field[3], 'required') ? 'required' : ''; ?> |
||||
<?= $this->{$name} == $value ? 'checked' : ''; ?> |
||||
<?= $count == 0 && $checkboxCount == 0 ? 'autofocus' : ''; ?> |
||||
value="<?= $value; ?>">
|
||||
<label for="<?= $value; ?>" class="form-check-label"><?= $label; ?></label><br>
|
||||
|
||||
<?php $checkboxCount++; ?> |
||||
<?php } ?> |
||||
</div> |
||||
|
||||
<?php } ?> |
||||
|
||||
<?php $count++; ?> |
||||
<?php } ?> |
||||
|
||||
<p class="mb-0"> |
||||
<?php if (_exists([$this->form->getReset()])) { ?> |
||||
<button type="reset" class="btn btn-dark"><?= $this->form->getReset(); ?></button>
|
||||
<?php } ?> |
||||
<button type="submit" class="btn btn-dark"><?= $this->form->getSubmit(); ?></button>
|
||||
</p> |
||||
|
||||
<input type="hidden" name="_token" value="<?= $this->csrfToken; ?>" />
|
||||
</form> |
@ -0,0 +1,49 @@
|
||||
<?php |
||||
use App\Classes\Config; |
||||
use App\Classes\User; |
||||
?> |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |
||||
<meta name="description" content="<?= $this->metaDescription; ?>">
|
||||
|
||||
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous"> |
||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> |
||||
|
||||
<?php if ($this->adminSection) { ?> |
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.59.4/codemirror.min.css" rel="stylesheet" integrity="sha384-K/FfhVUneW5TdId1iTRDHsOHhLGHoJekcX6UThyJhMRctwRxlL3XmSnTeWX2k3Qe" crossorigin="anonymous"> |
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.59.4/theme/tomorrow-night-eighties.min.css" rel="stylesheet" integrity="sha384-zTCWZYMg963D68otcZCn2SQ2SBwih+lCwYxWqvx6xH8/Wt6+NN+giHIvcMpN4cPD" crossorigin="anonymous"> |
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.15/dist/summernote-bs4.min.css" rel="stylesheet" integrity="sha384-JNFvp1YkK/DsvVg1KxCYX/jfLcrqFkwUE1+4kt+Zpkhvfeetb13H+j2ZZhrTJwRy" crossorigin="anonymous"> |
||||
<?php } ?> |
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/prismjs@1.22.0/themes/prism-tomorrow.min.css" rel="stylesheet" integrity="sha384-rG0ypOerdVJPawfZS6juq8t8GVE9oCCPJbOXV/bF+e61zYW9Ib6u9WwSbTOK6CKA" crossorigin="anonymous"> |
||||
<link href="https://cdn.jsdelivr.net/npm/prismjs@1.22.0/plugins/line-numbers/prism-line-numbers.min.css" rel="stylesheet" integrity="sha384-n3/UuPVL3caytud/opHXuyFoezGp2oAUB0foYaCAIs2QwGv/nV0kULHS2WAaJuxR" crossorigin="anonymous"> |
||||
|
||||
<link href="<?= Config::c('APP_URL'); ?>/css/style.css?v=<?= rand(); ?>" rel="stylesheet">
|
||||
|
||||
<title><?= ($this->escape)($this->pageTitle); ?><?= $this->pageTitle != '' ? ' - ' : '' ?>Rick van Vonderen</title>
|
||||
<link rel="icon" type="image/png" href="<?= Config::c('APP_URL'); ?>/img/favicon.png">
|
||||
</head> |
||||
<body> |
||||
<?= $this->partial('../app/views/partials/header.php'); ?> |
||||
|
||||
<div class="container"> |
||||
<div class="row"> |
||||
<div class="js-main-content col-<?= User::getToggle() ? '9' : '12'; ?>">
|
||||
<?= $this->yieldView(); ?> |
||||
<?= $this->partial('../app/views/partials/footer.php'); ?> |
||||
</div> |
||||
<?php if ($this->loggedIn) { ?> |
||||
<?= $this->partial('../app/views/partials/admin.php'); ?> |
||||
<?php } ?> |
||||
</div> |
||||
|
||||
<div id="isMobile" class="d-md-none d-lg-none d-xl-none"></div> |
||||
</div> |
||||
|
||||
<?= $this->partial('../app/views/partials/script.php'); ?> |
||||
</body> |
||||
</html> |
@ -0,0 +1,27 @@
|
||||
<?php |
||||
use App\Classes\Config; |
||||
use App\Classes\User; |
||||
?> |
||||
|
||||
<div class="content shadow p-4 mb-4"> |
||||
<?= $this->partial('../app/views/partials/message.php'); ?> |
||||
|
||||
<?php if (User::check()) { ?> |
||||
You're already logged in. Click to <a href="<?= \App\Classes\Config::c('APP_URL'); ?>/logout">log out</a>.
|
||||
|
||||
<?php if ($this->redirectURL) { ?> |
||||
<script type="text/javascript"> |
||||
setTimeout(function() { |
||||
window.location.replace("<?= $this->redirectURL; ?>");
|
||||
}, 3000); |
||||
</script> |
||||
<?php } ?> |
||||
<?php } else { ?> |
||||
<h1>Sign in</h1> |
||||
<?= $this->partial($this->injectView); ?> |
||||
<br> |
||||
<a href="<?= Config::c('APP_URL'); ?>/reset-password">Forgot password</a>?
|
||||
<?php } ?> |
||||
|
||||
<div class="pb-5"></div> |
||||
</div> |
@ -0,0 +1,31 @@
|
||||
<div class="js-admin-menu admin-menu col-3 <?= \App\Classes\User::getToggle() ? 'd-block' : 'd-none'; ?>">
|
||||
<div class="content admin-content shadow p-4"> |
||||
<a href="<?= \App\Classes\Config::c('APP_URL'); ?>/admin"><h4>Admin panel</h4></a>
|
||||
|
||||
<hr> |
||||
<h5>Navigation</h5> |
||||
- <a href="<?= \App\Classes\Config::c('APP_URL'); ?>/admin/section">Section</a>
|
||||
<br> |
||||
- <a href="<?= \App\Classes\Config::c('APP_URL'); ?>/admin/page">Page</a>
|
||||
|
||||
<hr> |
||||
<h5>Link</h5> |
||||
- <a href="<?= \App\Classes\Config::c('APP_URL'); ?>/admin/section-has-content">SectionHasContent</a>
|
||||
<br> |
||||
- <a href="<?= \App\Classes\Config::c('APP_URL'); ?>/admin/page-has-content">PageHasContent</a>
|
||||
|
||||
<hr> |
||||
<h5>Content</h5> |
||||
- <a href="<?= \App\Classes\Config::c('APP_URL'); ?>/admin/content">Content</a>
|
||||
<br> |
||||
- <a href="<?= \App\Classes\Config::c('APP_URL'); ?>/admin/media">Media</a>
|
||||
<br> |
||||
- <a href="<?= \App\Classes\Config::c('APP_URL'); ?>/admin/syntax-highlighting">Syntax Highlighting</a>
|
||||
|
||||
<hr> |
||||
- <a href="<?= \App\Classes\Config::c('APP_URL'); ?>/logout">Log out</a>
|
||||
</div> |
||||
</div> |
||||
<div class="js-admin-toggle admin-toggle fixed-bottom btn btn-dark"> |
||||
<i class="fa fa-bars" aria-hidden="true"></i> |
||||
</div> |
@ -0,0 +1,11 @@
|
||||
<?php |
||||
use App\Classes\Config; |
||||
?> |
||||
|
||||
<footer class="mb-4"> |
||||
<div class="row"> |
||||
<div class="col-12 col-lg-12"> |
||||
© <?= date('Y'); ?> Rick van Vonderen
|
||||
</div> |
||||
</div> |
||||
</footer> |
@ -0,0 +1,54 @@
|
||||
<?php |
||||
use App\Classes\Config; |
||||
?> |
||||
|
||||
<nav class="navbar navbar-expand-lg fixed-top navbar-dark bg-dark shadow"> |
||||
<a class="navbar-brand" href="<?= Config::c('APP_URL'); ?>/"><i class="fa fa-home"></i> Home</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> |
||||
<span class="navbar-toggler-icon"></span> |
||||
</button> |
||||
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent"> |
||||
<ul class="navbar-nav mr-auto"> |
||||
|
||||
<?php foreach ($this->navigation as $section) { ?> |
||||
<?php if (count($section) < 3) { continue; } ?> |
||||
<?php if (count($section) == 3) { ?> |
||||
<a class="nav-link mx-lg-2" |
||||
href="<?= Config::c('APP_URL'); ?>/<?= $section[0] . '/' . $section[2][0]; ?>"
|
||||
><?= ($this->escape)($section[2][1]); ?></a>
|
||||
<?php continue; ?> |
||||
<?php } ?> |
||||
<li class="nav-item dropdown"> |
||||
<a class="nav-link dropdown-toggle mx-lg-3 purple" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"> |
||||
<?= ($this->escape)($section[1]); ?> |
||||
</a> |
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink"> |
||||
|
||||
<?php foreach ($section as $key => $page) { ?> |
||||
<?php if ($key == 0 || $key == 1) { continue; } ?> |
||||
<a class="dropdown-item purple" |
||||
href="<?= Config::c('APP_URL'); ?>/<?= $section[0] . '/' . $page[0]; ?>"
|
||||
><?= ($this->escape)($page[1]); ?></a>
|
||||
<?php } ?> |
||||
|
||||
</div> |
||||
</li> |
||||
<?php } ?> |
||||
|
||||
</ul> |
||||
|
||||
<ul class="navbar-nav ml-auto"> |
||||
<li class="nav-item"> |
||||
<a class="nav-link" href="https://git.riyyi.com/riyyi" target="_blank"><i class="fa fa-coffee"></i> Gitea</a> |
||||
</li> |
||||
<li class="nav-item"> |
||||
<a class="nav-link" href="https://github.com/riyyi" target="_blank"><i class="fa fa-github"></i> GitHub</a> |
||||
</li> |
||||
<li class="nav-item"> |
||||
<a class="nav-link" href="https://gitlab.com/riyyi" target="_blank"><i class="fa fa-gitlab"></i> GitLab</a> |
||||
</li> |
||||
</ul> |
||||
|
||||
</div> |
||||
</nav> |
@ -0,0 +1,8 @@
|
||||
<?php if ($this->type != '') { ?> |
||||
<div class="alert alert-<?= $this->type; ?> alert-dismissible fade show" role="alert">
|
||||
<?= $this->message; ?> |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> |
||||
</div> |
||||
<?php } ?> |
@ -0,0 +1,25 @@
|
||||
<div class="row"> |
||||
<div class="col-12"> |
||||
<nav class="pb-2" aria-label="Page navigation"> |
||||
<ul class="pagination justify-content-center"> |
||||
<li class="page-item <?= $this->page <= 1 ? 'disabled' : ''; ?>">
|
||||
<a class="page-link" href="<?= $this->url . '?page=' . ($this->page - 1); ?>" aria-label="Previous">
|
||||
<span aria-hidden="true">«</span> |
||||
<span class="sr-only">Previous</span> |
||||
</a> |
||||
</li> |
||||
<?php for($i = 1; $i <= $this->pages; $i++) { ?> |
||||
<li class="page-item <?= $this->page == $i ? 'active' : ''; ?>">
|
||||
<a class="page-link" href="<?= $this->url . '?page=' . $i; ?>"><?= $i; ?></a>
|
||||
</li> |
||||
<?php } ?> |
||||
<li class="page-item <?= $this->page >= $this->pages ? 'disabled' : ''; ?>">
|
||||
<a class="page-link" href="<?= $this->url . '?page=' . ($this->page + 1); ?>" aria-label="Next">
|
||||
<span aria-hidden="true">»</span> |
||||
<span class="sr-only">Next</span> |
||||
</a> |
||||
</li> |
||||
</ul> |
||||
</nav> |
||||
</div> |
||||
</div> |
@ -0,0 +1,28 @@
|
||||
<?php |
||||
use App\Classes\Config; |
||||
?> |
||||
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script> |
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> |
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> |
||||
<?php if ($this->adminSection) { ?> |
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.59.4/codemirror.min.js" integrity="sha384-v0AyVIspkm1uirQ6Nsmr90ScryXAWf+xv0DlNrNo1BkSY3sqr7tsUKLZyTMHqy2G" crossorigin="anonymous"></script> |
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.59.4/mode/clike/clike.min.js" integrity="sha384-7LfH34Nu+Rz2rkqh9L9Okzi17vt5/sX9YZvMjHgRhwH94EdtzCaQ5SpIkXfn80/2" crossorigin="anonymous"></script> |
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.59.4/mode/xml/xml.min.js" integrity="sha384-Fohp26Rl1xXN67RS6nTZ+s+DEgvUoMZGBTzPuAwW/tjDMtaL27W3b4Irtxtf9KPw" crossorigin="anonymous"></script> |
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.59.4/mode/javascript/javascript.min.js" integrity="sha384-QPVXEBl5e4kOafHL/KBYAhkf9c7iaD6svR+RGt92QYCbdJiKcmfC4weMMPLif77e" crossorigin="anonymous"></script> |
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.59.4/mode/css/css.min.js" integrity="sha384-3UHVlVGKahbp3CTUk/YC+ICeNAx8Na6vIIrmH18NimXtGR/zF4Ce4F1d+gNPFh9a" crossorigin="anonymous"></script> |
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.59.4/mode/htmlmixed/htmlmixed.min.js" integrity="sha384-C4oV1iL5nO+MmIRm+JoD2sZ03wIafv758LRO92kyg7AFvpvn8oQAra4g3mrw5+53" crossorigin="anonymous"></script> |
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.59.4/mode/php/php.min.js" integrity="sha384-WFPjfiKs+lVB1vp9fq9wEhsgi/5Z86jVLyaGMe2wnxa/3o8SEUCySeqglAaRqxnI" crossorigin="anonymous"></script> |
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.59.4/mode/shell/shell.min.js" integrity="sha384-VzBcr5K83ctjdWufr/bGFmHLB7LbEhdN5dhKWvE3Q/H5Qfvz9dKPJgfHXeaq74da" crossorigin="anonymous"></script> |
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.59.4/mode/python/python.min.js" integrity="sha384-Wh2d9/yLlLvOQErKI4FDPRJXSklc6GlymSMyq37kJcVmfB73bJ33OqA2TpVHBisf" crossorigin="anonymous"></script> |
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.59.4/keymap/vim.min.js" integrity="sha384-MTJMQ129Rzid0TmDEIKZomitzagfahspgvwEWmm1s2ReXw8Evv5NTVf77JlmAOOM" crossorigin="anonymous"></script> |
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.15/dist/summernote-bs4.min.js" integrity="sha384-sH3/pYu1soe3hR5e4aLT8/9wusU2x7Xt10VNfJ6n0VbsUkmkPf+3jI7So6USyROv" crossorigin="anonymous"></script> |
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.22.0/components/prism-core.min.js" data-manual integrity="sha384-BhpFhn+hhQohKi2ygOAJsVKyJu2q+QYydLKC7rqeMZLyEJFQl1DZieRX4/WxKUsJ" crossorigin="anonymous"></script> |
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.22.0/plugins/autoloader/prism-autoloader.min.js" integrity="sha384-FIz0ezhzXpErvkjCsQ+74Je6cuWUBoVubidnSGt498wjjEBaAV27BAISa0/GaAFq" crossorigin="anonymous"></script> |
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.22.0/plugins/line-numbers/prism-line-numbers.min.js" integrity="sha384-xktrwc/DkME39VrlkNS1tFEeq/S0JFbc8J9Q8Bjx7Xy16Z3NnmUi+94RuffrOQZR" crossorigin="anonymous"></script> |
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.22.0/plugins/highlight-keywords/prism-highlight-keywords.min.js" integrity="sha384-Rk2xv6YOAfQH8z3ZAK37pgnQihXfgkER8B5EYhoFc+mMNPzf+t7g2J9U74FAvy2T" crossorigin="anonymous"></script> |
||||
|
||||
<script src="<?= Config::c('APP_URL'); ?>/js/app.js?v=<?= rand(); ?>"></script>
|
||||
<?php } ?> |
||||
<script src="<?= Config::c('APP_URL'); ?>/js/site.js?v=<?= rand(); ?>"></script>
|
@ -0,0 +1,14 @@
|
||||
<div class="content shadow p-4 mb-4"> |
||||
<?= $this->partial('../app/views/partials/message.php'); ?> |
||||
|
||||
<?php if (!$this->newPassword) { ?> |
||||
<h1>Reset password</h1> |
||||
<p>Please fill in one of the fields.</p> |
||||
<?php } else { ?> |
||||
<h1>Set new password</h1> |
||||
<?php } ?> |
||||
|
||||
<?= $this->partial($this->injectView); ?> |
||||
|
||||
<div class="pb-5"></div> |
||||
</div> |
@ -0,0 +1,20 @@
|
||||
{ |
||||
"name": "riyyi/website", |
||||
"description": "Riyyi's website.", |
||||
"type": "project", |
||||
"autoload": { |
||||
"files": [ |
||||
"app/helper.php" |
||||
], |
||||
"psr-4": { |
||||
"App\\Classes\\": "app/classes/", |
||||
"App\\Controllers\\": "app/controllers/", |
||||
"App\\Model\\": "app/model/", |
||||
"App\\Traits\\": "app/traits/" |
||||
} |
||||
}, |
||||
"require": { |
||||
"klein/klein": "^2.1", |
||||
"txthinking/mailer": "^2.0" |
||||
} |
||||
} |
@ -0,0 +1,17 @@
|
||||
<?php |
||||
|
||||
return [ |
||||
'APP_NAME' => '', |
||||
'APP_URL' => '', |
||||
|
||||
'DB_HOST' => '', |
||||
'DB_NAME' => '', |
||||
'DB_USERNAME' => '', |
||||
'DB_PASSWORD' => '', |
||||
|
||||
'MAIL_HOST' => '', |
||||
'MAIL_PORT' => '', |
||||
'MAIL_NAME' => '', |
||||
'MAIL_USERNAME' => '', |
||||
'MAIL_PASSWORD' => '', |
||||
]; |
Binary file not shown.
@ -0,0 +1,9 @@
|
||||
Options -MultiViews -Indexes +FollowSymLinks |
||||
|
||||
RewriteEngine On |
||||
RewriteBase / |
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-f |
||||
RewriteCond %{REQUEST_FILENAME} !-d |
||||
|
||||
RewriteRule ^(.*)$ index.php/$1 [NC,L] |
@ -0,0 +1,117 @@
|
||||
/* General */ |
||||
/*----------------------------------------*/ |
||||
|
||||
html { |
||||
font-family: "Segoe UI", "DejaVu Sans", sans-serif; |
||||
} |
||||
|
||||
body { |
||||
background-color: #f4f4f4; |
||||
} |
||||
|
||||
table td.middle { |
||||
vertical-align: middle; |
||||
} |
||||
|
||||
/* Navigation */ |
||||
/*----------------------------------------*/ |
||||
|
||||
nav.shadow { |
||||
box-shadow: 0 .25rem .5rem rgba(0,0,0,.28) !important; |
||||
} |
||||
|
||||
/* Main content */ |
||||
/*----------------------------------------*/ |
||||
|
||||
.container { |
||||
margin: 56px auto 0 auto; |
||||
} |
||||
|
||||
.content { |
||||
position: relative; |
||||
/* height: calc(100% - 56px); */ |
||||
min-height: calc(100vh - 80px); |
||||
padding: 20px 20px 50px 20px; |
||||
|
||||
background-color: #fff; |
||||
} |
||||
|
||||
#home { |
||||
position: absolute; |
||||
top: 50%; |
||||
left: 50%; |
||||
transform: translate(-50%, -50%); |
||||
|
||||
text-align: center; |
||||
} |
||||
|
||||
.padding-border { |
||||
padding: 10px 15px 10px 15px; |
||||
} |
||||
|
||||
.padding-border-col { |
||||
padding: 10px 15px 0px 0px; |
||||
} |
||||
|
||||
.pointer { |
||||
cursor: pointer; |
||||
} |
||||
|
||||
/* Admin content */ |
||||
/*----------------------------------------*/ |
||||
|
||||
.admin-toggle { |
||||
left: auto; |
||||
margin: 10px 15px; |
||||
padding: 5px 10px; |
||||
border-radius: 5px; |
||||
|
||||
color: #ffffff; |
||||
} |
||||
|
||||
.admin-menu { |
||||
position: fixed; |
||||
top: 0; |
||||
right: 0; |
||||
padding-right: 0px; |
||||
z-index: 1030; |
||||
} |
||||
|
||||
.admin-content { |
||||
min-height: 100vh; |
||||
} |
||||
|
||||
.admin-hidden { |
||||
position: absolute; |
||||
top: 0; |
||||
left: -9999px; |
||||
} |
||||
|
||||
.admin-upload-dragover { |
||||
background-color: rgba(0,0,0,0.1); |
||||
} |
||||
|
||||
/* Cursor color in normal mode */ |
||||
.CodeMirror.cm-fat-cursor .CodeMirror-cursor { |
||||
background: white; |
||||
} |
||||
|
||||
/* Cursor color in visual mode */ |
||||
.CodeMirror .cm-animate-fat-cursor { |
||||
background-color: white; |
||||
animation: none !important; |
||||
} |
||||
|
||||
/* Footer */ |
||||
/*----------------------------------------*/ |
||||
|
||||
footer { |
||||
position: absolute; |
||||
bottom: 0; |
||||
width: calc(100% - 30px); |
||||
height: 50px; |
||||
padding-top: 10px; |
||||
|
||||
text-align: center; |
||||
color: #909090; |
||||
} |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 434 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
@ -0,0 +1,10 @@
|
||||
<?php |
||||
|
||||
error_reporting(E_ALL); |
||||
ini_set('display_errors', 1); |
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php'; |
||||
|
||||
\App\Classes\Session::start(); |
||||
\App\Classes\Config::load(); |
||||
\App\Classes\Router::fire(); |
@ -0,0 +1,226 @@
|
||||
$(document).ready(function() { |
||||
|
||||
// Confirm to submit
|
||||
$('.js-confirm').on('click', function() { |
||||
return confirm("Are you sure you want to continue?"); |
||||
}); |
||||
|
||||
// Confirm to delete
|
||||
$('.js-delete').on('click', function(event) { |
||||
event.preventDefault(); |
||||
|
||||
var csrfToken = $(this).attr('data-token'); |
||||
|
||||
if (confirm('Are you sure you want to continue?')) { |
||||
$.ajax({ |
||||
url: $(this).attr('href'), |
||||
type: 'DELETE', |
||||
data: { _token: csrfToken }, |
||||
success: function(data) { |
||||
window.location.reload(); |
||||
} |
||||
}); |
||||
} |
||||
}); |
||||
|
||||
// Edit
|
||||
$('.js-edit').on('click', function(event) { |
||||
var href = $(this).attr('href'); |
||||
|
||||
$.ajax({ |
||||
url: href, |
||||
type: "PUT", |
||||
data: $('#form-edit').serialize(), |
||||
success: function(data, textStatus, jqXHR) { |
||||
window.location.href = data; |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
//------------------------------------------
|
||||
|
||||
$('.js-upload') |
||||
.on("drag dragstart dragend dragover dragenter dragleave drop", function(e) { |
||||
e.preventDefault(); |
||||
e.stopPropagation(); |
||||
}) |
||||
.on("dragover dragenter", function(e) { |
||||
$(this).addClass('admin-upload-dragover'); |
||||
}) |
||||
.on("dragend dragleave drop", function(e) { |
||||
$(this).removeClass('admin-upload-dragover'); |
||||
}) |
||||
.on("drop", function(e) { |
||||
// Set file input to the dropped files
|
||||
$(this).find('input[type=file]')[0].files = e.originalEvent.dataTransfer.files; |
||||
|
||||
updateUploadLabel($(this)); |
||||
}) |
||||
.change(function(e) { |
||||
updateUploadLabel($(this)); |
||||
}); |
||||
|
||||
function updateUploadLabel(element) { |
||||
var files = element.find('input[type=file]')[0].files; |
||||
var text = element.find('span'); |
||||
|
||||
// Update label text
|
||||
if (files.length == 1) { |
||||
text.html(files[0].name); |
||||
} |
||||
else { |
||||
text.html(files.length + ' files selected'); |
||||
|
||||
// Set tooltip
|
||||
var fileNames = ''; |
||||
Array.from(files).forEach(file => { fileNames += file.name + '\n'; }); |
||||
element.prop('title', fileNames); |
||||
} |
||||
} |
||||
|
||||
//------------------------------------------
|
||||
|
||||
// CodeMirror editor
|
||||
var editor; |
||||
var editorOptions = { |
||||
lineSeparator: '\n', |
||||
theme: 'tomorrow-night-eighties', |
||||
mode: 'text/html', |
||||
indentUnit: 4, |
||||
lineNumbers: true, |
||||
lineWrapping: true, |
||||
cursorBlinkRate: 0, |
||||
keyMap: 'vim', |
||||
}; |
||||
|
||||
// Copy editor selection to clipboard on <C-c>
|
||||
function registerEditorCopy() { |
||||
editor.on('vim-keypress', function(e) { |
||||
if (e === '<C-c>' && editor.state.vim.visualMode === true) { |
||||
document.execCommand("copy"); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
// Enable CodeMirror Editor
|
||||
$.fn.codeMirror = function() { |
||||
editor = CodeMirror.fromTextArea($(this)[0], editorOptions); |
||||
editor.setSize('100%', '500'); |
||||
registerEditorCopy(); |
||||
|
||||
return editor; |
||||
} |
||||
$('textarea#syntax-code').codeMirror().setOption('mode', 'text/x-csrc'); |
||||
|
||||
// Enable WYSIWYG Editor
|
||||
$('textarea#summernote').summernote({ |
||||
tabDisable: true, |
||||
tabsize: 4, |
||||
minHeight: 450, |
||||
maxHeight: null, |
||||
focus: true, |
||||
fontNames: [ |
||||
'Helvetica', 'Arial', 'Verdana', 'Trebuchet MS', 'sans-serif', |
||||
'Georgia', 'Times New Roman', 'Courier New', 'monospace' |
||||
], |
||||
fontNamesIgnoreCheck: [], |
||||
toolbar: [ |
||||
['style', ['style']], |
||||
['font', ['bold', 'underline', 'italic', 'strikethrough', 'clear']], |
||||
['fontname', ['fontname']], |
||||
['color', ['color']], |
||||
['para', ['ul', 'ol', 'paragraph']], |
||||
['table', ['table']], |
||||
['insert', ['link', 'picture', 'video']], |
||||
['view', ['fullscreen', 'codeview', 'help']], |
||||
], |
||||
callbacks: { |
||||
onInit: function() { |
||||
$("#summernote").summernote('codeview.activate'); |
||||
|
||||
editor = $('.CodeMirror')[0].CodeMirror; |
||||
registerEditorCopy(); |
||||
} |
||||
}, |
||||
prettifyHtml: false, |
||||
codemirror: editorOptions, |
||||
}); |
||||
|
||||
var syntaxLanguages = { |
||||
'c': 'text/x-csrc', |
||||
'cpp': 'text/x-c++src', |
||||
'css': 'text/css', |
||||
'html': 'text/html', |
||||
'javascript': 'text/javascript', |
||||
'php': 'application/x-httpd-php', |
||||
'python': 'text/x-python', |
||||
'shell': 'text/x-sh', |
||||
}; |
||||
|
||||
// Syntax Language selection
|
||||
$('#syntax-language').on('change', function() { |
||||
// Set the editor language mode
|
||||
editor.setOption('mode', syntaxLanguages[this.value]); |
||||
|
||||
// Set the language class
|
||||
var parse = $('code'); |
||||
parse.removeClass().addClass('language-' + this.value); |
||||
}) |
||||
.trigger('change'); |
||||
|
||||
// Syntax Highlight
|
||||
$('#syntax-highlight').on('click', function() { |
||||
// Set the code
|
||||
var parse = $('code'); |
||||
parse.text(editor.getValue()); |
||||
|
||||
// Highlight the <code> DOM element
|
||||
Prism.highlightAll(); |
||||
}); |
||||
|
||||
// Copy highlighted syntax to the clipboard
|
||||
$('#syntax-copy').on('click', function() { |
||||
// Copy text into hidden textarea
|
||||
var parse = $('div#syntax-parse').html(); |
||||
var copy = $('textarea#syntax-parse-copy'); |
||||
copy.val(parse); |
||||
|
||||
// Select the text field
|
||||
copy = copy[0]; |
||||
copy.select(); |
||||
copy.setSelectionRange(0, copy.value.length); |
||||
|
||||
// Copy the text inside the field to the clipboard
|
||||
document.execCommand("copy"); |
||||
|
||||
alert("Copied to the clipboard"); |
||||
}); |
||||
|
||||
//------------------------------------------
|
||||
|
||||
// Hotkeys
|
||||
$(window).keydown(function(e) { |
||||
|
||||
// Save form
|
||||
if (e.ctrlKey && e.keyCode == 83) { // Ctrl + S
|
||||
e.preventDefault(); |
||||
|
||||
// Codeview needs to be deactived before saving
|
||||
$('#summernote').summernote('codeview.deactivate'); |
||||
|
||||
$('.js-edit').click(); |
||||
} |
||||
|
||||
// Toggle codeview
|
||||
if (e.ctrlKey && e.keyCode == 71) { // Ctrl + G
|
||||
e.preventDefault(); |
||||
|
||||
$('#summernote').summernote('codeview.toggle'); |
||||
} |
||||
|
||||
}); |
||||
|
||||
}); |
||||
|
||||
// @Todo
|
||||
// - Look at converting .ajax() into the JS fetch API (native)
|
@ -0,0 +1,83 @@
|
||||
$(document).ready(function() { |
||||
|
||||
//------------------------------------------//
|
||||
|
||||
$.fn.inViewport = function() { |
||||
var elementTop = $(this).offset().top; |
||||
var elementBottom = elementTop + $(this).outerHeight(); |
||||
|
||||
var viewportTop = $(window).scrollTop(); |
||||
var viewportBottom = viewportTop + $(window).height(); |
||||
|
||||
return elementBottom > viewportTop && elementTop < viewportBottom; |
||||
} |
||||
|
||||
//------------------------------------------//
|
||||
|
||||
// Image hover mouseenter
|
||||
$(".js-img-hover").mouseenter(function(event) { |
||||
var width = $(window).width() - event.clientX - 1 + "px"; |
||||
if (!$("#isMobile").is(":hidden")) { |
||||
width = "100%"; |
||||
} |
||||
var url = $(this).data("img-hover"); |
||||
var divImg = $("<div id='img-hover' style='z-index: 9999; position: fixed; top: 0px; right: 0px;'>" + |
||||
"<img style='max-width: " + width + "; height auto;' src=" + url + "></div>"); |
||||
divImg.appendTo("body"); |
||||
}); |
||||
|
||||
// Image hover mouseleave
|
||||
$(".js-img-hover").mouseleave(function() { |
||||
$("#img-hover").remove(); |
||||
}); |
||||
|
||||
// Lazy load video's
|
||||
$(window).on('resize scroll', function() { |
||||
$('.js-video').each(function(index) { |
||||
if (!$(this).inViewport()) { |
||||
return true; |
||||
} |
||||
|
||||
$(this).find('source').each(function() { |
||||
var source = $(this).attr('data-src'); |
||||
if (source === undefined) { |
||||
return true; |
||||
} |
||||
|
||||
$(this).attr("src", source); |
||||
var video = this.parentElement; |
||||
video.load(); |
||||
video.play(); |
||||
$(this).removeAttr('data-src'); |
||||
}); |
||||
}); |
||||
}); |
||||
$(window).trigger('scroll'); |
||||
|
||||
// Only 1 field is required
|
||||
var $inputs = $('input[name=reset-password-username],input[name=reset-password-email]'); |
||||
$inputs.on('input', function() { |
||||
// Set the required property of the other input to false if this input is not empty.
|
||||
$inputs.not(this).prop('required', !$(this).val().length); |
||||
}); |
||||
|
||||
// List toggle
|
||||
$('.js-toggle').click(function() { |
||||
$(this).next("ul").toggle(400); |
||||
}); |
||||
|
||||
// Admin panel toggle
|
||||
$('.js-admin-toggle').click(function() { |
||||
$.get('/admin/toggle').done(function(data) { |
||||
if (data == '1') { |
||||
$('.js-admin-menu').removeClass('d-none').addClass('d-block'); |
||||
$('.js-main-content').removeClass('col-12').addClass('col-9'); |
||||
} |
||||
else if (data == '0') { |
||||
$('.js-admin-menu').removeClass('d-block').addClass('d-none'); |
||||
$('.js-main-content').removeClass('col-9').addClass('col-12'); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
}); |
@ -0,0 +1,26 @@
|
||||
<?php |
||||
|
||||
use \App\Classes\Router; |
||||
|
||||
Router::resource('/admin/section', 'CrudController'); |
||||
Router::resource('/admin/page', 'CrudController'); |
||||
Router::resource('/admin/content', 'CrudController'); |
||||
Router::resource('/admin/section-has-content', 'CrudController'); |
||||
Router::resource('/admin/page-has-content', 'CrudController'); |
||||
Router::resource('/admin/media', 'MediaController'); |
||||
|
||||
// Basic routes |
||||
return [ |
||||
// URL, controller, action, view/title/description |
||||
['/', 'IndexController', '', ''], |
||||
['/img/captcha.jpg', 'IndexController', 'captcha', ''], |
||||
['/sitemap.xml', 'IndexController', 'sitemap', ''], |
||||
['/login', 'LoginController', 'login', ['', 'Sign in', '']], |
||||
['/reset-password', 'LoginController', 'reset', ['', 'Reset password', '']], |
||||
['/logout', 'LoginController', 'logout', ''], |
||||
['/admin', 'AdminController', '', ''], |
||||
['/admin/toggle', 'AdminController', 'toggle', ''], |
||||
['/admin/syntax-highlighting', 'AdminController', 'syntax', ''], |
||||
['/test', 'TestController', '', ''], |
||||
// ["", "", "", ""], |
||||
]; |
@ -0,0 +1,52 @@
|
||||
#!/bin/sh |
||||
|
||||
# Syncs website contents to remote server |
||||
# Depends: rsync, ssh |
||||
|
||||
# User-config--------------------------- |
||||
|
||||
config="./syncconfig.sh" |
||||
|
||||
# ------------------------------------------ |
||||
|
||||
if [ -f "$config" ]; then |
||||
. "$config" |
||||
else |
||||
echo "Please configure the $config file" |
||||
exit 1 |
||||
fi |
||||
|
||||
if [ "$(dirname "$0")" != "." ]; then |
||||
echo "Please run this script from the directory it resides." |
||||
exit 1 |
||||
fi |
||||
|
||||
directories=\ |
||||
"app |
||||
public" |
||||
|
||||
source="." |
||||
destination="$user@$ip:$remote_path" |
||||
|
||||
echo "Syncing files to remote.." |
||||
|
||||
# Directory syncing |
||||
for dir in $directories; do |
||||
cd "$dir" || exit 1 |
||||
rsync -ar --delete-after --rsh="ssh -p $port" \ |
||||
--out-format="- %n" \ |
||||
--exclude="media" \ |
||||
"$source" "$destination/$dir" |
||||
cd .. |
||||
done |
||||
|
||||
# File syncing |
||||
rsync -a --rsh="ssh -p $port" \ |
||||
--out-format="- %n" \ |
||||
--include="composer.json" \ |
||||
`# --include="config.php"` \ |
||||
--include="route.php" \ |
||||
--exclude="*" \ |
||||
"$source" "$destination" |
||||
|
||||
echo "Synced all files to remote." |
Loading…
Reference in new issue