Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.42% covered (warning)
75.42%
681 / 903
20.00% covered (danger)
20.00%
4 / 20
CRAP
0.00% covered (danger)
0.00%
0 / 1
SetsController
75.11% covered (warning)
75.11%
670 / 892
20.00% covered (danger)
20.00%
4 / 20
997.50
0.00% covered (danger)
0.00%
0 / 1
 sandbox
88.46% covered (warning)
88.46%
69 / 78
0.00% covered (danger)
0.00%
0 / 1
20.61
 create
95.74% covered (success)
95.74%
45 / 47
0.00% covered (danger)
0.00%
0 / 1
6
 remove
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
72
 add
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 index
100.00% covered (success)
100.00%
74 / 74
100.00% covered (success)
100.00%
1 / 1
17
 getDifficultyAndSolved
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
90
 getFirstUnsolvedSetConnectionId
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
4.01
 ui
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
56
 decodeQueryType
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 addTsumego
70.27% covered (warning)
70.27%
26 / 37
0.00% covered (danger)
0.00%
0 / 1
12.63
 view
75.00% covered (warning)
75.00%
270 / 360
0.00% covered (danger)
0.00%
0 / 1
248.06
 updateAchievementConditions
84.00% covered (warning)
84.00%
21 / 25
0.00% covered (danger)
0.00%
0 / 1
5.10
 findUt
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getDifficultyColor
54.55% covered (warning)
54.55%
6 / 11
0.00% covered (danger)
0.00%
0 / 1
9.38
 getSizeColor
53.33% covered (warning)
53.33%
8 / 15
0.00% covered (danger)
0.00%
0 / 1
7.54
 getDateColor
23.53% covered (danger)
23.53%
4 / 17
0.00% covered (danger)
0.00%
0 / 1
36.62
 getSolvedColor
95.83% covered (success)
95.83%
46 / 48
0.00% covered (danger)
0.00%
0 / 1
3
 getExistingRanksArray
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
1 / 1
1
 resetProgress
