Subversion Repositories oidplus

Rev

Rev 1286 | Rev 1347 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
596 daniel-mar 1
<?php
2 daniel-mar 2
 
3
/*
4
 * OIDplus 2.0
1086 daniel-mar 5
 * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
2 daniel-mar 6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
 
1050 daniel-mar 20
namespace ViaThinkSoft\OIDplus;
511 daniel-mar 21
 
1086 daniel-mar 22
// phpcs:disable PSR1.Files.SideEffects
23
\defined('INSIDE_OIDPLUS') or die;
24
// phpcs:enable PSR1.Files.SideEffects
25
 
730 daniel-mar 26
class OIDplusGui extends OIDplusBaseClass {
2 daniel-mar 27
 
1116 daniel-mar 28
        /**
29
         * @param string $id
30
         * @return array
31
         */
1186 daniel-mar 32
        public function generateContentPage(string $id): array {
2 daniel-mar 33
                $out = array();
34
 
35
                $handled = false;
36
                $out['title'] = '';
32 daniel-mar 37
                $out['icon'] = '';
2 daniel-mar 38
                $out['text'] = '';
39
 
281 daniel-mar 40
                foreach (OIDplus::getPagePlugins() as $plugin) {
348 daniel-mar 41
                        try {
42
                                $plugin->gui($id, $out, $handled);
1050 daniel-mar 43
                        } catch (\Exception $e) {
360 daniel-mar 44
                                $out['title'] = _L('Error');
800 daniel-mar 45
                                $out['icon'] = 'img/error.png';
1201 daniel-mar 46
                                $htmlmsg = $e instanceof OIDplusException ? $e->getHtmlMessage() : htmlentities($e->getMessage());
1205 daniel-mar 47
                                if (strtolower(substr($htmlmsg, 0, 3)) === '<p ') {
48
                                        $out['text'] = $htmlmsg;
49
                                } else {
50
                                        $out['text'] = '<p>'.$htmlmsg.'</p>';
51
                                }
1266 daniel-mar 52
                                if (isset($_SERVER['SCRIPT_FILENAME']) && (strtolower(basename($_SERVER['SCRIPT_FILENAME'])) !== 'ajax.php')) { // don't send HTTP error codes in ajax.php, because we want a page and not a JavaScript alert box, when someone enters an invalid OID in the GoTo-Box
53
                                        if (PHP_SAPI != 'cli') @http_response_code($e instanceof OIDplusException ? $e->getHttpStatus() : 500);
54
                                }
1182 daniel-mar 55
                                if (OIDplus::baseConfig()->getValue('DEBUG')) {
1188 daniel-mar 56
                                        $out['text'] .= self::getExceptionTechInfo($e);
1182 daniel-mar 57
                                }
348 daniel-mar 58
                        }
281 daniel-mar 59
                        if ($handled) break;
61 daniel-mar 60
                }
2 daniel-mar 61
 
62
                if (!$handled) {
936 daniel-mar 63
                        if (isset($_SERVER['SCRIPT_FILENAME']) && (strtolower(basename($_SERVER['SCRIPT_FILENAME'])) !== 'ajax.php')) { // don't send HTTP error codes in ajax.php, because we want a page and not a JavaScript alert box, when someone enters an invalid OID in the GoTo-Box
1068 daniel-mar 64
                                if (PHP_SAPI != 'cli') @http_response_code(404);
936 daniel-mar 65
                        }
360 daniel-mar 66
                        $out['title'] = _L('Error');
800 daniel-mar 67
                        $out['icon'] = 'img/error.png';
360 daniel-mar 68
                        $out['text'] = _L('The resource cannot be found.');
2 daniel-mar 69
                }
70
 
71
                return $out;
72
        }
250 daniel-mar 73
 
1116 daniel-mar 74
        /**
75
         * @param string $goto
76
         * @param bool $new_window
77
         * @return string
78
         */
