Initial commit
Couldnt keep the history unfortunately.
This commit is contained in:
+31
@@ -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';
|
||||||
|
}
|
||||||
+178
@@ -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' => '',
|
||||||
|
];
|
||||||
BIN
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.
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 434 KiB |
Binary file not shown.
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."
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
user=""
|
||||||
|
ip=""
|
||||||
|
port=
|
||||||
|
|
||||||
|
remote_path=""
|
||||||
Reference in New Issue
Block a user