92.00% covered (success)
92.00%
23 / 25
0.00% covered (danger)
0.00%
0 / 1
6.02
 changeCollectionSize
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
2.15
1<?php
2
3use function PHPUnit\Framework\isNull;
4
5App::uses('SgfParser', 'Utility');
6App::uses('TsumegoUtil', 'Utility');
7App::uses('NotFoundException', 'Routing/Error');
8App::uses('BadRequestException', 'Routing/Error');
9App::uses('TsumegoButton', 'Utility');
10App::uses('TsumegoButtons', 'Utility');
11App::uses('SetsSelector', 'Utility');
12App::uses('AdminActivityLogger', 'Utility');
13App::uses('AdminActivityType', 'Model');
14App::uses('Progress', 'Utility');
15App::uses('SetEditRenderer', 'Utility');
16
17class SetsController extends AppController
18{
19    public $helpers = ['Html', 'Form'];
20
21    public $title = 'tsumego-hero.com';
22
23    /**
24     * @return void
25     */
26    public function sandbox()
27    {
28        $this->loadModel('User');
29        $this->loadModel('Tsumego');
30        $this->loadModel('TsumegoStatus');
31        $this->loadModel('Favorite');
32        $this->loadModel('SetConnection');
33
34        $this->set('_page', 'sandbox');
35        $this->set('_title', 'Tsumego Hero - Collections');
36        $setsNew = [];
37
38        if (isset($this->params['url']['restore']))
39        {
40            $restore = $this->Set->findById($this->params['url']['restore']);
41            if ($restore && $restore['Set']['public'] == -1)
42            {
43                $restore['Set']['public'] = 0;
44                $this->Set->save($restore);
45            }
46        }
47
48        $sets = $this->Set->find('all', [
49            'order' => ['Set.order'],
50            'conditions' => ['public' => 0],
51        ]) ?: [];
52        $u = $this->User->findById(Auth::getUserID());
53
54        if (Auth::isLoggedIn())
55        {
56            $uts = $this->TsumegoStatus->find('all', ['conditions' => ['user_id' => Auth::getUserID()]]);
57            if (!$uts)
58                $uts = [];
59            $tsumegoStatusMap = [];
60            $utsCount4 = count($uts);
61            for ($l = 0; $l < $utsCount4; $l++)
62                $tsumegoStatusMap[$uts[$l]['TsumegoStatus']['tsumego_id']] = $uts[$l]['TsumegoStatus']['status'];
63        }
64        $overallCounter = 0;
65
66        $setsCount = count($sets);
67        for ($i = 0; $i < $setsCount; $i++)
68        {
69            $ts = TsumegoUtil::collectTsumegosFromSet($sets[$i]['Set']['id']);
70            $sets[$i]['Set']['anz'] = count($ts);
71            $counter = 0;
72            $elo = 0;
73            $tsCount3 = count($ts);
74            for ($k = 0; $k < $tsCount3; $k++)
75            {
76
77                $elo += $ts[$k]['Tsumego']['rating'];
78                if (Auth::isLoggedIn())
79                    if (isset($tsumegoStatusMap[$ts[$k]['Tsumego']['id']]))
80                        if ($tsumegoStatusMap[$ts[$k]['Tsumego']['id']] == 'S' || $tsumegoStatusMap[$ts[$k]['Tsumego']['id']] == 'W' || $tsumegoStatusMap[$ts[$k]['Tsumego']['id']] == 'C')
81                            $counter++;
82            }
83            if (count($ts) > 0)
84                $elo = $elo / count($ts);
85            else
86                $elo = 0;
87            $date = new DateTime($sets[$i]['Set']['created']);
88            $month = date('F', strtotime($sets[$i]['Set']['created']));
89            $setday = $date->format('d. ');
90            $setyear = $date->format('Y');
91            if ($setday[0] == 0)
92                $setday = substr($setday, -3);
93            $sets[$i]['Set']['created'] = $date->format('Ymd');
94            $sets[$i]['Set']['createdDisplay'] = $setday . $month . ' ' . $setyear;
95            $percent = 0;
96            if (count($ts) > 0)
97                $percent = Util::getPercentButAvoid100UntilComplete($counter, count($ts));
98            $overallCounter += count($ts);
99            $sets[$i]['Set']['solvedNum'] = $counter;
100            $sets[$i]['Set']['solved'] = $percent;
101            $sets[$i]['Set']['solvedColor'] = $this->getSolvedColor($sets[$i]['Set']['solved']);
102            $sets[$i]['Set']['topicColor'] = $sets[$i]['Set']['color'];
103            $sets[$i]['Set']['difficultyColor'] = $this->getDifficultyColor($sets[$i]['Set']['difficulty']);
104            $sets[$i]['Set']['sizeColor'] = $this->getSizeColor($sets[$i]['Set']['anz']);
105            $sets[$i]['Set']['dateColor'] = $this->getDateColor($sets[$i]['Set']['created']);
106
107            $sn = [];
108            $sn['id'] = $sets[$i]['Set']['id'];
109            $sn['name'] = $sets[$i]['Set']['title'];
110            $sn['amount'] = count($ts);
111            $sn['color'] = $sets[$i]['Set']['color'];
112            $sn['difficulty'] = Rating::getReadableRankFromRating($elo);
113            $sn['solved'] = $percent;
114            array_push($setsNew, $sn);
115        }
116
117        $adminsList = $this->User->find('all', ['order' => 'id ASC', 'conditions' => ['isAdmin >' => 0]]) ?: [];
118        $admins = [];
119        foreach ($adminsList as $item)
120            $admins[] = $item['User']['name'];
121
122        $this->set('admins', $admins);
123        $this->set('sets', $sets);
124        $this->set('setsNew', $setsNew);
125        $this->set('overallCounter', $overallCounter);
126    }
127
128    /**
129     * @param int|null $tid Tsumego ID
130     * @return void
131     */
132    public function create($tid = null)
133    {
134        $this->loadModel('Tsumego');
135        $this->loadModel('SetConnection');
136        $redirect = false;
137        $t = [];
138        if (isset($this->data['Set']))
139        {
140            $s = $this->Set->find('all', ['order' => 'id DESC']);
141            if (!$s)
142                $s = [];
143            $ss = [];
144            $sCount = count($s);
145            for ($i = 0; $i < $sCount; $i++)
146                if ($s[$i]['Set']['id'] < 6472)
147                    array_push($ss, $s[$i]);
148
149            $seed = str_split('abcdefghijklmnopqrstuvwxyz0123456789');
150            shuffle($seed);
151            $rand = '';
152            foreach (array_rand($seed, 6) as $k)
153                $rand .= $seed[$k];
154            $hashName = '6473k339312/_' . $rand . '_' . $this->data['Set']['title'];
155            $hashName2 = '_' . $rand . '_' . $this->data['Set']['title'];
156
157            $set = [];
158            $set['Set']['title'] = $this->data['Set']['title'];
159            $set['Set']['public'] = 0;
160            $set['Set']['image'] = 'b1.png';
161            $set['Set']['difficulty'] = 4;
162            $set['Set']['author'] = 'various creators';
163            $set['Set']['order'] = Constants::$DEFAULT_SET_ORDER;
164
165            $this->Set->create();
166            $this->Set->save($set);
167
168            $t = [];
169            $t['Tsumego']['difficulty'] = 4;
170            $t['Tsumego']['variance'] = 100;
171            $t['Tsumego']['description'] = 'b to kill';
172            $t['Tsumego']['author'] = Auth::getUser()['name'];
173            $this->Tsumego->create();
174            $this->Tsumego->save($t);
175
176            $sc = [];
177            $sc['SetConnection']['set_id'] = $this->Set->id;
178            $sc['SetConnection']['tsumego_id'] = $this->Tsumego->id;
179            $sc['SetConnection']['num'] = 1;
180            $this->SetConnection->create();
181            $this->SetConnection->save($sc);
182
183            mkdir($hashName, 0777);
184            copy('6473k339312/__new/1.sgf', $hashName . '/1.sgf');
185            $redirect = true;
186        }
187        $this->set('t', $t);
188        $this->set('redirect', $redirect);
189    }
190
191    /**
192     * @param int $id Set ID
193     * @return void
194     */
195    public function remove($id)
196    {
197        $this->loadModel('Tsumego');
198        $redirect = false;
199
200        if (isset($this->data['Set']))
201            if (strpos(';' . $this->data['Set']['hash'], '6473k339312-') == 1)
202            {
203                $setID = (int) str_replace('6473k339312-', '', $this->data['Set']['hash']);
204
205                $s = $this->Set->findById($setID);
206                if ($s && ($s['Set']['public'] == 0 || $s['Set']['public'] == -1))
207                    $this->Set->delete($setID);
208                $ts = TsumegoUtil::collectTsumegosFromSet($setID);
209                if (count($ts) < 50)
210                    foreach ($ts as $item)
211                        $this->Tsumego->delete($item['Tsumego']['id']);
212                $redirect = true;
213            }
214        //$this->set('t', $t);
215        $this->set('redirect', $redirect);
216    }
217
218    /**
219     * @param int $tid Tsumego ID
220     * @return void
221     */
222    public function add($tid)
223    {
224        $this->loadModel('Tsumego');
225
226        if (isset($this->data['Tsumego']))
227        {
228            $t = [];
229            $t['Tsumego']['difficulty'] = $this->data['Tsumego']['difficulty'];
230            $t['Tsumego']['description'] = $this->data['Tsumego']['description'];
231            $this->Tsumego->save($t);
232        }
233        $ts = TsumegoUtil::collectTsumegosFromSet($tid);
234        $this->set('t', $ts[0]);
235    }
236
237    public function index(): void
238    {
239        $this->loadModel('User');
240        $this->loadModel('Tsumego');
241        $this->loadModel('Favorite');
242        $this->loadModel('AchievementCondition');
243        $this->loadModel('TsumegoStatus');
244        $this->loadModel('SetConnection');
245        $this->loadModel('UserContribution');
246        $this->set('_page', 'set');
247        $this->set('_title', 'Tsumego Hero - Collections');
248
249        $setTiles = [];
250        $difficultyTiles = [];
251        $sets = [];
252        $tagList = [];
253
254        $overallCounter = 0;
255        $problemsCount = 0;
256        $achievementUpdate = [];
257
258        $tsumegoFilters = new TsumegoFilters();
259        if ($tsumegoFilters->query == 'favorites')
260            $tsumegoFilters->setQuery('topics');
261
262        //setTiles
263        $setsRaw = $this->Set->find('all', [
264            'order' => ['Set.order', 'Set.id'],
265            'conditions' => ['public' => 1],
266        ]) ?: [];
267        foreach ($setsRaw as $set)
268            if (Auth::hasPremium() || !$set['Set']['premium'])
269                $setTiles [] = $set['Set']['title'];
270
271        //difficultyTiles
272        $dt = SetsController::getExistingRanksArray();
273        foreach ($dt as $item)
274            $difficultyTiles[] = $item['rank'];
275
276        //tagTiles
277        $tags = $this->Tag->find('all', [
278            'conditions' => [
279                'approved' => 1,
280                'NOT' => ['name' => 'Tsumego'],
281            ],
282        ]);
283
284        $tagTiles = [];
285        foreach ($tags as $tag)
286            $tagTiles[] = $tag['Tag']['name'];
287
288        $setsSelector = new SetsSelector($tsumegoFilters);
289
290        if (Auth::isLoggedIn())
291        {
292            $aCondition = $this->AchievementCondition->find('first', [
293                'order' => 'value DESC',
294                'conditions' => [
295                    'user_id' => Auth::getUserID(),
296                    'category' => 'set']]) ?: [];
297            $aCondition['AchievementCondition']['category'] = 'set';
298            $aCondition['AchievementCondition']['user_id'] = Auth::getUserID();
299            $aCondition['AchievementCondition']['value'] = $overallCounter;
300            ClassRegistry::init('AchievementCondition')->save($aCondition);
301            $achievementChecker = new AchievementChecker();
302            $achievementChecker->checkSetCompletedAchievements();
303            $achievementChecker->finalize();
304            $this->set('achievementUpdate', $achievementChecker->updated);
305            Auth::saveUser();
306        }
307
308        $ranksArray = SetsController::getExistingRanksArray();
309        foreach ($ranksArray as &$rank)
310        {
311            $rank['id'] = $rank['rank'];
312            $rank['name'] = $rank['rank'];
313        }
314
315        if ($tsumegoFilters->query == 'topics' && empty($tsumegoFilters->sets))
316            $queryRefresh = false;
317        elseif ($tsumegoFilters->query == 'difficulty' && empty($tsumegoFilters->ranks))
318            $queryRefresh = false;
319        elseif ($tsumegoFilters->query == 'tags' && empty($tsumegoFilters->tags))
320            $queryRefresh = false;
321        else
322            $queryRefresh = true;
323
324
325
326        $this->set('setsSelector', $setsSelector);
327        $this->set('ranksArray', $ranksArray);
328        $this->set('tagList', $tagList);
329        $this->set('setTiles', $setTiles);
330        $this->set('difficultyTiles', $difficultyTiles);
331        $this->set('tagTiles', $tagTiles);
332        $this->set('tsumegoFilters', $tsumegoFilters);
333        $this->set('hasPremium', Auth::hasPremium());
334        $this->set('queryRefresh', $queryRefresh);
335    }
336
337    public static function getDifficultyAndSolved($currentTagIds, $tsumegoStatusMap)
338    {
339        $tagTsumegoDifficulty = ClassRegistry::init('Tsumego')->find('all', ['conditions' => ['id' => $currentTagIds]]);
340        if (!$tagTsumegoDifficulty)
341            $tagTsumegoDifficulty = [];
342        $tagDifficultyResult = 0;
343        $statusCounter = 0;
344        $tagTsumegoDifficultyCount2 = count($tagTsumegoDifficulty);
345        for ($j = 0; $j < $tagTsumegoDifficultyCount2; $j++)
346        {
347            $tagDifficultyResult += $tagTsumegoDifficulty[$j]['Tsumego']['rating'];
348            if (isset($tsumegoStatusMap[$tagTsumegoDifficulty[$j]['Tsumego']['id']]))
349                if ($tsumegoStatusMap[$tagTsumegoDifficulty[$j]['Tsumego']['id']] == 'S' || $tsumegoStatusMap[$tagTsumegoDifficulty[$j]['Tsumego']['id']] == 'W' || $tsumegoStatusMap[$tagTsumegoDifficulty[$j]['Tsumego']['id']] == 'C')
350                    $statusCounter++;
351        }
352        if (count($tagTsumegoDifficulty) > 0)
353            $tagDifficultyResult = $tagDifficultyResult / count($tagTsumegoDifficulty);
354        else
355            $tagDifficultyResult = 0;
356        $tagDifficultyResult = Rating::getReadableRankFromRating($tagDifficultyResult);
357        $return = [];
358        $return['difficulty'] = $tagDifficultyResult;
359        if (count($currentTagIds) > 0)
360            $return['solved'] = Util::getPercentButAvoid100UntilComplete($statusCounter, count($currentTagIds));
361        else
362            $return['solved'] = 0;
363
364        return $return;
365    }
366
367    /**
368     * Gets the first unsolved set connection ID from a collection of tsumego buttons.
369     * Falls back to the first button if all are solved.
370     *
371     * @param TsumegoButtons $tsumegoButtons Iterator of TsumegoButton objects
372     * @return int|null The setConnectionID of the first unsolved button, or first button if all solved, or null if empty
373     */
374    private function getFirstUnsolvedSetConnectionId($tsumegoButtons)
375    {
376        if (empty($tsumegoButtons))
377            return null;
378        if ($firstUnsolvedButton = array_find((array) $tsumegoButtons, function ($tsumegoButton) {
379            return !TsumegoUtil::isSolvedStatus($tsumegoButton->status);
380        }))
381            return $firstUnsolvedButton->setConnectionID;
382        if ($firstRecentlyUnsolved = array_find((array) $tsumegoButtons, function ($tsumegoButton) {
383            return !TsumegoUtil::isRecentlySolved($tsumegoButton->status);
384        }))
385            return $firstRecentlyUnsolved->setConnectionID;
386        return $tsumegoButtons[0]->setConnectionID;
387    }
388
389    /**
390     * @param int|null $id Set ID
391     * @return void
392     */
393    public function ui($id = null)
394    {
395        $s = $this->Set->findById($id);
396        if (!$s)
397            throw new NotFoundException('Set not found');
398        $redirect = false;
399
400        if (isset($_FILES['adminUpload']))
401        {
402            $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
403            $randstring = 'set_';
404            for ($i = 0; $i < 6; $i++)
405                $randstring .= $characters[rand(0, strlen($characters))];
406            $filename = $randstring . '_' . $_FILES['adminUpload']['name'];
407
408            $errors = [];
409            $file_name = $_FILES['adminUpload']['name'];
410            $file_size = $_FILES['adminUpload']['size'];
411            $file_tmp = $_FILES['adminUpload']['tmp_name'];
412            $file_type = $_FILES['adminUpload']['type'];
413            $array = explode('.', $_FILES['adminUpload']['name']);
414            $file_ext = strtolower(end($array));
415            $extensions = ['png', 'jpg'];
416
417            if (in_array($file_ext, $extensions) === false)
418                $errors[] = 'png/jpg allowed.';
419            if ($file_size > 2097152)
420                $errors[] = 'The file is too large.';
421
422            if (empty($errors) == true)
423            {
424                $uploadfile = $_SERVER['DOCUMENT_ROOT'] . '/app/webroot/img/' . $filename;
425                move_uploaded_file($file_tmp, $uploadfile);
426            }
427
428            $s['Set']['image'] = $filename;
429            $this->Set->save($s);
430
431            $redirect = true;
432        }
433
434        $this->set('id', $id);
435        $this->set('s', $s);
436        $this->set('redirect', $redirect);
437    }
438
439    private function decodeQueryType($input)
440    {
441        if (is_numeric($input))
442            return 'topics';
443        if ($input == 'favorites')
444            return 'favorites';
445        try
446        {
447            Rating::getRankFromReadableRank($input);
448            return 'difficulty';
449        }
450        catch (Exception $e)
451        {
452            return 'tags';
453        }
454    }
455
456    public function addTsumego($setID)
457    {
458        if (!Auth::isLoggedIn())
459        {
460            CookieFlash::set('Not logged in', 'error');
461            return $this->redirect('/sets/view/' . $setID);
462        }
463
464        $set = ClassRegistry::init('Set')->findById($setID);
465        if (!$set)
466        {
467            CookieFlash::set('Specified set not found', 'error');
468            return $this->redirect('/sets/view/' . $setID);
469        }
470        $set = $set['Set'];
471
472        if ($set['public'] && !Auth::isAdmin())
473        {
474            CookieFlash::set('Only admins can add to public sets', 'error');
475            return $this->redirect('/sets/view/' . $setID);
476        }
477
478        if (!isset($this->data['order']))
479        {
480            CookieFlash::set('tsumego order to add not specified', 'error');
481            return $this->redirect('/sets/view/' . $setID);
482        }
483
484        $tsumegoModel = ClassRegistry::init('Tsumego');
485
486        $tsumegoModel->getDataSource()->begin();
487
488        try
489        {
490            $tsumego = [];
491            $tsumego['num'] = $this->data['order'];
492            $tsumego['author'] = Auth::getUser()['name'];
493            $tsumegoModel->create();
494            $tsumegoModel->save($tsumego);
495
496            $tsumego['id'] = $tsumegoModel->id;
497            $setConnection = [];
498            $setConnection['set_id'] = $setID;
499            $setConnection['tsumego_id'] = $tsumego['id'];
500            $setConnection['num'] = $this->data['order'];
501            ClassRegistry::init('SetConnection')->create();
502            ClassRegistry::init('SetConnection')->save($setConnection);
503
504            // Save SGF if provided (either from textarea or file upload)
505            $fileUpload = isset($_FILES['adminUpload']) && $_FILES['adminUpload']['error'] === UPLOAD_ERR_OK ? $_FILES['adminUpload'] : null;
506            $sgfDataOrFile = $this->data['sgf'] ?? $fileUpload;
507
508            if ($sgfDataOrFile)
509                ClassRegistry::init('Sgf')->uploadSgf($sgfDataOrFile, $tsumego['id'], Auth::getUserID(), Auth::isAdmin());
510            $tsumegoModel->getDataSource()->commit();
511        }
512        catch (Exception $e)
513        {
514            $tsumegoModel->getDataSource()->rollback();
515            CookieFlash::set('Unexpected error:' . $e->getMessage(), 'error');
516        }
517        return $this->redirect('/sets/view/' . $setID);
518    }
519
520    public function view(string|int|null $id = null, int $partition = 1): void
521    {
522        // transferring from 1 indexed for humans to 0 indexed for us programmers.
523        $partition = $partition - 1;
524        $this->loadModel('Tsumego');
525        $this->loadModel('TsumegoStatus');
526        $this->loadModel('Favorite');
527        $this->loadModel('AdminActivity');
528        $this->loadModel('TsumegoAttempt');
529        $this->loadModel('ProgressDeletion');
530        $this->loadModel('Achievement');
531        $this->loadModel('AchievementStatus');
532        $this->loadModel('AchievementCondition');
533        $this->loadModel('Sgf');
534        $this->loadModel('SetConnection');
535        $this->loadModel('Tag');
536        $this->loadModel('User');
537        $this->loadModel('UserContribution');
538
539        if (is_null($id))
540            throw new NotFoundException("Set to view not specified");
541
542        if ($id != '1')
543            $this->set('_page', 'set');
544        else
545            $this->set('_page', 'favs');
546        $tsIds = [];
547        $refreshView = false;
548        $avgTime = 0;
549        $accuracy = 0;
550        $allVcActive = false;
551        $allVcInactive = false;
552        $allArActive = false;
553        $allArInactive = false;
554        $allPassActive = false;
555        $allPassInactive = false;
556        $pdCounter = 0;
557        $acS = null;
558        $acA = null;
559
560        $queryType = self::decodeQueryType($id);
561
562        if ($queryType == 'topics' && is_numeric($id))
563        {
564            $set = $this->Set->findById($id);
565            if (!$set)
566                throw new NotFoundException("Set not found");
567        }
568
569        if ($queryType == 'tags')
570        {
571            $tag = $this->Tag->findByName($id);
572            if (!$tag)
573                throw new NotFoundException("Tag not found");
574        }
575
576        $tsumegoFilters = new TsumegoFilters($queryType);
577        if (Auth::isLoggedIn())
578            if (Auth::isAdmin())
579            {
580                $aad = $this->AdminActivity->find('first', ['order' => 'id DESC']);
581                // Check if last activity was a problem deletion - if so, actually delete it
582                if (isset($aad['AdminActivity']['type']) && $aad['AdminActivity']['type'] == AdminActivityType::PROBLEM_DELETE)
583                {
584                    $scDelete = $this->SetConnection->find('first', ['order' => 'created DESC', 'conditions' => ['tsumego_id' => $aad['AdminActivity']['tsumego_id']]]);
585                    $this->SetConnection->delete($scDelete['SetConnection']['id']);
586                    $this->Tsumego->delete($aad['AdminActivity']['tsumego_id']);
587                }
588            }
589        Util::setCookie('lastSet', $id);
590        $tsumegoButtons = new TsumegoButtons($tsumegoFilters, null, $partition, $id);
591        $this->set('startingSetConnectionID', $this->getFirstUnsolvedSetConnectionId($tsumegoButtons));
592
593        if ($tsumegoFilters->query == 'difficulty')
594        {
595            $set = [];
596            $set['Set']['id'] = $id;
597            $set['Set']['title'] = $id . $tsumegoButtons->getPartitionTitleSuffix();
598            $set['Set']['image'] = $id . 'Rank.png';
599            $set['Set']['multiplier'] = 1;
600            $set['Set']['public'] = 1;
601            $elo = Rating::getRankMinimalRatingFromReadableRank($id);
602            $set['Set']['difficulty'] = $elo;
603        }
604        elseif ($tsumegoFilters->query == 'tags')
605        {
606            $set = [];
607            $set['Set']['id'] = $id;
608            $set['Set']['image'] = '';
609            $set['Set']['multiplier'] = 1;
610            $set['Set']['public'] = 1;
611            $tagName = $this->Tag->findByName($id);
612            if ($tagName && isset($tagName['Tag']['description']))
613                $set['Set']['description'] = $tagName['Tag']['description'];
614            $set['Set']['title'] = $id . $tsumegoButtons->getPartitionTitleSuffix();
615        }
616        elseif ($tsumegoFilters->query == 'topics')
617        {
618            $set = ClassRegistry::init('Set')->findById($id);
619            $set['Set']['title'] = $set['Set']['title'] . $tsumegoButtons->getPartitionTitleSuffix();
620            $allArActive = true;
621            $allArInactive = true;
622            $allPassActive = true;
623            $allPassInactive = true;
624            foreach ($tsumegoButtons as $tsumegoButton)
625            {
626                if (!$tsumegoButton->alternativeResponse)
627                    $allArActive = false;
628                if (!$tsumegoButton->passEnabled)
629                    $allPassActive = false;
630            }
631            foreach ($tsumegoButtons as $tsumegoButton)
632                $tsIds [] = $tsumegoButton->tsumegoID;
633            if ($set['Set']['public'] == 0)
634                $this->set('_page', 'sandbox');
635            $this->set('isFav', false);
636            if (isset($this->data['Set']['title']))
637            {
638                $this->Set->create();
639                $changeSet = $set;
640                $changeSet['Set']['title'] = $this->data['Set']['title'];
641                $changeSet['Set']['title2'] = $this->data['Set']['title2'];
642                $this->set('data', $changeSet['Set']['title']);
643                $this->Set->save($changeSet, true);
644                $oldTitle = $set['Set']['title'];
645                $set = $this->Set->findById($id);
646                AdminActivityLogger::log(AdminActivityType::SET_TITLE_EDIT, null, $id, $oldTitle, $this->data['Set']['title']);
647            }
648            if (isset($this->data['Set']['description']))
649            {
650                $this->Set->create();
651                $changeSet = $set;
652                $changeSet['Set']['description'] = $this->data['Set']['description'];
653                $this->set('data', $changeSet['Set']['description']);
654                $this->Set->save($changeSet, true);
655                $oldDescription = $set['Set']['description'];
656                $set = $this->Set->findById($id);
657                AdminActivityLogger::log(AdminActivityType::SET_DESCRIPTION_EDIT, null, $id, $oldDescription, $this->data['Set']['description']);
658            }
659            if (isset($this->data['Set']['setDifficulty']))
660                if ($this->data['Set']['setDifficulty'] != 1200 && $this->data['Set']['setDifficulty'] >= 900 && $this->data['Set']['setDifficulty'] <= 2900)
661                {
662                    $setDifficultyTsumegoSet = TsumegoUtil::collectTsumegosFromSet($set['Set']['id']);
663                    $setDifficulty = $this->data['Set']['setDifficulty'];
664                    $setDifficultyTsumegoSetCount = count($setDifficultyTsumegoSet);
665                    for ($i = 0; $i < $setDifficultyTsumegoSetCount; $i++)
666                    {
667                        $setDifficultyTsumegoSet[$i]['Tsumego']['rating']
668                            = Util::clampOptional(
669                                $this->data['Set']['setDifficulty'],
670                                $setDifficultyTsumegoSet[$i]['Tsumego']['minimum_rating'],
671                                $setDifficultyTsumegoSet[$i]['Tsumego']['maximum_rating']);
672                        $this->Tsumego->save($setDifficultyTsumegoSet[$i]);
673                    }
674                    AdminActivityLogger::log(AdminActivityType::SET_RATING_EDIT, null, $id);
675                }
676            if (isset($this->data['Set']['color']))
677            {
678                $this->Set->create();
679                $changeSet = $set;
680                $changeSet['Set']['color'] = $this->data['Set']['color'];
681                $this->set('data', $changeSet['Set']['color']);
682                $this->Set->save($changeSet, true);
683                $oldColor = $set['Set']['color'];
684                $set = $this->Set->findById($id);
685                AdminActivityLogger::log(AdminActivityType::SET_COLOR_EDIT, null, $id, $oldColor, $this->data['Set']['color']);
686            }
687            if (isset($this->data['Set']['order']))
688            {
689                $this->Set->create();
690                $changeSet = $set;
691                $changeSet['Set']['order'] = $this->data['Set']['order'];
692                $this->set('data', $changeSet['Set']['order']);
693                $this->Set->save($changeSet, true);
694                $oldOrder = $set['Set']['order'];
695                $set = $this->Set->findById($id);
696                AdminActivityLogger::log(AdminActivityType::SET_ORDER_EDIT, null, $id, $oldOrder, $this->data['Set']['order']);
697            }
698            if (isset($this->data['Settings']))
699            {
700                if ($this->data['Settings']['r39'] == 'on')
701                {
702                    foreach ($tsumegoButtons as $tsumegoButton)
703                    {
704                        $tsumego = ClassRegistry::init('Tsumego')->findById($tsumegoButton->tsumegoID);
705                        $tsumego['alternative_response'] = true;
706                        ClassRegistry::init('Tsumego')->save($tsumego);
707                    }
708                    $allArActive = true;
709                    AdminActivityLogger::log(AdminActivityType::SET_ALTERNATIVE_RESPONSE, null, $id, null, '1');
710                }
711                if ($this->data['Settings']['r39'] == 'off')
712                {
713                    foreach ($tsumegoButtons as $tsumegoButton)
714                    {
715                        $tsumego = ClassRegistry::init('Tsumego')->findById($tsumegoButton->tsumegoID);
716                        $tsumego['alternative_response'] = false;
717                        ClassRegistry::init('Tsumego')->save($tsumego);
718                    }
719                    $allArInactive = true;
720                    AdminActivityLogger::log(AdminActivityType::SET_ALTERNATIVE_RESPONSE, null, $id, null, '0');
721                }
722                if ($this->data['Settings']['r43'] == 'yes')
723                {
724                    foreach ($tsumegoButtons as $tsumegoButton)
725                    {
726                        $tsumego = ClassRegistry::init('Tsumego')->findById($tsumegoButton->tsumegoID);
727                        $tsumego['pass'] = true;
728                        ClassRegistry::init('Tsumego')->save($tsumego);
729                    }
730                    $allPassActive = true;
731                    AdminActivityLogger::log(AdminActivityType::SET_PASS_MODE, null, $id, null, '1');
732                }
733                if ($this->data['Settings']['r43'] == 'no')
734                {
735                    foreach ($tsumegoButtons as $tsumegoButton)
736                    {
737                        $tsumego = ClassRegistry::init('Tsumego')->findById($tsumegoButton->tsumegoID);
738                        $tsumego['pass'] = false;
739                        ClassRegistry::init('Tsumego')->save($tsumego);
740                    }
741                    $allPassInactive = true;
742                    AdminActivityLogger::log(AdminActivityType::SET_PASS_MODE, null, $id, null, '0');
743                }
744                $this->set('formRedirect', true);
745            }
746        }
747        elseif ($tsumegoFilters->query == 'favorites')
748        {
749            $allUts = $this->TsumegoStatus->find('all', ['conditions' => ['user_id' => Auth::getUserID()]]) ?: [];
750            $idMap = [];
751            $statusMap = [];
752            $allUtsCount = count($allUts);
753            for ($i = 0; $i < $allUtsCount; $i++)
754            {
755                array_push($idMap, $allUts[$i]['TsumegoStatus']['tsumego_id']);
756                array_push($statusMap, $allUts[$i]['TsumegoStatus']['status']);
757            }
758            $fav = $this->Favorite->find('all', ['order' => 'created', 'direction' => 'DESC', 'conditions' => ['user_id' => Auth::getUserID()]]) ?: [];
759            if (!empty($fav))
760                $this->set('achievementUpdate', new AchievementChecker()->checkSetAchievements(-1)->finalize()->updated);
761            $ts = [];
762            $difficultyCount = 0;
763            $solvedCount = 0;
764            $sizeCount = 0;
765            $favCount = count($fav);
766            for ($i = 0; $i < $favCount; $i++)
767            {
768                $tx = $this->Tsumego->find('first', ['conditions' => ['id' => $fav[$i]['Favorite']['tsumego_id']]]);
769                $difficultyCount += $tx['Tsumego']['difficulty'];
770                $utx = $this->findUt($fav[$i]['Favorite']['tsumego_id'], $allUts, $idMap);
771                if ($utx['TsumegoStatus']['status'] == 'S' || $utx['TsumegoStatus']['status'] == 'W' || $utx['TsumegoStatus']['status'] == 'C')
772                    $solvedCount++;
773                $sizeCount++;
774                array_push($ts, $tx);
775            }
776            $allUtsCount = count($allUts);
777            for ($i = 0; $i < $allUtsCount; $i++)
778            {
779                $tsCount2 = count($ts);
780                for ($j = 0; $j < $tsCount2; $j++)
781                    if ($allUts[$i]['TsumegoStatus']['tsumego_id'] == $ts[$j]['Tsumego']['id'])
782                        $ts[$j]['Tsumego']['status'] = $allUts[$i]['TsumegoStatus']['status'];
783            }
784            $percent = Util::getPercentButAvoid100UntilComplete($solvedCount, $sizeCount);
785            $set = [];
786            $set['Set']['id'] = 1;
787            $set['Set']['title'] = 'Favorites';
788            $set['Set']['title2'] = null;
789            $set['Set']['author'] = Auth::getUser()['name'];
790            $set['Set']['description'] = '';
791            $set['Set']['image'] = 'fav';
792            $set['Set']['order'] = 0;
793            $set['Set']['public'] = 1;
794            $set['Set']['created'] = 20180322;
795            $set['Set']['createdDisplay'] = '22. March 2018';
796            $set['Set']['solvedNum'] = $sizeCount;
797            $set['Set']['solved'] = $percent;
798            $set['Set']['solvedColor'] = '#eee';
799            $set['Set']['topicColor'] = '#eee';
800            $set['Set']['difficultyColor'] = '#eee';
801            $set['Set']['sizeColor'] = '#eee';
802            $set['Set']['dateColor'] = '#eee';
803            $this->set('isFav', true);
804        }
805        else
806            throw new BadRequestException('Unknown query type: ' . $tsumegoFilters->query);
807
808        if ($tsumegoButtons->description)
809            $set['Set']['description'] = $tsumegoButtons->description;
810
811        $this->set('_title', $set['Set']['title'] . ' on Tsumego Hero');
812
813        if (Auth::isLoggedIn() && $tsumegoFilters->query == 'topics')
814        {
815            $ur = $this->TsumegoAttempt->find('all', [
816                'order' => 'created DESC',
817                'conditions' => [
818                    'user_id' => Auth::getUserID(),
819                    'tsumego_id' => $tsIds,
820                ],
821            ]) ?: [];
822            foreach ($tsumegoButtons as $tsumegoButton)
823            {
824                $urTemp = [];
825                $urSum = '';
826                $tsumegoButton->seconds = 0;
827                $solvedSeconds = []; // Track all successful solve times to find minimum (best)
828                $urCount2 = count($ur);
829                for ($j = 0; $j < $urCount2; $j++)
830                    if ($tsumegoButton->tsumegoID == $ur[$j]['TsumegoAttempt']['tsumego_id'])
831                    {
832                        array_push($urTemp, $ur[$j]);
833                        if ($ur[$j]['TsumegoAttempt']['solved'])
834                            $solvedSeconds[] = $ur[$j]['TsumegoAttempt']['seconds'];
835
836                        if (!$ur[$j]['TsumegoAttempt']['solved'])
837                        {
838                            $mis = $ur[$j]['TsumegoAttempt']['misplays'];
839                            if ($mis == 0)
840                                $mis = 1;
841                            while ($mis > 0)
842                            {
843                                $urSum .= 'F';
844                                $mis--;
845                            }
846                        }
847                        else
848                            $urSum .= $ur[$j]['TsumegoAttempt']['solved'];
849                    }
850                // Use minimum (best) solve time from all successful attempts
851                if (!empty($solvedSeconds))
852                    $tsumegoButton->seconds = min($solvedSeconds);
853                $tsumegoButton->performance = $urSum;
854            }
855        }
856
857        $problemSolvedPercent = $tsumegoButtons->getProblemsSolvedPercent();
858        $setRating = $tsumegoButtons->getProblemsRating();
859        $this->set('setRating', $setRating);
860
861        $this->set('problemSolvedPercent', $problemSolvedPercent);
862
863        $scoring = true;
864        if (Auth::isLoggedIn() && $tsumegoFilters->query == 'topics')
865        {
866            $pd = $this->ProgressDeletion->find('all', [
867                'conditions' => [
868                    'user_id' => Auth::getUserID(),
869                    'set_id' => $id]]) ?: [];
870            $pdCounter = 0;
871            $pdCount = count($pd);
872            for ($i = 0; $i < $pdCount; $i++)
873            {
874                $date = date_create($pd[$i]['ProgressDeletion']['created']);
875                $pd[$i]['ProgressDeletion']['d'] = $date->format('Y') . '-' . $date->format('m');
876                if (date('Y-m') == $pd[$i]['ProgressDeletion']['d'])
877                    $pdCounter++;
878            }
879            $urSecCounter = 0;
880            $urSecAvg = 0;
881            $pSsum = 0;
882            $pFsum = 0;
883            foreach ($tsumegoButtons as $tsumegoButton)
884            {
885                if ($tsumegoButton->seconds == 0)
886                    if (TsumegoUtil::isSolvedStatus($tsumegoButton->status))
887                        $tss = 60;
888                    else
889                        $tss = 0;
890                else
891                    $tss = $tsumegoButton->seconds;
892                $urSecAvg += $tss;
893                $urSecCounter++;
894
895                if ($tsumegoButton->performance == '')
896                    if (TsumegoUtil::isSolvedStatus($tsumegoButton->status))
897                        $tss2 = 'F';
898                    else
899                        $tss2 = '';
900                else
901                    $tss2 = $tsumegoButton->performance;
902                $pS = substr_count($tss2, '1');
903                $pF = substr_count($tss2, 'F');
904                $pSsum += $pS;
905                $pFsum += $pF;
906            }
907            if ($urSecCounter == 0)
908                $avgTime = 60;
909            else
910                $avgTime = round($urSecAvg / $urSecCounter, 2);
911            if ($pSsum + $pFsum == 0)
912                $accuracy = 0;
913            else
914                $accuracy = round($pSsum / ($pSsum + $pFsum) * 100, 2);
915            $avgTime2 = $avgTime;
916            if ($problemSolvedPercent >= 100)
917            {
918                $achievementChecker = new AchievementChecker();
919                if ($set['Set']['id'] != 210)
920                {
921                    $this->updateAchievementConditions($set['Set']['id'], $avgTime2, $accuracy);
922                    $achievementChecker->checkSetAchievements($set['Set']['id'], $setRating);
923                }
924                if ($id == 50 || $id == 52 || $id == 53 || $id == 54)
925                    $achievementChecker->setAchievementSpecial('cc1');
926                elseif ($id == 41 || $id == 49 || $id == 65 || $id == 66)
927                    $achievementChecker->setAchievementSpecial('cc2');
928                elseif ($id == 186 || $id == 187 || $id == 196 || $id == 203)
929                    $achievementChecker->setAchievementSpecial('cc3');
930                elseif ($id == 190 || $id == 193 || $id == 198)
931                    $achievementChecker->setAchievementSpecial('1000w1');
932                elseif ($id == 216)
933                    $achievementChecker->setAchievementSpecial('1000w2');
934                $achievementChecker->finalize();
935                $this->set('achievementUpdate', $achievementChecker->updated);
936            }
937
938            $acS = $this->AchievementCondition->find('first', [
939                'order' => 'value ASC',
940                'conditions' => [
941                    'set_id' => $id,
942                    'user_id' => Auth::getUserID(),
943                    'category' => 's']]);
944            $acA = $this->AchievementCondition->find('first', [
945                'order' => 'value DESC',
946                'conditions' => [
947                    'set_id' => $id,
948                    'user_id' => Auth::getUserID(),
949                    'category' => '%']]);
950        }
951        else
952            $scoring = false;
953
954        $allTags = $this->Tag->find('all') ?: [];
955        $allTagsSorted = [];
956        $allTagsKeys = [];
957        $allTagsCount = count($allTags);
958        for ($i = 0; $i < $allTagsCount; $i++)
959        {
960            array_push($allTagsSorted, $allTags[$i]['Tag']['name']);
961            $allTagsKeys[$allTags[$i]['Tag']['name']] = $allTags[$i];
962        }
963        sort($allTagsSorted);
964        $s2Tags = [];
965        $allTagsSortedCount = count($allTagsSorted);
966        for ($i = 0; $i < $allTagsSortedCount; $i++)
967            array_push($s2Tags, $allTagsKeys[$allTagsSorted[$i]]);
968
969        $allTags = $s2Tags;
970
971        if ($tsumegoFilters->query == 'topics')
972        {
973            $this->set('allVcActive', $allVcActive);
974            $this->set('allVcInactive', $allVcInactive);
975            $this->set('allArActive', $allArActive);
976            $this->set('allArInactive', $allArInactive);
977            $this->set('allPassActive', $allPassActive);
978            $this->set('allPassInactive', $allPassInactive);
979            $this->set('pdCounter', $pdCounter);
980            $this->set('acS', $acS);
981            $this->set('acA', $acA);
982        }
983
984        $this->set('tsumegoFilters', $tsumegoFilters);
985        $this->set('allTags', $allTags);
986        $this->set('tsumegoButtons', $tsumegoButtons);
987        $this->set('set', $set);
988        $this->set('refreshView', $refreshView);
989        $this->set('avgTime', $avgTime);
990        $this->set('accuracy', $accuracy);
991        $this->set('scoring', $scoring);
992        $this->set('partition', $partition);
993    }
994
995    /**
996     * @param int $sid Set ID
997     * @param float $avgTime Average time
998     * @param float $accuracy Accuracy percentage
999     * @return void
1000     */
1001    public function updateAchievementConditions($sid, $avgTime, $accuracy)
1002    {
1003        $uid = Auth::getUserID();
1004        $acS = $this->AchievementCondition->find('first', ['order' => 'value ASC', 'conditions' => ['set_id' => $sid, 'user_id' => $uid, 'category' => 's']]);
1005        $acA = $this->AchievementCondition->find('first', ['order' => 'value DESC', 'conditions' => ['set_id' => $sid, 'user_id' => $uid, 'category' => '%']]);
1006
1007        if ($acS == null)
1008        {
1009            $aCond = [];
1010            $aCond['AchievementCondition']['user_id'] = $uid;
1011            $aCond['AchievementCondition']['set_id'] = $sid;
1012            $aCond['AchievementCondition']['value'] = $avgTime;
1013            $aCond['AchievementCondition']['category'] = 's';
1014            $this->AchievementCondition->create();
1015            $this->AchievementCondition->save($aCond);
1016        }
1017        elseif ($avgTime < $acS['AchievementCondition']['value'])
1018        {
1019            $acS['AchievementCondition']['value'] = $avgTime;
1020            $this->AchievementCondition->save($acS);
1021        }
1022        if ($acA == null)
1023        {
1024            $aCond = [];
1025            $aCond['AchievementCondition']['user_id'] = $uid;
1026            $aCond['AchievementCondition']['set_id'] = $sid;
1027            $aCond['AchievementCondition']['value'] = $accuracy;
1028            $aCond['AchievementCondition']['category'] = '%';
1029            $this->AchievementCondition->create();
1030            $this->AchievementCondition->save($aCond);
1031        }
1032        elseif ($accuracy > $acA['AchievementCondition']['value'])
1033        {
1034            $acA['AchievementCondition']['value'] = $accuracy;
1035            $this->AchievementCondition->save($acA);
1036        }
1037    }
1038
1039    private function findUt($id = null, $allUts = null, $map = null)
1040    {
1041        $currentUt = array_search($id, $map);
1042        $ut = $allUts[$currentUt];
1043        if ($currentUt == 0)
1044            if ($id != $map[0])
1045                $ut = null;
1046
1047        return $ut;
1048    }
1049
1050    private function getDifficultyColor($difficulty = null)
1051    {
1052        if ($difficulty == 1)
1053            return '#33cc33';
1054        if ($difficulty == 2)
1055            return '#709533';
1056        if ($difficulty == 3)
1057            return '#2e3370';
1058        if ($difficulty == 4)
1059            return '#ac5d33';
1060        if ($difficulty == 5)
1061            return '#e02e33';
1062
1063        return 'white';
1064    }
1065
1066    private function getSizeColor($size = null)
1067    {
1068        $colors = [];
1069        array_push($colors, '#cc6600');
1070        array_push($colors, '#ac4e26');
1071        array_push($colors, '#963e3e');
1072        array_push($colors, '#802e58');
1073        array_push($colors, '#60167d');
1074        if ($size < 30)
1075            return $colors[0];
1076        if ($size < 60)
1077            return $colors[1];
1078        if ($size < 110)
1079            return $colors[2];
1080        if ($size < 202)
1081            return $colors[3];
1082
1083        return $colors[4];
1084    }
1085
1086    private function getDateColor($date = null)
1087    {
1088        $current = '20180705';
1089        $dist = $current - $date;
1090
1091        if ($dist < 7)
1092            return '#0033cc';
1093        if ($dist < 100)
1094            return '#0f33ad';
1095        if ($dist < 150)
1096            return '#1f338f';
1097        if ($dist < 200)
1098            return '#2e3370';
1099        if ($dist < 300)
1100            return '#3d3352';
1101        if ($dist < 400)
1102            return '#4c3333';
1103        if ($dist < 500)
1104            return '#57331f';
1105
1106        return '#663300';
1107    }
1108
1109    private function getSolvedColor($percent = null)
1110    {
1111        $colors = [];
1112
1113        array_push($colors, '#333333');
1114        array_push($colors, '#2e3d47');
1115        array_push($colors, '#2b4252');
1116        array_push($colors, '#29475c');
1117        array_push($colors, '#264c66');
1118        array_push($colors, '#245270');
1119        array_push($colors, '#21577a');
1120        array_push($colors, '#1f5c85');
1121        array_push($colors, '#1c618f');
1122        array_push($colors, '#1a6699');
1123
1124        array_push($colors, '#176ba3');
1125        array_push($colors, '#1470ad');
1126        array_push($colors, '#1275b8');
1127        array_push($colors, '#0f7ac2');
1128        array_push($colors, '#0d80cc');
1129        array_push($colors, '#0a85d6');
1130        array_push($colors, '#088ae0');
1131        array_push($colors, '#058feb');
1132        array_push($colors, '#0394f5');
1133        array_push($colors, '#0099ff');
1134
1135        array_push($colors, '#039cf8');
1136        array_push($colors, '#069ef2');
1137        array_push($colors, '#09a1eb');
1138        array_push($colors, '#0ca4e4');
1139        array_push($colors, '#10a6dd');
1140        array_push($colors, '#13a9d6');
1141        array_push($colors, '#16acd0');
1142        array_push($colors, '#19afc9');
1143        array_push($colors, '#1cb1c2');
1144        array_push($colors, '#1fb4bc');
1145
1146        array_push($colors, '#22b7b5');
1147        array_push($colors, '#25b9ae');
1148        array_push($colors, '#28bca7');
1149        array_push($colors, '#2bbfa0');
1150        array_push($colors, '#2ec29a');
1151        array_push($colors, '#32c493');
1152        array_push($colors, '#35c78c');
1153        array_push($colors, '#38ca86');
1154        array_push($colors, '#3bcc7f');
1155        array_push($colors, '#3ecf78');
1156        $steps = 2.5;
1157        $colorsCount = count($colors);
1158        for ($i = 0; $i < $colorsCount; $i++)
1159        {
1160            if ($percent <= $steps)
1161                return $colors[$i];
1162            $steps += 2.5;
1163        }
1164
1165        return '#333333';
1166    }
1167
1168    public static function getExistingRanksArray()
1169    {
1170        $ranksArray = [];
1171        $ranksArray[0]['rank'] = '15k';
1172        $ranksArray[1]['rank'] = '14k';
1173        $ranksArray[2]['rank'] = '13k';
1174        $ranksArray[3]['rank'] = '12k';
1175        $ranksArray[4]['rank'] = '11k';
1176        $ranksArray[5]['rank'] = '10k';
1177        $ranksArray[6]['rank'] = '9k';
1178        $ranksArray[7]['rank'] = '8k';
1179        $ranksArray[8]['rank'] = '7k';
1180        $ranksArray[9]['rank'] = '6k';
1181        $ranksArray[10]['rank'] = '5k';
1182        $ranksArray[11]['rank'] = '4k';
1183        $ranksArray[12]['rank'] = '3k';
1184        $ranksArray[13]['rank'] = '2k';
1185        $ranksArray[14]['rank'] = '1k';
1186        $ranksArray[15]['rank'] = '1d';
1187        $ranksArray[16]['rank'] = '2d';
1188        $ranksArray[17]['rank'] = '3d';
1189        $ranksArray[18]['rank'] = '4d';
1190        $ranksArray[19]['rank'] = '5d';
1191        $ranksArray[20]['rank'] = '6d';
1192        $ranksArray[21]['rank'] = '7d';
1193        $ranksArray[22]['rank'] = '8d';
1194        $ranksArray[23]['rank'] = '9d';
1195        $ranksArray[0]['color'] = 'rgba(63,  201, 196, [o])';
1196        $ranksArray[1]['color'] = 'rgba(63, 190, 201, [o])';
1197        $ranksArray[2]['color'] = 'rgba(63, 173, 201, [o])';
1198        $ranksArray[3]['color'] = 'rgba(63, 157, 201, [o])';
1199        $ranksArray[4]['color'] = 'rgba(63, 141, 201, [o])';
1200        $ranksArray[5]['color'] = 'rgba(88, 158, 244, [o])';
1201        $ranksArray[6]['color'] = 'rgba(88, 140, 244, [o])';
1202        $ranksArray[7]['color'] = 'rgba(88, 122, 244, [o])';
1203        $ranksArray[8]['color'] = 'rgba(88, 103, 244, [o])';
1204        $ranksArray[9]['color'] = 'rgba(90, 88, 244, [o])';
1205        $ranksArray[10]['color'] = 'rgba(109, 88, 244, [o])';
1206        $ranksArray[11]['color'] = 'rgba(127, 88, 244, [o])';
1207        $ranksArray[12]['color'] = 'rgba(145, 88, 244, [o])';
1208        $ranksArray[13]['color'] = 'rgba(163, 88, 244, [o])';
1209        $ranksArray[14]['color'] = 'rgba(182, 88, 244, [o])';
1210        $ranksArray[15]['color'] = 'rgba(200, 88, 244, [o])';
1211        $ranksArray[16]['color'] = 'rgba(218, 88, 244, [o])';
1212        $ranksArray[17]['color'] = 'rgba(236, 88, 244, [o])';
1213        $ranksArray[18]['color'] = 'rgba(244, 88, 234, [o])';
1214        $ranksArray[19]['color'] = 'rgba(244, 88, 187, [o])';
1215        $ranksArray[20]['color'] = 'rgba(244, 88, 145, [o])';
1216        $ranksArray[21]['color'] = 'rgba(244, 88, 127, [o])';
1217        $ranksArray[22]['color'] = 'rgba(244, 88, 101, [o])';
1218        $ranksArray[23]['color'] = 'rgba(244, 88, 88, [o])';
1219
1220        return $ranksArray;
1221    }
1222
1223    public function resetProgress(int $setID, int $partition): mixed
1224    {
1225        $redirectUrl = '/sets/view/' . $setID . '/' . $partition;
1226
1227        if (!Auth::isLoggedIn())
1228            return $this->redirect($redirectUrl);
1229
1230        if ($this->data['reset-check'] != 'reset')
1231        {
1232            CookieFlash::set('Reset check wasn\'t correctly typed', 'error');
1233            return $this->redirect($redirectUrl);
1234        }
1235
1236        $tsumegoFilters = new TsumegoFilters();
1237        if ($tsumegoFilters->collectionSize != 200)
1238        {
1239            CookieFlash::set('Reset is only possible for collection size 200', 'error');
1240            return $this->redirect($redirectUrl);
1241        }
1242        $tsumegoFilters->query = 'topics';
1243        $tsumegoButtons = new TsumegoButtons($tsumegoFilters, null, $partition - 1, $setID);
1244        $tsumegoIDToClear = [];
1245        foreach ($tsumegoButtons as $tsumegoButton)
1246            $tsumegoIDToClear[] = $tsumegoButton->tsumegoID;
1247
1248        if ($tsumegoButtons->getProblemsSolvedPercent() < 50)
1249            return $this->redirect($redirectUrl);
1250
1251        Util::query("
1252DELETE tsumego_status FROM tsumego_status
1253WHERE tsumego_status.user_id = ? AND tsumego_status.tsumego_id IN(" . implode(',', $tsumegoIDToClear) . ")", [Auth::getUserID()]);
1254        $progresDeletion = [];
1255        $progresDeletion['user_id'] = Auth::getUserID();
1256        $progresDeletion['set_id'] = $setID;
1257        ClassRegistry::init('ProgressDeletion')->create();
1258        ClassRegistry::init('ProgressDeletion')->save($progresDeletion);
1259        return $this->redirect($redirectUrl);
1260    }
1261
1262    public function changeCollectionSize(): mixed
1263    {
1264        $collectionSize = $this->data['collection_size'];
1265        if (is_null($collectionSize))
1266        {
1267            CookieFlash::set('Collection size to change not provided', 'error');
1268            return $this->redirect('/sets');
1269        }
1270        Preferences::set('collection_size', $this->data['collection_size']);
1271        return $this->redirect('/sets');
1272    }
1273}