Passed
Push — master ( fbd3ae...ed78f2 )
by Tomasz
10:12 queued 16s
created

UserAPI::getLogo()   C

Complexity

Conditions 14
Paths 33

Size

Total Lines 52
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 42
nc 33
nop 4
dl 0
loc 52
rs 5.9535
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * ******************************************************************************
5
 * Copyright 2011-2017 DANTE Ltd. and GÉANT on behalf of the GN3, GN3+, GN4-1 
6
 * and GN4-2 consortia
7
 *
8
 * License: see the web/copyright.php file in the file structure
9
 * ******************************************************************************
10
 */
11
12
/**
13
 * This is the collection of methods dedicated for the user GUI
14
 * @author Tomasz Wolniewicz <[email protected]>
15
 * @author Stefan Winter <[email protected]>
16
 * @package UserAPI
17
 *
18
 * Parts of this code are based on simpleSAMLPhp discojuice module.
19
 * This product includes GeoLite data created by MaxMind, available from
20
 * http://www.maxmind.com
21
 */
22
23
namespace core;
24
25
use GeoIp2\Database\Reader;
26
use \Exception;
27
28
/**
29
 * The basic methoods for the user GUI
30
 * @package UserAPI
31
 *
32
 */
33
class UserAPI extends CAT {
34
35
    /**
36
     * nothing special to be done here.
37
     */
38
    public function __construct() {
39
        parent::__construct();
40
    }
41
42
    /**
43
     * Prepare the device module environment and send back the link
44
     * This method creates a device module instance via the {@link DeviceFactory} call, 
45
     * then sets up the device module environment for the specific profile by calling 
46
     * {@link DeviceConfig::setup()} method and finally, called the devide writeInstaller meethod
47
     * passing the returned path name.
48
     * 
49
     * @param string $device identifier as in {@link devices.php}
50
     * @param int $profileId profile identifier
51
     *
52
     * @return array 
53
     *  array with the following fields: 
54
     *  profile - the profile identifier; 
55
     *  device - the device identifier; 
56
     *  link - the path name of the resulting installer
57
     *  mime - the mimetype of the installer
58
     */
59
    public function generateInstaller($device, $profileId, $generatedFor = "user", $token = NULL, $password = NULL) {
60
        $this->languageInstance->setTextDomain("devices");
61
        $this->loggerInstance->debug(4, "installer:$device:$profileId\n");
62
        $validator = new \web\lib\common\InputValidation();
63
        $profile = $validator->Profile($profileId);
64
        // test if the profile is production-ready and if not if the authenticated user is an owner
65
        if ($this->verifyDownloadAccess($profile) === FALSE) {
66
            return;
67
        }
68
        $installerProperties = [];
69
        $installerProperties['profile'] = $profileId;
70
        $installerProperties['device'] = $device;
71
        $cache = $this->getCache($device, $profile);
72
        $this->installerPath = $cache['path'];
73
        if ($this->installerPath && $token == NULL && $password == NULL) {
74
            $this->loggerInstance->debug(4, "Using cached installer for: $device\n");
75
            $installerProperties['link'] = "API.php?action=downloadInstaller&lang=" . $this->languageInstance->getLang() . "&profile=$profileId&device=$device&generatedfor=$generatedFor";
76
            $installerProperties['mime'] = $cache['mime'];
77
        } else {
78
            $myInstaller = $this->generateNewInstaller($device, $profile, $generatedFor, $token, $password);
79
            if ($myInstaller['link'] !== 0) {
80
                $installerProperties['mime'] = $myInstaller['mime'];
81
            }
82
            $installerProperties['link'] = $myInstaller['link'];
83
        }
84
        $this->languageInstance->setTextDomain("web_user");
85
        return($installerProperties);
86
    }
87
    
88
    private function verifyDownloadAccess($profile) {
89
        $attribs = $profile->getCollapsedAttributes();
90
        if (!isset($attribs['profile:production']) || (isset($attribs['profile:production']) && $attribs['profile:production'][0] != "on")) {
91
            $this->loggerInstance->debug(4, "Attempt to download a non-production ready installer for profile: $profile->identifier\n");
92
            $auth = new \web\lib\admin\Authentication();
93
            if (!$auth->isAuthenticated()) {
94
                $this->loggerInstance->debug(2, "User NOT authenticated, rejecting request for a non-production installer\n");
95
                header("HTTP/1.0 403 Not Authorized");
96
                return(FALSE);
97
            }
98
            $userObject = new User($_SESSION['user']);
99
            if (!$userObject->isIdPOwner($profile->institution)) {
100
                $this->loggerInstance->debug(2, "User not an owner of a non-production profile - access forbidden\n");
101
                header("HTTP/1.0 403 Not Authorized");
102
                return(FALSE);
103
            }
104
            $this->loggerInstance->debug(4, "User is the owner - allowing access\n");
105
        }
106
        return(TRUE);
107
    }
108
109
    /**
110
     * This function tries to find a cached copy of an installer for a given
111
     * combination of Profile and device
112
     * @param string $device
113
     * @param AbstractProfile $profile
114
     * @return boolean|string the string with the path to the cached copy, or FALSE if no cached copy exists
115
     */
116
    private function getCache($device, $profile) {
117
        $deviceList = \devices\Devices::listDevices();
118
        $deviceConfig = $deviceList[$device];
119
        $noCache = (isset(\devices\Devices::$Options['no_cache']) && \devices\Devices::$Options['no_cache']) ? 1 : 0;
120
        if (isset($deviceConfig['options']['no_cache'])) {
121
            $noCache = $deviceConfig['options']['no_cache'] ? 1 : 0;
122
        }
123
        if ($noCache) {
124
            $this->loggerInstance->debug(5, "getCache: the no_cache option set for this device\n");
125
            return(FALSE);
126
        }
127
        $this->loggerInstance->debug(5, "getCache: caching option set for this device\n");
128
        $cache = $profile->testCache($device);
129
        $iPath = $cache['cache'];
130
        if ($iPath && is_file($iPath)) {
131
            return(['path' => $iPath, 'mime' => $cache['mime']]);
132
        }
133
        return(FALSE);
134
    }
135
136
    /**
137
     * Generates a new installer for the given combination of device and Profile
138
     * 
139
     * @param string $device
140
     * @param AbstractProfile $profile
141
     * @return array info about the new installer (mime and link)
142
     */
143
    private function generateNewInstaller($device, $profile, $generatedFor, $token, $password) {
144
        $this->loggerInstance->debug(5, "generateNewInstaller() - Enter");
145
        $factory = new DeviceFactory($device);
146
        $this->loggerInstance->debug(5, "generateNewInstaller() - created Device");
147
        $dev = $factory->device;
148
        $out = [];
149
        if (isset($dev)) {
150
            $dev->setup($profile, $token, $password);
151
            $this->loggerInstance->debug(5, "generateNewInstaller() - Device setup done");
152
            $installer = $dev->writeInstaller();
153
            $this->loggerInstance->debug(5, "generateNewInstaller() - writeInstaller complete");
154
            $iPath = $dev->FPATH . '/tmp/' . $installer;
155
            if ($iPath && is_file($iPath)) {
156
                if (isset($dev->options['mime'])) {
157
                    $out['mime'] = $dev->options['mime'];
158
                } else {
159
                    $info = new \finfo();
160
                    $out['mime'] = $info->file($iPath, FILEINFO_MIME_TYPE);
161
                }
162
                $this->installerPath = $dev->FPATH . '/' . $installer;
163
                rename($iPath, $this->installerPath);
164
                $integerEap = (new \core\common\EAP($dev->selectedEap))->getIntegerRep();
165
                $profile->updateCache($device, $this->installerPath, $out['mime'], $integerEap);
166
                if (CONFIG['DEBUG_LEVEL'] < 4) {
167
                    \core\common\Entity::rrmdir($dev->FPATH . '/tmp');
168
                }
169
                $this->loggerInstance->debug(4, "Generated installer: " . $this->installerPath . ": for: $device, EAP:" . $integerEap . "\n");
170
                $out['link'] = "API.php?action=downloadInstaller&lang=" . $this->languageInstance->getLang() . "&profile=" . $profile->identifier . "&device=$device&generatedfor=$generatedFor";
171
            } else {
172
                $this->loggerInstance->debug(2, "Installer generation failed for: " . $profile->identifier . ":$device:" . $this->languageInstance->getLang() . "\n");
173
                $out['link'] = 0;
174
            }
175
        }
176
        return($out);
177
    }
178
179
    /**
180
     * interface to Devices::listDevices() 
181
     */
182
    public function listDevices($showHidden = 0) {
183
        $dev = \devices\Devices::listDevices();
184
        $returnList = [];
185
        $count = 0;
186
        if ($showHidden !== 0 && $showHidden != 1) {
187
            throw new Exception("show_hidden is only be allowed to be 0 or 1, but it is $showHidden!");
188
        }
189
        foreach ($dev as $device => $deviceProperties) {
190
            if (isset($deviceProperties['options']['hidden']) && $deviceProperties['options']['hidden'] && $showHidden == 0) {
191
                continue;
192
            }
193
            $count++;
194
195
            $deviceProperties['device'] = $device;
196
197
            $group = isset($deviceProperties['group']) ? $deviceProperties['group'] : 'other';
198
            if (!isset($returnList[$group])) {
199
                $returnList[$group] = [];
200
            }
201
            $returnList[$group][$device] = $deviceProperties;
202
        }
203
        return $returnList;
204
    }
205
206
    /**
207
     * 
208
     * @param string $device
209
     * @param int $profileId
210
     */
211
    public function deviceInfo($device, $profileId) {
212
        $this->languageInstance->setTextDomain("devices");
213
        $validator = new \web\lib\common\InputValidation();
214
        $out = 0;
215
        $profile = $validator->Profile($profileId);
216
        $factory = new DeviceFactory($device);
217
        $dev = $factory->device;
218
        if (isset($dev)) {
219
            $dev->setup($profile);
220
            $out = $dev->writeDeviceInfo();
221
        }
222
        $this->languageInstance->setTextDomain("web_user");
223
        echo $out;
224
    }
225
226
    /**
227
     * Prepare the support data for a given profile
228
     *
229
     * @param int $profId profile identifier
230
     * @return array
231
     * array with the following fields:
232
     * - local_email
233
     * - local_phone
234
     * - local_url
235
     * - description
236
     * - devices - an array of device names and their statuses (for a given profile)
237
     */
238
    public function profileAttributes($profId) {
239
        $this->languageInstance->setTextDomain("devices");
240
        $validator = new \web\lib\common\InputValidation();
241
        $profile = $validator->Profile($profId);
242
        $attribs = $profile->getCollapsedAttributes();
243
        $returnArray = [];
244
        $returnArray['silverbullet'] = $profile instanceof ProfileSilverbullet ? 1 : 0;
245
        if (isset($attribs['support:email'])) {
246
            $returnArray['local_email'] = $attribs['support:email'][0];
247
        }
248
        if (isset($attribs['support:phone'])) {
249
            $returnArray['local_phone'] = $attribs['support:phone'][0];
250
        }
251
        if (isset($attribs['support:url'])) {
252
            $returnArray['local_url'] = $attribs['support:url'][0];
253
        }
254
        if (isset($attribs['profile:description'])) {
255
            $returnArray['description'] = $attribs['profile:description'][0];
256
        }
257
        $returnArray['devices'] = $profile->listDevices();
258
        $this->languageInstance->setTextDomain("web_user");
259
        return($returnArray);
260
    }
261
262
    /**
263
     * Generate and send the installer
264
     *
265
     * @param string $device identifier as in {@link devices.php}
266
     * @param int $prof_id profile identifier
267
     * @return string binary stream: installerFile
268
     */
269
    public function downloadInstaller($device, $prof_id, $generated_for = 'user', $token = NULL, $password = NULL) {
270
        $this->loggerInstance->debug(4, "downloadInstaller arguments: $device,$prof_id,$generated_for\n");
271
        $output = $this->generateInstaller($device, $prof_id, $generated_for, $token, $password);
272
        $this->loggerInstance->debug(4, "output from GUI::generateInstaller:");
273
        $this->loggerInstance->debug(4, print_r($output, true));
274
        if (empty($output['link']) || $output['link'] === 0) {
275
            header("HTTP/1.0 404 Not Found");
276
            return;
277
        }
278
        $validator = new \web\lib\common\InputValidation();
279
        $profile = $validator->Profile($prof_id);
280
        $profile->incrementDownloadStats($device, $generated_for);
281
        $file = $this->installerPath;
282
        $filetype = $output['mime'];
283
        $this->loggerInstance->debug(4, "installer MIME type:$filetype\n");
284
        header("Content-type: " . $filetype);
285
        header('Content-Disposition: inline; filename="' . basename($file) . '"');
286
        header('Content-Length: ' . filesize($file));
287
        ob_clean();
288
        flush();
289
        readfile($file);
290
    }
291
292
    /**
293
     * resizes image files
294
     * 
295
     * @param string $inputImage
296
     * @param string $destFile
297
     * @param int $width
298
     * @param int $height
299
     * @param bool $resize shall we do resizing? width and height are ignored otherwise
300
     * @return array
301
     */
302
    private function processImage($inputImage, $destFile, $width, $height, $resize) {
303
        $info = new \finfo();
304
        $filetype = $info->buffer($inputImage, FILEINFO_MIME_TYPE);
305
        $offset = 60 * 60 * 24 * 30;
306
        // gmdate cannot fail here - time() is its default argument (and integer), and we are adding an integer to it
307
        $expiresString = "Expires: " . /** @scrutinizer ignore-type */ gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
308
        $blob = $inputImage;
309
310
        if ($resize === TRUE) {
311
            $image = new \Imagick();
312
            $image->readImageBlob($inputImage);
313
            $image->setImageFormat('PNG');
314
            $image->thumbnailImage($width, $height, 1);
315
            $blob = $image->getImageBlob();
316
            $this->loggerInstance->debug(4, "Writing cached logo $destFile for IdP/Federation.\n");
317
            file_put_contents($destFile, $blob);
318
        }
319
320
        return ["filetype" => $filetype, "expires" => $expiresString, "blob" => $blob];
321
    }
322
323
    /**
324
     * Get and prepare logo file 
325
     *
326
     * When called for DiscoJuice, first check if file cache exists
327
     * If not then generate the file and save it in the cache
328
     * @param int $idp IdP identifier
0 ignored issues
show
Bug introduced by
There is no parameter named $idp. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
329
     * @param int $width maximum width of the generated image - if 0 then it is treated as no upper bound
330
     * @param int $height  maximum height of the generated image - if 0 then it is treated as no upper bound
331
     * @return array|null array with image information or NULL if there is no logo
332
     */
333
    protected function getLogo($identifier, $type, $width = 0, $height = 0) {
334
        $expiresString = '';
335
        $resize = FALSE;
336
        $logoFile = "";
337
        $attributeName = "";
0 ignored issues
show
Unused Code introduced by
$attributeName is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
338
        $validator = new \web\lib\common\InputValidation();
339
        switch ($type) {
340
            case "federation":
341
                if (is_int($identifier)) {
342
                    throw new Exception("Federation identifiers are strings!");
343
                }
344
                $entity = $validator->Federation($identifier);
345
                $attributeName = "fed:logo_file";
346
                break;
347
            case "idp":
348
                if (!is_int($identifier)) {
349
                    throw new Exception("Institution identifiers are integers!");
350
                }
351
                $entity = $validator->IdP($identifier);
352
                $attributeName = "general:logo_file";
353
                break;
354
            default:
355
                throw new Exception("Unknown type of logo requested!");
356
        }
357
        $filetype = 'image/png'; // default, only one code path where it can become different
358
        if (is_numeric($width) && is_numeric($height) && ($width > 0 || $height > 0)) {
359
            $resize = TRUE;
360
            if ($height == 0) {
361
                $height = 10000;
362
            }
363
            if ($width == 0) {
364
                $width = 10000;
365
            }
366
            $logoFile = ROOT . '/web/downloads/logos/' . $identifier . '_' . $width . '_' . $height . '.png';
367
        }
368
        // we never use cache for full-scale images
369
        if ($resize === TRUE && is_file($logoFile)) {
370
            $this->loggerInstance->debug(4, "Using cached logo $logoFile for: $identifier\n");
371
            $blob = file_get_contents($logoFile);
372
        } else {
373
            $logoAttribute = $entity->getAttributes($attributeName);
374
            if (count($logoAttribute) == 0) {
375
                return(NULL);
376
            }
377
            $this->loggerInstance->debug(4,"RESIZE:$width:$height\n");
378
            $meta = $this->processImage($logoAttribute[0]['value'], $logoFile, $width, $height, $resize);
379
            $filetype = $meta['filetype'];
380
            $expiresString = $meta['expires'];
381
            $blob = $meta['blob'];
382
        }
383
        return ["filetype" => $filetype, "expires" => $expiresString, "blob" => $blob];
384
    }
385
    
386
    /**
387
     * find out where the user is currently located
388
     * @return array
389
     */
390
    public function locateUser() {
391
        $geoipVersion = CONFIG['GEOIP']['version'] ?? 0;
392
        switch ($geoipVersion) {
393
            case 0:
394
                return(['status' => 'error', 'error' => 'Geolocation not supported']);
395
            case 1:
396
                return($this->locateUser1());
397
            case 2:
398
                return($this->locateUser2());
399
            default:
400
                throw new Exception("This version of GeoIP is not known!");
401
        }
402
    }
403
    
404
    private function locateUser1() {
405 View Code Duplication
        if (CONFIG['GEOIP']['version'] != 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
406
            return ['status' => 'error', 'error' => 'Function for GEOIPv1 called, but config says this is not the version to use!'];
407
        }
408
        //$host = $_SERVER['REMOTE_ADDR'];
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
409
        $host = filter_input(INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP);
410
        $record = geoip_record_by_name($host);
411
        if ($record === FALSE) {
412
            return ['status' => 'error', 'error' => 'Problem listing countries'];
413
        }
414
        $result = ['status' => 'ok'];
415
        $result['country'] = $record['country_code'];
416
//  the two lines below are a dirty hack to take of the error in naming the UK federation
417
        if ($result['country'] == 'GB') {
418
            $result['country'] = 'UK';
419
        }
420
        $result['region'] = $record['region'];
421
        $result['geo'] = ['lat' => (float) $record['latitude'], 'lon' => (float) $record['longitude']];
422
        return($result);
423
    }
424
    
425
    /**
426
     * find out where the user is currently located, using GeoIP2
427
     * @return array
428
     */
429
    private function locateUser2() {
430 View Code Duplication
        if (CONFIG['GEOIP']['version'] != 2) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
431
            return ['status' => 'error', 'error' => 'Function for GEOIPv2 called, but config says this is not the version to use!'];
432
        }
433
        require_once CONFIG['GEOIP']['geoip2-path-to-autoloader'];
434
        $reader = new Reader(CONFIG['GEOIP']['geoip2-path-to-db']);
435
        $host = $_SERVER['REMOTE_ADDR'];
436
        try {
437
            $record = $reader->city($host);
438
        } catch (\Exception $e) {
439
            $result = ['status' => 'error', 'error' => 'Problem listing countries'];
440
            return($result);
441
        }
442
        $result = ['status' => 'ok'];
443
        $result['country'] = $record->country->isoCode;
444
//  the two lines below are a dirty hack to take of the error in naming the UK federation
445
        if ($result['country'] == 'GB') {
446
            $result['country'] = 'UK';
447
        }
448
        $result['region'] = $record->continent->name;
449
450
        $result['geo'] = ['lat' => (float) $record->location->latitude, 'lon' => (float) $record->location->longitude];
451
        return($result);
452
    }
