4in1_ws_web/lib/posts.php

374 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 bool $visible;
public string $shortName;
public string $sourceUrl;
protected array $texts = [];
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);
}
}