Basic plugin support + admin plugin (#5)

* Started writing plugin support

* Started work on the Admin plugin

* Added page list regeneration option

* Started work on the config editor

* Rebased

* Admin plugin can now edit the config

* Ability to edit pages + verify config before saving

* Make PHPStan happy :)

* Implemented authentication
This commit is contained in:
Belle Aerni 2023-01-08 08:54:54 -08:00 committed by GitHub
parent 0c79ec0ba6
commit 457cce525b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 320 additions and 4 deletions

37
package-lock.json generated
View file

@ -8,6 +8,7 @@
"name": "antcms",
"version": "1.0.0",
"devDependencies": {
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.8",
"tailwindcss": "^3.2.4"
}
@ -47,6 +48,18 @@
"node": ">= 8"
}
},
"node_modules/@tailwindcss/forms": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.3.tgz",
"integrity": "sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==",
"dev": true,
"dependencies": {
"mini-svg-data-uri": "^1.2.3"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
}
},
"node_modules/@tailwindcss/typography": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.8.tgz",
@ -447,6 +460,15 @@
"node": ">=8.6"
}
},
"node_modules/mini-svg-data-uri": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
"dev": true,
"bin": {
"mini-svg-data-uri": "cli.js"
}
},
"node_modules/minimist": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
@ -875,6 +897,15 @@
"fastq": "^1.6.0"
}
},
"@tailwindcss/forms": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.3.tgz",
"integrity": "sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==",
"dev": true,
"requires": {
"mini-svg-data-uri": "^1.2.3"
}
},
"@tailwindcss/typography": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.8.tgz",
@ -1179,6 +1210,12 @@
"picomatch": "^2.3.1"
}
},
"mini-svg-data-uri": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
"dev": true
},
"minimist": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",

View file

@ -15,6 +15,7 @@
},
"homepage": "https://github.com/BelleNottelling/AntCMS#readme",
"devDependencies": {
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.8",
"tailwindcss": "^3.2.4"
}

41
src/AntCMS/AntAuth.php Normal file
View file

@ -0,0 +1,41 @@
<?php
namespace AntCMS;
use AntCMS\AntConfig;
class AntAuth
{
public static function checkAuth()
{
$currentConfig = AntConfig::currentConfig();
if (empty($currentConfig['admin']['password'])) {
die("You must set a password in your config.yaml file before you can authenticate within AntCMS.");
}
if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
//First, we check if the passwords match in plain text. If it does, we hash the password and update the config file
if ($_SERVER['PHP_AUTH_PW'] == $currentConfig['admin']['password']) {
$currentConfig['admin']['password'] = password_hash($currentConfig['admin']['password'], PASSWORD_DEFAULT);
AntConfig::saveConfig($currentConfig);
}
//Now, we can perform the check as normal
if ($currentConfig['admin']['username'] == $_SERVER['PHP_AUTH_USER'] && password_verify($_SERVER['PHP_AUTH_PW'], $currentConfig['admin']['password'])) {
return true;
}
}
AntAuth::requireAuth();
}
private static function requireAuth()
{
$currentConfig = AntConfig::currentConfig();
header('WWW-Authenticate: Basic realm="' . $currentConfig['SiteInfo']['siteTitle'] . '"');
header('HTTP/1.0 401 Unauthorized');
echo 'You must enter a valid username and password to access this page';
exit;
}
}

View file

@ -17,8 +17,8 @@ class AntConfig
'generateKeywords' => true,
'enableCache' => true,
'admin' => array(
'username' => 'Admin',
'password' => '',
'username' => '',
),
'debug' => true,
'baseURL' => $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']),

13
src/AntCMS/AntPlugin.php Normal file
View file

@ -0,0 +1,13 @@
<?php
namespace AntCMS;
abstract class AntPlugin
{
public function handlePluginRoute(array $route)
{
die("Plugin did not define a handlePluginRoute function");
}
abstract function getName();
}

View file

@ -0,0 +1,26 @@
<?php
namespace AntCMS;
use AntCMS\AntTools;
class AntPluginLoader
{
public function loadPlugins()
{
$plugins = array();
$files = array();
$files = AntTools::getFileList(antPluginPath, null, true);
foreach ($files as $file) {
if (substr($file, -10) === "Plugin.php") {
include_once $file;
$className = pathinfo($file, PATHINFO_FILENAME);
$plugins[] = new $className();
}
}
return $plugins;
}
}

View file

@ -2,6 +2,7 @@
namespace AntCMS;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;
class AntYaml
@ -16,4 +17,12 @@ class AntYaml
$yaml = Yaml::dump($data);
file_put_contents($file, $yaml);
}
public static function parseYaml($yaml){
try {
return Yaml::parse($yaml);
} catch (ParseException $exception) {
return null;
}
}
}