453
454
    /**
455
     * Calculate the distance in km between two points given their
456
     * geo coordinates.
457
     * @param array $point1 - first point as an 'lat', 'lon' array 
458
     * @param array $profile1 - second point as an 'lat', 'lon' array 
459
     * @return float distance in km
460
     */
461
    private function geoDistance($point1, $profile1) {
462
463
        $distIntermediate = sin(deg2rad($point1['lat'])) * sin(deg2rad($profile1['lat'])) +
464
                cos(deg2rad($point1['lat'])) * cos(deg2rad($profile1['lat'])) * cos(deg2rad($point1['lon'] - $profile1['lon']));
465
        $dist = rad2deg(acos($distIntermediate)) * 60 * 1.1852;
466
        return(round($dist));
467
    }
468
469
    /**
470
     * Order active identity providers according to their distance and name
471
     * @param array $currentLocation - current location
472
     * @return array $IdPs -  list of arrays ('id', 'name');
473
     */
474
    public function orderIdentityProviders($country, $currentLocation = NULL) {
475
        $idps = $this->listAllIdentityProviders(1, $country);
476
477
        if (is_null($currentLocation)) {
478
            $currentLocation = ['lat' => "90", 'lon' => "0"];
479
            $userLocation = $this->locateUser();
480
            if ($userLocation['status'] == 'ok') {
481
                $currentLocation = $userLocation['geo'];
482
            }
483
        }
484
        $idpTitle = [];
485
        $resultSet = [];
486
        foreach ($idps as $idp) {
487
            $idpTitle[$idp['entityID']] = $idp['title'];
488
            $dist = 10000;
489
            if (isset($idp['geo'])) {
490
                $G = $idp['geo'];
491
                if (isset($G['lon'])) {
492
                    $d1 = $this->geoDistance($currentLocation, $G);
493
                    if ($d1 < $dist) {
494
                        $dist = $d1;
495
                    }
496
                } else {
497
                    foreach ($G as $g) {
498
                        $d1 = $this->geoDistance($currentLocation, $g);
499
                        if ($d1 < $dist) {
500
                            $dist = $d1;
501
                        }
502
                    }
503
                }
504
            }
505
            if ($dist > 100) {
506
                $dist = 10000;
507
            }
508
            $d = sprintf("%06d", $dist);
509
            $resultSet[$idp['entityID']] = $d . " " . $idp['title'];
510
        }
511
        asort($resultSet);
512
        $outarray = [];
513
        foreach (array_keys($resultSet) as $r) {
514
            $outarray[] = ['idp' => $r, 'title' => $idpTitle[$r]];
515
        }
516
        return($outarray);
517
    }
