diff --git a/decisionlog.md b/decisionlog.md
new file mode 100644
index 0000000..ca9d8c6
--- /dev/null
+++ b/decisionlog.md
@@ -0,0 +1,16 @@
+# ADR
+
+An Architectural Decision (AD) is a software design choice that addresses a functional or non-functional requirement that is architecturally significant. An Architecturally Significant Requirement (ASR) is a requirement that has a measurable effect on a software system’s architecture and quality. An Architectural Decision Record (ADR) captures a single AD, such as often done when writing personal notes or meeting minutes; the collection of ADRs created and maintained in a project constitute its decision log. All these are within the topic of Architectural Knowledge Management (AKM).
+
+## Overall goals
+
+A) Strive to simplify.
+B) Everyone contributes.
+
+
+# Decision Record
+
+Decision IDs are auto incremented. IDs not in the document no longer apply.
+
+- 003 Cuttlefish only generates static websites.
+- 004 A content project consists of content assets and a php configuration file. All other code is in this project.
diff --git a/src/blog/ControllerAdmin.php b/src/blog/ControllerAdmin.php
new file mode 100644
index 0000000..ad84024
--- /dev/null
+++ b/src/blog/ControllerAdmin.php
@@ -0,0 +1,109 @@
+ 'Overview',
+ 'clearCache' => 'Clear cache',
+ 'generateSite' => 'Generate static site',
+ 'logout' => 'Logout',
+ );
+
+ // admin section does not use content files
+ protected string $contents;
+
+ protected function isAllowedMethod($action): bool
+ {
+ return array_key_exists($action, $this->allowed_methods);
+ }
+
+ protected function showTasks(): string
+ {
+ $output = '
';
+ $am = $this->allowed_methods;
+ array_shift($am);
+ foreach ($am as $key => $value) :
+ $Url = new Cuttlefish\Url("/admin/$key");
+ $output .= sprintf('- %s
', $Url->url_absolute, $value);
+ endforeach;
+
+ $output .= '
';
+
+ return $output;
+ }
+
+ protected function showLogin(): string
+ {
+ return App::getInstance()->Security->login();
+ }
+
+ protected function clearCache()
+ {
+ $App = App::getInstance();
+ $App->Security->maybeLoginRedirect();
+
+ return $App->Cache->clear();
+ }
+
+ protected function generateSite(): void
+ {
+ $App = App::getInstance();
+ $App->Security->maybeLoginRedirect();
+ echo $App->Cache->generateSite();
+ }
+
+
+ /**
+ * @return void
+ */
+ public function init()
+ {
+ App::getInstance()->Cache->abort();
+
+ $action = ( isset($this->args[0]) ) ? $this->args[0] : 'index';
+ if ($this->isAllowedMethod($action)) {
+ $this->contents = $this->$action();
+ } else {
+ exit("Method $action is not allowed");
+ }
+
+ parent::init();
+ }
+
+ /**
+ * @return void
+ */
+ public function view()
+ {
+ parent::view();
+
+ $this->View = new Html($this->contents, array(
+ 'layout' => 'layout.php',
+ 'controller' => 'admin',
+ 'model' => 'page',
+ ));
+ }
+
+ public function index(): string
+ {
+ if (App::getInstance()->Security->isLoggedIn()) {
+ return $this->showTasks();
+ }
+
+ return $this->showLogin();
+ }
+
+ public function logout()
+ {
+ $App = App::getInstance();
+ $App->Security->maybeLoginRedirect();
+
+ return $App->Security->logout();
+ }
+}
diff --git a/src/blog/ControllerArchive.php b/src/blog/ControllerArchive.php
new file mode 100644
index 0000000..7878f94
--- /dev/null
+++ b/src/blog/ControllerArchive.php
@@ -0,0 +1,42 @@
+ $content_dir ), $this->ext);
+ $this->records = $Files->files();
+ }
+
+ /**
+ * @return void
+ */
+ public function model()
+ {
+ $this->Model = new ModelPost($this->records);
+ }
+
+ /**
+ * @return void
+ */
+ public function view()
+ {
+ parent::view();
+ $this->View = new Html($this->Model->contents, array(
+ 'layout' => 'layout.php',
+ 'controller' => 'archive',
+ 'model' => 'post',
+ ));
+ }
+}
diff --git a/src/blog/ControllerError.php b/src/blog/ControllerError.php
new file mode 100644
index 0000000..e1b006a
--- /dev/null
+++ b/src/blog/ControllerError.php
@@ -0,0 +1,45 @@
+args) . '.' . $this->ext;
+ $this->records = [ Filesystem::convertUrlToPath($url) ];
+ }
+
+ /**
+ * @return void
+ */
+ public function model()
+ {
+ $this->Model = new ModelPage($this->records);
+ }
+
+ /**
+ * @return void
+ */
+ public function view()
+ {
+ parent::view();
+
+ $this->View = new Html($this->Model->contents, array(
+ 'layout' => 'layout.php',
+ 'controller' => 'errors',
+ 'model' => 'page',
+ ));
+ }
+}
diff --git a/src/blog/ControllerFeed.php b/src/blog/ControllerFeed.php
new file mode 100644
index 0000000..5b1c4c4
--- /dev/null
+++ b/src/blog/ControllerFeed.php
@@ -0,0 +1,41 @@
+ $content_dir ), $this->ext);
+ $this->records = $Records->limit($limit + 5);
+ }
+
+ /**
+ * @return void
+ */
+ public function model()
+ {
+ $Model = new ModelPost($this->records);
+ $this->Model = $Model->limit(10);
+ }
+
+ /**
+ * @return void
+ */
+ public function view()
+ {
+ parent::view();
+ $this->View = new Feed($this->Model->contents);
+ }
+}
diff --git a/src/blog/ControllerHome.php b/src/blog/ControllerHome.php
new file mode 100644
index 0000000..d339008
--- /dev/null
+++ b/src/blog/ControllerHome.php
@@ -0,0 +1,45 @@
+ $content_dir ), $this->ext);
+ $this->records = $Files->limit($limit + 5);
+ }
+
+ /**
+ * @return void
+ */
+ public function model()
+ {
+ $Model = new ModelPost($this->records);
+ $this->Model = $Model->limit(Configuration::POSTS_HOMEPAGE);
+ }
+
+ /**
+ * @return void
+ */
+ public function view()
+ {
+ parent::view();
+ $this->View = new Html($this->Model->contents, array(
+ 'layout' => 'layout.php',
+ 'controller' => 'home',
+ 'model' => 'post',
+ ));
+ }
+}
diff --git a/src/blog/ControllerImage.php b/src/blog/ControllerImage.php
new file mode 100644
index 0000000..6566434
--- /dev/null
+++ b/src/blog/ControllerImage.php
@@ -0,0 +1,39 @@
+records = [ Filesystem::convertUrlToPath($content_dir . implode('/', $this->args)) ];
+ }
+
+ /**
+ * @return void
+ */
+ public function model()
+ {
+ $this->Model = new ModelFile($this->records);
+ }
+
+ /**
+ * @return void
+ */
+ public function view()
+ {
+ parent::view();
+ $this->View = new File($this->Model->contents[0]);
+ $this->View->render();
+ }
+}
diff --git a/src/blog/ControllerPage.php b/src/blog/ControllerPage.php
new file mode 100644
index 0000000..090b049
--- /dev/null
+++ b/src/blog/ControllerPage.php
@@ -0,0 +1,44 @@
+args) . '.' . $this->ext;
+ $this->records = [ Filesystem::convertUrlToPath($url) ];
+ }
+
+ /**
+ * @return void
+ */
+ public function model()
+ {
+ $this->Model = new ModelPage($this->records);
+ }
+
+ /**
+ * @return void
+ */
+ public function view()
+ {
+ parent::view();
+
+ $this->View = new Html($this->Model->contents, array(
+ 'layout' => 'layout.php',
+ 'controller' => 'pages',
+ 'model' => 'page',
+ ));
+ }
+}
diff --git a/src/blog/ControllerPost.php b/src/blog/ControllerPost.php
new file mode 100644
index 0000000..775a193
--- /dev/null
+++ b/src/blog/ControllerPost.php
@@ -0,0 +1,44 @@
+args) . '.' . $this->ext;
+ $this->records = [ Filesystem::convertUrlToPath($url) ];
+ }
+
+ /**
+ * @return void
+ */
+ public function model()
+ {
+ $this->Model = new ModelPost($this->records);
+ }
+
+ /**
+ * @return void
+ */
+ public function view()
+ {
+ parent::view();
+
+ $this->View = new Html($this->Model->contents, array(
+ 'layout' => 'layout.php',
+ 'controller' => 'posts',
+ 'model' => 'post',
+ ));
+ }
+}
diff --git a/src/blog/ModelFile.php b/src/blog/ModelFile.php
new file mode 100644
index 0000000..11ac036
--- /dev/null
+++ b/src/blog/ModelFile.php
@@ -0,0 +1,21 @@
+contents = $records;
+ }
+}
diff --git a/src/blog/ModelPage.php b/src/blog/ModelPage.php
new file mode 100644
index 0000000..20f8dcc
--- /dev/null
+++ b/src/blog/ModelPage.php
@@ -0,0 +1,24 @@
+ 'content',
+ );
+
+ /**
+ * @return void
+ */
+ public function contents($records)
+ {
+ foreach ($records as $record) {
+ $this->contents[] = $this->listContents($record);
+ }
+ }
+}
diff --git a/src/blog/ModelPost.php b/src/blog/ModelPost.php
new file mode 100644
index 0000000..f4d560c
--- /dev/null
+++ b/src/blog/ModelPost.php
@@ -0,0 +1,35 @@
+ 'metadata',
+ 'markdown|html' => 'content',
+ );
+
+ /**
+ * @return int
+ */
+ public function sortByPublished($a, $b)
+ {
+ return strcmp($b->metadata->Published, $a->metadata->Published);
+ }
+
+ /**
+ * @return self
+ */
+ public function contents($records)
+ {
+ foreach ($records as $record) {
+ $this->contents[] = $this->listContents($record);
+ }
+ usort($this->contents, array( $this, 'sortByPublished' ));
+
+ return $this;
+ }
+}
diff --git a/src/blog/functions.php b/src/blog/functions.php
new file mode 100644
index 0000000..68992b0
--- /dev/null
+++ b/src/blog/functions.php
@@ -0,0 +1,12 @@
+Cache = new Cache();
@@ -37,4 +39,20 @@ public function run()
new Router();
$this->Cache->end();
}
+
+
+ /**
+ * Singleton. The object is created from within the class itself
+ * only if the class has no instance.
+ *
+ * @return void
+ */
+ public static function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new App();
+ }
+
+ return self::$instance;
+ }
}
diff --git a/src/system/Cache.php b/src/system/Cache.php
index e4216d3..4d6de40 100644
--- a/src/system/Cache.php
+++ b/src/system/Cache.php
@@ -215,7 +215,6 @@ public function generateSite(): string
*/
public function clear()
{
- global $App;
$dir = $this->getCacheFolder();
$output = sprintf('Removing all files in %s
', $dir);
$Files = new Files($dir);
@@ -224,7 +223,7 @@ public function clear()
foreach ($dirs as $dir) {
Filesystem::removeDirs(realpath($dir . '/.'));
}
- $App->Environment->writeHtaccess();
+ App::getInstance()->Environment->writeAccess();
return (string) $output;
}
diff --git a/src/system/Defaults.php b/src/system/Defaults.php
index 4513917..24746cb 100644
--- a/src/system/Defaults.php
+++ b/src/system/Defaults.php
@@ -20,4 +20,6 @@ class Defaults
public const CONTENT_FOLDER = '../content';
public const LOGS_FOLDER = '../_logs';
public const THEMES_FOLDER = 'themes';
+
+ const routes = [];
}
diff --git a/src/system/Environment.php b/src/system/Environment.php
index f3e1996..eeaaea6 100644
--- a/src/system/Environment.php
+++ b/src/system/Environment.php
@@ -16,7 +16,7 @@ public function __construct()
}
// Externals environment
- $this->registerExternals();
+ $this->registerPlugins();
session_start();
}
@@ -57,9 +57,9 @@ public function writeHtaccess(): void
fclose($fp);
}
- protected function registerExternals(): void
+ protected function registerPlugins(): void
{
- $Files = new Files('/system/Ext', 'php');
+ $Files = new Files('/plugins', 'php');
foreach ($Files->files() as $key => $filepath) {
$this->register[ pathinfo($filepath, PATHINFO_FILENAME) ] = true;
$this->addIncludePath(pathinfo($filepath, PATHINFO_DIRNAME));
diff --git a/src/system/Router.php b/src/system/Router.php
index 29bb565..070d3ea 100644
--- a/src/system/Router.php
+++ b/src/system/Router.php
@@ -68,6 +68,9 @@ protected function classNotCallable($controller_class): void
{
$Url = new Url('/errors/404');
$log_message = "Not callable '$controller_class' or missing parameter.";
+ if (empty($controller_class)) {
+ $log_message = "Missing route";
+ }
$this->redirect($Url, $log_message);
}
@@ -81,16 +84,7 @@ protected function classNotCallable($controller_class): void
*/
protected function redirect($Url, string $log_message): void
{
- echo( "Location: " . $Url->url_absolute );
+ echo( "Location: " . $Url->url_absolute . PHP_EOL );
exit($log_message);
}
-
- protected function matchRoute($pattern, $routes)
- {
- $keys = array_flip($routes);
- $matches = preg_grep($pattern, $keys);
- if ($matches) {
- return array_shift($matches);
- }
- }
}
diff --git a/src/themes/basic/functions.php b/src/themes/basic/functions.php
index c0f5d92..da68e05 100644
--- a/src/themes/basic/functions.php
+++ b/src/themes/basic/functions.php
@@ -48,7 +48,5 @@ function pages()
*/
function isLoggedIn()
{
- global $App;
-
- return $App->Security->isLoggedIn();
+ return Cuttlefish\App::getInstance()->Security->isLoggedIn();
}