From 1ca1598c34179a6408f4e049fb9ea107589d8c04 Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sat, 10 Jun 2023 23:34:36 +0200 Subject: [PATCH 01/11] Implement backend for choosing sort direction and criteria --- public/index.php | 85 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/public/index.php b/public/index.php index b1162ec..fa3016d 100644 --- a/public/index.php +++ b/public/index.php @@ -15,6 +15,17 @@ function getWithVerifiedType(array $data, string $key) : mixed { throw ValueError('"' . $key . '" key not available in the array.'); } +function getDateTime(array $data, string $key) : DateTimeImmutable { + if (isset($data[$key])) { + $date = DateTimeImmutable::createFromFormat(DateTimeInterface::ISO8601, $data[$key]); + if ($date === $false) { + throw ValueError('"' . $key . '" key is not a valid ISO8601 datetime.'); + } + return $date; + } + throw ValueError('"' . $key . '" key not available in the array.'); +} + function getString(array $data, string $key, string $default = '') : string { if (isset($data[$key]) && is_string($data[$key])) { return $data[$key]; @@ -45,6 +56,58 @@ function getArrayOfStrings(array $data, string $key) : array { return $output; } +function datetimecmp($a, $b) { + if ($a === $b) { + return 0; + } + return $a < $b ? -1 : 1; +} + + +enum SortDirection : string +{ + case Asc = 'asc'; + case Desc = 'desc'; +} + + +enum SortCriteria : string +{ + case Names = 'names'; + case AdditionTime = 'addition_time'; + case UpdateTime = 'update_time'; + + public function getDefaultSortDirection() : SortDirection { + return match ($this) { + static::Names => SortDirection::Asc, + static::AdditionTime, static::UpdateTime => SortDirection::Desc, + }; + } + + public function getSortFunction(SortDirection $direction) : mixed { + switch ($this) { + case static::Names: + if ($direction === SortDirection::Asc) { + return fn($a, $b) => strnatcasecmp($a->name, $b->name); + } + return fn($a, $b) => -strnatcasecmp($a->name, $b->name); + break; + case static::AdditionTime: + if ($direction === SortDirection::Asc) { + return fn($a, $b) => datetimecmp($a->added_at, $b->added_at); + } + return fn($a, $b) => -datetimecmp($a->added_at, $b->added_at); + break; + case static::UpdateTime: + if ($direction === SortDirection::Asc) { + return fn($a, $b) => datetimecmp($a->last_updated_at, $b->last_updated_at); + } + return fn($a, $b) => -datetimecmp($a->last_updated_at, $b->last_updated_at); + break; + } + } +} + enum RepoCategory : string { @@ -101,6 +164,8 @@ public function __construct( /** Cog name. */ public string $name, public Repo $repo, + public DateTimeImmutable $added_at, + public DateTimeImmutable $last_updated_at, // Optional cog information. public InstallableType $type = InstallableType::Cog, public array $author = [], @@ -157,6 +222,8 @@ public static function fromArray(Repo $repo, string $name, array $data): static name: $name, repo: $repo, type: $type, + added_at: getDateTime($data, 'rx_added_at'), + last_updated_at: getDateTime($data, 'rx_last_updated_at'), author: getArrayOfStrings($data, 'author'), short: getString($data, 'short'), description: getString($data, 'description'), @@ -182,6 +249,8 @@ function getURL( ?int $page = null, ?string $filter_tag = null, ?string $search = null, + ?SortCriteria $sort_by = null, + ?SortDirection $sort_direction = null, ) : string { $params = []; $show_ua ??= $GLOBALS['show_ua']; @@ -200,6 +269,18 @@ function getURL( if ($search) { $params['search'] = $search; } + $previous_sort_by = $GLOBALS['sort_by']; + $sort_by ??= $previous_sort_by; + if ($sort_by !== SortCriteria::Names) { + $params['sort_by'] = $sort_by->value; + } + if ($sort_direction === null && $previous_sort_by !== $sort_by) { + $sort_direction = $sort_by->getDefaultSortDirection(); + } + $sort_direction ??= $GLOBALS['sort_direction']; + if ($sort_direction !== $sort_by->getDefaultSortDirection()) { + $params['sort_direction'] = $sort_direction->value; + } return "/?" . http_build_query($params); } @@ -209,6 +290,8 @@ function getURL( $search = preg_replace('/[^-a-zA-Z0-9 ]/', '', getString($_GET, 'search')); $filter_tag = strtolower(preg_replace('/[^-a-zA-Z0-9_]/', '', getString($_GET, 'filter_tag'))); $page = intval(preg_replace('/[^0-9]/', '', getString($_GET, 'p'))) ?: 1; +$sort_by = SortCriteria::tryFrom(getString($_GET, 'sort_by') ?: '') ?? SortCriteria::Names; +$sort_direction = SortDirection::tryFrom(getString($_GET, 'sort_direction') ?: '') ?? $sort_by->getDefaultSortDirection(); $json = json_decode(implode(" ", file('https://raw.githubusercontent.com/Cog-Creators/Red-Index/master/index/1-min.json')), TRUE); $cogs = []; @@ -261,7 +344,7 @@ function getURL( } } -usort($cogs, fn($a, $b) => strnatcasecmp($a->name, $b->name)); +usort($cogs, $sort_by->getSortFunction($sort_direction)); $cog_chunks = array_chunk($cogs, $per_page); ?> From 5929dcabe5d41a585b911189b61d8feba0ab92a1 Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sun, 11 Jun 2023 00:11:03 +0200 Subject: [PATCH 02/11] Fallback to sorting by name if dates are equivalent --- public/index.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/public/index.php b/public/index.php index fa3016d..18a629c 100644 --- a/public/index.php +++ b/public/index.php @@ -94,15 +94,23 @@ public function getSortFunction(SortDirection $direction) : mixed { break; case static::AdditionTime: if ($direction === SortDirection::Asc) { - return fn($a, $b) => datetimecmp($a->added_at, $b->added_at); + return fn($a, $b) => + datetimecmp($a->added_at, $b->added_at) + || strnatcasecmp($a->name, $b->name); } - return fn($a, $b) => -datetimecmp($a->added_at, $b->added_at); + return fn($a, $b) => + -datetimecmp($a->added_at, $b->added_at) + || -strnatcasecmp($a->name, $b->name); break; case static::UpdateTime: if ($direction === SortDirection::Asc) { - return fn($a, $b) => datetimecmp($a->last_updated_at, $b->last_updated_at); + return fn($a, $b) => + datetimecmp($a->last_updated_at, $b->last_updated_at) + || strnatcasecmp($a->name, $b->name); } - return fn($a, $b) => -datetimecmp($a->last_updated_at, $b->last_updated_at); + return fn($a, $b) => + -datetimecmp($a->last_updated_at, $b->last_updated_at) + || -strnatcasecmp($a->name, $b->name); break; } } From 8e231a24a63cc190387a85f9cecd6e203866784a Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sun, 11 Jun 2023 00:43:15 +0200 Subject: [PATCH 03/11] Prefer approval date over cog's own dates if newer --- public/index.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/public/index.php b/public/index.php index 18a629c..e9b204f 100644 --- a/public/index.php +++ b/public/index.php @@ -26,6 +26,13 @@ function getDateTime(array $data, string $key) : DateTimeImmutable { throw ValueError('"' . $key . '" key not available in the array.'); } +function getNullableDateTime(array $data, string $key) : ?DateTimeImmutable { + if (array_key_exists($key, $data) && is_null($data[$key])) { + return null; + } + return getDateTime($data, $key); +} + function getString(array $data, string $key, string $default = '') : string { if (isset($data[$key]) && is_string($data[$key])) { return $data[$key]; @@ -138,6 +145,8 @@ public function __construct( public string $url, public string $name, public RepoCategory $category, + public DateTimeImmutable $added_at, + public ?DateTimeImmutable $approved_at, public string $branch = '', public array $cogs = [], ) {} @@ -149,6 +158,8 @@ public static function fromArray(string $url, array $data): static { url: $url, name: getOrThrow($data, 'name'), category: $category, + added_at: getDateTime($data, 'rx_added_at'), + approved_at: getOptionalDateTime($data, 'rx_approved_at'), branch: $data['rx_branch'] ?? '', ); } catch (TypeError) { @@ -230,8 +241,8 @@ public static function fromArray(Repo $repo, string $name, array $data): static name: $name, repo: $repo, type: $type, - added_at: getDateTime($data, 'rx_added_at'), - last_updated_at: getDateTime($data, 'rx_last_updated_at'), + added_at: max(getDateTime($data, 'rx_added_at'), $repo->approved_at), + last_updated_at: max(getDateTime($data, 'rx_last_updated_at'), $repo->approved_at), author: getArrayOfStrings($data, 'author'), short: getString($data, 'short'), description: getString($data, 'description'), From 6b0e2efcdab56c3c78c9aba5b1c44d8dee82b5bc Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sun, 11 Jun 2023 01:06:30 +0200 Subject: [PATCH 04/11] $false->false --- public/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.php b/public/index.php index 7ba0ade..bf0dc23 100644 --- a/public/index.php +++ b/public/index.php @@ -18,7 +18,7 @@ function getWithVerifiedType(array $data, string $key) : mixed { function getDateTime(array $data, string $key) : DateTimeImmutable { if (isset($data[$key])) { $date = DateTimeImmutable::createFromFormat(DateTimeInterface::ISO8601, $data[$key]); - if ($date === $false) { + if ($date === false) { throw ValueError('"' . $key . '" key is not a valid ISO8601 datetime.'); } return $date; From 4da048368328d77c1e1af1f9edeb3791406dd09b Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sun, 11 Jun 2023 01:12:56 +0200 Subject: [PATCH 05/11] ISO8601 is obviously not compatible with ISO 8601, use ATOM instead --- public/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.php b/public/index.php index 45537ca..ddde849 100644 --- a/public/index.php +++ b/public/index.php @@ -17,7 +17,7 @@ function getWithVerifiedType(array $data, string $key) : mixed { function getDateTime(array $data, string $key) : DateTimeImmutable { if (isset($data[$key])) { - $date = DateTimeImmutable::createFromFormat(DateTimeInterface::ISO8601, $data[$key]); + $date = DateTimeImmutable::createFromFormat(DateTimeInterface::ATOM, $data[$key]); if ($date === false) { throw new ValueError('"' . $key . '" key is not a valid ISO8601 datetime.'); } From e3b65a4ff04fee176c6546d4edb3bf5c4561cc92 Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sun, 11 Jun 2023 01:24:34 +0200 Subject: [PATCH 06/11] ATOM is not compatible with ISO 8601 either, we want RFC3339_EXTENDED --- public/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.php b/public/index.php index ddde849..01eee2e 100644 --- a/public/index.php +++ b/public/index.php @@ -17,7 +17,7 @@ function getWithVerifiedType(array $data, string $key) : mixed { function getDateTime(array $data, string $key) : DateTimeImmutable { if (isset($data[$key])) { - $date = DateTimeImmutable::createFromFormat(DateTimeInterface::ATOM, $data[$key]); + $date = DateTimeImmutable::createFromFormat(DateTimeInterface::RFC3339_EXTENDED, $data[$key]); if ($date === false) { throw new ValueError('"' . $key . '" key is not a valid ISO8601 datetime.'); } From 170364500d02bb6da719c5e5ae0572b512138386 Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sun, 11 Jun 2023 01:25:22 +0200 Subject: [PATCH 07/11] RFC3339_EXTENDED is not extended enough, let's just use our own format --- public/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.php b/public/index.php index 01eee2e..b2077c6 100644 --- a/public/index.php +++ b/public/index.php @@ -17,7 +17,7 @@ function getWithVerifiedType(array $data, string $key) : mixed { function getDateTime(array $data, string $key) : DateTimeImmutable { if (isset($data[$key])) { - $date = DateTimeImmutable::createFromFormat(DateTimeInterface::RFC3339_EXTENDED, $data[$key]); + $date = DateTimeImmutable::createFromFormat("Y-m-d\\TH:i:s.uP", $data[$key]); if ($date === false) { throw new ValueError('"' . $key . '" key is not a valid ISO8601 datetime.'); } From 38498bdb9c803ae94aeb0a26e638939d605aa333 Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sun, 11 Jun 2023 01:25:43 +0200 Subject: [PATCH 08/11] Fix function name --- public/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.php b/public/index.php index b2077c6..834b777 100644 --- a/public/index.php +++ b/public/index.php @@ -159,7 +159,7 @@ public static function fromArray(string $url, array $data): static { name: getOrThrow($data, 'name'), category: $category, added_at: getDateTime($data, 'rx_added_at'), - approved_at: getOptionalDateTime($data, 'rx_approved_at'), + approved_at: getNullableDateTime($data, 'rx_approved_at'), branch: $data['rx_branch'] ?? '', ); } catch (TypeError) { From b287b48f76dcefd0ce538818ebfc8a62ad7a3127 Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sun, 11 Jun 2023 02:10:20 +0200 Subject: [PATCH 09/11] This isn't Python, logical operators return booleans, use Elvis instead --- public/index.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/index.php b/public/index.php index 834b777..7d13d6f 100644 --- a/public/index.php +++ b/public/index.php @@ -103,21 +103,21 @@ public function getSortFunction(SortDirection $direction) : mixed { if ($direction === SortDirection::Asc) { return fn($a, $b) => datetimecmp($a->added_at, $b->added_at) - || strnatcasecmp($a->name, $b->name); + ?: strnatcasecmp($a->name, $b->name); } return fn($a, $b) => -datetimecmp($a->added_at, $b->added_at) - || -strnatcasecmp($a->name, $b->name); + ?: -strnatcasecmp($a->name, $b->name); break; case static::UpdateTime: if ($direction === SortDirection::Asc) { return fn($a, $b) => datetimecmp($a->last_updated_at, $b->last_updated_at) - || strnatcasecmp($a->name, $b->name); + ?: strnatcasecmp($a->name, $b->name); } return fn($a, $b) => -datetimecmp($a->last_updated_at, $b->last_updated_at) - || -strnatcasecmp($a->name, $b->name); + ?: -strnatcasecmp($a->name, $b->name); break; } } From d34836a77abf95cbe01f31cd4326915b4e534257 Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sun, 11 Jun 2023 02:08:46 +0200 Subject: [PATCH 10/11] Add __debugInfo without cogs list to Repo --- public/index.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/public/index.php b/public/index.php index 7d13d6f..8759fb9 100644 --- a/public/index.php +++ b/public/index.php @@ -170,6 +170,17 @@ public static function fromArray(string $url, array $data): static { } return $repo; } + + public function __debugInfo() { + return [ + 'url' => $this->url, + 'name' => $this->name, + 'category' => $this->category, + 'added_at' => $this->added_at, + 'approved_at' => $this->approved_at, + 'branch' => $this->branch, + ]; + } } class Cog From 6a6fe9087034dd171a61fa7f6233a83f4503466a Mon Sep 17 00:00:00 2001 From: Jakub Kuczys Date: Sun, 11 Jun 2023 02:30:36 +0200 Subject: [PATCH 11/11] Fix the equality comparison in datetimecmp --- public/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.php b/public/index.php index 8759fb9..c2c1350 100644 --- a/public/index.php +++ b/public/index.php @@ -64,7 +64,7 @@ function getArrayOfStrings(array $data, string $key) : array { } function datetimecmp($a, $b) { - if ($a === $b) { + if ($a == $b) { return 0; } return $a < $b ? -1 : 1;