User:Hummel-riegel/SRF Process/SRF Process.php

From mediawiki.org
<?php
/*******************************************************************************
*	This file contains the Process Printer for SemanticResultFormats
*   (http://www.mediawiki.org/wiki/Extension:Semantic_Result_Formats)
*
*	Copyright (c) 2008 - 2009 Frank Dengler and Hans-Jörg Happel
*
*   Process Printer is free software: you can redistribute it and/or modify
*   it under the terms of the GNU General Public License as published by
*   the Free Software Foundation, either version 3 of the License, or
*   (at your option) any later version.
*
*   Process Printer is distributed in the hope that it will be useful,
*   but WITHOUT ANY WARRANTY; without even the implied warranty of
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*   GNU General Public License for more details.
*
*   You should have received a copy of the GNU General Public License
*   along with Process Printer. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/

if( !defined( 'MEDIAWIKI' ) ) {
	die( 'Not an entry point.' );
}

/**
 * This is a contribution to Semtantic Result Formats (SRF) which are an
 * extension of Semantic MediaWiki (SMW) which in turn is an extension
 * of MediaWiki
 *
 * SRF defines certain "printers" to render the results of SMW semantic
 * "ASK"-queries. Some of these printers make use of the GraphViz/dot
 * library (which is wrapped by a separate MediaWiki extension).
 *
 * The purpose of this extension, is to render results of ASK-Queries
 * (e.g. Classes with Attributes) as GraphViz-layouted process graphs
 *
 *
 * @author Frank Dengler
 * @author Hans-Jörg Happel
 * @author Thomas Hummel
 * @ingroup SemanticResultFormats
 *
 * @note AUTOLOADED
 */

//global variable defining picture path
global $IP;
$srfgPicturePath = $IP . '/images/';

class SRFProcess extends SMWResultPrinter {

	// configuration variables
	protected $m_graphValidation 	= false;
	protected $m_isDebugSet 		= false;
	protected $m_processCategory	= 'Process'; // Category for processes - required for rendering compound nodes

	// internal variables
	protected $m_process;	// process to be rendered


	public function getMimeType($res) {
		return 'text/x+pnml';
	}

	public function getFileName($res) {
		if ($this->getSearchLabel(SMW_OUTPUT_WIKI) != '') {
			return str_replace(' ', '_',$this->getSearchLabel(SMW_OUTPUT_WIKI)) . '.pnml';
		} else {
			return 'mygraph.pnml';
		}
	}


	/**
	 *  This method is called before rendering the output to push
	 *  the parameters for result formatting to the printer
	 *
	 *	@param params		array of parameters provided for the ask-query
	 *  @param outputmode	?
	 *	@return				void
	 *
	 */
	protected function readParameters($params,$outputmode) {

		SMWResultPrinter::readParameters($params,$outputmode);

		// init process graph instance
		$this->m_process = new ProcessGraph();

		// process configuration

		if (array_key_exists('graphname', $params)) {
			$this->m_process->setGraphName(trim($params['graphname']));
		}

		if (array_key_exists('graphsize', $params)) {
			$this->m_process->setGraphSize(trim($params['graphsize']));
		}

		if (array_key_exists('clustercolor', $params)) {
			$this->m_process->setClusterColor(trim($params['clustercolor']));
		}

		if (array_key_exists('rankdir', $params)) {
			$this->m_process->setRankdir(strtoupper(trim($params['rankdir'])));
		}

		if (array_key_exists('showroles', $params)) {
			if (self::isTrue($params['showroles'])) $this->m_process->setShowRoles(true);
		}

		if (array_key_exists('showstatus', $params)) {
			if (self::isTrue($params['showstatus'])) $this->m_process->setShowStatus(true);
		}

		if (array_key_exists('showresources', $params)) {
			if (self::isTrue($params['showresources'])) $this->m_process->setShowRessources(true);
		}

		if (array_key_exists('highlight', $params)) {
			$this->m_process->setHighlightNode(trim($params['highlight']));
		}

		if (array_key_exists('highlightcolor', $params)) {
			$this->m_process->setHighlightColor(trim($params['highlightcolor']));
		}

		if (array_key_exists('redlinkcolor', $params)) {
			$this->m_process->setHighlightColor(trim($params['redlinkcolor']));
		}

		if (array_key_exists('showredlinks', $params)) {
			if (self::isTrue($params['showredlinks'])) $this->m_process->setShowRedLinks(true);
		}

		if (array_key_exists('showcompound', $params)) {
			if (self::isTrue($params['showcompound'])) $this->m_process->setShowCompound(true);
		}

		// method configuration

		if (array_key_exists('debug', $params)) {
			if (self::isTrue($params['debug'])) $this->m_isDebugSet = true;
		}

		if (array_key_exists('graphvalidation', $params)) {
			if (self::isTrue($params['graphvalidation'])) $this->m_graphValidation = true;
		}

		if (array_key_exists('processcat', $params)) {
			$this->m_processCategory = $params['processcat'];
		}

	}

	public static function isTrue($value){
		$res = false;
		if ((strtolower(trim($value))=='yes') || (strtolower(trim($value)) == 'true')) $res = true;
		return $res;
	}
	
	public static function xmlspecialchars($text) {
   return str_replace('&#039;', '&apos;', htmlspecialchars($text, ENT_QUOTES));
  }

	public static $elementID = 0;
	public static $arcID = 0;
	public static $BlankNodeID = 0;
	
	public static $placePositionX = 1;
	public static $placePositionY = 201;
	public static $transPositionX = 25;
	public static $transPositionY = 301;

	public static function getBlankNodeID(){
	  return "blank_".self::$BlankNodeID++;
	}
	
	public static function getNewPlacePosition() {
		
		$res = 'x="'.self::$placePositionX.'" y="'.self::$placePositionY.'"';
	  self::$placePositionX = self::$placePositionX + 75;
		self::$placePositionY = self::$placePositionY + 0;
		
		return $res;
	}
	
	public static $posHelperUp = true;
	
