diff --git a/core/imageboard/image.php b/core/imageboard/image.php index f2c1f1a1..f3c0ebba 100644 --- a/core/imageboard/image.php +++ b/core/imageboard/image.php @@ -15,6 +15,26 @@ enum ImagePropType case STRING; } +function pop_val(array &$row, string $name): mixed +{ + $value = $row[$name]; + unset($row[$name]); + if(is_null($value)) { + return null; + } + return $value; +} +function pop_int(array &$row, string $name): ?int +{ + $val = pop_val($row, $name); + return (is_null($val)) ? null : (int)$val; +} +function pop_bool(array &$row, string $name): ?bool +{ + $val = pop_val($row, $name); + return (is_null($val)) ? null : bool_escape($val); +} + /** * Class Image * @@ -64,20 +84,7 @@ class Image implements \ArrayAccess public ?string $tmp_file = null; /** @var array */ - public static array $prop_types = [ - "id" => ImagePropType::INT, - "owner_id" => ImagePropType::INT, - "locked" => ImagePropType::BOOL, - "lossless" => ImagePropType::BOOL, - "video" => ImagePropType::BOOL, - "video_codec" => ImagePropType::STRING, - "image" => ImagePropType::BOOL, - "audio" => ImagePropType::BOOL, - "height" => ImagePropType::INT, - "width" => ImagePropType::INT, - "filesize" => ImagePropType::INT, - "length" => ImagePropType::INT, - ]; + public static array $prop_types = []; /** @var array */ private array $dynamic_props = []; @@ -88,30 +95,54 @@ class Image implements \ArrayAccess public function __construct(?array $row = null) { if (!is_null($row)) { + $this->id = pop_int($row, 'id'); + $this->hash = pop_val($row, 'hash'); + $this->filename = pop_val($row, 'filename'); + $this->filesize = pop_int($row, 'filesize'); + $this->mime = pop_val($row, 'mime'); + $this->ext = pop_val($row, 'ext'); + $this->posted = pop_val($row, 'posted'); + $this->source = pop_val($row, 'source'); + $this->owner_id = pop_int($row, 'owner_id'); + $this->owner_ip = pop_val($row, 'owner_ip'); + $this->locked = pop_bool($row, 'locked'); + $this->lossless = pop_bool($row, 'lossless'); + $this->video = pop_bool($row, 'video'); + $this->video_codec = pop_val($row, 'video_codec'); + $this->image = pop_bool($row, 'image'); + $this->audio = pop_bool($row, 'audio'); + $this->height = pop_int($row, 'height'); + $this->width = pop_int($row, 'width'); + $this->length = pop_int($row, 'length'); + + // Any remaining fields are dynamic properties foreach ($row as $name => $value) { if (is_numeric($name)) { continue; } + if(property_exists($this, $name)) { + throw new \Exception("Property $name already exists on Image"); + } - // some databases use table.name rather than name - $name = str_replace("images.", "", $name); - - if (is_null($value)) { - $value = null; - } else { - if(array_key_exists($name, static::$prop_types)) { + if(array_key_exists($name, static::$prop_types)) { + if (is_null($value)) { + $value = null; + } else { $value = match(static::$prop_types[$name]) { ImagePropType::BOOL => bool_escape((string)$value), ImagePropType::INT => int_escape((string)$value), ImagePropType::STRING => (string)$value, }; } - } - - if(property_exists($this, $name)) { - $this->$name = $value; - } else { $this->dynamic_props[$name] = $value; + } else { + // Database table has a column we don't know about, + // it isn't static and it isn't a known prop_type - + // maybe from an old extension that has since been + // disabled? Just ignore it. + if(UNITTEST) { + throw new \Exception("Unknown column $name in images table"); + } } } } @@ -119,10 +150,13 @@ class Image implements \ArrayAccess public function offsetExists(mixed $offset): bool { - return isset($this->dynamic_props[$offset]); + return array_key_exists($offset, $this->dynamic_props); } public function offsetGet(mixed $offset): mixed { + if(!$this->offsetExists($offset)) { + throw new \OutOfBoundsException("Undefined dynamic property: $offset"); + } return $this->dynamic_props[$offset]; } public function offsetSet(mixed $offset, mixed $value): void diff --git a/ext/approval/main.php b/ext/approval/main.php index 6fe93007..d3725580 100644 --- a/ext/approval/main.php +++ b/ext/approval/main.php @@ -24,6 +24,7 @@ class Approval extends Extension $config->set_default_bool(ApprovalConfig::COMMENTS, false); Image::$prop_types["approved"] = ImagePropType::BOOL; + Image::$prop_types["approved_by_id"] = ImagePropType::INT; } public function onImageAddition(ImageAdditionEvent $event): void diff --git a/ext/artists/main.php b/ext/artists/main.php index 9d401eaa..807abcfe 100644 --- a/ext/artists/main.php +++ b/ext/artists/main.php @@ -24,6 +24,11 @@ class Artists extends Extension /** @var ArtistsTheme */ protected Themelet $theme; + public function onInitExt(InitExtEvent $event): void + { + Image::$prop_types["author"] = ImagePropType::STRING; + } + public function onImageInfoSet(ImageInfoSetEvent $event): void { global $user; diff --git a/ext/favorites/main.php b/ext/favorites/main.php index c8b5ce19..171f7108 100644 --- a/ext/favorites/main.php +++ b/ext/favorites/main.php @@ -27,6 +27,11 @@ class Favorites extends Extension /** @var FavoritesTheme */ protected Themelet $theme; + public function onInitExt(InitExtEvent $event): void + { + Image::$prop_types["favorites"] = ImagePropType::INT; + } + public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event): void { global $database, $user; diff --git a/ext/notes/main.php b/ext/notes/main.php index 84a85173..5b174674 100644 --- a/ext/notes/main.php +++ b/ext/notes/main.php @@ -9,6 +9,11 @@ class Notes extends Extension /** @var NotesTheme */ protected Themelet $theme; + public function onInitExt(InitExtEvent $event): void + { + Image::$prop_types["notes"] = ImagePropType::INT; + } + public function onDatabaseUpgrade(DatabaseUpgradeEvent $event): void { global $config, $database; diff --git a/ext/numeric_score/main.php b/ext/numeric_score/main.php index 351d6d2d..ac125946 100644 --- a/ext/numeric_score/main.php +++ b/ext/numeric_score/main.php @@ -104,6 +104,11 @@ class NumericScore extends Extension /** @var NumericScoreTheme */ protected Themelet $theme; + public function onInitExt(InitExtEvent $event): void + { + Image::$prop_types["numeric_score"] = ImagePropType::INT; + } + public function onDisplayingImage(DisplayingImageEvent $event): void { global $user; diff --git a/ext/post_titles/main.php b/ext/post_titles/main.php index f2c8cf5f..7f3050bd 100644 --- a/ext/post_titles/main.php +++ b/ext/post_titles/main.php @@ -23,6 +23,7 @@ class PostTitles extends Extension $config->set_default_bool(PostTitlesConfig::DEFAULT_TO_FILENAME, false); $config->set_default_bool(PostTitlesConfig::SHOW_IN_WINDOW_TITLE, false); + Image::$prop_types["title"] = ImagePropType::STRING; } public function onDatabaseUpgrade(DatabaseUpgradeEvent $event): void diff --git a/ext/rating/main.php b/ext/rating/main.php index 99c9ebe5..59fa4393 100644 --- a/ext/rating/main.php +++ b/ext/rating/main.php @@ -97,6 +97,8 @@ class Ratings extends Extension } $config->set_default_array("ext_rating_" . $key . "_privs", array_keys($_shm_ratings)); } + + Image::$prop_types["rating"] = ImagePropType::STRING; } private function check_permissions(Image $image): bool diff --git a/tests/phpstan.neon b/tests/phpstan.neon index a8e1f56e..ad3d6fa0 100644 --- a/tests/phpstan.neon +++ b/tests/phpstan.neon @@ -14,3 +14,4 @@ parameters: - BASE_HREF - STATSD_HOST - TRACE_FILE + - UNITTEST