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