	public static function getNewTransPosition() {
		$res = 'x="'.self::$transPositionX.'" y="'.self::$transPositionY.'"';
		self::$transPositionX = self::$transPositionX + 75;
		if (self::$posHelperUp){self::$transPositionY = self::$transPositionY -200; self::$posHelperUp = false;}
		else {self::$transPositionY = self::$transPositionY + 200; self::$posHelperUp = true;}
		
		return $res;
	}
		

	public static function createPNMLPlace($id, $label){
			$res = '
      <place id="'.self::xmlspecialchars($id).'">
        <toolspecific tool="Horus" version="1.0.0">
          <elementGraphicsContribution alpha="255" hasGradient="false" index="'.self::$elementID++.'"/>
          <place>
            <viewName>
              <text>'.self::xmlspecialchars($label).'</text>
              <graphics>
                <offset x="0" y="-18"/>
                <line color="RGB {0, 0, 0}"/>
                <font family="Segoe UI" style="0" size="8" align="center"/>
              </graphics>
              <toolspecific tool="Horus" version="1.0.0">
                <elementGraphicsContribution index="'.self::$elementID++.'">
                  <dimension x="-1"/>
                  <fontdata>1|Segoe UI|8.0|0|WINDOWS|1|-12|0|0|0|400|0|0|0|1|0|0|0|0|Segoe UI</fontdata>
                </elementGraphicsContribution>
              </toolspecific>
            </viewName>
            <description>
              <graphics>
                <offset x="'.rand(0,100).'" y="'.rand(-100,0).'"/>
                <line color="RGB {128, 128, 128}"/>
                <font family="Segoe UI" style="0" size="7" align="left"/>
              </graphics>
              <toolspecific tool="Horus" version="1.0.0">
                <elementGraphicsContribution index="'.self::$elementID++.'">
                  <dimension x="-1"/>
                  <fontdata>1|Segoe UI|7.0|0|WINDOWS|1|-12|0|0|0|400|0|0|0|1|0|0|0|0|Segoe UI</fontdata>
                </elementGraphicsContribution>
              </toolspecific>
            </description>
            <notes>
              <graphics>
                <offset x="'.rand(0,100).'" y="'.rand(0,100).'"/>
                <line color="RGB {128, 128, 128}"/>
                <font family="Segoe UI" style="2" size="7" align="left"/>
              </graphics>
              <toolspecific tool="Horus" version="1.0.0">
                <elementGraphicsContribution index="'.self::$elementID++.'">
                  <dimension x="-1"/>
                  <fontdata>1|Segoe UI|7.0|2|WINDOWS|1|-12|0|0|0|0|1|0|0|1|0|0|0|0|Segoe UI</fontdata>
                </elementGraphicsContribution>
              </toolspecific>
            </notes>
            <visibility>
              <nameVisible>false</nameVisible>
              <viewNameVisible>true</viewNameVisible>
              <descriptionVisible>false</descriptionVisible>
              <notesVisible>false</notesVisible>
            </visibility>
            <schema/>
            <capacity>
              <total>-1</total>
              <usage>1.0</usage>
            </capacity>
          </place>

        </toolspecific>
        <name>
          <graphics>
            <offset x="'.rand(0,100).'" y="'.rand(-100,0).'"/>
            <line color="RGB {128, 128, 128}"/>
            <font family="Segoe UI" style="0" size="8" align="left"/>
          </graphics>
          <toolspecific tool="Horus" version="1.0.0">
            <elementGraphicsContribution index="'.self::$elementID++.'">
              <dimension x="-1"/>
              <fontdata>1|Segoe UI|8.0|0|WINDOWS|1|-12|0|0|0|400|0|0|0|1|0|0|0|0|Segoe UI</fontdata>
            </elementGraphicsContribution>
          </toolspecific>
        </name>
        <graphics>
          <position '.self::getNewPlacePosition().'/>
          <dimension x="25" y="25"/>
          <fill color="RGB {255, 255, 0}" gradient-color="RGB {255, 255, 255}" gradient-rotation="vertical"/>
          <line color="RGB {0, 0, 0}" width="1" style="solid"/>
        </graphics>
      </place>
      ';
      
      return $res;

	}
	
	public static function createPNMLRole($label){
		$res = '';
		
		$res .= '
		        <imageLabel typeId="edu.karlsruhe.horus.editors.roles.imageLabel.role" isVisible="true" isTextVisible="true">
              <text>'.self::xmlspecialchars($label).'</text>
              <graphics>
                <offset x="0" y="40"/>
                <line color="RGB {0, 0, 0}"/>
                <font family="Arial" style="0" size="7" align="center"/>
              </graphics>
              <toolspecific tool="Horus" version="1.0.0">
                <elementGraphicsContribution index="'.self::$elementID++.'">
                  <dimension x="-1"/>
                  <fontdata>1|Arial|7.0|0|WINDOWS|1|-12|0|0|0|400|0|0|0|1|0|0|0|0|Arial</fontdata>
                </elementGraphicsContribution>
              </toolspecific>
            </imageLabel>
            ';
		
		return $res;
	}
	
	public static function createPNMLResource($label){
		$res = '';
		
		$res .= '
		        <imageLabel typeId="biz.horus.editors.shm.horus.imageLabel.resource" isVisible="true" isTextVisible="true">
              <text>'.self::xmlspecialchars($label).'</text>
              <graphics>
                <offset x="0" y="-40"/>
                <line color="RGB {128, 128, 128}"/>
                <font family="Arial" style="0" size="7" align="center"/>
              </graphics>
              <toolspecific tool="Horus" version="1.0.0">
                <elementGraphicsContribution index="'.self::$elementID++.'">
                  <dimension x="-1"/>
                  <fontdata>1|Arial|7.0|0|WINDOWS|1|-12|0|0|0|400|0|0|0|1|0|0|0|0|Arial</fontdata>
                </elementGraphicsContribution>
              </toolspecific>
            </imageLabel>
            ';
		return $res;
	}
	
