First commit!

master
Nicolas Ong 2025-02-13 10:37:29 +01:00
commit 92a272c03d
40 changed files with 932 additions and 0 deletions

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2023 kholo
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# fibable
A little PHP file-based blog engine.
This is a lightweight and not yet finished blog engine, written in PHP, working without database, only with directory structure and files and a sort of Markdown format.

16
config.php Normal file
View File

@ -0,0 +1,16 @@
<?php
define("BLOG_TITLE", "blog title");
define("BLOG_SUB_TITLE", "blog sub title - optional, leave blank if none");
define("BLOG_LANGUAGE", "country code");
define("BLOG_CREATION_YEAR", 2023);
define("BLOG_LINK", "https://example.org/");
define("AUTHOR_NICKNAME", "blog author's nickname");
define("AUTHOR_REALNAME", "blog author's real name - optional, leave blank if none");
define("AUTHOR_EMAIL", "blog author's email address - optional, leave blank if none");
define("AUTHOR_BIOGRAPHY", "blog author's biography");
define("ARTICLE_COUNT_IN_NAV", 10);
$links = [
"link label" => "https://example.com"
];

5
css/blog.css Normal file
View File

@ -0,0 +1,5 @@
@charset "utf-8";
* { box-sizing: border-box; }
body { font-family: sans-serif; }

0
data/articles/.gitignore vendored Normal file
View File

8
data/cache/default.phtml vendored Normal file
View File

@ -0,0 +1,8 @@
<header>
<h2>L'article que vous cherchez n'existe pas</h2>
<nav></nav>
</header>
<p>
Il n'y a rien à voir ici ! Vous avez du vous tromper de chemin !
</p>
<footer>01/08/2023</footer>

0
data/images/.gitignore vendored Normal file
View File

1
data/serial/default.pobj Normal file
View File

@ -0,0 +1 @@
O:7:"Article":5:{s:5:"title";s:40:"L'article que vous cherchez n'existe pas";s:4:"date";s:10:"2023-08-01";s:3:"ref";s:2:"01";s:4:"tags";a:2:{i:0;s:4:"blog";i:1;s:5:"essai";}s:4:"view";s:26:"./data/cache/default.phtml";}

0
data/tags/.gitignore vendored Normal file
View File

10
index.php Normal file
View File

@ -0,0 +1,10 @@
<?php
spl_autoload_register(
function ($class_name) {
require_once "lib/$class_name.php";
}
);
$blog = new Blog();
$blog->run();

17
lib/Article.php Normal file
View File

@ -0,0 +1,17 @@
<?php
final class Article
{
public string $title;
public string $date;
public string $ref;
public array $tags;
public string $view;
public function render()
{
include $this->view;
}
}

14
lib/ArticleDate.php Normal file
View File

@ -0,0 +1,14 @@
<?php
final class ArticleDate
{
public string $str;
public array $articles = [];
public function __construct(string $str)
{
$this->str = $str;
}
}

119
lib/ArticleManager.php Normal file
View File

