Passed
Push — master ( 21bd5f...0f44a9 )
by Stefan
04:12
created

UIElements::previewCAinHTML()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 12
nc 5
nop 1
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
use Exception;
14
15
/**
16
 * This class provides various HTML snippets and other UI-related convenience functions.
17
 * 
18
 * @author Stefan Winter <[email protected]>
19
 */
20
class UIElements {
21
22
    /**
23
     * the custom displayable variant of the term 'federation'
24
     * 
25
     * @var string
26
     */
27
    public $nomenclature_fed;
28
29
    /**
30
     * the custom displayable variant of the term 'institution'
31
     * 
32
     * @var string
33
     */
34
    public $nomenclature_inst;
35
36
    /**
37
     * Initialises the class.
38
     * 
39
     * Mainly fetches various nomenclature from the config and attempts to translate those into local language. Needs pre-loading some terms.
40
     */
41
    public function __construct() {
42
        // some config elements are displayable. We need some dummies to 
43
        // translate the common values for them. If a deployment chooses a 
44
        // different wording, no translation, sorry
45
46
        $dummy_NRO = _("National Roaming Operator");
47
        $dummy_inst1 = _("identity provider");
48
        $dummy_inst2 = _("organisation");
49
        // and do something useless with the strings so that there's no "unused" complaint
50
        if ( $dummy_NRO . $dummy_inst1 . $dummy_inst2 == "") {
51
            // Oh well.
52
            explode(' ',$dummy_NRO);
53
        }
54
        $this->nomenclature_fed = _(CONFIG_CONFASSISTANT['CONSORTIUM']['nomenclature_federation']);
55
        $this->nomenclature_inst = _(CONFIG_CONFASSISTANT['CONSORTIUM']['nomenclature_institution']);
56
    }
57
58
    /**
59
     * provides human-readable text for the various option names as stored in DB.
60
     * 
61
     * @param string $input raw text in need of a human-readable display variant
62
     * @return string the human-readable variant
63
     * @throws Exception
64
     */
65
    public function displayName($input) {
66
67
        $ssidText = _("SSID");
68
        $ssidLegacyText = _("SSID (with WPA/TKIP)");
69
        $passpointOiText = _("HS20 Consortium OI");
70
71
        if (count(CONFIG_CONFASSISTANT['CONSORTIUM']['ssid']) > 0) {
72
            $ssidText = _("Additional SSID");
73
            $ssidLegacyText = _("Additional SSID (with WPA/TKIP)");
74
        }
75
        if (!empty(CONFIG_CONFASSISTANT['CONSORTIUM']['interworking-consortium-oi']) && count(CONFIG_CONFASSISTANT['CONSORTIUM']['interworking-consortium-oi']) > 0) {
76
            $passpointOiText = _("Additional HS20 Consortium OI");
77
        }
78
79
        $displayNames = [_("Support: Web") => "support:url",
80
            _("Support: EAP Types") => "support:eap_types",
81
            _("Support: Phone") => "support:phone",
82
            _("Support: E-Mail") => "support:email",
83
            sprintf(_("Name of %s"), $this->nomenclature_inst) => "general:instname",
84
            _("Location") => "general:geo_coordinates",
85
            _("Logo URL") => "general:logo_url",
86
            _("Logo image") => "general:logo_file",
87
            _("Configure Wired Ethernet") => "media:wired",
88
            _("Name (CN) of Authentication Server") => "eap:server_name",
89
            _("Enable device assessment") => "eap:enable_nea",
90
            _("Terms of Use") => "support:info_file",
91
            _("CA Certificate URL") => "eap:ca_url",
92
            _("CA Certificate File") => "eap:ca_file",
93
            _("Profile Display Name") => "profile:name",
94
            _("Production-Ready") => "profile:production",
95
            _("Admin Accepted Terms of Use") => 'hiddenprofile:tou_accepted',
96
            _("Extra text on downloadpage for device") => "device-specific:customtext",
97
            _("Redirection Target") => "device-specific:redirect",
98
            _("Extra text on downloadpage for EAP method") => "eap-specific:customtext",
99
            _("Turn on selection of EAP-TLS User-Name") => "eap-specific:tls_use_other_id",
100
            _("Profile Description") => "profile:description",
101
            _("Custom Installer Name Suffix") => "profile:customsuffix",
102
            sprintf(_("%s Administrator"), $this->nomenclature_fed) => "user:fedadmin",
103
            _("Real Name") => "user:realname",
104
            _("E-Mail Address") => "user:email",
105
            _("Remove/Disable SSID") => "media:remove_SSID",
106
            _("Content Filter Proxy, HTTP") => "media:force_proxy_http",
107
            _("Content Filter Proxy, HTTPS") => "media:force_proxy_https",
108
            _("Custom CSS file for User Area") => "fed:css_file",
109
            sprintf(_("%s Logo"), $this->nomenclature_fed) => "fed:logo_file",
110
            _("Preferred Skin for User Area") => "fed:desired_skin",
111
            _("Include NRO branding in installers") => "fed:include_logo_installers",
112
            sprintf(_("%s Name"), $this->nomenclature_fed) => "fed:realname",
113
            _("Custom text in IdP Invitations") => "fed:custominvite",
114
            sprintf(_("Enable %s"), \core\ProfileSilverbullet::PRODUCTNAME) => "fed:silverbullet",
115
            sprintf(_("%s: Do not terminate EAP"), \core\ProfileSilverbullet::PRODUCTNAME) => "fed:silverbullet-noterm",
116
            sprintf(_("%s: max users per profile"), \core\ProfileSilverbullet::PRODUCTNAME) => "fed:silverbullet-maxusers",
117
            _("Mint IdPs with CA on creation") => "fed:minted_ca_file",
118
            $ssidText => "media:SSID",
119
            $ssidLegacyText => "media:SSID_with_legacy",
120
            $passpointOiText => "media:consortium_OI",
121
        ];
122
123
        $find = array_keys($displayNames, $input, TRUE);
124
125
        if (count($find) == 0) { // this is an error! throw an Exception
126
            throw new \Exception("The translation of an option name was requested, but the option is not known to the system: " . htmlentities($input));
127
        }
128
        return $find[0];
129
    }
130
131
    /**
132
     * creates an HTML information block with a list of options from a given category and level
133
     * @param array $optionlist list of options
134
     * @param string $class option class of interest
135
     * @param string $level option level of interest
136
     * @return string HTML code
137
     */
138
    public function infoblock(array $optionlist, string $class, string $level) {
139
        $googleMarkers = [];
140
        $retval = "";
141
        $optioninfo = \core\Options::instance();
142
143
        foreach ($optionlist as $option) {
144
            $type = $optioninfo->optionType($option['name']);
145
            if (preg_match('/^' . $class . '/', $option['name']) && $option['level'] == "$level") {
146
                // all non-multilang attribs get this assignment ...
147
                $language = "";
148
                $content = $option['value'];
149
                // ... override them with multilang tags if needed
150
                if ($type["flag"] == "ML") {
151
                    $language = _("default/other languages");
152
                    if ($option['lang'] != 'C') {
153
                        $language = CONFIG['LANGUAGES'][$option['lang']]['display'] ?? "(unsupported language)";
154
                    }
155
                }
156
157
                switch ($type["type"]) {
158
                    case "coordinates":
159
                        $coords = json_decode($option['value'], true);
160
                        $googleMarkers[] = $coords;
161
                        break;
162
                    case "file":
163
                        $retval .= "<tr><td>" . $this->displayName($option['name']) . "</td><td>$language</td><td>";
164
                        switch ($option['name']) {
165
                            case "general:logo_file":
166
                            case "fed:logo_file":
167
                                $retval .= $this->previewImageinHTML('ROWID-' . $option['level'] . '-' . $option['row']);
168
                                break;
169
                            case "eap:ca_file":
170
                                // fall-through intended: display both the same way
171
                            case "fed:minted_ca_file":
172
                                $retval .= $this->previewCAinHTML('ROWID-' . $option['level'] . '-' . $option['row']);
173
                                break;
174
                            case "support:info_file":
175
                                $retval .= $this->previewInfoFileinHTML('ROWID-' . $option['level'] . '-' . $option['row']);
176
                                break;
177
                            default:
178
                        }
179
                        break;
180
                    case "boolean":
181
                        $retval .= "<tr><td>" . $this->displayName($option['name']) . "</td><td>$language</td><td><strong>" . ($content == "on" ? _("on") : _("off") ) . "</strong></td></tr>";
182
                        break;
183
                    default:
184
                        $retval .= "<tr><td>" . $this->displayName($option['name']) . "</td><td>$language</td><td><strong>$content</strong></td></tr>";
185
                }
186
            }
187
        }
188
        if (count($googleMarkers)) {
189
            $marker = '<markers>';
190
            $locationCount = 0;
191
            foreach ($googleMarkers as $g) {
192
                $locationCount++;
193
                $marker .= '<marker name="' . $locationCount . '" lat="' . $g['lat'] . '" lng="' . $g['lon'] . '" />';
194
            }
195
            $marker .= '</markers>';
196
            $retval .= '<tr><td><script>markers=\'' . $marker . '\';</script></td><td></td><td></td></tr>';
197
        }
198
        return $retval;
199
    }
200
201
    /**
202
     * creates HTML code to display all information boxes for an IdP
203
     * 
204
     * @param \core\IdP $myInst the IdP in question
205
     * @return string HTML code
206
     */
207
    public function instLevelInfoBoxes(\core\IdP $myInst) {
208
        $idpoptions = $myInst->getAttributes();
209
        $retval = "<div class='infobox'>
210
        <h2>" . sprintf(_("General %s details"), $this->nomenclature_inst) . "</h2>
211
        <table>
212
            <tr>
213
                <td>
214
                    " . _("Country:") . "
215
                </td>
216
                <td>
217
                </td>
218
                <td>
219
                    <strong>";
220
        $myFed = new \core\Federation($myInst->federation);
221
        $retval .= $myFed->name;
222
        $retval .= "</strong>
223
                </td>
224
            </tr>" . $this->infoblock($idpoptions, "general", "IdP") . "
225
        </table>
226
    </div>";
227
228
        $blocks = [["support", _("Global Helpdesk Details")], ["media", _("Media Properties")]];
229
        foreach ($blocks as $block) {
230
            $retval .= "<div class='infobox'>
231
            <h2>" . $block[1] . "</h2>
232
            <table>" .
233
                    $this->infoblock($idpoptions, $block[0], "IdP") .
234
                    "</table>
235
        </div>";
236
        }
237
        return $retval;
238
    }
239
240
    /**
241
     * pretty-prints a file size number in SI "bi" units
242
     * @param int $number the size of the file
243
     * @return string the pretty-print representation of the file size
244
     */
245
    private function displaySize(int $number) {
246
        if ($number > 1024 * 1024) {
247
            return round($number / 1024 / 1024, 2) . " MiB";
248
        }
249
        if ($number > 1024) {
250
            return round($number / 1024, 2) . " KiB";
251
        }
252
        return $number . " B";
253
    }
254
255
    /**
256
     * 
257
     * @param string $ref the database reference string
258
     * @param boolean $checkpublic should we check if the requested piece of data is public?
259
     * @return string|FALSE the requested data, or FALSE if something went wrong
260
     */
261
    public static function getBlobFromDB($ref, $checkpublic) {
262
        $validator = new \web\lib\common\InputValidation();
263
        $reference = $validator->databaseReference($ref);
264
265
        if ($reference === FALSE) {
1 ignored issue
show
introduced by
The condition $reference === FALSE can never be false.
Loading history...
266
            return FALSE;
267
        }
268
269
        // the data is either public (just give it away) or not; in this case, only
270
        // release if the data belongs to admin himself
271
        if ($checkpublic) {
272
            // we might be called without session context (filepreview) so get the
273
            // context if needed
274
            CAT_session_start();
275
            
276
            $owners = \core\EntityWithDBProperties::isDataRestricted($reference["table"], $reference["rowindex"]);
277
278
            $ownersCondensed = [];
279
280
            if ($owners !== FALSE) { // restricted datam see if we're authenticated and owners of the data
281
                $auth = new \web\lib\admin\Authentication();
282
                if (!$auth->isAuthenticated()) {
283
                    return FALSE; // admin-only, but we are not an admin
284
                }
285
                foreach ($owners as $oneowner) {
286
                    $ownersCondensed[] = $oneowner['ID'];
287
                }
288
                if (array_search($_SESSION['user'], $ownersCondensed) === FALSE) {
289
                    return FALSE; // wrong guy
290
                }
291
                // carry on and get the data
292
            }
293
        }
294
295
        $blob = \core\EntityWithDBProperties::fetchRawDataByIndex($reference["table"], $reference["rowindex"]);
296
        return $blob; // this means we might return FALSE here if something was wrong with the original requested reference
297
    }
298
299
    /**
300
     * 
301
     * @param string $reference a reference pointer to a database entry
302
     * @throws Exception
303
     */
304
    
305
    private function checkROWIDpresence($reference) {
306
        $found = preg_match("/^ROWID-.*/", $reference);
307
        if ($found  != 1) { // get excited on not-found AND on execution error
308
            throw new Exception("Error, ROWID expected.");
309
        }
310
    }
311
    
312
    /**
313
     * creates HTML code to display a nice UI representation of a CA
314
     * 
315
     * @param string $cAReference ROWID pointer to the CA to display
316
     * @return string HTML code
317
     */
318
    public function previewCAinHTML($cAReference) {
319
        $this->checkROWIDpresence($cAReference);
320
        $cAblob = base64_decode(UIElements::getBlobFromDB($cAReference, FALSE));
321
322
        $func = new \core\common\X509;
323
        $details = $func->processCertificate($cAblob);
324
        if ($details === FALSE) {
1 ignored issue
show
introduced by
The condition $details === FALSE can never be false.
Loading history...
325
            return _("There was an error processing the certificate!");
326
        }
327
328
        $details['name'] = preg_replace('/(.)\/(.)/', "$1<br/>$2", $details['name']);
329
        $details['name'] = preg_replace('/\//', "", $details['name']);
330
        $certstatus = ( $details['root'] == 1 ? "R" : "I");
331
        if ($details['ca'] == 0 && $details['root'] != 1) {
332
            return "<div class='ca-summary' style='background-color:red'><div style='position:absolute; right: 0px; width:20px; height:20px; background-color:maroon;  border-radius:10px; text-align: center;'><div style='padding-top:3px; font-weight:bold; color:#ffffff;'>S</div></div>" . _("This is a <strong>SERVER</strong> certificate!") . "<br/>" . $details['name'] . "</div>";
333
        }
334
        return "<div class='ca-summary'                                ><div style='position:absolute; right: 0px; width:20px; height:20px; background-color:#0000ff; border-radius:10px; text-align: center;'><div style='padding-top:3px; font-weight:bold; color:#ffffff;'>$certstatus</div></div>" . $details['name'] . "</div>";
335
    }
336
337
    /**
338
     * creates HTML code to display a nice UI representation of an image
339
     * 
340
     * @param string $imageReference ROWID pointer to the image to display
341
     * @return string HTML code
342
     */
343
    public function previewImageinHTML($imageReference) {
344
        $this->checkROWIDpresence($imageReference);
345
        return "<img style='max-width:150px' src='inc/filepreview.php?id=" . $imageReference . "' alt='" . _("Preview of logo file") . "'/>";
346
    }
347
348
    /**
349
     * creates HTML code to display a nice UI representation of a TermsOfUse file
350
     * 
351
     * @param string $fileReference ROWID pointer to the file to display
352
     * @return string HTML code
353
     */
354
    public function previewInfoFileinHTML($fileReference) {
355
        $this->checkROWIDpresence($fileReference);
356
        $fileBlob = UIElements::getBlobFromDB($fileReference, FALSE);
357
        $decodedFileBlob = base64_decode($fileBlob);
358
        $fileinfo = new \finfo();
359
        return "<div class='ca-summary'>" . _("File exists") . " (" . $fileinfo->buffer($decodedFileBlob, FILEINFO_MIME_TYPE) . ", " . $this->displaySize(strlen($decodedFileBlob)) . ")<br/><a href='inc/filepreview.php?id=$fileReference'>" . _("Preview") . "</a></div>";
360
    }
361
362
    /**
363
     * creates HTML code for a UI element which informs the user about something.
364
     * 
365
     * @param int $level what kind of information is to be displayed?
366
     * @param string $text the text to display
367
     * @param string $caption the caption to display
368
     * @param bool $omittabletags the output usually has tr/td table tags, this option suppresses them
369
     * @return string
370
     */
371
    public function boxFlexible(int $level, string $text = NULL, string $caption = NULL, bool $omittabletags = FALSE) {
372
373
        $uiMessages = [
374
            \core\common\Entity::L_OK => ['icon' => '../resources/images/icons/Quetto/check-icon.png', 'text' => _("OK")],
375
            \core\common\Entity::L_REMARK => ['icon' => '../resources/images/icons/Quetto/info-icon.png', 'text' => _("Remark")],
376
            \core\common\Entity::L_WARN => ['icon' => '../resources/images/icons/Quetto/danger-icon.png', 'text' => _("Warning!")],
377
            \core\common\Entity::L_ERROR => ['icon' => '../resources/images/icons/Quetto/no-icon.png', 'text' => _("Error!")],
378
        ];
379
380
        $retval = "";
381
        if (!$omittabletags) {
382
            $retval .= "<tr><td>";
383
        }
384
        $finalCaption = ($caption !== NULL ? $caption : $uiMessages[$level]['text']);
385
        $retval .= "<img class='icon' src='" . $uiMessages[$level]['icon'] . "' alt='" . $finalCaption . "' title='" . $finalCaption . "'/>";
386
        if (!$omittabletags) {
387
            $retval .= "</td><td>";
388
        }
389
        if ($text !== NULL) {
390
            $retval .= $text;
391
        }
392
        if (!$omittabletags) {
393
            $retval .= "</td></tr>";
394
        }
395
        return $retval;
396
    }
397
398
    /**
399
     * creates HTML code to display an "all is okay" message
400
     * 
401
     * @param string $text the text to display
402
     * @param string $caption the caption to display
403
     * @param bool $omittabletags the output usually has tr/td table tags, this option suppresses them
404
     * @return string HTML: the box
405
     */
406
    public function boxOkay(string $text = NULL, string $caption = NULL, bool $omittabletags = FALSE) {
407
        return $this->boxFlexible(\core\common\Entity::L_OK, $text, $caption, $omittabletags);
408
    }
409
410
    /**
411
     * creates HTML code to display a "smartass comment" message
412
     * 
413
     * @param string $text the text to display
414
     * @param string $caption the caption to display
415
     * @param bool $omittabletags the output usually has tr/td table tags, this option suppresses them
416
     * @return string HTML: the box
417
     */
418
    public function boxRemark(string $text = NULL, string $caption = NULL, bool $omittabletags = FALSE) {
419
        return $this->boxFlexible(\core\common\Entity::L_REMARK, $text, $caption, $omittabletags);
420
    }
421
422
    /**
423
     * creates HTML code to display a "something's a bit wrong" message
424
     * 
425
     * @param string $text the text to display
426
     * @param string $caption the caption to display
427
     * @param bool $omittabletags the output usually has tr/td table tags, this option suppresses them
428
     * @return string HTML: the box
429
     */
430
    public function boxWarning(string $text = NULL, string $caption = NULL, bool $omittabletags = FALSE) {
431
        return $this->boxFlexible(\core\common\Entity::L_WARN, $text, $caption, $omittabletags);
432
    }
433
434
    /**
435
     * creates HTML code to display a "Whoa! Danger, Will Robinson!" message
436
     * 
437
     * @param string $text the text to display
438
     * @param string $caption the caption to display
439
     * @param bool $omittabletags the output usually has tr/td table tags, this option suppresses them
440
     * @return string HTML: the box
441
     */
442
    public function boxError(string $text = NULL, string $caption = NULL, bool $omittabletags = FALSE) {
443
        return $this->boxFlexible(\core\common\Entity::L_ERROR, $text, $caption, $omittabletags);
444
    }
445
446
    /**
447
     * Injects the consortium logo in the middle of a given PNG.
448
     * 
449
     * Usually used on QR code PNGs - the parameters inform about the structure of
450
     * the QR code so that the logo does not prevent parsing of the QR code.
451
     * 
452
     * @param string $inputpngstring the PNG to edit
453
     * @param int $symbolsize size in pixels of one QR "pixel"
454
     * @param int $marginsymbols size in pixels of border around the actual QR
455
     * @return string the image with logo centered in the middle
456
     */
457
    public function pngInjectConsortiumLogo(string $inputpngstring, int $symbolsize, int $marginsymbols = 4) {
458
        $loggerInstance = new \core\common\Logging();
459
        $inputgd = imagecreatefromstring($inputpngstring);
460
461
        $loggerInstance->debug(4, "Consortium logo is at: " . ROOT . "/web/resources/images/consortium_logo_large.png");
462
        $logogd = imagecreatefrompng(ROOT . "/web/resources/images/consortium_logo_large.png");
463
464
        $sizeinput = [imagesx($inputgd), imagesy($inputgd)];
465
        $sizelogo = [imagesx($logogd), imagesy($logogd)];
466
        // Q level QR-codes can sustain 25% "damage"
467
        // make our logo cover approx 15% of area to be sure; mind that there's a $symbolsize * $marginsymbols pixel white border around each edge
468
        $totalpixels = ($sizeinput[0] - $symbolsize * $marginsymbols) * ($sizeinput[1] - $symbolsize * $marginsymbols);
469
        $totallogopixels = ($sizelogo[0]) * ($sizelogo[1]);
470
        $maxoccupy = $totalpixels * 0.04;
471
        // find out how much we have to scale down logo to reach 10% QR estate
472
        $scale = sqrt($maxoccupy / $totallogopixels);
473
        $loggerInstance->debug(4, "Scaling info: $scale, $maxoccupy, $totallogopixels\n");
474
        // determine final pixel size - round to multitude of $symbolsize to match exact symbol boundary
475
        $targetwidth = $symbolsize * (int)round($sizelogo[0] * $scale / $symbolsize);
476
        $targetheight = $symbolsize * (int)round($sizelogo[1] * $scale / $symbolsize);
477
        // paint white below the logo, in case it has transparencies (looks bad)
478
        // have one symbol in each direction extra white space
479
        $whiteimage = imagecreate($targetwidth + 2 * $symbolsize, $targetheight + 2 * $symbolsize);
480
        imagecolorallocate($whiteimage, 255, 255, 255);
481
        // also make sure the initial placement is a multitude of 12; otherwise "two half" symbols might be affected
482
        $targetplacementx = $symbolsize * (int)round(($sizeinput[0] / 2 - ($targetwidth - $symbolsize) / 2) / $symbolsize);
483
        $targetplacementy = $symbolsize * (int)round(($sizeinput[1] / 2 - ($targetheight - $symbolsize) / 2) / $symbolsize);
484
        imagecopyresized($inputgd, $whiteimage, $targetplacementx - $symbolsize, $targetplacementy - $symbolsize, 0, 0, $targetwidth + 2 * $symbolsize, $targetheight + 2 * $symbolsize, $targetwidth + 2 * $symbolsize, $targetheight + 2 * $symbolsize);
485
        imagecopyresized($inputgd, $logogd, $targetplacementx, $targetplacementy, 0, 0, $targetwidth, $targetheight, $sizelogo[0], $sizelogo[1]);
486
        ob_start();
487
        imagepng($inputgd);
488
        return ob_get_clean();
489
    }
490
491
}
492