1<?php 2# MantisBT - A PHP based bugtracking system 3 4# MantisBT is free software: you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation, either version 2 of the License, or 7# (at your option) any later version. 8# 9# MantisBT is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License 15# along with MantisBT. If not, see <http://www.gnu.org/licenses/>. 16 17/** 18 * HTML API 19 * 20 * These functions control the HTML output of each page. 21 * 22 * 23 * @package CoreAPI 24 * @subpackage HTMLAPI 25 * @copyright Copyright 2000 - 2002 Kenzaburo Ito - kenito@300baud.org 26 * @copyright Copyright 2002 MantisBT Team - mantisbt-dev@lists.sourceforge.net 27 * @link http://www.mantisbt.org 28 * 29 * @uses access_api.php 30 * @uses authentication_api.php 31 * @uses bug_api.php 32 * @uses config_api.php 33 * @uses constant_inc.php 34 * @uses current_user_api.php 35 * @uses database_api.php 36 * @uses error_api.php 37 * @uses event_api.php 38 * @uses file_api.php 39 * @uses filter_api.php 40 * @uses filter_constants_inc.php 41 * @uses form_api.php 42 * @uses helper_api.php 43 * @uses lang_api.php 44 * @uses news_api.php 45 * @uses php_api.php 46 * @uses print_api.php 47 * @uses project_api.php 48 * @uses rss_api.php 49 * @uses string_api.php 50 * @uses user_api.php 51 * @uses utility_api.php 52 * @uses layout_api.php 53 * @uses api_token_api.php 54 */ 55 56require_api( 'access_api.php' ); 57require_api( 'authentication_api.php' ); 58require_api( 'bug_api.php' ); 59require_api( 'config_api.php' ); 60require_api( 'constant_inc.php' ); 61require_api( 'current_user_api.php' ); 62require_api( 'database_api.php' ); 63require_api( 'error_api.php' ); 64require_api( 'event_api.php' ); 65require_api( 'file_api.php' ); 66require_api( 'filter_api.php' ); 67require_api( 'filter_constants_inc.php' ); 68require_api( 'form_api.php' ); 69require_api( 'helper_api.php' ); 70require_api( 'lang_api.php' ); 71require_api( 'news_api.php' ); 72require_api( 'php_api.php' ); 73require_api( 'print_api.php' ); 74require_api( 'project_api.php' ); 75require_api( 'rss_api.php' ); 76require_api( 'string_api.php' ); 77require_api( 'user_api.php' ); 78require_api( 'utility_api.php' ); 79require_api( 'layout_api.php' ); 80require_api( 'api_token_api.php' ); 81 82$g_rss_feed_url = null; 83 84$g_robots_meta = ''; 85 86# flag for error handler to skip header menus 87$g_error_send_page_header = true; 88 89$g_stylesheets_included = array(); 90$g_scripts_included = array(); 91 92/** 93 * Sets the url for the rss link associated with the current page. 94 * null: means no feed (default). 95 * @param string $p_rss_feed_url RSS feed URL. 96 * @return void 97 */ 98function html_set_rss_link( $p_rss_feed_url ) { 99 if( OFF != config_get( 'rss_enabled' ) ) { 100 global $g_rss_feed_url; 101 $g_rss_feed_url = $p_rss_feed_url; 102 } 103} 104 105/** 106 * This method marks the page as not for indexing by search engines 107 * @return void 108 */ 109function html_robots_noindex() { 110 global $g_robots_meta; 111 $g_robots_meta = 'noindex,follow'; 112} 113 114/** 115 * Prints the link that allows auto-detection of the associated feed. 116 * @return void 117 */ 118function html_rss_link() { 119 global $g_rss_feed_url; 120 121 if( $g_rss_feed_url !== null ) { 122 echo '<link rel="alternate" type="application/rss+xml" title="RSS" href="' . string_attribute( $g_rss_feed_url ) . '" />' . "\n"; 123 } 124} 125 126/** 127 * Prints a <script> tag to include a JavaScript file. 128 * @param string $p_filename Name of JavaScript file (with extension) to include. 129 * @return void 130 */ 131function html_javascript_link( $p_filename ) { 132 echo "\t", '<script type="text/javascript" src="', helper_mantis_url( 'js/' . $p_filename ), '"></script>', "\n"; 133} 134 135/** 136 * Prints a <script> tag to include a JavaScript file. 137 * @param string $p_url fully qualified domain name for the cdn js file 138 * @param string $p_hash resource hash to perform subresource integrity check 139 * @return void 140 */ 141function html_javascript_cdn_link( $p_url, $p_hash = '' ) { 142 $t_integrity = ''; 143 if( $p_hash !== '' ) { 144 $t_integrity = 'integrity="' . $p_hash . '" '; 145 } 146 echo "\t", '<script type="text/javascript" src="', $p_url, '" ', $t_integrity, 'crossorigin="anonymous"></script>', "\n"; 147} 148 149/** 150 * Print the document type and the opening <html> tag 151 * @return void 152 */ 153function html_begin() { 154 echo '<!DOCTYPE html>', "\n"; 155 echo '<html>', "\n"; 156} 157 158/** 159 * Begin the <head> section 160 * @return void 161 */ 162function html_head_begin() { 163 echo '<head>', "\n"; 164} 165 166/** 167 * Print the content-type 168 * @return void 169 */ 170function html_content_type() { 171 echo "\t", '<meta http-equiv="Content-type" content="text/html; charset=utf-8" />', "\n"; 172} 173 174/** 175 * Print the window title 176 * @param string $p_page_title Window title. 177 * @return void 178 */ 179function html_title( $p_page_title = null ) { 180 $t_page_title = string_html_specialchars( $p_page_title ); 181 $t_title = string_html_specialchars( config_get( 'window_title' ) ); 182 echo "\t", '<title>'; 183 if( empty( $t_page_title ) ) { 184 echo $t_title; 185 } else { 186 if( empty( $t_title ) ) { 187 echo $t_page_title; 188 } else { 189 echo $t_page_title . ' - ' . $t_title; 190 } 191 } 192 echo '</title>', "\n"; 193} 194 195/** 196 * Require a CSS file to be in html page headers 197 * @param string $p_stylesheet_path Path to CSS style sheet. 198 * @return void 199 */ 200function require_css( $p_stylesheet_path ) { 201 global $g_stylesheets_included; 202 $g_stylesheets_included[$p_stylesheet_path] = $p_stylesheet_path; 203} 204 205/** 206 * Print the link to include the CSS file 207 * @return void 208 */ 209function html_css() { 210 global $g_stylesheets_included; 211 html_css_link( config_get_global( 'css_include_file' ) ); 212 # Add right-to-left css if needed 213 if( lang_get( 'directionality' ) == 'rtl' ) { 214 html_css_link( config_get_global( 'css_rtl_include_file' ) ); 215 } 216 foreach( $g_stylesheets_included as $t_stylesheet_path ) { 217 # status_config.php is a special css file, dynamically generated. 218 # Add a hash to the query string to differentiate content based on its 219 # relevant properties. This allows a browser to cache them separately and force 220 # a reload when the content may differ. 221 if( $t_stylesheet_path == 'status_config.php' ) { 222 $t_stylesheet_path = helper_url_combine( 223 helper_mantis_url( 'css/status_config.php' ), 224 'cache_key=' . helper_generate_cache_key( array( 'user' ) ) 225 ); 226 } 227 html_css_link( $t_stylesheet_path ); 228 } 229 230 # dropzone css 231 if ( config_get_global( 'cdn_enabled' ) == ON ) { 232 html_css_cdn_link( 'https://cdnjs.cloudflare.com/ajax/libs/dropzone/' . DROPZONE_VERSION . '/min/dropzone.min.css' ); 233 } else { 234 html_css_link( 'dropzone-' . DROPZONE_VERSION . '.min.css' ); 235 } 236} 237 238/** 239 * Prints a CSS link 240 * @param string $p_filename Filename. 241 * @return void 242 */ 243function html_css_link( $p_filename ) { 244 # If no path is specified, look for CSS files in default directory 245 if( $p_filename == basename( $p_filename ) ) { 246 $p_filename = 'css/' . $p_filename; 247 } 248 echo "\t", '<link rel="stylesheet" type="text/css" href="', string_sanitize_url( helper_mantis_url( $p_filename ), true ), '" />', "\n"; 249} 250 251/** 252 * Prints a CSS link for CDN 253 * @param string $p_url fully qualified domain name to the js file name 254 * @param string $p_hash resource hash to perform subresource integrity check 255 * @return void 256 */ 257function html_css_cdn_link( $p_url, $p_hash = '' ) { 258 $t_integrity = ''; 259 if( $p_hash !== '' ) { 260 $t_integrity = 'integrity="' . $p_hash . '" '; 261 } 262 echo "\t", '<link rel="stylesheet" type="text/css" href="', $p_url, '" ', $t_integrity, ' crossorigin="anonymous" />', "\n"; 263} 264 265/** 266 * Print an HTML meta tag to redirect to another page 267 * This function is optional and may be called by pages that need a redirect. 268 * $p_time is the number of seconds to wait before redirecting. 269 * If we have handled any errors on this page return false and don't redirect. 270 * 271 * @param string $p_url The page to redirect: has to be a relative path. 272 * @param integer $p_time Seconds to wait for before redirecting. 273 * @param boolean $p_sanitize Apply string_sanitize_url to passed URL. 274 * @return boolean 275 */ 276function html_meta_redirect( $p_url, $p_time = null, $p_sanitize = true ) { 277 if( ON == config_get_global( 'stop_on_errors' ) && error_handled() ) { 278 return false; 279 } 280 281 if( null === $p_time ) { 282 $p_time = current_user_get_pref( 'redirect_delay' ); 283 } 284 285 $t_url = config_get_global( 'path' ); 286 if( $p_sanitize ) { 287 $t_url .= string_sanitize_url( $p_url ); 288 } else { 289 $t_url .= $p_url; 290 } 291 292 $t_url = htmlspecialchars( $t_url ); 293 294 echo "\t" . '<meta http-equiv="Refresh" content="' . $p_time . '; URL=' . $t_url . '" />' . "\n"; 295 296 return true; 297} 298 299/** 300 * Require a javascript file to be in html page headers 301 * @param string $p_script_path Path to javascript file. 302 * @return void 303 */ 304function require_js( $p_script_path ) { 305 global $g_scripts_included; 306 $g_scripts_included[$p_script_path] = $p_script_path; 307} 308 309/** 310 * Javascript... 311 * @return void 312 */ 313function html_head_javascript() { 314 global $g_scripts_included; 315 # Add a hash to the query string to differentiate content based on its 316 # relevant properties. This allows a browser to cache them separately and force 317 # a reload when the content may differ. 318 $t_javascript_translations = helper_url_combine( 319 helper_mantis_url( 'javascript_translations.php' ), 320 'cache_key=' . helper_generate_cache_key( array( 'lang' ) ) 321 ); 322 $t_javascript_config = helper_url_combine( 323 helper_mantis_url( 'javascript_config.php' ), 324 'cache_key=' . helper_generate_cache_key( array( 'user' ) ) 325 ); 326 echo "\t" . '<script type="text/javascript" src="' . $t_javascript_config . '"></script>' . "\n"; 327 echo "\t" . '<script type="text/javascript" src="' . $t_javascript_translations . '"></script>' . "\n"; 328 329 if ( config_get_global( 'cdn_enabled' ) == ON ) { 330 # JQuery 331 html_javascript_cdn_link( 'https://ajax.googleapis.com/ajax/libs/jquery/' . JQUERY_VERSION . '/jquery.min.js', JQUERY_HASH ); 332 333 # Dropzone 334 html_javascript_cdn_link( 'https://cdnjs.cloudflare.com/ajax/libs/dropzone/' . DROPZONE_VERSION . '/min/dropzone.min.js', DROPZONE_HASH ); 335 } else { 336 # JQuery 337 html_javascript_link( 'jquery-' . JQUERY_VERSION . '.min.js' ); 338 339 # Dropzone 340 html_javascript_link( 'dropzone-' . DROPZONE_VERSION . '.min.js' ); 341 } 342 343 html_javascript_link( 'common.js' ); 344 foreach ( $g_scripts_included as $t_script_path ) { 345 html_javascript_link( $t_script_path ); 346 } 347} 348 349/** 350 * End the <head> section 351 * @return void 352 */ 353function html_head_end() { 354 echo '</head>', "\n"; 355} 356 357/** 358 * Prints the logo with an URL link. 359 * @param string $p_logo Path to the logo image. If not specified, will get it 360 * from $g_logo_image 361 * @return void 362 */ 363function html_print_logo( $p_logo = null ) { 364 if( !$p_logo ) { 365 $p_logo = config_get_global( 'logo_image' ); 366 } 367 368 if( !is_blank( $p_logo ) ) { 369 $t_logo_url = config_get_global( 'logo_url' ); 370 $t_show_url = !is_blank( $t_logo_url ); 371 372 if( $t_show_url ) { 373 echo '<a id="logo-link" href="', config_get_global( 'logo_url' ), '">'; 374 } 375 $t_alternate_text = string_html_specialchars( config_get( 'window_title' ) ); 376 echo '<img id="logo-image" alt="', $t_alternate_text, '" style="max-height: 80px;" src="' . helper_mantis_url( $p_logo ) . '" />'; 377 if( $t_show_url ) { 378 echo '</a>'; 379 } 380 } 381} 382 383 384 385/** 386 * Print a user-defined banner at the top of the page if there is one. 387 * @return void 388 */ 389function html_top_banner() { 390 $t_page = config_get_global( 'top_include_page' ); 391 $t_logo_image = config_get_global( 'logo_image' ); 392 393 if( !is_blank( $t_page ) && file_exists( $t_page ) && !is_dir( $t_page ) ) { 394 include( $t_page ); 395 } else if( !is_blank( $t_logo_image ) ) { 396 echo '<div id="banner">'; 397 html_print_logo( $t_logo_image ); 398 echo '</div>'; 399 } 400 401 event_signal( 'EVENT_LAYOUT_PAGE_HEADER' ); 402} 403 404/** 405 * Outputs a message to confirm an operation's result. 406 * @param array $p_buttons Array of (URL, label) pairs used to generate 407 * the buttons; if label is null or unspecified, 408 * the default 'proceed' text will be displayed; 409 * If the array is empty or not provided, no 410 * buttons will be printed. 411 * @param string $p_message Message to display to the user. If none is 412 * provided, a default message will be printed 413 * @param integer $p_type One of the constants CONFIRMATION_TYPE_SUCCESS, 414 * CONFIRMATION_TYPE_WARNING, CONFIRMATION_TYPE_FAILURE 415 * @return void 416 */ 417function html_operation_confirmation( array $p_buttons = null, $p_message = '', $p_type = CONFIRMATION_TYPE_SUCCESS ) { 418 switch( $p_type ) { 419 case CONFIRMATION_TYPE_FAILURE: 420 $t_alert_css = 'alert-danger'; 421 $t_message = 'operation_failed'; 422 break; 423 case CONFIRMATION_TYPE_WARNING: 424 $t_alert_css = 'alert-warning'; 425 $t_message = 'operation_warnings'; 426 break; 427 case CONFIRMATION_TYPE_SUCCESS: 428 default: 429 $t_alert_css = 'alert-success'; 430 $t_message = 'operation_successful'; 431 break; 432 } 433 434 echo '<div class="container-fluid">'; 435 echo '<div class="col-md-12 col-xs-12">'; 436 echo '<div class="space-0"></div>'; 437 echo '<div class="alert ' . $t_alert_css . ' center">'; 438 439 # Print message 440 if( is_blank( $p_message ) ) { 441 $t_message = lang_get( $t_message ); 442 } else { 443 $t_message = $p_message; 444 } 445 echo '<p class="bold bigger-110">' . $t_message . '</p>'; 446 447 # Print buttons 448 if( !empty( $p_buttons ) ) { 449 echo '<br />'; 450 echo '<div class="btn-group">'; 451 foreach( $p_buttons as $t_button ) { 452 $t_url = string_sanitize_url( $t_button[0] ); 453 $t_label = isset( $t_button[1] ) ? $t_button[1] : lang_get( 'proceed' ); 454 455 print_link_button( $t_url, $t_label ); 456 } 457 echo '</div>'; 458 } 459 460 echo '</div></div></div>', PHP_EOL; 461} 462 463/** 464 * Outputs an operation successful message with a single redirect link. 465 * @param string $p_redirect_url The url to redirect to. 466 * @param string $p_message Message to display to the user. 467 * @return void 468 */ 469function html_operation_successful( $p_redirect_url, $p_message = '' ) { 470 html_operation_confirmation( array( array( $p_redirect_url ) ), $p_message ); 471} 472 473/** 474 * Outputs a warning message with a single redirect link. 475 * @param string $p_redirect_url The url to redirect to. 476 * @param string $p_message Message to display to the user. 477 * @return void 478 */ 479function html_operation_warning( $p_redirect_url, $p_message = '' ) { 480 html_operation_confirmation( 481 array( array( $p_redirect_url ) ), 482 $p_message, 483 CONFIRMATION_TYPE_WARNING 484 ); 485} 486 487/** 488 * Outputs an error message with a single redirect link. 489 * @param string $p_redirect_url The url to redirect to. 490 * @param string $p_message Message to display to the user. 491 * @return void 492 */ 493function html_operation_failure( $p_redirect_url, $p_message = '' ) { 494 html_operation_confirmation( 495 array( array( $p_redirect_url ) ), 496 $p_message, 497 CONFIRMATION_TYPE_FAILURE 498 ); 499} 500 501/** 502 * End the <body> section 503 * @return void 504 */ 505function html_body_end() { 506 # Should code need to be added to this function in the future, it should be 507 # placed *above* this event, which needs to be the last thing to occur 508 # before the actual body ends (see #20084) 509 event_signal( 'EVENT_LAYOUT_BODY_END' ); 510 511 echo '</body>', "\n"; 512} 513 514/** 515 * Print the closing <html> tag 516 * @return void 517 */ 518function html_end() { 519 echo '</html>', "\n"; 520 521 if( function_exists( 'fastcgi_finish_request' ) ) { 522 fastcgi_finish_request(); 523 } 524} 525 526/** 527 * Print the menu bar with a list of projects to which the user has access 528 * 529 * @see $g_show_project_menu_bar 530 * 531 * @return void 532 */ 533function print_project_menu_bar() { 534 $t_project_ids = current_user_get_accessible_projects(); 535 $t_current_project_id = helper_get_current_project(); 536 $t_button_classes = 'btn btn-xs btn-white btn-info'; 537 538 echo '<div class="col-md-12 col-xs-12">' . "\n"; 539 echo '<div class="btn-group">' . "\n"; 540 541 echo project_link_for_menu( 542 ALL_PROJECTS, 543 $t_current_project_id == ALL_PROJECTS, 544 $t_button_classes 545 ); 546 echo "\n"; 547 foreach( $t_project_ids as $t_id ) { 548 echo project_link_for_menu( 549 $t_id, 550 $t_current_project_id == $t_id, 551 $t_button_classes 552 ); 553 echo "\n"; 554 print_subproject_menu_bar( $t_current_project_id, $t_id, array( $t_id ) ); 555 } 556 557 echo '</div>' . "\n"; 558 echo '<div class="space-4"></div>' . "\n"; 559 echo '</div>' . "\n"; 560} 561 562/** 563 * Print the menu bar with a list of subprojects to which the user has access 564 * 565 * @param integer $p_current_project_id Selected project id. 566 * @param integer $p_parent_project_id Parent project id. 567 * @param array $p_parents Parent project identifiers. 568 * 569 * @return void 570 */ 571function print_subproject_menu_bar( $p_current_project_id, $p_parent_project_id, array $p_parents = array() ) { 572 $t_subprojects = current_user_get_accessible_subprojects( $p_parent_project_id ); 573 574 foreach( $t_subprojects as $t_subproject_id ) { 575 echo project_link_for_menu( 576 $t_subproject_id, 577 $t_subproject_id == $p_current_project_id, 578 'btn btn-xs btn-white btn-info', 579 $p_parents, 580 icon_get( 'fa-angle-double-right', 'ace-icon' ) 581 ); 582 echo "\n"; 583 584 # Recursive call to render this subproject's subprojects 585 print_subproject_menu_bar( 586 $p_current_project_id, 587 $t_subproject_id, 588 array_merge( $p_parents, array( $t_subproject_id) ) 589 ); 590 } 591} 592 593/** 594 * Print a generic menu (tabs). 595 * 596 * @param array $p_menu_items List of menu items 597 * @param string $p_current_page Current page's file name to highlight active tab 598 * @param string $p_event Optional event to signal, 599 */ 600function print_menu( array $p_menu_items, $p_current_page = '', $p_event = null ) { 601 echo '<ul class="nav nav-tabs padding-18">' . "\n"; 602 603 foreach( $p_menu_items as $t_item ) { 604 $t_active = $p_current_page && strpos( $t_item['url'], $p_current_page ) !== false ? 'active' : ''; 605 606 echo '<li class="' . $t_active . '">'; 607 if( $t_item['label'] == '' ) { 608 echo '<a href="'. lang_get_defaulted( $t_item['url'] ) .'">'; 609 print_icon( 'fa-info-circle', 'blue ace-icon' ); 610 echo '</a>'; 611 } else { 612 echo '<a href="'. helper_mantis_url( $t_item['url'] ) .'">' . lang_get_defaulted( $t_item['label'] ) . '</a>'; 613 } 614 echo '</li>' . "\n"; 615 } 616 617 # Plugins menu items - these are html hyperlinks (<a> tags) 618 foreach( plugin_menu_items( $p_event ) as $t_item ) { 619 $t_active = $p_current_page && strpos( $t_item, $p_current_page ) !== false 620 ? 'active' 621 : ''; 622 echo '<li class="' . $t_active . '">', $t_item, '</li>', "\n"; 623 } 624 625 echo '</ul>' . "\n"; 626} 627 628/** 629 * Print a generic submenu (buttons group). 630 * 631 * @param array $p_menu_items List of menu items 632 * @param string $p_current_page Current page's file name to highlight active tab 633 * @param string $p_event Optional event to signal, 634 */ 635function print_submenu( array $p_menu_items, $p_current_page = '', $p_event = null ) { 636 # Plugin / Event added options 637 $t_plugin_menu_items = plugin_menu_items( $p_event ); 638 639 if( $p_menu_items || $t_plugin_menu_items ) { 640 echo '<div class="space-10"></div>'; 641 echo '<div class="col-md-12 col-xs-12 center">'; 642 echo '<div class="btn-group">', "\n"; 643 644 $t_btn_template = '<a class="btn btn-sm btn-primary btn-white %s" href="%s">%s%s</a>' . "\n"; 645 646 foreach( $p_menu_items as $t_item ) { 647 if( is_array( $t_item ) ) { 648 $t_active = $p_current_page && strpos( $t_item['url'], $p_current_page ) !== false 649 ? 'active' : ''; 650 $t_icon = array_key_exists( 'icon', $t_item ) 651 ? icon_get( $t_item['icon'] ) . ' ' 652 : ''; 653 654 printf( $t_btn_template, 655 $t_active, 656 $t_item['url'], 657 $t_icon, 658 lang_get_defaulted( $t_item['label'] ) 659 ); 660 } else { 661 # Cooked link 662 echo $t_item; 663 } 664 } 665 666 # Plugins menu items - these are html hyperlinks (<a> tags) 667 # The plugin is responsible for setting the 'active' class as appropriate 668 foreach( plugin_menu_items( $p_event ) as $t_item ) { 669 echo $t_item; 670 } 671 672 echo '</div></div>', "\n"; 673 } 674} 675 676/** 677 * Print the Summary page's submenu. 678 * The submenu is only printed if there is at least one plugin-defined link, in 679 * which case a 'Synthesis' button is added for the summary page itself. 680 * @param string $p_current_page Current page's file name to highlight active menu item 681 * @return void 682 */ 683function print_summary_submenu( $p_current_page = '' ) { 684 # Plugin / Event added options 685 $t_menu_items = plugin_menu_items( 'EVENT_SUBMENU_SUMMARY' ); 686 687 if( $t_menu_items ) { 688 $t_filter_param = filter_get_temporary_key_param( summary_get_filter() ); 689 690 $t_synthesis['summary_page.php'] = array( 691 'url' => helper_url_combine( helper_mantis_url( 'summary_page.php' ), $t_filter_param ), 692 'icon' => 'fa-table', 693 'label' => 'synthesis', 694 ); 695 696 if( $p_current_page == '' ) { 697 $p_current_page = 'summary_page.php'; 698 } 699 print_submenu( array_merge( $t_synthesis, $t_menu_items ), $p_current_page ); 700 } 701} 702 703/** 704 * Print the menu for the manage section 705 * 706 * @param string $p_page Specifies the current page name so it's link can be disabled. 707 * @return void 708 */ 709function print_manage_menu( $p_page = '' ) { 710 $t_pages = array(); 711 712 if( access_has_global_level( config_get( 'manage_site_threshold' ) ) ) { 713 $t_pages['manage_overview_page.php'] = array( 'url' => 'manage_overview_page.php', 'label' => '' ); 714 } 715 if( access_has_global_level( config_get( 'manage_user_threshold' ) ) ) { 716 $t_pages['manage_user_page.php'] = array( 'url' => 'manage_user_page.php', 'label' => 'manage_users_link' ); 717 } 718 if( access_has_project_level( config_get( 'manage_project_threshold' ) ) ) { 719 $t_pages['manage_proj_page.php'] = array( 'url' => 'manage_proj_page.php', 'label' => 'manage_projects_link' ); 720 } 721 if( access_has_global_level( config_get( 'tag_edit_threshold' ) ) ) { 722 $t_pages['manage_tags_page.php'] = array( 'url' => 'manage_tags_page.php', 'label' => 'manage_tags_link' ); 723 } 724 if( access_has_global_level( config_get( 'manage_custom_fields_threshold' ) ) ) { 725 $t_pages['manage_custom_field_page.php'] = array( 'url' => 'manage_custom_field_page.php', 'label' => 'manage_custom_field_link' ); 726 } 727 if( config_get( 'enable_profiles' ) == ON && access_has_global_level( config_get( 'manage_global_profile_threshold' ) ) ) { 728 $t_pages['manage_prof_menu_page.php'] = array( 'url' => 'manage_prof_menu_page.php', 'label' => 'manage_global_profiles_link' ); 729 } 730 if( access_has_global_level( config_get( 'manage_plugin_threshold' ) ) ) { 731 $t_pages['manage_plugin_page.php'] = array( 'url' => 'manage_plugin_page.php', 'label' => 'manage_plugin_link' ); 732 } 733 734 if( access_has_project_level( config_get( 'manage_configuration_threshold' ) ) ) { 735 $t_pages['adm_permissions_report.php'] = array( 736 'url' => 'adm_permissions_report.php', 737 'label' => 'manage_config_link' 738 ); 739 } 740 741 print_menu( $t_pages, $p_page, 'EVENT_MENU_MANAGE' ); 742} 743 744/** 745 * Print the menu for the manage configuration section 746 * @param string $p_page Specifies the current page name so it's link can be disabled. 747 * @return void 748 */ 749function print_manage_config_menu( $p_page = '' ) { 750 if( !access_has_project_level( config_get( 'manage_configuration_threshold' ) ) ) { 751 return; 752 } 753 754 $t_pages = array(); 755 756 $t_pages['adm_permissions_report.php'] = array( 'url' => 'adm_permissions_report.php', 757 'label' => 'permissions_summary_report' ); 758 759 if( access_has_global_level( config_get( 'view_configuration_threshold' ) ) ) { 760 $t_pages['adm_config_report.php'] = array( 'url' => 'adm_config_report.php', 761 'label' => 'configuration_report' ); 762 } 763 764 $t_pages['manage_config_work_threshold_page.php'] = array( 'url' => 'manage_config_work_threshold_page.php', 765 'label' => 'manage_threshold_config' ); 766 767 $t_pages['manage_config_workflow_page.php'] = array( 'url' => 'manage_config_workflow_page.php', 768 'label' => 'manage_workflow_config' ); 769 770 if( config_get( 'relationship_graph_enable' ) ) { 771 $t_pages['manage_config_workflow_graph_page.php'] = array( 'url' => 'manage_config_workflow_graph_page.php', 772 'label' => 'manage_workflow_graph' ); 773 } 774 775 if( config_get( 'enable_email_notification' ) == ON ) { 776 $t_pages['manage_config_email_page.php'] = array( 'url' => 'manage_config_email_page.php', 777 'label' => 'manage_email_config' ); 778 } 779 780 $t_pages['manage_config_columns_page.php'] = array( 'url' => 'manage_config_columns_page.php', 781 'label' => 'manage_columns_config' ); 782 783 # Plugin / Event added options 784 $t_event_menu_options = event_signal( 'EVENT_MENU_MANAGE_CONFIG' ); 785 $t_menu_options = array(); 786 foreach ( $t_event_menu_options as $t_plugin => $t_plugin_menu_options ) { 787 foreach ( $t_plugin_menu_options as $t_callback => $t_callback_menu_options ) { 788 if( is_array( $t_callback_menu_options ) ) { 789 $t_menu_options = array_merge( $t_menu_options, $t_callback_menu_options ); 790 } else { 791 if( !is_null( $t_callback_menu_options ) ) { 792 $t_menu_options[] = $t_callback_menu_options; 793 } 794 } 795 } 796 } 797 798 echo '<div class="space-10"></div>' . "\n"; 799 echo '<div class="center">' . "\n"; 800 echo '<div class="btn-toolbar inline">' . "\n"; 801 echo '<div class="btn-group">' . "\n"; 802 803 foreach ( $t_pages as $t_page ) { 804 $t_active = $t_page['url'] == $p_page ? 'active' : ''; 805 echo '<a class="btn btn-sm btn-white btn-primary ' . $t_active . '" href="'. helper_mantis_url( $t_page['url'] ) .'">' . "\n"; 806 echo lang_get_defaulted( $t_page['label'] ); 807 echo '</a>' . "\n"; 808 } 809 810 foreach ( $t_menu_options as $t_menu_item ) { 811 echo $t_menu_item; 812 } 813 814 echo '</div>' . "\n"; 815 echo '</div>' . "\n"; 816 echo '</div>' . "\n"; 817} 818 819/** 820 * Print the menu for the account section 821 * @param string $p_page Specifies the current page name so it's link can be disabled. 822 * @return void 823 */ 824function print_account_menu( $p_page = '' ) { 825 $t_pages['account_page.php'] = array( 'url'=>'account_page.php', 'label'=>'account_link' ); 826 $t_pages['account_prefs_page.php'] = array( 'url'=>'account_prefs_page.php', 'label'=>'change_preferences_link' ); 827 $t_pages['account_manage_columns_page.php'] = array( 'url'=>'account_manage_columns_page.php', 'label'=>'manage_columns_config' ); 828 829 if( config_get( 'enable_profiles' ) == ON && access_has_project_level( config_get( 'add_profile_threshold' ) ) ) { 830 $t_pages['account_prof_menu_page.php'] = array( 'url'=>'account_prof_menu_page.php', 'label'=>'manage_profiles_link' ); 831 } 832 833 if( config_get( 'enable_sponsorship' ) == ON && access_has_project_level( config_get( 'view_sponsorship_total_threshold' ) ) && !current_user_is_anonymous() ) { 834 $t_pages['account_sponsor_page.php'] = array( 'url'=>'account_sponsor_page.php', 'label'=>'my_sponsorship' ); 835 } 836 837 if( api_token_can_create() ) { 838 $t_pages['api_tokens_page.php'] = array( 'url' => 'api_tokens_page.php', 'label' => 'api_tokens_link' ); 839 } 840 841 print_menu( $t_pages, $p_page, 'EVENT_MENU_ACCOUNT' ); 842} 843 844/** 845 * Print the menu for the documentation section 846 * @param string $p_page Specifies the current page name so it's link can be disabled. 847 * @return void 848 */ 849function print_doc_menu( $p_page = '' ) { 850 # User Documentation 851 $t_doc_url = config_get_global( 'manual_url' ); 852 if( is_null( parse_url( $t_doc_url, PHP_URL_SCHEME ) ) ) { 853 # URL has no scheme, so it is relative to MantisBT root 854 if( is_blank( $t_doc_url ) || 855 !file_exists( config_get_global( 'absolute_path' ) . $t_doc_url ) 856 ) { 857 # Local documentation not available, use online docs 858 $t_doc_url = 'http://www.mantisbt.org/documentation.php'; 859 } else { 860 $t_doc_url = helper_mantis_url( $t_doc_url ); 861 } 862 } 863 864 $t_pages[$t_doc_url] = array( 865 'url' => $t_doc_url, 866 'label' => 'user_documentation' 867 ); 868 869 # Project Documentation 870 $t_pages['proj_doc_page.php'] = array( 871 'url' => 'proj_doc_page.php', 872 'label' => 'project_documentation' 873 ); 874 875 # Add File 876 if( file_allow_project_upload() ) { 877 $t_pages['proj_doc_add_page.php'] = array( 878 'url' => 'proj_doc_add_page.php', 879 'label' => 'add_file' 880 ); 881 } 882 883 print_menu( $t_pages, $p_page ); 884} 885 886/** 887 * Print the menu for the summary section. 888 * @param string $p_page Specifies the current page name so it's link can be disabled. 889 * @param array $p_filter Filter array, the one in use for summary pages. 890 * @return void 891 */ 892function print_summary_menu( $p_page = '', array $p_filter = null ) { 893 $t_link = 'summary_page.php'; 894 $t_filter_param = $p_filter ? filter_get_temporary_key_param( $p_filter ) : null; 895 if( $t_filter_param ) { 896 $t_link = helper_url_combine( $t_link, $t_filter_param ); 897 } 898 $t_pages['summary_page.php'] = array( 899 'url' => $t_link, 900 'label' => 'summary_link', 901 ); 902 903 print_menu( $t_pages, $p_page, 'EVENT_MENU_SUMMARY' ); 904 905 summary_print_filter_info( $p_filter ); 906} 907 908/** 909 * Print the admin tab bar. 910 * @param string $p_page Specifies the current page name so it is set as active. 911 * @return void 912 */ 913function print_admin_menu_bar( $p_page ) { 914 # Build array with admin menu items, add Upgrade tab if necessary 915 $t_menu_items['index.php'] = icon_get( 'fa-info-circle', 'blue ace-icon' ); 916 917 # At the beginning of admin checks, the DB is not yet loaded so we can't 918 # check the schema to inform user that an upgrade is needed 919 if( $p_page == 'check/index.php' ) { 920 # Relative URL up one level to ensure valid links on Admin Checks page 921 $t_path = '../'; 922 } else { 923 global $g_upgrade; 924 include_once( 'schema.php' ); 925 if( count( $g_upgrade ) - 1 != config_get( 'database_version', -1, ALL_USERS, ALL_PROJECTS ) ) { 926 $t_menu_items['install.php'] = 'Upgrade your installation'; 927 } 928 929 $t_path = ''; 930 } 931 932 $t_menu_items += array( 933 'check/index.php' => 'Check Installation', 934 'system_utils.php' => 'System Utilities', 935 'test_langs.php' => 'Test Lang', 936 'email_queue.php' => 'Email Queue', 937 ); 938 939 echo '<div class="space-10"></div>' . "\n"; 940 echo '<ul class="nav nav-tabs padding-18">' . "\n"; 941 942 foreach( $t_menu_items as $t_menu_page => $t_description ) { 943 $t_class_active = $t_menu_page == $p_page ? ' class="active"' : ''; 944 $t_class_green = $t_menu_page == 'install.php' ? 'class="bold green" ' : ''; 945 946 echo "\t<li$t_class_active>"; 947 echo "<a " . $t_class_green 948 . 'href="' . $t_path . $t_menu_page . '">' 949 . $t_description . "</a>"; 950 echo '</li>' . "\n"; 951 } 952 953 echo '</ul>' . "\n"; 954} 955 956/** 957 * Print an html button inside a form 958 * @param string $p_action Form Action. 959 * @param string $p_button_text Button Text. 960 * @param array $p_fields An array of hidden fields to include on the form. 961 * @param string $p_method Form submit method - default post. 962 * @return void 963 */ 964function html_button( $p_action, $p_button_text, array $p_fields = array(), $p_method = 'post' ) { 965 $t_form_name = explode( '.php', $p_action, 2 ); 966 $p_action = urlencode( $p_action ); 967 $p_button_text = string_attribute( $p_button_text ); 968 969 if( strtolower( $p_method ) == 'get' ) { 970 $t_method = 'get'; 971 } else { 972 $t_method = 'post'; 973 } 974 975 echo '<form method="' . $t_method . '" action="' . $p_action . '" class="form-inline">' . "\n"; 976 echo "\t" . '<fieldset>'; 977 # Add a CSRF token only when the form is being sent via the POST method 978 if( $t_method == 'post' ) { 979 echo form_security_field( $t_form_name[0] ); 980 } 981 982 foreach( $p_fields as $t_key => $t_val ) { 983 $t_key = string_attribute( $t_key ); 984 $t_val = string_attribute( $t_val ); 985 986 echo "\t\t" . '<input type="hidden" name="' . $t_key . '" value="' . $t_val . '" />' . "\n"; 987 } 988 989 echo "\t\t" . '<input type="submit" class="btn btn-primary btn-sm btn-white btn-round" value="' . $p_button_text . '" />' . "\n"; 990 echo "\t" . '</fieldset>'; 991 echo '</form>' . "\n"; 992} 993 994/** 995 * Get the foreground color CSS class for the given status, user and project. 996 * @see html_get_status_css_bg() for background color 997 * 998 * @param integer $p_status An enumeration value. 999 * @param integer $p_user A valid user identifier. 1000 * @param integer $p_project A valid project identifier. 1001 * @return string 1002 * 1003 * @todo This does not work properly when displaying issues from a project other 1004 * than then current one, if the other project has custom status or colors. 1005 * This is due to the dynamic css for color coding (css/status_config.php). 1006 * Build CSS including project or even user-specific colors ? 1007 */ 1008function html_get_status_css_fg( $p_status, $p_user = null, $p_project = null ) { 1009 $t_status_enum = config_get( 'status_enum_string', null, $p_user, $p_project ); 1010 if( MantisEnum::hasValue( $t_status_enum, $p_status ) ) { 1011 return 'status-' . $p_status . '-fg'; 1012 } else { 1013 return ''; 1014 } 1015} 1016 1017/** 1018 * Get the background color CSS class for the given status, user and project. 1019 * @see html_get_status_css_fg() for foreground color 1020 * 1021 * @param integer $p_status An enumeration value. 1022 * @param integer $p_user A valid user identifier. 1023 * @param integer $p_project A valid project identifier. 1024 * 1025 * @return string 1026 */ 1027function html_get_status_css_bg( $p_status, $p_user = null, $p_project = null ) { 1028 $t_status_enum = config_get( 'status_enum_string', null, $p_user, $p_project ); 1029 if( MantisEnum::hasValue( $t_status_enum, $p_status ) ) { 1030 return 'status-' . $p_status . '-bg'; 1031 } else { 1032 return ''; 1033 } 1034} 1035 1036/** 1037 * Get the css class name for the given status, user and project. 1038 * 1039 * @param integer $p_status An enumeration value. 1040 * @param integer $p_user A valid user identifier. 1041 * @param integer $p_project A valid project identifier. 1042 * @return string 1043 * 1044 * @deprecated 2.21.0 Use html_get_status_css_fg() or html_get_status_css_bg() instead 1045 */ 1046function html_get_status_css_class( $p_status, $p_user = null, $p_project = null ) { 1047 error_parameters( 1048 __FUNCTION__ . '()', 1049 'html_get_status_css_fg() or html_get_status_css_bg()' 1050 ); 1051 trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED ); 1052 1053 $t_class = html_get_status_css_fg( $p_status, $p_user, $p_project ) 1054 . ' ' 1055 . html_get_status_css_bg( $p_status, $p_user, $p_project ); 1056 1057 return trim( $t_class ); 1058} 1059 1060/** 1061 * Class that provides managed generation of an HTML table content, consisting of <tr> and <td> elements 1062 * which are arranged sequentially on a grid. 1063 * Items consist of "header" and "content", which are rendered to separate table cells. 1064 * An option is provided to arrange the header and content in vertical or horizontal orientation. 1065 * Vertical orientation places header on top of content, while horizontal orientation places the header 1066 * to the left of content cell. 1067 * Each item can have a different colspan, which is used to arrange the items efficiently. When the 1068 * arrangement is made, an item with higher colspan than current free space may be placed in next row, 1069 * but still fill the current row with next items if they fit. This may cause a variation in expected 1070 * order, but allows for a more compact fill for rows. 1071 */ 1072class TableGridLayout { 1073 const ORIENTATION_VERTICAL = 0; 1074 const ORIENTATION_HORIZONTAL = 1; 1075 1076 protected $cols; 1077 private $_max_colspan; 1078 1079 public $items = array(); 1080 public $item_orientation; 1081 1082 /** 1083 * Set this variable to add a class attribute for each <tr> 1084 * @var string 1085 */ 1086 public $tr_class = null; 1087 1088 /** 1089 * Constructor. 1090 * $p_orientation may be one of this class constants: 1091 * ORIENTATION_VERTICAL, ORIENTATION_HORIZONTAL 1092 * @param integer $p_cols Number of columns for the table 1093 * @param integer $p_orientation Orientation for header and content cells 1094 */ 1095 public function __construct( $p_cols, $p_orientation = null ) { 1096 # sanitize values 1097 switch( $p_orientation ) { 1098 case self::ORIENTATION_HORIZONTAL: 1099 if( $p_cols < 2 ) { 1100 $p_cols = 2; 1101 } 1102 $this->_max_colspan = $p_cols-1; 1103 break; 1104 case self::ORIENTATION_VERTICAL: 1105 default: 1106 $p_orientation = self::ORIENTATION_VERTICAL; 1107 if( $p_cols < 1 ) { 1108 $p_cols = 1; 1109 } 1110 $this->_max_colspan = $p_cols; 1111 } 1112 1113 $this->cols = $p_cols; 1114 $this->item_orientation = $p_orientation; 1115 } 1116 1117 /** 1118 * Adds a item to the collection 1119 * @param TableFieldsItem $p_item An item 1120 */ 1121 public function add_item( TableFieldsItem $p_item ) { 1122 if( $p_item->colspan > $this->_max_colspan ) { 1123 $p_item->colspan = $this->_max_colspan; 1124 } 1125 $this->items[] = $p_item; 1126 } 1127 1128 /** 1129 * Prints the HTMl for the generated table cells, for all items contained 1130 */ 1131 public function render() { 1132 $t_rows_items = array(); 1133 $t_rows_freespace = array(); 1134 $t_used_rows = 0; 1135 1136 # Arrange the items in rows accounting for their actual cell space 1137 foreach( $this->items as $t_item ) { 1138 # Get the actual table columns needed to render the item 1139 $t_item_cols = ( $this->item_orientation == self::ORIENTATION_VERTICAL ) ? $t_item->colspan : $t_item->colspan + 1; 1140 # Search for a row with enough space to fit the item 1141 $t_found = false; 1142 for( $t_ix = 0; $t_ix < $t_used_rows; $t_ix++ ) { 1143 if( $t_rows_freespace[$t_ix] >= $t_item_cols ) { 1144 # Found a row with available space. Add the item here 1145 $t_found = true; 1146 $t_rows_freespace[$t_ix] -= $t_item_cols; 1147 $t_rows_items[$t_ix][] = $t_item; 1148 break; 1149 } 1150 } 1151 # If no suitable row was found, create new one and add the item here 1152 if( !$t_found ) { 1153 $t_rows_items[] = array( $t_item ); 1154 $t_used_rows++; 1155 $t_rows_freespace[] = $this->cols - $t_item_cols; 1156 } 1157 } 1158 1159 # Render the arranged items 1160 if( $this->tr_class ) { 1161 $p_tr_attr_class = ' class="' . $this->tr_class . '"'; 1162 } else { 1163 $p_tr_attr_class = ''; 1164 } 1165 foreach( $t_rows_items as $t_row ) { 1166 switch( $this->item_orientation ) { 1167 1168 case self::ORIENTATION_HORIZONTAL: 1169 $t_cols_left = $this->cols; 1170 echo '<tr' . $p_tr_attr_class . '>'; 1171 foreach( $t_row as $t_item ) { 1172 $this->render_td_item_header( $t_item, 1 ); 1173 $this->render_td_item_content( $t_item, $t_item->colspan ); 1174 $t_cols_left -= ( $t_item->colspan + 1 ); 1175 } 1176 if( $t_cols_left > 0 ) { 1177 $this->render_td_empty($t_cols_left); 1178 } 1179 echo '</tr>'; 1180 break; 1181 1182 # default is vertical orientation 1183 default: 1184 # row for headers 1185 $t_cols_left = $this->cols; 1186 echo '<tr' . $p_tr_attr_class . '>'; 1187 foreach( $t_row as $t_item ) { 1188 $this->render_td_item_header( $t_item, $t_item->colspan ); 1189 $t_cols_left -= $t_item->colspan; 1190 } 1191 if( $t_cols_left > 0 ) { 1192 $this->render_td_empty_header( $t_cols_left ); 1193 } 1194 echo '</tr>'; 1195 # row for contents 1196 $t_cols_left = $this->cols; 1197 echo '<tr' . $p_tr_attr_class . '>'; 1198 foreach( $t_row as $t_item ) { 1199 $this->render_td_item_content( $t_item, $t_item->colspan ); 1200 $t_cols_left -= $t_item->colspan; 1201 } 1202 if( $t_cols_left > 0 ) { 1203 $this->render_td_empty($t_cols_left); 1204 } 1205 echo '</tr>'; 1206 } 1207 } 1208 } 1209 1210 /** 1211 * Prints HTML code for an empty TD cell 1212 * @param integer $p_colspan Colspan attribute for cell 1213 */ 1214 protected function render_td_empty( $p_colspan ) { 1215 echo '<td'; 1216 if( $p_colspan > 1) { 1217 echo ' colspan="' . $p_colspan . '"'; 1218 } 1219 echo '>'; 1220 echo ' '; 1221 echo '</td>'; 1222 } 1223 1224 /** 1225 * Prints HTML code for an empty TD cell, of header type 1226 * @param integer $p_colspan Colspan attribute for cell 1227 */ 1228 protected function render_td_empty_header( $p_colspan ) { 1229 $this->render_td_empty( $p_colspan ); 1230 } 1231 1232 /** 1233 * Prints HTML code for TD cell representing the Item header 1234 * @abstract 1235 * @param TableFieldsItem $p_item Item to display 1236 * @param integer $p_colspan Colspan attribute for cell 1237 */ 1238 protected function render_td_item_header( TableFieldsItem $p_item, $p_colspan ) { 1239 echo '<th'; 1240 if( $p_item->attr_class ) { 1241 echo 'class="' . $p_item->attr_class . '"'; 1242 } 1243 if( $p_colspan > 1) { 1244 echo ' colspan="' . $p_colspan . '"'; 1245 } 1246 if( $p_item->header_attr_id ) { 1247 echo ' id="' . $p_item->header_attr_id . '"'; 1248 } 1249 echo '>'; 1250 echo $p_item->header; 1251 echo '</th>'; 1252 } 1253 1254 /** 1255 * Prints HTML code for TD cell representing the Item content 1256 * @abstract 1257 * @param TableFieldsItem $p_item Item to display 1258 * @param integer $p_colspan Colspan attribute for cell 1259 */ 1260 protected function render_td_item_content( TableFieldsItem $p_item, $p_colspan ) { 1261 echo '<td'; 1262 if( $p_item->attr_class ) { 1263 echo 'class="' . $p_item->attr_class . '"'; 1264 } 1265 if( $p_colspan > 1) { 1266 echo ' colspan="' . $p_colspan . '"'; 1267 } 1268 if( $p_item->content_attr_id ) { 1269 echo ' id="' . $p_item->content_attr_id . '"'; 1270 } 1271 echo '>'; 1272 echo $p_item->header; 1273 echo '</td>'; 1274 } 1275} 1276 1277/** 1278 * Class that represent Items to use with TableGridLayout 1279 */ 1280class TableFieldsItem { 1281 public $header; 1282 public $content; 1283 public $colspan; 1284 public $attr_class = null; 1285 public $content_attr_id = null; 1286 public $header_attr_id = null; 1287 1288 /** 1289 * Constructor 1290 * @param string $p_header HTMl to be used in header cell 1291 * @param string $p_content HTMl to be used in content cell 1292 * @param integer $p_colspan Colspan for the content cell 1293 * @param string $p_class Class to be added to the cells 1294 * @param string $p_content_id Id attribute to use for content cell 1295 * @param string $p_header_id Id attribute to use for header cell 1296 */ 1297 public function __construct( $p_header, $p_content, $p_colspan = 1, $p_class = null, $p_content_id = null, $p_header_id = null ) { 1298 $this->header = $p_header; 1299 $this->content = $p_content; 1300 if( $p_colspan < 1 ) { 1301 $p_colspan = 1; 1302 } 1303 $this->colspan = $p_colspan; 1304 $this->attr_class = $p_class; 1305 $this->content_attr_id = $p_content_id; 1306 $this->header_attr_id = $p_header_id; 1307 } 1308} 1309 1310