	public static function createPNMLTransition($id, $label, $or_activity="noOrActivity", $fixedBackground = null, $roles=null, $resources=null){
   
	    $inputOR = "false";
      $outputOR = "false";
      $inAndOutputOR = "false";
      $background = "192, 192, 192";
      
      
      switch($or_activity){
        case 'inputOR':
          $inputOR = "true";
          break;
        case 'outputOR':
          $outputOR = "true";
          $background = "0, 0, 255";
          break;
        case 'inAndOutputOR':
          $inAndOutputOR = "true";
          break;
        case 'parallelOR':
          $background = "61, 239, 16";
          break;
        case 'noOrActivity':
        default:
        break;
      }
      if(isset($fixedBackground)){
        $background = $fixedBackground;
      }


	  $res ='
      <transition id="'.self::xmlspecialchars($id).'">
        <toolspecific tool="Horus" version="1.0.0">
          <elementGraphicsContribution alpha="255" hasGradient="false" index="'.self::$elementID++.'">
            '.$roles.'
            '.$resources.'
          </elementGraphicsContribution>
          <transition>
            <viewName>
              <text>'.self::xmlspecialchars($label).'</text>
              <graphics>
                <offset x="0" y="27"/>
                <line color="RGB {0, 0, 0}"/>
                <font family="Segoe UI" style="0" size="8" align="center"/>
              </graphics>
              <toolspecific tool="Horus" version="1.0.0">
                <elementGraphicsContribution index="'.self::$elementID++.'">
                  <dimension x="-1"/>
                  <fontdata>1|Segoe UI|8.0|0|WINDOWS|1|-12|0|0|0|400|0|0|0|1|0|0|0|0|Segoe UI</fontdata>
                </elementGraphicsContribution>
              </toolspecific>
            </viewName>
            <description>
              <graphics>
                <offset x="'.rand(0,100).'" y="'.rand(-100,0).'"/>
                <line color="RGB {128, 128, 128}"/>
                <font family="Segoe UI" style="0" size="7" align="left"/>
              </graphics>
              <toolspecific tool="Horus" version="1.0.0">
                <elementGraphicsContribution index="'.self::$elementID++.'">
                  <dimension x="-1"/>
                  <fontdata>1|Segoe UI|7.0|0|WINDOWS|1|-12|0|0|0|400|0|0|0|1|0|0|0|0|Segoe UI</fontdata>
                </elementGraphicsContribution>
              </toolspecific>
            </description>
            <notes>
              <graphics>
                <offset x="'.rand(0,100).'" y="'.rand(0,100).'"/>
                <line color="RGB {128, 128, 128}"/>
                <font family="Segoe UI" style="2" size="7" align="left"/>
              </graphics>
              <toolspecific tool="Horus" version="1.0.0">
                <elementGraphicsContribution index="'.self::$elementID++.'">
                  <dimension x="-1"/>
                  <fontdata>1|Segoe UI|7.0|2|WINDOWS|1|-12|0|0|0|0|1|0|0|1|0|0|0|0|Segoe UI</fontdata>
                </elementGraphicsContribution>
              </toolspecific>
            </notes>
            <visibility>
              <nameVisible>false</nameVisible>
              <viewNameVisible>true</viewNameVisible>
              <descriptionVisible>false</descriptionVisible>
              <notesVisible>false</notesVisible>
            </visibility>
            <webService>
              <activity>empty</activity>
            </webService>
          </transition>
          <customProperties>
            <customProperty>
              <name>edu.karlsruhe.horus.editors.petrinets.properties.inAndOutputOR</name>
              <type>Boolean</type>
              <value>'.$inAndOutputOR.'</value>
            </customProperty>
            <customProperty>
              <name>edu.karlsruhe.horus.editors.petrinets.properties.outputOR</name>
              <type>Boolean</type>
              <value>'.$outputOR.'</value>
            </customProperty>
            <customProperty>
              <name>edu.karlsruhe.horus.editors.petrinets.properties.inputOR</name>
              <type>Boolean</type>
              <value>'.$inputOR.'</value>
            </customProperty>
          </customProperties>
        </toolspecific>
        <name>
          <graphics>
            <offset x="'.rand(0,100).'" y="'.rand(-100,20).'"/>
            <line color="RGB {128, 128, 128}"/>
            <font family="Segoe UI" style="0" size="8" align="left"/>
          </graphics>
          <toolspecific tool="Horus" version="1.0.0">
            <elementGraphicsContribution index="'.self::$elementID++.'">
              <dimension x="-1"/>
              <fontdata>1|Segoe UI|8.0|0|WINDOWS|1|-12|0|0|0|400|0|0|0|1|0|0|0|0|Segoe UI</fontdata>
            </elementGraphicsContribution>
          </toolspecific>
        </name>
        <graphics>
          <position '.self::getNewTransPosition().'/>
          <dimension x="40" y="20"/>
          <fill color="RGB {'.$background.'}" gradient-color="RGB {255, 255, 255}" gradient-rotation="vertical"/>
          <line color="RGB {0, 0, 0}" width="1" style="solid"/>
        </graphics>
      </transition>
	  ';
	  return $res;
	}

	public static function createPNMLArc($source, $target){
	  $res ='
	  <arc id="arc'.self::$arcID++.'" source="'.self::xmlspecialchars($source).'" target="'.self::xmlspecialchars($target).'">
	    <toolspecific tool="Horus" version="1.0.0">
          <elementGraphicsContribution>
            <endArrowHead style="1" fill="true" xScaling="5" yScaling="3"/>
          </elementGraphicsContribution>
          <arc/>
        </toolspecific>

        <graphics>
          <line color="RGB {0, 0, 0}" width="1" style="solid"/>
        </graphics>
	  </arc>
	  ';
	  return $res;
	}

	public static function connectPNMLTransitions($source, $target){
	  	$source = self::xmlspecialchars($source);
	  	$target = self::xmlspecialchars($target);
	  	$BlankNodeID = SRFProcess::getBlankNodeID();
        $res = SRFProcess::createPNMLArc($source, $BlankNodeID);
        $res .= SRFProcess::createPNMLPlace($BlankNodeID, $BlankNodeID);
        $res .= SRFProcess::createPNMLArc($BlankNodeID, $target);
        return $res;
	}



