commit ccb900b1580c786dd32f4880e86f357e8b460ebd
Author: Markus Koch
Date: Fri Jan 27 20:29:27 2017 +0100
Initial commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..72bbf97
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+downloads
+b/*/*.*
+!b/2017/00-template.php
+!b/img/2017/fft.png
+!b/pimg/keep
+s/*
+p/*
+!p/00-template.php
diff --git a/README.MD b/README.MD
new file mode 100644
index 0000000..3ab67cd
--- /dev/null
+++ b/README.MD
@@ -0,0 +1,18 @@
+[Simpub CMS] - A Simple CMS for Nerds
+=====================================
+
+Directories and Files
+---------------------
+* index.php: Main page, assembles layout and loads sub pages
+* shared.php: Shared functions and global settings
+* blog.php: Blog core (include from page in /p/ to use)
+* page.php: Page core (include from page in /p/ to use)
+* control.php: Admin interface
+* style.css: Main style file
+* userstyle.css: User-defined styles used on content pages
+
+* /p/: User content (use 404.php as error handler)
+* /b/: User blog posts. Create subdirectory with year, then drop .php blog posts into there
+* /b/img: Again, create subfolder with year, drop images in there
+* /b/pimg: Must be writable by www-data, will contain auto-generated thumbnails
+* /s/: Resources for pages
diff --git a/_index.html b/_index.html
new file mode 100644
index 0000000..5c5a9f4
--- /dev/null
+++ b/_index.html
@@ -0,0 +1,44 @@
+
+
+ Error 500 - Down for maintenance
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 500 Down for maintenance!
+
+
+
+
+
+ The site is currently down for maintenance.
+ Please try again later.
+
+
+
+
+
+
+
diff --git a/b/2017/00-template.php b/b/2017/00-template.php
new file mode 100644
index 0000000..c1c296e
--- /dev/null
+++ b/b/2017/00-template.php
@@ -0,0 +1,55 @@
+ __FILE__,
+ 'title' => 'Title',
+ 'description' => 'Description',
+ 'author' => 'Markus',
+ 'tags' => 'video,tags',
+ 'status' => 'unlisted', // released, private, unlisted
+ 'ctime' => strtotime("2017-01-24 20:20")
+ );
+ if (!isset($CMS_ACTIVE) or !renderPost()) return;
+?>
+
+
+" (take url)
+ // Link will normally link to self
+ includeGraphics("Random FFT image", "2017/fft.png", $preview="", $style="width:640px;", $link="http://notsyncing.net/");
+?>
+
+
+
+Hidden section
+
+ This will only show when opening the post, but not on the overview page.
+
+
+
+Protected section
+
+
+ This will only show after entering the correct password.
+
+
+
+Some provided styles
+
+
+ $ some formatted code
+ $ foobar
+
+
+
+ Inline $code
is also possible.
+
diff --git a/b/img/2017/fft.png b/b/img/2017/fft.png
new file mode 100644
index 0000000..949881a
Binary files /dev/null and b/b/img/2017/fft.png differ
diff --git a/b/pimg/keep b/b/pimg/keep
new file mode 100644
index 0000000..e69de29
diff --git a/blog.php b/blog.php
new file mode 100644
index 0000000..8166c4f
--- /dev/null
+++ b/blog.php
@@ -0,0 +1,263 @@
+file = substr($blogentry->file, strlen(POSTS_ROOT), -4);
+ if (!(($posts = apcu_fetch('posts')) && isset($posts[$blogentry->file]) && $blogentry=$posts[$blogentry->file])) {
+ //echo "[DBG] Regenerate meta info block...";
+ if (!isset($blogentry->file))
+ $blogentry->file = "";
+ if (!isset($blogentry->title))
+ $blogentry->title = "Untitled";
+ if (!isset($blogentry->description))
+ $blogentry->description = $blogentry->title;
+ if (!isset($blogentry->author))
+ $blogentry->author = "Anonymous";
+ if (!isset($blogentry->status))
+ $blogentry->status = "private";
+ if (!isset($blogentry->ctime))
+ $blogentry->ctime = time();
+ if (!isset($blogentry->tags))
+ $blogentry->tags = "untagged";
+
+ $blogentry->tags = explode(',', $blogentry->tags);
+ sort($blogentry->tags);
+ $blogentry->permalink = "?p=blog&b=" . str_replace("/", POSTS_DIR_SEP, $blogentry->file);
+ $blogentry->isPublic = ($blogentry->status == "released");
+ }
+ $renderIt = $CMS_INDEXING || ($blogentry->status == "released") || ($blogentry->status == "unlisted");
+
+ if ($renderIt) {
+ if (isset($postUrl) && !$CMS_INDEXING) { // Set meta information
+ $pageTitle = $blogentry->title;
+ $htmlHead .= '
+ tags) . '">
+ ';
+ }
+ echo " ";
+ //echo '' . date("l, F jS Y, H:i", $blogentry->ctime) . "
";
+ echo '' . $blogentry->author . ' | ' . date("l, F jS Y, H:i", $blogentry->ctime) . " ";
+ }
+
+ return $renderIt;
+ }
+
+ function renderFooter() {
+ global $counters;
+ global $meta;
+ $i = 1;
+ if ($counters['sources'] > 1) {
+ echo 'Sources: ';
+ foreach ($meta['sources'] as $v) {
+ echo ' [' . $i++ . ']: ' . $v . ' ';
+ }
+ echo '
';
+ }
+
+ global $blogentry;
+ echo 'Tags: ';
+ foreach ($blogentry->tags as $v) {
+ echo '' . $v . ' ';
+ }
+ echo "
";
+
+
+ }
+
+ function extended() {
+ global $CMS_SINGLE_POST;
+ global $blogentry;
+ $renderExt = isset($CMS_SINGLE_POST) && $CMS_SINGLE_POST;
+ if ($renderExt) {
+ echo ' ';
+ }
+ else {
+ echo '→ Click here to continue reading ←
';
+ }
+ return $renderExt;
+ }
+
+ function password($pw) {
+ global $userPassword;
+ if ("$userPassword" == "$pw") {
+ echo ' ';
+ return true;
+ }
+ else {
+ echo "";
+ if ($userPassword == "")
+ echo "This section is protected. To view it, enter the correct password.";
+ else
+ echo "The password you entered is incorrect! Please try again.";
+?>
+
+
+ $object){
+ ob_start(); // These ob_lines will prevent output of actual HTML (the function already does this?!)
+ require "$file";
+ $blogentry->includeFile = $file;
+ //echo " ";
+ ob_end_clean(); // Enable output, drop cached
+ //array_push($posts, $blogentry);
+ $posts[$blogentry->file] = $blogentry;
+ }
+
+ # Create list of columns relevant for sorting
+ $sort = array();
+ foreach($posts as $k=>$v) {
+ $sort['ctime'][$k] = $v->ctime;;
+ // $sort['author'][$k] = $v->author;
+ }
+
+ // Now sort the entries
+ // Multiple keys: array_multisort($sort['author'], SORT_ASC, $sort['title'], SORT_ASC,$posts);
+ array_multisort($sort['ctime'], SORT_DESC, $posts);
+ }
+ // Store to cache
+ apcu_store('posts', $posts, CACHE_POSTS_TTL);
+ $CMS_INDEXING = false;
+ }
+
+
+ echo "";
+ if (isset($postUrl)) { // Display post
+ $CMS_SINGLE_POST = true;
+ echo '';
+ // Header is rendered in renderPost()
+ require $postUrl; // This will render the body
+ //if ($blogentry->isPublic) { // Render the blog footer
+ //echo "
Written by $blogentry->author
";
+ renderFooter();
+ //}
+ echo '
';
+ }
+ else {
+ //echo "Blog posts ";
+ if (isset($tagSearch)) {
+ echo "";
+ $pageTitle = "Archive for '$tagSearch'";
+ }
+ $i = 0;
+ $morePosts = false;
+ foreach($posts as $k=>$v) {
+ if ($v->isPublic) {
+ if (!isset($tagSearch) || in_array($tagSearch, $v->tags)) {
+ if ($i++ >= $postsSkip) {
+ //echo "permalink\">$v->title by $v->author ";
+ echo '';
+ require $v->includeFile;
+ renderFooter();
+ echo "
";
+ echo ' ';
+ if (($i % POSTS_PAGE_SIZE) == 0) {
+ $morePosts = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ if ($morePosts || $postsSkip > 0)
+ echo "
";
+ if ($morePosts)
+ echo '<< Older posts ';
+ if ($postsSkip > 0)
+ echo 'Newer posts >> ';
+ if ($morePosts || $postsSkip > 0)
+ echo "
";
+ }
+ echo " ";
+?>
diff --git a/control.php b/control.php
new file mode 100644
index 0000000..f0df4ac
--- /dev/null
+++ b/control.php
@@ -0,0 +1,4 @@
+
+Purged cache.
diff --git a/img/cc-byncsa.png b/img/cc-byncsa.png
new file mode 100644
index 0000000..ed028fe
Binary files /dev/null and b/img/cc-byncsa.png differ
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..0ddc347
--- /dev/null
+++ b/index.php
@@ -0,0 +1,96 @@
+ ' . $item['label'] . ' ';
+ // The in the line above can be removed when using a fixed width style in the .css file.
+ }
+ }
+
+ if ($page==HOME_PAGE && !strstr($_SERVER['REQUEST_URI'], "&")) // TODO: The second clause is pretty cheap...
+ $homesel = 'class="selectedMenu"';
+ else
+ $homesel = "";
+
+ ob_start(function($output) {
+ global $pageTitle;
+ global $htmlHead;
+ $output = '
+
+
+ ' . $pageTitle . ($pageTitle == "" ? "" : " - ") . PAGE_NAME . '
+
+
+ ' . $htmlHead . '
+
+
+ ' . $output;
+ return $output;
+ });
+?>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/p/00-template.php b/p/00-template.php
new file mode 100644
index 0000000..9374fe9
--- /dev/null
+++ b/p/00-template.php
@@ -0,0 +1,20 @@
+
+
+Subpage template
+
+ This is a subpage. The following code demonstrates the features of page.php.
+
+
+
diff --git a/page.php b/page.php
new file mode 100644
index 0000000..25810f0
--- /dev/null
+++ b/page.php
@@ -0,0 +1,13 @@
+$name ";
+ }
+ function addEntry($title, $preview, $link, $description) {
+ //echo '' . $title . ' ';
+ echo '' . $title . ' ' . $description . ' ';
+ }
+ function endEntry() {
+ echo '
';
+ }
+?>
diff --git a/shared.php b/shared.php
new file mode 100644
index 0000000..1e7fa56
--- /dev/null
+++ b/shared.php
@@ -0,0 +1,138 @@
+ "Page A", "link" => "?p=00-template", "highlight" => "related-subpages,00-template"),
+ array("label" => "Page B", "link" => "?p=404", "highlight" => "404"),
+ array("label" => "Repository", "link" => "https://git.notsyncing.net:8080", "highlight" => ""),
+ array("label" => "Contact", "link" => "?p=00-template", "highlight" => "")
+ );
+
+ $blogDirs = array("2017");
+
+
+ /*
+ * This function will include an image as figure.
+ * returns : Number of the figure for later referencing.
+ * $title : Image title, will be prefixed with Figure n.
+ * $path : If $path starts with http or / it will be
+ * treated as an absolute path and no downscaled preview will be rendered.
+ * Other paths will be prefixed with /b/img.
+ * $preview : When set to "", no preview will be generated. Otherwise the specified
+ * path will be used.
+ * $link : Link to follow when clicking the image. Default is the full resolution image.
+ */
+ $counters['figure'] = 1;
+ function includeGraphics($title, $path, $preview="+", $style="", $link="+") {
+ global $blogentry;
+ global $counters;
+
+ $title = "Figure " . $counters['figure'] . ". $title";
+
+ // TODO: External image
+ if (substr($path, 0, 4) == "http" || $path[0] == "/") {
+ $preview="";
+ $spath=$path;
+ }
+ else {
+ $spath = POSTS_BASE . "img/" . $path;
+ $fpath = $_SERVER['DOCUMENT_ROOT'] . $spath;
+ }
+
+ if ($preview == "+") {
+ $previewPath = POSTS_BASE . "pimg/" . str_replace("/", "_", $path);
+ $fpreviewPath = $_SERVER['DOCUMENT_ROOT'] . $previewPath;
+ if (!file_exists($fpreviewPath)) {
+ preg_match("/^[A-Za-z_\.\-0-9\/]*$/", $fpath, $matches); // SANITIZED!
+ if (!$matches) {
+ echo "SERVER ERROR: Image not valid!
";
+ return ;
+ }
+ $page=substr($page,0,12); // SECURITY: Max 12 chars
+ exec("gm convert '$fpath' -resize 640 '$fpreviewPath';");
+ }
+ }
+ elseif ($preview == "") {
+ $previewPath = $spath;
+ }
+ else {
+ $previewPath = $preview;
+ }
+
+ if ($link == "+")
+ $link = $spath;
+
+ echo '
+
+
+
+ ' . $title . '
+ ';
+
+ return $counters['figure']++;
+ }
+
+
+ /*
+ * This function will include a video.
+ * returns : Number of the video for later referencing.
+ * $url : URL of the video. Autodetects YouTube. Otherwise HTML5 video.
+ * $title : Display "Video n. $title" below the video.
+ * $width : Width of the player
+ */
+ $counters['video'] = 1;
+ function includeVideo($url, $title="", $width=640) {
+ global $counters;
+ // TODO: HTML5 video
+ echo '';
+ echo '';
+ echo '
';
+ if ($title != "") {
+ echo 'Video ' . $counters['video'] . ". $title ";
+ }
+ return $counters['video']++;
+ }
+
+
+ /*
+ * This function will add a reference
+ * returns : Number of the source for later referencing
+ * $url : URL of the source, if its a number it will not create a new entry.
+ */
+ $meta['sources'] = array();
+ $counters['sources'] = 1;
+ function addSource($url) {
+ global $counters;
+ global $meta;
+ if (is_numeric($url)) {
+ echo "[" . $url . "] ";
+ return $url;
+ }
+ else {
+ // Check whether source already exists
+ $i = 1;
+ foreach ($meta['sources'] as $v) {
+ if ($v == $url) {
+ echo "[" . $i . "] ";
+ return $i;
+ }
+ $i++;
+ }
+ // If not found, then add
+ $meta['sources'][$counters['sources']] = $url;
+ echo "[" . $counters['sources'] . "] ";
+ return $counters['sources']++;
+ }
+ }
+?>
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..2306cca
--- /dev/null
+++ b/style.css
@@ -0,0 +1,277 @@
+/* ------------- General ------------- */
+body {
+ padding:0px;
+ margin:0px;
+ font-family: sans-serif;
+ min-width:724px;
+ background-color: black;
+ color: white;
+}
+
+/* ------------- Nav bar ------------- */
+nav {
+ width:100%;
+ height:50px;
+ background-color:#151515;
+ box-shadow: 0px 0px 4px white;
+ border-bottom: 1px solid #222222;
+}
+nav ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ text-align: center;
+}
+nav li {
+ display: inline-block;
+ margin: 0 10px;
+ margin-top: 4px;
+ transition: all 0.3s;
+}
+nav a {
+ padding-top:12px;
+ padding-bottom:9px;
+ text-decoration: none;
+ display: block;
+ color: #285064;
+}
+nav ul a {
+ /*FIXEDWITH width: 80px;*/
+ padding-top:12px;
+ padding-bottom:14px;
+ text-decoration: none;
+ display: block;
+ color: #d6d6d6; /*#c21234*/
+}
+nav a:hover {
+ color: #ffffff;
+ border-bottom: 2px solid #ff1234;
+ margin-bottom: -2px;
+}
+.selectedMenu {
+ border-bottom: 2px solid #d91234;
+ margin-bottom: -2px;
+}
+.pagetitle {
+ float: left;
+ width:auto;
+ font-family: monospace;
+ letter-spacing: -2px;
+ font-size: 20pt;
+ margin-top: -4px;
+ padding-bottom: 9px;
+}
+#navContainer {
+ max-width: 1280px;
+ padding:0px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+
+/* ------------- Content ------------- */
+#contentContainer {
+ margin: 10px;
+}
+
+#content {
+ background-color: black;
+ max-width: 1280px;
+ padding:12px;
+ margin-left: auto;
+ margin-right: auto;
+ border-radius: 2px;
+ border: 1px solid #444444;
+ text-align:justify;
+}
+
+#footer {
+ background-color: #333333;
+ max-width: 1280px;
+ padding:10px;
+ padding-left:12px;
+ padding-right:12px;
+ border-radius: 2px;
+ margin-left: auto;
+ margin-right: auto;
+ border: 1px solid #eeeeee;
+ text-align:center;
+ font-size: 10pt;
+ color: #eeeeee;
+ vertical-align: middle;
+}
+
+#footer a {
+ color: #222222;
+ text-decoration: none;
+}
+
+/* ------------- Elements ------------- */
+content {
+ font-size:11pt;
+}
+content img {
+
+}
+content figure {
+ font-style: italic;
+ margin-left:16px;
+}
+content figcaption {
+ margin-left: 2px;
+}
+content .videoframe {
+ margin-left:16px;
+ box-shadow: 0px 0px 3px white;
+ border: 1 px solid #f3f3f3;
+}
+content vidcaption {
+ margin-left: 18px;
+ font-style: italic;
+}
+content a {
+ color:#aaaaaa;
+ text-decoration: none;
+}
+content pre {
+ background-color:#dddddd;
+ padding: 3px;
+ border-radius: 3px;
+}
+
+/* ------------- Blog ------------- */
+blog .head {
+ font-size:10pt;
+ text-align:right;
+ vertical-align:top;
+}
+blog .author {
+ font-style: italic;
+}
+blog .date {
+
+}
+blog .tags {
+ font-size: 10pt;
+ font-style: italic;
+}
+blog .tags a {
+ background-color:#222222;
+ color: white;
+ padding: 3px;
+ border-radius: 3px;
+}
+blog .sources {
+ font-size: 9pt;
+ text-align: left;
+}
+blog h2 {
+ margin-top:40px;
+}
+blog img {
+ width: 640px;
+ box-shadow: 0px 0px 3px white;
+ border: 1 px solid #f3f3f3;
+}
+blog .bloginfoheader { /* Stuff like Search results for 'xx' */
+ background-color:gray;
+ color:#f5f5f5;
+ border-radius: 4px;
+ padding:2px;
+}
+blogheading h1 {
+ /*margin:0px;*/
+}
+blogheading table {
+ padding:0px;
+ margin:0px;
+ margin-bottom:-18px; /* Cheap fix to correct some spacing */
+ margin-left:-2px;
+ margin-top:-2px;
+ border: 0px;
+ width:100%;
+}
+blogheading td {
+ margin:0px;
+ padding:0px;
+}
+blogheading tr {
+ margin:0px;
+ padding:0px;
+}
+blogheading a {
+ color: inherit;
+ text-decoration: inherit;
+}
+.postSeparator {
+ border:0px;
+ margin-top: 20px;
+ /*border-bottom: 1px solid black;
+ box-shadow: 1px 1px 2px black;*/
+}
+.blognav {
+ margin-top:30px;
+ margin-left:10px;
+ margin-right:10px;
+ width: calc(100% - 20px);
+}
+.blognav a {
+ background-color: #cccccc;
+ border-radius: 10px;
+ padding: 10px;
+ border: 1px solid black;
+ transition-duration: .3s;
+}
+.blognav a:hover {
+ background-color: #eeeeee;
+}
+.post {
+ background-color:#121212; /* #f2f2f2 */
+ border-radius: 2px;
+ border: 1px solid #cccccc;
+ margin: -2px;
+ padding: 4px;
+}
+.sourceSeparator {
+ border:none;
+ border-bottom: 1px solid #bbbbbb;
+}
+
+/* ------------- Entries ------------- */
+.pageDetailsHeading {
+ font-size:1.5em;
+ font-weight: bold;
+ margin: 0.3em;
+ margin-left: 0px;
+ cursor: pointer;
+}
+.listing {
+ width:100%;
+}
+.listing td {
+ padding-left: 10px;
+ padding-right: 10px;
+}
+.listing .previewImage {
+ padding-left: 20px;
+}
+
+/* ------------- Generic elements ------------- */
+.code {
+ font-family: monospace;
+ background: #080808;
+ border: white dashed 1px;
+ padding: 3px;
+ word-wrap: break-word;
+ text-align: left;
+ /*white-space: pre-wrap;*/
+ /*line-height: 1.8em;*/
+}
+code {
+ font-family: monospace;
+ background: #080808;
+ border: white dashed 1px;
+ padding: 2px;
+ line-height: 1.8em;
+}
+
diff --git a/userstyle.css b/userstyle.css
new file mode 100644
index 0000000..e69de29