add wisiwig editor TinyMCE. default content type = markdown.
This commit is contained in:
+498
-203
@@ -1,204 +1,499 @@
|
||||
<?php
|
||||
// models/Book.php
|
||||
|
||||
class Book {
|
||||
private $pdo;
|
||||
|
||||
public function __construct($pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function findById($id) {
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM books WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function findByShareToken($share_token) {
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM books WHERE share_token = ?");
|
||||
$stmt->execute([$share_token]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function findByUser($user_id, $only_published = false) {
|
||||
$sql = "
|
||||
SELECT b.*,
|
||||
COUNT(c.id) as chapter_count,
|
||||
COALESCE(SUM(c.word_count), 0) as total_words
|
||||
FROM books b
|
||||
LEFT JOIN chapters c ON b.id = c.book_id
|
||||
WHERE b.user_id = ?
|
||||
";
|
||||
if ($only_published) {
|
||||
$sql .= " AND b.published = 1 ";
|
||||
}
|
||||
$sql .= " GROUP BY b.id ORDER BY b.created_at DESC ";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([$user_id]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function create($data) {
|
||||
$share_token = bin2hex(random_bytes(16));
|
||||
$published = isset($data['published']) ? (int)$data['published'] : 0;
|
||||
|
||||
$stmt = $this->pdo->prepare("
|
||||
INSERT INTO books (title, description, genre, user_id, series_id, sort_order_in_series, share_token, published)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
return $stmt->execute([
|
||||
$data['title'],
|
||||
$data['description'] ?? null,
|
||||
$data['genre'] ?? null,
|
||||
$data['user_id'],
|
||||
$data['series_id'] ?? null,
|
||||
$data['sort_order_in_series'] ?? null,
|
||||
$share_token,
|
||||
$published
|
||||
]);
|
||||
}
|
||||
|
||||
public function update($id, $data) {
|
||||
$published = isset($data['published']) ? (int)$data['published'] : 0;
|
||||
|
||||
$stmt = $this->pdo->prepare("
|
||||
UPDATE books
|
||||
SET title = ?, description = ?, genre = ?, series_id = ?, sort_order_in_series = ?, published = ?
|
||||
WHERE id = ? AND user_id = ?
|
||||
");
|
||||
return $stmt->execute([
|
||||
$data['title'],
|
||||
$data['description'] ?? null,
|
||||
$data['genre'] ?? null,
|
||||
$data['series_id'] ?? null,
|
||||
$data['sort_order_in_series'] ?? null,
|
||||
$published,
|
||||
$id,
|
||||
$data['user_id']
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete($id, $user_id) {
|
||||
try {
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
// Удаляем главы книги
|
||||
$stmt = $this->pdo->prepare("DELETE FROM chapters WHERE book_id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
// Удаляем саму книгу
|
||||
$stmt = $this->pdo->prepare("DELETE FROM books WHERE id = ? AND user_id = ?");
|
||||
$result = $stmt->execute([$id, $user_id]);
|
||||
|
||||
$this->pdo->commit();
|
||||
return $result;
|
||||
} catch (Exception $e) {
|
||||
$this->pdo->rollBack();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function userOwnsBook($book_id, $user_id) {
|
||||
$stmt = $this->pdo->prepare("SELECT id FROM books WHERE id = ? AND user_id = ?");
|
||||
$stmt->execute([$book_id, $user_id]);
|
||||
return $stmt->fetch() !== false;
|
||||
}
|
||||
|
||||
public function generateNewShareToken($book_id) {
|
||||
$new_token = bin2hex(random_bytes(16));
|
||||
$stmt = $this->pdo->prepare("UPDATE books SET share_token = ? WHERE id = ?");
|
||||
$success = $stmt->execute([$new_token, $book_id]);
|
||||
return $success ? $new_token : false;
|
||||
}
|
||||
|
||||
public function getPublishedChapters($book_id) {
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT * FROM chapters
|
||||
WHERE book_id = ? AND status = 'published'
|
||||
ORDER BY sort_order, created_at
|
||||
");
|
||||
$stmt->execute([$book_id]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function updateCover($book_id, $filename) {
|
||||
$stmt = $this->pdo->prepare("UPDATE books SET cover_image = ? WHERE id = ?");
|
||||
return $stmt->execute([$filename, $book_id]);
|
||||
}
|
||||
|
||||
public function deleteCover($book_id) {
|
||||
|
||||
$book = $this->findById($book_id);
|
||||
$old_filename = $book['cover_image'];
|
||||
|
||||
if ($old_filename) {
|
||||
$file_path = COVERS_PATH . $old_filename;
|
||||
if (file_exists($file_path)) {
|
||||
unlink($file_path);
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare("UPDATE books SET cover_image = NULL WHERE id = ?");
|
||||
return $stmt->execute([$book_id]);
|
||||
}
|
||||
|
||||
public function updateSeriesInfo($book_id, $series_id, $sort_order) {
|
||||
$stmt = $this->pdo->prepare("UPDATE books SET series_id = ?, sort_order_in_series = ? WHERE id = ?");
|
||||
return $stmt->execute([$series_id, $sort_order, $book_id]);
|
||||
}
|
||||
|
||||
public function removeFromSeries($book_id) {
|
||||
$stmt = $this->pdo->prepare("UPDATE books SET series_id = NULL, sort_order_in_series = NULL WHERE id = ?");
|
||||
return $stmt->execute([$book_id]);
|
||||
}
|
||||
|
||||
public function findBySeries($series_id) {
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT b.*
|
||||
FROM books b
|
||||
WHERE b.series_id = ?
|
||||
ORDER BY b.sort_order_in_series, b.created_at
|
||||
");
|
||||
$stmt->execute([$series_id]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function reorderSeriesBooks($series_id, $new_order) {
|
||||
try {
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
foreach ($new_order as $order => $book_id) {
|
||||
$stmt = $this->pdo->prepare("UPDATE books SET sort_order_in_series = ? WHERE id = ? AND series_id = ?");
|
||||
$stmt->execute([$order + 1, $book_id, $series_id]);
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
$this->pdo->rollBack();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getBookStats($book_id, $only_published_chapters = false) {
|
||||
$sql = "
|
||||
SELECT
|
||||
COUNT(c.id) as chapter_count,
|
||||
COALESCE(SUM(c.word_count), 0) as total_words
|
||||
FROM books b
|
||||
LEFT JOIN chapters c ON b.id = c.book_id
|
||||
WHERE b.id = ?
|
||||
";
|
||||
|
||||
if ($only_published_chapters) {
|
||||
$sql .= " AND c.status = 'published'";
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([$book_id]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
}
|
||||
<?php
|
||||
// models/Book.php
|
||||
require_once __DIR__ . '/../includes/parsedown/ParsedownExtra.php';
|
||||
class Book {
|
||||
private $pdo;
|
||||
|
||||
public function __construct($pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function findById($id) {
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM books WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function findByShareToken($share_token) {
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM books WHERE share_token = ?");
|
||||
$stmt->execute([$share_token]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function findByUser($user_id, $only_published = false) {
|
||||
$sql = "
|
||||
SELECT b.*,
|
||||
COUNT(c.id) as chapter_count,
|
||||
COALESCE(SUM(c.word_count), 0) as total_words
|
||||
FROM books b
|
||||
LEFT JOIN chapters c ON b.id = c.book_id
|
||||
WHERE b.user_id = ?
|
||||
";
|
||||
if ($only_published) {
|
||||
$sql .= " AND b.published = 1 ";
|
||||
}
|
||||
$sql .= " GROUP BY b.id ORDER BY b.created_at DESC ";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([$user_id]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function create($data) {
|
||||
$share_token = bin2hex(random_bytes(16));
|
||||
$published = isset($data['published']) ? (int)$data['published'] : 0;
|
||||
$editor_type = $data['editor_type'] ?? 'markdown';
|
||||
|
||||
$stmt = $this->pdo->prepare("
|
||||
INSERT INTO books (title, description, genre, user_id, series_id, sort_order_in_series, share_token, published, editor_type)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
return $stmt->execute([
|
||||
$data['title'],
|
||||
$data['description'] ?? null,
|
||||
$data['genre'] ?? null,
|
||||
$data['user_id'],
|
||||
$data['series_id'] ?? null,
|
||||
$data['sort_order_in_series'] ?? null,
|
||||
$share_token,
|
||||
$published,
|
||||
$editor_type
|
||||
]);
|
||||
}
|
||||
|
||||
public function update($id, $data) {
|
||||
$published = isset($data['published']) ? (int)$data['published'] : 0;
|
||||
$editor_type = $data['editor_type'] ?? 'markdown';
|
||||
|
||||
$stmt = $this->pdo->prepare("
|
||||
UPDATE books
|
||||
SET title = ?, description = ?, genre = ?, series_id = ?, sort_order_in_series = ?, published = ?, editor_type = ?
|
||||
WHERE id = ? AND user_id = ?
|
||||
");
|
||||
return $stmt->execute([
|
||||
$data['title'],
|
||||
$data['description'] ?? null,
|
||||
$data['genre'] ?? null,
|
||||
$data['series_id'] ?? null,
|
||||
$data['sort_order_in_series'] ?? null,
|
||||
$published,
|
||||
$editor_type,
|
||||
$id,
|
||||
$data['user_id']
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function delete($id, $user_id) {
|
||||
try {
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
// Удаляем главы книги
|
||||
$stmt = $this->pdo->prepare("DELETE FROM chapters WHERE book_id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
// Удаляем саму книгу
|
||||
$stmt = $this->pdo->prepare("DELETE FROM books WHERE id = ? AND user_id = ?");
|
||||
$result = $stmt->execute([$id, $user_id]);
|
||||
|
||||
$this->pdo->commit();
|
||||
return $result;
|
||||
} catch (Exception $e) {
|
||||
$this->pdo->rollBack();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function userOwnsBook($book_id, $user_id) {
|
||||
$stmt = $this->pdo->prepare("SELECT id FROM books WHERE id = ? AND user_id = ?");
|
||||
$stmt->execute([$book_id, $user_id]);
|
||||
return $stmt->fetch() !== false;
|
||||
}
|
||||
|
||||
public function generateNewShareToken($book_id) {
|
||||
$new_token = bin2hex(random_bytes(16));
|
||||
$stmt = $this->pdo->prepare("UPDATE books SET share_token = ? WHERE id = ?");
|
||||
$success = $stmt->execute([$new_token, $book_id]);
|
||||
return $success ? $new_token : false;
|
||||
}
|
||||
|
||||
public function getPublishedChapters($book_id) {
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT * FROM chapters
|
||||
WHERE book_id = ? AND status = 'published'
|
||||
ORDER BY sort_order, created_at
|
||||
");
|
||||
$stmt->execute([$book_id]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function updateCover($book_id, $filename) {
|
||||
$stmt = $this->pdo->prepare("UPDATE books SET cover_image = ? WHERE id = ?");
|
||||
return $stmt->execute([$filename, $book_id]);
|
||||
}
|
||||
|
||||
public function deleteCover($book_id) {
|
||||
|
||||
$book = $this->findById($book_id);
|
||||
$old_filename = $book['cover_image'];
|
||||
|
||||
if ($old_filename) {
|
||||
$file_path = COVERS_PATH . $old_filename;
|
||||
if (file_exists($file_path)) {
|
||||
unlink($file_path);
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare("UPDATE books SET cover_image = NULL WHERE id = ?");
|
||||
return $stmt->execute([$book_id]);
|
||||
}
|
||||
|
||||
public function updateSeriesInfo($book_id, $series_id, $sort_order) {
|
||||
$stmt = $this->pdo->prepare("UPDATE books SET series_id = ?, sort_order_in_series = ? WHERE id = ?");
|
||||
return $stmt->execute([$series_id, $sort_order, $book_id]);
|
||||
}
|
||||
|
||||
public function removeFromSeries($book_id) {
|
||||
$stmt = $this->pdo->prepare("UPDATE books SET series_id = NULL, sort_order_in_series = NULL WHERE id = ?");
|
||||
return $stmt->execute([$book_id]);
|
||||
}
|
||||
|
||||
public function findBySeries($series_id) {
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT b.*
|
||||
FROM books b
|
||||
WHERE b.series_id = ?
|
||||
ORDER BY b.sort_order_in_series, b.created_at
|
||||
");
|
||||
$stmt->execute([$series_id]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function reorderSeriesBooks($series_id, $new_order) {
|
||||
try {
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
foreach ($new_order as $order => $book_id) {
|
||||
$stmt = $this->pdo->prepare("UPDATE books SET sort_order_in_series = ? WHERE id = ? AND series_id = ?");
|
||||
$stmt->execute([$order + 1, $book_id, $series_id]);
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
$this->pdo->rollBack();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getBookStats($book_id, $only_published_chapters = false) {
|
||||
$sql = "
|
||||
SELECT
|
||||
COUNT(c.id) as chapter_count,
|
||||
COALESCE(SUM(c.word_count), 0) as total_words
|
||||
FROM books b
|
||||
LEFT JOIN chapters c ON b.id = c.book_id
|
||||
WHERE b.id = ?
|
||||
";
|
||||
|
||||
if ($only_published_chapters) {
|
||||
$sql .= " AND c.status = 'published'";
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([$book_id]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function convertChaptersContent($book_id, $from_editor, $to_editor) {
|
||||
try {
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
$chapters = $this->getAllChapters($book_id);
|
||||
|
||||
foreach ($chapters as $chapter) {
|
||||
$converted_content = $this->convertContent(
|
||||
$chapter['content'],
|
||||
$from_editor,
|
||||
$to_editor
|
||||
);
|
||||
|
||||
$this->updateChapterContent($chapter['id'], $converted_content);
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
$this->pdo->rollBack();
|
||||
error_log("Error converting chapters: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function getAllChapters($book_id) {
|
||||
$stmt = $this->pdo->prepare("SELECT id, content FROM chapters WHERE book_id = ?");
|
||||
$stmt->execute([$book_id]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
private function updateChapterContent($chapter_id, $content) {
|
||||
$word_count = $this->countWords($content);
|
||||
$stmt = $this->pdo->prepare("
|
||||
UPDATE chapters
|
||||
SET content = ?, word_count = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
");
|
||||
return $stmt->execute([$content, $word_count, $chapter_id]);
|
||||
}
|
||||
|
||||
private function countWords($text) {
|
||||
$text = strip_tags($text);
|
||||
$text = preg_replace('/[^\p{L}\p{N}\s]/u', ' ', $text);
|
||||
$words = preg_split('/\s+/', $text);
|
||||
$words = array_filter($words);
|
||||
return count($words);
|
||||
}
|
||||
|
||||
private function convertContent($content, $from_editor, $to_editor) {
|
||||
if ($from_editor === $to_editor) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($from_editor === 'markdown' && $to_editor === 'html') {
|
||||
// Markdown to HTML с улучшенной обработкой абзацев
|
||||
return $this->markdownToHtmlWithParagraphs($content);
|
||||
} elseif ($from_editor === 'html' && $to_editor === 'markdown') {
|
||||
// HTML to Markdown
|
||||
return $this->htmlToMarkdown($content);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Error converting content from {$from_editor} to {$to_editor}: " . $e->getMessage());
|
||||
return $content;
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function markdownToHtmlWithParagraphs($markdown) {
|
||||
$parsedown = new ParsedownExtra();
|
||||
|
||||
// Включаем разметку строк для лучшей обработки абзацев
|
||||
$parsedown->setBreaksEnabled(true);
|
||||
|
||||
// Обрабатываем Markdown
|
||||
$html = $parsedown->text($markdown);
|
||||
|
||||
// Дополнительная обработка для обеспечения правильной структуры абзацев
|
||||
$html = $this->ensureParagraphStructure($html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function ensureParagraphStructure($html) {
|
||||
// Если HTML не содержит тегов абзацев или div'ов, оборачиваем в <p>
|
||||
if (!preg_match('/<(p|div|h[1-6]|blockquote|pre|ul|ol|li)/i', $html)) {
|
||||
// Разбиваем на строки и оборачиваем каждую непустую строку в <p>
|
||||
$lines = explode("\n", trim($html));
|
||||
$wrappedLines = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if (!empty($line)) {
|
||||
// Пропускаем уже обернутые строки
|
||||
if (!preg_match('/^<[^>]+>/', $line) || preg_match('/^<(p|div|h[1-6])/i', $line)) {
|
||||
$wrappedLines[] = $line;
|
||||
} else {
|
||||
$wrappedLines[] = "<p>{$line}</p>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$html = implode("\n", $wrappedLines);
|
||||
}
|
||||
|
||||
// Убеждаемся, что теги правильно закрыты
|
||||
$html = $this->balanceTags($html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function balanceTags($html) {
|
||||
// Простая балансировка тегов - в реальном проекте лучше использовать DOMDocument
|
||||
$tags = [
|
||||
'p' => 0,
|
||||
'div' => 0,
|
||||
'span' => 0,
|
||||
'strong' => 0,
|
||||
'em' => 0,
|
||||
];
|
||||
|
||||
// Счетчик открывающих и закрывающих тегов
|
||||
foreach ($tags as $tag => &$count) {
|
||||
$open = substr_count($html, "<{$tag}>") + substr_count($html, "<{$tag} ");
|
||||
$close = substr_count($html, "</{$tag}>");
|
||||
$count = $open - $close;
|
||||
}
|
||||
|
||||
// Добавляем недостающие закрывающие теги
|
||||
foreach ($tags as $tag => $count) {
|
||||
if ($count > 0) {
|
||||
$html .= str_repeat("</{$tag}>", $count);
|
||||
}
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
private function htmlToMarkdown($html) {
|
||||
// Сначала нормализуем HTML структуру
|
||||
$html = $this->normalizeHtml($html);
|
||||
|
||||
// Базовая конвертация HTML в Markdown
|
||||
$markdown = $html;
|
||||
|
||||
// Обрабатываем абзацы - заменяем на двойные переносы строк
|
||||
$markdown = preg_replace('/<p[^>]*>(.*?)<\/p>/is', "$1\n\n", $markdown);
|
||||
|
||||
// Обрабатываем разрывы строк
|
||||
$markdown = preg_replace('/<br[^>]*>\s*<\/br[^>]*>/i', "\n", $markdown);
|
||||
$markdown = preg_replace('/<br[^>]*>/i', " \n", $markdown); // Два пробела для Markdown разрыва
|
||||
|
||||
// Заголовки
|
||||
$markdown = preg_replace('/<h1[^>]*>(.*?)<\/h1>/is', "# $1\n\n", $markdown);
|
||||
$markdown = preg_replace('/<h2[^>]*>(.*?)<\/h2>/is', "## $1\n\n", $markdown);
|
||||
$markdown = preg_replace('/<h3[^>]*>(.*?)<\/h3>/is', "### $1\n\n", $markdown);
|
||||
$markdown = preg_replace('/<h4[^>]*>(.*?)<\/h4>/is', "#### $1\n\n", $markdown);
|
||||
$markdown = preg_replace('/<h5[^>]*>(.*?)<\/h5>/is', "##### $1\n\n", $markdown);
|
||||
$markdown = preg_replace('/<h6[^>]*>(.*?)<\/h6>/is', "###### $1\n\n", $markdown);
|
||||
|
||||
// Жирный текст
|
||||
$markdown = preg_replace('/<strong[^>]*>(.*?)<\/strong>/is', '**$1**', $markdown);
|
||||
$markdown = preg_replace('/<b[^>]*>(.*?)<\/b>/is', '**$1**', $markdown);
|
||||
|
||||
// Курсив
|
||||
$markdown = preg_replace('/<em[^>]*>(.*?)<\/em>/is', '*$1*', $markdown);
|
||||
$markdown = preg_replace('/<i[^>]*>(.*?)<\/i>/is', '*$1*', $markdown);
|
||||
|
||||
// Подчеркивание (не стандартно в Markdown, но обрабатываем)
|
||||
$markdown = preg_replace('/<u[^>]*>(.*?)<\/u>/is', '<u>$1</u>', $markdown);
|
||||
|
||||
// Зачеркивание
|
||||
$markdown = preg_replace('/<s[^>]*>(.*?)<\/s>/is', '~~$1~~', $markdown);
|
||||
$markdown = preg_replace('/<strike[^>]*>(.*?)<\/strike>/is', '~~$1~~', $markdown);
|
||||
$markdown = preg_replace('/<del[^>]*>(.*?)<\/del>/is', '~~$1~~', $markdown);
|
||||
|
||||
// Списки
|
||||
$markdown = preg_replace('/<li[^>]*>(.*?)<\/li>/is', '- $1', $markdown);
|
||||
$markdown = preg_replace('/<ul[^>]*>(.*?)<\/ul>/is', "$1\n", $markdown);
|
||||
$markdown = preg_replace('/<ol[^>]*>(.*?)<\/ol>/is', "$1\n", $markdown);
|
||||
|
||||
// Блочные цитаты
|
||||
$markdown = preg_replace('/<blockquote[^>]*>(.*?)<\/blockquote>/is', "> $1\n", $markdown);
|
||||
|
||||
// Код
|
||||
$markdown = preg_replace('/<code[^>]*>(.*?)<\/code>/is', '`$1`', $markdown);
|
||||
$markdown = preg_replace('/<pre[^>]*>(.*?)<\/pre>/is', "```\n$1\n```", $markdown);
|
||||
|
||||
// Ссылки
|
||||
$markdown = preg_replace('/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/is', '[$2]($1)', $markdown);
|
||||
|
||||
// Изображения
|
||||
$markdown = preg_replace('/<img[^>]*src="([^"]*)"[^>]*alt="([^"]*)"[^>]*>/is', '', $markdown);
|
||||
|
||||
// Удаляем все остальные HTML-теги
|
||||
$markdown = strip_tags($markdown);
|
||||
|
||||
// Чистим лишние пробелы и переносы
|
||||
$markdown = preg_replace('/\n\s*\n\s*\n/', "\n\n", $markdown);
|
||||
$markdown = preg_replace('/^\s+|\s+$/m', '', $markdown); // Trim каждой строки
|
||||
$markdown = trim($markdown);
|
||||
|
||||
return $markdown;
|
||||
}
|
||||
|
||||
private function normalizeHtml($html) {
|
||||
// Нормализуем HTML структуру перед конвертацией
|
||||
$html = preg_replace('/<div[^>]*>(.*?)<\/div>/is', "<p>$1</p>", $html);
|
||||
|
||||
// Убираем лишние пробелы
|
||||
$html = preg_replace('/\s+/', ' ', $html);
|
||||
|
||||
// Восстанавливаем структуру абзацев
|
||||
$html = preg_replace('/([^>])\s*<\/(p|div)>\s*([^<])/', "$1</$2>\n\n$3", $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function normalizeBookContent($book_id) {
|
||||
try {
|
||||
$chapters = $this->getAllChapters($book_id);
|
||||
$book = $this->findById($book_id);
|
||||
|
||||
foreach ($chapters as $chapter) {
|
||||
$normalized_content = '';
|
||||
|
||||
if ($book['editor_type'] == 'html') {
|
||||
// Нормализуем HTML контент
|
||||
$normalized_content = $this->normalizeHtmlContent($chapter['content']);
|
||||
} else {
|
||||
// Нормализуем Markdown контент
|
||||
$normalized_content = $this->normalizeMarkdownContent($chapter['content']);
|
||||
}
|
||||
|
||||
if ($normalized_content !== $chapter['content']) {
|
||||
$this->updateChapterContent($chapter['id'], $normalized_content);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
error_log("Error normalizing book content: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function normalizeHtmlContent($html) {
|
||||
// Простая нормализация HTML - оборачиваем текст без тегов в <p>
|
||||
if (!preg_match('/<[^>]+>/', $html) && trim($html) !== '') {
|
||||
// Если нет HTML тегов, оборачиваем в <p>
|
||||
$lines = explode("\n", trim($html));
|
||||
$wrapped = array_map(function($line) {
|
||||
$line = trim($line);
|
||||
return $line ? "<p>{$line}</p>" : '';
|
||||
}, $lines);
|
||||
return implode("\n", array_filter($wrapped));
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function normalizeMarkdownContent($markdown) {
|
||||
// Нормализация Markdown - убеждаемся, что есть пустые строки между абзацами
|
||||
$lines = explode("\n", $markdown);
|
||||
$normalized = [];
|
||||
$inParagraph = false;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$trimmed = trim($line);
|
||||
|
||||
if (empty($trimmed)) {
|
||||
// Пустая строка - конец абзаца
|
||||
if ($inParagraph) {
|
||||
$normalized[] = '';
|
||||
$inParagraph = false;
|
||||
}
|
||||
} else {
|
||||
// Непустая строка
|
||||
if (!$inParagraph && !empty($normalized) && end($normalized) !== '') {
|
||||
// Добавляем пустую строку перед новым абзацем
|
||||
$normalized[] = '';
|
||||
}
|
||||
$normalized[] = $line;
|
||||
$inParagraph = true;
|
||||
}
|
||||
}
|
||||
|
||||
return implode("\n", $normalized);
|
||||
}
|
||||
}
|
||||
?>
|
||||
Executable → Regular
+157
-157
@@ -1,158 +1,158 @@
|
||||
<?php
|
||||
// models/Series.php
|
||||
|
||||
class Series {
|
||||
private $pdo;
|
||||
|
||||
public function __construct($pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function findById($id) {
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT s.*,
|
||||
COUNT(b.id) as book_count,
|
||||
COALESCE((
|
||||
SELECT SUM(c.word_count)
|
||||
FROM chapters c
|
||||
JOIN books b2 ON c.book_id = b2.id
|
||||
WHERE b2.series_id = s.id AND b2.published = 1
|
||||
), 0) as total_words
|
||||
FROM series s
|
||||
LEFT JOIN books b ON s.id = b.series_id AND b.published = 1
|
||||
WHERE s.id = ?
|
||||
GROUP BY s.id
|
||||
");
|
||||
$stmt->execute([$id]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function findByUser($user_id, $include_stats = true) {
|
||||
if ($include_stats) {
|
||||
$sql = "
|
||||
SELECT s.*,
|
||||
COUNT(b.id) as book_count,
|
||||
COALESCE((
|
||||
SELECT SUM(c.word_count)
|
||||
FROM chapters c
|
||||
JOIN books b2 ON c.book_id = b2.id
|
||||
WHERE b2.series_id = s.id AND b2.user_id = ?
|
||||
), 0) as total_words
|
||||
FROM series s
|
||||
LEFT JOIN books b ON s.id = b.series_id
|
||||
WHERE s.user_id = ?
|
||||
GROUP BY s.id
|
||||
ORDER BY s.created_at DESC
|
||||
";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([$user_id, $user_id]);
|
||||
} else {
|
||||
$sql = "SELECT * FROM series WHERE user_id = ? ORDER BY created_at DESC";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([$user_id]);
|
||||
}
|
||||
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function create($data) {
|
||||
$stmt = $this->pdo->prepare("
|
||||
INSERT INTO series (title, description, user_id)
|
||||
VALUES (?, ?, ?)
|
||||
");
|
||||
return $stmt->execute([
|
||||
$data['title'],
|
||||
$data['description'] ?? null,
|
||||
$data['user_id']
|
||||
]);
|
||||
}
|
||||
|
||||
public function update($id, $data) {
|
||||
$stmt = $this->pdo->prepare("
|
||||
UPDATE series
|
||||
SET title = ?, description = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ? AND user_id = ?
|
||||
");
|
||||
return $stmt->execute([
|
||||
$data['title'],
|
||||
$data['description'] ?? null,
|
||||
$id,
|
||||
$data['user_id']
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete($id, $user_id) {
|
||||
try {
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
$stmt = $this->pdo->prepare("UPDATE books SET series_id = NULL, sort_order_in_series = NULL WHERE series_id = ? AND user_id = ?");
|
||||
$stmt->execute([$id, $user_id]);
|
||||
|
||||
$stmt = $this->pdo->prepare("DELETE FROM series WHERE id = ? AND user_id = ?");
|
||||
$result = $stmt->execute([$id, $user_id]);
|
||||
|
||||
$this->pdo->commit();
|
||||
return $result;
|
||||
} catch (Exception $e) {
|
||||
$this->pdo->rollBack();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function userOwnsSeries($series_id, $user_id) {
|
||||
$stmt = $this->pdo->prepare("SELECT id FROM series WHERE id = ? AND user_id = ?");
|
||||
$stmt->execute([$series_id, $user_id]);
|
||||
return $stmt->fetch() !== false;
|
||||
}
|
||||
|
||||
public function getBooksInSeries($series_id, $only_published = false) {
|
||||
$sql = "SELECT * FROM books WHERE series_id = ?";
|
||||
if ($only_published) {
|
||||
$sql .= " AND published = 1";
|
||||
}
|
||||
$sql .= " ORDER BY sort_order_in_series, created_at";
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([$series_id]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function getNextSortOrder($series_id) {
|
||||
$stmt = $this->pdo->prepare("SELECT MAX(sort_order_in_series) as max_order FROM books WHERE series_id = ?");
|
||||
$stmt->execute([$series_id]);
|
||||
$result = $stmt->fetch();
|
||||
return ($result['max_order'] ?? 0) + 1;
|
||||
}
|
||||
|
||||
public function getSeriesStats($series_id, $user_id = null) {
|
||||
$sql = "
|
||||
SELECT
|
||||
COUNT(b.id) as book_count,
|
||||
COALESCE(SUM(stats.chapter_count), 0) as chapter_count,
|
||||
COALESCE(SUM(stats.total_words), 0) as total_words
|
||||
FROM series s
|
||||
LEFT JOIN books b ON s.id = b.series_id
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
book_id,
|
||||
COUNT(id) as chapter_count,
|
||||
SUM(word_count) as total_words
|
||||
FROM chapters
|
||||
GROUP BY book_id
|
||||
) stats ON b.id = stats.book_id
|
||||
WHERE s.id = ?
|
||||
";
|
||||
|
||||
$params = [$series_id];
|
||||
|
||||
if ($user_id) {
|
||||
$sql .= " AND s.user_id = ?";
|
||||
$params[] = $user_id;
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
<?php
|
||||
// models/Series.php
|
||||
|
||||
class Series {
|
||||
private $pdo;
|
||||
|
||||
public function __construct($pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function findById($id) {
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT s.*,
|
||||
COUNT(b.id) as book_count,
|
||||
COALESCE((
|
||||
SELECT SUM(c.word_count)
|
||||
FROM chapters c
|
||||
JOIN books b2 ON c.book_id = b2.id
|
||||
WHERE b2.series_id = s.id AND b2.published = 1
|
||||
), 0) as total_words
|
||||
FROM series s
|
||||
LEFT JOIN books b ON s.id = b.series_id AND b.published = 1
|
||||
WHERE s.id = ?
|
||||
GROUP BY s.id
|
||||
");
|
||||
$stmt->execute([$id]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function findByUser($user_id, $include_stats = true) {
|
||||
if ($include_stats) {
|
||||
$sql = "
|
||||
SELECT s.*,
|
||||
COUNT(b.id) as book_count,
|
||||
COALESCE((
|
||||
SELECT SUM(c.word_count)
|
||||
FROM chapters c
|
||||
JOIN books b2 ON c.book_id = b2.id
|
||||
WHERE b2.series_id = s.id AND b2.user_id = ?
|
||||
), 0) as total_words
|
||||
FROM series s
|
||||
LEFT JOIN books b ON s.id = b.series_id
|
||||
WHERE s.user_id = ?
|
||||
GROUP BY s.id
|
||||
ORDER BY s.created_at DESC
|
||||
";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([$user_id, $user_id]);
|
||||
} else {
|
||||
$sql = "SELECT * FROM series WHERE user_id = ? ORDER BY created_at DESC";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([$user_id]);
|
||||
}
|
||||
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function create($data) {
|
||||
$stmt = $this->pdo->prepare("
|
||||
INSERT INTO series (title, description, user_id)
|
||||
VALUES (?, ?, ?)
|
||||
");
|
||||
return $stmt->execute([
|
||||
$data['title'],
|
||||
$data['description'] ?? null,
|
||||
$data['user_id']
|
||||
]);
|
||||
}
|
||||
|
||||
public function update($id, $data) {
|
||||
$stmt = $this->pdo->prepare("
|
||||
UPDATE series
|
||||
SET title = ?, description = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ? AND user_id = ?
|
||||
");
|
||||
return $stmt->execute([
|
||||
$data['title'],
|
||||
$data['description'] ?? null,
|
||||
$id,
|
||||
$data['user_id']
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete($id, $user_id) {
|
||||
try {
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
$stmt = $this->pdo->prepare("UPDATE books SET series_id = NULL, sort_order_in_series = NULL WHERE series_id = ? AND user_id = ?");
|
||||
$stmt->execute([$id, $user_id]);
|
||||
|
||||
$stmt = $this->pdo->prepare("DELETE FROM series WHERE id = ? AND user_id = ?");
|
||||
$result = $stmt->execute([$id, $user_id]);
|
||||
|
||||
$this->pdo->commit();
|
||||
return $result;
|
||||
} catch (Exception $e) {
|
||||
$this->pdo->rollBack();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function userOwnsSeries($series_id, $user_id) {
|
||||
$stmt = $this->pdo->prepare("SELECT id FROM series WHERE id = ? AND user_id = ?");
|
||||
$stmt->execute([$series_id, $user_id]);
|
||||
return $stmt->fetch() !== false;
|
||||
}
|
||||
|
||||
public function getBooksInSeries($series_id, $only_published = false) {
|
||||
$sql = "SELECT * FROM books WHERE series_id = ?";
|
||||
if ($only_published) {
|
||||
$sql .= " AND published = 1";
|
||||
}
|
||||
$sql .= " ORDER BY sort_order_in_series, created_at";
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([$series_id]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function getNextSortOrder($series_id) {
|
||||
$stmt = $this->pdo->prepare("SELECT MAX(sort_order_in_series) as max_order FROM books WHERE series_id = ?");
|
||||
$stmt->execute([$series_id]);
|
||||
$result = $stmt->fetch();
|
||||
return ($result['max_order'] ?? 0) + 1;
|
||||
}
|
||||
|
||||
public function getSeriesStats($series_id, $user_id = null) {
|
||||
$sql = "
|
||||
SELECT
|
||||
COUNT(b.id) as book_count,
|
||||
COALESCE(SUM(stats.chapter_count), 0) as chapter_count,
|
||||
COALESCE(SUM(stats.total_words), 0) as total_words
|
||||
FROM series s
|
||||
LEFT JOIN books b ON s.id = b.series_id
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
book_id,
|
||||
COUNT(id) as chapter_count,
|
||||
SUM(word_count) as total_words
|
||||
FROM chapters
|
||||
GROUP BY book_id
|
||||
) stats ON b.id = stats.book_id
|
||||
WHERE s.id = ?
|
||||
";
|
||||
|
||||
$params = [$series_id];
|
||||
|
||||
if ($user_id) {
|
||||
$sql .= " AND s.user_id = ?";
|
||||
$params[] = $user_id;
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
?>
|
||||
+118
-118
@@ -1,119 +1,119 @@
|
||||
<?php
|
||||
// models/User.php
|
||||
|
||||
class User {
|
||||
private $pdo;
|
||||
|
||||
public function __construct($pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function findById($id) {
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function findByUsername($username) {
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function findByEmail($email) {
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE email = ?");
|
||||
$stmt->execute([$email]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function findAll() {
|
||||
$stmt = $this->pdo->prepare("SELECT id, username, display_name, email, created_at, last_login, is_active FROM users ORDER BY created_at DESC");
|
||||
$stmt->execute();
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function create($data) {
|
||||
$password_hash = password_hash($data['password'], PASSWORD_DEFAULT);
|
||||
|
||||
$is_active = $data['is_active'] ?? 0;
|
||||
|
||||
$stmt = $this->pdo->prepare("
|
||||
INSERT INTO users (username, display_name, email, password_hash, is_active)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
");
|
||||
|
||||
return $stmt->execute([
|
||||
$data['username'],
|
||||
$data['display_name'] ?? $data['username'],
|
||||
$data['email'] ?? null,
|
||||
$password_hash,
|
||||
$is_active
|
||||
]);
|
||||
}
|
||||
|
||||
public function update($id, $data) {
|
||||
$sql = "UPDATE users SET display_name = ?, email = ?";
|
||||
$params = [$data['display_name'], $data['email']];
|
||||
|
||||
if (!empty($data['password'])) {
|
||||
$sql .= ", password_hash = ?";
|
||||
$params[] = password_hash($data['password'], PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
$sql .= " WHERE id = ?";
|
||||
$params[] = $id;
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
return $stmt->execute($params);
|
||||
}
|
||||
|
||||
public function updateStatus($id, $is_active) {
|
||||
$stmt = $this->pdo->prepare("UPDATE users SET is_active = ? WHERE id = ?");
|
||||
return $stmt->execute([$is_active, $id]);
|
||||
}
|
||||
|
||||
public function delete($id) {
|
||||
$stmt = $this->pdo->prepare("DELETE FROM users WHERE id = ?");
|
||||
return $stmt->execute([$id]);
|
||||
}
|
||||
|
||||
public function updateLastLogin($id) {
|
||||
$stmt = $this->pdo->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?");
|
||||
return $stmt->execute([$id]);
|
||||
}
|
||||
|
||||
public function verifyPassword($password, $hash) {
|
||||
return password_verify($password, $hash);
|
||||
}
|
||||
|
||||
public function updateAvatar($id, $filename) {
|
||||
$stmt = $this->pdo->prepare("UPDATE users SET avatar = ? WHERE id = ?");
|
||||
return $stmt->execute([$filename, $id]);
|
||||
}
|
||||
|
||||
public function updateBio($id, $bio) {
|
||||
$stmt = $this->pdo->prepare("UPDATE users SET bio = ? WHERE id = ?");
|
||||
return $stmt->execute([$bio, $id]);
|
||||
}
|
||||
|
||||
public function updateProfile($id, $data) {
|
||||
$sql = "UPDATE users SET display_name = ?, email = ?, bio = ?";
|
||||
$params = [
|
||||
$data['display_name'] ?? '',
|
||||
$data['email'] ?? null,
|
||||
$data['bio'] ?? null
|
||||
];
|
||||
|
||||
if (!empty($data['avatar'])) {
|
||||
$sql .= ", avatar = ?";
|
||||
$params[] = $data['avatar'];
|
||||
}
|
||||
|
||||
$sql .= " WHERE id = ?";
|
||||
$params[] = $id;
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
return $stmt->execute($params);
|
||||
}
|
||||
}
|
||||
<?php
|
||||
// models/User.php
|
||||
|
||||
class User {
|
||||
private $pdo;
|
||||
|
||||
public function __construct($pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function findById($id) {
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function findByUsername($username) {
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function findByEmail($email) {
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE email = ?");
|
||||
$stmt->execute([$email]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function findAll() {
|
||||
$stmt = $this->pdo->prepare("SELECT id, username, display_name, email, created_at, last_login, is_active FROM users ORDER BY created_at DESC");
|
||||
$stmt->execute();
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function create($data) {
|
||||
$password_hash = password_hash($data['password'], PASSWORD_DEFAULT);
|
||||
|
||||
$is_active = $data['is_active'] ?? 0;
|
||||
|
||||
$stmt = $this->pdo->prepare("
|
||||
INSERT INTO users (username, display_name, email, password_hash, is_active)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
");
|
||||
|
||||
return $stmt->execute([
|
||||
$data['username'],
|
||||
$data['display_name'] ?? $data['username'],
|
||||
$data['email'] ?? null,
|
||||
$password_hash,
|
||||
$is_active
|
||||
]);
|
||||
}
|
||||
|
||||
public function update($id, $data) {
|
||||
$sql = "UPDATE users SET display_name = ?, email = ?";
|
||||
$params = [$data['display_name'], $data['email']];
|
||||
|
||||
if (!empty($data['password'])) {
|
||||
$sql .= ", password_hash = ?";
|
||||
$params[] = password_hash($data['password'], PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
$sql .= " WHERE id = ?";
|
||||
$params[] = $id;
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
return $stmt->execute($params);
|
||||
}
|
||||
|
||||
public function updateStatus($id, $is_active) {
|
||||
$stmt = $this->pdo->prepare("UPDATE users SET is_active = ? WHERE id = ?");
|
||||
return $stmt->execute([$is_active, $id]);
|
||||
}
|
||||
|
||||
public function delete($id) {
|
||||
$stmt = $this->pdo->prepare("DELETE FROM users WHERE id = ?");
|
||||
return $stmt->execute([$id]);
|
||||
}
|
||||
|
||||
public function updateLastLogin($id) {
|
||||
$stmt = $this->pdo->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?");
|
||||
return $stmt->execute([$id]);
|
||||
}
|
||||
|
||||
public function verifyPassword($password, $hash) {
|
||||
return password_verify($password, $hash);
|
||||
}
|
||||
|
||||
public function updateAvatar($id, $filename) {
|
||||
$stmt = $this->pdo->prepare("UPDATE users SET avatar = ? WHERE id = ?");
|
||||
return $stmt->execute([$filename, $id]);
|
||||
}
|
||||
|
||||
public function updateBio($id, $bio) {
|
||||
$stmt = $this->pdo->prepare("UPDATE users SET bio = ? WHERE id = ?");
|
||||
return $stmt->execute([$bio, $id]);
|
||||
}
|
||||
|
||||
public function updateProfile($id, $data) {
|
||||
$sql = "UPDATE users SET display_name = ?, email = ?, bio = ?";
|
||||
$params = [
|
||||
$data['display_name'] ?? '',
|
||||
$data['email'] ?? null,
|
||||
$data['bio'] ?? null
|
||||
];
|
||||
|
||||
if (!empty($data['avatar'])) {
|
||||
$sql .= ", avatar = ?";
|
||||
$params[] = $data['avatar'];
|
||||
}
|
||||
|
||||
$sql .= " WHERE id = ?";
|
||||
$params[] = $id;
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
return $stmt->execute($params);
|
||||
}
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user