381 lines
11 KiB
PHP
381 lines
11 KiB
PHP
<?php
|
|
|
|
enum PostLanguage: string {
|
|
case Russian = 'ru';
|
|
case English = 'en';
|
|
|
|
public static function getDefault(): PostLanguage {
|
|
return self::English;
|
|
}
|
|
}
|
|
|
|
class Post extends model {
|
|
|
|
const DB_TABLE = 'posts';
|
|
|
|
public int $id;
|
|
public string $date;
|
|
public ?string $updateTime;
|
|
public bool $visible;
|
|
public string $shortName;
|
|
public string $sourceUrl;
|
|
public string $keywords;
|
|
|
|
protected array $texts = [];
|
|
|
|
public function edit(array $fields) {
|
|
$fields['update_time'] = date(mysql::DATETIME_FORMAT, time());
|
|
parent::edit($fields);
|
|
}
|
|
|
|
public function addText(PostLanguage $lang, string $title, string $md, bool $toc): ?PostText {
|
|
$html = markup::markdownToHtml($md, lang: $lang);
|
|
$text = markup::htmlToText($html);
|
|
|
|
$data = [
|
|
'title' => $title,
|
|
'lang' => $lang->value,
|
|
'post_id' => $this->id,
|
|
'html' => $html,
|
|
'text' => $text,
|
|
'md' => $md,
|
|
'toc' => $toc,
|
|
];
|
|
|
|
$db = DB();
|
|
if (!$db->insert('posts_texts', $data))
|
|
return null;
|
|
|
|
$id = $db->insertId();
|
|
|
|
$post_text = posts::getText($id);
|
|
$post_text->updateImagePreviews();
|
|
|
|
return $post_text;
|
|
}
|
|
|
|
public function registerText(PostText $postText): void {
|
|
if (array_key_exists($postText->lang->value, $this->texts))
|
|
throw new Exception("text for language {$postText->lang->value} has already been registered");
|
|
$this->texts[$postText->lang->value] = $postText;
|
|
}
|
|
|
|
public function loadTexts() {
|
|
if (!empty($this->texts))
|
|
return;
|
|
$db = DB();
|
|
$q = $db->query("SELECT * FROM posts_texts WHERE post_id=?", $this->id);
|
|
while ($row = $db->fetch($q)) {
|
|
$text = new PostText($row);
|
|
$this->registerText($text);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return PostText[]
|
|
*/
|
|
public function getTexts(): array {
|
|
$this->loadTexts();
|
|
return $this->texts;
|
|
}
|
|
|
|
public function getText(PostLanguage $lang): ?PostText {
|
|
$this->loadTexts();
|
|
return $this->texts[$lang->value] ?? null;
|
|
}
|
|
|
|
public function hasLang(PostLanguage $lang) {
|
|
$this->loadTexts();
|
|
foreach ($this->texts as $text) {
|
|
if ($text->lang == $lang)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function getUrl(?PostLanguage $lang = null): string {
|
|
$buf = $this->shortName != '' ? "/articles/{$this->shortName}/" : "/articles/{$this->id}/";
|
|
if ($lang && $lang != PostLanguage::English)
|
|
$buf .= '?lang='.$lang->value;
|
|
return $buf;
|
|
}
|
|
|
|
public function getTimestamp(): int {
|
|
return (new DateTime($this->date))->getTimestamp();
|
|
}
|
|
|
|
public function getDate(): string {
|
|
return date('j M', $this->getTimestamp());
|
|
}
|
|
|
|
public function getYear(): int {
|
|
return (int)date('Y', $this->getTimestamp());
|
|
}
|
|
|
|
public function getFullDate(): string {
|
|
return date('j F Y', $this->getTimestamp());
|
|
}
|
|
|
|
public function getDateForInputField(): string {
|
|
return date('Y-m-d', $this->getTimestamp());
|
|
}
|
|
}
|
|
|
|
class PostText extends model {
|
|
const DB_TABLE = 'posts_texts';
|
|
|
|
public int $id;
|
|
public int $postId;
|
|
public PostLanguage $lang;
|
|
public string $title;
|
|
public string $md;
|
|
public string $html;
|
|
public string $text;
|
|
public bool $toc;
|
|
public string $tocHtml;
|
|
|
|
public function edit(array $fields) {
|
|
if ($fields['md'] != $this->md) {
|
|
$fields['html'] = markup::markdownToHtml($fields['md'], lang: $this->lang);
|
|
$fields['text'] = markup::htmlToText($fields['html']);
|
|
}
|
|
|
|
if ((isset($fields['toc']) && $fields['toc']) || $this->toc) {
|
|
$fields['toc_html'] = markup::toc($fields['md']);
|
|
}
|
|
|
|
parent::edit($fields);
|
|
$this->updateImagePreviews();
|
|
}
|
|
|
|
public function updateHtml(): void {
|
|
$html = markup::markdownToHtml($this->md, lang: $this->lang);
|
|
$this->html = $html;
|
|
DB()->query("UPDATE posts_texts SET html=? WHERE id=?", $html, $this->id);
|
|
}
|
|
|
|
public function updateText(): void {
|
|
$html = markup::markdownToHtml($this->md, lang: $this->lang);
|
|
$text = markup::htmlToText($html);
|
|
$this->text = $text;
|
|
DB()->query("UPDATE posts_texts SET text=? WHERE id=?", $text, $this->id);
|
|
}
|
|
|
|
public function getDescriptionPreview(int $len): string {
|
|
if (mb_strlen($this->text) >= $len)
|
|
return mb_substr($this->text, 0, $len-3).'...';
|
|
return $this->text;
|
|
}
|
|
|
|
public function getFirstImage(): ?Upload {
|
|
if (!preg_match('/\{image:([\w]{8})/', $this->md, $match))
|
|
return null;
|
|
return uploads::getUploadByRandomId($match[1]);
|
|
}
|
|
|
|
public function getHtml(bool $is_retina, string $theme): string {
|
|
$html = $this->html;
|
|
return markup::htmlImagesFix($html, $is_retina, $theme);
|
|
}
|
|
|
|
public function getTableOfContentsHtml(): ?string {
|
|
return $this->toc ? $this->tocHtml : null;
|
|
}
|
|
|
|
public function hasTableOfContents(): bool {
|
|
return $this->toc;
|
|
}
|
|
|
|
/**
|
|
* @param bool $update Whether to overwrite preview if already exists
|
|
* @return int
|
|
* @throws Exception
|
|
*/
|
|
public function updateImagePreviews(bool $update = false): int {
|
|
$images = [];
|
|
if (!preg_match_all('/\{image:([\w]{8}),(.*?)}/', $this->md, $matches))
|
|
return 0;
|
|
|
|
for ($i = 0; $i < count($matches[0]); $i++) {
|
|
$id = $matches[1][$i];
|
|
$w = $h = null;
|
|
$opts = explode(',', $matches[2][$i]);
|
|
foreach ($opts as $opt) {
|
|
if (str_contains($opt, '=')) {
|
|
list($k, $v) = explode('=', $opt);
|
|
if ($k == 'w')
|
|
$w = (int)$v;
|
|
else if ($k == 'h')
|
|
$h = (int)$v;
|
|
}
|
|
}
|
|
$images[$id][] = [$w, $h];
|
|
}
|
|
|
|
if (empty($images))
|
|
return 0;
|
|
|
|
$images_affected = 0;
|
|
$uploads = uploads::getUploadsByRandomId(array_keys($images), true);
|
|
foreach ($uploads as $upload_key => $u) {
|
|
if ($u === null) {
|
|
logError(__METHOD__.': upload '.$upload_key.' is null');
|
|
continue;
|
|
}
|
|
foreach ($images[$u->randomId] as $s) {
|
|
list($w, $h) = $s;
|
|
list($w, $h) = $u->getImagePreviewSize($w, $h);
|
|
if ($u->createImagePreview($w, $h, $update, $u->imageMayHaveAlphaChannel()))
|
|
$images_affected++;
|
|
}
|
|
}
|
|
|
|
return $images_affected;
|
|
}
|
|
|
|
}
|
|
|
|
class posts {
|
|
|
|
static function getCount(bool $include_hidden = false): int {
|
|
$db = DB();
|
|
$sql = "SELECT COUNT(*) FROM posts";
|
|
if (!$include_hidden) {
|
|
$sql .= " WHERE visible=1";
|
|
}
|
|
return (int)$db->result($db->query($sql));
|
|
}
|
|
|
|
/**
|
|
* @return Post[]
|
|
*/
|
|
static function getList(int $offset = 0,
|
|
int $count = -1,
|
|
bool $include_hidden = false,
|
|
?PostLanguage $filter_by_lang = null
|
|
): array {
|
|
$db = DB();
|
|
$sql = "SELECT * FROM posts";
|
|
if (!$include_hidden)
|
|
$sql .= " WHERE visible=1";
|
|
$sql .= " ORDER BY `date` DESC";
|
|
if ($offset != 0 && $count != -1)
|
|
$sql .= "LIMIT $offset, $count";
|
|
$q = $db->query($sql);
|
|
$posts = [];
|
|
while ($row = $db->fetch($q)) {
|
|
$posts[$row['id']] = $row;
|
|
}
|
|
|
|
if (!empty($posts)) {
|
|
foreach ($posts as &$post)
|
|
$post = new Post($post);
|
|
$q = $db->query("SELECT * FROM posts_texts WHERE post_id IN (".implode(',', array_keys($posts)).")");
|
|
while ($row = $db->fetch($q)) {
|
|
$posts[$row['post_id']]->registerText(new PostText($row));
|
|
}
|
|
}
|
|
|
|
if ($filter_by_lang !== null)
|
|
$posts = array_filter($posts, fn(Post $post) => $post->hasLang($filter_by_lang));
|
|
|
|
return array_values($posts);
|
|
}
|
|
|
|
static function add(array $data = []): ?Post {
|
|
$db = DB();
|
|
if (!$db->insert('posts', $data))
|
|
return null;
|
|
return self::get($db->insertId());
|
|
}
|
|
|
|
static function delete(Post $post): void {
|
|
$db = DB();
|
|
$db->query("DELETE FROM posts WHERE id=?", $post->id);
|
|
$db->query("DELETE FROM posts_texts WHERE post_id=?", $post->id);
|
|
}
|
|
|
|
static function get(int $id): ?Post {
|
|
$db = DB();
|
|
$q = $db->query("SELECT * FROM posts WHERE id=?", $id);
|
|
return $db->numRows($q) ? new Post($db->fetch($q)) : null;
|
|
}
|
|
|
|
static function getText(int $text_id): ?PostText {
|
|
$db = DB();
|
|
$q = $db->query("SELECT * FROM posts_texts WHERE id=?", $text_id);
|
|
return $db->numRows($q) ? new PostText($db->fetch($q)) : null;
|
|
}
|
|
|
|
static function getByName(string $short_name): ?Post {
|
|
$db = DB();
|
|
$q = $db->query("SELECT * FROM posts WHERE short_name=?", $short_name);
|
|
return $db->numRows($q) ? new Post($db->fetch($q)) : null;
|
|
}
|
|
|
|
static function getPostsById(array $ids, bool $flat = false): array {
|
|
if (empty($ids)) {
|
|
return [];
|
|
}
|
|
|
|
$db = DB();
|
|
$posts = array_fill_keys($ids, null);
|
|
|
|
$q = $db->query("SELECT * FROM posts WHERE id IN(".implode(',', $ids).")");
|
|
|
|
while ($row = $db->fetch($q)) {
|
|
$posts[(int)$row['id']] = new Post($row);
|
|
}
|
|
|
|
if ($flat) {
|
|
$list = [];
|
|
foreach ($ids as $id) {
|
|
$list[] = $posts[$id];
|
|
}
|
|
unset($posts);
|
|
return $list;
|
|
}
|
|
|
|
return $posts;
|
|
}
|
|
|
|
static function getPostTextsById(array $ids, bool $flat = false): array {
|
|
if (empty($ids)) {
|
|
return [];
|
|
}
|
|
|
|
$db = DB();
|
|
$posts = array_fill_keys($ids, null);
|
|
|
|
$q = $db->query("SELECT * FROM posts_texts WHERE id IN(".implode(',', $ids).")");
|
|
|
|
while ($row = $db->fetch($q)) {
|
|
$posts[(int)$row['id']] = new PostText($row);
|
|
}
|
|
|
|
if ($flat) {
|
|
$list = [];
|
|
foreach ($ids as $id) {
|
|
$list[] = $posts[$id];
|
|
}
|
|
unset($posts);
|
|
return $list;
|
|
}
|
|
|
|
return $posts;
|
|
}
|
|
|
|
/**
|
|
* @param Upload $upload
|
|
* @return PostText[] Array of PostTexts that includes specified upload
|
|
*/
|
|
static function getTextsWithUpload(Upload $upload): array {
|
|
$db = DB();
|
|
$q = $db->query("SELECT id FROM posts_texts WHERE md LIKE '%{image:{$upload->randomId}%'");
|
|
$ids = [];
|
|
while ($row = $db->fetch($q))
|
|
$ids[] = (int)$row['id'];
|
|
return self::getPostTextsById($ids, true);
|
|
}
|
|
|
|
} |