Issue #10730: Use crypto_api for generating nonces and improve hashing
[mantis/radio.git] / core / relationship_graph_api.php
blobd7471715165ca079a85020910508cc41010cd78f
1 <?php
2 # MantisBT - A PHP based bugtracking system
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.
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.
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/>.
17 /**
18 * Relationship Graph API
20 * This uses GraphViz utilities to generate relationship graphs for
21 * issues. Either GraphViz (for all OSs except Windows) or
22 * WinGraphviz (for Windows) must be installed in order to use this
23 * feature.
25 * Graphviz is available at:
26 * - http://www.graphviz.org/
27 * - http://www.research.att.com/sw/tools/graphviz/
29 * WinGraphviz is available at:
30 * - http://home.so-net.net.tw/oodtsen/wingraphviz/
32 * Most Linux distributions already have a GraphViz package
33 * conveniently available for download and install. Refer to
34 * config_defaults_inc.php for how to enable this feature once
35 * GraphViz is installed.
37 * @package CoreAPI
38 * @subpackage RelationshipGraphAPI
39 * @author Juliano Ravasi Ferraz <jferraz at users sourceforge net>
40 * @copyright Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org
41 * @copyright Copyright (C) 2002 - 2010 MantisBT Team - mantisbt-dev@lists.sourceforge.net
42 * @link http://www.mantisbt.org
44 * @uses access_api.php
45 * @uses bug_api.php
46 * @uses config_api.php
47 * @uses constant_inc.php
48 * @uses graphviz_api.php
49 * @uses helper_api.php
50 * @uses relationship_api.php
51 * @uses string_api.php
52 * @uses utility_api.php
55 require_api( 'access_api.php' );
56 require_api( 'bug_api.php' );
57 require_api( 'config_api.php' );
58 require_api( 'constant_inc.php' );
59 require_api( 'graphviz_api.php' );
60 require_api( 'helper_api.php' );
61 require_api( 'relationship_api.php' );
62 require_api( 'string_api.php' );
63 require_api( 'utility_api.php' );
65 # Generate a relationship graph for the given issue.
66 function relgraph_generate_rel_graph( $p_bug_id, $p_bug = null ) {
68 # List of visited issues and their data.
69 $v_bug_list = array();
70 $v_rel_list = array();
72 # Queue for breadth-first
73 $v_queue = array();
75 # Now we visit all related issues.
76 $t_max_depth = config_get( 'relationship_graph_max_depth' );
78 # Put the first element into queue.
79 array_push( $v_queue, array( 0, $p_bug_id ) );
81 # And now we proccess it
82 while( !empty( $v_queue ) ) {
83 list( $t_depth, $t_id ) = array_shift( $v_queue );
85 if( isset( $v_bug_list[$t_id] ) ) {
86 continue;
89 if( !bug_exists( $t_id ) ) {
90 continue;
93 if( !access_has_bug_level( VIEWER, $t_id ) ) {
94 continue;
97 $v_bug_list[$t_id] = bug_get( $t_id, false );
99 $t_relationships = relationship_get_all_src( $t_id );
100 foreach( $t_relationships as $t_relationship ) {
101 $t_dst = $t_relationship->dest_bug_id;
102 if( BUG_DEPENDANT == $t_relationship->type ) {
103 $v_rel_list[$t_id][$t_dst] = BUG_DEPENDANT;
104 $v_rel_list[$t_dst][$t_id] = BUG_BLOCKS;
105 } else {
106 $v_rel_list[$t_id][$t_dst] = $t_relationship->type;
107 $v_rel_list[$t_dst][$t_id] = $t_relationship->type;
110 if( $t_depth < $t_max_depth ) {
111 array_push( $v_queue, array( $t_depth + 1, $t_dst ) );
115 $t_relationships = relationship_get_all_dest( $t_id );
116 foreach( $t_relationships as $t_relationship ) {
117 $t_dst = $t_relationship->src_bug_id;
118 if( BUG_DEPENDANT == $t_relationship->type ) {
119 $v_rel_list[$t_id][$t_dst] = BUG_BLOCKS;
120 $v_rel_list[$t_dst][$t_id] = BUG_DEPENDANT;
121 } else {
122 $v_rel_list[$t_id][$t_dst] = $t_relationship->type;
123 $v_rel_list[$t_dst][$t_id] = $t_relationship->type;
126 if( $t_depth < $t_max_depth ) {
127 array_push( $v_queue, array( $t_depth + 1, $t_dst ) );
132 # We have already collected all the information we need to generate
133 # the graph. Now it is the matter to create a Digraph object and
134 # store the information there, along with graph formatting attributes.
135 $t_id_string = bug_format_id( $p_bug_id );
136 $t_graph_fontname = config_get( 'relationship_graph_fontname' );
137 $t_graph_fontsize = config_get( 'relationship_graph_fontsize' );
138 $t_graph_fontpath = get_font_path();
139 $t_view_on_click = config_get( 'relationship_graph_view_on_click' );
140 $t_neato_tool = config_get( 'neato_tool' );
142 $t_graph_attributes = array();
144 if( !empty( $t_graph_fontpath ) ) {
145 $t_graph_attributes['fontpath'] = $t_graph_fontpath;
148 $t_graph = new Graph( $t_id_string, $t_graph_attributes, $t_neato_tool );
150 $t_graph->set_default_node_attr( array (
151 'fontname' => $t_graph_fontname,
152 'fontsize' => $t_graph_fontsize,
153 'shape' => 'record',
154 'style' => 'filled',
155 'height' => '0.2',
156 'width' => '0.4'
157 ) );
159 $t_graph->set_default_edge_attr( array (
160 'style' => 'solid',
161 'color' => '#0000C0',
162 'dir' => 'none'
163 ) );
165 # Add all issue nodes and edges to the graph.
166 ksort( $v_bug_list );
167 foreach( $v_bug_list as $t_id => $t_bug ) {
168 $t_id_string = bug_format_id( $t_id );
170 if( $t_view_on_click ) {
171 $t_url = string_get_bug_view_url( $t_id );
172 } else {
173 $t_url = "bug_relationship_graph.php?bug_id=$t_id&graph=relation";
176 relgraph_add_bug_to_graph( $t_graph, $t_id_string, $t_bug, $t_url, $t_id == $p_bug_id );
178 # Now add all relationship edges to the graph.
179 if( isset( $v_rel_list[$t_id] ) ) {
180 foreach( $v_rel_list[$t_id] as $t_dst => $t_relation ) {
182 # Do not create edges for unvisited bugs.
183 if( !isset( $v_bug_list[$t_dst] ) ) {
184 continue;
187 # avoid double links
188 if( $t_dst < $t_id ) {
189 continue;
192 $t_related_id = bug_format_id( $t_dst );
194 global $g_relationships;
195 if( isset( $g_relationships[$t_relation] ) && isset( $g_relationships[$t_relation]['#edge_style'] ) ) {
196 $t_edge_style = $g_relationships[$t_relation]['#edge_style'];
197 } else {
198 $t_edge_style = array();
201 $t_graph->add_edge( $t_id_string, $t_related_id, $t_edge_style );
206 return $t_graph;
209 # Generate a dependency relationship graph for the given issue.
210 function relgraph_generate_dep_graph( $p_bug_id, $p_bug = null, $p_horizontal = false ) {
212 # List of visited issues and their data.
213 $v_bug_list = array();
215 # Firstly, we visit all ascendant issues and all descendant issues
216 # and collect all the necessary data in the $v_bug_list variable.
217 # We do not visit other descendants of our parents, neither other
218 # ascendants of our children, to avoid displaying too much unrelated
219 # issues. We still collect the information about those relationships,
220 # so, if these issues happen to be visited also, relationship links
221 # will be preserved.
222 # The first issue in the list is the one we are parting from.
223 if( null === $p_bug ) {
224 $p_bug = bug_get( $p_bug_id, true );
227 $v_bug_list[$p_bug_id] = $p_bug;
228 $v_bug_list[$p_bug_id]->is_descendant = true;
229 $v_bug_list[$p_bug_id]->parents = array();
230 $v_bug_list[$p_bug_id]->children = array();
232 # Now we visit all ascendants of the root issue.
233 $t_relationships = relationship_get_all_dest( $p_bug_id );
234 foreach( $t_relationships as $t_relationship ) {
235 if( $t_relationship->type != BUG_DEPENDANT ) {
236 continue;
239 $v_bug_list[$p_bug_id]->parents[] = $t_relationship->src_bug_id;
240 relgraph_add_parent( $v_bug_list, $t_relationship->src_bug_id );
243 $t_relationships = relationship_get_all_src( $p_bug_id );
244 foreach( $t_relationships as $t_relationship ) {
245 if( $t_relationship->type != BUG_DEPENDANT ) {
246 continue;
249 $v_bug_list[$p_bug_id]->children[] = $t_relationship->dest_bug_id;
250 relgraph_add_child( $v_bug_list, $t_relationship->dest_bug_id );
253 # We have already collected all the information we need to generate
254 # the graph. Now it is the matter to create a Digraph object and
255 # store the information there, along with graph formatting attributes.
256 $t_id_string = bug_format_id( $p_bug_id );
257 $t_graph_fontname = config_get( 'relationship_graph_fontname' );
258 $t_graph_fontsize = config_get( 'relationship_graph_fontsize' );
259 $t_graph_fontpath = get_font_path();
260 $t_view_on_click = config_get( 'relationship_graph_view_on_click' );
261 $t_dot_tool = config_get( 'dot_tool' );
263 $t_graph_attributes = array();
265 if( !empty( $t_graph_fontpath ) ) {
266 $t_graph_attributes['fontpath'] = $t_graph_fontpath;
269 if( $p_horizontal ) {
270 $t_graph_attributes['rankdir'] = 'LR';
271 $t_graph_orientation = 'horizontal';
272 } else {
273 $t_graph_orientation = 'vertical';
276 $t_graph = new Digraph( $t_id_string, $t_graph_attributes, $t_dot_tool );
278 $t_graph->set_default_node_attr( array (
279 'fontname' => $t_graph_fontname,
280 'fontsize' => $t_graph_fontsize,
281 'shape' => 'record',
282 'style' => 'filled',
283 'height' => '0.2',
284 'width' => '0.4'
285 ) );
287 $t_graph->set_default_edge_attr( array (
288 'style' => 'solid',
289 'color' => '#C00000',
290 'dir' => 'back'
291 ) );
293 # Add all issue nodes and edges to the graph.
294 foreach( $v_bug_list as $t_related_bug_id => $t_related_bug ) {
295 $t_id_string = bug_format_id( $t_related_bug_id );
297 if( $t_view_on_click ) {
298 $t_url = string_get_bug_view_url( $t_related_bug_id );
299 } else {
300 $t_url = "bug_relationship_graph.php?bug_id=$t_related_bug_id&graph=dependency&orientation=$t_graph_orientation";
303 relgraph_add_bug_to_graph( $t_graph, $t_id_string, $t_related_bug, $t_url, $t_related_bug_id == $p_bug_id );
305 # Now add all relationship edges to the graph.
306 foreach( $v_bug_list[$t_related_bug_id]->parents as $t_parent_id ) {
308 # Do not create edges for unvisited bugs.
309 if( !isset( $v_bug_list[$t_parent_id] ) ) {
310 continue;
313 $t_parent_node = bug_format_id( $t_parent_id );
314 $t_graph->add_edge( $t_parent_node, $t_id_string );
318 return $t_graph;
321 # Internal function used to visit ascendant issues recursively.
322 function relgraph_add_parent( &$p_bug_list, $p_bug_id ) {
324 # If the issue is already in the list, we already visited it, just
325 # leave.
326 if( isset( $p_bug_list[$p_bug_id] ) ) {
327 return true;
330 # Check if the issue really exists and we have access to it. If not,
331 # it is like it didn't exist.
332 if( !bug_exists( $p_bug_id ) ) {
333 return false;
336 if( !access_has_bug_level( VIEWER, $p_bug_id ) ) {
337 return false;
340 # Add the issue to the list.
341 $p_bug_list[$p_bug_id] = bug_get( $p_bug_id, false );
342 $p_bug_list[$p_bug_id]->is_descendant = false;
343 $p_bug_list[$p_bug_id]->parents = array();
344 $p_bug_list[$p_bug_id]->children = array();
346 # Add all parent issues to the list of parents and visit them
347 # recursively.
348 $t_relationships = relationship_get_all_dest( $p_bug_id );
349 foreach( $t_relationships as $t_relationship ) {
350 if( $t_relationship->type != BUG_DEPENDANT ) {
351 continue;
354 $p_bug_list[$p_bug_id]->parents[] = $t_relationship->src_bug_id;
355 relgraph_add_parent( $p_bug_list, $t_relationship->src_bug_id );
358 # Add all child issues to the list of children. Do not visit them
359 # since this will add too much data that is unrelated to the original
360 # issue, and has a potential to generate really huge graphs.
361 $t_relationships = relationship_get_all_src( $p_bug_id );
362 foreach( $t_relationships as $t_relationship ) {
363 if( $t_relationship->type != BUG_DEPENDANT ) {
364 continue;
367 $p_bug_list[$p_bug_id]->children[] = $t_relationship->dest_bug_id;
370 return true;
373 # Internal function used to visit descendant issues recursively.
374 function relgraph_add_child( &$p_bug_list, $p_bug_id ) {
376 # Check if the issue is already in the issue list.
377 if( isset( $p_bug_list[$p_bug_id] ) ) {
379 # The issue is in the list, but we cannot discard it since it
380 # may be a parent issue (whose children were not visited).
382 if( !$p_bug_list[$p_bug_id]->is_descendant ) {
384 # Yes, we visited this issue as a parent... This is the case
385 # where someone set up a cyclic relationship (I really hope
386 # nobody ever do this, but should keep sanity) for this
387 # issue. We just have to finish the job, visiting all issues
388 # that were already listed by _add_parent().
389 $p_bug_list[$p_bug_id]->is_descendant = true;
391 foreach( $p_bug_list[$p_bug_id]->children as $t_child ) {
392 relgraph_add_child( $p_bug_id, $t_child );
395 } else {
396 # The issue is not in the list, proceed as usual.
397 # Check if the issue really exists and we have access to it.
398 # If not, it is like it didn't exist.
399 if( !bug_exists( $p_bug_id ) ) {
400 return false;
403 if( !access_has_bug_level( VIEWER, $p_bug_id ) ) {
404 return false;
407 # Add the issue to the list.
408 $p_bug_list[$p_bug_id] = bug_get( $p_bug_id, false );
409 $p_bug_list[$p_bug_id]->is_descendant = true;
410 $p_bug_list[$p_bug_id]->parents = array();
411 $p_bug_list[$p_bug_id]->children = array();
413 # Add all parent issues to the list of parents. Do not visit them
414 # for the same reason we didn't visit the children of all
415 # ancestors.
416 $t_relationships = relationship_get_all_dest( $p_bug_id );
417 foreach( $t_relationships as $t_relationship ) {
418 if( $t_relationship->type != BUG_DEPENDANT ) {
419 continue;
422 $p_bug_list[$p_bug_id]->parents[] = $t_relationship->src_bug_id;
425 # Add all child issues to the list of children and visit them
426 # recursively.
427 $t_relationships = relationship_get_all_src( $p_bug_id );
428 foreach( $t_relationships as $t_relationship ) {
429 if( $t_relationship->type != BUG_DEPENDANT ) {
430 continue;
433 $p_bug_list[$p_bug_id]->children[] = $t_relationship->dest_bug_id;
434 relgraph_add_child( $p_bug_list, $t_relationship->dest_bug_id );
438 return true;
441 # Outputs a png image for the given relationship graph, previously
442 # generated by relgraph_generate_graph_for_bug().
443 function relgraph_output_image( $p_graph ) {
444 $p_graph->output( 'png', true );
447 # Outputs an image map in HTML form (too bad dot didn't output XHTML...)
448 # for the given relationship graph, previously generated by
449 # relgraph_generate_graph_for_bug().
450 function relgraph_output_map( $p_graph, $p_name ) {
451 echo '<map name="' . $p_name . '">' . "\n";
452 $p_graph->output( 'cmap' );
453 echo '</map>' . "\n";
456 # Internal function used to add a bug to the given graph.
457 function relgraph_add_bug_to_graph( &$p_graph, $p_bug_id, $p_bug, $p_url = null, $p_highlight = false ) {
458 $t_node_attributes = array();
459 $t_node_attributes['label'] = $p_bug_id;
461 if( $p_highlight ) {
462 $t_node_attributes['color'] = '#0000FF';
463 $t_node_attributes['style'] = 'bold, filled';
464 } else {
465 $t_node_attributes['color'] = 'black';
466 $t_node_attributes['style'] = 'filled';
469 $t_node_attributes['fillcolor'] = get_status_color( $p_bug->status );
471 if( null !== $p_url ) {
472 $t_node_attributes['URL'] = $p_url;
475 $t_summary = string_display_line_links( $p_bug->summary );
476 $t_status = get_enum_element( 'status', $p_bug->status );
477 $t_node_attributes['tooltip'] = '[' . $t_status . '] ' . $t_summary;
479 $p_graph->add_node( $p_bug_id, $t_node_attributes );