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