v1.0.0.9
Publisium
- <?php
-
- declare(strict_types=1);
-
- require_once __DIR__ . '/database.php';
-
- final class ArticleVersions
- {
- private const KEEP = 50;
- private const FIELDS = 'title, excerpt, content_blocks, category_id, cover_image_path, seo_title, seo_description, access_type, price_cents';
-
- public static function snapshot(int $articleId, ?int $userId, string $userName, string $note = 'save'): void
- {
- try {
- $db = Database::site();
-
- $statement = $db->prepare('SELECT ' . self::FIELDS . ' FROM articles WHERE id = :id LIMIT 1');
- $statement->execute(['id' => $articleId]);
- $article = $statement->fetch();
- if (!$article) {
- return;
- }
-
- $hash = self::hash($article);
-
- $lastStatement = $db->prepare('SELECT content_hash FROM article_versions WHERE article_id = :id ORDER BY id DESC LIMIT 1');
- $lastStatement->execute(['id' => $articleId]);
- if ($lastStatement->fetchColumn() === $hash) {
- return;
- }
-
- $db->prepare(
- 'INSERT INTO article_versions
- (article_id, content_hash, title, excerpt, content_blocks, category_id, cover_image_path, seo_title, seo_description, access_type, price_cents, saved_by, saved_by_name, note)
- VALUES (:article, :hash, :title, :excerpt, :content, :category, :cover, :seo_title, :seo_description, :access, :price, :user, :user_name, :note)'
- )->execute([
- 'article' => $articleId,
- 'hash' => $hash,
- 'title' => $article['title'],
- 'excerpt' => $article['excerpt'],
- 'content' => $article['content_blocks'],
- 'category' => $article['category_id'],
- 'cover' => $article['cover_image_path'],
- 'seo_title' => $article['seo_title'],
- 'seo_description' => $article['seo_description'],
- 'access' => $article['access_type'],
- 'price' => $article['price_cents'],
- 'user' => $userId,
- 'user_name' => mb_substr($userName, 0, 120),
- 'note' => mb_substr($note, 0, 60),
- ]);
-
- self::prune($articleId);
- } catch (PDOException $e) {
- error_log('Publisium: could not save article version: ' . $e->getMessage());
- }
- }
-
- public static function recent(int $articleId, int $limit = 15): array
- {
- try {
- $statement = Database::site()->prepare(
- 'SELECT id, title, saved_by_name, note, created_at FROM article_versions WHERE article_id = :id ORDER BY id DESC LIMIT ' . max(1, min(50, $limit))
- );
- $statement->execute(['id' => $articleId]);
-
- return $statement->fetchAll();
- } catch (PDOException $e) {
- return [];
- }
- }
-
- public static function restore(int $articleId, int $versionId, ?int $userId, string $userName): bool
- {
- $db = Database::site();
-
- $statement = $db->prepare('SELECT * FROM article_versions WHERE id = :version AND article_id = :article LIMIT 1');
- $statement->execute(['version' => $versionId, 'article' => $articleId]);
- $version = $statement->fetch();
- if (!$version) {
- return false;
- }
-
- self::snapshot($articleId, $userId, $userName, 'before restore');
-
- $db->prepare(
- 'UPDATE articles SET title = :title, excerpt = :excerpt, content_blocks = :content, category_id = :category,
- cover_image_path = :cover, seo_title = :seo_title, seo_description = :seo_description,
- access_type = :access, price_cents = :price, updated_at = NOW()
- WHERE id = :id'
- )->execute([
- 'title' => $version['title'],
- 'excerpt' => $version['excerpt'],
- 'content' => $version['content_blocks'],
- 'category' => $version['category_id'],
- 'cover' => $version['cover_image_path'],
- 'seo_title' => $version['seo_title'],
- 'seo_description' => $version['seo_description'],
- 'access' => $version['access_type'],
- 'price' => $version['price_cents'],
- 'id' => $articleId,
- ]);
-
- self::snapshot($articleId, $userId, $userName, 'restored');
-
- return true;
- }
-
- private static function prune(int $articleId): void
- {
- $db = Database::site();
-
- $statement = $db->prepare('SELECT id FROM article_versions WHERE article_id = :id ORDER BY id DESC LIMIT 1 OFFSET ' . self::KEEP);
- $statement->execute(['id' => $articleId]);
- $threshold = $statement->fetchColumn();
-
- if ($threshold !== false) {
- $db->prepare('DELETE FROM article_versions WHERE article_id = :article AND id <= :threshold')
- ->execute(['article' => $articleId, 'threshold' => (int) $threshold]);
- }
- }
-
- private static function hash(array $article): string
- {
- $parts = [];
- foreach (explode(', ', self::FIELDS) as $field) {
- $parts[] = (string) ($article[$field] ?? '');
- }
-
- return sha1(implode("\x1f", $parts));
- }
- }
-