	/**
	 *	This method renders the result set provided by SMW according to the printer
	 *
	 *  @param res				SMWQueryResult, result set of the ask query provided by SMW
	 *  @param outputmode		?
	 *  @returns				String, rendered HTML output of this printer for the ask-query
	 *
	 */
	protected function getResultText($res, $outputmode) {
		global $wgContLang; // content language object

		//
		//	GraphViz settings
		//

		$wgGraphVizSettings = new GraphVizSettings;
		$this->isHTML 		= true;


		//
		//	Iterate all rows in result set
		//

		$row = $res->getNext(); // get initial row (i.e. array of SMWResultArray)

		while ( $row !== false) {

			$node;
			$cond_edge;

			/*
			$subject = $row[0]->getResultSubject(); // get Subject of the Result
			// creates a new node if $val has type wikipage
			if ( $subject->getTypeID() == '_wpg' ) {
				$val = $subject->getShortWikiText();
				$node  = $this->m_process->makeNode($val, $val);
			}*/

     		//
			//	Iterate all colums of the row (which describe properties of the proces node)
			//

			foreach ($row as $field) {

				// check column title
				$req = $field->getPrintRequest();
				switch ((strtolower($req->getLabel()))) {


	 				case "": // First row with process step

	 					foreach ($field->getContent() as $value) {
							$val = $value->getShortWikiText();
	 						$node = $this->m_process->makeNode($val, $val);
						}

	 					break;


					case strtolower($wgContLang->getNsText(NS_CATEGORY)):

						foreach ($field->getContent() as $value) {
							$val = $value->getShortWikiText();
							if ($val == ($wgContLang->getNsText(NS_CATEGORY) . ':' . $this->m_processCategory)) $node->setAtomic(false);
						}

	 					break;

					case "hasrole":
						foreach ($field->getContent() as $value) {
							$val = $value->getShortWikiText();
							$role = $this->m_process->makeRole($val, $val);
							$node->addRole($role);
						}
						break;

					case "usesresource":
						foreach ($field->getContent() as $value) {
							$val = $value->getShortWikiText();
							$xres = $this->m_process->makeRessource($val, $val);
							$node->addUsedRessource($xres);
						}
						break;

					case "producesresource":
						foreach ($field->getContent() as $value) {
							$val = $value->getShortWikiText();
							$xres = $this->m_process->makeRessource($val, $val);
							$node->addProducedRessource($xres);
						}
						break;

					case "hassuccessor":

						if (count($field->getContent()) > 1){

							// SplitParallel
							$edge = new SplitParallelEdge();
							$edge->setFrom($node);
							foreach ($field->getContent() as $value) {
								$val = $value->getShortWikiText();
								$edge->addTo($this->m_process->makeNode($val, $val));
							}

						} else {

							// Sequence
							foreach ($field->getContent() as $value) {
								$val = $value->getShortWikiText();
								$edge = new SequentialEdge();
								$edge->setFrom($node);
								$edge->setTo($this->m_process->makeNode($val, $val));
							}
						}

						break;

					case "hasorsuccessor":

						if (count($field->getContent()) > 0){

							// SplitExclusiveOr
							$edge = new SplitExclusiveOrEdge();
							$edge->setFrom($node);
							foreach ($field->getContent() as $value) {
								$val = $value->getShortWikiText();
								$edge->addTo($this->m_process->makeNode($val, $val));
							}
						}

						break;

					case "hascontruesuccessor":

						if (count($field->getContent()) > 0){

							// SplitConditional
							if (!isset($cond_edge)){
								$cond_edge = new SplitConditionalOrEdge();
								$cond_edge->setFrom($node);
							}

							// should be only one
							foreach ($field->getContent() as $value) {
								$val = $value->getShortWikiText();
								$cond_edge->setToTrue($this->m_process->makeNode($val, $val));
							}

						}

						break;

					case "hasconfalsesuccessor":

						if (count($field->getContent()) > 0){

					 		// SplitConditional
							if (!isset($cond_edge)){
								$cond_edge = new SplitConditionalOrEdge();
								$cond_edge->setFrom($node);
							}

							// should be only one
							foreach ($field->getContent() as $value) {
								$val = $value->getShortWikiText();
								$cond_edge->setToFalse($this->m_process->makeNode($val, $val));
							}
						}

						break;

					case "hascondition":

						if (count($field->getContent()) > 0){

					 		// SplitConditional
							if (!isset($cond_edge)){
								$cond_edge = new SplitConditionalOrEdge();
								$cond_edge->setFrom($node);
							}

							// should be only one
							foreach ($field->getContent() as $value) {
								$val = $value->getShortWikiText();
								$cond_edge->setConditionText($val);

							}
						}

						break;

					case "hasstatus":

						// should be only one
						foreach ($field->getContent() as $value) {
							$val = $value->getShortWikiText();
							$node->setStatus($val);
						}

						break;

					default:

						// TODO - redundant column in result

	 			}
			}

			// reset row variables
			unset($node);
			unset($cond_edge);

		  	$row = $res->getNext();		// switch to next row
		}

		if ($outputmode == SMW_OUTPUT_FILE) { // make pnml file

		$result = $this->m_process->getPNMLCode();
		//$result = $this->m_process->getGraphVizCode();

		} else {

		//
		// generate graphInput
		//
		$graphInput = $this->m_process->getGraphVizCode();

		//
		// render graphViz code
		//
		$result = renderGraphviz($graphInput);

		/**
		* Link to generate outputfile
		*
		**/
		if ($this->getSearchLabel($outputmode)) {
				$label = $this->getSearchLabel($outputmode);
		} else {
				wfLoadExtensionMessages('SemanticResultFormats');
				$label = wfMsgForContent('srf_process_link');
		}
		$link = $res->getQueryLink($label);
		$link->setParameter('process','format');
		if ($this->getSearchLabel(SMW_OUTPUT_WIKI) != '') {
			$link->setParameter($this->getSearchLabel(SMW_OUTPUT_WIKI),'searchlabel');
		}
		if (array_key_exists('limit', $this->m_params)) {
				$link->setParameter($this->m_params['limit'],'limit');
		} else { // use a reasonable default limit
				$link->setParameter(20,'limit');
		}

		$result .= $link->getText(SMW_OUTPUT_HTML,$this->mLinker);
		}


		$debug = '';
		if ($this->m_isDebugSet) $debug = '<pre>' . $graphInput . '</pre>';

		return $result . $debug;


	}
}

