diff options
author | Evgeny Zinoviev <me@ch1p.io> | 2022-07-09 19:40:17 +0300 |
---|---|---|
committer | Evgeny Zinoviev <me@ch1p.io> | 2022-07-09 19:40:17 +0300 |
commit | f7bfdf58def6aadc922e1632f407d1418269a0d7 (patch) | |
tree | d7a0b2819e6a26c11d40ee0b27267ea827fbb345 /model |
initial
Diffstat (limited to 'model')
-rw-r--r-- | model/Page.php | 44 | ||||
-rw-r--r-- | model/Post.php | 185 | ||||
-rw-r--r-- | model/Tag.php | 24 | ||||
-rw-r--r-- | model/Upload.php | 152 |
4 files changed, 405 insertions, 0 deletions
diff --git a/model/Page.php b/model/Page.php new file mode 100644 index 0000000..6711a2c --- /dev/null +++ b/model/Page.php @@ -0,0 +1,44 @@ +<?php + +class Page extends Model { + + const DB_TABLE = 'pages'; + const DB_KEY = 'short_name'; + + public string $title; + public string $md; + public string $html; + public int $ts; + public int $updateTs; + public bool $visible; + public string $shortName; + + public function edit(array $data) { + $data['update_ts'] = time(); + if ($data['md'] != $this->md) + $data['html'] = markup::markdownToHtml($data['md']); + parent::edit($data); + } + + public function isUpdated(): bool { + return $this->updateTs && $this->updateTs != $this->ts; + } + + public function getHtml(bool $retina): string { + $html = $this->html; + if ($retina) + $html = markup::htmlRetinaFix($html); + return $html; + } + + public function getUrl(): string { + return "/{$this->shortName}/"; + } + + public function updateHtml() { + $html = markup::markdownToHtml($this->md); + $this->html = $html; + getDb()->query("UPDATE pages SET html=? WHERE short_name=?", $html, $this->shortName); + } + +} diff --git a/model/Post.php b/model/Post.php new file mode 100644 index 0000000..10a396b --- /dev/null +++ b/model/Post.php @@ -0,0 +1,185 @@ +<?php + +class Post extends Model { + + const DB_TABLE = 'posts'; + + public int $id; + public string $title; + public string $md; + public string $html; + public string $text; + public int $ts; + public int $updateTs; + public bool $visible; + public string $shortName; + + public function edit(array $data) { + $cur_ts = time(); + if (!$this->visible && $data['visible']) + $data['ts'] = $cur_ts; + + $data['update_ts'] = $cur_ts; + + if ($data['md'] != $this->md) { + $data['html'] = \markup::markdownToHtml($data['md']); + $data['text'] = \markup::htmlToText($data['html']); + } + + parent::edit($data); + $this->updateImagePreviews(); + } + + public function updateHtml() { + $html = \markup::markdownToHtml($this->md); + $this->html = $html; + + getDb()->query("UPDATE posts SET html=? WHERE id=?", $html, $this->id); + } + + public function updateText() { + $html = \markup::markdownToHtml($this->md); + $text = \markup::htmlToText($html); + $this->text = $text; + + getDb()->query("UPDATE posts 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::getByRandomId($match[1]); + } + + public function getUrl(): string { + return $this->shortName != '' ? "/{$this->shortName}/" : "/{$this->id}/"; + } + + public function getDate(): string { + return date('j M', $this->ts); + } + + public function getYear(): int { + return (int)date('Y', $this->ts); + } + + public function getFullDate(): string { + return date('j F Y', $this->ts); + } + + public function getUpdateDate(): string { + return date('j M', $this->updateTs); + } + + public function getFullUpdateDate(): string { + return date('j F Y', $this->updateTs); + } + + public function getHtml(bool $retina): string { + $html = $this->html; + if ($retina) + $html = markup::htmlRetinaFix($html); + return $html; + } + + public function isUpdated(): bool { + return $this->updateTs && $this->updateTs != $this->ts; + } + + /** + * @return Tag[] + */ + public function getTags(): array { + $db = getDb(); + $q = $db->query("SELECT tags.* FROM posts_tags + LEFT JOIN tags ON tags.id=posts_tags.tag_id + WHERE posts_tags.post_id=? + ORDER BY posts_tags.tag_id", $this->id); + return array_map('Tag::create_instance', $db->fetchAll($q)); + } + + /** + * @return int[] + */ + public function getTagIds(): array { + $ids = []; + $db = getDb(); + $q = $db->query("SELECT tag_id FROM posts_tags WHERE post_id=? ORDER BY tag_id", $this->id); + while ($row = $db->fetch($q)) { + $ids[] = (int)$row['tag_id']; + } + return $ids; + } + + public function setTagIds(array $new_tag_ids) { + $cur_tag_ids = $this->getTagIds(); + $add_tag_ids = array_diff($new_tag_ids, $cur_tag_ids); + $rm_tag_ids = array_diff($cur_tag_ids, $new_tag_ids); + + $db = getDb(); + if (!empty($add_tag_ids)) { + $rows = []; + foreach ($add_tag_ids as $id) + $rows[] = ['post_id' => $this->id, 'tag_id' => $id]; + $db->multipleInsert('posts_tags', $rows); + } + + if (!empty($rm_tag_ids)) + $db->query("DELETE FROM posts_tags WHERE post_id=? AND tag_id IN(".implode(',', $rm_tag_ids).")", $this->id); + + $upd_tag_ids = array_merge($new_tag_ids, $rm_tag_ids); + $upd_tag_ids = array_unique($upd_tag_ids); + foreach ($upd_tag_ids as $id) + posts::recountPostsWithTag($id); + } + + /** + * @param bool $update Whether to overwrite preview if already exists + * @return int + */ + 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 (strpos($opt, '=') !== false) { + 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 $u) { + foreach ($images[$u->randomId] as $s) { + list($w, $h) = $s; + list($w, $h) = $u->getImagePreviewSize($w, $h); + if ($u->createImagePreview($w, $h, $update)) { + $images_affected++; + } + } + } + + return $images_affected; + } + +} diff --git a/model/Tag.php b/model/Tag.php new file mode 100644 index 0000000..a8324f7 --- /dev/null +++ b/model/Tag.php @@ -0,0 +1,24 @@ +<?php + +class Tag extends Model implements Stringable { + + const DB_TABLE = 'tags'; + + public int $id; + public string $tag; + public int $postsCount; + public int $visiblePostsCount; + + public function getUrl(): string { + return '/'.$this->tag.'/'; + } + + public function getPostsCount(bool $is_admin): int { + return $is_admin ? $this->postsCount : $this->visiblePostsCount; + } + + public function __toString(): string { + return $this->tag; + } + +} diff --git a/model/Upload.php b/model/Upload.php new file mode 100644 index 0000000..586be24 --- /dev/null +++ b/model/Upload.php @@ -0,0 +1,152 @@ +<?php + +class Upload extends Model { + + const DB_TABLE = 'uploads'; + + public static array $ImageExtensions = ['jpg', 'jpeg', 'png', 'gif']; + public static array $VideoExtensions = ['mp4', 'ogg']; + + public int $id; + public string $randomId; + public int $ts; + public string $name; + public int $size; + public int $downloads; + public int $image; // TODO: remove + public int $imageW; + public int $imageH; + public string $note; + + public function getDirectory(): string { + global $config; + return $config['uploads_dir'].'/'.$this->randomId; + } + + public function getDirectUrl(): string { + global $config; + return 'https://'.$config['uploads_host'].'/'.$this->randomId.'/'.$this->name; + } + + public function getDirectPreviewUrl(int $w, int $h, bool $retina = false): string { + global $config; + if ($w == $this->imageW && $this->imageH == $h) + return $this->getDirectUrl(); + + if ($retina) { + $w *= 2; + $h *= 2; + } + + return 'https://'.$config['uploads_host'].'/'.$this->randomId.'/p'.$w.'x'.$h.'.jpg'; + } + + // TODO remove? + public function incrementDownloads() { + $db = getDb(); + $db->query("UPDATE uploads SET downloads=downloads+1 WHERE id=?", $this->id); + $this->downloads++; + } + + public function getSize(): string { + return sizeString($this->size); + } + + public function getMarkdown(): string { + if ($this->isImage()) { + $md = '{image:'.$this->randomId.',w='.$this->imageW.',h='.$this->imageH.'}{/image}'; + } else if ($this->isVideo()) { + $md = '{video:'.$this->randomId.'}{/video}'; + } else { + $md = '{fileAttach:'.$this->randomId.'}{/fileAttach}'; + } + $md .= ' <!-- '.$this->name.' -->'; + return $md; + } + + public function setNote(string $note) { + $db = getDb(); + $db->query("UPDATE uploads SET note=? WHERE id=?", $note, $this->id); + } + + public function isImage(): bool { + return in_array(extension($this->name), self::$ImageExtensions); + } + + public function isVideo(): bool { + return in_array(extension($this->name), self::$VideoExtensions); + } + + public function getImageRatio(): float { + return $this->imageW / $this->imageH; + } + + public function getImagePreviewSize(?int $w = null, ?int $h = null): array { + if (is_null($w) && is_null($h)) + throw new Exception(__METHOD__.': both width and height can\'t be null'); + + if (is_null($h)) + $h = round($w / $this->getImageRatio()); + + if (is_null($w)) + $w = round($h * $this->getImageRatio()); + + return [$w, $h]; + } + + /** + * @param ?int $w + * @param ?int $h + * @param bool $update Whether to proceed if preview already exists + * @return bool + */ + public function createImagePreview(?int $w = null, ?int $h = null, bool $update = false): bool { + global $config; + + $orig = $config['uploads_dir'].'/'.$this->randomId.'/'.$this->name; + $updated = false; + + for ($mult = 1; $mult <= 2; $mult++) { + $dw = $w * $mult; + $dh = $h * $mult; + $dst = $config['uploads_dir'].'/'.$this->randomId.'/p'.$dw.'x'.$dh.'.jpg'; + + if (file_exists($dst)) { + if (!$update) + continue; + unlink($dst); + } + + $img = imageopen($orig); + imageresize($img, $dw, $dh, [255, 255, 255]); + imagejpeg($img, $dst, $mult == 1 ? 93 : 67); + imagedestroy($img); + + setperm($dst); + $updated = true; + } + + return $updated; + } + + /** + * @return int Number of deleted files + */ + public function deleteAllImagePreviews(): int { + global $config; + $dir = $config['uploads_dir'].'/'.$this->randomId; + $files = scandir($dir); + $deleted = 0; + foreach ($files as $f) { + if (preg_match('/^p(\d+)x(\d+)\.jpg$/', $f)) { + if (is_file($dir.'/'.$f)) + unlink($dir.'/'.$f); + else + logError('deleteAllImagePreviews: '.$dir.'/'.$f.' is not a file!'); + $deleted++; + } + } + return $deleted; + } + +} |