@ -0,0 +1,119 @@
<?php
final class ArticleManager
{
const BASE_PATH = "./data/articles";
public static function get(string $fullref): Article
{
$serial_path = "./data/serial/$fullref.pobj";
return is_file($serial_path) ?
unserialize(file_get_contents($serial_path)) :
unserialize(file_get_contents("./data/serial/default.pobj"));
}
public static function getMultiple(array $fullrefs): array
{
$articles = [];
foreach ($fullrefs as $fullref) {
$articles[] = self::get($fullref);
}
return $articles;
}
public static function getNewest(): Article
{
$dot_dirs = [".", ".."];
$years = array_diff(scandir(self::BASE_PATH, SCANDIR_SORT_DESCENDING), $dot_dirs);
$year = $years[0];
$path = self::BASE_PATH . "/$year";
$months = array_diff(scandir($path, SCANDIR_SORT_DESCENDING), $dot_dirs);
$month = $months[0];
$path = "$path/$month";
$days = array_diff(scandir($path, SCANDIR_SORT_DESCENDING), $dot_dirs);
$day = $days[0];
$path = "$path/$day";
foreach (array_diff(scandir($path, SCANDIR_SORT_DESCENDING), $dot_dirs) as $file) {
$file_path = "$path/$file";
if (pathinfo($file_path, PATHINFO_EXTENSION) !== "txt") {
continue;
}
$ref = substr($file, 0, -4);
break;
}
return self::get("$year$month$day$ref");
}
public static function getLatestArticles(int $count): array
{
$dot_dirs = [".", ".."];
$articles = [];
foreach (array_diff(scandir(self::BASE_PATH, SCANDIR_SORT_DESCENDING), $dot_dirs) as $year) {
$year_path = self::BASE_PATH . "/$year";
foreach (array_diff(scandir($year_path, SCANDIR_SORT_DESCENDING), $dot_dirs) as $month) {
$month_path = "$year_path/$month";
foreach (array_diff(scandir($month_path, SCANDIR_SORT_DESCENDING), $dot_dirs) as $day) {
$day_path = "$month_path/$day";
foreach (array_diff(scandir($day_path, SCANDIR_SORT_DESCENDING), $dot_dirs) as $file) {
$file_path = "$day_path/$file";
if (pathinfo($file_path, PATHINFO_EXTENSION) !== "txt") {
continue;
}
$articles[] = self::getArticle($file_path);
if (count($articles) === $count) {
return $articles;
}
}
}
}
}
return $articles;
}
public static function getAll(): array
{
$dot_dirs = [".", ".."];
$dates = [];
foreach (array_diff(scandir(self::BASE_PATH, SCANDIR_SORT_DESCENDING), $dot_dirs) as $year) {
$year_path = self::BASE_PATH . "/$year";
foreach (array_diff(scandir($year_path, SCANDIR_SORT_DESCENDING), $dot_dirs) as $month) {
$month_path = "$year_path/$month";
foreach (array_diff(scandir($month_path, SCANDIR_SORT_DESCENDING), $dot_dirs) as $day) {
$day_path = "$month_path/$day";
$date = "$year$month$day";
foreach (array_diff(scandir($day_path, SCANDIR_SORT_DESCENDING), $dot_dirs) as $file) {
$file_path = "$day_path/$file";
if (pathinfo($file_path, PATHINFO_EXTENSION) !== "txt") {
continue;
}
if (!isset($dates["$date"])) {
$dates["$date"] = new ArticleDate("$day/$month/$year");
}
$dates["$date"]->articles[] = self::getArticle($file_path);
}
}
}
}
return $dates;
}
private static function getArticle(string $file_path): Article
{
$pattern = "|(./data)/articles/([0-9]+)/([0-9]+)/([0-9]+)/([0-9]+)\.txt|";
$replacement = "$1/serial/$2$3$4$5.pobj";
$serial_path = preg_replace($pattern, $replacement, $file_path);
return (file_exists($serial_path) and filemtime($serial_path) > filemtime($file_path)) ?
unserialize(file_get_contents($serial_path)) :
self::createArticle($file_path, $serial_path);
}
private static function createArticle(string $file_path, string $serial_path): Article
{
$parser = new ArticleParser();
$article = $parser->parse($file_path);
file_put_contents($serial_path, serialize($article));
file_put_contents("./data/cache/latest", "", LOCK_EX);
TagManager::add($article->tags, substr(basename($serial_path), 0, -5));
return $article;
}
}

122
lib/ArticleParser.php Normal file
View File