1186 daniel-mar 79
        public function link(string $goto, bool $new_window=false): string {
327 daniel-mar 80
                if ($new_window) {
81
                        return 'href="?goto='.urlencode($goto).'" target="_blank"';
250 daniel-mar 82
                } else {
327 daniel-mar 83
                        if (strpos($goto, '#') !== false) {
84
                                list($goto, $anchor) = explode('#', $goto, 2);
85
                                return 'href="?goto='.urlencode($goto).'#'.htmlentities($anchor).'" onclick="openOidInPanel('.js_escape($goto).', true, '.js_escape($anchor).'); return false;"';
86
                        } else {
87
                                return 'href="?goto='.urlencode($goto).'" onclick="openOidInPanel('.js_escape($goto).', true); return false;"';
88
                        }
250 daniel-mar 89
                }
90
        }
362 daniel-mar 91
 
1116 daniel-mar 92
        /**
93
         * @param string $goto
94
         * @param bool $useJs
95
         * @return string
96
         * @throws OIDplusConfigInitializationException
97
         * @throws OIDplusException
98
         */
1186 daniel-mar 99
        public function getLanguageBox(string $goto, bool $useJs): string {
1116 daniel-mar 100
                $out = '<div id="languageBox">';
362 daniel-mar 101
                $langbox_entries = array();
102
                $non_default_languages = 0;
103
                foreach (OIDplus::getAllPluginManifests('language') as $pluginManifest) {
389 daniel-mar 104
                        $flag = $pluginManifest->getLanguageFlag();
105
                        $code = $pluginManifest->getLanguageCode();
1041 daniel-mar 106
                        if ($code != OIDplus::getDefaultLang()) $non_default_languages++;
362 daniel-mar 107
                        if ($code == OIDplus::getCurrentLang()) {
108
                                $class = 'lng_flag';
109
                        } else {
110
                                $class = 'lng_flag picture_ghost';
111
                        }
1116 daniel-mar 112
                        $add = ($goto != '') ? '&amp;goto='.urlencode($goto) : '';
632 daniel-mar 113
 
635 daniel-mar 114
                        $dirs = glob(OIDplus::localpath().'plugins/'.'*'.'/language/'.$code.'/');
632 daniel-mar 115
 
116
                        if (count($dirs) > 0) {
117
                                $dir = substr($dirs[0], strlen(OIDplus::localpath()));
1060 daniel-mar 118
                                $langbox_entries[$code] = '<span class="lang_flag_bg"><a '.($useJs ? 'onclick="return !setLanguage(\''.$code.'\')" ' : '').'href="?lang='.$code.$add.'"><img src="'.OIDplus::webpath(null,OIDplus::PATH_RELATIVE).$dir.$flag.'" alt="'.$pluginManifest->getName().'" title="'.$pluginManifest->getName().'" class="'.$class.'" id="lng_flag_'.$code.'" height="20"></a></span> ';
632 daniel-mar 119
                        }
362 daniel-mar 120
                }
121
                if ($non_default_languages > 0) {
426 daniel-mar 122
                        foreach ($langbox_entries as $ent) {
1056 daniel-mar 123
                                $out .= "$ent\n\t\t";
426 daniel-mar 124
                        }
362 daniel-mar 125
                }
1056 daniel-mar 126
                $out .= '</div>';
127
                return $out;
362 daniel-mar 128
        }
366 daniel-mar 129
 
1116 daniel-mar 130
        /**
131
         * @param \Throwable $exception
132
         * @return void
133
         * @throws OIDplusException
134
         */