/**
 * Class representing a process graph
 */
class ProcessGraph{

	// configuration variables
	protected $m_graphName 		= '';
	protected $m_rankdir		= 'TB';
	protected $m_graphSize		= '';
	protected $m_clusterColor	= 'lightgrey';
	protected $m_showStatus		= false;	// should status be rendered?
	protected $m_showRoles		= false;	// should roles be rendered?
	protected $m_showRessources	= false;	// should ressources be rendered?
	protected $m_highlightNode	= '';		// node to be highlighted
	protected $m_highlightColor = 'blue';	// highlight font color
	protected $m_showRedLinks	= false;	// check and highlight red links?
	protected $m_redLinkColor	= 'red';	// red link font color
	protected $m_showCompound	= true;		// highlight compound nodes (=subprocesses)

	// instance variables
	protected $m_nodes		= array();	// list of all nodes
	protected $m_startnodes	= array();	// list of start nodes
	protected $m_endnodes	= array();	// list of end nodes
	protected $m_ressources	= array();	// list of ressources
	protected $m_roles		= array();	// list of roles
	protected $m_errors		= array();	// list of errors


	/**
	 * This method should be used for getting new or existing nodes
	 * If a node does not exist yet, it will be created
	 *
	 * @param $id			string, node id
	 * @param $label		string, node label
 	 * @return				Object of type ProcessNode
	 */
	public function makeNode($id, $label){
		$node;

		// check if node exists
		if (isset($this->m_nodes[$id])){
			// take existing node
			$node = $this->m_nodes[$id];

		} else {
			// create new node

			$node = new ProcessNode();
			$node->setId($id);
			$node->setLabel($label);
			$node->setProcess($this);

			// is actual node name the same like the one to highlight?
			if (strcasecmp($id , $this->m_highlightNode) == 0){
				$node->setFontColor($this->m_highlightColor);
			}

			// is the node a red link (i.e. corresponding wiki page does not yet exist)?
			if ($this->m_showRedLinks){
				$title = new Title();
				$title = $title->newFromDBkey($id);
				if (isset($title) && (!$title->exists())) $node->setFontColor($this->m_redLinkColor);
			}

			// add new node to process
			$this->m_nodes[$id] = $node;
		}

		return $node;

	}

	public function makeRole($id, $label){
		$role;

		// check if role exists
		if (isset($this->m_roles[$id])){
			// take existing roles
			$role = $this->m_roles[$id];

		} else {
			$role = new ProcessRole();
			$role->setId($id);
			$role->setLabel($label);

			// add new role to process
			$this->m_roles[$id] = $role;
		}

		return $role;

	}

	public function makeRessource($id, $label){
		$res;

		// check if res exists
		if (isset($this->m_ressources[$id])){
			// take existing res
			$res = $this->m_ressources[$id];

		} else {
			$res = new ProcessRessource();
			$res->setId($id);
			$res->setLabel($label);

			// add new res to process
			$this->m_ressources[$id] = $res;

		}

		return $res;

	}

	public function getEndNodes(){
		if (count($this->m_endnodes) == 0){
			foreach($this->m_nodes as $node){
				if (count($node->getSucc()) == 0) $this->m_endnodes[] = $node;
			}
		}

		return $this->m_endnodes;
	}

	public function getStartNodes(){

		if (count($this->m_startnodes) == 0){
			foreach($this->m_nodes as $node){
				if (count($node->getPred()) == 0){
					$this->m_startnodes[] = $node;
				}
			}
		}

		return $this->m_startnodes;
	}

	public function setShowStatus($show){
		$this->m_showStatus = $show;
	}

	public function getShowStatus(){
		return $this->m_showStatus;
	}

	public function setShowRoles($show){
		$this->m_showRoles = $show;
	}

	public function getShowRoles(){
		return $this->m_showRoles;
	}

	public function setShowCompound($show){
		$this->m_showCompound = $show;
	}

	public function getShowCompound(){
		return $this->m_showCompound;
	}

	public function setShowRessources($show){
		$this->m_showRessources = $show;
	}

	public function getShowRessources(){
		return $this->m_showRessources;
	}

	public function setGraphName($name){
		$this->m_graphName = $name;
	}

	public function getGraphName(){
		if ($this->m_graphName == '') $this->m_graphName = 'ProcessQueryResult' . rand(1, 99999);
		return $this->m_graphName;
	}

	public function setGraphSize($size){
		$this->m_graphSize = $size;
	}

	public function setRankdir($rankdir){
		$this->m_rankdir = $rankdir;
	}

	public function setClusterColor($color){
		$this->m_clusterColor = $color;
	}

	public function setHighlightColor($color){
		$this->m_highlightColor = $color;
	}

	public function setRedLinkColor($color){
		$this->m_redLinkColor = $color;
	}

	public function setShowRedLinks($show){
		$this->m_showRedLinks = $show;
	}

	public function setHighlightNode($name){
		$this->m_highlightNode = $name;
	}

	public function addError($error){
		$this->m_errors[] = $error;
	}

	public function getErrors(){
		return $this->m_errors;
	}

