Passed
Push — master ( 54cc30...cc7767 )
by Maja
08:24
created

OptionDisplay::tooltip()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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