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:
parent
0c79ec0ba6
commit
457cce525b
12 changed files with 320 additions and 4 deletions
37
package-lock.json
generated
37
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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
41
src/AntCMS/AntAuth.php
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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
13
src/AntCMS/AntPlugin.php
Normal 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();
|
||||
}
|
26
src/AntCMS/AntPluginLoader.php
Normal file
26
src/AntCMS/AntPluginLoader.php
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
161
src/Plugins/Admin/AdminPlugin.php
Normal file
161
src/Plugins/Admin/AdminPlugin.php
Normal 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
5
src/Themes/Default/Templates/textarea_edit_layout.html
Normal file
5
src/Themes/Default/Templates/textarea_edit_layout.html
Normal 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>
|
|
@ -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('/');
|
||||
|
|
|
@ -30,5 +30,6 @@ module.exports = {
|
|||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/typography'),
|
||||
require('@tailwindcss/forms'),
|
||||
],
|
||||
}
|
Loading…
Reference in a new issue