InputValidation   F
last analyzed

Complexity

Total Complexity 100

Size/Duplication

Total Lines 631
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
wmc 100
eloc 225
c 5
b 1
f 0
dl 0
loc 631
rs 2

27 Methods

Rating   Name   Duplication   Size   Complexity  
A inputValidationError() 0 6 1
A existingFederation() 0 22 5
A existingProfile() 0 12 4
A existingDevice() 0 9 2
A existingIdP() 0 16 5
B existingFederationInt() 0 25 7
A existingDeploymentManaged() 0 12 3
B existingIdPInt() 0 28 10
A integer() 0 6 2
A partType() 0 11 4
A hugeInteger() 0 6 2
A databaseReference() 0 11 3
A string() 0 29 6
A email() 0 8 2
A consortiumOI() 0 10 4
A supportedLanguage() 0 12 3
A optionName() 0 4 1
A realm() 0 40 5
A image() 0 15 3
A simpleInputFilter() 0 18 4
A coordJsonEncoded() 0 9 6
A hostname() 0 9 3
A syntaxConformUser() 0 14 4
A coordinate() 0 13 4
A sms() 0 7 2
A boolean() 0 6 2
A token() 0 7 3

How to fix   Complexity   

Complex Class

Complex classes like InputValidation often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use InputValidation, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * *****************************************************************************
5
 * Contributions to this work were made on behalf of the GÉANT project, a 
6
 * project that has received funding from the European Union’s Framework 
7
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
8
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
9
 * 691567 (GN4-1) and No. 731122 (GN4-2).
10
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
11
 * of the copyright in all material which was developed by a member of the GÉANT
12
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
13
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
14
 * UK as a branch of GÉANT Vereniging.
15
 * 
16
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
17
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
18
 *
19
 * License: see the web/copyright.inc.php file in the file structure or
20
 *          <base_url>/copyright.php after deploying the software
21
 */
22
23
namespace web\lib\common;
24
25
use \Exception;
0 ignored issues
show
Bug introduced by
The type \Exception was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
27
/**
28
 * performs validation of user inputs
29
 */