    public function getPNMLCode() {

    	$connectionRouter = "0"; // How does Horus connect the Nodes? 0 -> Manually (Straight Arrows), 1 -> Manhatten (90° angles), 2 -> Shortest Path
        // header
		$res = '<?xml version="1.0" encoding="UTF-8"?>
<pnml>
  <net id="noID" type="http://www.lip6.fr/pnml/pntds/PNMLPTNet.pntd">
    <page id="page1">
      <toolspecific tool="Horus" version="1.0.0">
        <pageGraphics connectionRouter="'.$connectionRouter.'" gridVisibility="false" gridEnabled="true" gridSize="5" gridColor="RGB {192, 192, 192}" rulerVisibility="false" rulerUnits="1" snapToGeometry="true" zoom="1.0"/>
      </toolspecific>
      <graphics>
        <fill color="RGB {255, 255, 255}"/>
      </graphics>';

		//
		// add startnodes
		//
		// TODO I18N
		$res .= SRFProcess::createPNMLPlace("Start", "Start");

		//{rank=source; "Start";}
        //"Start"[shape=box,label="Start",style=filled,color=green]

		foreach ($this->getStartNodes() as $node){
		  $res .=SRFProcess::createPNMLArc("Start", $node->getId());
		}

		$res .= '
		';

				//
		// add subnodes
		//
		foreach($this->m_nodes as $node){
			$res .= $node->getPNMLCode();
		}
		
		
		//
		// add endnodes
		//
		// TODO I18N

		$res .= SRFProcess::createPNMLPlace("End", "End");

		foreach ($this->getEndNodes() as $node){
			$res .= SRFProcess::createPNMLArc($node->getId(),"End");
		}


		$res .= '

	';



		//
		// add final stuff
		//
		$res .='
    </page>
  </net>
</pnml>
	';


    	//return utf8_encode($res);
    	return $res;
    }



    /**
    * Create GraphViz Code
    **/
	public function getGraphVizCode(){
		//
		// header
		//
		$res = 'digraph ' . $this->getGraphName() .' {

	ranksep="0.5";';
		if ($this->m_graphSize != '') $res .='
	size="'. $this->m_graphSize . '";';
		$res .='
	rankdir=' . $this->m_rankdir . ';
	';

		//
		// add startnodes
		//
		// TODO I18N
		$res .='
	{rank=source; "Start";}
		"Start"[shape=box,label="Start",style=filled,color=green];';

		foreach ($this->getStartNodes() as $node){
			$res .= '
		"Start" -> "' . $node->getId() . '";';
		}

		$res .= '
		';

		//
		// add endnodes
		//
		// TODO I18N
		$res .= '
	{rank=sink; "End"; }
		"End"[shape=box,label="End",style=filled,color=green];';

		foreach ($this->getEndNodes() as $node){
			$res .= '
		"' . $node->getId() . '" -> "End";';
		}

		$res .= '

	';

		//
		// add subnodes
		//
		foreach($this->m_nodes as $node){
			$res .= $node->getGraphVizCode();
		}

		//
		// add final stuff
		//
		$res .=
	'
	}';

		return $res;

	}

}

abstract class ProcessElement{

	// TODO I18N
	private $m_id		 = 'no_id';
	private $m_label	 = 'unlabeled';

	public function getId(){
		return $this->m_id;
	}

	public function setId($id){
		$this->m_id = $id;
	}

	public function getLabel(){
		return $this->m_label;
	}

	public function setLabel($label){
		$this->m_label = $label;
	}

}

class ProcessRessource extends ProcessElement{

	private $m_usedby		= array();
	private	$m_producedby	= array();

	public function getProducers(){
		return $this->m_producedby;
	}

	public function getUsers(){
		return $this->m_usedby;
	}

	public function addProducer($node){
		$this->m_producedby[] = $node;
	}

	public function addUser($node){
		$this->m_usedby[] = $node;
	}

}

class ProcessRole extends ProcessElement{

	private $m_nodes	= array();

	public function getNodes(){
		return $this->m_nodes;
	}

	public function addNode($node){
		$this->m_nodes[] = $node;
	}

}

/**
 * Class reperesenting a process node
 */
class ProcessNode extends ProcessElement{
	private $m_is_startnode	= false;	// explicit statement if this is a start node
	private $m_is_endnode	= false;	// explicit statement if this is a termination node
	private $m_status;					// status value
	private $m_is_atomic	= true;		// set false if this is a compound node

	private $m_process;					// reference to parent process

	private $m_fontColor = '';			// font color to render

	private $m_usedressources 		= array();	// ressources used by this node
	private $m_producedressources 	= array();	// ressources produces by this node
	private $m_roles				= array();	// roles related to this node

	private $m_edgeout;					// outgoing edge (can be only one)
	private	$m_edgesin 	=	array();	// incoming edges (can be many)

	public function setStatus($status){
		$this->m_status = $status;
	}

	public function getStatus(){
		return $this->m_status;
	}

	public function setFontColor($color){
		$this->m_fontColor = $color;
	}

	public function setProcess($proc){
		$this->m_process =  $proc;
	}

	public function getProcess(){
		return $this->m_process;
	}

	public function getPred(){
		$res = array();

		foreach ($this->m_edgesin as $edge){
			$res = array_merge($res, $edge->getPred());
		}

		return $res;
	}

	public function getSucc(){
		$res = array();

		if (isset($this->m_edgeout)){
			$res = $this->m_edgeout->getSucc();
		}

		return $res;
	}

	public function setEdgeOut($edge){
		$this->m_edgeout = $edge;
	}

	public function getEdgeOut(){
		return $this->m_edgeout;
	}

	public function addEdgeIn($edge){
		$this->m_edgesin[] = $edge;
	}

	public function getEdgesIn(){
		return $this->m_edgesin;
	}

	public function addRole($role){
		$this->m_roles[] = $role;
		$role->addNode($this);
	}

	public function getRoles(){
		return $this->m_roles;
	}

	public function addUsedRessource($res){
		$this->m_usedressources[] = $res;
		$res->addUser($this);
	}

	public function getUsedRessources(){
		return $this->m_usedressources;
	}

	public function addProducedRessource($res){
		$this->m_producedressources[] = $res;
		$res->addProducer($this);
	}

	public function getProducedRessources(){
		return $this->m_producedressources;
	}

	public function isAtomic(){
		return $this->m_is_atomic;
	}

	public function setAtomic($atomic){
		$this->m_is_atomic = $atomic;
	}
	
