upload_id; } public function getPath(): string { return YTKNS_UPLOADS . '/' . $this->getId(); } public function getUrl(): string { return 'https://' . Config::get('domain.main') . '/uploads/' . $this->getId(); } public function getUserId(): int { return $this->user_id; } public function setUserId(int $userId): void { $this->user_id = $userId; } public function getType(): string { return $this->upload_type ?? 'text/plain'; } public function getName(): string { return $this->upload_name ?? ''; } public function getHash(): string { return $this->upload_hash ?? str_pad('', 64, '0'); } public function getUser(): User { return User::byId($this->getUserId()); } public function getUseCount(): int { return $this->upload_use_count ?? 0; } public function getCreated(): int { return $this->upload_created; } public function getlastUsed(): ?int { return $this->upload_last_used; } public function getDeleted(): ?int { return $this->upload_deleted; } public function getDMCA(): ?int { return $this->upload_dmca; } public function delete(bool $hard): void { if($hard) { if(is_file($this->getPath())) unlink($this->getPath()); if($this->getDMCA() < 1) { $delete = DB::prepare(' DELETE FROM `ytkns_uploads` WHERE `upload_id` = :id '); $delete->bindValue('id', $this->getId()); $delete->execute(); } } else { $delete = DB::prepare(' UPDATE `ytkns_uploads` SET `upload_deleted` = NOW() WHERE `upload_id` = :id '); $delete->bindValue('id', $this->getId()); $delete->execute(); } } public function toJson(bool $asString = false) { $uploadInfo = [ 'id' => $this->getId(), 'name' => $this->getName(), 'type' => $this->getType(), 'user' => $this->getUserId(), 'uses' => $this->getUseCount(), 'hash' => $this->getHash(), 'created' => $this->getCreated(), 'last_used' => $this->getLastUsed(), 'deleted' => $this->getDeleted(), 'dmca' => $this->getDMCA(), ]; if($asString) $uploadInfo = json_encode($uploadInfo); return $uploadInfo; } public static function generateId(int $length = 16): string { $token = random_bytes($length); $chars = strlen(self::ID_CHARS); for($i = 0; $i < $length; $i++) $token[$i] = self::ID_CHARS[ord($token[$i]) % $chars]; return $token; } public static function create(User $user, string $fileName, string $fileType, string $fileHash): self { $id = self::generateId(); $create = DB::prepare(' INSERT INTO `ytkns_uploads` ( `upload_id`, `user_id`, `upload_ip`, `upload_name`, `upload_type`, `upload_hash` ) VALUES ( :id, :user, INET6_ATON(:ip), :name, :type, UNHEX(:hash) ) '); $create->bindValue('id', $id); $create->bindValue('user', $user->getId()); $create->bindValue('ip', $_SERVER['REMOTE_ADDR']); $create->bindValue('name', $fileName); $create->bindValue('type', $fileType); $create->bindValue('hash', $fileHash); $create->execute(); try { return self::byId($id); } catch(UploadNotFoundException $ex) { throw new UploadCreationFailedException; } } public static function byId(string $id): self { $getUpload = DB::prepare(' SELECT `upload_id`, `user_id`, `upload_use_count`, `upload_name`, `upload_type`, UNIX_TIMESTAMP(`upload_created`) AS `upload_created`, UNIX_TIMESTAMP(`upload_last_used`) AS `upload_last_used`, UNIX_TIMESTAMP(`upload_deleted`) AS `upload_deleted`, UNIX_TIMESTAMP(`upload_dmca`) AS `upload_dmca`, INET6_NTOA(`upload_ip`) AS `upload_ip`, LOWER(HEX(`upload_hash`)) AS `upload_hash` FROM `ytkns_uploads` WHERE `upload_id` = :id AND `upload_deleted` IS NULL '); $getUpload->bindValue('id', $id); $upload = $getUpload->execute() ? $getUpload->fetchObject(self::class) : false; if(!$upload) throw new UploadNotFoundException; return $upload; } public static function byHash(string $hash): ?self { $getUpload = DB::prepare(' SELECT `upload_id`, `user_id`, `upload_use_count`, `upload_name`, `upload_type`, UNIX_TIMESTAMP(`upload_created`) AS `upload_created`, UNIX_TIMESTAMP(`upload_last_used`) AS `upload_last_used`, UNIX_TIMESTAMP(`upload_deleted`) AS `upload_deleted`, UNIX_TIMESTAMP(`upload_dmca`) AS `upload_dmca`, INET6_NTOA(`upload_ip`) AS `upload_ip`, LOWER(HEX(`upload_hash`)) AS `upload_hash` FROM `ytkns_uploads` WHERE `upload_hash` = UNHEX(:hash) AND `upload_deleted` IS NULL '); $getUpload->bindValue('hash', $hash); $upload = $getUpload->execute() ? $getUpload->fetchObject(self::class) : false; return $upload ? $upload : null; } public static function deleted(): array { $getDeleted = DB::prepare(' SELECT `upload_id`, `user_id`, `upload_use_count`, `upload_name`, `upload_type`, UNIX_TIMESTAMP(`upload_created`) AS `upload_created`, UNIX_TIMESTAMP(`upload_last_used`) AS `upload_last_used`, UNIX_TIMESTAMP(`upload_deleted`) AS `upload_deleted`, UNIX_TIMESTAMP(`upload_dmca`) AS `upload_dmca`, INET6_NTOA(`upload_ip`) AS `upload_ip`, LOWER(HEX(`upload_hash`)) AS `upload_hash` FROM `ytkns_uploads` WHERE `upload_deleted` IS NOT NULL OR `upload_dmca` IS NOT NULL '); if(!$getDeleted->execute()) return []; $deleted = []; while($upload = $getDeleted->fetchObject(self::class)) $deleted[] = $upload; return $deleted; } public static function purgeOrphans(): void { DB::exec(' UPDATE `ytkns_uploads` SET `upload_deleted` = NOW() WHERE `upload_use_count` < 1 AND (`upload_created` + INTERVAL 1 DAY) < NOW() AND `upload_dmca` IS NULL '); $orphans = self::deleted(); foreach($orphans as $orphan) $orphan->delete(true); } public static function resync(array $uploadFields): void { if(empty($uploadFields)) return; $effectNames = array_keys($uploadFields); $fetchEffectsWhereIn = range(0, count($effectNames) - 1); array_walk($fetchEffectsWhereIn, function(&$i, $k, $v) { $i = $v . $i; }, ':field_'); $fetchEffects = DB::prepare(sprintf(' SELECT `effect_name`, `effect_params` FROM `ytkns_zones_effects` WHERE `effect_name` IN (%s) ', implode(', ', $fetchEffectsWhereIn))); foreach($fetchEffectsWhereIn as $index => $name) $fetchEffects->bindValue($name, $effectNames[$index]); $effectsWithUploads = $fetchEffects->execute() ? $fetchEffects->fetchAll(\PDO::FETCH_OBJ) : false; $effectsWithUploads = $effectsWithUploads ? $effectsWithUploads : []; $uploadUseCounts = []; foreach($effectsWithUploads as $effectWithUploads) { if(!array_key_exists($effectWithUploads->effect_name, $uploadFields)) continue; $effectParams = json_decode($effectWithUploads->effect_params, true); foreach($uploadFields[$effectWithUploads->effect_name] as $uploadField) { if(empty($uploadId = $effectParams[$uploadField])) continue; if(!isset($uploadUseCounts[$uploadId])) $uploadUseCounts[$uploadId] = 1; else $uploadUseCounts[$uploadId] += 1; } } $updateUploadUseCount = DB::prepare(' UPDATE `ytkns_uploads` SET `upload_use_count` = :count WHERE `upload_id` = :id '); DB::exec('UPDATE `ytkns_uploads` SET `upload_use_count` = 0'); foreach($uploadUseCounts as $uploadId => $uploadUseCount) { $updateUploadUseCount->bindValue('id', $uploadId); $updateUploadUseCount->bindValue('count', $uploadUseCount); $updateUploadUseCount->execute(); } } }