30
class InputValidation extends \core\common\Entity
31
{
32
33
    /**
34
     * returns a simple HTML <p> element with basic explanations about what was
35
     * wrong with the input
36
     * 
37
     * @param string $customtext explanation provided by the validator function
38
     * @return string
39
     */
40
    private function inputValidationError($customtext)
41
    {
42
        \core\common\Entity::intoThePotatoes();
43
        $retval = "<p>" . _("Input validation error: ") . $customtext . "</p>";
44
        \core\common\Entity::outOfThePotatoes();
45
        return $retval;
46
    }
47
48
    /**
49
     * Is this a known Federation? Optionally, also check if the authenticated
50
     * user is a federation admin of that federation
51
     * @param mixed       $input the ISO code of the federation
52
     * @param string|NULL $owner the authenticated username, optional
53
     * @return \core\Federation
54
     * @throws Exception
55
     */
56
    public function existingFederation($input, $owner = NULL)
57
    {
58
        $cat = new \core\CAT(); // initialises Entity static members
59
        $fedIdentifiers = array_keys($cat->knownFederations);
60
        if (!in_array(strtoupper($input), $fedIdentifiers)) {
61
            throw new Exception($this->inputValidationError(sprintf("This %s does not exist!", \core\common\Entity::$nomenclature_fed)));
62
        }
63
        // totally circular, but this hopefully *finally* make Scrutinizer happier
64
        $correctIndex = array_search(strtoupper($input), $fedIdentifiers);
65
        $postFed = $fedIdentifiers[$correctIndex];
66
67
        $temp = new \core\Federation($postFed);
68
        if ($owner === NULL) {
69
            return $temp;
70
        }
71
72
        foreach ($temp->listFederationAdmins() as $oneowner) {
73
            if ($oneowner == $owner) {
74
                return $temp;
75
            }
76
        }
77
        throw new Exception($this->inputValidationError(sprintf("User is not %s administrator!", \core\common\Entity::$nomenclature_fed)));
78
    }
79
80
    /**
81
     * Is this a known Federation? Optionally, also check if the authenticated
82
     * user is a federation admin of that federation
83
     * @param mixed       $input the ISO code of the federation
84
     * @param string|NULL $owner the authenticated username, optional
85
     * @return array(\core\Federation, string)
86
     * @throws Exception
87
     */
88
    public function existingFederationInt($input, $owner = NULL)
89
    {
90
        $cat = new \core\CAT(); // initialises Entity static members
91
        $fedIdentifiers = array_keys($cat->knownFederations);
92
        if (!in_array(strtoupper($input), $fedIdentifiers)) {
93
            throw new Exception($this->inputValidationError(sprintf("This %s does not exist!", \core\common\Entity::$nomenclature_fed)));
94
        }
95
        // totally circular, but this hopefully *finally* make Scrutinizer happier
96
        $correctIndex = array_search(strtoupper($input), $fedIdentifiers);
97
        $postFed = $fedIdentifiers[$correctIndex];
98
        $temp = new \core\Federation($postFed);
99
        if ($owner === NULL) {
100
            return [$temp,'readonly'];
101
        }
102
        $user = new \core\User($owner);        
103
        foreach ($temp->listFederationAdmins() as $oneowner) {
104
            if ($oneowner == $owner) {
105
                return [$temp, 'fullaccess'];
106
            }
107
        }
108
        if ($user->isSuperadmin()|| $user->isSupport()) {
109
                $this->loggerInstance->debug(4, "You are the superadmin/support\n");
110
                return [$temp,'readonly'];                
111
            }
112
        throw new Exception($this->inputValidationError(sprintf("User is not %s administrator!", \core\common\Entity::$nomenclature_fed)));
113
    }
114
    
115
    /**
116
     * Is this a known IdP? Optionally, also check if the authenticated
117
     * user is an admin of that IdP
118
     * It is a wrapper around existingIdPInt.
119
     * 
120
     * @param mixed            $input             the numeric ID of the IdP in the system
121
     * @param string           $owner             the authenticated username, optional
122
     * @param \core\Federation $claimedFedBinding if set, cross-check that IdP belongs to specified federation (useful in admin API mode)
123
     * @return \core\IdP
124
     * @throws Exception
125
     */
126
    public function existingIdP($input, $owner = NULL, $claimedFedBinding = NULL)
127
    {
128
        $clean = $this->integer($input);
129
        if ($clean === FALSE) {
130
            throw new Exception($this->inputValidationError("Value for IdP is not an integer!"));
131
        }
132
        
133
        $checkResult = $this->existingIdPInt($input, $owner, $claimedFedBinding);
134
        $this->loggerInstance->debug(5, $checkResult, "existingIdP:", "\n");
135
        if ($checkResult[1] == 'fullaccess') {
136
            return $checkResult[0];
137
        }
138
        if ($owner == NULL && $checkResult[1] == 'nouser') {
139
            return $checkResult[0];
140
        }
141
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type core\IdP.
Loading history...
142
    }
143
    
144
    
145
    /**
146
     * Is this a known IdP? Optionally, also check if the authenticated
147
     * user is an admin of that IdP or a federation admin for the parent federation
148
     * federaton admins superadmin and support get read-only access, superadmins get readonly access as well
149
     * @param mixed            $input             the numeric ID of the IdP in the system
150
     * @param string           $owner             the authenticated username, optional
151
     * @param \core\Federation $claimedFedBinding if set, cross-check that IdP belongs to specified federation (useful in admin API mode)
152
     * @return array(\core\IdP, string)
153
     * @throws Exception
154
     */
155
    public function existingIdPInt($input, $owner = NULL, $claimedFedBinding = NULL)
156
    {
157
        $clean = $this->integer($input);
158
        if ($clean === FALSE) {
159
            throw new Exception($this->inputValidationError("Value for IdP is not an integer!"));
160
        }
161
        $temp = new \core\IdP($input); // constructor throws an exception if NX, game over
162
        if ($owner !== NULL) { // check if the authenticated user is allowed to see this institution
163
            $user = new \core\User($owner);        
164
            foreach ($temp->listOwners() as $oneowner) {
165
                if ($oneowner['ID'] == $owner) {
166
                    return [$temp, 'fullaccess'];
167
                }
168
            }
169
            if ($user->isFederationAdmin($temp->federation)) {
170
                $this->loggerInstance->debug(4, "You are fed admin for this IdP\n");
171
                return [$temp,'readonly'];
172
            }
173
            if ($user->isSuperadmin() || $user->isSupport()) {
174
                $this->loggerInstance->debug(4, "You are the superadmin/support\n");
175
                return [$temp,'readonly'];                
176
            }
177
            throw new Exception($this->inputValidationError("This IdP identifier is not accessible!"));
178
        }
179
        if ($claimedFedBinding !== NULL && strtoupper($temp->federation) != strtoupper($claimedFedBinding->tld)) {
180
            throw new Exception($this->inputValidationError("This IdP does not belong to the claimed federation!"));
181
        }
182
        return [$temp,'nouser'];
183
    }
184
185
    /**
186
     * Checks if the input refers to a known Profile. Optionally also takes an
187
     * IdP identifier and then checks if the Profile belongs to the referenced 
188
     * IdP
189
     * 
190
     * @param mixed    $input         the numeric ID of the Profile in the system
191
     * @param int|NULL $idpIdentifier the numeric ID of the IdP in the system, optional
192
     * @return \core\AbstractProfile
193
     * @throws Exception
194
     */
195
    public function existingProfile($input, $idpIdentifier = NULL)
196
    {
197
        $clean = $this->integer($input);
198
        if ($clean === FALSE) {
199
            throw new Exception("Non-integer was passed to Profile validator!");
200
        }
201
        $temp = \core\ProfileFactory::instantiate($clean); // constructor throws an exception if NX, game over
202
203
        if ($idpIdentifier !== NULL && $temp->institution != $idpIdentifier) {
204
            throw new Exception($this->inputValidationError("The profile does not belong to the IdP!"));
205
        }
206
        return $temp;
207
    }
208
209
    /**
210
     * Checks if the input refers to a known DeploymentManaged. Optionally also takes an
211
     * IdP identifier and then checks if the Profile belongs to the referenced 
212
     * IdP
213
     * 
214
     * @param mixed     $input the numeric ID of the Deployment in the system
215
     * @param \core\IdP $idp   the IdP
216
     * @return \core\DeploymentManaged
217
     * @throws Exception
218
     */
219
    public function existingDeploymentManaged($input, $idp)
220
    {
221
        $clean = $this->integer($input);
222
        if ($clean === FALSE) {
223
            throw new Exception("Non-integer was passed to Profile validator!");
224
        }
225
        $temp = new \core\DeploymentManaged($idp, $clean); // constructor throws an exception if NX, game over
226
227
        if ($temp->institution != $idp->identifier) {
228
            throw new Exception($this->inputValidationError("The profile does not belong to the IdP!"));
229
        }
230
        return $temp;
231
    }
232
233
    /**
234
     * Checks if this is a device known to the system
235
     * @param mixed $input the name of the device (index in the Devices.php array)
236
     * @return string returns the same string on success, throws an Exception on failure
237
     * @throws Exception
238
     */
239
    public function existingDevice($input)
240
    {
241
        $devicelist = \devices\Devices::listDevices();
242
        $keyArray = array_keys($devicelist);
243
        if (!isset($devicelist[$input])) {
244
            throw new Exception($this->inputValidationError("This device does not exist!"));
245
        }
246
        $correctIndex = array_search($input, $keyArray);
247
        return $keyArray[$correctIndex];
248
    }
249
250
    /**
251
     * Checks if the input was a valid string.
252
     * 
253
     * @param mixed   $input           a string to be made SQL-safe
254
     * @param boolean $allowWhitespace whether some whitespace (e.g. newlines should be preserved (true) or redacted (false)
255
     * @return string the massaged string
256
     * @throws Exception
257
     */
258
    public function string($input, $allowWhitespace = FALSE)
259
    {
260
        // always chop out invalid characters, and surrounding whitespace
261
        $retvalStep0 = iconv("UTF-8", "UTF-8//TRANSLIT", $input);
262
        if ($retvalStep0 === FALSE) {
263
            throw new Exception("iconv failure for string sanitisation. With TRANSLIT, this should never happen!");
264
        }
265
        $retvalStep1 = trim($retvalStep0);
266
        // if some funny person wants to inject markup tags, remove them
267
        if ($retvalStep1 !== '') {
268
            $retval = htmlspecialchars(strip_tags($retvalStep1), ENT_NOQUOTES);             
269
            if ($retval === '') {
270
                throw new Exception("filter_var failure for string sanitisation.");
271
            }
272
        } else {
273
            $retval = '';
274
        }
275
        // unless explicitly wanted, take away intermediate disturbing whitespace
276
        // a simple "space" is NOT disturbing :-)
277
        if ($allowWhitespace === FALSE) {
278
            $afterWhitespace = preg_replace('/(\0|\r|\x0b|\t|\n)/', '', $retval);
279
        } else {
280
            // even if we allow whitespace, not pathological ones!
281
            $afterWhitespace = preg_replace('/(\0|\r|\x0b)/', '', $retval);
282
        }
283
        if (is_array($afterWhitespace)) {
284
            throw new Exception("This function has to be given a string and returns a string. preg_replace has generated an array instead!");
285
        }
286
        return (string) $afterWhitespace;
287
    }
288
289
    /**
290
     * Is this an integer, or a string that represents an integer?
291
     * 
292
     * @param mixed $input the raw input
293
     * @return boolean|int returns the input, or FALSE if it is not an integer-like value
294
     */
295
    public function integer($input)
296
    {
297
        if (is_numeric($input)) {
298
            return (int) $input;
299
        }
300
        return FALSE;
301
    }
302
303
    /**
304
     * Is this a string representing a potentially more than 64-Bit length integer?
305
     * 
306
     * @param string $input the input data which is possibly a really large integer
307
     * @return boolean|string returns the input, or FALSE if it is not an integer-like string
308
     */
309
    public function hugeInteger($input)
310
    {
311
        if (is_numeric($input)) {
312
            return $input;
313
        }
314
        return FALSE;
315
    }
316
317
    /**
318
     * Checks if the input is the hex representation of a Consortium OI (i.e. three
319
     * or five bytes)
320
     * 
321
     * @param mixed $input the raw input
322
     * @return boolean|string returns the input, or FALSE on validation failure
323
     */
324
    public function consortiumOI($input)
325
    {
326
        $shallow = $this->string($input);
327
        if (strlen($shallow) != 6 && strlen($shallow) != 10) {
328
            return FALSE;
329
        }
330
        if (!preg_match("/^[a-fA-F0-9]+$/", $shallow)) {
331
            return FALSE;
332
        }
333
        return $shallow;
334
    }
335
336
    /**
337
     * Is the input an NAI realm? Throws HTML error and returns FALSE if not.
338
     * 
339
     * @param mixed $input the input to check
340
     * @return boolean|string returns the realm, or FALSE if it was malformed
341
     */
342
    public function realm($input)
343
    {
344
        \core\common\Entity::intoThePotatoes();
345
        if (strlen($input) == 0) {
346
            echo $this->inputValidationError(_("Realm is empty!"));
347
            \core\common\Entity::outOfThePotatoes();
348
            return FALSE;
349
        }
350
351
        // basic string checks
352
        $check = $this->string($input);
353
        // list of things to check, and the error they produce
354
        $pregCheck = [
355
            "/@/" => _("Realm contains an @ sign!"),
356
            "/^\./" => _("Realm begins with a . (dot)!"),
357
            "/\.$/" => _("Realm ends with a . (dot)!"),
358
            "/ /" => _("Realm contains spaces!"),
359
        ];
360
361
        // bark on invalid constructs
362
        foreach ($pregCheck as $search => $error) {
363
            if (preg_match($search, $check) == 1) {
364
                echo $this->inputValidationError($error);
365
                \core\common\Entity::outOfThePotatoes();
366
                return FALSE;
367
            }
368
        }
369
370
        if (preg_match("/\./", $check) == 0) {
371
            echo $this->inputValidationError(_("Realm does not contain at least one . (dot)!"));
372
            \core\common\Entity::outOfThePotatoes();
373
            return FALSE;
374
        }
375
376
        // none of the special HTML entities should be here. In case someone wants
377
        // to mount a CSS attack by providing something that matches the realm constructs
378
        // below but has interesting stuff between, mangle the input so that these
379
        // characters do not do any harm.
380
        \core\common\Entity::outOfThePotatoes();
381
        return htmlentities($check, ENT_QUOTES);
382
    }
383
384
    /**
385
     * could this be a valid username? 
386
     * 
387
     * Only checks correct form, not if the user actually exists in the system.
388
     * 
389
     * @param mixed $input the username
390
     * @return string echoes back the input string, or throws an Exception if bogus
391
     * @throws Exception
392
     */
393
    public function syntaxConformUser($input)
394
    {
395
        $retvalStep0 = iconv("UTF-8", "UTF-8//TRANSLIT", $input);
396
        if ($retvalStep0 === FALSE) {
397
            throw new Exception("iconv failure for string sanitisation. With TRANSLIT, this should never happen!");
398
        }
399
        $retvalStep1 = trim($retvalStep0);
400
401
        $retval = preg_replace('/(\0|\r|\x0b|\t|\n)/', '', $retvalStep1);
402
        if ($retval != "" && !ctype_print($retval)) {
403
            throw new Exception($this->inputValidationError("The user identifier is not an ASCII string!"));
404
        }
405
406
        return $retval;
407
    }
408
409
    /**
410
     * could this be a valid token? 
411
     * 
412
     * Only checks correct form, not if the token actually exists in the system.
413
     * @param mixed $input the raw input
414
     * @return string echoes back the input string, or throws an Exception if bogus
415
     * @throws Exception
416
     */
417
    public function token($input)
418
    {
419
        $retval = $input;
420
        if ($input != "" && preg_match('/[^0-9a-fA-F]/', $input) != 0) {
421
            throw new Exception($this->inputValidationError("Token is not a hexadecimal string!"));
422
        }
423
        return $retval;
424
    }
425
426
    /**
427
     * Is this be a valid coordinate vector on one axis?
428
     * 
429
     * @param mixed $input a numeric value in range of a geo coordinate [-180;180]
430
     * @return string returns back the input if all is good; throws an Exception if out of bounds or not numeric
431
     * @throws Exception
432
     */
433
    public function coordinate($input)
434
    {
435
        $oldlocale = setlocale(LC_NUMERIC, 0);
436
        setlocale(LC_NUMERIC, "en_GB");
437
        if (!is_numeric($input)) {
438
            throw new Exception($this->inputValidationError("Coordinate is not a numeric value!"));
439
        }
440
        setlocale(LC_NUMERIC, $oldlocale);
441
        // lat and lon are always in the range of [-180;+180]
442
        if ($input < -180 || $input > 180) {
443
            throw new Exception($this->inputValidationError("Coordinate is out of bounds. Which planet are you from?"));
444
        }
445
        return $input;
446
    }
447
448
    /**
449
     * Is this a valid coordinate pair in JSON encoded representation?
450
     * 
451
     * @param mixed $input the string to be checked: is this a serialised array with lat/lon keys in a valid number range?
452
     * @return string returns $input if checks have passed; throws an Exception if something's wrong
453
     * @throws Exception
454
     */
455
    public function coordJsonEncoded($input)
456
    {
457
        $tentative = json_decode($input, true);
458
        if (is_array($tentative)) {
459
            if (isset($tentative['lon']) && isset($tentative['lat']) && $this->coordinate($tentative['lon']) && $this->coordinate($tentative['lat'])) {
460
                return $input;
461
            }
462
        }
463
        throw new Exception($this->inputValidationError("Wrong coordinate encoding (2.0 uses JSON, not serialize)!"));
464
    }
465
466
    /**
467
     * This checks the state of a HTML GET/POST "boolean".
468
     * 
469
     * If not checked, no value is submitted at all; if checked, has the word "on". 
470
     * Anything else is a big error.
471
     * 
472
     * @param mixed $input the string to test
473
     * @return boolean TRUE if the input was "on". It is not possible in HTML to signal "off"
474
     * @throws Exception
475
     */
476
    public function boolean($input)
477
    {
478
        if ($input != "on") {
479
            throw new Exception($this->inputValidationError("Unknown state of boolean option!"));
480
        }
481
        return TRUE;
482
    }
483
484
    /**
485
     * checks if we have the strings "IdP" "SP" or "IdPSP"
486
     * 
487
     * @param string $partTypeRaw the string to be validated as participant type
488
     * @return string validated result
489
     * @throws Exception
490
     */
491
    public function partType($partTypeRaw)
492
    {
493
        switch ($partTypeRaw) {
494
            case \core\IdP::TYPE_IDP:
495
                return \core\IdP::TYPE_IDP;
496
            case \core\IdP::TYPE_SP:
497
                return \core\IdP::TYPE_SP;
498
            case \core\IdP::TYPE_IDPSP:
499
                return \core\IdP::TYPE_IDPSP;
500
            default:
501
                throw new Exception("Unknown Participant Type!");
502
        }
503
    }
504
505
    const TABLEMAPPING = [
506
        "IdP" => "institution_option",
507
        "Profile" => "profile_option",
508
        "FED" => "federation_option",
509
    ];
510
511
    /**
512
     * Is this a valid database reference? Has the form <tablename>-<rowID> and there
513
     * needs to be actual data at that place
514
     * 
515
     * @param string $input the reference to check
516
     * @return boolean|array the reference split up into "table" and "rowindex", or FALSE
517
     */
518
    public function databaseReference($input)
519
    {
520
        $pregMatches = [];
521
        if (preg_match("/^ROWID-(IdP|Profile|FED)-([0-9]+)$/", $input, $pregMatches) != 1) {
522
            return FALSE;
523
        }
524
        $rownumber = $this->integer($pregMatches[2]);
525
        if ($rownumber === FALSE) {
526
            return FALSE;
527
        }
528
        return ["table" => self::TABLEMAPPING[$pregMatches[1]], "rowindex" => $rownumber];
529
    }
530
531
    /**
532
     * is this a valid hostname?
533
     * 
534
     * @param mixed $input the raw input
535
     * @return boolean|string echoes the hostname, or FALSE if bogus
536
     */
537
    public function hostname($input)
538
    {
539
        // is it a valid IP address (IPv4 or IPv6), or a hostname?
540
        if (filter_var($input, FILTER_VALIDATE_IP) || filter_var($input, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
541
            // if it's a verified IP address or hostname then it does not contain
542
            // rubbish of course. But just to be sure, run htmlspecialchars around it
543
            return htmlspecialchars($input, ENT_QUOTES);
544
        }
545
        return FALSE;
546
    }
547
548
    /**
549
     * is this a valid email address?
550
     * 
551
     * @param mixed $input the raw input
552
     * @return boolean|string echoes the mail address, or FALSE if bogus
553
     */
554
    public function email($input)
555
    {
556
557
        if (filter_var($this->string($input), FILTER_VALIDATE_EMAIL)) {
558
            return $input;
559
        }
560
        // if we get here, it's bogus
561
        return FALSE;
562
    }
563
564
    /**
565
     * is this a well-formed SMS number? Light massaging - leading + will be removed
566
     * @param string $input the raw input
567
     * @return boolean|string
568
     */
569
    public function sms($input)
570
    {
571
        $number = str_replace(' ', '', str_replace(".", "", str_replace("+", "", $input)));
572
        if (!is_numeric($number)) {
573
            return FALSE;
574
        }
575
        return $number;
576
    }
577
578
    /**
579
     * Is this is a language we support? If not, sanitise to our configured default language.
580
     * 
581
     * @param mixed $input the candidate language identifier
582
     * @return string
583
     * @throws Exception
584
     */
585
    public function supportedLanguage($input)
586
    {
587
        if (!array_key_exists($input, \config\Master::LANGUAGES)) {
588
            return \config\Master::APPEARANCE['defaultlocale'];
589
        }
590
        // otherwise, use the inversion trick to convince Scrutinizer that this is
591
        // a vetted value
592
        $retval = array_search(\config\Master::LANGUAGES[$input], \config\Master::LANGUAGES);
593
        if ($retval === FALSE) {
594
            throw new Exception("Impossible: the value we are searching for does exist, because we reference it directly.");
595
        }
596
        return $retval;
597
    }
598
599
    /**
600
     * Makes sure we are not receiving a bogus option name. The called function throws
601
     * an assertion if the name is not known.
602
     * 
603
     * @param mixed $input the unvetted option name
604
     * @return string
605
     */
606
    public function optionName($input)
607
    {
608
        $object = \core\Options::instance();
609
        return $object->assertValidOptionName($input);
610
    }
611
612
    /**
613
     * Checks to see if the input is a valid image of sorts
614
     * 
615
     * @param mixed $binary blob that may or may not be a parseable image
616
     * @return boolean
617
     */
618
    public function image($binary)
619
    {
620
        if (class_exists('\\Gmagick')) { 
621
            $image = new \Gmagick(); 
622
        } else {
623
            $image = new \Imagick();
624
        }
625
        try {
626
            $image->readImageBlob($binary);
627
        } catch (\ImagickException $exception) {
628
            echo "Error" . $exception->getMessage();
629
            return FALSE;
630
        }
631
        // image survived the sanity check
632
        return TRUE;
633
    }
634
635
    /**
636
     * searches for values in GET and POST, and filters the value according to
637
     * which kind of data is expected
638
     * 
639
     * @param string $varName name of the variable in GET/POST
640
     * @param string $filter  which type of filter to apply (safe_text / int)
641
     * @return NULL|string|integer the returned value
642
     */
643
    public function simpleInputFilter($varName, $filter)
644
    {
645
        $safeText = ["options" => ["regexp" => "/^[\w\d-]+$/"]];
646
        switch ($filter) {
647
            case 'safe_text':
648
                $out = filter_input(INPUT_GET, $varName, FILTER_VALIDATE_REGEXP, $safeText) ?? filter_input(INPUT_POST, $varName, FILTER_VALIDATE_REGEXP, $safeText);
649
                break;
650
            case 'int':
651
                $out = filter_input(INPUT_GET, $varName, FILTER_VALIDATE_INT) ?? filter_input(INPUT_POST, $varName, FILTER_VALIDATE_INT);
652
                break;
653
            default:
654
                $out = NULL;
655
                break;
656
        }
657
        if ($out === false) { // an error occurred during the filter_input runs; make this NULL instead then
658
            $out = NULL;
659
        }
660
        return $out;
661
    }
662
663
}
664