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