518
519
    /**
520
     * Detect the best device driver form the browser
521
     *
522
     * Detects the operating system and returns its id 
523
     * display name and group membership (as in devices.php)
524
     * @return array|false OS information, indexed by 'id', 'display', 'group'
525
     */
526
    public function detectOS() {
527
        $oldDomain = $this->languageInstance->setTextDomain("devices");
528
        $Dev = \devices\Devices::listDevices();
529
        $this->languageInstance->setTextDomain($oldDomain);
530
        $devId = $this->deviceFromRequest();
531
        if ($devId !== FALSE) {
532
            $ret = $this->returnDevice($devId, $Dev[$devId]);
533
            if ($ret !== FALSE) {
534
                return($ret);
535
            } 
536
        }
537
// the device has not been specified or not specified correctly, try to detect if from the browser ID
538
        $browser = filter_input(INPUT_SERVER, 'HTTP_USER_AGENT', FILTER_SANITIZE_STRING);
539
        $this->loggerInstance->debug(4, "HTTP_USER_AGENT=$browser\n");
540
        foreach ($Dev as $devId => $device) {
541
            if (!isset($device['match'])) {
542
                continue;
543
            }
544
            if (preg_match('/' . $device['match'] . '/', $browser)) {
545
                return ($this->returnDevice($devId, $device));
546
            }
547
        }
548
        $this->loggerInstance->debug(2, "Unrecognised system: $browser\n");
549
        return(false);
550
    }
