Test Setup Failed
Push — master ( d51715...e28ecf )
by Stefan
15:13 queued 11s
created

OptionDisplay::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 3
c 2
b 0
f 0
dl 0
loc 4
rs 10
cc 1
nc 1
nop 2
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
 * We need to display previously set options in various forms. This class covers
29
 * the ways to do that; the generated page content can then be parsed with 
30
 * OptionParser.
31
 * 
32
 * @author Stefan Winter <[email protected]>
33
 */
34
class OptionDisplay extends \core\common\Entity {
35
    
36
    /**
37
     * stores all the options we are caring about
38
     * 
39
     * @var array
40
     */
41
    private $listOfOptions;
42
43
    /**
44
     * on which level are we operating?
45
     * 
46
     * @var string
47
     */
48
    private $level;
49
50
    /**
51
     * a counter storing how many locations are to be displayed
52
     * 
53
     * @var integer
54
     */
55
    private $allLocationCount;
56
57
    /**
58
     * When "fresh" options are displayed (HTML select/otion fields, optionally
59
     * with language, and of varying data types) we want to give each option
60
     * the same prominence and iterate over all options in the list. This
61
     * variable keeps track how many option HTML code we've already sent, so
62
     * that we can iterate correctly.
63
     * 
64
     * Only used inside noPrefillText variant of the optiontext() call
65
     * 
66
     * @var integer
67
     */
68
    private $optionIterator;
69
70
    /**
71
     * Which attributes are we talking about?
72
     * @param array  $options the options of interest
73
     * @param string $level   the level on which these options were defined by the user (not applicable for XHR UI, then it is NULL)
74
     */
75
    public function __construct(array $options, string $level = NULL) {
76
        $this->listOfOptions = $options;
77
        $this->level = $level;
78
        $this->allLocationCount = 0;
79
    }
80
81
    /**
82
     * creates a table with all the set options prefilled. Only displays options
83
     * of the category indicated.
84
     * @param string $attributePrefix category of option to display
85
     * @return string HTML code <table>
86
     */
87
    public function prefilledOptionTable(string $attributePrefix) {
88
        $retval = "<table id='expandable_$attributePrefix" . "_options'>";
89
90
        $prepopulate = [];
91
        foreach ($this->listOfOptions as $existingAttribute) {
92
            if ($existingAttribute['level'] == $this->level) {
93
                $prepopulate[] = $existingAttribute;
94
            }
95
        }
96
        if (is_array($prepopulate) && ( count($prepopulate) > 0 || $attributePrefix == "device-specific" || $attributePrefix == "eap-specific" )) { // editing... fill with values
97
            $retval .= $this->addOptionEdit($attributePrefix, $prepopulate);
98
        } else {
99
            $retval .= $this->addOptionNew($attributePrefix);
100
        }
101
        $retval .= "</table>";
102
        return $retval;
103
    }
104
105
    /**
106
     * Displays options for a given option class, in Edit mode.
107
     * 
108
     * @param string $class       the class of options that is to be displayed
109
     * @param array  $prepopulate should an empty set of fillable options be displayed, or do we have existing data to prefill with
110
     * @return string
111
     */
112
    private function addOptionEdit(string $class, array $prepopulate = []) { // no GET class ? we've been called directly:
113
        // this can mean either a new object (list all options with empty values)
114
        // or that an object is to be edited. In that case, $prepopulated has to
115
        // contain the array of existing variables
116
        // we expect the variable $class to contain the class of options
117
        $retval = "";
118
        $optioninfo = \core\Options::instance();
119
        $blackListOnPrefill = "user:fedadmin|managedsp:vlan|managedsp:operatorname";
120
        if (\config\Master::FUNCTIONALITY_LOCATIONS['CONFASSISTANT_SILVERBULLET'] == "LOCAL" && \config\Master::FUNCTIONALITY_LOCATIONS['CONFASSISTANT_RADIUS'] != "LOCAL") {
121
            $blackListOnPrefill .= "|fed:silverbullet";
122
        }
123
        foreach ($prepopulate as $option) {
124
            if (preg_match("/$class:/", $option['name']) && !preg_match("/($blackListOnPrefill)/", $option['name'])) {
125
                $optiontypearray = $optioninfo->optionType($option['name']);
126
                $loggerInstance = new \core\common\Logging();
127
                $loggerInstance->debug(5, "About to execute optiontext with PREFILL!\n");
128
                $retval .= $this->optiontext([$option['name']], ($optiontypearray["type"] == "file" ? 'ROWID-' . $option['level'] . '-' . $option['row'] : $option['value']), $option['lang']);
129
            }
130
        }
131
        return $retval;
132
    }
133
134
    /**
135
     * Displays options for a given option class, in New mode.
136
     * 
137
     * @param string $class the class of options that is to be displayed
138
     * @return string
139
     */
140
    private function addOptionNew(string $class) {
141
        $retval = "";
142
143
        $optioninfo = \core\Options::instance();
144
145
        $list = $optioninfo->availableOptions($class);
146
        switch ($class) {
147
            case "general":
148
                unset($list[array_search("general:geo_coordinates", $list)]);
149
                break;
150
            case "user":
151
                unset($list[array_search("user:fedadmin", $list)]);
152
                break;
153
            case "managedsp":
154
                unset($list[array_search("managedsp:vlan", $list)]);
155
                unset($list[array_search("managedsp:operatorname", $list)]);
156
                break;
157
            case "fed":
158
                //normally, we have nothing to hide on that level
159
                // if we are a Managed IdP exclusive deployment, do not display or allow
160
                // to change the "Enable Managed IdP" boolean - it is simply always there
161
                if (\config\Master::FUNCTIONALITY_LOCATIONS['CONFASSISTANT_SILVERBULLET'] == "LOCAL" && \config\Master::FUNCTIONALITY_LOCATIONS['CONFASSISTANT_RADIUS'] != "LOCAL") {
162
                    unset($list[array_search("fed:silverbullet", $list)]);
163
                }
164
                break;
165
            default:
166
                break;
167
        }
168
        $list2 = array_values($list);
169
170
        // add as many options as there are different option types
171
        $numberOfOptions = count($list2);
172
        for ($this->optionIterator = 0; $this->optionIterator < $numberOfOptions; $this->optionIterator++) {
173
            $retval .= $this->optiontext($list2);
174
        }
175
        return $retval;
176
    }
177
178
    /**
179
     * produce code for a option-specific tooltip
180
     * @param int     $rowid     the number (nonce during page build) of the option 
181
     *                           that should get the tooltip
182
     * @param string  $input     the option name. Tooltip for it will be displayed
183
     *                           if we have one available.
184
     * @param boolean $isVisible should the tooltip be visible with the option,
185
     *                           or are they both currently hidden?
186
     * @return string
187
     */
188
    private function tooltip($rowid, $input, $isVisible) {
189
        \core\common\Entity::intoThePotatoes();
190
        $descriptions = [];
191
        if (count(\config\ConfAssistant::CONSORTIUM['ssid']) > 0) {
192
            $descriptions["media:SSID"] = sprintf(_("This attribute can be set if you want to configure an additional SSID besides the default SSIDs for %s. It is almost always a bad idea not to use the default SSIDs. The only exception is if you have premises with an overlap of the radio signal with another %s hotspot. Typical misconceptions about additional SSIDs include: I want to have a local SSID for my own users. It is much better to use the default SSID and separate user groups with VLANs. That approach has two advantages: 1) your users will configure %s properly because it is their everyday SSID; 2) if you use a custom name and advertise this one as extra secure, your users might at some point roam to another place which happens to have the same SSID name. They might then be misled to believe that they are connecting to an extra secure network while they are not."), \config\ConfAssistant::CONSORTIUM['display_name'], \config\ConfAssistant::CONSORTIUM['display_name'], \config\ConfAssistant::CONSORTIUM['display_name']);
193
        }
194
        $descriptions["media:force_proxy"] = sprintf(_("The format of this option is: IPv4|IPv6|hostname:port . Forcing your users through a content filter of your own is a significant invasion of user self-determination. It also has technical issues. Please throughly read the discussion at %s before specifying a proxy with this option."), "https://github.com/GEANT/CAT/issues/96");
195
        $descriptions["managedsp:realmforvlan"] = sprintf(_("If you are also using %s, then your own realm is automatically tagged with the VLAN you choose, there is no need to add it here manually."), \core\ProfileSilverbullet::PRODUCTNAME);
196
        \core\common\Entity::outOfThePotatoes();
197
        if (!isset($descriptions[$input])) {
198
            return "";
199
        }
200
        return "<span class='tooltip' id='S$rowid-tooltip-$input' style='display:" . ($isVisible ? "block" : "none") . "' onclick='alert(\"" . $descriptions[$input] . "\")'><img src='../resources/images/icons/question-mark-icon.png" . "'></span>";
201
    }
202
203
    /**
204
     * 
205
     * @param int   $rowid the number (nonce during page build) of the option 
206
     *                     that should get the tooltip
207
     * @param array $list  elements of the drop-down list
208
     * @return array HTML code and which option is active
209
     * @throws \Exception
210
     */
211
    private function selectElement($rowid, $list) {
212
        $jsmagic = "onchange='
213
                               if (/#ML#/.test(document.getElementById(\"option-S" . $rowid . "-select\").value)) {
214
                                   document.getElementById(\"S$rowid-input-langselect\").style.display = \"block\";
215
                                   } else {
216
                                   document.getElementById(\"S$rowid-input-langselect\").style.display = \"none\";
217
                                   }";
218
        foreach (array_keys(OptionDisplay::HTML_DATATYPE_TEXTS) as $key) {
219
            $jsmagic .= "if (/#" . $key . "#/.test(document.getElementById(\"option-S" . $rowid . "-select\").value)) {
220
                                  document.getElementById(\"S$rowid-input-file\").style.display = \"" . ($key == \core\Options::TYPECODE_FILE ? "block" : "none") . "\";
221
                                  document.getElementById(\"S$rowid-input-text\").style.display = \"" . ($key == \core\Options::TYPECODE_TEXT ? "block" : "none") . "\";
222
                                  document.getElementById(\"S$rowid-input-string\").style.display = \"" . ($key == \core\Options::TYPECODE_STRING ? "block" : "none") . "\";
223
                                  document.getElementById(\"S$rowid-input-boolean\").style.display = \"" . ($key == \core\Options::TYPECODE_BOOLEAN ? "block" : "none") . "\";
224
                                  document.getElementById(\"S$rowid-input-integer\").style.display = \"" . ($key == \core\Options::TYPECODE_INTEGER ? "block" : "none") . "\";
225
                             }
226
                             ";
227
            // hide all tooltips (each is a <span>, and there are no other <span>s)
228
            $jsmagic .= <<< FOO
229
                    var ourtooltips = document.querySelectorAll(&#34;[id^=&#39;S$rowid-tooltip-&#39;]&#34;);
230
                    for (var i=0; i<ourtooltips.length; i++) {
231
                      ourtooltips[i].style.display = "none";
232
                    }
233
                    var optionnamefull = document.getElementById("option-S$rowid-select").value;
234
                    var firstdelimiter = optionnamefull.indexOf("#");
235
                    var optionname = optionnamefull.substring(0,firstdelimiter);
236
                    var tooltipifany = document.getElementById("S$rowid-tooltip-"+optionname);
237
                    if (tooltipifany != null) {
238
                      tooltipifany.style.display = "block";
239
                    }
240
FOO;
241
        }
242
        $jsmagic .= "'";
243
244
        $optioninfo = \core\Options::instance();
245
        $retval = "<span style='display:flex';>";
246
        $iterator = 0;
247
        $tooltips = "";
248
        $uiElements = new UIElements();
249
        $activelisttype = [];
250
        switch (count($list)) {
251
            case 1: // if there is only one option available, don't introduce an artificial drop-down for it
252
                $value = array_shift($list);
253
                $listtype = $optioninfo->optionType($value);
254
                $retval .= $uiElements->displayName($value);
255
                $retval .= "<input type='hidden' name='option[S$rowid]' value='$value#" . $listtype["type"] . "#" . $listtype["flag"] . "#'/>";
256
                $activelisttype = $listtype;
257
                $tooltips = $this->tooltip($rowid, $value, TRUE);
258
                break;
259
            default:
260
                $retval .= "<select id='option-S$rowid-select' name='option[S$rowid]' $jsmagic>";
261
                foreach ($list as $value) {
262
                    $listtype = $optioninfo->optionType($value);
263
                    $retval .= "<option id='option-S$rowid-v-$value' value='$value#" . $listtype["type"] . "#" . $listtype["flag"] . "#' ";
264
                    if ($iterator == $this->optionIterator) {
265
                        $retval .= "selected='selected'";
266
                        $activelisttype = $listtype;
267
                        $tooltips .= $this->tooltip($rowid, $value, TRUE);
268
                    } else {
269
                        $tooltips .= $this->tooltip($rowid, $value, FALSE);
270
                    }
271
                    $retval .= ">" . $uiElements->displayName($value) . "</option>";
272
                    $iterator++;
273
                }
274
275
                if (count($activelisttype) == 0) {
276
                    throw new \Exception("We should have found the active list type by now!");
277
                }
278
                $retval .= "</select>";
279
        }
280
        $retval .= $tooltips;
281
        $retval .= "</span>";
282
283
        return ["TEXT" => $retval, "ACTIVE" => $activelisttype];
284
    }
285
286
    /**
287
     * HTML code to display the language selector
288
     * 
289
     * @param int     $rowid       the number (nonce during page build) of the option 
290
     *                             that should get the tooltip
291
     * @param boolean $makeVisible is the language selector to be made visible?
292
     * @return string
293
     */
294
    private function selectLanguage($rowid, $makeVisible) {
295
        \core\common\Entity::intoThePotatoes();
296
        $retval = "<select style='display:" . ($makeVisible ? "block" : "none") . "' name='value[S$rowid-lang]' id='S" . $rowid . "-input-langselect'>
297
            <option value='' name='select_language' selected>" . _("select language") . "</option>
298
            <option value='C' name='all_languages'>" . _("default/other languages") . "</option>";
299
        foreach (\config\Master::LANGUAGES as $langindex => $possibleLang) {
300
            $thislang = $possibleLang['display'];
301
            $retval .= "<option value='$langindex' name='$langindex'>$thislang</option>";
302
        }
303
        $retval .= "</select>";
304
        \core\common\Entity::outOfThePotatoes();
305
        return $retval;
306
    }
307
308
    const HTML_DATATYPE_TEXTS = [
309
        \core\Options::TYPECODE_FILE => ["html" => "input type='file'", "tail" => ' size=\'10\''],
310
        \core\Options::TYPECODE_BOOLEAN => ["html" => "input type='checkbox'", "tail" => ''],
311
        \core\Options::TYPECODE_INTEGER => ["html" => "input type='number'", "tail" => ''],
312
        \core\Options::TYPECODE_STRING => ["html" => "input type='string'", "tail" => ''],
313
        \core\Options::TYPECODE_TEXT => ["html" => "textarea cols='30' rows='3'", "tail" => '></textarea'],
314
    ];
315
316
    /**
317
     * HTML code for a given option. Marks the matching datatype as visible, all other datatypes hidden
318
     * @param int   $rowid      the number (nonce during page build) of the option 
319
     *                          that should get the tooltip
320
     * @param array $activetype the active datatype that is to be visible
321
     * @return string
322
     */
323
    private function inputFields($rowid, $activetype) {
324
        $retval = "";
325
        foreach (OptionDisplay::HTML_DATATYPE_TEXTS as $key => $type) {
326
            $retval .= "<" . $type['html'] . " style='display:" . ($activetype['type'] == $key ? "block" : "none") . "' name='value[S$rowid-$key]' id='S" . $rowid . "-input-" . $key . "'" . $type['tail'] . ">";
327
        }
328
        return $retval;
329
    }
330
331
    /**
332
     * HTML code to display a "fresh" option (including type selector and JavaScript to show/hide relevant input fields)
333
     * @param int   $rowid the HTML field base name of the option to be displayed
334
     * @param array $list  the list of option names to include in the type selector
335
     * @return string HTML code
336
     * @throws Exception
337
     */
338
    private function noPrefillText(int $rowid, array $list) {
339
        // first column: the <select> element with the names of options and their field-toggling JS magic
340
        $selectorInfo = $this->selectElement($rowid, $list);
341
        $retval = "<td>" . $selectorInfo["TEXT"] . "</td>";
342
        // second column: the <select> element for language selection - only visible if the active option is multi-lang
343
        $retval .= "<td>" . $this->selectLanguage($rowid, $selectorInfo['ACTIVE']['flag'] == "ML") . "</td>";
344
        // third column: the actual input fields; the data type of the active option is visible, all others hidden
345
        $retval .= "<td>" . $this->inputFields($rowid, $selectorInfo['ACTIVE']) . "</td>";
346
        return $retval;
347
    }
348
349
    /**
350
     * generates HTML code that displays an already set option.
351
     * 
352
     * @param int    $rowid       the HTML field base name of the option to be displayed
353
     * @param string $optionName  the name of the option to display
354
     * @param string $optionValue the value of the option to display
355
     * @param mixed  $optionLang  the language of the option to display
356
     * @return string HTML code
357
     * @throws Exception
358
     */
359
    private function prefillText(int $rowid, string $optionName, string $optionValue, $optionLang) {
360
        \core\common\Entity::intoThePotatoes();
361
        $retval = "";
362
        $optioninfo = \core\Options::instance();
363
        $loggerInstance = new \core\common\Logging();
364
        $loggerInstance->debug(5, "Executed with PREFILL $optionValue!\n");
365
        $retval .= "<td>";
366
        $uiElements = new UIElements();
367
        $listtype = $optioninfo->optionType($optionName);
368
        $retval .= "<span style='display:flex;'>" . $uiElements->displayName($optionName);
369
        $retval .= $this->tooltip($rowid, $optionName, TRUE) . "</span>";
370
        $retval .= "<input type='hidden' id='option-S$rowid-select' name='option[S$rowid]' value='$optionName#" . $listtype["type"] . "#" . $listtype["flag"] . "#' ></td>";
371
372
        // language tag if any
373
        $retval .= "<td>";
374
        if ($listtype["flag"] == "ML") {
375
376
            $language = "(" . strtoupper($optionLang) . ")";
377
            if ($optionLang == 'C') {
378
                $language = _("(default/other languages)");
379
            }
380
            $retval .= $language;
381
            $retval .= "<input type='hidden' name='value[S$rowid-lang]' id='S" . $rowid . "-input-langselect' value='" . $optionLang . "' style='display:block'>";
382
        }
383
        $retval .= "</td>";
384
// attribute content
385
        $retval .= "<td>";
386
        $displayedVariant = "";
387
        switch ($listtype["type"]) {
388
            case \core\Options::TYPECODE_COORDINATES:
389
                $this->allLocationCount = $this->allLocationCount + 1;
390
                // display of the locations varies by map provider
391
                $classname = "\web\lib\admin\Map" . \config\ConfAssistant::MAPPROVIDER['PROVIDER'];
392
                $link = $classname::optionListDisplayCode($optionValue, $this->allLocationCount);
393
                $retval .= "<input readonly style='display:none' type='text' name='value[S$rowid-" . \core\Options::TYPECODE_TEXT . "]' id='S$rowid-input-text' value='$optionValue'>$link";
394
                break;
395
            case \core\Options::TYPECODE_FILE:
396
                $retval .= "<input readonly type='text' name='value[S$rowid-" . \core\Options::TYPECODE_STRING . "]' id='S" . $rowid . "-input-string' style='display:none' value='" . urlencode($optionValue) . "'>";
397
                $uiElements = new UIElements();
398
                switch ($optionName) {
399
                    case "eap:ca_file":
400
                    // fall-through intentional: display both types the same way
401
                    case "fed:minted_ca_file":
402
                        $retval .= $uiElements->previewCAinHTML($optionValue);
403
                        break;
404
                    case "general:logo_file":
405
                    // fall-through intentional: display both types the same way
406
                    case "fed:logo_file":
407
                        $retval .= $uiElements->previewImageinHTML($optionValue);
408
                        break;
409
                    case "support:info_file":
410
                        $retval .= $uiElements->previewInfoFileinHTML($optionValue);
411
                        break;
412
                    default:
413
                        $retval .= _("file content");
414
                }
415
                break;
416
            case \core\Options::TYPECODE_STRING:
417
            // fall-thorugh is intentional; mostly identical HTML code for the three types
418
            case \core\Options::TYPECODE_INTEGER:
419
            // fall-thorugh is intentional; mostly identical HTML code for the three types
420
            case \core\Options::TYPECODE_TEXT:
421
                $displayedVariant = $optionValue; // for all three types, value tag and actual display are identical
422
            case \core\Options::TYPECODE_BOOLEAN:
423
                if ($listtype['type'] == \core\Options::TYPECODE_BOOLEAN) {// only modify in this one case
424
                    $displayedVariant = ($optionValue == "on" ? _("on") : _("off"));
425
                }
426
                $retval .= "<strong>$displayedVariant</strong><input type='hidden' name='value[S$rowid-" . $listtype['type'] . "]' id='S" . $rowid . "-input-" . $listtype["type"] . "' value=\"" . htmlspecialchars($optionValue) . "\" style='display:block'>";
427
                break;
428
            default:
429
                // this should never happen!
430
                throw new Exception("Internal Error: unknown attribute type $listtype!");
431
        }
432
        $retval .= "</td>";
433
        \core\common\Entity::outOfThePotatoes();
434
        return $retval;
435
    }
436
437
    /**
438
     * Displays a container for options. Either with prefilled data or empty; if
439
     * empty then has HTML <input> tags with clever javaScript to allow selection
440
     * of different option names and types
441
     * @param array  $list         options which should be displayed; can be only exactly one if existing option, or multiple if new option type
442
     * @param string $prefillValue for an existing option, it's value to be displayed
443
     * @param string $prefillLang  for an existing option, the language of the value to be displayed
444
     * @return string HTML code <tr>
445
     * @throws Exception
446
     */
447
    public function optiontext(array $list, string $prefillValue = NULL, string $prefillLang = NULL) {
448
        $rowid = mt_rand();
449
450
        $retval = "<tr id='option-S$rowid' style='vertical-align:top'>";
451
452
        $item = "MULTIPLE";
453
        if ($prefillValue === NULL) {
454
            $retval .= $this->noPrefillText($rowid, $list);
455
        }
456
457
        if ($prefillValue !== NULL) {
458
            // prefill is always only called with a list with exactly one element.
459
            // if we see anything else here, get excited.
460
            if (count($list) != 1) {
461
                throw new Exception("Optiontext prefilled display only can work with exactly one option!");
462
            }
463
            $item = array_pop($list);
464
            $retval .= $this->prefillText($rowid, $item, $prefillValue, $prefillLang);
465
        }
466
        $retval .= "
467
468
       <td>
469
          <button type='button' class='delete' onclick='";
470
        if ($prefillValue !== NULL && $item == "general:geo_coordinates") {
471
            $funcname = "Map" . \config\ConfAssistant::MAPPROVIDER['PROVIDER'] . 'DeleteCoord';
472
            $retval .= 'if (typeof ' . $funcname . ' === "function") { ' . $funcname . '(' . $this->allLocationCount . '); } ';
473
        }
474
        $retval .= 'deleteOption("option-S' . $rowid . '")';
475
        $retval .= "'>-</button>
476
       </td>
477
    </tr>";
478
        return $retval;
479
    }
480
481
}
482