@ -0,0 +1,122 @@
<?php
final class ArticleParser
{
const PATTERN_DATE = "|\./data/articles/([0-9]+)/([0-9]+)/([0-9]+)/([0-9]+).txt|";
const PATTERN_TAGS = "/^\{(([a-zA-Z0-9]+,*)+)\}$/";
const PATTERN_IMG = "/^([a-zA-Z0-9\-_\.]+\.(jpg|png)) \((.*)\)$/";
const PATTERN_BOLD = "/\*([^*]*)\*/";
const PATTERN_ITALIC = "|/([^/]*)/|";
const PATTERN_UNDERLINE = "/_([^_]*)_/";
const REPLACE_DATE = "$1-$2-$3";
const REPLACE_REF = "$4";
const REPLACE_BOLD = "<strong>$1</strong>";
const REPLACE_ITALIC = "<em>$1</em>";
const REPLACE_UNDERLINE = "<u>$1</u>";
private bool $in_p = false;
private array $content = [];
private array $p = [];
public function parse(string $file_path): Article
{
$article = new Article();
$article->date = preg_replace(self::PATTERN_DATE, self::REPLACE_DATE, $file_path);
$article->ref = preg_replace(self::PATTERN_DATE, self::REPLACE_REF, $file_path);
$lines = file($file_path, FILE_IGNORE_NEW_LINES);
foreach ($lines as $line) {
if (substr($line, 0, 2) === "# ") {
$article->title = substr($line, 2);
continue;
}
if (substr($line, 0, 3) === "## ") {
$this->closeParagraph();
$this->content[] = "<h3>" . substr($line, 3) . "</h3>";
continue;
}
if (substr($line, 0, 4) === "### ") {
$this->closeParagraph();
$this->content[] = "<h4>" . substr($line, 4) . "</h4>";
continue;
}
if (substr($line, 0, 5) === "#### ") {
$this->closeParagraph();
$this->content[] = "<h5>" . substr($line, 5) . "</h5>";
continue;
}
if (preg_match(self::PATTERN_TAGS, $line, $matches)) {
$article->tags = explode(",", $matches[1]);
continue;
}
if (preg_match(self::PATTERN_IMG, $line, $matches)) {
$this->closeParagraph();
$this->content[] = "<figure>";
$this->content[] = " <img src=\"" . dirname($file_path) . "/$matches[1]\" alt=\"$matches[3]\">";
$this->content[] = " <figcaption>$matches[3]</figcaption>";
$this->content[] = "</figure>";
continue;
}
if ($line === "") {
$this->closeParagraph();
continue;
} else {
if (!$this->in_p) {
$this->content[] = "<p>";
$this->in_p = true;
}
$this->p[] = $this->parseLine($line);
}
}
$this->closeParagraph();
array_unshift($this->content,
"<header>",
" <h2>" . $article->title . "</h2>",
" <nav>" . $this->getHtmlTags($article->tags) . "</nav>",
"</header>"
);
$this->content[] = "<footer>" .
preg_replace("/([0-9]+)\-([0-9]+)-([0-9]+)/", "$3/$2/$1", $article->date) .
"</footer>";
$view_path = "./data/cache/{$article->date}_{$article->ref}.phtml";
file_put_contents($view_path, " " . implode("\n ", $this->content) . "\n");
$article->view = $view_path;
$this->content = [];
return $article;
}
private function closeParagraph()
{
if ($this->in_p) {
$this->content[] = " " . implode("<br>", $this->p);
$this->content[] = "</p>";
$this->in_p = false;
$this->p = [];
}
}
private function parseLine(string $line): string
{
$patterns = [
self::PATTERN_BOLD,
self::PATTERN_ITALIC,
self::PATTERN_UNDERLINE
];
$replacements = [
self::REPLACE_BOLD,
self::REPLACE_ITALIC,
self::REPLACE_UNDERLINE
];
return preg_replace($patterns, $replacements, htmlspecialchars($line, ENT_HTML5, "UTF-8"));
}
private function getHtmlTags(array $tags): string
{
$html = "";
foreach ($tags as $tag) {
$html .= "<a href=\"?view=tags&tag=$tag\" class=\"tag\">$tag</a>";
}
return $html;
}
}

117
lib/Blog.php Normal file
View File

