Passed
Push — master ( c3c977...ef459a )
by Stefan
07:40
created

OptionDisplay::prefilledOptionTable()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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