aboutsummaryrefslogtreecommitdiff
path: root/model
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2022-07-09 19:40:17 +0300
committerEvgeny Zinoviev <me@ch1p.io>2022-07-09 19:40:17 +0300
commitf7bfdf58def6aadc922e1632f407d1418269a0d7 (patch)
treed7a0b2819e6a26c11d40ee0b27267ea827fbb345 /model
initial
Diffstat (limited to 'model')
-rw-r--r--model/Page.php44
-rw-r--r--model/Post.php185
-rw-r--r--model/Tag.php24
-rw-r--r--model/Upload.php152
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;
+ }
+
+}