@ -0,0 +1,117 @@
<?php
final class Blog
{
private array $links = [];
public function __construct()
{
$this->loadConfig();
}
public function run()
{
$this->render();
}
private function loadConfig()
{
require_once "config.php";
foreach ($links as $title => $url) {
$this->links[] = new Link($title, $url);
}
}
private function render()
{
extract($this->populateViews());
include "views/index.phtml";
}
private function populateViews(): array
{
return array_merge(
["lang" => BLOG_LANGUAGE],
$this->populateHeader(),
$this->populateMainView(),
$this->populateBio(),
$this->populateNavByTag(),
$this->populateNavByLatest(),
$this->populateFooter()
);
}
private function populateHeader(): array
{
return [
"title" => BLOG_TITLE,
"has_sub_title" => BLOG_SUB_TITLE !== "",
"sub_title" => BLOG_SUB_TITLE
];
}
private function populateMainView(): array
{
$variables = [];
$view = filter_input(INPUT_GET, "view", FILTER_SANITIZE_STRING) ?: "single";
$variables["view"] = $view;
switch ($view) {
case "single":
$date = filter_input(INPUT_GET, "date", FILTER_SANITIZE_STRING);
$ref = filter_input(INPUT_GET, "ref", FILTER_SANITIZE_STRING);
$variables["article"] = ($date !== null and $ref !== null) ?
ArticleManager::get(str_replace("-", "", $date) . $ref) :
ArticleManager::getNewest();
break;
case "tags":
$tag = filter_input(INPUT_GET, "tag", FILTER_SANITIZE_STRING) ?: "";
$variables["articles"] = ArticleManager::getMultiple(TagManager::getFromTag($tag));
break;
case "archives":
$variables["dates"] = ArticleManager::getAll();;
break;
}
return $variables;
}
private function populateBio(): array
{
return [
"author" => AUTHOR_NICKNAME,
"bio" => AUTHOR_BIOGRAPHY,
];
}
private function populateNavByTag(): array
{
return ["tags" => TagManager::getTags()];
}
private function populateNavByLatest(): array
{
return [
"latest" => ArticleManager::getLatestArticles(ARTICLE_COUNT_IN_NAV)
];
}
private function populateFooter(): array
{
return [
"has_links" => count($this->links) > 0,
"links" => $this->links,
"footer" => $this->getFooterString()
];
}
private function getFooterString(): string
{
$currentYear = getdate()["year"];
$footerYear = BLOG_CREATION_YEAR .
(BLOG_CREATION_YEAR !== $currentYear ? " - $currentYear" : "");
$footerAuthor = AUTHOR_NICKNAME .
(AUTHOR_REALNAME !== "" ? " (" . AUTHOR_REALNAME . ")" : "");
return "$footerYear. $footerAuthor";
}
}

12
lib/Entry.php Normal file
View File

@ -0,0 +1,12 @@
<?php
final class Entry
{
public string $title;
public string $content;
public string $date;
public string $url;
public string $id;
}

31
lib/EntryParser.php Normal file
View File

@ -0,0 +1,31 @@
<?php
final class EntryParser
{
const BASE_PATH = "./data/cache/uuids/";
public static function parse(Post $post, string $link): Entry
{
$entry = new Entry();
$entry->title = $post->title;
$entry->content = $post->content;
$entry->date = date("c", $post->date);
$entry->url = $link .
"/?view=single&amp;date=" . date("Y-m-d", $post->date) .
"&amp;ref=" . $post->id;
$entry->id = self::getEntryUUID($post);
return $entry;
}
private static function getEntryUUID(Post $post): string
{
$ref = date("Ymd", $post->date) . $post->id;
$file = self::BASE_PATH . $ref;
if (!is_file($file)) {
file_put_contents($file, UUIDGenerator::generate(), LOCK_EX);
}
return file_get_contents($file);
}
}

125
lib/Feed.php Normal file
View File

