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 + + + + + + + + + +
+ +
+

+ + + + +
500Down 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 '

permalink\">$blogentry->title

' . 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 "

Archive for '$tagSearch'

"; + $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 ''; + if ($postsSkip > 0) + echo ''; + if ($morePosts || $postsSkip > 0) + echo "
<< Older postsNewer posts >>

"; + } + 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 ''; + echo ''; + } + function endEntry() { + echo '

' . $title . '

' . $title . '

 


' . $description . '
 

'; + } +?> 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