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 * Summary API 19 * 20 * @package CoreAPI 21 * @subpackage SummaryAPI 22 * @copyright Copyright 2000 - 2002 Kenzaburo Ito - kenito@300baud.org 23 * @copyright Copyright 2002 MantisBT Team - mantisbt-dev@lists.sourceforge.net 24 * @link http://www.mantisbt.org 25 * 26 * @uses access_api.php 27 * @uses authentication_api.php 28 * @uses bug_api.php 29 * @uses config_api.php 30 * @uses constant_inc.php 31 * @uses current_user_api.php 32 * @uses database_api.php 33 * @uses filter_constants_inc.php 34 * @uses helper_api.php 35 * @uses project_api.php 36 * @uses string_api.php 37 * @uses user_api.php 38 * @uses utility_api.php 39 */ 40 41require_api( 'access_api.php' ); 42require_api( 'authentication_api.php' ); 43require_api( 'bug_api.php' ); 44require_api( 'config_api.php' ); 45require_api( 'constant_inc.php' ); 46require_api( 'current_user_api.php' ); 47require_api( 'database_api.php' ); 48require_api( 'filter_constants_inc.php' ); 49require_api( 'helper_api.php' ); 50require_api( 'project_api.php' ); 51require_api( 'string_api.php' ); 52require_api( 'user_api.php' ); 53require_api( 'utility_api.php' ); 54 55/** 56 * Print row with percentage in summary table 57 * 58 * @param string $p_label The summary row label. 59 * @param string $p_open Count of open issues - normally string with hyperlink to filter. 60 * @param string $p_resolved Count of resolved issues - normally string with hyperlink to filter. 61 * @param string $p_closed Count of closed issues - normally string with hyperlink to filter. 62 * @param string $p_total Count of total issues - normally string with hyperlink to filter. 63 * @param string $p_resolved_ratio Ratio of resolved 64 * @param string $p_ratio Ratio of total bugs 65 * @return void 66 */ 67function summary_helper_print_row( $p_label, $p_open, $p_resolved, $p_closed, $p_total, $p_resolved_ratio, $p_ratio) { 68 echo '<tr>'; 69 printf( '<td class="width50">%s</td>', $p_label ); 70 printf( '<td class="width12 align-right">%s</td>', $p_open ); 71 printf( '<td class="width12 align-right">%s</td>', $p_resolved ); 72 printf( '<td class="width12 align-right">%s</td>', $p_closed ); 73 printf( '<td class="width12 align-right">%s</td>', $p_total ); 74 printf( '<td class="width12 align-right">%s</td>', $p_resolved_ratio ); 75 printf( '<td class="width12 align-right">%s</td>', $p_ratio ); 76 echo '</tr>'; 77} 78 79/** 80 * Returns a string representation of the user, together with a link to the issues 81 * acted on by the user ( reported, handled or commented on ) 82 * 83 * @param integer $p_user_id A valid user identifier. 84 * @param array $p_filter Filter array. 85 * @return string 86 */ 87function summary_helper_get_developer_label( $p_user_id, array $p_filter = null ) { 88 $t_user = string_display_line( user_get_name( $p_user_id ) ); 89 90 $t_link_prefix = summary_get_link_prefix( $p_filter ); 91 92 return '<a class="subtle" href="' . $t_link_prefix 93 . '&' . FILTER_PROPERTY_REPORTER_ID . '=' . $p_user_id 94 . '&' . FILTER_PROPERTY_HANDLER_ID . '=' . $p_user_id 95 . '&' . FILTER_PROPERTY_NOTE_USER_ID . '=' . $p_user_id 96 . '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . META_FILTER_NONE 97 . '&' . FILTER_PROPERTY_MATCH_TYPE . '=' . FILTER_MATCH_ANY 98 . '">' . $t_user . '</a>'; 99 100} 101 102/** 103 * Calculate bug status count according to 'open', 'resolved' and 'closed', 104 * then put the numbers into $p_cache array 105 * 106 * @param array &$p_cache The cache array. 107 * @param string $p_key The key of the array. 108 * @param string $p_status The status of issues. 109 * @param integer $p_bugcount The bug count of $p_status issues. 110 * @return void 111 */ 112function summary_helper_build_bugcount( &$p_cache, $p_key, $p_status, $p_bugcount ) { 113 $t_resolved_val = config_get( 'bug_resolved_status_threshold' ); 114 $t_closed_val = config_get( 'bug_closed_status_threshold' ); 115 116 if( $t_closed_val <= $p_status ) { 117 if( isset( $p_cache[$p_key]['closed'] ) ) { 118 $p_cache[$p_key]['closed'] += $p_bugcount; 119 } else { 120 $p_cache[$p_key]['closed'] = $p_bugcount; 121 } 122 } else if( $t_resolved_val <= $p_status ) { 123 if( isset( $p_cache[$p_key]['resolved'] ) ) { 124 $p_cache[$p_key]['resolved'] += $p_bugcount; 125 } else { 126 $p_cache[$p_key]['resolved'] = $p_bugcount; 127 } 128 } else { 129 if( isset( $p_cache[$p_key]['open'] ) ) { 130 $p_cache[$p_key]['open'] += $p_bugcount; 131 } else { 132 $p_cache[$p_key]['open'] = $p_bugcount; 133 } 134 } 135} 136/** 137 * Build bug links for 'open', 'resolved' and 'closed' issue counts 138 * 139 * @param string $p_bug_link The base bug link. 140 * @param string &$p_bugs_open The open bugs count, return open bugs link. 141 * @param string &$p_bugs_resolved The resolved bugs count, return resolved bugs link. 142 * @param string &$p_bugs_closed The closed bugs count, return closed bugs link. 143 * @param string &$p_bugs_total The total bugs count, return total bugs link. 144 * @return void 145 */ 146function summary_helper_build_buglinks( $p_bug_link, &$p_bugs_open, &$p_bugs_resolved, &$p_bugs_closed, &$p_bugs_total) { 147 $t_resolved_val = config_get( 'bug_resolved_status_threshold' ); 148 $t_closed_val = config_get( 'bug_closed_status_threshold' ); 149 150 if( 0 < $p_bugs_open ) { 151 $p_bugs_open = '<a class="subtle" href="' . $p_bug_link . '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . $t_resolved_val . '">' . $p_bugs_open . '</a>'; 152 } 153 if( 0 < $p_bugs_resolved ) { 154 $p_bugs_resolved = '<a class="subtle" href="' . $p_bug_link . '&' . FILTER_PROPERTY_STATUS . '=' . $t_resolved_val . '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . $t_closed_val . '">' . $p_bugs_resolved . '</a>'; 155 } 156 if( 0 < $p_bugs_closed ) { 157 $p_bugs_closed = '<a class="subtle" href="' . $p_bug_link . '&' . FILTER_PROPERTY_STATUS . '=' . $t_closed_val . '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . META_FILTER_NONE . '">' . $p_bugs_closed . '</a>'; 158 } 159 if( 0 < $p_bugs_total ) { 160 $p_bugs_total = '<a class="subtle" href="' . $p_bug_link . '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . META_FILTER_NONE . '">' . $p_bugs_total . '</a>'; 161 } 162} 163 164/** 165 * Calculate bug ratio 166 * @param integer $p_bugs_open The open bugs count. 167 * @param integer $p_bugs_resolved The resolved bugs count. 168 * @param integer $p_bugs_closed The closed bugs count. 169 * @param integer $p_bugs_total_count The total bugs count. 170 * @return array array of ($t_bugs_resolved_ratio, $t_bugs_ratio) 171 */ 172function summary_helper_get_bugratio( $p_bugs_open, $p_bugs_resolved, $p_bugs_closed, $p_bugs_total_count) { 173 $t_bugs_total = $p_bugs_open + $p_bugs_resolved + $p_bugs_closed; 174 $t_bugs_resolved_ratio = ( $p_bugs_resolved + $p_bugs_closed ) / ( $t_bugs_total == 0 ? 1 : $t_bugs_total ); 175 $t_bugs_ratio = $t_bugs_total / ( $p_bugs_total_count == 0 ? 1 : $p_bugs_total_count ); 176 $t_bugs_resolved_ratio = sprintf( "%.1f%%", $t_bugs_resolved_ratio * 100 ); 177 $t_bugs_ratio = sprintf( "%.1f%%", $t_bugs_ratio * 100 ); 178 return array($t_bugs_resolved_ratio, $t_bugs_ratio); 179} 180 181/** 182 * Used in summary reports - this function prints out the summary for the given enum setting. 183 * The enum field name is passed in through $p_enum. 184 * A filter can be used to limit the visibility. 185 * 186 * @param string $p_enum Enum field name. 187 * @param array $p_filter Filter array. 188 * @return void 189 */ 190function summary_print_by_enum( $p_enum, array $p_filter = null ) { 191 $t_project_id = helper_get_current_project(); 192 193 $t_project_filter = helper_project_specific_where( $t_project_id ); 194 if( ' 1<>1' == $t_project_filter ) { 195 return; 196 } 197 $t_link_prefix = summary_get_link_prefix( $p_filter ); 198 199 $t_status_query = ( 'status' == $p_enum ) ? '' : ' ,status '; 200 $t_query = new DBQuery(); 201 $t_sql = 'SELECT COUNT(id) as bugcount, ' . $p_enum . ' ' . $t_status_query 202 . ' FROM {bug} WHERE ' . $t_project_filter; 203 if( !empty( $p_filter ) ) { 204 $t_subquery = filter_cache_subquery( $p_filter ); 205 $t_sql .= ' AND {bug}.id IN :filter'; 206 $t_query->bind( 'filter', $t_subquery ); 207 } 208 $t_sql .= ' GROUP BY ' . $p_enum . ' ' . $t_status_query 209 . ' ORDER BY ' . $p_enum . ' ' . $t_status_query; 210 $t_query->sql( $t_sql ); 211 212 $t_cache = array(); 213 $t_bugs_total_count = 0; 214 215 while( $t_row = $t_query->fetch() ) { 216 $t_enum = $t_row[$p_enum]; 217 $t_status = $t_row['status']; 218 $t_bugcount = $t_row['bugcount']; 219 $t_bugs_total_count += $t_bugcount; 220 221 summary_helper_build_bugcount( $t_cache, $t_enum, $t_status, $t_bugcount ); 222 } 223 224 switch( $p_enum ) { 225 case 'status': 226 $t_filter_property = FILTER_PROPERTY_STATUS; 227 break; 228 case 'severity': 229 $t_filter_property = FILTER_PROPERTY_SEVERITY; 230 break; 231 case 'resolution': 232 $t_filter_property = FILTER_PROPERTY_RESOLUTION; 233 break; 234 case 'priority': 235 $t_filter_property = FILTER_PROPERTY_PRIORITY; 236 break; 237 default: 238 # Unknown Enum type 239 trigger_error( ERROR_GENERIC, ERROR ); 240 } 241 242 foreach( $t_cache as $t_enum => $t_item) { 243 # Build up the hyperlinks to bug views 244 $t_bugs_open = isset( $t_item['open'] ) ? $t_item['open'] : 0; 245 $t_bugs_resolved = isset( $t_item['resolved'] ) ? $t_item['resolved'] : 0; 246 $t_bugs_closed = isset( $t_item['closed'] ) ? $t_item['closed'] : 0; 247 $t_bugs_total = $t_bugs_open + $t_bugs_resolved + $t_bugs_closed; 248 $t_bugs_ratio = summary_helper_get_bugratio( $t_bugs_open, $t_bugs_resolved, $t_bugs_closed, $t_bugs_total_count); 249 250 $t_bug_link = $t_link_prefix . '&' . $t_filter_property . '=' . $t_enum; 251 252 if( !is_blank( $t_bug_link ) ) { 253 $t_resolved_val = config_get( 'bug_resolved_status_threshold' ); 254 $t_closed_val = config_get( 'bug_closed_status_threshold' ); 255 256 if( 0 < $t_bugs_open ) { 257 $t_bugs_open = '<a class="subtle" href="' . $t_bug_link 258 . '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . $t_resolved_val . '">' 259 . $t_bugs_open . '</a>'; 260 } else { 261 if( ( 'status' == $p_enum ) && ( $t_enum >= $t_resolved_val ) ) { 262 $t_bugs_open = '-'; 263 } 264 } 265 if( 0 < $t_bugs_resolved ) { 266 $t_bugs_resolved = '<a class="subtle" href="' . $t_bug_link 267 # Only add status filter if not already part of the link 268 . ( 'status' != $p_enum ? '&' . FILTER_PROPERTY_STATUS . '=' . $t_resolved_val : '' ) 269 . '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . $t_closed_val . '">' 270 . $t_bugs_resolved . '</a>'; 271 } else { 272 if( ( 'status' == $p_enum ) && (( $t_enum < $t_resolved_val ) || ( $t_enum >= $t_closed_val ) ) ) { 273 $t_bugs_resolved = '-'; 274 } 275 } 276 if( 0 < $t_bugs_closed ) { 277 $t_bugs_closed = '<a class="subtle" href="' . $t_bug_link 278 # Only add status filter if not already part of the link 279 . ( 'status' != $p_enum ? '&' . FILTER_PROPERTY_STATUS . '=' . $t_closed_val : '' ) 280 . '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . META_FILTER_NONE . '">' 281 . $t_bugs_closed . '</a>'; 282 } else { 283 if( ( 'status' == $p_enum ) && ( $t_enum < $t_closed_val ) ) { 284 $t_bugs_closed = '-'; 285 } 286 } 287 if( 0 < $t_bugs_total ) { 288 $t_bugs_total = '<a class="subtle" href="' . $t_bug_link 289 . '&' . FILTER_PROPERTY_HIDE_STATUS . '=' 290 . META_FILTER_NONE . '">' . $t_bugs_total . '</a>'; 291 } 292 if( 'status' == $p_enum ) $t_bugs_ratio[0] = '-'; 293 } 294 summary_helper_print_row( get_enum_element( $p_enum, $t_enum ), $t_bugs_open, $t_bugs_resolved, $t_bugs_closed, $t_bugs_total, $t_bugs_ratio[0], $t_bugs_ratio[1] ); 295 } 296} 297 298/** 299 * Print list of open bugs with the highest activity score the score is calculated assigning 300 * one "point" for each history event associated with the bug. 301 * A filter can be used to limit the visibility. 302 * 303 * @param array $p_filter Filter array. 304 * @return void 305 */ 306function summary_print_by_activity( array $p_filter = null ) { 307 $t_project_id = helper_get_current_project(); 308 $t_resolved = config_get( 'bug_resolved_status_threshold' ); 309 $t_specific_where = helper_project_specific_where( $t_project_id ); 310 if( ' 1<>1' == $t_specific_where ) { 311 return; 312 } 313 $t_query = new DBQuery(); 314 $t_sql = 'SELECT COUNT(h.id) as count, b.id, b.summary, b.view_state' 315 . ' FROM {bug} b JOIN {bug_history} h ON h.bug_id = b.id' 316 . ' WHERE b.status < ' . $t_query->param( (int)$t_resolved ) 317 . ' AND ' . $t_specific_where; 318 if( !empty( $p_filter ) ) { 319 $t_subquery = filter_cache_subquery( $p_filter ); 320 $t_sql .= ' AND b.id IN :filter'; 321 $t_query->bind( 'filter', $t_subquery ); 322 } 323 $t_sql .= ' GROUP BY h.bug_id, b.id, b.summary, b.last_updated, b.view_state' 324 . ' ORDER BY count DESC, b.last_updated DESC'; 325 $t_query->sql( $t_sql ); 326 327 $t_count = 0; 328 $t_private_bug_threshold = config_get( 'private_bug_threshold' ); 329 $t_summarydata = array(); 330 $t_summarybugs = array(); 331 while( $t_row = $t_query->fetch() ) { 332 # Skip private bugs unless user has proper permissions 333 if( ( VS_PRIVATE == $t_row['view_state'] ) && ( false == access_has_bug_level( $t_private_bug_threshold, $t_row['id'] ) ) ) { 334 continue; 335 } 336 337 if( $t_count++ == 10 ) { 338 break; 339 } 340 341 $t_summarydata[] = array( 342 'id' => $t_row['id'], 343 'summary' => $t_row['summary'], 344 'count' => $t_row['count'], 345 ); 346 $t_summarybugs[] = $t_row['id']; 347 } 348 349 bug_cache_array_rows( $t_summarybugs ); 350 351 foreach( $t_summarydata as $t_row ) { 352 $t_bugid = string_get_bug_view_link( $t_row['id'], false ); 353 $t_summary = string_display_line( $t_row['summary'] ); 354 $t_notescount = $t_row['count']; 355 356 echo '<tr>' . "\n"; 357 echo '<td class="small">' . $t_bugid . ' - ' . $t_summary . '</td><td class="align-right">' . $t_notescount . '</td>' . "\n"; 358 echo '</tr>' . "\n"; 359 } 360} 361 362/** 363 * Print list of bugs opened from the longest time. 364 * A filter can be used to limit the visibility. 365 * 366 * @param array $p_filter Filter array. 367 * @return void 368 */ 369function summary_print_by_age( array $p_filter = null ) { 370 $t_project_id = helper_get_current_project(); 371 $t_resolved = config_get( 'bug_resolved_status_threshold' ); 372 373 $t_specific_where = helper_project_specific_where( $t_project_id ); 374 if( ' 1<>1' == $t_specific_where ) { 375 return; 376 } 377 $t_query = new DBQuery(); 378 $t_sql = 'SELECT * FROM {bug} WHERE status < ' . $t_query->param( (int)$t_resolved ) 379 . ' AND ' . $t_specific_where; 380 if( !empty( $p_filter ) ) { 381 $t_subquery = filter_cache_subquery( $p_filter ); 382 $t_sql .= ' AND {bug}.id IN :filter'; 383 $t_query->bind( 'filter', $t_subquery ); 384 } 385 $t_sql .= ' ORDER BY date_submitted ASC, priority DESC'; 386 $t_query->sql( $t_sql ); 387 388 $t_count = 0; 389 $t_private_bug_threshold = config_get( 'private_bug_threshold' ); 390 391 while( $t_row = $t_query->fetch() ) { 392 # as we select all from bug_table, inject into the cache. 393 bug_cache_database_result( $t_row ); 394 395 # Skip private bugs unless user has proper permissions 396 if( ( VS_PRIVATE == bug_get_field( $t_row['id'], 'view_state' ) ) && ( false == access_has_bug_level( $t_private_bug_threshold, $t_row['id'] ) ) ) { 397 continue; 398 } 399 400 if( $t_count++ == 10 ) { 401 break; 402 } 403 404 $t_bugid = string_get_bug_view_link( $t_row['id'], false ); 405 $t_summary = string_display_line( $t_row['summary'] ); 406 $t_days_open = intval( ( time() - $t_row['date_submitted'] ) / SECONDS_PER_DAY ); 407 408 echo '<tr>' . "\n"; 409 echo '<td class="small">' . $t_bugid . ' - ' . $t_summary . '</td><td class="align-right">' . $t_days_open . '</td>' . "\n"; 410 echo '</tr>' . "\n"; 411 } 412} 413 414/** 415 * print bug counts by assigned to each developer. 416 * A filter can be used to limit the visibility. 417 * 418 * @param array $p_filter Filter array. 419 * @return void 420 */ 421function summary_print_by_developer( array $p_filter = null ) { 422 $t_project_id = helper_get_current_project(); 423 424 $t_specific_where = helper_project_specific_where( $t_project_id ); 425 if( ' 1<>1' == $t_specific_where ) { 426 return; 427 } 428 429 $t_query = new DBQuery(); 430 $t_sql = 'SELECT COUNT(id) as bugcount, handler_id, status' 431 . ' FROM {bug} WHERE handler_id>0 AND ' . $t_specific_where; 432 if( !empty( $p_filter ) ) { 433 $t_subquery = filter_cache_subquery( $p_filter ); 434 $t_sql .= ' AND {bug}.id IN :filter'; 435 $t_query->bind( 'filter', $t_subquery ); 436 } 437 $t_sql .= ' GROUP BY handler_id, status' 438 . ' ORDER BY handler_id, status'; 439 $t_query->sql( $t_sql ); 440 441 $t_summaryusers = array(); 442 $t_cache = array(); 443 $t_bugs_total_count = 0; 444 445 while( $t_row = $t_query->fetch() ) { 446 $t_summaryusers[] = $t_row['handler_id']; 447 $t_status = $t_row['status']; 448 $t_bugcount = $t_row['bugcount']; 449 $t_bugs_total_count += $t_bugcount; 450 $t_label = $t_row['handler_id']; 451 452 summary_helper_build_bugcount( $t_cache, $t_label, $t_status, $t_bugcount ); 453 } 454 455 user_cache_array_rows( array_unique( $t_summaryusers ) ); 456 457 foreach( $t_cache as $t_label => $t_item) { 458 # Build up the hyperlinks to bug views 459 $t_bugs_open = isset( $t_item['open'] ) ? $t_item['open'] : 0; 460 $t_bugs_resolved = isset( $t_item['resolved'] ) ? $t_item['resolved'] : 0; 461 $t_bugs_closed = isset( $t_item['closed'] ) ? $t_item['closed'] : 0; 462 $t_bugs_total = $t_bugs_open + $t_bugs_resolved + $t_bugs_closed; 463 $t_bugs_ratio = summary_helper_get_bugratio( $t_bugs_open, $t_bugs_resolved, $t_bugs_closed, $t_bugs_total_count); 464 465 $t_link_prefix = summary_get_link_prefix( $p_filter ); 466 467 $t_bug_link = $t_link_prefix . '&' . FILTER_PROPERTY_HANDLER_ID . '=' . $t_label; 468 $t_label = summary_helper_get_developer_label( $t_label, $p_filter ); 469 summary_helper_build_buglinks( $t_bug_link, $t_bugs_open, $t_bugs_resolved, $t_bugs_closed, $t_bugs_total ); 470 summary_helper_print_row( $t_label, $t_bugs_open, $t_bugs_resolved, $t_bugs_closed, $t_bugs_total, $t_bugs_ratio[0], $t_bugs_ratio[1] ); 471 } 472} 473 474/** 475 * Print bug counts by reporter id. 476 * A filter can be used to limit the visibility. 477 * 478 * @param array $p_filter Filter array. 479 * @return void 480 */ 481function summary_print_by_reporter( array $p_filter = null ) { 482 $t_reporter_summary_limit = config_get( 'reporter_summary_limit' ); 483 484 $t_project_id = helper_get_current_project(); 485 486 $t_specific_where = helper_project_specific_where( $t_project_id ); 487 if( ' 1<>1' == $t_specific_where ) { 488 return; 489 } 490 $t_query = new DBQuery(); 491 $t_sql = 'SELECT reporter_id, COUNT(*) as num FROM {bug} WHERE ' . $t_specific_where; 492 if( !empty( $p_filter ) ) { 493 $t_subquery = filter_cache_subquery( $p_filter ); 494 $t_sql .= ' AND {bug}.id IN :filter'; 495 $t_query->bind( 'filter', $t_subquery ); 496 } 497 $t_sql .= ' GROUP BY reporter_id ORDER BY num DESC'; 498 $t_query->sql( $t_sql ); 499 500 $t_reporters = array(); 501 $t_bugs_total_count = 0; 502 $t_reporters_count = 0; 503 while( $t_row = $t_query->fetch() ) { 504 $t_reporters[] = (int)$t_row['reporter_id']; 505 $t_bugs_total_count += $t_row['num']; 506 $t_reporters_count++; 507 if( $t_reporters_count == $t_reporter_summary_limit ) { 508 break; 509 } 510 } 511 512 if( empty( $t_reporters ) ) { 513 return; 514 } 515 user_cache_array_rows( $t_reporters ); 516 517 $t_query = new DBQuery(); 518 $t_sql = 'SELECT reporter_id, status, COUNT(id) AS bugcount FROM {bug}' 519 . ' WHERE ' . $t_query->sql_in( 'reporter_id', $t_reporters ) 520 . ' AND ' . $t_specific_where; 521 if( !empty( $p_filter ) ) { 522 $t_subquery = filter_cache_subquery( $p_filter ); 523 $t_sql .= ' AND {bug}.id IN :filter'; 524 $t_query->bind( 'filter', $t_subquery ); 525 } 526 $t_sql .= ' GROUP BY reporter_id, status ORDER BY reporter_id, status'; 527 $t_query->sql( $t_sql ); 528 529 $t_resolved_status = config_get( 'bug_resolved_status_threshold' ); 530 $t_closed_status = config_get( 'bug_closed_status_threshold' ); 531 $t_reporter_stats = array(); 532 while( $t_row = $t_query->fetch() ) { 533 $t_reporter_id = (int)$t_row['reporter_id']; 534 if( !isset( $t_reporter_stats[$t_reporter_id] ) ) { 535 $t_reporter_stats[$t_reporter_id] = array( 536 'open' => 0, 537 'resolved' => 0, 538 'closed' => 0, 539 'total' => 0, 540 'reporter_id' => $t_reporter_id 541 ); 542 } 543 $t_bugcount = (int)$t_row['bugcount']; 544 $t_status = (int)$t_row['status']; 545 $t_reporter_stats[$t_reporter_id]['total'] += $t_bugcount; 546 if( $t_status >= $t_closed_status ) { 547 $t_reporter_stats[$t_reporter_id]['closed'] += $t_bugcount; 548 } elseif ( $t_status >= $t_resolved_status ) { 549 $t_reporter_stats[$t_reporter_id]['resolved'] += $t_bugcount; 550 } else { 551 $t_reporter_stats[$t_reporter_id]['open'] += $t_bugcount; 552 } 553 } 554 555 # calculate ratios 556 foreach( $t_reporter_stats as $t_reporter_id => $t_stats ) { 557 $t_reporter_stats[$t_reporter_id]['ratios'] =summary_helper_get_bugratio( 558 $t_stats['open'], 559 $t_stats['resolved'], 560 $t_stats['closed'], 561 $t_bugs_total_count 562 ); 563 } 564 565 # sort based on total issue count 566 # note that after array_multisort, we lose the numeric indexes, but we stored 567 # the reporter id inside each sub-array 568 array_multisort ( array_column( $t_reporter_stats, 'total' ), SORT_DESC, $t_reporter_stats ); 569 570 # print results 571 foreach( $t_reporter_stats as $t_stats ) { 572 if( $t_stats['total'] == 0 ) { 573 continue; 574 } 575 $t_reporter_id = $t_stats['reporter_id']; 576 $t_user = string_display_line( user_get_name( $t_reporter_id ) ); 577 $t_link_prefix = summary_get_link_prefix( $p_filter ); 578 579 $t_bug_link = $t_link_prefix . '&' . FILTER_PROPERTY_REPORTER_ID . '=' . $t_reporter_id; 580 if( 0 < $t_stats['open'] ) { 581 $t_bugs_open = '<a class="subtle" href="' . $t_bug_link . '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . $t_resolved_status . '">'. $t_stats['open'] . '</a>'; 582 } else { 583 $t_bugs_open = 0; 584 } 585 if( 0 < $t_stats['resolved'] ) { 586 $t_bugs_resolved = '<a class="subtle" href="' . $t_bug_link . '&' . FILTER_PROPERTY_STATUS . '=' . $t_resolved_status . '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . $t_closed_status . '">' . $t_stats['resolved'] . '</a>'; 587 } else { 588 $t_bugs_resolved = 0; 589 } 590 if( 0 < $t_stats['closed'] ) { 591 $t_bugs_closed = '<a class="subtle" href="' . $t_bug_link . '&' . FILTER_PROPERTY_STATUS . '=' . $t_closed_status . '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . META_FILTER_NONE . '">' . $t_stats['closed'] . '</a>'; 592 } else { 593 $t_bugs_closed = 0; 594 } 595 if( 0 < $t_stats['total'] ) { 596 $t_bugs_total = '<a class="subtle" href="' . $t_bug_link . '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . META_FILTER_NONE . '">' . $t_stats['total'] . '</a>'; 597 } else { 598 $t_bugs_total = 0; 599 } 600 $t_bugs_ratio = $t_stats['ratios']; 601 summary_helper_print_row( $t_user, $t_bugs_open, $t_bugs_resolved, $t_bugs_closed, $t_bugs_total, $t_bugs_ratio[0], $t_bugs_ratio[1] ); 602 } 603} 604 605/** 606 * Print a bug count per category. 607 * A filter can be used to limit the visibility. 608 * 609 * @param array $p_filter Filter array. 610 * @return void 611 */ 612function summary_print_by_category( array $p_filter = null ) { 613 $t_summary_category_include_project = config_get( 'summary_category_include_project' ); 614 615 $t_project_id = helper_get_current_project(); 616 617 $t_specific_where = trim( helper_project_specific_where( $t_project_id ) ); 618 if( '1<>1' == $t_specific_where ) { 619 return; 620 } 621 $t_project_query = ( ON == $t_summary_category_include_project ) ? 'b.project_id, ' : ''; 622 623 $t_query = new DBQuery(); 624 $t_sql = 'SELECT COUNT(b.id) as bugcount, ' . $t_project_query . ' c.name AS category_name, category_id, b.status' 625 . ' FROM {bug} b JOIN {category} c ON b.category_id=c.id' 626 . ' WHERE b.' . $t_specific_where; 627 if( !empty( $p_filter ) ) { 628 $t_subquery = filter_cache_subquery( $p_filter ); 629 $t_sql .= ' AND b.id IN :filter'; 630 $t_query->bind( 'filter', $t_subquery ); 631 } 632 $t_sql .= ' GROUP BY ' . $t_project_query . ' c.name, b.category_id, b.status' 633 . ' ORDER BY ' . $t_project_query . ' c.name'; 634 $t_query->sql( $t_sql ); 635 636 $t_cache = array(); 637 $t_bugs_total_count = 0; 638 639 while( $t_row = $t_query->fetch() ) { 640 $t_status = $t_row['status']; 641 $t_bugcount = $t_row['bugcount']; 642 $t_bugs_total_count += $t_bugcount; 643 $t_label = $t_row['category_name']; 644 if( ( ON == $t_summary_category_include_project ) && ( ALL_PROJECTS == $t_project_id ) ) { 645 $t_label = sprintf( '[%s] %s', project_get_name( $t_row['project_id'] ), $t_label ); 646 } 647 648 summary_helper_build_bugcount( $t_cache, $t_label, $t_status, $t_bugcount ); 649 } 650 651 foreach( $t_cache as $t_label => $t_item) { 652 # Build up the hyperlinks to bug views 653 $t_bugs_open = isset( $t_item['open'] ) ? $t_item['open'] : 0; 654 $t_bugs_resolved = isset( $t_item['resolved'] ) ? $t_item['resolved'] :0; 655 $t_bugs_closed = isset( $t_item['closed'] ) ? $t_item['closed'] : 0; 656 $t_bugs_total = $t_bugs_open + $t_bugs_resolved + $t_bugs_closed; 657 $t_bugs_ratio = summary_helper_get_bugratio( $t_bugs_open, $t_bugs_resolved, $t_bugs_closed, $t_bugs_total_count); 658 659 $t_link_prefix = summary_get_link_prefix( $p_filter ); 660 661 $t_bug_link = $t_link_prefix . '&' . FILTER_PROPERTY_CATEGORY_ID . '=' . urlencode( $t_label ); 662 summary_helper_build_buglinks( $t_bug_link, $t_bugs_open, $t_bugs_resolved, $t_bugs_closed, $t_bugs_total ); 663 summary_helper_print_row( string_display_line( $t_label ), $t_bugs_open, $t_bugs_resolved, $t_bugs_closed, $t_bugs_total, $t_bugs_ratio[0], $t_bugs_ratio[1] ); 664 } 665} 666 667/** 668 * Print bug counts by project. 669 * A filter can be used to limit the visibility. 670 * @todo check p_cache - static? 671 * 672 * @param array $p_projects Array of project id's. 673 * @param integer $p_level Indicates the depth of the project within the sub-project hierarchy. 674 * @param array $p_cache Summary cache. 675 * @param array $p_filter Filter array. 676 * @return void 677 */ 678function summary_print_by_project( array $p_projects = array(), $p_level = 0, array $p_cache = null, array $p_filter = null ) { 679 $t_project_id = helper_get_current_project(); 680 681 if( empty( $p_projects ) ) { 682 if( ALL_PROJECTS == $t_project_id ) { 683 $p_projects = current_user_get_accessible_projects(); 684 } else { 685 $p_projects = array( 686 $t_project_id, 687 ); 688 } 689 } 690 691 # Retrieve statistics one time to improve performance. 692 if( null === $p_cache ) { 693 $t_query = new DBQuery(); 694 $t_sql = 'SELECT project_id, status, COUNT( status ) AS bugcount FROM {bug}'; 695 if( !empty( $p_filter ) ) { 696 $t_subquery = filter_cache_subquery( $p_filter ); 697 $t_sql .= ' WHERE {bug}.id IN :filter'; 698 $t_query->bind( 'filter', $t_subquery ); 699 } 700 $t_sql .= ' GROUP BY project_id, status'; 701 $t_query->sql( $t_sql ); 702 703 $p_cache = array(); 704 $t_bugs_total_count = 0; 705 while( $t_row = $t_query->fetch() ) { 706 $t_project_id = $t_row['project_id']; 707 $t_status = $t_row['status']; 708 $t_bugcount = $t_row['bugcount']; 709 $t_bugs_total_count += $t_bugcount; 710 711 summary_helper_build_bugcount( $p_cache, $t_project_id, $t_status, $t_bugcount ); 712 } 713 $p_cache["_bugs_total_count_"] = $t_bugs_total_count; 714 } 715 716 $t_bugs_total_count = $p_cache["_bugs_total_count_"]; 717 foreach( $p_projects as $t_project ) { 718 $t_name = str_repeat( '» ', $p_level ) . project_get_name( $t_project ); 719 720 $t_pdata = isset( $p_cache[$t_project] ) ? $p_cache[$t_project] : array( 'open' => 0, 'resolved' => 0, 'closed' => 0 ); 721 722 $t_bugs_open = isset( $t_pdata['open'] ) ? $t_pdata['open'] : 0; 723 $t_bugs_resolved = isset( $t_pdata['resolved'] ) ? $t_pdata['resolved'] : 0; 724 $t_bugs_closed = isset( $t_pdata['closed'] ) ? $t_pdata['closed'] : 0; 725 $t_bugs_total = $t_bugs_open + $t_bugs_resolved + $t_bugs_closed; 726 727 $t_bugs_ratio = summary_helper_get_bugratio( $t_bugs_open, $t_bugs_resolved, $t_bugs_closed, $t_bugs_total_count); 728 729# FILTER_PROPERTY_PROJECT_ID filter by project does not work ?? 730# $t_bug_link = '<a class="subtle" href="' . config_get( 'bug_count_hyperlink_prefix' ) . '&' . FILTER_PROPERTY_PROJECT_ID . '=' . urlencode( $t_project ); 731# summary_helper_build_buglinks( $t_bug_link, $t_bugs_open, $t_bugs_resolved, $t_bugs_closed, $t_bugs_total ); 732 733 summary_helper_print_row( string_display_line( $t_name ), $t_bugs_open, $t_bugs_resolved, $t_bugs_closed, $t_bugs_total, $t_bugs_ratio[0], $t_bugs_ratio[1]); 734 735 if( count( project_hierarchy_get_subprojects( $t_project ) ) > 0 ) { 736 $t_subprojects = current_user_get_accessible_subprojects( $t_project ); 737 738 if( count( $t_subprojects ) > 0 ) { 739 summary_print_by_project( $t_subprojects, $p_level + 1, $p_cache ); 740 } 741 } 742 } 743} 744 745/** 746 * Print developer / resolution report. 747 * A filter can be used to limit the visibility. 748 * 749 * @param string $p_resolution_enum_string Resolution enumeration string value. 750 * @param array $p_filter Filter array. 751 * @return void 752 */ 753function summary_print_developer_resolution( $p_resolution_enum_string, array $p_filter = null ) { 754 $t_project_id = helper_get_current_project(); 755 756 # Get the resolution values to use 757 $c_res_s = MantisEnum::getValues( $p_resolution_enum_string ); 758 $t_enum_res_count = count( $c_res_s ); 759 760 $t_specific_where = helper_project_specific_where( $t_project_id ); 761 if( ' 1<>1' == $t_specific_where ) { 762 return; 763 } 764 765 $t_specific_where .= ' AND handler_id > 0'; 766 767 # Get all of the bugs and split them up into an array 768 $t_query = new DBQuery(); 769 $t_sql = 'SELECT COUNT(id) as bugcount, handler_id, resolution' 770 . ' FROM {bug} WHERE ' . $t_specific_where; 771 if( !empty( $p_filter ) ) { 772 $t_subquery = filter_cache_subquery( $p_filter ); 773 $t_sql .= ' AND {bug}.id IN :filter'; 774 $t_query->bind( 'filter', $t_subquery ); 775 } 776 $t_sql .= ' GROUP BY handler_id, resolution' 777 . ' ORDER BY handler_id, resolution'; 778 $t_query->sql( $t_sql ); 779 780 $t_handler_res_arr = array(); 781 $t_arr = $t_query->fetch(); 782 while( $t_arr ) { 783 if( !isset( $t_handler_res_arr[$t_arr['handler_id']] ) ) { 784 $t_handler_res_arr[$t_arr['handler_id']] = array(); 785 $t_handler_res_arr[$t_arr['handler_id']]['total'] = 0; 786 } 787 if( !isset( $t_handler_res_arr[$t_arr['handler_id']][$t_arr['resolution']] ) ) { 788 $t_handler_res_arr[$t_arr['handler_id']][$t_arr['resolution']] = 0; 789 } 790 $t_handler_res_arr[$t_arr['handler_id']][$t_arr['resolution']] += $t_arr['bugcount']; 791 $t_handler_res_arr[$t_arr['handler_id']]['total'] += $t_arr['bugcount']; 792 793 $t_arr = $t_query->fetch(); 794 } 795 796 # Sort array so devs with highest number of bugs are listed first 797 uasort( $t_handler_res_arr, 798 function( $a, $b ) { 799 return $b['total'] - $a['total']; 800 } 801 ); 802 803 $t_threshold_fixed = config_get( 'bug_resolution_fixed_threshold' ); 804 $t_threshold_notfixed = config_get( 'bug_resolution_not_fixed_threshold' ); 805 806 $t_link_prefix = summary_get_link_prefix( $p_filter ); 807 808 $t_row_count = 0; 809 810 # We now have a multi dimensional array of users and resolutions, with the value of each resolution for each user 811 foreach( $t_handler_res_arr as $t_handler_id => $t_arr2 ) { 812 $t_total = $t_arr2['total']; 813 814 # Only print developers who have had at least one bug assigned to them. This helps 815 # prevent divide by zeroes, showing developers not on this project, and showing 816 # users that aren't actually developers... 817 818 if( $t_total > 0 ) { 819 echo '<tr>'; 820 $t_row_count++; 821 echo '<td>'; 822 echo summary_helper_get_developer_label( $t_handler_id, $p_filter ); 823 echo "</td>\n"; 824 825 # We need to track the percentage of bugs that are considered fixed, as well as 826 # those that aren't considered bugs to begin with (when looking at %age) 827 $t_bugs_fixed = 0; 828 $t_bugs_notbugs = 0; 829 for( $j = 0;$j < $t_enum_res_count;$j++ ) { 830 $t_res_bug_count = 0; 831 832 if( isset( $t_arr2[$c_res_s[$j]] ) ) { 833 $t_res_bug_count = $t_arr2[$c_res_s[$j]]; 834 } 835 836 echo '<td class="align-right">'; 837 if( 0 < $t_res_bug_count ) { 838 $t_bug_link = $t_link_prefix . 839 '&' . FILTER_PROPERTY_HANDLER_ID . '=' . $t_handler_id . 840 '&' . FILTER_PROPERTY_RESOLUTION . '=' . $c_res_s[$j] . 841 '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . META_FILTER_NONE; 842 echo '<a class="subtle" href="' . $t_bug_link . '">' . $t_res_bug_count . '</a>'; 843 } else { 844 echo $t_res_bug_count; 845 } 846 echo "</td>\n"; 847 848 if( $c_res_s[$j] >= $t_threshold_fixed ) { 849 if( $c_res_s[$j] < $t_threshold_notfixed ) { 850 # Count bugs with a resolution between fixed and not fixed thresholds 851 $t_bugs_fixed += $t_res_bug_count; 852 } else { 853 # Count bugs with a resolution above the not fixed threshold 854 $t_bugs_notbugs += $t_res_bug_count; 855 } 856 } 857 858 } 859 860 # Display Total 861 echo '<td class="align-right">'; 862 $t_bug_link = $t_link_prefix . 863 '&' . FILTER_PROPERTY_HANDLER_ID . '=' . $t_handler_id . 864 '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . META_FILTER_NONE; 865 echo '<a class="subtle" href="' . $t_bug_link . '">' . $t_total . '</a>'; 866 echo "</td>\n"; 867 868 # Percentage 869 $t_percent_fixed = 0; 870 if( ( $t_total - $t_bugs_notbugs ) > 0 ) { 871 $t_percent_fixed = ( $t_bugs_fixed / ( $t_arr2['total'] - $t_bugs_notbugs ) ); 872 } 873 echo '<td class="align-right">'; 874 printf( '% 1.0f%%', ( $t_percent_fixed * 100 ) ); 875 echo "</td>\n"; 876 echo '</tr>'; 877 } 878 } 879} 880 881/** 882 * Print reporter / resolution report. 883 * A filter can be used to limit the visibility. 884 * 885 * @param string $p_resolution_enum_string Resolution enumeration string value. 886 * @param array $p_filter Filter array. 887 * @return void 888 */ 889function summary_print_reporter_resolution( $p_resolution_enum_string, array $p_filter = null ) { 890 $t_reporter_summary_limit = config_get( 'reporter_summary_limit' ); 891 892 $t_project_id = helper_get_current_project(); 893 894 # Get the resolution values to use 895 $c_res_s = MantisEnum::getValues( $p_resolution_enum_string ); 896 $t_enum_res_count = count( $c_res_s ); 897 898 # Checking if it's a per project statistic or all projects 899 $t_specific_where = helper_project_specific_where( $t_project_id ); 900 if( ' 1<>1' == $t_specific_where ) { 901 return; 902 } 903 904 # Get all of the bugs and split them up into an array 905 906 $t_query = new DBQuery(); 907 $t_sql = 'SELECT COUNT(id) as bugcount, reporter_id, resolution' 908 . ' FROM {bug} WHERE ' . $t_specific_where; 909 if( !empty( $p_filter ) ) { 910 $t_subquery = filter_cache_subquery( $p_filter ); 911 $t_sql .= ' AND {bug}.id IN :filter'; 912 $t_query->bind( 'filter', $t_subquery ); 913 } 914 $t_sql .= ' GROUP BY reporter_id, resolution'; 915 $t_query->sql( $t_sql ); 916 917 $t_reporter_res_arr = array(); 918 $t_reporter_bugcount_arr = array(); 919 $t_arr = $t_query->fetch(); 920 while( $t_arr ) { 921 if( !isset( $t_reporter_res_arr[$t_arr['reporter_id']] ) ) { 922 $t_reporter_res_arr[$t_arr['reporter_id']] = array(); 923 $t_reporter_bugcount_arr[$t_arr['reporter_id']] = 0; 924 } 925 if( !isset( $t_reporter_res_arr[$t_arr['reporter_id']][$t_arr['resolution']] ) ) { 926 $t_reporter_res_arr[$t_arr['reporter_id']][$t_arr['resolution']] = 0; 927 } 928 $t_reporter_res_arr[$t_arr['reporter_id']][$t_arr['resolution']] += $t_arr['bugcount']; 929 $t_reporter_bugcount_arr[$t_arr['reporter_id']] += $t_arr['bugcount']; 930 931 $t_arr = $t_query->fetch(); 932 } 933 934 # Sort our total bug count array so that the reporters with the highest number of bugs are listed first, 935 arsort( $t_reporter_bugcount_arr ); 936 937 $t_threshold_fixed = config_get( 'bug_resolution_fixed_threshold' ); 938 $t_threshold_notfixed = config_get( 'bug_resolution_not_fixed_threshold' ); 939 940 $t_link_prefix = summary_get_link_prefix( $p_filter ); 941 942 $t_row_count = 0; 943 944 # We now have a multi dimensional array of users and resolutions, with the value of each resolution for each user 945 foreach( $t_reporter_bugcount_arr as $t_reporter_id => $t_total_user_bugs ) { 946 947 # Limit the number of reporters listed 948 if( $t_row_count >= $t_reporter_summary_limit ) { 949 break; 950 } 951 952 # Only print reporters who have reported at least one bug. This helps 953 # prevent divide by zeroes, showing reporters not on this project, and showing 954 # users that aren't actually reporters... 955 if( $t_total_user_bugs > 0 ) { 956 $t_arr2 = $t_reporter_res_arr[$t_reporter_id]; 957 958 echo '<tr>'; 959 $t_row_count++; 960 echo '<td>'; 961 echo string_display_line( user_get_name( $t_reporter_id ) ); 962 echo "</td>\n"; 963 964 # We need to track the percentage of bugs that are considered fix, as well as 965 # those that aren't considered bugs to begin with (when looking at %age) 966 $t_bugs_fixed = 0; 967 $t_bugs_notbugs = 0; 968 for( $j = 0;$j < $t_enum_res_count;$j++ ) { 969 $t_res_bug_count = 0; 970 971 if( isset( $t_arr2[$c_res_s[$j]] ) ) { 972 $t_res_bug_count = $t_arr2[$c_res_s[$j]]; 973 } 974 975 echo '<td class="align-right">'; 976 if( 0 < $t_res_bug_count ) { 977 $t_bug_link = $t_link_prefix . 978 '&' . FILTER_PROPERTY_REPORTER_ID . '=' . $t_reporter_id . 979 '&' . FILTER_PROPERTY_RESOLUTION . '=' . $c_res_s[$j] . 980 '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . META_FILTER_NONE; 981 echo '<a class="subtle" href="' . $t_bug_link . '">' . $t_res_bug_count . '</a>'; 982 } else { 983 echo $t_res_bug_count; 984 } 985 echo "</td>\n"; 986 987 if( $c_res_s[$j] >= $t_threshold_fixed ) { 988 if( $c_res_s[$j] < $t_threshold_notfixed ) { 989 # Count bugs with a resolution between fixed and not fixed thresholds 990 $t_bugs_fixed += $t_res_bug_count; 991 } else { 992 # Count bugs with a resolution above the not fixed threshold 993 $t_bugs_notbugs += $t_res_bug_count; 994 } 995 } 996 997 } 998 999 # Display Total 1000 echo '<td class="align-right">'; 1001 $t_bug_link = $t_link_prefix . 1002 '&' . FILTER_PROPERTY_REPORTER_ID . '=' . $t_reporter_id . 1003 '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . META_FILTER_NONE; 1004 echo '<a class="subtle" href="' . $t_bug_link . '">' . $t_total_user_bugs . '</a>'; 1005 echo "</td>\n"; 1006 1007 # Percentage 1008 $t_percent_errors = 0; 1009 if( $t_total_user_bugs > 0 ) { 1010 $t_percent_errors = ( $t_bugs_notbugs / $t_total_user_bugs ); 1011 } 1012 echo '<td class="align-right">'; 1013 printf( '% 1.0f%%', ( $t_percent_errors * 100 ) ); 1014 echo "</td>\n"; 1015 echo '</tr>'; 1016 } 1017 } 1018} 1019 1020/** 1021 * Print reporter effectiveness report. 1022 * A filter can be used to limit the visibility. 1023 * 1024 * @param string $p_severity_enum_string Severity enumeration string. 1025 * @param string $p_resolution_enum_string Resolution enumeration string. 1026 * @param array $p_filter Filter array. 1027 * @return void 1028 */ 1029function summary_print_reporter_effectiveness( $p_severity_enum_string, $p_resolution_enum_string, array $p_filter = null ) { 1030 $t_reporter_summary_limit = config_get( 'reporter_summary_limit' ); 1031 1032 $t_project_id = helper_get_current_project(); 1033 1034 $t_severity_multipliers = config_get( 'severity_multipliers' ); 1035 $t_resolution_multipliers = config_get( 'resolution_multipliers' ); 1036 1037 # Get the severity values to use 1038 $c_sev_s = MantisEnum::getValues( $p_severity_enum_string ); 1039 $t_enum_sev_count = count( $c_sev_s ); 1040 1041 # Get the resolution values to use 1042 $c_res_s = MantisEnum::getValues( $p_resolution_enum_string ); 1043 1044 # Checking if it's a per project statistic or all projects 1045 $t_specific_where = helper_project_specific_where( $t_project_id ); 1046 if( ' 1<>1' == $t_specific_where ) { 1047 return; 1048 } 1049 1050 # Get all of the bugs and split them up into an array 1051 $t_query = new DBQuery(); 1052 $t_sql = 'SELECT COUNT(id) as bugcount, reporter_id, resolution, severity' 1053 . ' FROM {bug} WHERE ' . $t_specific_where; 1054 if( !empty( $p_filter ) ) { 1055 $t_subquery = filter_cache_subquery( $p_filter ); 1056 $t_sql .= ' AND {bug}.id IN :filter'; 1057 $t_query->bind( 'filter', $t_subquery ); 1058 } 1059 $t_sql .= ' GROUP BY reporter_id, resolution, severity'; 1060 $t_query->sql( $t_sql ); 1061 1062 $t_reporter_ressev_arr = array(); 1063 $t_reporter_bugcount_arr = array(); 1064 $t_arr = $t_query->fetch(); 1065 while( $t_arr ) { 1066 if( !isset( $t_reporter_ressev_arr[$t_arr['reporter_id']] ) ) { 1067 $t_reporter_ressev_arr[$t_arr['reporter_id']] = array(); 1068 $t_reporter_bugcount_arr[$t_arr['reporter_id']] = 0; 1069 } 1070 if( !isset( $t_reporter_ressev_arr[$t_arr['reporter_id']][$t_arr['severity']] ) ) { 1071 $t_reporter_ressev_arr[$t_arr['reporter_id']][$t_arr['severity']] = array(); 1072 $t_reporter_ressev_arr[$t_arr['reporter_id']][$t_arr['severity']]['total'] = 0; 1073 } 1074 if( !isset( $t_reporter_ressev_arr[$t_arr['reporter_id']][$t_arr['severity']][$t_arr['resolution']] ) ) { 1075 $t_reporter_ressev_arr[$t_arr['reporter_id']][$t_arr['severity']][$t_arr['resolution']] = 0; 1076 } 1077 $t_reporter_ressev_arr[$t_arr['reporter_id']][$t_arr['severity']][$t_arr['resolution']] += $t_arr['bugcount']; 1078 $t_reporter_ressev_arr[$t_arr['reporter_id']][$t_arr['severity']]['total'] += $t_arr['bugcount']; 1079 $t_reporter_bugcount_arr[$t_arr['reporter_id']] += $t_arr['bugcount']; 1080 1081 $t_arr = $t_query->fetch(); 1082 } 1083 1084 # Sort our total bug count array so that the reporters with the highest number of bugs are listed first, 1085 arsort( $t_reporter_bugcount_arr ); 1086 1087 $t_row_count = 0; 1088 1089 # We now have a multi dimensional array of users, resolutions and severities, with the 1090 # value of each resolution and severity for each user 1091 foreach( $t_reporter_bugcount_arr as $t_reporter_id => $t_total_user_bugs ) { 1092 1093 # Limit the number of reporters listed 1094 if( $t_row_count >= $t_reporter_summary_limit ) { 1095 break; 1096 } 1097 1098 # Only print reporters who have reported at least one bug. This helps 1099 # prevent divide by zeroes, showing reporters not on this project, and showing 1100 # users that aren't actually reporters... 1101 if( $t_total_user_bugs > 0 ) { 1102 $t_arr2 = $t_reporter_ressev_arr[$t_reporter_id]; 1103 1104 echo '<tr>'; 1105 $t_row_count++; 1106 echo '<td>'; 1107 echo string_display_line( user_get_name( $t_reporter_id ) ); 1108 echo '</td>'; 1109 1110 $t_total_severity = 0; 1111 $t_total_errors = 0; 1112 for( $j = 0; $j < $t_enum_sev_count; $j++ ) { 1113 if( !isset( $t_arr2[$c_sev_s[$j]] ) ) { 1114 continue; 1115 } 1116 1117 $t_sev_bug_count = $t_arr2[$c_sev_s[$j]]['total']; 1118 $t_sev_mult = 1; 1119 if( isset( $t_severity_multipliers[$c_sev_s[$j]] ) ) { 1120 $t_sev_mult = $t_severity_multipliers[$c_sev_s[$j]]; 1121 } 1122 1123 if( $t_sev_bug_count > 0 ) { 1124 $t_total_severity += ( $t_sev_bug_count * $t_sev_mult ); 1125 } 1126 1127 foreach( $t_resolution_multipliers as $t_res => $t_res_mult ) { 1128 if( isset( $t_arr2[$c_sev_s[$j]][$t_res] ) ) { 1129 $t_total_errors += ( $t_sev_mult * $t_res_mult ); 1130 } 1131 } 1132 } 1133 echo '<td class="align-right">' . $t_total_severity . '</td>'; 1134 echo '<td class="align-right">' . $t_total_errors . '</td>'; 1135 printf( '<td class="align-right">%d</td>', $t_total_severity - $t_total_errors ); 1136 echo '</tr>'; 1137 } 1138 } 1139} 1140 1141/** 1142 * Calculate time stats for resolved issues. 1143 * A filter can be used to limit the visibility. 1144 * 1145 * @param integer $p_project_id. 1146 * @param array $p_filter Filter array. 1147 * @return array 1148 */ 1149function summary_helper_get_time_stats( $p_project_id, array $p_filter = null ) { 1150 $t_specific_where = helper_project_specific_where( $p_project_id ); 1151 $t_resolved = config_get( 'bug_resolved_status_threshold' ); 1152 1153 # The issue may have passed through the status we consider resolved 1154 # (e.g. bug is CLOSED, not RESOLVED). The linkage to the history field 1155 # will look up the most recent 'resolved' status change and return it as well 1156 1157 $t_stats = array( 1158 'bug_id' => 0, 1159 'largest_diff' => 0, 1160 'total_time' => 0, 1161 'average_time' => 0, 1162 ); 1163 1164 $t_sql_inner = ' FROM {bug} b LEFT JOIN {bug_history} h' 1165 . ' ON b.id = h.bug_id AND h.type = :hist_type' 1166 . ' AND h.field_name = :hist_field AND b.date_submitted <= h.date_modified' 1167 . ' WHERE b.status >= :int_resolved' 1168 . ' AND h.new_value >= :str_resolved AND h.old_value < :str_resolved' 1169 . ' AND ' . $t_specific_where; 1170 $t_params = array( 1171 'hist_type' => 0, 1172 'hist_field' => 'status', 1173 'int_resolved' => (int)$t_resolved, 1174 'str_resolved' => (string)$t_resolved 1175 ); 1176 if( !empty( $p_filter ) ) { 1177 $t_subquery = filter_cache_subquery( $p_filter ); 1178 $t_sql_inner .= ' AND b.id IN :filter'; 1179 $t_params['filter'] = $t_subquery; 1180 } 1181 1182 if( db_has_capability( DB_CAPABILITY_WINDOW_FUNCTIONS ) ) { 1183 if(db_is_mssql() ) { 1184 # sqlserver by default uses the column datatype, which is INT. This datatype can be overflowed 1185 # when a big number of issues are included, since we are adding the total number of seconds. 1186 $t_diff_expr = 'CAST(diff AS BIGINT)'; 1187 } else { 1188 $t_diff_expr = 'diff'; 1189 } 1190 $t_sql = 'SELECT id, diff, SUM(' . $t_diff_expr . ') OVER () AS total_time, AVG(' . $t_diff_expr . ') OVER () AS avg_time' 1191 . ' FROM ( SELECT b.id, MAX(h.date_modified) - b.date_submitted AS diff' 1192 . $t_sql_inner 1193 . ' GROUP BY b.id,b.date_submitted ) subquery' 1194 . ' ORDER BY diff DESC'; 1195 $t_query = new DbQuery( $t_sql, $t_params ); 1196 $t_query->set_limit(1); 1197 if( $t_row = $t_query->fetch() ) { 1198 $t_stats = array( 1199 'bug_id' => $t_row['id'], 1200 'largest_diff' => number_format( (int)$t_row['diff'] / SECONDS_PER_DAY, 2 ), 1201 'total_time' => number_format( (int)$t_row['total_time'] / SECONDS_PER_DAY, 2 ), 1202 'average_time' => number_format( (int)$t_row['avg_time'] / SECONDS_PER_DAY, 2 ), 1203 ); 1204 } 1205 } else { 1206 $t_sql = 'SELECT b.id, b.date_submitted, b.last_updated, MAX(h.date_modified) AS hist_update, b.status' 1207 . $t_sql_inner 1208 . ' GROUP BY b.id, b.status, b.date_submitted, b.last_updated ORDER BY b.id ASC'; 1209 $t_query = new DbQuery( $t_sql, $t_params ); 1210 1211 $t_bug_count = 0; 1212 $t_largest_diff = 0; 1213 $t_total_time = 0; 1214 while( $t_row = $t_query->fetch() ) { 1215 $t_bug_count++; 1216 $t_date_submitted = $t_row['date_submitted']; 1217 $t_last_updated = $t_row['hist_update'] !== null ? $t_row['hist_update'] : $t_row['last_updated']; 1218 1219 if( $t_last_updated < $t_date_submitted ) { 1220 $t_last_updated = 0; 1221 $t_date_submitted = 0; 1222 } 1223 1224 $t_diff = $t_last_updated - $t_date_submitted; 1225 $t_total_time += $t_diff; 1226 if( $t_diff > $t_largest_diff ) { 1227 $t_largest_diff = $t_diff; 1228 $t_bug_id = $t_row['id']; 1229 } 1230 } 1231 1232 if( $t_bug_count > 0 ) { 1233 $t_average_time = $t_total_time / $t_bug_count; 1234 } else { 1235 $t_average_time = 0; 1236 $t_bug_id = 0; 1237 } 1238 1239 $t_stats = array( 1240 'bug_id' => $t_bug_id, 1241 'largest_diff' => number_format( $t_largest_diff / SECONDS_PER_DAY, 2 ), 1242 'total_time' => number_format( $t_total_time / SECONDS_PER_DAY, 2 ), 1243 'average_time' => number_format( $t_average_time / SECONDS_PER_DAY, 2 ), 1244 ); 1245 } 1246 return $t_stats; 1247} 1248 1249/** 1250 * Returns a filter to be used in summary pages. 1251 * A temporary filter is retrieved if a valid temporary filter key is submitted 1252 * to the page as request parameter "filter". 1253 * If no filter key was provided, returns a generic filter that shows all 1254 * accesible issues by the user. 1255 * 1256 * @return array Filter array 1257 */ 1258function summary_get_filter() { 1259 $t_filter = null; 1260 $f_tmp_key = gpc_get_string( 'filter', null ); 1261 if( null !== $f_tmp_key ) { 1262 $t_filter = filter_temporary_get( $f_tmp_key, null ); 1263 } 1264 # if filter parameter doesn't exist or can't be loaded, return a default filter 1265 if( null === $t_filter ) { 1266 # TODO: for summary, as default, we want to show all status. 1267 # Until a better implementation for default/empty filters, we need to adjust here 1268 $t_filter = filter_get_default(); 1269 $t_filter[FILTER_PROPERTY_HIDE_STATUS] = array( META_FILTER_NONE ); 1270 $t_filter['_view_type'] = FILTER_VIEW_TYPE_SIMPLE; 1271 } 1272 return $t_filter; 1273} 1274 1275/** 1276 * Print filter related information for summary page. 1277 * If a filter has been applied, display a notice, bug count, link to view issues. 1278 * @param array $p_filter Filter array. 1279 * @return void 1280 */ 1281function summary_print_filter_info( array $p_filter = null ) { 1282 if( null === $p_filter ) { 1283 return; 1284 } 1285 # If filter is temporary, then it has been provided explicitly. 1286 # When no filter is specified for summary page, we receive a defaulted filter 1287 # which don't have any specific id. 1288 if( !filter_is_temporary( $p_filter ) ) { 1289 return; 1290 } 1291 $t_filter_query = filter_cache_subquery( $p_filter ); 1292 $t_bug_count = $t_filter_query->get_bug_count(); 1293 $t_view_issues_link = helper_url_combine( 'view_all_bug_page.php', filter_get_temporary_key_param( $p_filter ) ); 1294 ?> 1295 <div class="space-10"></div> 1296 <div class="col-md-12 col-xs-12"> 1297 <div class="alert alert-warning center"> 1298 <?php 1299 echo '<a href="', $t_view_issues_link, '" title="', lang_get( 'view_bugs_link' ), '">'; 1300 echo lang_get( 'summary_notice_filter_is_applied' ), ' ', 1301 '(', $t_bug_count, ' ', lang_get( 'bugs' ) , ')'; 1302 echo '</a>'; 1303 ?> 1304 </div> 1305 </div> 1306 <?php 1307} 1308 1309/** 1310 * Calculate the number of "open" and "resolve" issues actions in the last X days. 1311 * This includes each and successive resolution transitions. 1312 * A filter can be used to limit the visibility. 1313 * 1314 * @param array $p_date_array An array of integers representing days is passed in. 1315 * @param array $p_filter Filter array. 1316 * @return array Accumulated count for each day range. 1317 */ 1318function summary_by_dates_bug_count( array $p_date_array, array $p_filter = null ) { 1319 $t_project_id = helper_get_current_project(); 1320 $t_specific_where = helper_project_specific_where( $t_project_id ); 1321 $t_resolved = config_get( 'bug_resolved_status_threshold' ); 1322 $t_date_array = array_values( $p_date_array ); 1323 sort( $t_date_array ); 1324 1325 $t_query = new DBQuery(); 1326 $t_now = db_now(); 1327 $t_prev_days = 0; 1328 $t_sql_ranges = 'CASE'; 1329 foreach( $t_date_array as $t_ix => $t_days ) { 1330 $c_days = (int)$t_days; 1331 $t_range_start = $t_now - $c_days * SECONDS_PER_DAY + 1; 1332 $t_range_end = $t_now - $t_prev_days * SECONDS_PER_DAY; 1333 $t_sql_ranges .= ' WHEN date_modified' 1334 . ' BETWEEN ' . $t_query->param( $t_range_start ) 1335 . ' AND ' . $t_query->param( $t_range_end ) 1336 . ' THEN ' . $t_ix; 1337 $t_prev_days = $c_days; 1338 } 1339 $t_sql_ranges .= ' ELSE -1 END'; 1340 1341 $t_sql_inner = 'SELECT CASE WHEN h.type = :hist_type_new THEN :action_open' 1342 . ' WHEN h.type = :hist_type_upd AND h.old_value >= :status AND h.new_value < :status THEN :action_open' 1343 . ' WHEN h.type = :hist_type_upd AND h.old_value < :status AND h.new_value >= :status THEN :action_close' 1344 . ' ELSE null END AS action, date_modified' 1345 . ' FROM {bug_history} h JOIN {bug} b ON b.id = h.bug_id' 1346 . ' WHERE h.date_modified > :mint_ime' 1347 . ' AND ( h.type = :hist_type_new OR h.type = :hist_type_upd AND h.field_name = :hist_field )' 1348 . ' AND ' . $t_specific_where; 1349 $t_query->bind( array ( 1350 'hist_type_upd' => NORMAL_TYPE, 1351 'hist_type_new' => NEW_BUG, 1352 'hist_field' => 'status', 1353 'status' => (string)$t_resolved, 1354 'action_open' => 'O', 1355 'action_close' => 'C', 1356 'mint_ime' => $t_now - $t_prev_days * SECONDS_PER_DAY 1357 ) ); 1358 1359 if( !empty( $p_filter ) ) { 1360 $t_subquery = filter_cache_subquery( $p_filter ); 1361 $t_sql_inner .= ' AND b.id IN :filter'; 1362 $t_query->bind( 'filter', $t_subquery ); 1363 } 1364 1365 $t_sql = 'SELECT action, date_range, COUNT(*) AS range_count FROM' 1366 . ' ( SELECT action, ' . $t_sql_ranges . ' AS date_range' 1367 . ' FROM (' . $t_sql_inner . ') sub_actions' 1368 . ' WHERE action IS NOT NULL ) sub_count' 1369 . ' GROUP BY action, date_range ORDER BY date_range, action'; 1370 $t_query->sql( $t_sql ); 1371 1372 # initialize the result array to 0 1373 $t_count_array = array(); 1374 foreach( $t_date_array as $t_ix => $t_value ) { 1375 $t_count_array['open'][$t_ix] = 0; 1376 $t_count_array['close'][$t_ix] = 0; 1377 } 1378 1379 # The query returns the count specific to each date range (some ranges may 1380 # not exist in the query result if the count is 0). 1381 # Fill the array with those values first. 1382 while( $t_row = $t_query->fetch() ) { 1383 $t_index = (int)$t_row['date_range']; 1384 if( $t_index >= 0 ) { 1385 switch( $t_row['action'] ) { 1386 case 'O': 1387 $t_count_array['open'][$t_index] = $t_row['range_count']; 1388 break; 1389 case 'C': 1390 $t_count_array['close'][$t_index] = $t_row['range_count']; 1391 } 1392 } 1393 } 1394 1395 # This function returns the accumulated count. Process the array to add 1396 # each successive date range 1397 $t_count_open = 0; 1398 $t_count_closed = 0; 1399 foreach( $t_date_array as $t_ix => $t_value ) { 1400 $t_count_open += $t_count_array['open'][$t_ix]; 1401 $t_count_array['open'][$t_ix] = $t_count_open; 1402 $t_count_closed += $t_count_array['close'][$t_ix]; 1403 $t_count_array['close'][$t_ix] = $t_count_closed; 1404 } 1405 return $t_count_array; 1406} 1407 1408/** 1409 * This function shows the number of "open" and "resolve" issues actions in the 1410 * last X days. This includes each issue submission, and it's succesive resolve 1411 * and reopen transitions. 1412 * A filter can be used to limit the visibility. 1413 * 1414 * @param array $p_date_array An array of integers representing days is passed in. 1415 * @param array $p_filter Filter array. 1416 * @return void 1417 */ 1418function summary_print_by_date( array $p_date_array, array $p_filter = null ) { 1419 # clean and sort dates array 1420 $t_date_array = array_values( $p_date_array ); 1421 sort( $t_date_array ); 1422 1423 $t_by_dates_count = summary_by_dates_bug_count( $t_date_array, $p_filter ); 1424 $t_open_count_array = $t_by_dates_count['open']; 1425 $t_resolved_count_array = $t_by_dates_count['close']; 1426 1427 foreach( $t_date_array as $t_ix => $t_days ) { 1428 $t_new_count = $t_open_count_array[$t_ix]; 1429 $t_resolved_count = $t_resolved_count_array[$t_ix]; 1430 $t_start_date = mktime( 0, 0, 0, date( 'm' ), ( date( 'd' ) - $t_days ), date( 'Y' ) ); 1431 $t_end_date = mktime( 0, 0, 0 ) + SECONDS_PER_DAY; 1432 1433 $t_link_prefix = summary_get_link_prefix( $p_filter ); 1434 1435 # if we come from a filter, don't clear status properties 1436 if( !filter_is_temporary( $p_filter ) ) { 1437 $t_status_prop = '&' . FILTER_PROPERTY_HIDE_STATUS . '=' . META_FILTER_NONE; 1438 } else { 1439 $t_status_prop = ''; 1440 } 1441 $t_new_bugs_link = $t_link_prefix 1442 . '&' . FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED . '=' . ON 1443 . '&' . FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR . '=' . date( 'Y', $t_start_date ) 1444 . '&' . FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH . '=' . date( 'm', $t_start_date ) 1445 . '&' . FILTER_PROPERTY_DATE_SUBMITTED_START_DAY . '=' . date( 'd', $t_start_date ) 1446 . '&' . FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR . '=' . date( 'Y', $t_end_date ) 1447 . '&' . FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH . '=' . date( 'm', $t_end_date ) 1448 . '&' . FILTER_PROPERTY_DATE_SUBMITTED_END_DAY . '=' . date( 'd', $t_end_date ) 1449 . $t_status_prop . '">'; 1450 echo '<tr>' . "\n"; 1451 echo ' <td class="width50">' . $t_days . '</td>' . "\n"; 1452 1453 if( $t_new_count > 0 ) { 1454 echo ' <td class="align-right"><a class="subtle" href="' . $t_new_bugs_link . $t_new_count . '</a></td>' . "\n"; 1455 } else { 1456 echo ' <td class="align-right">' . $t_new_count . '</td>' . "\n"; 1457 } 1458 echo ' <td class="align-right">' . $t_resolved_count . '</td>' . "\n"; 1459 1460 $t_balance = $t_new_count - $t_resolved_count; 1461 $t_style = ''; 1462 if( $t_balance > 0 ) { 1463 1464 # we are talking about bugs: a balance > 0 is "negative" for the project... 1465 $t_style = ' red'; 1466 $t_balance = sprintf( '%+d', $t_balance ); 1467 1468 # "+" modifier added in PHP >= 4.3.0 1469 } else if( $t_balance < 0 ) { 1470 $t_style = ' green'; 1471 $t_balance = sprintf( '%+d', $t_balance ); 1472 } 1473 1474 echo ' <td class="align-right' . $t_style . '">' . $t_balance . "</td>\n"; 1475 echo '</tr>' . "\n"; 1476 } 1477} 1478 1479function summary_get_link_prefix( array $p_filter = null ) { 1480 $t_filter_action = filter_is_temporary( $p_filter ) ? FILTER_ACTION_PARSE_ADD : FILTER_ACTION_PARSE_NEW; 1481 $t_link_prefix = 'view_all_set.php?type=' . $t_filter_action . '&temporary=y&new=1'; 1482 $t_link_prefix = helper_url_combine( $t_link_prefix, filter_get_temporary_key_param( $p_filter ) ); 1483 return $t_link_prefix; 1484} 1485