Compare commits

...

7 Commits

Author SHA1 Message Date
Riyyi ef9b1a1998 Helper: Add parameter in _log() to disable die() after printing 3 years ago
Riyyi 077e4c434c Everywhere: Add config and cache page 3 years ago
Riyyi 2dee309279 Model: Add updateOrCreate function 3 years ago
Riyyi ac66a97d5e Model: Add documentation DocBlock to query function 3 years ago
Riyyi f49f615238 CRUD: Fix show page heading when model doesn't have a title 3 years ago
Riyyi 2fb4481876 Model: Fix creation of new models 3 years ago
Riyyi 253a3fe2f6 Admin+Config: Add toggle that disables Cloudflare cache 3 years ago
  1. 128
      app/controllers/CacheController.php
  2. 7
      app/helper.php
  3. 23
      app/model/ConfigModel.php
  4. 114
      app/model/Model.php
  5. 82
      app/views/admin/cache.php
  6. 2
      app/views/admin/crud/show.php
  7. 2
      app/views/layouts/default.php
  8. 6
      app/views/partials/admin.php
  9. 4
      app/views/partials/script.php
  10. 4
      config.php.example
  11. 34
      public/js/app.js
  12. 3
      route.php

128
app/controllers/CacheController.php

@ -0,0 +1,128 @@
<?php
namespace App\Controllers;
use App\Classes\Config;
use App\Model\ConfigModel;
class CacheController extends PageController {
public function cacheAction(): void
{
$config = $this->getConfigValues();
$this->router->service()->config = $config;
parent::view();
}
public function developmentAction(): void
{
if (Config::c('CLOUDFLARE_ENABLED') != '1') {
return;
}
$token = Config::c('CLOUDFLARE_TOKEN');
$zone = Config::c('CLOUDFLARE_ZONE');
$url = "https://api.cloudflare.com/client/v4/zones/$zone/settings/development_mode";
$headers = [
"Authorization: Bearer $token",
"Content-Type: application/json"
];
$config = $this->getConfigValues();
$currentState = $config['CLOUDFLARE_DEVELOPMENT_MODE_ENABLED'];
$newState = $currentState == '1' ? 'off' : 'on';
$data = '{"value": "' . $newState . '"}';
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PATCH');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
// curl_setopt($curl, CURLINFO_HEADER_OUT, 1);
$response = curl_exec($curl);
// $info = curl_getinfo($curl, CURLINFO_HEADER_OUT);
curl_close($curl);
$this->saveConfigValues($response);
echo $response;
// echo $info;
}
//-------------------------------------//
private static function getConfigValues(): array
{
$result = [];
$config = [
'CLOUDFLARE_DEVELOPMENT_MODE_ENABLED' => '0',
'CLOUDFLARE_DEVELOPMENT_MODE_UPDATED_AT' => '',
'CLOUDFLARE_DEVELOPMENT_MODE_EXPIRES_IN' => '',
];
foreach ($config as $key => $value) {
$result[$key] = ConfigModel::firstOrCreate(
['key' => $key],
['value' => $value]
)->value;
}
if ($result['CLOUDFLARE_DEVELOPMENT_MODE_ENABLED']) {
$expiresIn = $result['CLOUDFLARE_DEVELOPMENT_MODE_EXPIRES_IN'];
$updatedAt = $result['CLOUDFLARE_DEVELOPMENT_MODE_UPDATED_AT'];
$expiresAtObject = new \DateTime($updatedAt);
$expiresAt = $expiresAtObject
->modify("+ $expiresIn seconds")
->format('Y-m-d H:i:s');
$nowObject = new \DateTime('now');
$now = $nowObject->format('Y-m-d H:i:s');
if ($now >= $expiresAt) {
ConfigModel::updateOrCreate(
['key' => 'CLOUDFLARE_DEVELOPMENT_MODE_ENABLED'],
['value' => 0]);
$result['CLOUDFLARE_DEVELOPMENT_MODE_ENABLED'] = 0;
}
else {
$result['enabled-remaining'] = $expiresAtObject->modify(
'- ' . $nowObject->getTimestamp() . ' seconds'
)->format('H:i:s');
}
}
return $result;
}
private static function saveConfigValues(string $response): void
{
$decodedResponse = json_decode($response, true);
if ($decodedResponse['success'] == true) {
$state = $decodedResponse['result']['value'];
$expiresIn = $decodedResponse['result']['time_remaining'];
$updatedAt = $decodedResponse['result']['modified_on'];
$updatedAtFormatted = (new \DateTime($updatedAt))->format('Y-m-d H:i:s');
ConfigModel::updateOrCreate(
['key' => 'CLOUDFLARE_DEVELOPMENT_MODE_ENABLED'],
['value' => $state == 'on' ? 1 : 0]);
ConfigModel::updateOrCreate(
['key' => 'CLOUDFLARE_DEVELOPMENT_MODE_EXPIRES_IN'],
['value' => $expiresIn]);
ConfigModel::updateOrCreate(
['key' => 'CLOUDFLARE_DEVELOPMENT_MODE_UPDATED_AT'],
['value' => $updatedAtFormatted]);
}
}
}

