PageRenderTime 57ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/core/html_api.php

https://github.com/Kirill/mantisbt
PHP | 1621 lines | 962 code | 198 blank | 461 comment | 168 complexity | 81da44a98b02ec84c9173e2c2803d13f MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file