@ -0,0 +1,125 @@
<?php
final class Feed
{
const BASE_PATH = "./data/cache/";
const POST_COUNT = 10;
private string $type;
public function __construct(string $type = "rss")
{
$this->type = $type;
$this->loadConfig();
}
public function run()
{
$this->render();
}
private function loadConfig()
{
require_once "config.php";
}
private function render()
{
header("Content-Type: application/" . $this->type . "+xml");
$file = self::BASE_PATH . $this->type . ".xml";
$latest = self::BASE_PATH . "latest";
if (!is_file($file) or filemtime($file) < filemtime($latest)) {
extract($this->populateViews(FeedManager::getLatestPosts(self::POST_COUNT)));
ob_start();
include "views/" . $this->type . ".phtml";
$output = ob_get_clean();
file_put_contents($file, $output, LOCK_EX);
}
include $file;
}
private function populateViews(array $posts): array
{
return $this->type === "rss" ?
$this->populateRss($posts) :
$this->populateAtom($posts);
}
private function populateRss(array $posts): array
{
return array_merge(
$this->populateCommon(),
$this->populateRssRoot(),
$this->populateItems($posts)
);
}
private function populateAtom(array $posts): array
{
return array_merge(
$this->populateCommon(),
$this->populateAtomRoot(),
$this->populateEntries($posts)
);
}
private function populateCommon(): array
{
return [
"title" => BLOG_TITLE,
"has_sub_title" => BLOG_SUB_TITLE !== "",
"sub_title" => BLOG_SUB_TITLE,
"author_name" => AUTHOR_NICKNAME,
"has_author_email" => AUTHOR_EMAIL !== "",
"author_email" => AUTHOR_EMAIL
];
}
private function populateRssRoot(): array
{
return [
"last_build_date" => date("r"),
"link" => BLOG_LINK,
"self" => BLOG_LINK . "/rss.php"
];
}
private function populateItems(array $posts): array
{
$items = [];
foreach ($posts as $post) {
$items[] = ItemParser::parse($post, BLOG_LINK);
}
return ["items" => $items];
}
private function populateAtomRoot(): array
{
return [
"url" => BLOG_LINK,
"updated" => date("c"),
"id" => $this->getAtomUUID(),
"self" => BLOG_LINK . "/atom.php"
];
}
private function getAtomUUID(): string
{
$file = self::BASE_PATH . "uuid";
if (!is_file($file)) {
file_put_contents($file, UUIDGenerator::generate(), LOCK_EX);
}
return file_get_contents($file);
}
private function populateEntries(array $posts): array
{
$entries = [];
foreach ($posts as $post) {
$entries[] = EntryParser::parse($post, BLOG_LINK);
}
return ["entries" => $entries];
}
}

34
lib/FeedManager.php Normal file
View File

@ -0,0 +1,34 @@
<?php
final class FeedManager
{
const BASE_PATH = "./data/articles";
public static function getLatestPosts(int $count): array
{
$dot_dirs = [".", ".."];
$posts = [];
foreach (array_diff(scandir(self::BASE_PATH, SCANDIR_SORT_DESCENDING), $dot_dirs) as $year) {
$year_path = self::BASE_PATH . "/$year";
foreach (array_diff(scandir($year_path, SCANDIR_SORT_DESCENDING), $dot_dirs) as $month) {
$month_path = "$year_path/$month";
foreach (array_diff(scandir($month_path, SCANDIR_SORT_DESCENDING), $dot_dirs) as $day) {
$day_path = "$month_path/$day";
foreach (array_diff(scandir($day_path, SCANDIR_SORT_DESCENDING), $dot_dirs) as $file) {
$file_path = "$day_path/$file";
if (pathinfo($file_path, PATHINFO_EXTENSION) !== "txt") {
continue;
}
$posts[] = FeedParser::parse($file_path);
if (count($posts) === $count) {
return $posts;
}
}
}
}
}
return $posts;
}
}

43
lib/FeedParser.php Normal file
View File

@ -0,0 +1,43 @@
<?php
final class FeedParser
{
public static function parse(string $file_path): Post
{
$post = new Post();
$post->date = self::getDate($file_path);
$post->id = preg_replace(ArticleParser::PATTERN_DATE, ArticleParser::REPLACE_REF, $file_path);
$lines = file($file_path, FILE_IGNORE_NEW_LINES);
foreach ($lines as $line) {
if (substr($line, 0, 2) === "# ") {
$post->title = substr($line, 2);
continue;
}
if (preg_match(ArticleParser::PATTERN_TAGS, $line, $matches)) {
$post->tags = explode(",", $matches[1]);
break;
}
}
$post->content = self::getContent($post->date, $post->id);
return $post;
}
private static function getDate(string $file_path): int
{
$strdate = preg_replace(ArticleParser::PATTERN_DATE, ArticleParser::REPLACE_DATE, $file_path);
list($year, $month, $day) = explode("-", $strdate, 3);
$strtime = date("H:i:s", filemtime($file_path));
list($hour, $minute, $second) = explode(":", $strtime);
return mktime($hour, $minute, $second, $month, $day, $year);
}
private static function getContent(int $date, string $id): string
{
$file = "./data/cache/" . date("Y-m-d", $date) . "_${id}.phtml";
ob_start();
include $file;
return ob_get_clean();
}
}