1188 daniel-mar 135
        public static function html_exception_handler(\Throwable $exception) {
1203 daniel-mar 136
                // Note: This method must be static, because of its registration as Exception handler
1201 daniel-mar 137
 
1203 daniel-mar 138
                if ($exception instanceof OIDplusException) {
139
                        $htmlTitle = $exception->gethtmlTitle();
140
                        $htmlMessage = $exception->getHtmlMessage();
1266 daniel-mar 141
                        if (isset($_SERVER['SCRIPT_FILENAME']) && (strtolower(basename($_SERVER['SCRIPT_FILENAME'])) !== 'ajax.php')) { // don't send HTTP error codes in ajax.php, because we want a page and not a JavaScript alert box, when someone enters an invalid OID in the GoTo-Box
142
                                if (PHP_SAPI != 'cli') @http_response_code($exception->getHttpStatus());
143
                        }
366 daniel-mar 144
                } else {
1203 daniel-mar 145
                        $htmlTitle = '';
1227 daniel-mar 146
                        //$htmlMessage = htmlentities($exception->getMessage());
147
                        $htmlMessage = nl2br(htmlentities(html_to_text($exception->getMessage())));
1266 daniel-mar 148
                        if (isset($_SERVER['SCRIPT_FILENAME']) && (strtolower(basename($_SERVER['SCRIPT_FILENAME'])) !== 'ajax.php')) { // don't send HTTP error codes in ajax.php, because we want a page and not a JavaScript alert box, when someone enters an invalid OID in the GoTo-Box
149
                                if (PHP_SAPI != 'cli') @http_response_code(500);
150
                        }
366 daniel-mar 151
                }
1203 daniel-mar 152
                if (!$htmlTitle) {
153
                        $htmlTitle = _L('OIDplus Error');
154
                }
155
 
156
                echo '<!DOCTYPE HTML>';
157
                echo '<html><head><title>'.$htmlTitle.'</title></head><body>';
158
                echo '<h1>'.$htmlTitle.'</h1>';
159
                echo $htmlMessage;
160
                echo self::getExceptionTechInfo($exception);
161
                echo '</body></html>';
366 daniel-mar 162
        }
420 daniel-mar 163
 
1116 daniel-mar 164
        /**
165
         * @param \Throwable $exception
166
         * @return string
167
         */
1188 daniel-mar 168
        private static function getExceptionTechInfo(\Throwable $exception): string {
1116 daniel-mar 169
                $out  = '<p><b>'.htmlentities(_L('Technical information about the problem')).':</b></p>';
780 daniel-mar 170
                $out .= '<pre>';
171
                $out .= get_class($exception)."\n";
1227 daniel-mar 172
 
173
                $sourceFile = $exception->getFile();
1201 daniel-mar 174
                $stacktrace = $exception->getTraceAsString();
1227 daniel-mar 175
 
176
                // Censor paths
1201 daniel-mar 177
                try {
178
                        $syspath = OIDplus::localpath(NULL);
179
                        $stacktrace = str_replace($syspath, '...'.DIRECTORY_SEPARATOR, $stacktrace); // for security
1227 daniel-mar 180
                        $sourceFile = str_replace($syspath, '...'.DIRECTORY_SEPARATOR, $sourceFile); // for security
1201 daniel-mar 181
                } catch (\Throwable $e) {
182
                        // Catch Exception and Error, because this step (censoring) is purely optional and shoult not prevent the stacktrace of being shown
183
                }
1227 daniel-mar 184
 
185
                $out .= _L('at file %1 (line %2)',$sourceFile,"".$exception->getLine())."\n\n";
186
                $out .= _L('Stacktrace').":\n";
1201 daniel-mar 187
                $out .= htmlentities($stacktrace);
1227 daniel-mar 188
 
780 daniel-mar 189
                $out .= '</pre>';
190
                return $out;
191
        }
192
 
1116 daniel-mar 193
        /**
194
         * @return string
195
         */
1130 daniel-mar 196
        public function tabBarStart(): string {
420 daniel-mar 197
                return '<ul class="nav nav-tabs" id="myTab" role="tablist">';
198
        }
199
 
1116 daniel-mar 200
        /**
201
         * @return string
202
         */
1130 daniel-mar 203
        public function tabBarEnd(): string {
420 daniel-mar 204
                return '</ul>';
205
        }
206
 
1116 daniel-mar 207
        /**
208
         * @param string $id
209
         * @param string $title
210
         * @param bool $active
211
         * @return string
212
         */
1130 daniel-mar 213
        public function tabBarElement(string $id, string $title, bool $active): string {
641 daniel-mar 214
                // data-bs-toggle is for Bootstrap 5
215
                // data-toggle is for Bootstrap 4 (InternetExplorer compatibility)
216
                return '<li class="nav-item"><a class="nav-link'.($active ? ' active' : '').'" id="'.$id.'-tab" data-bs-toggle="tab" data-toggle="tab" href="#'.$id.'" role="tab" aria-controls="'.$id.'" aria-selected="'.($active ? 'true' : 'false').'">'.$title.'</a></li>';
420 daniel-mar 217
        }
