Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.91% covered (success)
90.91%
90 / 99
66.67% covered (warning)
66.67%
8 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
TsumegoMerger
90.91% covered (success)
90.91%
90 / 99
66.67% covered (warning)
66.67%
8 / 12
30.68
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 checkInput
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
4.59
 mergeSlaveSetConnections
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 mergeStatus
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
4.01
 mergeStatuses
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 mergeTsumegoAttempts
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 mergeComments
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 mergeFavorites
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 mergeTagConnections
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 mergeTimeModeAttempts
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 mergeIssues
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 execute
89.19% covered (warning)
89.19%
33 / 37
0.00% covered (danger)
0.00%
0 / 1
3.01
1<?php
2
3class TsumegoMerger
4{
5    public function __construct($masterTsumegoID, $slaveTsumegoID)
6    {
7        $this->masterTsumegoID = $masterTsumegoID;
8        $this->slaveTsumegoID = $slaveTsumegoID;
9    }
10
11    private function checkInput(): ?array
12    {
13        $masterTsumego = ClassRegistry::init('Tsumego')->findById($this->masterTsumegoID);
14        if (!$masterTsumego)
15            return ['message' => 'Merge masterTsumego not found', 'type' => 'error'];
16
17        $slaveTsumego = ClassRegistry::init('Tsumego')->findById($this->slaveTsumegoID);
18        if (!$slaveTsumego)
19            return ['message' => 'Slave tsumego does not exist.', 'type' => 'error'];
20
21        if ($this->masterTsumegoID == $this->slaveTsumegoID)
22            return ['message' => 'Tsumegos already merged.', 'type' => 'error'];
23        return null;
24    }
25
26    private function mergeSlaveSetConnections()
27    {
28        $slaveSetConnectionBrothers = ClassRegistry::init('SetConnection')->find('all', ['conditions' => ['tsumego_id' => $this->slaveTsumegoID]]);
29        foreach ($slaveSetConnectionBrothers as $slaveTsumegoBrother)
30        {
31            $slaveTsumegoBrother['SetConnection']['tsumego_id'] = $this->masterTsumegoID;
32            ClassRegistry::init('SetConnection')->save($slaveTsumegoBrother);
33        }
34    }
35
36    private function mergeStatus(?int $statusID1, ?int $statusID2): void
37    {
38        if (!$statusID1)
39        {
40            $tsumegoStatus = ClassRegistry::init('TsumegoStatus')->findById($statusID2)['TsumegoStatus'];
41            $tsumegoStatus['tsumego_id'] = $this->masterTsumegoID;
42            ClassRegistry::init('TsumegoStatus')->save($tsumegoStatus);
43            return;
44        }
45
46        // second status doesn't exist, so we just keep the master one
47        if (!$statusID2)
48            return;
49
50        $masterStatus = ClassRegistry::init('TsumegoStatus')->findById($statusID1)['TsumegoStatus'];
51        $slaveStatus = ClassRegistry::init('TsumegoStatus')->findById($statusID2)['TsumegoStatus'];
52        $masterStatus['status'] = TsumegoStatus::less($masterStatus['status'], $slaveStatus['status']) ? $slaveStatus['status'] : $masterStatus['status'];
53        ClassRegistry::init('TsumegoStatus')->save($masterStatus);
54    }
55
56    private function mergeStatuses()
57    {
58        $statusMergeSources = Util::query("
59SELECT
60    user_id,
61    MAX(CASE WHEN tsumego_id = :id1 THEN id END)     AS tsumego_status_id_1,
62    MAX(CASE WHEN tsumego_id = :id1 THEN status END) AS tsumego_status_1,
63    MAX(CASE WHEN tsumego_id = :id2 THEN id END)     AS tsumego_status_id_2,
64    MAX(CASE WHEN tsumego_id = :id2 THEN status END) AS tsumego_status_2
65FROM tsumego_status
66WHERE tsumego_id IN (:id1, :id2)
67GROUP BY user_id
68HAVING
69    COUNT(*) BETWEEN 1 AND 2", [':id1' => $this->masterTsumegoID, ':id2' => $this->slaveTsumegoID]);
70        foreach ($statusMergeSources as $statusMergeSource)
71            $this->mergeStatus($statusMergeSource['tsumego_status_id_1'], $statusMergeSource['tsumego_status_id_2']);
72    }
73
74    private function mergeTsumegoAttempts()
75    {
76        $slaveAttempts = ClassRegistry::init('TsumegoAttempt')->find('all', ['conditions' => ['tsumego_id' => $this->slaveTsumegoID]]);
77        foreach ($slaveAttempts as $slaveAttempt)
78        {
79            $slaveAttempt['TsumegoAttempt']['tsumego_id'] = $this->masterTsumegoID;
80            ClassRegistry::init('TsumegoAttempt')->save($slaveAttempt);
81        }
82    }
83
84    private function mergeComments()
85    {
86        $slaveComments = ClassRegistry::init('TsumegoComment')->find('all', ['conditions' => ['tsumego_id' => $this->slaveTsumegoID]]);
87        foreach ($slaveComments as $slaveComment)
88        {
89            $slaveComment['TsumegoComment']['tsumego_id'] = $this->masterTsumegoID;
90            ClassRegistry::init('TsumegoComment')->save($slaveComment);
91        }
92    }
93
94    private function mergeFavorites()
95    {
96        $favoritesMergeSource = Util::query("
97SELECT
98    user_id,
99    MAX(CASE WHEN tsumego_id = :id1 THEN id END)     AS favorite_id_1,
100    MAX(CASE WHEN tsumego_id = :id2 THEN id END)     AS favorite_id_2
101FROM favorite
102WHERE tsumego_id IN (:id1, :id2)
103GROUP BY user_id
104HAVING
105    COUNT(*) BETWEEN 1 AND 2", [':id1' => $this->masterTsumegoID, ':id2' => $this->slaveTsumegoID]);
106        foreach ($favoritesMergeSource as $favoriteMergeSource)
107        {
108            // slave is empty, nothing to do
109            if (!$favoriteMergeSource['favorite_id_2'])
110                continue;
111
112            // master is empty, we change the slave to master
113            if (!$favoriteMergeSource['favorite_id_1'])
114            {
115                $favorite = ClassRegistry::init('Favorite')->findById($favoriteMergeSource['favorite_id_2'])['Favorite'];
116                $favorite['tsumego_id'] = $this->masterTsumegoID;
117                ClassRegistry::init('Favorite')->save($favorite);
118                continue;
119            }
120            // when both master and slave is present, we don't have to do anything, the slave one will be removed by
121            // foreign key cascade
122        }
123    }
124
125    private function mergeTagConnections()
126    {
127        $tagMergeSources = Util::query("
128SELECT
129    tag_id,
130    MAX(CASE WHEN tsumego_id = :id1 THEN id END)     AS tag_connection_id_1,
131    MAX(CASE WHEN tsumego_id = :id2 THEN id END)     AS tag_connection_id_2
132FROM tag_connection
133WHERE tsumego_id IN (:id1, :id2)
134GROUP BY tag_id
135HAVING
136    COUNT(*) BETWEEN 1 AND 2", [':id1' => $this->masterTsumegoID, ':id2' => $this->slaveTsumegoID]);
137        foreach ($tagMergeSources as $tagMergeSource)
138        {
139            // slave is empty, nothing to do
140            if (!$tagMergeSource['tag_connection_id_2'])
141                continue;
142
143            // master is empty, we change the slave to master
144            if (!$tagMergeSource['tag_connection_id_1'])
145            {
146                $tagConnection = ClassRegistry::init('TagConnection')->findById($tagMergeSource['tag_connection_id_2'])['TagConnection'];
147                $tagConnection['tsumego_id'] = $this->masterTsumegoID;
148                ClassRegistry::init('TagConnection')->save($tagConnection);
149                continue;
150            }
151            // when both master and slave is present, we don't have to do anything, the slave one will be removed by
152            // foreign key cascade
153        }
154    }
155
156    public function mergeTimeModeAttempts()
157    {
158        Util::query('UPDATE time_mode_attempt SET tsumego_id = :master_tsumego_id WHERE tsumego_id = :slave_tsumego_id',
159            [':master_tsumego_id' => $this->masterTsumegoID, ':slave_tsumego_id' => $this->slaveTsumegoID]);
160    }
161
162    public function mergeIssues()
163    {
164        Util::query('UPDATE tsumego_issue SET tsumego_id = :master_tsumego_id WHERE tsumego_id = :slave_tsumego_id',
165            [':master_tsumego_id' => $this->masterTsumegoID, ':slave_tsumego_id' => $this->slaveTsumegoID]);
166    }
167
168    public function execute(): array
169    {
170        if ($result = $this->checkInput())
171            return $result;
172
173        $db = ClassRegistry::init('Tsumego')->getDataSource();
174        try
175        {
176            $masterSgf = ClassRegistry::init('Sgf')->find('first', [
177                'conditions' => ['tsumego_id' => $this->masterTsumegoID, 'accepted' => true],
178                'order' => 'id DESC'])['Sgf'];
179            $slaveSgf = ClassRegistry::init('Sgf')->find('first', [
180                'conditions' => ['tsumego_id' => $this->slaveTsumegoID, 'accepted' => true],
181                'order' => 'id DESC'])['Sgf'];
182            $oldData = [
183                'tsumego_old' => $this->slaveTsumegoID,
184                'master_sgf' => $masterSgf['sgf'],
185                'master_correct_moves' => $masterSgf['correct_moves'],
186                'master_first_move_color' => $masterSgf['first_move_color'],
187                'slave_sgf' => $slaveSgf['sgf'],
188                'slave_correct_moves' => $slaveSgf['correct_moves'],
189                'slave_first_move_color' => $slaveSgf['first_move_color']];
190
191            $db->begin();
192            $this->mergeSlaveSetConnections();
193            $this->mergeStatuses();
194            $this->mergeTsumegoAttempts();
195            $this->mergeComments();
196            $this->mergeFavorites();
197            $this->mergeTagConnections();
198            $this->mergeTimeModeAttempts();
199            $this->mergeIssues();
200            ClassRegistry::init('Tsumego')->delete($this->slaveTsumegoID);
201            AdminActivityLogger::log(
202                AdminActivityType::TSUMEGO_MERGE,
203                $this->masterTsumegoID,
204                null,
205                json_encode($oldData, JSON_UNESCAPED_UNICODE));
206            $db->commit();
207            return ['message' => 'Tsumegos merged.', 'type' => 'success'];
208        }
209        catch (Exception $e)
210        {
211            $db->rollback();
212            return ['message' => $e->getMessage(), 'type' => 'error'];
213        }
214    }
215
216    private $masterTsumegoID;
217    private $slaveTsumegoID;
218}