result($db->query("SELECT COUNT(*) FROM uploads")); } static function isExtensionAllowed(string $ext): bool { return in_array($ext, UPLOADS_ALLOWED_EXTENSIONS); } static function add(string $tmp_name, string $name, string $note): ?int { global $config; $name = sanitize_filename($name); if (!$name) $name = 'file'; $random_id = self::_getNewUploadRandomId(); $size = filesize($tmp_name); $is_image = detect_image_type($tmp_name) !== false; $image_w = 0; $image_h = 0; if ($is_image) { list($image_w, $image_h) = getimagesize($tmp_name); } $db = DB(); if (!$db->insert('uploads', [ 'random_id' => $random_id, 'ts' => time(), 'name' => $name, 'size' => $size, 'image' => (int)$is_image, 'image_w' => $image_w, 'image_h' => $image_h, 'note' => $note, 'downloads' => 0, ])) { return null; } $id = $db->insertId(); $dir = $config['uploads_dir'].'/'.$random_id; $path = $dir.'/'.$name; mkdir($dir); chmod($dir, 0775); // g+w rename($tmp_name, $path); chmod($path, 0664); // g+w return $id; } static function delete(int $id): bool { $upload = self::get($id); if (!$upload) return false; $db = DB(); $db->query("DELETE FROM uploads WHERE id=?", $id); rrmdir($upload->getDirectory()); return true; } /** * @return Upload[] */ static function getAllUploads(): array { $db = DB(); $q = $db->query("SELECT * FROM uploads ORDER BY id DESC"); return array_map('Upload::create_instance', $db->fetchAll($q)); } static function get(int $id): ?Upload { $db = DB(); $q = $db->query("SELECT * FROM uploads WHERE id=?", $id); if ($db->numRows($q)) { return new Upload($db->fetch($q)); } else { return null; } } /** * @param string[] $ids * @param bool $flat * @return Upload[] */ static function getUploadsByRandomId(array $ids, bool $flat = false): array { if (empty($ids)) { return []; } $db = DB(); $uploads = array_fill_keys($ids, null); $q = $db->query("SELECT * FROM uploads WHERE random_id IN('".implode('\',\'', array_map([$db, 'escape'], $ids))."')"); while ($row = $db->fetch($q)) { $uploads[$row['random_id']] = new Upload($row); } if ($flat) { $list = []; foreach ($ids as $id) { $list[] = $uploads[$id]; } unset($uploads); return $list; } return $uploads; } static function getUploadByRandomId(string $random_id): ?Upload { $db = DB(); $q = $db->query("SELECT * FROM uploads WHERE random_id=? LIMIT 1", $random_id); if ($db->numRows($q)) { return new Upload($db->fetch($q)); } else { return null; } } static function _getNewUploadRandomId(): string { $db = DB(); do { $random_id = strgen(8); } while ($db->numRows($db->query("SELECT id FROM uploads WHERE random_id=?", $random_id)) > 0); return $random_id; } } 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; function getDirectory(): string { global $config; return $config['uploads_dir'].'/'.$this->randomId; } function getDirectUrl(): string { global $config; return 'https://'.$config['uploads_host'].'/'.$this->randomId.'/'.$this->name; } 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; } $prefix = $this->imageMayHaveAlphaChannel() ? 'a' : 'p'; return 'https://'.$config['uploads_host'].'/'.$this->randomId.'/'.$prefix.$w.'x'.$h.'.jpg'; } // TODO remove? function incrementDownloads() { $db = DB(); $db->query("UPDATE uploads SET downloads=downloads+1 WHERE id=?", $this->id); $this->downloads++; } function getSize(): string { return sizeString($this->size); } 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 .= ' '; return $md; } function setNote(string $note) { $db = DB(); $db->query("UPDATE uploads SET note=? WHERE id=?", $note, $this->id); } function isImage(): bool { return in_array(extension($this->name), self::$ImageExtensions); } // assume all png images have alpha channel // i know this is wrong, but anyway function imageMayHaveAlphaChannel(): bool { return strtolower(extension($this->name)) == 'png'; } function isVideo(): bool { return in_array(extension($this->name), self::$VideoExtensions); } function getImageRatio(): float { return $this->imageW / $this->imageH; } 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]; } function createImagePreview(?int $w = null, ?int $h = null, bool $force_update = false, bool $may_have_alpha = false): bool { global $config; $orig = $config['uploads_dir'].'/'.$this->randomId.'/'.$this->name; $updated = false; foreach (themes::getThemes() as $theme) { if (!$may_have_alpha && $theme == 'dark') continue; for ($mult = 1; $mult <= 2; $mult++) { $dw = $w * $mult; $dh = $h * $mult; $prefix = $may_have_alpha ? 'a' : 'p'; $dst = $config['uploads_dir'].'/'.$this->randomId.'/'.$prefix.$dw.'x'.$dh.($theme == 'dark' ? '_dark' : '').'.jpg'; if (file_exists($dst)) { if (!$force_update) continue; unlink($dst); } $img = imageopen($orig); imageresize($img, $dw, $dh, themes::getThemeAlphaColorAsRGB($theme)); imagejpeg($img, $dst, $mult == 1 ? 93 : 67); imagedestroy($img); setperm($dst); $updated = true; } } return $updated; } /** * @return int Number of deleted files */ function deleteAllImagePreviews(): int { global $config; $dir = $config['uploads_dir'].'/'.$this->randomId; $files = scandir($dir); $deleted = 0; foreach ($files as $f) { if (preg_match('/^[ap](\d+)x(\d+)(?:_dark)?\.jpg$/', $f)) { if (is_file($dir.'/'.$f)) unlink($dir.'/'.$f); else logError(__METHOD__.': '.$dir.'/'.$f.' is not a file!'); $deleted++; } } return $deleted; } }