  public function getPNMLCode() {
		// use highlight color if set (either CURRENTPAGE or REDLINK highlighting - see ProcessGraph::makeNode()
		$high = '';
		if( $this->m_fontColor != '') {
			$high = ',fontcolor=' . $this->m_fontColor;
		}

		// make double circle for non-atomic nodes (i.e. subprocesses)
		$compound = '';
		if ($this->getProcess()->getShowCompound() && !$this->isAtomic()) $compound = ',penwidth=2.0';

		// show roles
		//if ($this->getProcess()->getShowRoles()){ //TODO
		if (true){

			foreach ($this->getRoles() as $role){
				$roles .= $role->getLabel().'
';
     
			}
			if($roles != '') {$roles = SRFProcess::createPNMLRole($roles);}
		}
		
			
		//if ($this->getProcess()->getShowRessources()){ //TODO
    if (false){
      $resource = '';
			foreach ($this->getUsedRessources() as $xres){
				$resources = $xres->getLabel().'
';
			}
			
			foreach ($this->getProducedRessources() as $xres){
				$resources .= $xres->getLabel().'
';
				
			}
			if ($resources != ''){$resources = SRFProcess::createPNMLResource($resources);}
		}
		
		//
		// render node itself
		//
    
    $res = SRFProcess::createPNMLTransition($this->getID(), $this->getLabel(), null , null , $roles, $resources);

		//
		// render outgoing node
		//
		if (isset($this->m_edgeout)) $res .= $this->m_edgeout->getPNMLCode();
		
		$res .= '
	';

		return $res;
    }

	public function getGraphVizCode(){
        global $srfgPicturePath;
		//
		// show node status
		//
		$status = '';
		if ($this->getProcess()->getShowStatus()){
			//$color = 'grey' . $this->getStatus();
			//$color = 'grey' . rand(1, 100);
			//$status = ',style=filled,color=' . $color;
			if ($this->getStatus() != ''){
				if ($this->getStatus() < 25){
					$status = ',image="'. $srfgPicturePath .'p000.png"';
				} else if ($this->getStatus() < 50){
					$status = ',image="'. $srfgPicturePath .'p025.png"';
				} else if ($this->getStatus() < 75){
					$status = ',image="'. $srfgPicturePath .'p050.png"';
				} else if ($this->getStatus() < 100){
					$status = ',image="'. $srfgPicturePath .'p075.png"';
				} else if ($this->getStatus() == 100){
					$status = ',image="'. $srfgPicturePath .'p100.png"';
				}
			}

		}

		// use highlight color if set (either CURRENTPAGE or REDLINK highlighting - see ProcessGraph::makeNode()
		$high = '';
		if( $this->m_fontColor != '') {
			$high = ',fontcolor=' . $this->m_fontColor;
		}

		// make double circle for non-atomic nodes (i.e. subprocesses)
		$compound = '';
		if ($this->getProcess()->getShowCompound() && !$this->isAtomic()) $compound = ',penwidth=2.0';


		//
		// render node itself
		//
		$res =
	'"' . $this->getId() . '" [URL="[[' . $this->getId() . ']]",label="' . $this->getLabel() . '"' . $status . $high . $compound . '];
	';

		//
		// render outgoing node
		//
		if (isset($this->m_edgeout)) $res .= $this->m_edgeout->getGraphVizCode();


		//
		// show cluster for roles and ressources
		//
		$rrcluster = false;
		$rrcode = 'subgraph "cluster_role' . rand(1,9999) . '" { style=filled;color=lightgrey;';

		// show roles
		if ($this->getProcess()->getShowRoles()){

			foreach ($this->getRoles() as $role){
				$rrcluster = true;
				$rrcode .= '
				"' . $role->getId() . '"[label="' . $role->getLabel() . '",shape=doubleoctagon, color=red, URL="[[' . $role->getId() . ']]"];
				"' . $role->getId() . '" -> "' . $this->getId() . '" [color=red,arrowhead = none,constraint=false];
				';

			}
		}

		if ($this->getProcess()->getShowRessources()){

			foreach ($this->getUsedRessources() as $xres){
				$rrcluster = true;
				$rrcode .= '
			"' . $xres->getId() . '"[label="' . $xres->getLabel() . '",shape=folder, color=blue, URL="[[' . $xres->getId() . ']]"];
			"' . $xres->getId() . '" -> "' . $this->getId() . '" [color=blue,constraint=false];
				';
			}

			foreach ($this->getProducedRessources() as $xres){
				$rrcluster = true;
				$rrcode .= '
			"' . $xres->getId() . '"[label="' . $xres->getLabel() . '",shape=folder, color=blue, URL="[[' . $xres->getId() . ']]"];
			"' . $this->getId() . '" -> "' . $xres->getId() . '" [color=blue,constraint=false];
				';
				}

		}

		if ($rrcluster) $res .= $rrcode . '}';

		$res .= '
	';

		return $res;
	}




}

/**
 * Abstract base class for edges in a process graph
 */
abstract class ProcessEdge{

	private $m_id;

	public function getId(){
		if (!isset($this->m_id)){
			$this->m_id = 'edge' . rand(1, 99999);
		}

		return $this->m_id;
	}

	abstract public function getSucc();
	abstract public function getPred();

    abstract public function getPNMLCode();
	abstract public function getGraphVizCode();
}

abstract class SplitEdge extends ProcessEdge{

	protected $m_from;
	protected $m_to 	= array();

	public function setFrom($node){
		$this->m_from = $node;
		$node->setEdgeOut($this);
	}

	public function addTo($node){
		$this->m_to[] = $node;
		$node->addEdgeIn($this);
	}

	public function getPred(){
		return array($this->m_from);
	}

	public function getSucc(){
		return $this->m_to;
	}

}

class SplitConditionalOrEdge extends ProcessEdge{

	protected $m_from;
	protected $m_to_true;
	protected $m_to_false;
	protected $m_con_text = 'empty_condition';

	public function getSucc(){
		return array($this->m_to_false, $this->m_to_true);
	}

	public function getPred(){
		return array($this->m_from);
	}

	public function setFrom($node){
		$this->m_from = $node;
		$node->setEdgeOut($this);
	}

	public function setToFalse($node){
		$this->m_to_false = $node;
		$node->addEdgeIn($this);
	}

	public function setToTrue($node){
		$this->m_to_true = $node;
		$node->addEdgeIn($this);
	}

