diff --git a/core/basepage.php b/core/basepage.php index 1d7ae666..6b3aaa69 100644 --- a/core/basepage.php +++ b/core/basepage.php @@ -496,6 +496,9 @@ class BasePage return ["script.js"]; } + /** + * @return array{0: NavLink[], 1: NavLink[]} + */ protected function get_nav_links(): array { $pnbe = send_event(new PageNavBuildingEvent()); @@ -641,6 +644,7 @@ class BasePage class PageNavBuildingEvent extends Event { + /** @var NavLink[] */ public array $links = []; public function add_nav_link(string $name, Link $link, string $desc, ?bool $active = null, int $order = 50): void @@ -653,6 +657,7 @@ class PageSubNavBuildingEvent extends Event { public string $parent; + /** @var NavLink[] */ public array $links = []; public function __construct(string $parent) @@ -704,6 +709,9 @@ class NavLink } } + /** + * @param string[] $pages_matched + */ public static function is_active(array $pages_matched, string $url = null): bool { /** diff --git a/core/cacheengine.php b/core/cacheengine.php index 147af159..b6e2b0b3 100644 --- a/core/cacheengine.php +++ b/core/cacheengine.php @@ -67,6 +67,11 @@ class EventTracingCache implements CacheInterface return $val; } + /** + * @param string[] $keys + * @param mixed $default + * @return mixed[] + */ public function getMultiple($keys, $default = null) { $this->tracer->begin("Cache Get Multiple", ["keys" => $keys]); @@ -75,6 +80,9 @@ class EventTracingCache implements CacheInterface return $val; } + /** + * @param array $values + */ public function setMultiple($values, $ttl = null) { $this->tracer->begin("Cache Set Multiple", ["keys" => array_keys($values)]); @@ -83,6 +91,9 @@ class EventTracingCache implements CacheInterface return $val; } + /** + * @param string[] $keys + */ public function deleteMultiple($keys) { $this->tracer->begin("Cache Delete Multiple", ["keys" => $keys]); diff --git a/core/command_builder.php b/core/command_builder.php index 4908a492..22b02742 100644 --- a/core/command_builder.php +++ b/core/command_builder.php @@ -10,7 +10,9 @@ namespace Shimmie2; class CommandBuilder { private string $executable; + /** @var string[] */ private array $args = []; + /** @var string[] */ public array $output; public function __construct(string $executable) diff --git a/core/config.php b/core/config.php index e403435a..8f930e0e 100644 --- a/core/config.php +++ b/core/config.php @@ -41,6 +41,8 @@ interface Config /** * Set a configuration option to a new value, regardless of what the value is at the moment. + * + * @param mixed[] $value */ public function set_array(string $name, array $value): void; //@} /*--------------------------------------------------------------------------------------------*/ @@ -93,6 +95,8 @@ interface Config * This has the advantage that the values will show up in the "advanced" setup * page where they can be modified, while calling get_* with a "default" * parameter won't show up. + * + * @param mixed[] $value */ public function set_default_array(string $name, array $value): void; //@} /*--------------------------------------------------------------------------------------------*/ @@ -120,6 +124,9 @@ interface Config /** * Pick a value out of the table by name, cast to the appropriate data type. + * + * @param mixed[] $default + * @return mixed[] */ public function get_array(string $name, ?array $default = []): ?array; //@} /*--------------------------------------------------------------------------------------------*/ @@ -134,6 +141,7 @@ interface Config */ abstract class BaseConfig implements Config { + /** @var array */ public array $values = []; public function set_int(string $name, ?int $value): void @@ -205,16 +213,31 @@ abstract class BaseConfig implements Config } } + /** + * @template T of int|null + * @param T $default + * @return T|int + */ public function get_int(string $name, ?int $default = null): ?int { return (int)($this->get($name, $default)); } + /** + * @template T of float|null + * @param T $default + * @return T|float + */ public function get_float(string $name, ?float $default = null): ?float { return (float)($this->get($name, $default)); } + /** + * @template T of string|null + * @param T $default + * @return T|string + */ public function get_string(string $name, ?string $default = null): ?string { $val = $this->get($name, $default); @@ -224,17 +247,34 @@ abstract class BaseConfig implements Config return $val; } + /** + * @template T of bool|null + * @param T $default + * @return T|bool + */ public function get_bool(string $name, ?bool $default = null): ?bool { return bool_escape($this->get($name, $default)); } - public function get_array(string $name, ?array $default = []): ?array + /** + * @template T of array|null + * @param T $default + * @return T|array + */ + public function get_array(string $name, ?array $default = null): ?array { - return explode(",", $this->get($name, "")); + $val = $this->get($name); + if(is_null($val)) { + return $default; + } + if(empty($val)) { + return []; + } + return explode(",", $val); } - private function get(string $name, $default = null): mixed + private function get(string $name, mixed $default = null): mixed { if (isset($this->values[$name])) { return $this->values[$name]; diff --git a/core/database.php b/core/database.php index f034752d..d9b1e60b 100644 --- a/core/database.php +++ b/core/database.php @@ -19,8 +19,12 @@ enum DatabaseDriverID: string class DatabaseException extends SCoreException { public string $query; + /** @var array */ public array $args; + /** + * @param array $args + */ public function __construct(string $msg, string $query, array $args) { parent::__construct($msg); @@ -32,6 +36,8 @@ class DatabaseException extends SCoreException /** * A class for controlled database access + * + * @phpstan-type QueryArgs array */ class Database { @@ -52,6 +58,7 @@ class Database * How many queries this DB object has run */ public int $query_count = 0; + /** @var string[] */ public array $queries = []; public function __construct(string $dsn) @@ -162,13 +169,15 @@ class Database return $this->get_engine()->get_version($this->get_db()); } + /** + * @param QueryArgs $args + */ private function count_time(string $method, float $start, string $query, ?array $args): void { global $_tracer, $tracer_enabled; $dur = ftime() - $start; // trim whitespace - $query = preg_replace('/[\n\t ]/m', ' ', $query); - $query = preg_replace('/ +/m', ' ', $query); + $query = preg_replace('/[\n\t ]+/m', ' ', $query); $query = trim($query); if ($tracer_enabled) { $_tracer->complete($start * 1000000, $dur * 1000000, "DB Query", ["query" => $query, "args" => $args, "method" => $method]); @@ -188,6 +197,9 @@ class Database $this->get_engine()->notify($this->get_db(), $channel, $data); } + /** + * @param QueryArgs $args + */ public function _execute(string $query, array $args = []): PDOStatement { try { @@ -203,6 +215,8 @@ class Database /** * Execute an SQL query with no return + * + * @param QueryArgs $args */ public function execute(string $query, array $args = []): PDOStatement { @@ -214,6 +228,9 @@ class Database /** * Execute an SQL query and return a 2D array. + * + * @param QueryArgs $args + * @return array> */ public function get_all(string $query, array $args = []): array { @@ -225,6 +242,8 @@ class Database /** * Execute an SQL query and return a iterable object for use with generators. + * + * @param QueryArgs $args */ public function get_all_iterable(string $query, array $args = []): PDOStatement { @@ -236,6 +255,9 @@ class Database /** * Execute an SQL query and return a single row. + * + * @param QueryArgs $args + * @return array */ public function get_row(string $query, array $args = []): ?array { @@ -247,6 +269,9 @@ class Database /** * Execute an SQL query and return the first column of each row. + * + * @param QueryArgs $args + * @return array */ public function get_col(string $query, array $args = []): array { @@ -258,6 +283,8 @@ class Database /** * Execute an SQL query and return the first column of each row as a single iterable object. + * + * @param QueryArgs $args */ public function get_col_iterable(string $query, array $args = []): \Generator { @@ -271,6 +298,9 @@ class Database /** * Execute an SQL query and return the the first column => the second column. + * + * @param QueryArgs $args + * @return array */ public function get_pairs(string $query, array $args = []): array { @@ -283,6 +313,8 @@ class Database /** * Execute an SQL query and return the the first column => the second column as an iterable object. + * + * @param QueryArgs $args */ public function get_pairs_iterable(string $query, array $args = []): \Generator { @@ -296,6 +328,8 @@ class Database /** * Execute an SQL query and return a single value, or null. + * + * @param QueryArgs $args */ public function get_one(string $query, array $args = []): mixed { @@ -307,6 +341,8 @@ class Database /** * Execute an SQL query and returns a bool indicating if any data was returned + * + * @param QueryArgs $args */ public function exists(string $query, array $args = []): bool { diff --git a/core/dbengine.php b/core/dbengine.php index 19502551..abd9909a 100644 --- a/core/dbengine.php +++ b/core/dbengine.php @@ -129,7 +129,7 @@ class PostgreSQL extends DBEngine } // shimmie functions for export to sqlite -function _unix_timestamp($date): int +function _unix_timestamp(string $date): int { return strtotime($date); } @@ -137,11 +137,11 @@ function _now(): string { return date("Y-m-d H:i:s"); } -function _floor($a): float +function _floor(float|int $a): float { return floor($a); } -function _log($a, $b = null): float +function _log(float $a, ?float $b = null): float { if (is_null($b)) { return log($a); @@ -149,19 +149,19 @@ function _log($a, $b = null): float return log($b, $a); } } -function _isnull($a): bool +function _isnull(mixed $a): bool { return is_null($a); } -function _md5($a): string +function _md5(string $a): string { return md5($a); } -function _concat($a, $b): string +function _concat(string $a, string $b): string { return $a . $b; } -function _lower($a): string +function _lower(string $a): string { return strtolower($a); } @@ -169,7 +169,7 @@ function _rand(): int { return rand(); } -function _ln($n): float +function _ln(float $n): float { return log($n); } diff --git a/core/event.php b/core/event.php index 93d4eeec..c66b2ac9 100644 --- a/core/event.php +++ b/core/event.php @@ -143,6 +143,9 @@ class PageRequestEvent extends Event * Many things use these functions */ + /** + * @return string[] + */ public function get_search_terms(): array { $search_terms = []; @@ -172,7 +175,6 @@ class PageRequestEvent extends Event $str = $out; // end legacy - $search_terms = Tag::explode($str); } return $search_terms; diff --git a/core/extension.php b/core/extension.php index 5f54305d..b3431846 100644 --- a/core/extension.php +++ b/core/extension.php @@ -22,9 +22,10 @@ abstract class Extension protected Themelet $theme; public ExtensionInfo $info; + /** @var string[] */ private static array $enabled_extensions = []; - public function __construct($class = null) + public function __construct(?string $class = null) { $class = $class ?? get_called_class(); $this->theme = $this->get_theme_object($class); @@ -87,6 +88,9 @@ abstract class Extension return in_array($key, self::$enabled_extensions); } + /** + * @return string[] + */ public static function get_enabled_extensions(): array { return self::$enabled_extensions; @@ -141,8 +145,11 @@ abstract class ExtensionInfo public string $name; public string $license; public string $description; + /** @var array */ public array $authors = []; + /** @var string[] */ public array $dependencies = []; + /** @var string[] */ public array $conflicts = []; public ExtensionVisibility $visibility = ExtensionVisibility::DEFAULT; public ?string $link = null; @@ -170,8 +177,11 @@ abstract class ExtensionInfo return $this->support_info; } + /** @var array */ private static array $all_info_by_key = []; + /** @var array */ private static array $all_info_by_class = []; + /** @var string[] */ private static array $core_extensions = []; protected function __construct() @@ -207,16 +217,25 @@ abstract class ExtensionInfo $this->supported = empty($this->support_info); } + /** + * @return ExtensionInfo[] + */ public static function get_all(): array { return array_values(self::$all_info_by_key); } + /** + * @return string[] + */ public static function get_all_keys(): array { return array_keys(self::$all_info_by_key); } + /** + * @return string[] + */ public static function get_core_extensions(): array { return self::$core_extensions; @@ -285,6 +304,7 @@ abstract class FormatterExtension extends Extension */ abstract class DataHandlerExtension extends Extension { + /** @var string[] */ protected array $SUPPORTED_MIME = []; public function onDataUpload(DataUploadEvent $event): void @@ -396,6 +416,9 @@ abstract class DataHandlerExtension extends Extension return MimeType::matches_array($mime, $this->SUPPORTED_MIME); } + /** + * @return string[] + */ public static function get_all_supported_mimes(): array { $arr = []; @@ -413,6 +436,9 @@ abstract class DataHandlerExtension extends Extension return $arr; } + /** + * @return string[] + */ public static function get_all_supported_exts(): array { $arr = []; diff --git a/core/imageboard/event.php b/core/imageboard/event.php index 5011dc1c..f7be1f2b 100644 --- a/core/imageboard/event.php +++ b/core/imageboard/event.php @@ -63,7 +63,9 @@ class ImageReplaceEvent extends Event ) { parent::__construct(); $this->old_hash = $image->hash; - $this->new_hash = md5_file($tmp_filename); + $hash = md5_file($tmp_filename); + assert($hash !== false, "Failed to hash file $tmp_filename"); + $this->new_hash = $hash; } } diff --git a/core/imageboard/image.php b/core/imageboard/image.php index 92c5afec..0f2415e2 100644 --- a/core/imageboard/image.php +++ b/core/imageboard/image.php @@ -23,6 +23,8 @@ enum ImagePropType * As of 2.2, this no longer necessarily represents an * image per se, but could be a video, sound file, or any * other supported upload type. + * + * @implements \ArrayAccess */ #[Type(name: "Post")] class Image implements \ArrayAccess @@ -71,6 +73,8 @@ class Image implements \ArrayAccess /** * One will very rarely construct an image directly, more common * would be to use Image::by_id, Image::by_hash, etc. + * + * @param array|null $row */ public function __construct(?array $row = null) { @@ -82,6 +86,7 @@ class Image implements \ArrayAccess continue; } elseif(property_exists($this, $name)) { $t = (new \ReflectionProperty($this, $name))->getType(); + assert(!is_null($t)); if(is_a($t, \ReflectionNamedType::class)) { $this->$name = match($t->getName()) { "int" => is_null($value) ? $value : int_escape((string)$value), @@ -116,10 +121,12 @@ class Image implements \ArrayAccess public function offsetExists(mixed $offset): bool { + assert(is_string($offset)); return array_key_exists($offset, static::$prop_types); } public function offsetGet(mixed $offset): mixed { + assert(is_string($offset)); if(!$this->offsetExists($offset)) { throw new \OutOfBoundsException("Undefined dynamic property: $offset"); } @@ -127,16 +134,19 @@ class Image implements \ArrayAccess } public function offsetSet(mixed $offset, mixed $value): void { + assert(is_string($offset)); $this->dynamic_props[$offset] = $value; } public function offsetUnset(mixed $offset): void { + assert(is_string($offset)); unset($this->dynamic_props[$offset]); } #[Field(name: "post_id")] public function graphql_oid(): int { + assert(!is_null($this->id)); return $this->id; } #[Field(name: "id")] @@ -170,6 +180,9 @@ class Image implements \ArrayAccess return (is_numberish($id) && strlen($id) != 32) ? Image::by_id((int)$id) : Image::by_hash($id); } + /** + * @param string[] $tags + */ public static function by_random(array $tags = [], int $limit_range = 0): ?Image { $max = Search::count_images($tags); @@ -234,7 +247,9 @@ class Image implements \ArrayAccess #[Field(name: "owner")] public function get_owner(): User { - return User::by_id($this->owner_id); + $user = User::by_id($this->owner_id); + assert(!is_null($user)); + return $user; } /** @@ -457,7 +472,7 @@ class Image implements \ArrayAccess * Get the image's mime type. */ #[Field(name: "mime")] - public function get_mime(): ?string + public function get_mime(): string { if ($this->mime === MimeType::WEBP && $this->lossless) { return MimeType::WEBP_LOSSLESS; @@ -468,7 +483,7 @@ class Image implements \ArrayAccess /** * Set the image's mime type. */ - public function set_mime($mime): void + public function set_mime(string $mime): void { $this->mime = $mime; $ext = FileExtension::get_for_mime($this->get_mime()); @@ -545,6 +560,8 @@ class Image implements \ArrayAccess /** * Set the tags for this image. + * + * @param string[] $unfiltered_tags */ public function set_tags(array $unfiltered_tags): void { diff --git a/core/imageboard/misc.php b/core/imageboard/misc.php index 402c9093..63ea0706 100644 --- a/core/imageboard/misc.php +++ b/core/imageboard/misc.php @@ -12,9 +12,10 @@ namespace Shimmie2; * Add a directory full of images * * @param string $base + * @param string[] $extra_tags * @return UploadResult[] */ -function add_dir(string $base, ?array $extra_tags = []): array +function add_dir(string $base, array $extra_tags = []): array { global $database; $results = []; @@ -33,6 +34,7 @@ function add_dir(string $base, ?array $extra_tags = []): array ])); $results = []; foreach($dae->images as $image) { + assert(!is_null($image->id)); $results[] = new UploadSuccess($filename, $image->id); } return $results; @@ -59,7 +61,7 @@ function get_file_ext(string $filename): ?string * @param int $orig_width * @param int $orig_height * @param bool $use_dpi_scaling Enables the High-DPI scaling. - * @return array + * @return array{0: int, 1: int} */ function get_thumbnail_size(int $orig_width, int $orig_height, bool $use_dpi_scaling = false): array { @@ -107,6 +109,9 @@ function get_thumbnail_size(int $orig_width, int $orig_height, bool $use_dpi_sca } } +/** + * @return array{0: int, 1: int, 2: float} + */ function get_scaled_by_aspect_ratio(int $original_width, int $original_height, int $max_width, int $max_height): array { $xscale = ($max_width / $original_width); @@ -120,7 +125,7 @@ function get_scaled_by_aspect_ratio(int $original_width, int $original_height, i /** * Fetches the thumbnails height and width settings and applies the High-DPI scaling setting before returning the dimensions. * - * @return array [width, height] + * @return array{0: int, 1: int} */ function get_thumbnail_max_size_scaled(): array { @@ -147,7 +152,9 @@ function create_image_thumb(Image $image, string $engine = null): void } - +/** + * @param array{0: int, 1: int} $tsize + */ function create_scaled_image( string $inname, string $outname, diff --git a/core/imageboard/tag.php b/core/imageboard/tag.php index 9d7de0c4..d5eddd6b 100644 --- a/core/imageboard/tag.php +++ b/core/imageboard/tag.php @@ -86,7 +86,8 @@ class TagUsage */ class Tag { - private static $tag_id_cache = []; + /** @var array */ + private static array $tag_id_cache = []; public static function get_or_create_id(string $tag): int { @@ -196,9 +197,13 @@ class Tag public static function sanitize(string $tag): string { $tag = preg_replace("/\s/", "", $tag); # whitespace + assert($tag !== null); $tag = preg_replace('/\x20[\x0e\x0f]/', '', $tag); # unicode RTL + assert($tag !== null); $tag = preg_replace("/\.+/", ".", $tag); # strings of dots? + assert($tag !== null); $tag = preg_replace("/^(\.+[\/\\\\])+/", "", $tag); # trailing slashes? + assert($tag !== null); $tag = trim($tag, ", \t\n\r\0\x0B"); if ($tag == ".") { @@ -211,6 +216,10 @@ class Tag return $tag; } + /** + * @param string[] $tags1 + * @param string[] $tags2 + */ public static function compare(array $tags1, array $tags2): bool { if (count($tags1) !== count($tags2)) { @@ -225,6 +234,11 @@ class Tag return $tags1 == $tags2; } + /** + * @param string[] $source + * @param string[] $remove + * @return string[] + */ public static function get_diff_tags(array $source, array $remove): array { $before = array_map('strtolower', $source); @@ -238,6 +252,10 @@ class Tag return $after; } + /** + * @param string[] $tags + * @return string[] + */ public static function sanitize_array(array $tags): array { global $page; diff --git a/core/microhtml.php b/core/microhtml.php index f3eb3681..33fb375c 100644 --- a/core/microhtml.php +++ b/core/microhtml.php @@ -46,13 +46,19 @@ function SHM_FORM(string $target, string $method = "POST", bool $multipart = fal ); } -function SHM_SIMPLE_FORM($target, ...$children): HTMLElement +/** + * @param array $children + */ +function SHM_SIMPLE_FORM(string $target, ...$children): HTMLElement { $form = SHM_FORM($target); $form->appendChild(emptyHTML(...$children)); return $form; } +/** + * @param array $args + */ function SHM_SUBMIT(string $text, array $args = []): HTMLElement { $args["type"] = "submit"; @@ -60,6 +66,9 @@ function SHM_SUBMIT(string $text, array $args = []): HTMLElement return INPUT($args); } +/** + * @param array $args + */ function SHM_A(string $href, string|HTMLElement $text, string $id = "", string $class = "", array $args = []): HTMLElement { $args["href"] = make_link($href); @@ -83,7 +92,7 @@ function SHM_COMMAND_EXAMPLE(string $ex, string $desc): HTMLElement ); } -function SHM_USER_FORM(User $duser, string $target, string $title, $body, $foot): HTMLElement +function SHM_USER_FORM(User $duser, string $target, string $title, HTMLElement $body, HTMLElement|string $foot): HTMLElement { if (is_string($foot)) { $foot = TFOOT(TR(TD(["colspan" => "2"], INPUT(["type" => "submit", "value" => $foot])))); @@ -106,12 +115,12 @@ function SHM_USER_FORM(User $duser, string $target, string $title, $body, $foot) * Generates a . - * @param array $options An array of pairs of parameters for