5 * Copyright 2011, Alcatel-Lucent
6 * Copyright 2012,2015-2016, Franck Villaume - TrivialDev
8 * This file is part of FusionForge. FusionForge is free software;
9 * you can redistribute it and/or modify it under the terms of the
10 * GNU General Public License as published by the Free Software
11 * Foundation; either version 2 of the Licence, or (at your option)
14 * FusionForge is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License along
20 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 * Standard Alcatel-Lucent disclaimer for contributing to open source
27 * "The Roadmap ("Contribution") has not been tested and/or
28 * validated for release as or in products, combinations with products or
29 * other commercial use. Any use of the Contribution is entirely made at
30 * the user's own responsibility and the user can not rely on any features,
31 * functionalities or performances Alcatel-Lucent has attributed to the
34 * THE CONTRIBUTION BY ALCATEL-LUCENT IS PROVIDED AS IS, WITHOUT WARRANTY
35 * OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
36 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, COMPLIANCE,
37 * NON-INTERFERENCE AND/OR INTERWORKING WITH THE SOFTWARE TO WHICH THE
38 * CONTRIBUTION HAS BEEN MADE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL
39 * ALCATEL-LUCENT BE LIABLE FOR ANY DAMAGES OR OTHER LIABLITY, WHETHER IN
40 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
41 * CONTRIBUTION OR THE USE OR OTHER DEALINGS IN THE CONTRIBUTION, WHETHER
42 * TOGETHER WITH THE SOFTWARE TO WHICH THE CONTRIBUTION RELATES OR ON A STAND
46 require_once '../env.inc.php';
47 require_once $gfcommon.'include/pre.php';
48 require_once $gfcommon.'tracker/include/ArtifactTypeFactoryHtml.class.php';
49 require_once $gfcommon.'tracker/ArtifactFactory.class.php';
50 require_once $gfcommon.'tracker/Roadmap.class.php';
51 require_once $gfcommon.'tracker/RoadmapFactory.class.php';
53 // Templates ------------
54 $templates[0]['display_graph'] = 1;
55 $templates[0]['begin_tracker'] = '<table>'."\n";
56 $templates[0]['end_tracker'] = '</table>'."\n";
57 $templates[0]['separator'] = '<br/>'."\n";
58 $templates[0]['begin_release'] = '<fieldset><legend>%s<a href="%s">%s</a>%s</legend>'."\n";
59 $templates[0]['end_release'] = '</fieldset>'."\n";
60 $templates[0]['begin_ticket'] = '<tr>'."\n";
61 $templates[0]['ticket_icon'] = '<td class="align-center">%s</td>';
62 $templates[0]['ticket_id'] = '<td style="white-space: nowrap;"><a href="%s">[#%s]</a></td>';
63 $templates[0]['ticket_summary'] = '<td><span title="%s">%s</span></td>'."\n";
64 $templates[0]['end_ticket'] = '</tr>'."\n";
66 $templates[1]['display_graph'] = 0;
67 $templates[1]['begin_tracker'] = '<ul>'."\n";
68 $templates[1]['end_tracker'] = '</ul>'."\n";
69 $templates[1]['separator'] = '<br/>'."\n";
70 $templates[1]['begin_release'] = '<h2>%s<a href="%s">%s</a></h2>'."\n";
71 $templates[1]['end_release'] = "\n";
72 $templates[1]['begin_ticket'] = '<li>'."\n";
73 $templates[1]['ticket_icon'] = '';
74 $templates[1]['ticket_id'] = '<a href="%s">[#%s]</a> ';
75 $templates[1]['ticket_summary'] = '%2$s'."\n";
76 $templates[1]['end_ticket'] = '</li>'."\n";
78 //----------------------
80 function local_exit($msg='') {
90 $atfh->header(array('title' => _('Roadmap'), 'modal' => 1));
97 function nrange($col1, $col2, $nb) {
99 $range = array_fill(0, $nb, $col1);
113 for ($i = 2; $i <= $nb; $i++) {
114 $range[$i] = $range[1] + round(($range[0] - $range[1]) * sqrt(($i - 1) / $nb));
116 $range[] = array_shift($range);
119 $range = array_reverse($range);
126 function color_gradient($colors, $nb_colors) {
127 if (! is_array($colors)) {
129 } elseif (count($colors) < 2 || $nb_colors <= 2) {
133 $colors[0] = preg_replace('/^#/', '', $colors[0]);
134 $colors[1] = preg_replace('/^#/', '', $colors[1]);
136 $colors_start = array_map('hexdec', str_split($colors[0], 2));
137 $colors_end = array_map('hexdec', str_split($colors[1], 2));
138 $format_func = create_function('$val', 'return sprintf("%02s", $val);');
140 $colors_comp = array();
141 for($i = 0; $i < 3; $i++) {
142 $colors_comp[] = array_map($format_func,
143 array_map('dechex', nrange($colors_start[$i], $colors_end[$i], $nb_colors)));
145 for($i = 0; $i < $nb_colors; $i++) {
146 $grad[] = $colors_comp[0][$i].$colors_comp[1][$i].$colors_comp[2][$i];
155 if (!forge_get_config('use_tracker')) {
156 exit_disabled('home');
159 $group_id = getIntFromRequest('group_id');
161 $group = group_get_object($group_id);
162 if (!$group || !is_object($group)) {
165 if ($group->isError()) {
166 if($group->isPermissionDeniedError()) {
167 exit_permission_denied($group->getErrorMessage(),'tracker');
169 exit_error($group->getErrorMessage(), 'tracker');
173 $atfh = new ArtifactTypeFactoryHtml($group);
174 if (!$atfh || !is_object($atfh) || $atfh->isError()) {
175 exit_error(_('Error')._(': ')._('Could Not Get ArtifactTypeFactoryHtml'), 'tracker');
178 $template = getIntFromRequest('template', 0);
179 $ajax = getIntFromRequest('ajax', 0);
181 $sysdebug_enable = false;
184 $roadmap_name = getStringFromRequest('roadmap_name');
185 $roadmap_id = getIntFromRequest('roadmap_id', 0);
186 $selected_release = getStringFromRequest('release');
187 $nb_release = getIntFromRequest('nb_release', 2);
188 $display_graph = getIntFromRequest('display_graph', 2);
190 $roadmap_factory = new RoadmapFactory($group);
191 $roadmaps = $roadmap_factory->getRoadmaps(true);
192 if (empty($roadmaps)) {
193 $information = _('No roadmap available');
194 local_exit('<p>'._('The roadmap provides a short view on the trackers by viewing all tickets related to a release.').'</p>'.
195 '<p>'.sprintf(_('If you have project administrator rights, you can easily <a href="%s">create roadmaps</a>.'),
196 util_make_uri('/tracker/admin/index.php?group_id='.$group_id.'&admin_roadmap=1&new_roadmap=submit')).'</p>');
199 $selected_roadmap = null;
202 $selected_roadmap = $roadmap_factory->getRoadmapByName($roadmap_name, true);
203 if (! is_object($selected_roadmap)) {
204 $error_msg .= _('Error')._(': ').sprintf(_('roadmap %s is not available'), $roadmap_name);
207 $roadmap_id = $selected_roadmap->getID();
209 } elseif ($roadmap_id) {
210 $selected_roadmap = $roadmap_factory->getRoadmapByID($roadmap_id, true);
211 if (! is_object($selected_roadmap)) {
212 $error_msg .= _('Error')._(': ').sprintf(_('roadmap %s is not available'), 'ID='.$roadmap_id);
215 $roadmap_name = $selected_roadmap->getName();
218 $roadmap_id = $roadmap_factory->getDefault();
220 $selected_roadmap = new Roadmap($group, $roadmap_id);
221 if (! is_object($selected_roadmap)) {
222 $error_msg .= _('Error')._(': ').sprintf(_('roadmap %s is not available'), 'ID='.$roadmap_id);
226 $selected_roadmap = $roadmaps[0];
227 if (! is_object($selected_roadmap)) {
228 local_exit(_('No roadmap available'));
230 $roadmap_id = $selected_roadmap->getID();
232 $roadmap_name = $selected_roadmap->getName();
234 $release_order = array_reverse($selected_roadmap->getReleases());
235 if ($selected_release && ! in_array($selected_release, $release_order)) {
236 $error_msg .= _('Error')._(': ').sprintf(_('release %s is not available'), $selected_release);
243 $rel_states_colors = array();
244 $rel_states = array();
245 $total_rel_states = array();
246 $rel_art_states_colors = array();
247 $rel_art_states = array();
248 $total_rel_art_states = array();
251 $at_arr = $atfh->getArtifactTypes();
252 $artifact_type_list = $selected_roadmap->getList();
254 if (!$at_arr || count($at_arr) < 1) {
255 local_exit($HTML->information(_('No trackers have been set up.')));
257 foreach ($at_arr as $artifact_type) {
258 if (!is_object($artifact_type)) {
260 } elseif ($artifact_type->isError()) {
261 echo $artifact_type->getErrorMessage();
264 $artifact_type_id = $artifact_type->getID();
266 if (! array_key_exists($artifact_type_id, $artifact_type_list) || ! $artifact_type_list[$artifact_type_id]) {
267 // This tracker is not used for the roadmap
270 $field_id = $artifact_type_list[$artifact_type_id];
272 $ath = new ArtifactTypeHtml($group, $artifact_type_id);
274 if (!forge_check_perm ('tracker', $artifact_type_id, 'read')) {
278 $artifact_type_name = $artifact_type->getName();
279 $uses_custom_status = $artifact_type->usesCustomStatuses();
281 // Get all states for this artifact_type; probably useless to do it
282 if ($display_graph && $templates[$template]['display_graph']) {
283 if ($uses_custom_status) {
284 $rel_art_states_colors[$artifact_type_name] = array();
285 $arr_status = $artifact_type->getExtraFieldElements($artifact_type->getCustomStatusField());
286 foreach ($arr_status as $status) {
287 @$red_green_count[$status['status_id']]++;
288 $rel_art_states_colors[$artifact_type_name][$status['element_name']] = 0;
291 $res = $artifact_type->getStatuses();
292 while ($row = db_fetch_array($res)) {
293 $rel_states_colors[$row['id']] = 0;
294 if (! $uses_custom_status) {
295 @$red_green_count[$status['id']]++;
296 $rel_art_states_colors[$artifact_type_name][$row['status_name']] = 0;
300 // Set color radiant for custom states graph
301 $nb_colors = count(array_keys($rel_art_states_colors[$artifact_type_name]));
302 if (count($red_green_count) && array_key_exists('1', $red_green_count) && array_key_exists('2', $red_green_count) &&
303 ($red_green_count['1'] + $red_green_count['2']) == $nb_colors) {
304 $colors_gradient = array_merge(color_gradient(array('ff0000', 'ffc1bf'), $red_green_count['1']),
305 color_gradient(array('c1ffbf', '00ff00'), $red_green_count['2']));
308 $colors = array('ff0000', '00ff00');
309 $colors_gradient = color_gradient($colors, $nb_colors);
312 foreach ($rel_art_states_colors[$artifact_type_name] as $state => $color) {
313 $rel_art_states_colors[$artifact_type_name][$state] = '#'.$colors_gradient[$i];
317 // Set color radiant for open/closed states graph
318 $nb_colors = count(array_keys($rel_states_colors));
319 $colors = array('dd0000', '00dd00');
320 $colors_gradient = color_gradient($colors, $nb_colors);
322 foreach ($rel_states_colors as $state => $color) {
323 $rel_states_colors[$state] = '#'.$colors_gradient[$i];
328 $af = new ArtifactFactory($ath);
329 if (!$af || !is_object($af)) {
330 exit_error(_('Could Not Get Factory'), 'tracker');
331 } elseif ($af->isError()) {
332 exit_error($af->getErrorMessage(), 'tracker');
335 if ($selected_release) {
336 $release_filter = array($selected_release);
337 } elseif ($nb_release) {
338 $release_filter = array_slice($release_order, 0, $nb_release);
340 $release_filter = false;
342 $art_arr = $af->getArtifactsByReleases($field_id, $release_filter);
344 if (!$art_arr && $af->isError()) {
345 exit_error($af->getErrorMessage(), 'tracker');
348 if (! count($art_arr)) {
353 foreach($art_arr as $artifact) {
354 $extra_data = $artifact->getExtraFieldDataText();
355 $release_value = explode(',',$extra_data[$field_id]['value']);
357 if ($selected_release && !in_array($selected_release, $release_value)) {
361 foreach ($release_value as $release_value_id) {
362 $custom_status_name = $artifact->getCustomStatusName();
363 $artifact_id = $artifact->getID();
365 @$rmap[$release_value_id][$artifact_type_name] .= $templates[$template]['begin_ticket'];
368 if ($artifact->getStatusID() == 1) {
369 $icon = html_image('ic/ticket-open.png','','',array('alt' => $custom_status_name, 'title' => $custom_status_name));
371 $icon = html_image('ic/ticket-closed.png','','',array('alt' => $custom_status_name, 'title' => $custom_status_name));
373 $rmap[$release_value_id][$artifact_type_name] .= sprintf($templates[$template]['ticket_icon'], $icon);
376 $rmap[$release_value_id][$artifact_type_name] .= sprintf($templates[$template]['ticket_id'],
377 util_make_uri('/tracker/?func=detail&aid='.$artifact_id.'&group_id='.$group_id.'&atid='.$ath->getID()),
381 $rmap[$release_value_id][$artifact_type_name] .= sprintf($templates[$template]['ticket_summary'],
382 htmlentities($artifact->getDetails(), ENT_COMPAT, 'UTF-8'),
383 $artifact->getSummary());
385 $rmap[$release_value_id][$artifact_type_name] .= $templates[$template]['end_ticket'];
388 @$rel_states[$release_value_id][$artifact->getStatusID()]['count']++;
389 $rel_states[$release_value_id][$artifact->getStatusID()]['name'] = $artifact->getStatusName();
390 @$total_rel_states[$release_value_id]++;
391 @$rel_art_states[$release_value_id][$artifact_type_name][$custom_status_name]['count']++;
392 $rel_art_states[$release_value_id][$artifact_type_name][$custom_status_name]['name'] = $custom_status_name;
393 @$total_rel_art_states[$release_value_id][$artifact_type_name]++;
400 $atfh->header(array('title' => _('Roadmap'), 'modal' => 1));
402 // Start selection tools box
403 echo '<div id="div_options">';
404 echo '<fieldset><legend>'._('Display options').'</legend>';
405 echo $HTML->openForm(array('action' => '/tracker/roadmap.php?group_id='.$group_id, 'method' => 'post'));
406 echo $HTML->listTableTop();
409 if (! empty($roadmaps) && ! $selected_release) {
410 echo '<td class="align-center">' . _('Roadmap')._(': ');
411 foreach ($roadmaps as $roadmap) {
412 $roadmap_select[$roadmap->getID()] = $roadmap->getName();
414 $roadmap_html_params['id'] = 'roadmap';
415 echo html_build_select_box_from_assoc($roadmap_select, 'roadmap', $roadmap_id, false, false, '', false, '', false, $roadmap_html_params);
419 if (! $selected_release) {
420 echo '<td class="align-center">'._('Number of release(s) to display')._(': ');
421 echo '<select name="nb_release" id="nb_release" >';
422 foreach (array(1, 2, 4, 8) as $nb) {
423 echo '<option value="'.$nb.'"'.($nb_release == $nb ? ' selected="selected"' : '').' >'.$nb.'</option>';
425 echo '<option value="0"'.(! $nb_release ? ' selected="selected"' : '').' >'._('All').'</option>';
426 echo '</select></td>';
428 echo html_e('td', array(),
429 util_make_link('/tracker/roadmap.php?group_id='.$group_id.($roadmap_id ? '&roadmap_id='.$roadmap_id : ''), _('Return to last release(s)')));
432 echo '<td class="align-center">' . _('Display graphs')._(': ');
433 if (! $selected_release) {
434 $display_graph_values[] = _('Only last');
435 $display_graph_ids[] = 2;
437 $display_graph_values[] = _('All');
438 $display_graph_ids[] = 1;
439 $display_graph_values[] = _('None');
440 $display_graph_ids[] = 0;
441 $display_graph_html_params['id'] = 'display_graph';
442 echo html_build_select_box_from_arrays($display_graph_ids, $display_graph_values, 'display_graph', $display_graph, false, '', false, '', false, $display_graph_html_params);
445 // End selection tools box
446 echo '<td id="noscript" class="align-center"><input type="submit" name="submit" value="'._('Submit').'" /></td>';
448 echo $HTML->listTableBottom();
449 echo $HTML->closeForm();
450 echo '</fieldset></div>'."\n";
454 echo '<div id="div_roadmap">'."\n";
456 if (empty($release_order)) {
457 echo $HTML->information(_('No release available'));
460 for ($i = 0; $i < count($release_order); $i++) {
461 $release_value = $release_order[$i];
463 if ($selected_release && $release_value != $selected_release) {
467 if (! $selected_release && $nb_release && $i >= $nb_release) {
471 echo '<div id="release_'.$i.'" >'."\n";
472 echo sprintf($templates[$template]['begin_release'],
473 $roadmap_name._(': '),
474 util_make_uri('/tracker/roadmap.php?group_id='.$group_id.'&roadmap_id='.$roadmap_id.'&release='.urlencode($release_value)),
476 ' '.$HTML->getFileTxtPic(_('Display as text'), _('Display as text'), array("onclick"=>"getReleaseTxt('".addslashes($release_value)."')")));
479 if ($display_graph && $templates[$template]['display_graph']) {
482 if (array_key_exists($release_value, $rel_states) && is_array($rel_states[$release_value])) {
483 foreach ($rel_states_colors as $state => $color) {
484 if (array_key_exists($state, $rel_states[$release_value])) {
485 $data = $rel_states[$release_value][$state];
486 $percent = round($data['count'] / $total_rel_states[$release_value] * 100);
487 $graph .= '<td style="background: '.$color.'; width: '.$percent.'%;"> </td>';
488 $legend .= '<td style="white-space: nowrap; width: '.$percent.'%;"><i>'.$data['name'].': '.$data['count'].' ('.$percent.'%)</i></td>';
493 echo '<div class="graph'.$graph_class.'"'. ($graph_class && $display_graph == 2 ? 'style="display: none" >' : '>');
494 echo $HTML->listTableTop();
496 <td class="align-center">
497 <table class="progress halfwidth">
499 <tr>'. $graph .'</tr>
502 <table class="halfwidth">
503 <tr class="align-center">' .$legend. '</tr>
507 echo $HTML->listTableBottom();
514 if (! array_key_exists($release_value, $rmap)) {
515 echo html_e('p', array(), _('No data for this release'));
517 foreach ($rmap[$release_value] as $artifact_type_name => $ticket_list) {
518 echo '<h3>'.$artifact_type_name.'</h3>'."\n";
521 if ($display_graph && $templates[$template]['display_graph']) {
524 if (array_key_exists($release_value, $rel_art_states) && is_array($rel_art_states[$release_value]) &&
525 array_key_exists($artifact_type_name, $rel_art_states[$release_value]) && is_array($rel_art_states[$release_value][$artifact_type_name])) {
527 foreach ($rel_art_states_colors[$artifact_type_name] as $state => $color) {
528 if (array_key_exists($state, $rel_art_states[$release_value][$artifact_type_name])) {
529 $data = $rel_art_states[$release_value][$artifact_type_name][$state];
530 $percent = round($data['count'] / $total_rel_art_states[$release_value][$artifact_type_name] * 100);
531 $total += $data['count'];
532 $graph .= '<td style="background: '.$color.'; width: '.$percent.'%;"> </td>';
533 $legend .= '<td style="white-space: nowrap; width: '.$percent.'%;"><i>'.$data['name'].': '.$data['count'].' ('.$percent.'%)</i></td>';
536 // Display unknown statuses if exists
537 if ($total != $total_rel_art_states[$release_value][$artifact_type_name]) {
538 $percent = round((1 - $total / $total_rel_art_states[$release_value][$artifact_type_name]) * 100);
539 $graph .= '<td style="background: #aaaaaa; width: '.$percent.'%;"> </td>';
540 $legend .= '<td style="white-space: nowrap; width: '.$percent.'%;"><i>'._('Unknown').': '.($total_rel_art_states[$release_value][$artifact_type_name] - $total).' ('.$percent.'%)</i></td>';
546 <div class="graph<?php echo $graph_class ?>" <?php echo ($graph_class && $display_graph == 2 ? 'style="display: none"' : '') ?>>
547 <table class="fullwidth">
549 <td class="align-center">
550 <table class="progress halfwidth">
552 <tr><?php echo $graph; ?></tr>
555 <table class="halfwidth">
556 <tr class="align-center"><?php echo $legend ?></tr>
566 echo $templates[$template]['begin_tracker'];
568 echo $templates[$template]['end_tracker'];
569 echo $templates[$template]['separator'];
573 echo $templates[$template]['end_release'];
574 echo '</div><br/>'."\n";
582 <script type="text/javascript">/* <![CDATA[ */
584 if (! $selected_release) {
586 jQuery('#roadmap').change(function() {
590 jQuery('#nb_release').change(function() {
597 jQuery('#display_graph').change(function() {
598 var select_val = jQuery('#display_graph').val();
599 var divs = document.getElementsByTagName('div');
600 for (var i = 0; i < divs.length; i++) {
601 if (divs[i].className == 'graph0') {
602 if (select_val == 1 || select_val == 2) {
603 divs[i].style.display = 'inline';
606 divs[i].style.display = 'none';
609 if (divs[i].className == 'graph1') {
610 if (select_val == 1) {
611 divs[i].style.display = 'inline';
614 divs[i].style.display = 'none';
620 function updatePage() {
621 jQuery('#div_roadmap').empty().append('<div class="align-center"><img src="/images/ajax-loader.gif" /></div>');
626 group_id: <?php echo $group_id ?>,
627 roadmap_id: jQuery('#roadmap').val(),
628 nb_release: jQuery('#nb_release').val(),
629 display_graph: jQuery('#display_graph').val(),
632 success: function(rep) {
633 jQuery('#div_roadmap').empty().append(rep);
638 function getReleaseTxt(release) {
639 var selected_roadmap = jQuery('#roadmap').val();
640 var selected_nb_release = jQuery('#nb_release').val();
641 var selected_display_graph = jQuery('#display_graph').val();
645 data: '<?php echo 'group_id='.$group_id.'&roadmap_id='.$roadmap_id ?>&ajax=1&template=1&release='+release,
646 success: function(rep) {
647 jQuery('#div_options').empty().append('<a href="<?php echo util_make_uri('/tracker/roadmap.php?group_id='.$group_id) ?>&roadmap_id='+selected_roadmap+'&nb_release='+selected_nb_release+'&display_graph='+selected_display_graph+'" ><?php echo _('Return to last release(s)') ?></a>');
648 jQuery('#div_roadmap').empty().append(rep);
653 function hideFormButton() {
654 var element = document.getElementById('noscript');
655 if (element) element.style.display = 'none';