12
lib/Item.php Normal file
View File

@ -0,0 +1,12 @@
<?php
final class Item
{
public string $title;
public string $description;
public string $pub_date;
public string $url;
public array $categories = [];
}

21
lib/ItemParser.php Normal file
View File

@ -0,0 +1,21 @@
<?php
final class ItemParser
{
public static function parse(Post $post, string $link): Item
{
$item = new Item();
$item->title = $post->title;
$item->description = $post->content;
$item->pub_date = date("r", $post->date);
$item->url = $link .
"/?view=single&amp;date=" . date("Y-m-d", $post->date) .
"&amp;ref=" . $post->id;
foreach ($post->tags as $tag) {
$item->categories[] = $tag;
}
return $item;
}
}

15
lib/Link.php Normal file
View File

@ -0,0 +1,15 @@
<?php
final class Link
{
public string $title;
public string $url;
public function __construct(string $title, string $url)
{
$this->title = $title;
$this->url = $url;
}
}

12
lib/Post.php Normal file
View File

@ -0,0 +1,12 @@
<?php
final class Post
{
public string $title;
public string $content;
public int $date;
public string $id;
public array $tags;
}

33
lib/TagManager.php Normal file
View File

@ -0,0 +1,33 @@
<?php
final class TagManager
{
const BASE_PATH = "./data/tags";
public static function getTags(): array
{
$tags = [];
foreach (array_diff(scandir(self::BASE_PATH), [".", ".."]) as $file) {
$tags[] = $file;
}
return $tags;
}
public static function getFromTag(string $tag): array
{
return file(self::BASE_PATH . "/$tag", FILE_IGNORE_NEW_LINES);
}
public static function add(array $tags, string $fullref)
{
foreach ($tags as $tag) {
$tagfile = self::BASE_PATH . "/$tag";
if (is_file($tagfile) and strpos(file_get_contents($tagfile), $fullref) !== false) {
continue;
}
file_put_contents($tagfile, "$fullref\n", FILE_APPEND);
}
}
}

18
lib/UUIDGenerator.php Normal file
View File