View file

@ -0,0 +1,161 @@
<?php
use AntCMS\AntCMS;
use AntCMS\AntPlugin;
use AntCMS\AntConfig;
use AntCMS\AntPages;
use AntCMS\AntYaml;
use AntCMS\AntAuth;
class AdminPlugin extends AntPlugin
{
public function handlePluginRoute(array $route)
{
AntAuth::checkAuth();
$currentStep = $route[0] ?? 'none';
$antCMS = new AntCMS;
$pageTemplate = $antCMS->getPageLayout();
$currentConfig = AntConfig::currentConfig();
array_shift($route);
switch ($currentStep) {
case 'config':
$this->configureAntCMS($route);
break;
case 'pages':
$this->managePages($route);
break;
default:
$HTMLTemplate = "<h1>AntCMS Admin Plugin</h1>\n";
$HTMLTemplate .= "<a href='//" . $currentConfig['baseURL'] . "plugin/admin/config/'>AntCMS Configuration</a><br>\n";
$HTMLTemplate .= "<a href='//" . $currentConfig['baseURL'] . "plugin/admin/pages/'>Page management</a><br>\n";
$pageTemplate = str_replace('<!--AntCMS-Title-->', 'AntCMS Configuration', $pageTemplate);
$pageTemplate = str_replace('<!--AntCMS-Body-->', $HTMLTemplate, $pageTemplate);
echo $pageTemplate;
break;
}
}
public function getName()
{
return 'Admin';
}
private function configureAntCMS(array $route)
{
$antCMS = new AntCMS;
$pageTemplate = $antCMS->getPageLayout();
$HTMLTemplate = $antCMS->getThemeTemplate('textarea_edit_layout');
$currentConfig = AntConfig::currentConfig();
$currentConfigFile = file_get_contents(antConfigFile);
switch ($route[0] ?? 'none') {
case 'edit':
$HTMLTemplate = str_replace('<!--AntCMS-ActionURL-->', '//' . $currentConfig['baseURL'] . 'plugin/admin/config/save', $HTMLTemplate);
$HTMLTemplate = str_replace('<!--AntCMS-TextAreaContent-->', htmlspecialchars($currentConfigFile), $HTMLTemplate);
break;
case 'save':
if (!$_POST['textarea']) {
header('Location: //' . $currentConfig['baseURL'] . "plugin/admin/config/");
}
$yaml = AntYaml::parseYaml($_POST['textarea']);
if (is_array($yaml)) {
AntYaml::saveFile(antConfigFile, $yaml);
}
header('Location: //' . $currentConfig['baseURL'] . "plugin/admin/config/");
exit;
default:
$HTMLTemplate = "<h1>AntCMS Configuration</h1>\n";
$HTMLTemplate .= "<a href='//" . $currentConfig['baseURL'] . "plugin/admin/config/edit'>Click here to edit the config file</a><br>\n";
$HTMLTemplate .= "<ul>\n";
foreach ($currentConfig as $key => $value) {
if (is_array($value)) {
$HTMLTemplate .= "<li>$key:</li>\n";
$HTMLTemplate .= "<ul>\n";
foreach ($value as $key => $value) {
$value = is_bool($value) ? $this->boolToWord($value) : $value;
$HTMLTemplate .= "<li>$key: $value</li>\n";
}
$HTMLTemplate .= "</ul>\n";
} else {
$value = is_bool($value) ? $this->boolToWord($value) : $value;
$HTMLTemplate .= "<li>$key: $value</li>\n";
}
}
$HTMLTemplate .= "</ul>\n";
}
$pageTemplate = str_replace('<!--AntCMS-Title-->', 'AntCMS Configuration', $pageTemplate);
$pageTemplate = str_replace('<!--AntCMS-Body-->', $HTMLTemplate, $pageTemplate);
echo $pageTemplate;
exit;
}
private function managePages(array $route)
{
$antCMS = new AntCMS;
$pageTemplate = $antCMS->getPageLayout();
$HTMLTemplate = $antCMS->getThemeTemplate('textarea_edit_layout');
$pages = AntPages::getPages();
$currentConfig = AntConfig::currentConfig();
switch ($route[0] ?? 'none') {
case 'regenerate':
AntPages::generatePages();
header('Location: //' . $currentConfig['baseURL'] . "plugin/admin/pages/");
exit;
case 'edit':
array_shift($route);
$pagePath = implode('/', $route);
$page = file_get_contents(antContentPath . '/' . $pagePath);
$HTMLTemplate = str_replace('<!--AntCMS-ActionURL-->', '//' . $currentConfig['baseURL'] . "plugin/admin/pages/save/$pagePath", $HTMLTemplate);
$HTMLTemplate = str_replace('<!--AntCMS-TextAreaContent-->', htmlspecialchars($page), $HTMLTemplate);
break;
case 'save':
array_shift($route);
$pagePath = antContentPath . '/' . implode('/', $route);
if (!$_POST['textarea']) {
header('Location: //' . $currentConfig['baseURL'] . "plugin/admin/pages/");
}
file_put_contents($pagePath, $_POST['textarea']);
header('Location: //' . $currentConfig['baseURL'] . "plugin/admin/pages/");
exit;
default:
$HTMLTemplate = "<h1>Page Management</h1>\n";
$HTMLTemplate .= "<a href='//" . $currentConfig['baseURL'] . "plugin/admin/pages/regenerate'>Click here to regenerate the page list</a><br>\n";
$HTMLTemplate .= "<ul>\n";
foreach ($pages as $page) {
$HTMLTemplate .= "<li>\n";
$HTMLTemplate .= "<h2>" . $page['pageTitle'] . "</h2>\n";
$HTMLTemplate .= "<a href='//" . $currentConfig['baseURL'] . "plugin/admin/pages/edit" . $page['functionalPagePath'] . "'>Edit this page</a><br>\n";
$HTMLTemplate .= "<ul>\n";
$HTMLTemplate .= "<li>Full page path: " . $page['fullPagePath'] . "</li>\n";
$HTMLTemplate .= "<li>Functional page path: " . $page['functionalPagePath'] . "</li>\n";
$HTMLTemplate .= "<li>Show in navbar: " . $this->boolToWord($page['showInNav']) . "</li>\n";
$HTMLTemplate .= "</ul>\n";
$HTMLTemplate .= "</li>\n";
}
$HTMLTemplate .= "</ul>\n";
}
$pageTemplate = str_replace('<!--AntCMS-Title-->', 'AntCMS Page Management', $pageTemplate);
$pageTemplate = str_replace('<!--AntCMS-Body-->', $HTMLTemplate, $pageTemplate);
echo $pageTemplate;
exit;
}
private function boolToWord($value)
{
return boolval($value) ? 'true' : 'false';
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,5 @@
<form action="<!--AntCMS-ActionURL-->" method="post">
<textarea name="textarea" rows="25" cols="75" type="text"
class="form-textarea p-3 border-gray-200 bg-gray-100 dark:bg-zinc-900 dark:border-gray-700"><!--AntCMS-TextAreaContent--></textarea>
<input type="submit" value="Save">
</form>

View file

@ -9,12 +9,14 @@ const antConfigFile = __DIR__ . '/config.yaml';
const antPagesList = __DIR__ . '/pages.yaml';
const antContentPath = __DIR__ . '/Content';
const antThemePath = __DIR__ . '/Themes';
const antPluginPath = __DIR__ . '/Plugins';
require_once __DIR__ . '/Vendor/autoload.php';
require_once __DIR__ . '/Autoload.php';
use AntCMS\AntCMS;
use AntCMS\AntConfig;
use AntCMS\AntPages;
use AntCMS\AntPluginLoader;
$antCms = new AntCMS();
@ -48,18 +50,38 @@ if ($currentConfg['forceHTTPS'] && 'cli' !== PHP_SAPI) {
}
}
$requestedPage = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$requestedPage = strtolower(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
$segments = explode('/', $requestedPage);
if ($segments[0] === '') {
array_shift($segments);
}
if ($segments[0] === 'Themes' && $segments[2] === 'Assets') {
if ($segments[0] === 'themes' && $segments[2] === 'assets') {
$antCms->serveContent(AntDir . $requestedPage);
exit;
}
if ($segments[0] === 'plugin') {
$pluginName = $segments[1];
$pluginLoader = new AntPluginLoader();
$plugins = $pluginLoader->loadPlugins();
//Drop the first two elements of the array so the remaining segments are specific to the plugin.
array_splice($segments, 0, 2);
foreach ($plugins as $plugin) {
if (strtolower($plugin->getName()) === strtolower($pluginName)) {
$plugin->handlePluginRoute($segments);
exit;
}
}
// plugin not found
header("HTTP/1.0 404 Not Found");
echo ("Error 404");
exit;
}
$indexes = ['/', '/index.php', '/index.html'];
if (in_array($segments[0], $indexes)) {
$antCms->renderPage('/');

View file

@ -30,5 +30,6 @@ module.exports = {
},
plugins: [
require('@tailwindcss/typography'),
require('@tailwindcss/forms'),
],
}