218
 
1116 daniel-mar 219
        /**
220
         * @return string
221
         */
1130 daniel-mar 222
        public function tabContentStart(): string {
420 daniel-mar 223
                return '<div class="tab-content" id="myTabContent">';
224
        }
225
 
1116 daniel-mar 226
        /**
227
         * @return string
228
         */
1130 daniel-mar 229
        public function tabContentEnd(): string {
420 daniel-mar 230
                return '</div>';
231
        }
232
 
1116 daniel-mar 233
        /**
234
         * @param string $id
235
         * @param string $content
236
         * @param bool $active
237
         * @return string
238
         */
1130 daniel-mar 239
        public function tabContentPage(string $id, string $content, bool $active): string {
420 daniel-mar 240
                return '<div class="tab-pane fade'.($active ? ' show active' : '').'" id="'.$id.'" role="tabpanel" aria-labelledby="'.$id.'-tab">'.$content.'</div>';
241
        }
242
 
1116 daniel-mar 243
        /**
244
         * @param string $systemtitle
245
         * @param string $pagetitle
246
         * @return string
247
         */
1130 daniel-mar 248
        public function combine_systemtitle_and_pagetitle(string $systemtitle, string $pagetitle): string {
1066 daniel-mar 249
                // Please also change the function in oidplus_base.js
250
                if ($systemtitle == $pagetitle) {
251
                        return $systemtitle;
252
                } else {
253
                        return $pagetitle . ' - ' . $systemtitle;
254
                }
255
        }
256
 
1116 daniel-mar 257
        /**
258
         * @param string $title
259
         * @return string[]
260
         * @throws OIDplusException
261
         */
262
        private function getCommonHeadElems(string $title): array {
1065 daniel-mar 263
                // Get theme color (color of title bar)
264
                $design_plugin = OIDplus::getActiveDesignPlugin();
265
                $theme_color = is_null($design_plugin) ? '' : $design_plugin->getThemeColor();
1055 daniel-mar 266
 
1065 daniel-mar 267
                $head_elems = array();
268
                $head_elems[] = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">';
269
                if (OIDplus::baseConfig()->getValue('DATABASE_PLUGIN','') !== '') {
270
                        $head_elems[] = '<meta name="OIDplus-SystemTitle" content="'.htmlentities(OIDplus::config()->getValue('system_title')).'">'; // Do not remove. This meta tag is acessed by oidplus_base.js
271
                }
272
                if ($theme_color != '') {
273
                        $head_elems[] = '<meta name="theme-color" content="'.htmlentities($theme_color).'">';
274
                }
275
                $head_elems[] = '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
1066 daniel-mar 276
                $head_elems[] = '<title>'.htmlentities($title).'</title>';
1286 daniel-mar 277
                $head_elems[] = '<script src="'.htmlentities(OIDplus::webpath(null, OIDplus::PATH_RELATIVE)).'polyfill.min.js.php"></script>';
278
                $head_elems[] = '<script src="'.htmlentities(OIDplus::webpath(null, OIDplus::PATH_RELATIVE)).'oidplus.min.js.php?noBaseConfig=1" type="text/javascript"></script>';
279
                $head_elems[] = '<link rel="stylesheet" href="'.htmlentities(OIDplus::webpath(null, OIDplus::PATH_RELATIVE)).'oidplus.min.css.php?noBaseConfig=1">';
280
                $head_elems[] = '<link rel="shortcut icon" type="image/x-icon" href="'.htmlentities(OIDplus::webpath(null, OIDplus::PATH_RELATIVE)).'favicon.ico.php">';
1065 daniel-mar 281
                if (OIDplus::baseConfig()->exists('CANONICAL_SYSTEM_URL')) {
1286 daniel-mar 282
                        $head_elems[] = '<link rel="canonical" href="'.htmlentities(OIDplus::canonicalURL().OIDplus::webpath(null, OIDplus::PATH_RELATIVE)).'">';
1065 daniel-mar 283
                }
1066 daniel-mar 284
 
285
                return $head_elems;
286
        }