@ -0,0 +1,18 @@
<?php
final class UUIDGenerator
{
public static function generate(): string
{
return "urn:uuid:" . sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
}

12
views/archives.phtml Normal file
View File

@ -0,0 +1,12 @@
<ul class="archives">
<?php foreach ($dates as $date): ?>
<li class="date">
<h3><?=$date->str?></h3>
<ul class="articles">
<?php foreach ($date->articles as $article): ?>
<li class="article"><a href="?view=single&date=<?=$article->date?>"><?=$article->title?></a></li>
<?php endforeach ?>
</ul>
</li>
<?php endforeach ?>
</ul>

8
views/aside.phtml Normal file
View File

@ -0,0 +1,8 @@
<aside>
<?php
include "views/bio.phtml";
include "views/links.phtml";
include "views/nav-by-tag.phtml";
include "views/nav-by-latest.phtml";
?>
</aside>

26
views/atom.phtml Normal file
View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><?=$title?></title>
<?php if ($has_sub_title): ?>
<subtitle><?=$sub_title?></subtitle>
<?php endif; ?>
<link href="<?=$url?>" rel="alternate" type="text/html" />
<link href="<?=$self?>" rel="self" type="application/atom+xml" />
<updated><?=$updated?></updated>
<author>
<name><?=$author_name?></name>
<?php if ($has_author_email): ?>
<email><?=$author_email?></email>
<?php endif; ?>
</author>
<id><?=$id?></id>
<?php foreach ($entries as $entry): ?>
<entry>
<title><?=$entry->title?></title>
<link href="<?=$entry->url?>" />
<id><?=$entry->id?></id>
<updated><?=$entry->date?></updated>
<content type="html"><![CDATA[<?=$entry->content?>]]></content>
</entry>
<?php endforeach; ?>
</feed>

7
views/bio.phtml Normal file
View File

@ -0,0 +1,7 @@
<div class="bio">
<figure>
<img src="./data/images/avatar.jpg" alt="<?=$author?>" class="portrait">
<figcaption><?=$author?></figcaption>
</figure>
<p><?=$bio?></p>
</div>

3
views/footer.phtml Normal file
View File

@ -0,0 +1,3 @@
<footer>
<p><?=$footer?></p>
</footer>

6
views/header.phtml Normal file
View File

@ -0,0 +1,6 @@
<header>
<h1><?=$title?></h1>
<?php if ($has_sub_title): ?>
<h2><?=$sub_title?></h2>
<?php endif ?>
</header>

18
views/index.phtml Normal file
View File

@ -0,0 +1,18 @@
<!doctype html>
<html lang="<?=$lang?>">
<head>
<meta charset="utf-8">
<title><?=$title?></title>
<link href="css/blog.css" rel="stylesheet" title="Style par défaut" type="text/css">
<link href="rss.php" rel="alternate" title="<?=$title?> RSS feed" type="application/rss+xml">
<link href="atom.php" rel="alternate" title="<?=$title?> Atom feed" type="application/atom+xml">
</head>
<body>
<?php
include "views/header.phtml";
include "views/main.phtml";
include "views/aside.phtml";
include "views/footer.phtml";
?>
</body>
</html>

5
views/links.phtml Normal file
View File

@ -0,0 +1,5 @@
<ul class="links">
<?php foreach ($links as $link): ?>
<li class="icon"><a rel="me" href="<?=$link->url?>" class="icon <?=$link->title?>"><?=$link->title?></a></li>
<?php endforeach ?>
</ul>

3
views/main.phtml Normal file
View File

@ -0,0 +1,3 @@
<main>
<?php include "views/$view.phtml"; ?>
</main>

View File

@ -0,0 +1,6 @@
<nav class="latest">
<?php foreach ($latest as $prev): ?>
<a href="?view=single&date=<?=$prev->date?>&ref=<?=$prev->ref?>"><?=$prev->title?></a>
<?php endforeach ?>
<a href="?view=archives">Archives</a>
</nav>

5
views/nav-by-tag.phtml Normal file
View File

@ -0,0 +1,5 @@
<nav class="tags">
<?php foreach ($tags as $tag): ?>
<a href="?view=tags&tag=<?=$tag?>"><?=$tag?></a>
<?php endforeach ?>
</nav>

26
views/rss.phtml Normal file
View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<atom:link href="<?=$self?>" rel="self" type="application/rss+xml" />
<title><?=$title?></title>
<?php if ($has_sub_title): ?>
<description><?=$sub_title?></description>
<?php endif; ?>
<lastBuildDate><?=$last_build_date?></lastBuildDate>
<link><?=$link?></link>
<?php foreach ($items as $item): ?>
<item>
<title><?=$item->title?></title>
<description><![CDATA[<?=$item->description?>]]></description>
<pubDate><?=$item->pub_date?></pubDate>
<link><?=$item->url?></link>
<?php if ($has_author_email): ?>
<author><?="$author_email ($author_name)"?></author>
<?php endif; ?>
<?php foreach ($item->categories as $category): ?>
<category><?=$category?></category>
<?php endforeach; ?>
</item>
<?php endforeach; ?>
</channel>
</rss>

3
views/single.phtml Normal file
View File

@ -0,0 +1,3 @@
<article>
<?php $article->render(); ?>
</article>

5
views/tags.phtml Normal file
View File

@ -0,0 +1,5 @@
<ul class="articles">
<?php foreach ($articles as $article): ?>
<li><a href="?view=single&date=<?=$article->date?>&ref=<?=$article->ref?>"><?=$article->title?></a></li>
<?php endforeach ?>
</ul>