551
    
552
    /*
553
     * test if devise is defined and is not hidden. If all is fine return extracted information.
554
     * Return FALSE if the device has not been correctly specified
555
     */
556
    private function returnDevice($devId, $device) {
557
        if (!isset($device['options']['hidden']) || $device['options']['hidden'] == 0) {
558
            $this->loggerInstance->debug(4, "Browser_id: $devId\n");
559
            return(['device' => $devId, 'display' => $device['display'], 'group' => $device['group']]);
560
        }
561
        return(FALSE);
562
    }
563
   
564
    /**
565
     * This methods cheks if the devide has been specified as the HTTP parameters
566
     * @return device id is correcty specified or FALSE otherwise
567
     */
568
    private function deviceFromRequest() {
569
        $devId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_STRING) ?? filter_input(INPUT_POST, 'device', FILTER_SANITIZE_STRING);
570
        if ($devId === NULL || $devId === FALSE) {
571
            $this->loggerInstance->debug(2, "Invalid device id provided\n");
572
            return(FALSE);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return FALSE; (false) is incompatible with the return type documented by core\UserAPI::deviceFromRequest of type core\device.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
573
        }
574
        $Dev = \devices\Devices::listDevices();
575
        if (!isset($Dev['$devId'])) {
576
            $this->loggerInstance->debug(2, "Unrecognised system: $devId\n");
577
            return(FALSE);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return FALSE; (false) is incompatible with the return type documented by core\UserAPI::deviceFromRequest of type core\device.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
578
        }