287
 
1116 daniel-mar 288
        /**
289
         * @param string $page_title_1
290
         * @param string $page_title_2
291
         * @param string $static_icon
292
         * @param string $static_content
293
         * @param array $extra_head_tags
294
         * @param string $static_node_id
295
         * @return string
296
         * @throws OIDplusConfigInitializationException
297
         * @throws OIDplusException
298
         */
1130 daniel-mar 299
        public function showMainPage(string $page_title_1, string $page_title_2, string $static_icon, string $static_content, array $extra_head_tags=array(), string $static_node_id=''): string {
1066 daniel-mar 300
                $head_elems = $this->getCommonHeadElems($page_title_1);
1065 daniel-mar 301
                $head_elems = array_merge($head_elems, $extra_head_tags);
1055 daniel-mar 302
 
1116 daniel-mar 303
                $plugins = OIDplus::getAllPlugins();
1066 daniel-mar 304
                foreach ($plugins as $plugin) {
305
                        $plugin->htmlHeaderUpdate($head_elems);
306
                }
307
 
1065 daniel-mar 308
                # ---
1055 daniel-mar 309
 
1065 daniel-mar 310
                $out  = "<!DOCTYPE html>\n";
311
 
312
                $out .= "<html lang=\"".substr(OIDplus::getCurrentLang(),0,2)."\">\n";
313
                $out .= "<head>\n";
314
                $out .= "\t".implode("\n\t",$head_elems)."\n";
315
                $out .= "</head>\n";
316
 
317
                $out .= "<body>\n";
318
 
1056 daniel-mar 319
                $out .= '<div id="loading" style="display:none">Loading&#8230;</div>';
1055 daniel-mar 320
 
1056 daniel-mar 321
                $out .= '<div id="frames">';
322
                $out .= '<div id="content_window" class="borderbox">';
1055 daniel-mar 323
 
1056 daniel-mar 324
                $out .= '<h1 id="real_title">';
325
                if ($static_icon != '') $out .= '<img src="'.htmlentities($static_icon).'" width="48" height="48" alt=""> ';
326
                $out .= htmlentities($page_title_2).'</h1>';
327
                $out .= '<div id="real_content">'.$static_content.'</div>';
1066 daniel-mar 328
                if ((!isset($_SERVER['REQUEST_METHOD'])) || ($_SERVER['REQUEST_METHOD'] == 'GET')) {
1289 daniel-mar 329
                        $out .= '<br><p><img src="img/share.png" width="15" height="15" alt="'._L('Share').'"> <a href="'.htmlentities(OIDplus::canonicalUrl($static_node_id)).'" id="static_link" class="gray_footer_font">'._L('Static link to this page').'</a>';
1066 daniel-mar 330
                        $out .= '</p>';
331
                }
1056 daniel-mar 332
                $out .= '<br>';
1055 daniel-mar 333
 
1056 daniel-mar 334
                $out .= '</div>';
1055 daniel-mar 335
 
1056 daniel-mar 336
                $out .= '<div id="system_title_bar">';
1055 daniel-mar 337
 
1066 daniel-mar 338
                $out .= '<div id="system_title_menu" onclick="mobileNavButtonClick(this)" onmouseenter="mobileNavButtonHover(this)" onmouseleave="mobileNavButtonHover(this)">';
339
                $out .= '       <div id="bar1"></div>';
340
                $out .= '       <div id="bar2"></div>';
341
                $out .= '       <div id="bar3"></div>';
342
                $out .= '</div>';
343
 
1056 daniel-mar 344
                $out .= '<div id="system_title_text">';
1066 daniel-mar 345
                $out .= '       <a '.OIDplus::gui()->link('oidplus:system').' id="system_title_a">';
346
                $out .= '               <span id="system_title_logo"></span>';
347
                $out .= '               <span id="system_title_1">'.htmlentities(OIDplus::getEditionInfo()['vendor'].' OIDplus 2.0').'</span><br>';
348
                $out .= '               <span id="system_title_2">'.htmlentities(OIDplus::config()->getValue('system_title')).'</span>';
349
                $out .= '       </a>';
350
                $out .= '</div>';
351
 
352
                $out .= '</div>';
353
 
354
                $out .= OIDplus::gui()->getLanguageBox($static_node_id, true);
355
 
356
                $out .= '<div id="gotobox">';
357
                $out .= '<input type="text" name="goto" id="gotoedit" value="'.htmlentities($static_node_id).'">';
358
                $out .= '<input type="button" value="'._L('Go').'" onclick="gotoButtonClicked()" id="gotobutton">';
359
                $out .= '</div>';
360
 
361
                $out .= '<div id="oidtree" class="borderbox">';
362
                //$out .= '<noscript>';
363
                //$out .= '<p><b>'._L('Please enable JavaScript to use all features').'</b></p>';
364
                //$out .= '</noscript>';
365
                $out .= OIDplus::menuUtils()->nonjs_menu();
366
                $out .= '</div>';
367
 
368
                $out .= '</div>';
369
 
370
                $out .= "\n</body>\n";
371
                $out .= "</html>\n";
372
 
373
                # ---
374
 
1116 daniel-mar 375
                $plugins = OIDplus::getAllPlugins();
1066 daniel-mar 376
                foreach ($plugins as $plugin) {
377
                        $plugin->htmlPostprocess($out);
378
                }
379
 
380
                return $out;
381
        }
