Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
68.75% covered (warning)
68.75%
33 / 48
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
SgfController
65.12% covered (warning)
65.12%
28 / 43
0.00% covered (danger)
0.00%
0 / 3
26.87
0.00% covered (danger)
0.00%
0 / 1
 fetch
47.06% covered (danger)
47.06%
8 / 17
0.00% covered (danger)
0.00%
0 / 1
11.34
 upload
83.33% covered (warning)
83.33%
15 / 18
0.00% covered (danger)
0.00%
0 / 1
6.17
 validateSgfFormat
62.50% covered (warning)
62.50%
5 / 8
0.00% covered (danger)
0.00%
0 / 1
4.84
1<?php
2
3App::uses('AdminActivityLogger', 'Utility');
4App::uses('AdminActivityType', 'Model');
5App::uses('NotFoundException', 'Routing/Error');
6App::uses('BadRequestException', 'Routing/Error');
7App::uses('ForbiddenException', 'Routing/Error');
8
9class SgfController extends AppController
10{
11    public function fetch(int $sgfID)
12    {
13        if (!Auth::isLoggedIn())
14        {
15            $this->response->statusCode(403);
16            $this->response->body('User not logged in.');
17            return $this->response;
18        }
19
20        $sgf = ClassRegistry::init('Sgf')->find('first', ['conditions' => ['id' => $sgfID], 'order' => 'id DESC']);
21        if (!$sgf)
22        {
23            $this->response->statusCode(404);
24            $this->response->body('Sgf not found.');
25            return $this->response;
26        }
27
28        $status = ClassRegistry::init('TsumegoStatus')->find('first', ['conditions' => ['tsumego_id' => $sgf['Sgf']['tsumego_id'], 'user_id' => Auth::getUserID()]]);
29        if (!Auth::isAdmin() && (!$status || !TsumegoUtil::isRecentlySolved($status['TsumegoStatus']['status'])))
30        {
31            $this->response->statusCode(403);
32            $this->response->body('Related tsumego is not in a solved state for the user ' . Auth::getUser()['name']);
33            return $this->response;
34        }
35
36        $this->response->statusCode(200);
37        $this->response->body($sgf['Sgf']['sgf']);
38        return $this->response;
39    }
40
41    public function upload($setConnectionID)
42    {
43        if (!Auth::isLoggedIn())
44            throw new ForbiddenException('Must be logged in to upload SGF files.');
45
46        $setConnection = ClassRegistry::init('SetConnection')->findById($setConnectionID);
47        if (!$setConnection)
48            throw new NotFoundException("Specified set connection does not exist.");
49
50        // Use besogo textarea if provided, otherwise use file upload
51        $fileUpload = isset($_FILES['adminUpload']) && $_FILES['adminUpload']['error'] === UPLOAD_ERR_OK ? $_FILES['adminUpload'] : null;
52        $sgfDataOrFile = $this->data['sgfForBesogo'] ?? file_get_contents($fileUpload['tmp_name']);
53
54        if (!$sgfDataOrFile)
55            throw new BadRequestException('No SGF data provided.');
56
57        self::validateSgfFormat($sgfDataOrFile);
58
59        AdminActivityLogger::log(
60            AdminActivityType::SGF_EDIT,
61            $setConnection['SetConnection']['tsumego_id'],
62            $setConnection['SetConnection']['set_id'],
63        );
64
65        $this->set('sgf', $sgfDataOrFile);
66        $this->set('setConnectionID', $setConnectionID);
67        $this->render('/Tsumegos/setup_new_sgf');
68    }
69
70    public static function validateSgfFormat(string $sgf): void
71    {
72        $maxSize = 1024 * 1024; // 1 MB
73        if (strlen($sgf) > $maxSize)
74            throw new BadRequestException('SGF data exceeds maximum size of 1 MB.');
75
76        $trimmed = trim($sgf);
77        if (!str_starts_with($trimmed, '(;'))
78            throw new BadRequestException('Invalid SGF format: must start with "(;".');
79
80        if (!str_contains($trimmed, 'GM[1]'))
81            throw new BadRequestException('Invalid SGF format: must contain GM[1] (Go game marker).');
82    }
83}