579
        return($devId);
580
    }
581
582
    /**
583
     * finds all the user certificates that originated in a given token
584
     * @param string $token
585
     * @return array|false returns FALSE if a token is invalid, otherwise array of certs
586
     */
587
    public function getUserCerts($token) {
588
        $validator = new \web\lib\common\InputValidation();
589
        $cleanToken = $validator->token($token);
590
        if ($cleanToken) {
591
            // check status of this silverbullet token according to info in DB:
592
            // it can be VALID (exists and not redeemed, EXPIRED, REDEEMED or INVALID (non existent)
593
            $tokenStatus = \core\ProfileSilverbullet::tokenStatus($cleanToken);
594
        } else {
595
            return false;
596
        }
597
        $profile = new \core\ProfileSilverbullet($tokenStatus['profile'], NULL);
598
        $userdata = $profile->userStatus($tokenStatus['db_id']);
599
        $allcerts = [];
600
        foreach ($userdata as $index => $content) {
601
            $allcerts = array_merge($allcerts, $content['cert_status']);
602
        }
603
        return $allcerts;
604
    }
605
606
607
    public $device;
608
    private $installerPath;
609
610
    /**
611
     * helper function to sort profiles by their name
612
     * @param \core\AbstractProfile $profile1 the first profile's information
613
     * @param \core\AbstractProfile $profile2 the second profile's information
614
     * @return int
615
     */
616
    private static function profileSort($profile1, $profile2) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
617
        return strcasecmp($profile1->name, $profile2->name);
618
    }
619
620
}
621