7
app/helper.php

@ -51,14 +51,17 @@ function _randomStr(int $length, string $keyspace =
* Print variable inside of a <pre> and exit
*
* @param mixed[] $output The variable (single/array) to print
* @param bool $die Call die(); after printing
*
* @return void Nothing
*/
function _log($output): void {
function _log($output, bool $die = true): void {
echo '<pre>';
var_dump($output);
echo '</pre>';
die();
if ($die) {
die();
}
}
//-------------------------------------//

23
app/model/ConfigModel.php

@ -0,0 +1,23 @@
<?php
namespace App\Model;
use App\Traits\Log;
class ConfigModel extends Model {
use Log;
protected $table = 'config';
protected $sort = 'key';
public $title = "Config";
// Attribute rules
// Name | Type | Required | Filtered
public $rules = [
["key", "text", 1, 0],
["value", "text", 0, 0],
["log_id", "text", 1, 1],
];
}

114
app/model/Model.php

@ -63,21 +63,41 @@ abstract class Model {
//-------------------------------------//
/**
* Retreive data via PDO prepared statements
*
* The most frequently used constants for PDO are listed below,
* find more at: {@link https://www.php.net/manual/en/pdo.constants.php}
* - PDO::PARAM_BOOL
* - PDO::PARAM_NULL
* - PDO::PARAM_INT
* - PDO::PARAM_STR
*
* Usage:
* self::query(
* "SELECT * FROM `example` WHERE `id` = :id AND `number` = :number AND `text` = :text", [
* [':id', 1],
* [':number', 7, \PDO::PARAM_INT],
* [':text', 'A random string', \PDO::PARAM_STR],
* ]);
*
* self::query(
* 'SELECT * FROM `example` WHERE `id` IN (?, ?, ?) AND `thing` = ?, [
* 1, 2, 3, 'stuff'
* ],
* '?'
* );
*
* @param $query The full prepared query statement
* @param $parameters The values to insert into the prepared statement
* @param $type Type of prepared statement, ':' for named placeholders,
* '?' for value placeholders
*
* @return array|null Retreived data, or 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;
}
@ -237,7 +257,7 @@ abstract class Model {
$required = false;
foreach ($this->rules as $rule) {
if ($rule[0] == $attribute && $rule[2] == 1) {
if ($rule[0] == $attribute && $rule[2] == 1 && $rule[3] == 0) {
$required = true;
break;
}
@ -325,16 +345,6 @@ abstract class Model {
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
{
@ -413,14 +423,15 @@ abstract class Model {
}
/**
* Load Model data: all, with a limit or pagination
* Load all Models, optionally 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 {
public static function all(int $limitOrPage = -1, int $limit = -1): ?array
{
$class = get_called_class();
$model = new $class;
@ -451,7 +462,9 @@ abstract class Model {
/**
* Retreive Model, or instantiate
* Usage: $model = \App\Model\Example::firstOrNew(['name' => 'Example name']);
*
* Usage:
* $model = \App\Model\Example::firstOrNew(['name' => 'Example name']);
*
* @param $search Retrieve by
* @param $data Instantiate with search plus data
@ -474,13 +487,16 @@ abstract class Model {
/**
* Create new Model
* Usage: $model = \App\Model\Example::create(['name' => 'Example name']);
*
* 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 {
public static function create(array $data): Model
{
$class = get_called_class();
$model = new $class;
$model->fill($data);
@ -489,15 +505,20 @@ abstract class Model {
}
/**
* Retreive Model, or create
* Usage: $model = \App\Model\Example::firstOrCreate(['name' => 'Example name']);
* Retreive Model, create if it doesn't exist
*
* Usage:
* $model = \App\Model\AddressModel::firstOrCreate(
* ['zip_code' => '1234AB', 'house_number' => 3],
* ['street' => 'Example lane']);
*
* @param $search Retrieve by
* @param $data Instantiate with search plus data
* @param $data Data used for creation
*
* @return Model The Model
*/
public static function firstOrCreate(array $search, array $data = []): Model {
public static function firstOrCreate(array $search, array $data = []): Model
{
$model = self::firstOrNew($search, $data);
if (!$model->exists()) {
@ -507,14 +528,27 @@ abstract class Model {
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;
// }
/**
* Update Model, create if it doesn't exist
*
* Usage:
* $model = \App\Model\FlightModel::updateOrCreate(
* ['departure' => 'Oakland', 'desination' => 'San Diego'],
* ['price' => 99]);
*
* @param $search Retrieve by
* @param $data Data used for creation
*
* @return Model The Model
*/
public static function updateOrCreate(array $search, array $data): Model
{
$model = self::firstOrNew($search, $data);
$model->fill($data);
$model->save();
return $model;
}
}

82
app/views/admin/cache.php

@ -0,0 +1,82 @@
<?php
use \App\Classes\Config;
?>
<div class="content shadow p-4 mb-4">
<h3 class="mb-4">Cache</h3>
<?php if (Config::c('CLOUDFLARE_ENABLED') != '1') { ?>
To enable the Cloudflare cache options,
make sure to set the following option in the <p>config.php</p> file:
<pre class="line-numbers mb-4 language-php"><p class="language-php"><span class="token single-quoted-string string">'CLOUDFLARE_ENABLED'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token single-quoted-string string">'1'</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></p></pre>
</p><?php } else { ?>
<div>
<p>
Cloudflare cache options:
</p>
</div>
<hr>
<div class="row align-items-center">
<div class="col-9 col-md-10 col-lg-11">
<h5>Purge CSS/JavaScript</h5>
<p class="mb-0">Granuarly remove .css and .js files from Cloudflare&apos;s cache.</p>
</div>
<div class="col-3 col-md-2 col-lg-1">
<div class="d-flex justify-content-end">
<button id="" class="btn btn-dark">Purge</button>
</div>
</div>
</div>
<hr>
<div class="row align-items-center">
<div class="col-9 col-md-10 col-lg-11">
<h5>Purge Fonts/Images</h5>
<p class="mb-0">Granuarly remove font and images files from Cloudflare&apos;s cache.</p>
</div>
<div class="col-3 col-md-2 col-lg-1">
<div class="d-flex justify-content-end">
<button id="" class="btn btn-danger">Purge</button>
</div>
</div>
</div>
<hr>
<div class="row align-items-center">
<div class="col-9 col-md-10 col-lg-11">
<h5>Purge All Files</h5>
<p class="mb-0">Remove ALL files from Cloudflare&apos;s cache.</p>
</div>
<div class="col-3 col-md-2 col-lg-1">
<div class="d-flex justify-content-end">
<button id="" class="btn btn-danger">Purge</button>
</div>
</div>
</div>
<hr>
<div class="row align-items-center">
<div class="col-9 col-mlg-10 col-xl-11">
<h5>Enable Development Mode</h5>
<p class="mb-0">
This will bypass Cloudflare&apos;s accelerated cache and slow down your site,
but is useful if you are making changes to cacheable content
(like images, CSS, or JavaScript) and would like to see those changes right away.
Once entered, development mode will last for 3 hours and then automatically toggle off.
<?php $state = $this->config['CLOUDFLARE_DEVELOPMENT_MODE_ENABLED']; ?>
<span id="develop-enabled" style="visibility: <?= $state == '1' ? 'visible' : 'hidden'; ?>;">
Enabled for another
<code id="develop-remaining">
<?= $state == '1' ? $this->config['enabled-remaining'] : ''; ?>
</code> hours.
</span>
</p>
</div>
<div class="col-3 col-lg-2 col-xl-1">
<div class="d-flex justify-content-start">
<input type="checkbox" id="development-mode"
<?= $this->config['CLOUDFLARE_DEVELOPMENT_MODE_ENABLED'] ? 'checked' : ''; ?>>
</div>
</div>
</div>
<?php } ?>
<div class="pb-5"></div>
</div>

2
app/views/admin/crud/show.php

@ -1,7 +1,7 @@
<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>
<h3><?= property_exists($this->model, 'title') ? ($this->escape)($this->model->title) : 'Show'; ?></h3>
<table class="table table-bordered table-striped">
<thead>

2
app/views/layouts/default.php

@ -22,7 +22,7 @@
<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">
<link href="<?= Config::c('APP_URL'); ?>/css/style.css" 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">

6
app/views/partials/admin.php

@ -22,6 +22,12 @@
<br>
- <a href="<?= \App\Classes\Config::c('APP_URL'); ?>/admin/syntax-highlighting">Syntax Highlighting</a>
<hr>
<h5>Config</h5>
- <a href="<?= \App\Classes\Config::c('APP_URL'); ?>/admin/cache">Cache</a>
<br>
- <a href="<?= \App\Classes\Config::c('APP_URL'); ?>/admin/config">Config</a>
<hr>
- <a href="<?= \App\Classes\Config::c('APP_URL'); ?>/logout">Log out</a>
</div>

4
app/views/partials/script.php

@ -23,6 +23,6 @@
<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>
<script src="<?= Config::c('APP_URL'); ?>/js/app.js"></script>
<?php } ?>
<script src="<?= Config::c('APP_URL'); ?>/js/site.js?v=<?= rand(); ?>"></script>
<script src="<?= Config::c('APP_URL'); ?>/js/site.js"></script>

4
config.php.example

@ -14,4 +14,8 @@ return [
'MAIL_NAME' => '',
'MAIL_USERNAME' => '',
'MAIL_PASSWORD' => '',
'CLOUDFLARE_ENABLED' => '',
'CLOUDFLARE_TOKEN' => '',
'CLOUDFLARE_ZONE' => '',
];

34
public/js/app.js

@ -227,6 +227,40 @@ $(document).ready(function() {
});
//------------------------------------------
// Developer mode
$('#development-mode').on('click', function(e)
{
e.preventDefault();
if (!confirm('Are you sure you want to continue?')) {
return;
}
$.get('/admin/toggle-development-mode').done(function(data)
{
const response = JSON.parse(data);
if (response.success == true) {
if (response.result.value == 'on') {
e.target.checked = true;
$('#develop-enabled').css('visibility', 'visible');
$('#develop-remaining').text('03:00:00');
}
else {
e.target.checked = false;
$('#develop-enabled').css('visibility', 'hidden');
}
alert("Development mode has been set to: '" + response.result.value + "'");
}
else {
console.log(data);
alert("Development mode could not be enabled!")
}
});
});
});
// @Todo

3
route.php

@ -2,6 +2,7 @@
use \App\Classes\Router;
Router::resource('/admin/config', 'CrudController');
Router::resource('/admin/section', 'CrudController');
Router::resource('/admin/page', 'CrudController');
Router::resource('/admin/content', 'CrudController');
@ -20,7 +21,9 @@ return [
['/reset-password', 'LoginController', 'reset', ['', 'Reset password', '']],
['/logout', 'LoginController', 'logout', ''],
['/admin', 'AdminController', '', ''],
['/admin/cache', 'CacheController', 'cache', ''],
['/admin/toggle', 'AdminController', 'toggle', ''],
['/admin/toggle-development-mode', 'CacheController', 'development', ''],
['/admin/syntax-highlighting', 'AdminController', 'syntax', ''],
['/test', 'TestController', '', ''],
// ["", "", "", ""],

Loading…
Cancel
Save