Passed
Push — master ( 1974f7...769154 )
by Tomasz
04:46
created

UserAPI::detectOS()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 25
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 18
nc 9
nop 0
dl 0
loc 25
rs 8.439
c 0
b 0
f 0
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 \Exception;
26
27
/**
28
 * The basic methoods for the user GUI
29
 * @package UserAPI
30
 *
31
 */
32
class UserAPI extends CAT {
33
34
    /**
35
     * nothing special to be done here.
36
     */
37
    public function __construct() {
38
        parent::__construct();
39
    }
40
41
    /**
42
     * Prepare the device module environment and send back the link
43
     * This method creates a device module instance via the {@link DeviceFactory} call, 
44
     * then sets up the device module environment for the specific profile by calling 
45
     * {@link DeviceConfig::setup()} method and finally, called the devide writeInstaller meethod
46
     * passing the returned path name.
47
     * 
48
     * @param string $device identifier as in {@link devices.php}
49
     * @param int $profileId profile identifier
50
     *
51
     * @return array 
52
     *  array with the following fields: 
53
     *  profile - the profile identifier; 
54
     *  device - the device identifier; 
55
     *  link - the path name of the resulting installer
56
     *  mime - the mimetype of the installer
57
     */
58
    public function generateInstaller($device, $profileId, $generatedFor = "user", $token = NULL, $password = NULL) {
59
        $this->languageInstance->setTextDomain("devices");
60
        $this->loggerInstance->debug(4, "installer:$device:$profileId\n");
61
        $validator = new \web\lib\common\InputValidation();
62
        $profile = $validator->Profile($profileId);
63
        // test if the profile is production-ready and if not if the authenticated user is an owner
64
        if ($this->verifyDownloadAccess($profile) === FALSE) {
65
            return;
66
        }
67
        $installerProperties = [];
68
        $installerProperties['profile'] = $profileId;
69
        $installerProperties['device'] = $device;
70
        $cache = $this->getCache($device, $profile);
71
        $this->installerPath = $cache['path'];
72
        if ($this->installerPath && $token == NULL && $password == NULL) {
73
            $this->loggerInstance->debug(4, "Using cached installer for: $device\n");
74
            $installerProperties['link'] = "API.php?action=downloadInstaller&lang=" . $this->languageInstance->getLang() . "&profile=$profileId&device=$device&generatedfor=$generatedFor";
75
            $installerProperties['mime'] = $cache['mime'];
76
        } else {
77
            $myInstaller = $this->generateNewInstaller($device, $profile, $generatedFor, $token, $password);
78
            if ($myInstaller['link'] !== 0) {
79
                $installerProperties['mime'] = $myInstaller['mime'];
80
            }
81
            $installerProperties['link'] = $myInstaller['link'];
82
        }
83
        $this->languageInstance->setTextDomain("web_user");
84
        return($installerProperties);
85
    }
86
    
87
    private function verifyDownloadAccess($profile) {
88
        $attribs = $profile->getCollapsedAttributes();
89
        if (\core\common\Entity::getAttributeValue($attribs, 'profile:production', 0) !== 'on') {
90
            $this->loggerInstance->debug(4, "Attempt to download a non-production ready installer for profile: $profile->identifier\n");
91
            $auth = new \web\lib\admin\Authentication();
92
            if (!$auth->isAuthenticated()) {
93
                $this->loggerInstance->debug(2, "User NOT authenticated, rejecting request for a non-production installer\n");
94
                header("HTTP/1.0 403 Not Authorized");
95
                return(FALSE);
96
            }
97
            $userObject = new User($_SESSION['user']);
98
            if (!$userObject->isIdPOwner($profile->institution)) {
99
                $this->loggerInstance->debug(2, "User not an owner of a non-production profile - access forbidden\n");
100
                header("HTTP/1.0 403 Not Authorized");
101
                return(FALSE);
102
            }
103
            $this->loggerInstance->debug(4, "User is the owner - allowing access\n");
104
        }
105
        return(TRUE);
106
    }
107
108
    /**
109
     * This function tries to find a cached copy of an installer for a given
110
     * combination of Profile and device
111
     * @param string $device
112
     * @param AbstractProfile $profile
113
     * @return boolean|string the string with the path to the cached copy, or FALSE if no cached copy exists
114
     */
115
    private function getCache($device, $profile) {
116
        $deviceList = \devices\Devices::listDevices();
117
        $deviceConfig = $deviceList[$device];
118
        $noCache = (isset(\devices\Devices::$Options['no_cache']) && \devices\Devices::$Options['no_cache']) ? 1 : 0;
119
        if (isset($deviceConfig['options']['no_cache'])) {
120
            $noCache = $deviceConfig['options']['no_cache'] ? 1 : 0;
121
        }
122
        if ($noCache) {
123
            $this->loggerInstance->debug(5, "getCache: the no_cache option set for this device\n");
124
            return(FALSE);
125
        }
126
        $this->loggerInstance->debug(5, "getCache: caching option set for this device\n");
127
        $cache = $profile->testCache($device);
128
        $iPath = $cache['cache'];
129
        if ($iPath && is_file($iPath)) {
130
            return(['path' => $iPath, 'mime' => $cache['mime']]);
131
        }
132
        return(FALSE);
133
    }
134
135
    /**
136
     * Generates a new installer for the given combination of device and Profile
137
     * 
138
     * @param string $device
139
     * @param AbstractProfile $profile
140
     * @return array info about the new installer (mime and link)
141
     */
142
    private function generateNewInstaller($device, $profile, $generatedFor, $token, $password) {
143
        $this->loggerInstance->debug(5, "generateNewInstaller() - Enter");
144
        $factory = new DeviceFactory($device);
145
        $this->loggerInstance->debug(5, "generateNewInstaller() - created Device");
146
        $dev = $factory->device;
147
        $out = [];
148
        if (isset($dev)) {
149
            $dev->setup($profile, $token, $password);
150
            $this->loggerInstance->debug(5, "generateNewInstaller() - Device setup done");
151
            $installer = $dev->writeInstaller();
152
            $this->loggerInstance->debug(5, "generateNewInstaller() - writeInstaller complete");
153
            $iPath = $dev->FPATH . '/tmp/' . $installer;
154
            if ($iPath && is_file($iPath)) {
155
                if (isset($dev->options['mime'])) {
156
                    $out['mime'] = $dev->options['mime'];
157
                } else {
158
                    $info = new \finfo();
159
                    $out['mime'] = $info->file($iPath, FILEINFO_MIME_TYPE);
160
                }
161
                $this->installerPath = $dev->FPATH . '/' . $installer;
162
                rename($iPath, $this->installerPath);
163
                $integerEap = (new \core\common\EAP($dev->selectedEap))->getIntegerRep();
164
                $profile->updateCache($device, $this->installerPath, $out['mime'], $integerEap);
165
                if (CONFIG['DEBUG_LEVEL'] < 4) {
166
                    \core\common\Entity::rrmdir($dev->FPATH . '/tmp');
167
                }
168
                $this->loggerInstance->debug(4, "Generated installer: " . $this->installerPath . ": for: $device, EAP:" . $integerEap . "\n");
169
                $out['link'] = "API.php?action=downloadInstaller&lang=" . $this->languageInstance->getLang() . "&profile=" . $profile->identifier . "&device=$device&generatedfor=$generatedFor";
170
            } else {
171
                $this->loggerInstance->debug(2, "Installer generation failed for: " . $profile->identifier . ":$device:" . $this->languageInstance->getLang() . "\n");
172
                $out['link'] = 0;
173
            }
174
        }
175
        return($out);
176
    }
177
178
    /**
179
     * interface to Devices::listDevices() 
180
     */
181
    public function listDevices($showHidden = 0) {
182
        $dev = \devices\Devices::listDevices();
183
        $returnList = [];
184
        $count = 0;
185
        if ($showHidden !== 0 && $showHidden != 1) {
186
            throw new Exception("show_hidden is only be allowed to be 0 or 1, but it is $showHidden!");
187
        }
188
        foreach ($dev as $device => $deviceProperties) {
189
            if (\core\common\Entity::getAttributeValue($deviceProperties, 'options', 'hidden') === 1 && $showHidden === 0) {
190
                continue;
191
            }
192
            $count++;
193
            $deviceProperties['device'] = $device;
194
            $group = isset($deviceProperties['group']) ? $deviceProperties['group'] : 'other';
195
            if (!isset($returnList[$group])) {
196
                $returnList[$group] = [];
197
            }
198
            $returnList[$group][$device] = $deviceProperties;
199
        }
200
        return $returnList;
201
    }
202
203
    /**
204
     * 
205
     * @param string $device
206
     * @param int $profileId
207
     */
208
    public function deviceInfo($device, $profileId) {
209
        $this->languageInstance->setTextDomain("devices");
210
        $validator = new \web\lib\common\InputValidation();
211
        $out = 0;
212
        $profile = $validator->Profile($profileId);
213
        $factory = new DeviceFactory($device);
214
        $dev = $factory->device;
215
        if (isset($dev)) {
216
            $dev->setup($profile);
217
            $out = $dev->writeDeviceInfo();
218
        }
219
        $this->languageInstance->setTextDomain("web_user");
220
        echo $out;
221
    }
222
223
    /**
224
     * Prepare the support data for a given profile
225
     *
226
     * @param int $profId profile identifier
227
     * @return array
228
     * array with the following fields:
229
     * - local_email
230
     * - local_phone
231
     * - local_url
232
     * - description
233
     * - devices - an array of device names and their statuses (for a given profile)
234
     */
235
    public function profileAttributes($profId) {
236
        $this->languageInstance->setTextDomain("devices");
237
        $validator = new \web\lib\common\InputValidation();
238
        $profile = $validator->Profile($profId);
239
        $attribs = $profile->getCollapsedAttributes();
240
        $returnArray = [];
241
        $returnArray['silverbullet'] = $profile instanceof ProfileSilverbullet ? 1 : 0;
242
        if (isset($attribs['support:email'])) {
243
            $returnArray['local_email'] = $attribs['support:email'][0];
244
        }
245
        if (isset($attribs['support:phone'])) {
246
            $returnArray['local_phone'] = $attribs['support:phone'][0];
247
        }
248
        if (isset($attribs['support:url'])) {
249
            $returnArray['local_url'] = $attribs['support:url'][0];
250
        }
251
        if (isset($attribs['profile:description'])) {
252
            $returnArray['description'] = $attribs['profile:description'][0];
253
        }
254
        $returnArray['devices'] = $profile->listDevices();
255
        $this->languageInstance->setTextDomain("web_user");
256
        return($returnArray);
257
    }
258
259
    /**
260
     * Generate and send the installer
261
     *
262
     * @param string $device identifier as in {@link devices.php}
263
     * @param int $prof_id profile identifier
264
     * @return string binary stream: installerFile
265
     */
266
    public function downloadInstaller($device, $prof_id, $generated_for = 'user', $token = NULL, $password = NULL) {
267
        $this->loggerInstance->debug(4, "downloadInstaller arguments: $device,$prof_id,$generated_for\n");
268
        $output = $this->generateInstaller($device, $prof_id, $generated_for, $token, $password);
269
        $this->loggerInstance->debug(4, "output from GUI::generateInstaller:");
270
        $this->loggerInstance->debug(4, print_r($output, true));
271
        if (empty($output['link']) || $output['link'] === 0) {
272
            header("HTTP/1.0 404 Not Found");
273
            return;
274
        }
275
        $validator = new \web\lib\common\InputValidation();
276
        $profile = $validator->Profile($prof_id);
277
        $profile->incrementDownloadStats($device, $generated_for);
278
        $file = $this->installerPath;
279
        $filetype = $output['mime'];
280
        $this->loggerInstance->debug(4, "installer MIME type:$filetype\n");
281
        header("Content-type: " . $filetype);
282
        header('Content-Disposition: inline; filename="' . basename($file) . '"');
283
        header('Content-Length: ' . filesize($file));
284
        ob_clean();
285
        flush();
286
        readfile($file);
287
    }
288
289
    /**
290
     * resizes image files
291
     * 
292
     * @param string $inputImage
293
     * @param string $destFile
294
     * @param int $width
295
     * @param int $height
296
     * @param bool $resize shall we do resizing? width and height are ignored otherwise
297
     * @return array
298
     */
299
    private function processImage($inputImage, $destFile, $width, $height, $resize) {
300
        $info = new \finfo();
301
        $filetype = $info->buffer($inputImage, FILEINFO_MIME_TYPE);
302
        $offset = 60 * 60 * 24 * 30;
303
        // gmdate cannot fail here - time() is its default argument (and integer), and we are adding an integer to it
304
        $expiresString = "Expires: " . /** @scrutinizer ignore-type */ gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
305
        $blob = $inputImage;
306
307
        if ($resize === TRUE) {
308
            $image = new \Imagick();
309
            $image->readImageBlob($inputImage);
310
            $image->setImageFormat('PNG');
311
            $image->thumbnailImage($width, $height, 1);
312
            $blob = $image->getImageBlob();
313
            $this->loggerInstance->debug(4, "Writing cached logo $destFile for IdP/Federation.\n");
314
            file_put_contents($destFile, $blob);
315
        }
316
317
        return ["filetype" => $filetype, "expires" => $expiresString, "blob" => $blob];
318
    }
319
320
    /**
321
     * Get and prepare logo file 
322
     *
323
     * When called for DiscoJuice, first check if file cache exists
324
     * If not then generate the file and save it in the cache
325
     * @param int $identifier IdP of Federation identifier
326
     * @param string either 'idp' or 'federation' is allowed 
327
     * @param int $width maximum width of the generated image - if 0 then it is treated as no upper bound
328
     * @param int $height  maximum height of the generated image - if 0 then it is treated as no upper bound
329
     * @return array|null array with image information or NULL if there is no logo
330
     */
331
    protected function getLogo($identifier, $type, $width = 0, $height = 0) {
332
        $expiresString = '';
333
        $resize = FALSE;
334
        $attributeName = [
335
            'federation' => "fed:logo_file",
336
            'idp' => "general:logo_file",
337
        ];
338
        
339
        $logoFile = "";
340
        $validator = new \web\lib\common\InputValidation();
341
        switch ($type) {
342
            case "federation":
343
                $entity = $validator->Federation($identifier);
344
                break;
345
            case "idp":
346
                $entity = $validator->IdP($identifier);
347
                break;
348
            default:
349
                throw new Exception("Unknown type of logo requested!");
350
        }
351
        $filetype = 'image/png'; // default, only one code path where it can become different
352
        list($width, $height, $resize) = $this->testForResize($width, $height);
353
        if ($resize) {
354
            $logoFile = ROOT . '/web/downloads/logos/' . $identifier . '_' . $width . '_' . $height . '.png';
355
        }
356
        if (is_file($logoFile)) { // $logoFile could be an empty string but then we will get a FALSE
357
            $this->loggerInstance->debug(4, "Using cached logo $logoFile for: $identifier\n");
358
            $blob = file_get_contents($logoFile);
359
        } else {
360
            $logoAttribute = $entity->getAttributes($attributeName[$type]);
361
            if (count($logoAttribute) == 0) {
362
                return(NULL);
363
            }
364
            $this->loggerInstance->debug(4,"RESIZE:$width:$height\n");
365
            $meta = $this->processImage($logoAttribute[0]['value'], $logoFile, $width, $height, $resize);
366
            $filetype = $meta['filetype'];
367
            $expiresString = $meta['expires'];
368
            $blob = $meta['blob'];
369
        }
370
        return ["filetype" => $filetype, "expires" => $expiresString, "blob" => $blob];
371
    }
372
    
373
    private function testForResize($width, $height) {
374
        if (is_numeric($width) && is_numeric($height) && ($width > 0 || $height > 0)) {
375
            if ($height == 0) {
376
                $height = 10000;
377
            }
378
            if ($width == 0) {
379
                $width = 10000;
380
            }
381
            $resize = TRUE;
382
        } else {
383
            $width = 0;
384
            $height = 0;
385
            $resize = FALSE;
386
        }
387
        return ([$width, $height, $resize]);
388
    }
389
390
    /**
391
     * find out where the user is currently located
392
     * @return array
393
     */
394
    public function locateUser() {
395
        $loc = new \core\UserLocation();
396
        return($loc->location);
397
    }
398
399
400
    /**
401
     * Calculate the distance in km between two points given their
402
     * geo coordinates.
403
     * @param array $point1 - first point as an 'lat', 'lon' array 
404
     * @param array $profile1 - second point as an 'lat', 'lon' array 
405
     * @return float distance in km
406
     */
407
    private function geoDistance($point1, $profile1) {
408
409
        $distIntermediate = sin(deg2rad($point1['lat'])) * sin(deg2rad($profile1['lat'])) +
410
                cos(deg2rad($point1['lat'])) * cos(deg2rad($profile1['lat'])) * cos(deg2rad($point1['lon'] - $profile1['lon']));
411
        $dist = rad2deg(acos($distIntermediate)) * 60 * 1.1852;
412
        return(round($dist));
413
    }
414
415
    /**
416
     * Order active identity providers according to their distance and name
417
     * @param array $currentLocation - current location
418
     * @return array $IdPs -  list of arrays ('id', 'name');
419
     */
420
    public function orderIdentityProviders($country, $currentLocation = NULL) {
421
        $idps = $this->listAllIdentityProviders(1, $country);
422
        $here = $this->setCurrentLocation($currentLocation);
423
        $idpTitle = [];
424
        $resultSet = [];
425
        foreach ($idps as $idp) {
426
            $idpTitle[$idp['entityID']] = $idp['title'];
427
            $d = $this->getIdpDistance($idp, $here);
428
            $resultSet[$idp['entityID']] = $d . " " . $idp['title'];
429
        }
430
        asort($resultSet);
431
        $outarray = [];
432
        foreach (array_keys($resultSet) as $r) {
433
            $outarray[] = ['idp' => $r, 'title' => $idpTitle[$r]];
434
        }
435
        return($outarray);
436
    }
437
438
    private function setCurrentLocation($currentLocation) {
439
        if (is_null($currentLocation)) {
440
            $currentLocation = ['lat' => "90", 'lon' => "0"];
441
            $userLocation = $this->locateUser();
442
            if ($userLocation['status'] == 'ok') {
443
                $currentLocation = $userLocation['geo'];
444
            }
445
        }
446
        return($currentLocation);
447
    }
448
    
449
    private function getIdpDistance($idp, $location) {
450
        $dist = 10000;
451
        if (isset($idp['geo'])) {
452
            $G = $idp['geo'];
453
            if (isset($G['lon'])) {
454
                $d1 = $this->geoDistance($location, $G);
455
                if ($d1 < $dist) {
456
                    $dist = $d1;
457
                }
458
            } else {
459
                foreach ($G as $g) {
460
                    $d1 = $this->geoDistance($location, $g);
461
                    if ($d1 < $dist) {
462
                        $dist = $d1;
463
                    }
464
                }
465
            }
466
        }
467
        if ($dist > 100) {
468
            $dist = 10000;
469
        }
470
        return(sprintf("%06d", $dist));
471
    }
472
473
    /**
474
     * Detect the best device driver form the browser
475
     * Detects the operating system and returns its id 
476
     * display name and group membership (as in devices.php)
477
     * @return array|FALSE OS information, indexed by 'id', 'display', 'group'
478
     */
479
    public function detectOS() {
480
        $oldDomain = $this->languageInstance->setTextDomain("devices");
481
        $Dev = \devices\Devices::listDevices();
482
        $this->languageInstance->setTextDomain($oldDomain);
483
        $devId = $this->deviceFromRequest();
484
        if ($devId !== FALSE) {
485
            $ret = $this->returnDevice($devId, $Dev[$devId]);
486
            if ($ret !== FALSE) {
487
                return($ret);
488
            } 
489
        }
490
// the device has not been specified or not specified correctly, try to detect if from the browser ID
491
        $browser = filter_input(INPUT_SERVER, 'HTTP_USER_AGENT', FILTER_SANITIZE_STRING);
492
        $this->loggerInstance->debug(4, "HTTP_USER_AGENT=$browser\n");
493
        foreach ($Dev as $devId => $device) {
494
            if (!isset($device['match'])) {
495
                continue;
496
            }
497
            if (preg_match('/' . $device['match'] . '/', $browser)) {
498
                return ($this->returnDevice($devId, $device));
499
            }
500
        }
501
        $this->loggerInstance->debug(2, "Unrecognised system: $browser\n");
502
        return(false);
503
    }
504
    
505
    /*
506
     * test if devise is defined and is not hidden. If all is fine return extracted information.
507
     * Return FALSE if the device has not been correctly specified
508
     */
509
    private function returnDevice($devId, $device) {
510
        if (\core\common\Entity::getAttributeValue($device, 'options', 'hidden') !== 1) {
511
            $this->loggerInstance->debug(4, "Browser_id: $devId\n");
512
            return(['device' => $devId, 'display' => $device['display'], 'group' => $device['group']]);
513
        }
514
        return(FALSE);
515
    }
516
   
517
    /**
518
     * This methods cheks if the devide has been specified as the HTTP parameters
519
     * @return device id|FALSE if correcty specified or FALSE otherwise
520
     */
521
    private function deviceFromRequest() {
522
        $devId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_STRING) ?? filter_input(INPUT_POST, 'device', FILTER_SANITIZE_STRING);
523
        if ($devId === NULL || $devId === FALSE) {
524
            $this->loggerInstance->debug(2, "Invalid device id provided\n");
525
            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...
526
        }
527
        $Dev = \devices\Devices::listDevices();
528
        if (!isset($Dev['$devId'])) {
529
            $this->loggerInstance->debug(2, "Unrecognised system: $devId\n");
530
            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...
531
        }
532
        return($devId);
533
    }
