Spis treści

PHP: Prosty blog z wykorzystaniem markdown

Wpis na blogu opisujący trochę czemu kiedyś napisałem tego bloga: Prosty blog w markdownie

Konstrukcja rozwiązania

Poniżej struktura plików:

🗀 /blog
├── 🗀 posts
│   ├── post1.md
│   └── post2.md
├── index.php
└── blog.php

Schemat działania programu

Kod

Zawartość index.php:

<?php
declare(strict_types=1);
 
$postsDir = __DIR__ . '/posts/';
$posts = [];
 
// scan markdown files
foreach (glob($postsDir . '*.md') as $file) {
    $filename = basename($file, '.md');
    $content = file_get_contents($file);
 
    // extract first markdown H1
    if (preg_match('/^#\s+(.+)$/m', $content, $matches)) {
        $title = trim($matches[1]);
    } else {
        $title = $filename;
    }
 
    $posts[] = [
        'slug' => $filename,
        'title' => $title,
        'time' => filemtime($file),
    ];
}
<html>
// sort newest first
usort($posts, fn($a, $b) => $b['time'] <=> $a['time']);
?>
  <?php if (empty($posts)): ?>
    <p>Brak wpisów.</p>
  <?php else: ?>
    <ul class="post-list">
      <?php foreach ($posts as $post): ?>
        <li class="post-item">
          <a href="/blog/blog.php?b=<?= htmlspecialchars($post['slug']) ?>">
            <?= htmlspecialchars($post['title']) ?>
          </a>
          <time datetime="<?= date('c', $post['time']) ?>">
            <?= date('Y-m-d', $post['time']) ?>
          </time>
        </li>
      <?php endforeach; ?>
    </ul>
  <?php endif; ?>
 
</main>
 
</html>

Zawartość blog.php:

<?php
declare(strict_types=1);
 
// ========================
// CONFIG
// ========================
$blogDir = __DIR__ . '/posts/';
$defaultPost = 'index';
 
// ========================
// SECURITY: sanitize input
// ========================
$post = $_GET['b'] ?? $defaultPost;
 
// allow only safe filenames
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $post)) {
    http_response_code(400);
    die('Invalid post name.');
}
 
$mdFile = $blogDir . $post . '.md';
 
if (!file_exists($mdFile)) {
    http_response_code(404);
    $mdContent = "# 404\n\nPost not found.";
} else {
    $mdContent = file_get_contents($mdFile);
}
 
// ========================
// MARKDOWN PARSER
// ========================
require_once __DIR__ . '/../vendor/Parsedown.php';
require_once __DIR__ . '/../vendor/ParsedownExtra.php';
 
 
 
$parser = new ParsedownExtra();
$parser->setSafeMode(false);       // allow HTML in markdown
$parser->setMarkupEscaped(false);  // don't escape HTML
 
$htmlContent = $parser->text($mdContent);
?>
<html>
<body>
 
  <?= $htmlContent ?>
</main>
 
</html>