382
 
1116 daniel-mar 383
        /**
384
         * @param string $page_title_1
385
         * @param string $page_title_2
386
         * @param string $static_icon
387
         * @param string $static_content
388
         * @param string[] $extra_head_tags
389
         * @return string
390
         * @throws OIDplusConfigInitializationException
391
         * @throws OIDplusException
392
         */
1130 daniel-mar 393
        public function showSimplePage(string $page_title_1, string $page_title_2, string $static_icon, string $static_content, array $extra_head_tags=array()): string {
1066 daniel-mar 394
                $head_elems = $this->getCommonHeadElems($page_title_1);
395
                $head_elems = array_merge($head_elems, $extra_head_tags);
396
 
397
                # ---
398
 
399
                $out  = "<!DOCTYPE html>\n";
400
 
401
                $out .= "<html lang=\"".substr(OIDplus::getCurrentLang(),0,2)."\">\n";
402
                $out .= "<head>\n";
403
                $out .= "\t".implode("\n\t",$head_elems)."\n";
404
                $out .= "</head>\n";
405
 
406
                $out .= "<body>\n";
407
 
408
                $out .= '<div id="loading" style="display:none">Loading&#8230;</div>';
409
 
410
                $out .= '<div id="frames">';
411
                $out .= '<div id="content_window" class="borderbox">';
412
 
413
                $out .= '<h1 id="real_title">';
414
                if ($static_icon != '') $out .= '<img src="'.htmlentities($static_icon).'" width="48" height="48" alt=""> ';
415
                $out .= htmlentities($page_title_2).'</h1>';
416
                $out .= '<div id="real_content">'.$static_content.'</div>';
417
                $out .= '<br>';
418
 
419
                $out .= '</div>';
420
 
421
                $out .= '<div id="system_title_bar">';
422
 
423
                $out .= '<div id="system_title_text">';
1056 daniel-mar 424
                $out .= '       <span id="system_title_logo"></span>';
425
                $out .= '       <span id="system_title_1">'.htmlentities(OIDplus::getEditionInfo()['vendor'].' OIDplus 2.0').'</span><br>';
426
                $out .= '       <span id="system_title_2">'.htmlentities($page_title_1).'</span>';
427
                $out .= '</div>';
1055 daniel-mar 428
 
1056 daniel-mar 429
                $out .= '</div>';
1055 daniel-mar 430
 
1116 daniel-mar 431
                $out .= OIDplus::gui()->getLanguageBox('', true);
1055 daniel-mar 432
 
1056 daniel-mar 433
                $out .= '</div>';
434
 
1065 daniel-mar 435
                $out .= "\n</body>\n";
436
                $out .= "</html>\n";
1056 daniel-mar 437
 
1065 daniel-mar 438
                # ---
439
 
1056 daniel-mar 440
                return $out;
1055 daniel-mar 441
        }
442
 
366 daniel-mar 443
}