Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.51% covered (success)
95.51%
85 / 89
72.73% covered (warning)
72.73%
8 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
TsumegoFilters
95.35% covered (success)
95.35%
82 / 86
72.73% covered (warning)
72.73%
8 / 11
38
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
7
 empty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 processItem
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
7
 getSetTitle
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
5.03
 getSetID
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
5.03
 setQuery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 filterRanks
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 filterTags
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 filterSets
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
4.37
 addConditionsToQuery
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 calculateCount
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3App::uses('Preferences', 'Utility');
4App::uses('Query', 'Utility');
5App::uses('Rating', 'Utility');
6
7class TsumegoFilters
8{
9    public function __construct(?string $newQuery = null, bool $empty = false)
10    {
11        if ($empty)
12            return;
13        if ($newQuery == 'published')
14        {
15            $this->query = $newQuery;
16            return;
17        }
18
19        $this->query = self::processItem('query', 'topics', null, $newQuery);
20        $this->collectionSize = (int) self::processItem('collection_size', '200');
21        $rawSets = self::processItem('filtered_sets', [], function ($input) { return array_values(array_filter(explode('@', $input))); });
22
23        foreach ($rawSets as $set)
24        {
25            $found = ClassRegistry::init('Set')->find('first', ['conditions' => ['title' => $set, 'public' => 1]]);
26            if ($found)
27            {
28                $this->sets[] = $set;
29                $this->setIDs[] = $found['Set']['id'];
30            }
31        }
32
33        $this->ranks = self::processItem('filtered_ranks', [], function ($input) {
34            return array_values(array_filter(explode('@', $input), fn($rank) => Rating::isValidReadableRank($rank)));
35        });
36        $rawTags = self::processItem('filtered_tags', [], function ($input) { return array_values(array_filter(explode('@', $input))); });
37
38        foreach ($rawTags as $tag)
39        {
40            $found = ClassRegistry::init('Tag')->findByName($tag);
41            if ($found)
42            {
43                $this->tags[] = $tag;
44                $this->tagIDs[] = $found['Tag']['id'];
45            }
46        }
47    }
48
49    public static function empty()
50    {
51        return new TsumegoFilters(null, true);
52    }
53
54    /**
55     * Process a preference item with optional transformation and new value override.
56     *
57     * @param string $name The preference key
58     * @param mixed $default Default value if not set
59     * @param callable|null $processToResult Optional callback to transform the stored string value
60     * @param string|null $newValue Optional new value to set
61     * @return mixed The processed value
62     */
63    private static function processItem(string $name, mixed $default, $processToResult = null, ?string $newValue = null)
64    {
65        // Get current value from Preferences (handles both logged-in and guest storage)
66        $stringResult = Preferences::get($name, '');
67
68        // Check for cookie override (used for filter links like /topics?filtered_sets=SetName)
69        if (!empty($_COOKIE[$name]))
70        {
71            $stringResult = $_COOKIE[$name];
72            if ($stringResult == 'clear')
73            {
74                Util::clearCookie($name);
75                $stringResult = '';
76            }
77        }
78
79        // Apply new value if provided
80        if ($newValue)
81        {
82            $stringResult = $newValue;
83            // Clear the cookie override so subsequent requests
84            // read from preferences instead of stale cookie value.
85            if (!empty($_COOKIE[$name]))
86                Util::clearCookie($name);
87        }
88
89        // Save back to preferences if value changed or was overridden
90        Preferences::set($name, $stringResult);
91
92        // Return default if empty
93        if (!$stringResult)
94            return $default;
95
96        // Apply transformation if provided
97        return $processToResult ? $processToResult($stringResult) : $stringResult;
98    }
99
100    public function getSetTitle($set): string
101    {
102        if ($this->query == 'topics')
103            return $set['Set']['title'];
104        if ($this->query == 'difficulty')
105            return $_COOKIE['lastSet'] ?? 'Tsumego';
106        if ($this->query == 'tags')
107            return $_COOKIE['lastSet'] ?? 'Tsumego';
108
109        if ($this->query == 'favorites')
110            return 'Favorites';
111        throw new Exception('Unknown query: ""' . $this->query);
112    }
113
114    public function getSetID($set): string
115    {
116        if ($this->query == 'topics')
117            return $set['Set']['id'];
118        if ($this->query == 'difficulty')
119            return $_COOKIE['lastSet'] ?? 'favorites';
120        if ($this->query == 'tags')
121            return $_COOKIE['lastSet'] ?? 'favorites';
122
123        if ($this->query == 'favorites')
124            return 'favorites';
125        return "Unsupported yet";
126    }
127
128    public function setQuery($query)
129    {
130        $this->query = self::processItem('query', 'topics', null, $query);
131    }
132
133    public function filterRanks(Query $query): void
134    {
135        if (empty($this->ranks))
136            return;
137
138        $rankConditions = '';
139        foreach ($this->ranks as $rankFilter)
140        {
141            $rankCondition = '';
142            RatingBounds::coverRank($rankFilter, '15k')->addSqlConditions($rankCondition);
143            Util::addSqlOrCondition($rankConditions, $rankCondition);
144        }
145        $query->conditions[] = $rankConditions;
146    }
147
148    public function filterTags(Query $query): void
149    {
150        if (empty($this->tagIDs))
151            return;
152        if (!str_contains($query->query, 'JOIN tag_connection'))
153            $query->query .= ' JOIN tag_connection ON tag_connection.tsumego_id = tsumego.id';
154        $query->conditions[] = 'tag_connection.tag_id IN (' . implode(',', $this->tagIDs) . ')';
155    }
156
157    public function filterSets(Query $query): void
158    {
159        if (empty($this->setIDs))
160            return;
161        if (!str_contains($query->query, 'JOIN set_connection'))
162            $query->query .= ' JOIN set_connection ON set_connection.tsumego_id = tsumego.id';
163        if (!str_contains($query->query, 'JOIN `set`'))
164            $query->query .= ' JOIN `set` ON `set`.id = set_connection.set_id';
165        $query->conditions[] = '`set`.id IN (' . implode(',', $this->setIDs) . ')';
166    }
167
168    public function addConditionsToQuery(Query $query): void
169    {
170        $query->query .= ' JOIN set_connection on set_connection.tsumego_id = tsumego.id';
171        $query->query .= ' JOIN `set` on `set`.id = set_connection.set_id';
172        $query->conditions[] = '`set`.public = 1';
173        $this->filterSets($query);
174        $this->filterTags($query);
175        $this->filterRanks($query);
176    }
177
178    public function calculateCount(): int
179    {
180        $query = new Query('FROM tsumego');
181        $query->selects[] = 'COUNT(DISTINCT tsumego.id) AS total';
182        $this->addConditionsToQuery($query);
183        return Util::query($query->str())[0]['total'];
184    }
185
186    public string $query;
187    public int $collectionSize = 0;
188    public array $sets = [];
189    public array $setIDs = [];
190    public array $ranks = [];
191    public array $tags = [];
192    public array $tagIDs = [];
193}