534
535
    /**
536
     * finds all the user certificates that originated in a given token
537
     * @param string $token
538
     * @return array|false returns FALSE if a token is invalid, otherwise array of certs
539
     */
540
    public function getUserCerts($token) {
541
        $validator = new \web\lib\common\InputValidation();
542
        $cleanToken = $validator->token($token);
543
        if ($cleanToken) {
544
            // check status of this silverbullet token according to info in DB:
545
            // it can be VALID (exists and not redeemed, EXPIRED, REDEEMED or INVALID (non existent)
546
            $invitationObject = new \core\SilverbulletInvitation($cleanToken);
547
        } else {
548
            return false;
549
        }
550
        $profile = new \core\ProfileSilverbullet($invitationObject->profile, NULL);
551
        $userdata = $profile->userStatus($invitationObject->userId);
552
        $allcerts = [];
553
        foreach ($userdata as $content) {
554
            $allcerts = array_merge($allcerts, $content->associatedCertificates);
555
        }
556
        return $allcerts;
557
    }
558
559
    public $device;
560
    private $installerPath;
561
562
    /**
563
     * helper function to sort profiles by their name
564
     * @param \core\AbstractProfile $profile1 the first profile's information
565
     * @param \core\AbstractProfile $profile2 the second profile's information
566
     * @return int
567
     */
568
    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...
569
        return strcasecmp($profile1->name, $profile2->name);
570
    }
571
572
}
573