Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 175
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
CompatibilityChecker
0.00% covered (danger)
0.00%
0 / 175
0.00% covered (danger)
0.00%
0 / 14
2450
0.00% covered (danger)
0.00%
0 / 1
 index
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 whichBrowser
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 check
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
12
 upload
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
56
 storeData
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 schoolName
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
30
 getData
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 1
342
 getParameters
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getId
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 deleteId
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 loadConfig
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 mapData
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getBrowserDetector
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOsDetector
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2015-2023 (original work) Open Assessment Technologies SA.
19 */
20
21namespace oat\taoClientDiagnostic\controller;
22
23use common_exception_MissingParameter;
24use common_session_SessionManager;
25use oat\tao\model\mvc\DefaultUrlService;
26use oat\taoClientDiagnostic\exception\StorageException;
27use oat\taoClientDiagnostic\model\authorization\Authorization;
28use oat\taoClientDiagnostic\model\CompatibilityChecker as CompatibilityCheckerModel;
29use oat\taoClientDiagnostic\model\diagnostic\DiagnosticServiceInterface;
30use oat\taoClientDiagnostic\model\schoolName\SchoolNameService;
31use oat\taoClientDiagnostic\model\storage\Storage;
32use Sinergi\BrowserDetector\Browser;
33use Sinergi\BrowserDetector\Os;
34use tao_helpers_Display;
35
36/**
37 * Class CompatibilityChecker
38 * @package oat\taoClientDiagnostic\controller
39 */
40class CompatibilityChecker extends \tao_actions_CommonModule
41{
42    public const COOKIE_ID = 'compatibility_checker_id';
43
44    /**
45     * If logged in, display index view with config data
46     * If not, forward to login
47     */
48    public function index()
49    {
50        $authorizationService = $this->getServiceLocator()->get(Authorization::SERVICE_ID);
51        if ($authorizationService->isAuthorized()) {
52            $config = $this->loadConfig();
53
54            if (!empty($config['diagnostic']['pageTitle'])) {
55                $this->setData('title', $config['diagnostic']['pageTitle']);
56            }
57
58            $this->setData('client_config_url', $this->getClientConfigUrl());
59            $this->setData('content-config', $config);
60            $this->setData('logout', $this->getServiceLocator()->get(DefaultUrlService::SERVICE_ID)->getLogoutUrl());
61            $this->setData('content-controller', 'taoClientDiagnostic/controller/CompatibilityChecker/diagnostics');
62            $this->setData('content-template', 'CompatibilityChecker' . DIRECTORY_SEPARATOR . 'diagnostics.tpl');
63            $this->setView('index.tpl');
64        } else {
65            $this->redirect($authorizationService->getAuthorizationUrl(_url('index')));
66        }
67    }
68
69    /**
70     * Render browser detection view
71     */
72    public function whichBrowser()
73    {
74        $result = [
75            'browser' =>  $this->getBrowserDetector()->getName(),
76            'browserVersion' => $this->getBrowserDetector()->getVersion(),
77            'os' => $this->getOsDetector()->getName(),
78            'osVersion' => $this->getOsDetector()->getVersion(),
79            'isMobile' => $this->getOsDetector()->isMobile(),
80        ];
81        $this->returnJson($result);
82    }
83
84    /**
85     * Check if requester is compatible (os+browser)
86     * Register compatibility
87     * return json message
88     */
89    public function check()
90    {
91        try {
92            $data = $this->getData(true);
93            $id   = $this->getId();
94
95            $checker            = $this->getServiceLocator()->get(CompatibilityCheckerModel::SERVICE_ID);
96            $isCompatible       = (int)$checker->isCompatibleConfig();
97            $data['compatible'] = $isCompatible;
98
99            try {
100                $storageService = $this->getServiceLocator()->get(Storage::SERVICE_ID);
101                $storageService->store($id, $data);
102            } catch (StorageException $e) {
103                $this->logInfo($e->getMessage());
104            }
105
106            $compatibilityMessage = [
107                //Not compatible
108                CompatibilityCheckerModel::COMPATIBILITY_NONE => [
109                    'success' => true,
110                    'type'    => 'error',
111                    'message' => __(
112                        'Your system requires a compatibility update, please contact your system administrator.'
113                    )
114                ],
115                //Compatible
116                CompatibilityCheckerModel::COMPATIBILITY_COMPATIBLE => [
117                    'success' => true,
118                    'type'    => 'success',
119                    'message' => __('This browser is compatible but its support may end soon.')
120                ],
121                //Not tested
122                CompatibilityCheckerModel::COMPATIBILITY_NOT_TESTED => [
123                    'success' => true,
124                    'type'    => 'warning',
125                    'message' => __('This browser is not tested.')
126                ],
127                //Fully supported
128                CompatibilityCheckerModel::COMPATIBILITY_SUPPORTED => [
129                    'success' => true,
130                    'type'    => 'success',
131                    'message' => __('This browser is supported.')
132                ],
133                //Not supported
134                CompatibilityCheckerModel::COMPATIBILITY_NOT_SUPPORTED => [
135                    'success' => true,
136                    'type'    => 'warning',
137                    'message' => __('This browser is not supported.')
138                ],
139            ];
140
141            $this->returnJson($compatibilityMessage[$isCompatible]);
142        } catch (common_exception_MissingParameter $e) {
143            $this->returnJson(array('success' => false, 'type' => 'error', 'message' => $e->getUserMessage()));
144        }
145    }
146
147    /**
148     * Action is used to check upload speed from client side.
149     */
150    public function upload()
151    {
152        try {
153            if ($this->getRequestMethod() !== \Request::HTTP_POST) {
154                throw new \common_exception_NotImplemented('Only post method is accepted.');
155            }
156            if (!isset($_POST['upload']) || !is_string($_POST['upload'])) {
157                throw new \common_exception_InconsistentData("'upload' POST variable is missed.");
158            }
159            $size = mb_strlen($_POST['upload'], '8bit');
160            $result = ['success' => true, 'size' => $size];
161        } catch (\common_exception_NotImplemented $e) {
162            $result = ['success' => false, 'error' => $e->getMessage()];
163            $this->logWarning($e->getMessage());
164        } catch (\common_exception_InconsistentData $e) {
165            $result = ['success' => false, 'error' => $e->getMessage()];
166            $this->logWarning($e->getMessage());
167        } catch (\Exception $e) {
168            $result = ['success' => false, 'type' => 'error', 'message' => 'Please contact administrator'];
169            $this->logWarning($e->getMessage());
170        }
171        $this->returnJson($result);
172    }
173
174    /**
175     * Register data from the front end
176     */
177    public function storeData()
178    {
179        $data = $this->getData();
180        $id   = $this->getId();
181
182        try {
183            $storageService = $this->getServiceLocator()->get(Storage::SERVICE_ID);
184            $storageService->store($id, $data);
185            $this->returnJson(array('success' => true, 'type' => 'success'));
186        } catch (StorageException $e) {
187            $this->logInfo($e->getMessage());
188            $this->returnJson(array('success' => false, 'type' => 'error'));
189        }
190    }
191
192    /**
193     * Retrieve a school name
194     */
195    public function schoolName()
196    {
197        // simple counter measure to slow down brute force attack
198        sleep(1);
199
200        $data = $this->getParameters();
201
202        $required = ['school_number', 'school_pin'];
203
204        $response = [];
205        $success = true;
206
207        foreach ($required as $fieldName) {
208            if (!isset($data[$fieldName])) {
209                $success = false;
210                $response['errorCode'] = 400;
211                $response['errorMessage'] = __('Missing field %s', $fieldName);
212                break;
213            }
214        }
215
216        if ($success) {
217            try {
218                $schoolNameProvider = $this->getServiceLocator()->get(SchoolNameService::SERVICE_ID);
219                $response['data'] = $schoolNameProvider->getSchoolName($data['school_number'], $data['school_pin']);
220            } catch (\Exception $e) {
221                $success = false;
222                $response['errorCode'] = 404;
223                $response['errorMessage'] = __('Cannot retrieve the school name. Please verify your input');
224            }
225        }
226
227        $response['success'] = $success;
228        $this->returnJson($response);
229    }
230
231    /**
232     * Fetch POST data
233     * Get login by cookie
234     * Get Ip
235     * If check parameters is true, check mandatory parameters
236     *
237     * @param bool $check
238     * @return array
239     * @throws common_exception_MissingParameter
240     */
241    protected function getData($check = false)
242    {
243        $data = $this->getParameters();
244
245        if ($this->hasRequestParameter('type')) {
246            $type = $this->getRequestParameter('type');
247            unset($data['type']);
248
249            if ($type !== 'custom_input') {
250                foreach ($data as $key => $value) {
251                    $data[$type . '_' . $key] = $value;
252                    unset($data[$key]);
253                }
254            }
255        }
256
257        $data = $this->mapData($data);
258
259        if ($this->hasRequestParameter('school_name')) {
260            $data[Storage::DIAGNOSTIC_SCHOOL_NAME] = tao_helpers_Display::sanitizeXssHtml(
261                trim($this->getRequestParameter('school_name'))
262            );
263        }
264
265        if ($this->hasRequestParameter('school_id')) {
266            $data[Storage::DIAGNOSTIC_SCHOOL_ID] = tao_helpers_Display::sanitizeXssHtml(
267                trim($this->getRequestParameter('school_id'))
268            );
269        }
270
271        if ($this->hasRequestParameter('workstation')) {
272            $data[Storage::DIAGNOSTIC_WORKSTATION] = tao_helpers_Display::sanitizeXssHtml(
273                trim($this->getRequestParameter('workstation'))
274            );
275        }
276
277        if ($this->hasRequestParameter('school_number')) {
278            $data[Storage::DIAGNOSTIC_SCHOOL_NUMBER] = tao_helpers_Display::sanitizeXssHtml(
279                trim($this->getRequestParameter('school_number'))
280            );
281        }
282
283        if ($check) {
284            if (!$this->hasRequestParameter('os')) {
285                throw new common_exception_MissingParameter('os');
286            }
287            if (!$this->hasRequestParameter('osVersion')) {
288                throw new common_exception_MissingParameter('osVersion');
289            }
290            if (!$this->hasRequestParameter('browser')) {
291                throw new common_exception_MissingParameter('browser');
292            }
293            if (!$this->hasRequestParameter('browserVersion')) {
294                throw new common_exception_MissingParameter('browserVersion');
295            }
296            $data['osVersion'] = preg_replace('/[^\w\.]/', '', $data['osVersion']);
297            $data['browserVersion'] = preg_replace('/[^\w\.]/', '', $data['browserVersion']);
298        }
299
300        if (isset($_COOKIE['login'])) {
301            $data['login'] = $_COOKIE['login'];
302        } else {
303            $data['login'] = 'Anonymous';
304        }
305
306        $user = common_session_SessionManager::getSession()->getUser();
307        if ($user && $user->getIdentifier()) {
308            $data['user_id'] = $user->getIdentifier();
309        }
310
311        $data['version'] = $this->getServiceLocator()
312            ->get(\common_ext_ExtensionsManager::SERVICE_ID)
313            ->getExtensionById('taoClientDiagnostic')
314            ->getVersion();
315
316        $data['ip'] = (!empty($_SERVER['HTTP_X_REAL_IP']))
317            ? $_SERVER['HTTP_X_REAL_IP']
318            : ((!empty($_SERVER['REMOTE_ADDR'])) ? $_SERVER['REMOTE_ADDR'] : 'unknown');
319
320        return $data;
321    }
322
323    /**
324     * Get current http parameters
325     *
326     * @return array
327     */
328    protected function getParameters()
329    {
330        return $this->getRequestParameters();
331    }
332
333    /**
334     * Get cookie id OR create it if doesnt exist
335     * @return string
336     */
337    protected function getId()
338    {
339        if (! isset($_COOKIE[self::COOKIE_ID])) {
340            $id = uniqid();
341            setcookie(self::COOKIE_ID, $id);
342        } else {
343            $id = $_COOKIE[self::COOKIE_ID];
344        }
345        return $id;
346    }
347
348    /**
349     * Delete cookie id
350     */
351    public function deleteId()
352    {
353        setcookie(self::COOKIE_ID, null);
354        $this->returnJson(array('success' => true, 'type' => 'success'));
355    }
356
357    /**
358     * Get config parameters for compatibility check
359     *
360     * @return mixed
361     */
362    protected function loadConfig()
363    {
364        /** @var DiagnosticServiceInterface $service */
365        $service = $this->getServiceLocator()->get(DiagnosticServiceInterface::SERVICE_ID);
366        $config = $service->getDiagnosticJsConfig();
367        $config['controller'] = 'CompatibilityChecker';
368        return $config;
369    }
370
371    /**
372     * Map custom input data from the 'customInput' configuration.
373     *
374     * @param array $data
375     * @return array
376     */
377    protected function mapData(array $data)
378    {
379        $config = $this->loadConfig();
380
381        foreach ($data as $k => $d) {
382            if (!empty($config['diagnostic']['customInput'][$k])) {
383                $data[$config['diagnostic']['customInput'][$k]] = $d;
384                unset($data[$k]);
385            }
386        }
387
388        return $data;
389    }
390
391    /**
392     * Get the browser detector
393     *
394     * @return Browser
395     */
396    protected function getBrowserDetector()
397    {
398        return new Browser();
399    }
400
401    /**
402     * Get the operating system detector
403     *
404     * @return Os
405     */
406    protected function getOsDetector()
407    {
408        return new Os();
409    }
410}