OptionParser   F
last analyzed

Complexity

Total Complexity 98

Size/Duplication

Total Lines 552
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 98
eloc 283
c 2
b 0
f 0
dl 0
loc 552
rs 2

11 Methods

Rating   Name   Duplication   Size   Complexity  
C postProcessValidAttributes() 0 55 13
C checkUploadSanity() 0 56 17
C sendOptionsToDatabase() 0 33 12
A collateOptionArrays() 0 9 1
A determineLanguages() 0 10 4
A postProcessCoordinates() 0 9 3
A processSubmittedFields() 0 51 4
A __construct() 0 5 1
A displaySummaryInUI() 0 26 6
D sanitiseInputs() 0 92 23
C furtherStringChecks() 0 56 14

How to fix   Complexity   

Complex Class

Complex classes like OptionParser 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 OptionParser, 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\admin;
24
25
use Exception;
26
27
?>
28
<?php
29
30
/**
31
 * This class parses HTML field input from POST and FILES and extracts valid and authorised options to be set.
32
 * 
33
 * @author Stefan Winter <[email protected]>
34
 */
35
class OptionParser extends \core\common\Entity {
36
37
    /**
38
     * an instance of the InputValidation class which we use heavily for syntax checks.
39
     * 
40
     * @var \web\lib\common\InputValidation
41
     */
42
    private $validator;
43
44
    /**
45
     * an instance of the UIElements() class to draw some UI widgets from.
46
     * 
47
     * @var UIElements
48
     */
49
    private $uiElements;
50
51
    /**
52
     * a handle for the Options singleton
53
     * 
54
     * @var \core\Options
55
     */
56
    private $optioninfoObject;
57
58
    /**
59
     * initialises the various handles.
60
     */
61
    public function __construct() {
62
        $this->validator = new \web\lib\common\InputValidation();
63
        $this->uiElements = new UIElements();
64
        $this->optioninfoObject = \core\Options::instance();
65
        $this->loggerInstance = new \core\common\Logging();
66
    }
67
68
    /**
69
     * Verifies whether an incoming upload was actually valid data
70
     * 
71
     * @param string $optiontype     for which option was the data uploaded
72
     * @param string $incomingBinary the uploaded data
73
     * @return array ['result'=>boolean whether the data was valid, 'details'=>string description of the problem]
74
     */
75
        private function checkUploadSanity(string $optiontype, string $incomingBinary) {
76
        switch ($optiontype) {
77
            case "general:logo_file":
78
            case "fed:logo_file":
79
            case "internal:logo_from_url":
80
                $result = $this->validator->image($incomingBinary);
81
                // we check logo_file with ImageMagick
82
                if ($result) {
83
                    return ['result'=>TRUE, 'details'=>''];
84
                }
85
                return ['result'=>FALSE, 'details'=>_('unsupported image type')];
86
            case "eap:ca_file":
87
            // fall-through intended: both CA types are treated the same
88
            case "fed:minted_ca_file":
89
                // echo "Checking $optiontype with file $filename";
90
                $cert = (new \core\common\X509)->processCertificate($incomingBinary);
91
                if ($cert !== FALSE) { // could also be FALSE if it was incorrect incoming data
92
                    $fail = false;
93
                    if ($cert['full_details']['type'] == 'server') {
94
                        $reason = _("%s - server certificate (<a href='%s'>more info</a>)");
95
                        $fail = true;
96
                    } elseif($cert['basicconstraints_set'] === 0) {
97
                        $reason = _("%s - missing required CA extensions (<a href='%s'>more info</a>)");
98
                        $fail = true;
99
                    }    
100
                    if ($fail) {
101
                        if (\config\ConfAssistant::CERT_GUIDELINES === '') {
102
                            $ret_val = sprintf(preg_replace('/\(<a.*>\)/', '', $reason), $cert['full_details']['subject']['CN']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $reason does not seem to be defined for all execution paths leading up to this point.
Loading history...
103
                        } else {
104
                            $ret_val = sprintf($reason, $cert['full_details']['subject']['CN'], \config\ConfAssistant::CERT_GUIDELINES);
105
                        }
106
                        return ['result'=>FALSE, 'details'=>$ret_val];
107
                    }                
108
                    return ['result'=>TRUE, 'details'=>''];
109
                }
110
                // the certificate seems broken
111
                return ['result'=>FALSE, 'details'=>''];
112
            case "support:info_file":
113
                $info = new \finfo();
114
                $filetype = $info->buffer($incomingBinary, FILEINFO_MIME_TYPE);
115
116
                // we only take plain text files in UTF-8!
117
                if ($filetype == "text/plain" && iconv("UTF-8", "UTF-8", $incomingBinary) !== FALSE) {
118
                    return ['result'=>TRUE, ''];
119
                }
120
                return ['result'=>FALSE, 'details'=>_("incorrect file type - must be UTF8 text")];
121
            case "media:openroaming": // and any other enum_* data type actually
122
                $optionClass = \core\Options::instance();
123
                $optionProps = $optionClass->optionType($optiontype);
124
                $allowedValues = explode(',', substr($optionProps["flags"], 7));
125
                if (in_array($incomingBinary,$allowedValues))  {
126
                    return ['result'=>TRUE, 'details'=>''];
127
                }
128
                return ['result'=>FALSE, 'details'=>''];
129
            default:
130
                return ['result'=>FALSE, 'details'=>''];
131
        }
132
    }
133
134
    /**
135
     * Known-good options are sometimes converted, this function takes care of that.
136
     * 
137
     * Cases in point:
138
     * - CA import by URL reference: fetch cert from URL and store it as CA file instead
139
     * - Logo import by URL reference: fetch logo from URL and store it as logo file instead
140
     * - CA file: mangle the content so that *only* the valid content remains (raw input may contain line breaks or spaces which are valid, but some supplicants choke upon)
141
     * 
142
     * @param array $options the list of options we got
143
     * @param array $good    by-reference: the future list of actually imported options
144
     * @param array $bad     by-reference: the future list of submitted but rejected options
145
     * @return array the options, post-processed
146
     */
147
    private function postProcessValidAttributes(array $options, array &$good, array &$bad) {
148
        foreach ($options as $index => $iterateOption) {
149
            foreach ($iterateOption as $name => $optionPayload) {
150
                switch ($name) {
151
                    case "eap:ca_url": // eap:ca_url becomes eap:ca_file by downloading the file
152
                        $finalOptionname = "eap:ca_file";
153
                    // intentional fall-through, treatment identical to logo_url
154
                    case "general:logo_url": // logo URLs become logo files by downloading the file
155
                        $finalOptionname = $finalOptionname ?? "general:logo_file";
156
                        if (empty($optionPayload['content'])) {
157
                            break;
158
                        }
159
                        $bindata = \core\common\OutsideComm::downloadFile($optionPayload['content']);
160
                        unset($options[$index]);
161
                        if ($bindata === FALSE) {
162
                            $bad[] = ['type'=>$name, 'details'=>_("missing content")];
163
                            break;
164
                        }
165
                        if ($this->checkUploadSanity($finalOptionname, $bindata)['result']) {
166
                            $good[] = $name;
167
                            $options[] = [$finalOptionname => ['lang' => NULL, 'content' => base64_encode($bindata)]];
168
                        } else {
169
                            $bad[] = ['type'=>$name, 'details'=>''];
170
                        }
171
                        break;
172
                    case "eap:ca_file":
173
                    case "fed:minted_ca_file":
174
                        // CA files get split (PEM files can contain more than one CA cert)
175
                        // the data being processed here is always "good": 
176
                        // if it was eap:ca_file initially then its sanity was checked in step 1;
177
                        // if it was eap:ca_url then it was checked after we downloaded it
178
                        if (empty($optionPayload['content'])) {
179
                            break;
180
                        }
181
                        if (preg_match('/^ROWID-/', $optionPayload['content'])) {
182
                            // accounted for, already in DB
183
                            $good[] = $name;
184
                            break;
185
                        }
186
                        $content = base64_decode($optionPayload['content']);
187
                        unset($options[$index]);
188
                        $x509 = new \core\common\X509();
189
                        $cAFiles = $x509->splitCertificate($content);
190
                        foreach ($cAFiles as $cAFile) {
191
                            $options[] = [$name => ['lang' => NULL, 'content' => base64_encode($x509->pem2der($cAFile))]];
192
                        }
193
                        $good[] = $name;
194
                        break;
195
                    default:
196
                        $good[] = $name; // all other options were checked and are sane in step 1 already
197
                        break;
198
                }
199
            }
200
        }
201
        return $options;
202
    }
203
204
    /**
205
     * extracts a coordinate pair from _POST (if any) and returns it in our 
206
     * standard attribute notation
207
     * 
208
     * @param array $postArray data as sent by POST
209
     * @param array $good      options which have been successfully parsed
210
     * @return array
211
     */
212
    private function postProcessCoordinates(array $postArray, array &$good) {
213
        if (!empty($postArray['geo_long']) && !empty($postArray['geo_lat'])) {
214
215
            $lat = $this->validator->coordinate($postArray['geo_lat']);
216
            $lon = $this->validator->coordinate($postArray['geo_long']);
217
            $good[] = ("general:geo_coordinates");
218
            return [0 => ["general:geo_coordinates" => ['lang' => NULL, 'content' => json_encode(["lon" => $lon, "lat" => $lat])]]];
219
        }
220
        return [];
221
    }
222
223
    /**
224
     * creates HTML code for a user-readable summary of the imports
225
     * @param array $good           list of actually imported options
226
     * @param array $bad            list of submitted but rejected options
227
     * @param array $mlAttribsWithC list of language-variant options
228
     * @return string HTML code
229
     */
230
    private function displaySummaryInUI(array $good, array $bad, array $mlAttribsWithC) {
231
        \core\common\Entity::intoThePotatoes();
232
        $retval = "";
233
        // don't do your own table - only the <tr>s here
234
        // list all attributes that were set correctly
235
        $listGood = array_count_values($good);
236
        $uiElements = new UIElements();
237
        foreach ($listGood as $name => $count) {
238
            /// number of times attribute is present, and its name
239
            /// Example: "5x Support E-Mail"
240
            $retval .= $this->uiElements->boxOkay(sprintf(_("%dx %s"), $count, $uiElements->displayName($name)));
241
        }
242
        // list all attributes that had errors
243
244
        foreach ($bad as $badInstance) {
245
            $details = $badInstance['details'] === '' ? '' : ' - '.$badInstance['details'];
246
            $retval .= $this->uiElements->boxError(sprintf(_("%s"), $uiElements->displayName($badInstance['type']).$details));
247
        }
248
        // list multilang without default
249
        foreach ($mlAttribsWithC as $attribName => $isitsetornot) {
250
            if ($isitsetornot == FALSE) {
251
                $retval .= $this->uiElements->boxWarning(sprintf(_("You did not set a 'default language' value for %s. This means we can only display this string for installers which are <strong>exactly</strong> in the language you configured. For the sake of all other languages, you may want to edit the profile again and populate the 'default/other' language field."), $uiElements->displayName($attribName)));
252
            }
253
        }
254
        \core\common\Entity::outOfThePotatoes();
255
        return $retval;
256
    }
257
258
    /**
259
     * Incoming data is in $_POST and possibly in $_FILES. Collate values into 
260
     * one array according to our name and numbering scheme.
261
     * 
262
     * @param array $postArray  _POST
263
     * @param array $filesArray _FILES
264
     * @return array
265
     */
266
    private function collateOptionArrays(array $postArray, array $filesArray) {
267
268
        $optionarray = $postArray['option'] ?? [];
269
        $valuearray = $postArray['value'] ?? [];
270
        $filesarray = $filesArray['value']['tmp_name'] ?? [];
271
272
        $iterator = array_merge($optionarray, $valuearray, $filesarray);
273
274
        return $iterator;
275
    }
276
277
    /**
278
     * The very end of the processing: clean input data gets sent to the database
279
     * for storage
280
     * 
281
     * @param mixed  $object            for which object are the options
282
     * @param array  $options           the options to store
283
     * @param array  $pendingattributes list of attributes which are already stored but may need to be deleted
284
     * @param string $device            when the $object is Profile, this indicates device-specific attributes
285
     * @param int    $eaptype           when the $object is Profile, this indicates eap-specific attributes
286
     * @return array list of attributes which were previously stored but are to be deleted now
287
     * @throws Exception
288
     */
289
    private function sendOptionsToDatabase($object, array $options, array $pendingattributes, string $device = NULL, int $eaptype = NULL) {
290
        $retval = [];
291
        foreach ($options as $iterateOption) {
292
            foreach ($iterateOption as $name => $optionPayload) {
293
                $optiontype = $this->optioninfoObject->optionType($name);
294
                // some attributes are in the DB and were only called by reference
295
                // keep those which are still referenced, throw the rest away
296
                if ($optiontype["type"] == \core\Options::TYPECODE_FILE && preg_match("/^ROWID-.*-([0-9]+)/", $optionPayload['content'], $retval)) {
297
                    unset($pendingattributes[$retval[1]]);
298
                    continue;
299
                }
300
                switch (get_class($object)) {
301
                    case 'core\\ProfileRADIUS':
302
                        if ($device !== NULL) {
303
                            $object->addAttributeDeviceSpecific($name, $optionPayload['lang'], $optionPayload['content'], $device);
304
                        } elseif ($eaptype !== NULL) {
305
                            $object->addAttributeEAPSpecific($name, $optionPayload['lang'], $optionPayload['content'], $eaptype);
306
                        } else {
307
                            $object->addAttribute($name, $optionPayload['lang'], $optionPayload['content']);
308
                        }
309
                        break;
310
                    case 'core\\IdP':
311
                    case 'core\\User':
312
                    case 'core\\Federation':
313
                    case 'core\\DeploymentManaged':
314
                        $object->addAttribute($name, $optionPayload['lang'], $optionPayload['content']);
315
                        break;
316
                    default:
317
                        throw new Exception("This type of object can't have options that are parsed by this file!");
318
                }
319
            }
320
        }
321
        return $pendingattributes;
322
    }
323
324
    /** many of the content check cases in sanitiseInputs condense due to
325
     *  identical treatment except which validator function to call and 
326
     *  where in POST the content is.
327
     * 
328
     * This is a map between datatype and validation function.
329
     * 
330
     * @var array
331
     */
332
    private const VALIDATOR_FUNCTIONS = [
333
        \core\Options::TYPECODE_TEXT => ["function" => "string", "field" => \core\Options::TYPECODE_TEXT, "extraarg" => [TRUE]],
334
        \core\Options::TYPECODE_COORDINATES => ["function" => "coordJsonEncoded", "field" => \core\Options::TYPECODE_TEXT, "extraarg" => []],
335
        \core\Options::TYPECODE_BOOLEAN => ["function" => "boolean", "field" => \core\Options::TYPECODE_BOOLEAN, "extraarg" => []],
336
        \core\Options::TYPECODE_INTEGER => ["function" => "integer", "field" => \core\Options::TYPECODE_INTEGER, "extraarg" => []],
337
    ];
338
339
    /**
340
     * filters the input to find syntactically correctly submitted attributes
341
     * 
342
     * @param array $listOfEntries list of POST and FILES entries
343
     * @return array sanitised list of options
344
     * @throws Exception
345
     */
346
    private function sanitiseInputs(array $listOfEntries) {
347
        $retval = [];
348
        $bad = [];
349
        $multilangAttrsWithC = [];
350
        foreach ($listOfEntries as $objId => $objValueRaw) {
351
// pick those without dash - they indicate a new value        
352
            if (preg_match('/^S[0123456789]*$/', $objId) != 1) { // no match
353
                continue;
354
            }
355
            $objValue = $this->validator->optionName(preg_replace('/#.*$/', '', $objValueRaw));
356
            $optioninfo = $this->optioninfoObject->optionType($objValue);
357
            $languageFlag = NULL;
358
            if ($optioninfo["flag"] == "ML") {
359
                if (!isset($listOfEntries["$objId-lang"])) {
360
                    $bad[] = ['type'=>$objValue, 'details'=>''];
361
                    continue;
362
                }
363
                $languageFlag = $this->validator->string($listOfEntries["$objId-lang"]);
364
                $this->determineLanguages($objValue, $listOfEntries["$objId-lang"], $multilangAttrsWithC);
365
            }
366
367
            switch ($optioninfo["type"]) {
368
                case \core\Options::TYPECODE_TEXT:
369
                case \core\Options::TYPECODE_COORDINATES:
370
                case \core\Options::TYPECODE_INTEGER:
371
                    $varName = $listOfEntries["$objId-" . self::VALIDATOR_FUNCTIONS[$optioninfo['type']]['field']];
372
                    if (!empty($varName)) {
373
                        $content = call_user_func_array([$this->validator, self::VALIDATOR_FUNCTIONS[$optioninfo['type']]['function']], array_merge([$varName], self::VALIDATOR_FUNCTIONS[$optioninfo['type']]['extraarg']));
374
                        break;
375
                    }
376
                    continue 2;
377
                case \core\Options::TYPECODE_BOOLEAN:
378
                    $varName = $listOfEntries["$objId-" . \core\Options::TYPECODE_BOOLEAN];
379
                    if (!empty($varName)) {
380
                        $contentValid = $this->validator->boolean($varName);
381
                        if ($contentValid) {
382
                            $content = "on";
383
                        } else {
384
                            $bad[] = ['type'=>$objValue, 'details'=>''];
385
                            continue 2;
386
                        }
387
                        break;
388
                    }
389
                    continue 2;
390
                case \core\Options::TYPECODE_STRING:
391
                    $previsionalContent = $listOfEntries["$objId-" . \core\Options::TYPECODE_STRING];
392
                    if (!empty(trim($previsionalContent))) {
393
                        $content = $this->furtherStringChecks($objValue, $previsionalContent, $bad);
394
                        if ($content === FALSE) {
395
                            continue 2;
396
                        }
397
                        break;
398
                    }
399
                    continue 2;
400
                    
401
                case \core\Options::TYPECODE_ENUM_OPENROAMING:
402
                    $previsionalContent = $listOfEntries["$objId-" . \core\Options::TYPECODE_ENUM_OPENROAMING];
403
                    if (!empty($previsionalContent)) {
404
                        $content = $this->furtherStringChecks($objValue, $previsionalContent, $bad);
405
                        if ($content === FALSE) {
406
                            continue 2;
407
                        }
408
                        break;
409
                    }
410
                    continue 2;    
411
                case \core\Options::TYPECODE_FILE:
412
                    // this is either actually an uploaded file, or a reference to a DB entry of a previously uploaded file
413
                    $reference = $listOfEntries["$objId-" . \core\Options::TYPECODE_STRING];
414
                    if (!empty($reference)) { // was already in, by ROWID reference, extract
415
                        // ROWID means it's a multi-line string (simple strings are inline in the form; so allow whitespace)
416
                        $content = $this->validator->string(urldecode($reference), TRUE);
417
                        break;
418
                    }
419
                    $fileName = $listOfEntries["$objId-" . \core\Options::TYPECODE_FILE] ?? "";
420
                    if ($fileName != "") { // let's do the download
421
                        $rawContent = \core\common\OutsideComm::downloadFile("file:///" . $fileName);
422
                        $sanity = $this->checkUploadSanity($objValue, $rawContent);
0 ignored issues
show
Bug introduced by
It seems like $rawContent can also be of type false; however, parameter $incomingBinary of web\lib\admin\OptionParser::checkUploadSanity() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

422
                        $sanity = $this->checkUploadSanity($objValue, /** @scrutinizer ignore-type */ $rawContent);
Loading history...
423
                        if ($rawContent === FALSE || !$sanity['result']) {
424
                            $bad[] = ['type'=>$objValue, 'details'=>$sanity['details']];
425
                            continue 2;
426
                        }
427
                        $content = base64_encode($rawContent);
428
                        break;
429
                    }
430
                    continue 2;
431
                default:
432
                    throw new Exception("Internal Error: Unknown option type " . $objValue . "!");
433
            }
434
            // lang can be NULL here, if it's not a multilang attribute, or a ROWID reference. Never mind that.
435
            $retval[] = ["$objValue" => ["lang" => $languageFlag, "content" => $content]];
436
        }
437
        return [$retval, $multilangAttrsWithC, $bad];
438
    }
439
440
    /**
441
     * find out which languages were submitted, and whether a default language was in the set
442
     * @param string $attribute           the name of the attribute we are looking at
443
     * @param string $languageFlag        which language flag was submitted
444
     * @param array  $multilangAttrsWithC by-reference: add to this if we found a C language variant
445
     * @return void
446
     */
447
    private function determineLanguages($attribute, $languageFlag, &$multilangAttrsWithC) {
448
        if (!isset($multilangAttrsWithC[$attribute])) { // on first sight, initialise the attribute as "no C language set"
449
            $multilangAttrsWithC[$attribute] = FALSE;
450
        }
451
        if ($languageFlag == "") { // user forgot to select a language
452
            $languageFlag = "C";
453
        }
454
        // did we get a C language? set corresponding value to TRUE
455
        if ($languageFlag == "C") {
456
            $multilangAttrsWithC[$attribute] = TRUE;
457
        }
458
    }
459
460
    /**
461
     * 
462
     * @param string $attribute          which attribute was sent?
463
     * @param string $previsionalContent which content was sent?
464
     * @param array  $bad                list of malformed attributes, by-reference
465
     * @return string|false FALSE if value is not in expected format, else the content itself
466
     */
467
    private function furtherStringChecks($attribute, $previsionalContent, &$bad) {
468
        $content = FALSE;
469
        switch ($attribute) {
470
            case "media:consortium_OI":
471
                $content = $this->validator->consortiumOI($previsionalContent);
472
                if ($content === FALSE) {
473
                    $bad[] = ['type'=>$attribute, 'details'=>''];
474
                    return FALSE;
475
                }
476
                break;
477
            case "media:remove_SSID":
478
                $content = $this->validator->string($previsionalContent);
479
                if ($content == "eduroam") {
480
                    $bad[] = ['type'=>$attribute, 'details'=>''];
481
                    return FALSE;
482
                }
483
                break;
484
            case "media:force_proxy":
485
                $content = $this->validator->string($previsionalContent);
486
                $serverAndPort = explode(':', strrev($content), 2);
487
                if (count($serverAndPort) != 2) {
488
                    $bad[] = ['type'=>$attribute, 'details'=>''];
489
                    return FALSE;
490
                }
491
                $port = strrev($serverAndPort[0]);
492
                if (!is_numeric($port)) {
493
                    $bad[] = ['type'=>$attribute, 'details'=>''];
494
                    return FALSE;
495
                }
496
                break;
497
            case "support:url":
498
                $content = $this->validator->string($previsionalContent);
499
                if (preg_match("/^http/", $content) != 1) {
500
                    $bad[] = ['type'=>$attribute, 'details'=>''];
501
                    return FALSE;
502
                }
503
                break;
504
            case "support:email":
505
                $content = $this->validator->email($previsionalContent);
506
                if ($content === FALSE) {
507
                    $bad[] = ['type'=>$attribute, 'details'=>''];
508
                    return FALSE;
509
                }
510
                break;
511
            case "managedsp:operatorname":
512
                $content = $previsionalContent;
513
                if (!preg_match("/^1.*\..*/", $content)) {
514
                    $bad[] = ['type'=>$attribute, 'details'=>''];
515
                    return FALSE;
516
                }
517
                break;
518
            default:
519
                $content = $this->validator->string($previsionalContent);
520
                break;
521
        }
522
        return $content;
523
    }
524
525
    /**
526
     * The main function: takes all HTML field inputs, makes sense of them and stores valid data in the database
527
     * 
528
     * @param mixed  $object     The object for which attributes were submitted
529
     * @param array  $postArray  incoming attribute names and values as submitted with $_POST
530
     * @param array  $filesArray incoming attribute names and values as submitted with $_FILES
531
     * @param int    $eaptype    for eap-specific attributes (only used where $object is a ProfileRADIUS instance)
532
     * @param string $device     for device-specific attributes (only used where $object is a ProfileRADIUS instance)
533
     * @return string text to be displayed in UI with the summary of attributes added
534
     * @throws Exception
535
     */
536
    public function processSubmittedFields($object, array $postArray, array $filesArray, int $eaptype = NULL, string $device = NULL) {
537
        $good = [];
538
        // Step 1: collate option names, option values and uploaded files (by 
539
        // filename reference) into one array for later handling
540
541
        $iterator = $this->collateOptionArrays($postArray, $filesArray);
542
543
        // Step 2: sieve out malformed input
544
        // $multilangAttrsWithC is a helper array to keep track of multilang 
545
        // options that were set in a specific language but are not 
546
        // accompanied by a "default" language setting
547
        // if there are some without C by the end of processing, we need to warn
548
        // the admin that this attribute is "invisible" in certain languages
549
        // attrib_name -> boolean
550
        // $bad contains the attributes which failed input validation
551
552
        list($cleanData, $multilangAttrsWithC, $bad) = $this->sanitiseInputs($iterator);
553
554
        // Step 3: now we have clean input data. Some attributes need special care:
555
        // URL-based attributes need to be downloaded to get their actual content
556
        // CA files may need to be split (PEM can contain multiple CAs 
557
558
        $optionsStep2 = $this->postProcessValidAttributes($cleanData, $good, $bad);
559
560
        // Step 4: coordinates do not follow the usual POST array as they are 
561
        // two values forming one attribute; extract those two as an extra step
562
563
        $options = array_merge($optionsStep2, $this->postProcessCoordinates($postArray, $good));
564
        
565
        // Step 5: push all the received options to the database. Keep mind of 
566
        // the list of existing database entries that are to be deleted.
567
        // 5a: first deletion step: purge all old content except file-based attributes;
568
        //     then take note of which file-based attributes are now stale
569
        if ($device === NULL && $eaptype === NULL) {
570
            $remaining = $object->beginflushAttributes();
571
            $killlist = $this->sendOptionsToDatabase($object, $options, $remaining);
572
        } elseif ($device !== NULL) {
573
            $remaining = $object->beginFlushMethodLevelAttributes(0, $device);
574
            $killlist = $this->sendOptionsToDatabase($object, $options, $remaining, $device);
575
        } else {
576
            $remaining = $object->beginFlushMethodLevelAttributes($eaptype, "");
577
            $killlist = $this->sendOptionsToDatabase($object, $options, $remaining, NULL, $eaptype);
578
        }
579
        // 5b: finally, kill the stale file-based attributes which are not wanted any more.
580
        $object->commitFlushAttributes($killlist);
581
582
        // finally: return HTML code that gives feedback about what we did. 
583
        // In some cases, callers won't actually want to display it; so simply
584
        // do not echo the return value. Reasons not to do this is if we working
585
        // e.g. from inside an overlay
586
        return $this->displaySummaryInUI($good, $bad, $multilangAttrsWithC);
587
    }
588
589
}
590