function get_graph($oWorkflow) { $fontsize = 11.0; $fontname = "Times-Roman"; $opts = array('fontsize' => $fontsize, 'fontname' => $fontname); $graph = new Image_GraphViz(true, $opts); $graph->dotCommand = $this->dotCommand; // we need all states & transitions // FIXME do we want guards? // we want to enable link-editing, and indicate that transitions "converge" // so we use a temporary "node" for transitions // we also use a "fake" URL which we catch later // so we can give good "alt" tags. $states = KTWorkflowState::getByWorkflow($oWorkflow); $transitions = KTWorkflowTransition::getByWorkflow($oWorkflow); $this->state_names = array(); $this->transition_names = array(); $state_opts = array('shape' => 'box', 'fontsize' => $fontsize, 'fontname' => $fontname); $transition_opts = array('shape' => 'box', 'color' => '#ffffff', 'fontsize' => $fontsize, 'fontname' => $fontname); $finaltransition_opts = array('color' => '#333333'); $sourcetransition_opts = array('color' => '#999999'); // to make this a little more useful, we want to cascade our output from // start to end states - this will tend to give a better output. // // to do this, we need to order our nodes in terms of "nearness" to the // initial node. $processing_nodes = array(); $sorted_ids = array(); $availability = array(); $sources = array(); $destinations = array(); $states = KTUtil::keyArray($states); $transitions = KTUtil::keyArray($transitions); foreach ($transitions as $tid => $oTransition) { $sources[$tid] = KTWorkflowAdminUtil::getSourceStates($oTransition, array('ids' => true)); $destinations[$tid] = $oTransition->getTargetStateId(); foreach ($sources[$tid] as $sourcestateid) { $av = (array) KTUtil::arrayGet($availability, $sourcestateid, array()); $av[] = $tid; $availability[$sourcestateid] = $av; } } //var_dump($sources); exit(0); //var_dump($availability); exit(0); $processing = array($oWorkflow->getStartStateId()); while (!empty($processing)) { $active = array_shift($processing); if (!$processing_nodes[$active]) { // mark that we've seen this node $processing_nodes[$active] = true; $sorted[] = $active; // now add all reachable nodes to the *end* of the queue. foreach ((array) $availability[$active] as $tid) { $next = $destinations[$tid]; if (!$processing_nodes[$next]) { $processing[] = $next; } } } //var_dump($processing); } //var_dump($sorted); exit(0); foreach ($sorted as $sid) { $oState = $states[$sid]; $this->state_names[$oState->getId()] = $oState->getHumanName(); $local_opts = array('URL' => sprintf("s%d", $oState->getId()), 'label' => $oState->getHumanName(), 'color' => '#666666'); if ($oState->getId() == $oWorkflow->getStartStateId()) { $local_opts['color'] = '#000000'; $local_opts['style'] = 'filled'; $local_opts['fillcolor'] = '#cccccc'; } $graph->addNode(sprintf('state%d', $oState->getId()), KTUtil::meldOptions($state_opts, $local_opts)); } foreach ($transitions as $tid => $oTransition) { $name = sprintf('transition%d', $tid); $this->transition_names[$oTransition->getId()] = $oTransition->getHumanName(); // we "cheat" and use $graph->addNode($name, KTUtil::meldOptions($transition_opts, array('URL' => sprintf("t%d", $tid), 'label' => $oTransition->getHumanName()))); $dest = sprintf("state%d", $oTransition->getTargetStateId()); $graph->addEdge(array($name => $dest), $finaltransition_opts); foreach ($sources[$tid] as $source_id) { $source_name = sprintf("state%d", $source_id); $graph->addEdge(array($source_name => $name), $sourcetransition_opts); } } // some simple analysis $errors = array(); $info = array(); $sourceless_transitions = array(); foreach ($transitions as $tid => $oTransition) { if (empty($sources[$tid])) { $sourceless_transitions[] = $oTransition->getHumanName(); } } if (!empty($sourceless_transitions)) { $errors[] = sprintf(_kt("Some transitions have no source states: %s"), implode(', ', $sourceless_transitions)); } $unlinked_states = array(); foreach ($states as $sid => $oState) { if (!$processing_nodes[$sid]) { // quick sanity check $unlinked_states[] = $oState->getHumanName(); } } if (!empty($unlinked_states)) { $errors[] = sprintf(_kt("Some states cannot be reached from the initial state (<strong>%s</strong>): %s"), $states[$oWorkflow->getStartStateId()]->getHumanName(), implode(', ', $unlinked_states)); } $data = array('graph' => $graph, 'errors' => $errors, 'info' => $info); return $data; }