	public function setConditionText($cond){
		$this->m_con_text = $cond;
	}

    public function getPNMLCode(){

        $p = $this->m_from;

		if ((!isset($this->m_from)) || (!isset($this->m_to_false)) || (!isset($this->m_to_true))){

			echo "error with SplitConditionalOrEdge"; // TODO
			exit;
		}

		// cond-Shape
		$con = SRFProcess::getBlankNodeID();
		$res = SRFProcess::createPNMLTransition($con,
"Condition:
".$this->m_con_text, "outputOR", "128, 128, 255");
		$res .= SRFProcess::connectPNMLTransitions($p->getId(), $con);

		// True Succres
		$place = SRFProcess::getBlankNodeID();
		$res .= SRFProcess::createPNMLPlace($place, "True");
		$res .= SRFProcess::createPNMLArc($con, $place);
		$res .= SRFProcess::createPNMLArc($place, $this->m_to_true->getId());

		// False Succ
		$place = SRFProcess::getBlankNodeID();
		$res .= SRFProcess::createPNMLPlace($place, "False");
		$res .= SRFProcess::createPNMLArc($con, $place);
		$res .= SRFProcess::createPNMLArc($place, $this->m_to_false->getId());


		$res .= '
	}
	';

        return $res;
    }

	public function getGraphVizCode(){

		$p = $this->m_from;

		if ((!isset($this->m_from)) || (!isset($this->m_to_false)) || (!isset($this->m_to_true))){

			echo "error with SplitConditionalOrEdge"; // TODO
			exit;
		}


		$res =
	'subgraph "clus_' . $this->getId() . '" { ;
		';

		// cond-Shape
		$con = 'con' .  rand(1, 99999);
		$res .=
		'"'. $con . '"[shape=diamond,label="' . $this->m_con_text . '",style=filled,color=skyblue];
		"' . $p->getId() . '" -> "'. $con . '";
		';

		// True Succ
		$res .=
		'"' . $this->m_to_true->getId() . '" [URL = "[['. $this->m_to_true->getId() . ']]"];
		';

		$res .=
		'"'. $con .'" -> "' . $this->m_to_true->getId() .'" [label="true"];
		';

		// False Succ
		$res .=
		'"' . $this->m_to_false->getId() . '" [URL = "[['. $this->m_to_false->getId() . ']]"];
		';

		$res .=
		'"'. $con .'" -> "' . $this->m_to_false->getId() .'" [label="false"];';


		$res .= '
	}
	';

		return $res;
	}

}

class SplitExclusiveOrEdge extends SplitEdge{

	public function getPNMLCode(){
        $p = $this->getPred();
		$p = $p[0];

     	// add OR-Shape
		$orx = SRFProcess::getBlankNodeID();
		$res =	SRFProcess::createPNMLTransition($orx, "XOR", "outputOR");
		$res .= SRFProcess::connectPNMLTransitions($p->getId(),$orx);

		foreach ($this->getSucc() as $s){
			$res .= SRFProcess::connectPNMLTransitions($orx, $s->getId());
		}

		$res .= '
	}
	';

      return $res;
    }

	public function getGraphVizCode(){

		$p = $this->getPred();
		$p = $p[0];

		$res =
	'subgraph "clus_' . $this->getId() . '" { ;
		';

		// add OR-Shape
		$orx = 'or' .  rand(1, 99999);
		$res .=
		'"'. $orx . '"[shape=box,label="+",style=filled,color=gold];
		"' . $p->getId() . '" -> "'. $orx . '";
		';

		foreach ($this->getSucc() as $s){
			$res .=
		'"' . $s->getId() . '" [URL="[['. $s->getId() . ']]"];
		';

			$res .=
		'"'. $orx .'" -> "' . $s->getId() .'";
		';
		}

		$res .= '
	}
	';

		return $res;
	}

}

class SplitParallelEdge extends SplitEdge{

    public function getPNMLCode(){

        $p = $this->getPred();
		$p = $p[0];

		// add AND-Shape
		$and = SRFProcess::getBlankNodeID();
		$res =	SRFProcess::createPNMLTransition($and, "AND", "parallelOR");
		$res .= SRFProcess::connectPNMLTransitions($p->getId(),$and);

		foreach ($this->getSucc() as $s){
          $res .= SRFProcess::connectPNMLTransitions($and, $s->getId());
		}


        return $res;
    }

	public function getGraphVizCode(){

		$p = $this->getPred();
		$p = $p[0];

		$res =
	'subgraph "clus_' . $this->getId() . '" { ;
		';

		// add AND-Shape
		$and = 'and' .  rand(1, 99999);
		$res .=
		'"'. $and . '"[shape=box,label="||",style=filled,color=palegreen];
		"' . $p->getId() . '" -> "'. $and . '";
		';

		foreach ($this->getSucc() as $s){
			$res .=
		'"' . $s->getId() . '" [URL = "[['. $s->getId() . ']]"];
		';

			$res .=
		'"'. $and .'" -> "' . $s->getId() .'";
		';
		}

		$res .= '
	}
	';

		return $res;
	}

}

class SequentialEdge extends ProcessEdge{

	private $m_from;
	private $m_to;

	public function setFrom($node){
		$this->m_from = $node;
		$node->setEdgeOut($this);
	}

	public function setTo($node){
		$this->m_to = $node;
		$node->addEdgeIn($this);
	}

	public function getPred(){
		return array($this->m_from);
	}

	public function getSucc(){
		return array($this->m_to);
	}

	public function getPNMLCode(){
	    $res = SRFProcess::connectPNMLTransitions($this->m_from->getId(), $this->m_to->getId());
        return $res;
    }

	public function getGraphVizCode(){

		$p = $this->m_from;
		$s = $this->m_to;

		$res =
	'subgraph "clus_' . $this->getId() . '" { ;
		';

		$res .=
		'"' . $s->getId() . '" [URL = "[['. $s->getId() . ']]"];
		';

		$res .=
		'"'. $p->getId() .'" -> "' . $s->getId() .'";';

		$res .= '
	}
	';

		return $res;
	}

}