update linkmgmt.php for 0.20.x
authorgithub138 <m.ehinger@ltur.de>
Tue, 29 Jan 2013 13:39:29 +0000 (14:39 +0100)
committergithub138 <m.ehinger@ltur.de>
Tue, 29 Jan 2013 13:39:29 +0000 (14:39 +0100)
linkmgmt.php

index deaa4ad9b7d67e7532c99d5a4c03917e876d2c36..7adfe66e60f62968d1fa11f3510416af99994896 100644 (file)
@@ -1,59 +1,88 @@
 <?php
 /*
- * Link Management
+ * Link Management for RT 0.20.x
  *
- *  Displays links between Ports.
- *  Allows you to create/delete front and backend links between ports
- *     Link object backend ports by name (e.g. handy for patch panels)
- *  Change CableIDs
- *  Change Port Reservation Comment
+ *     Features:
+ *             - create links between ports
+ *             - create backend links between ports
+ *             - visually display links / chains
+ *                      e.g.
+ *                     (Object)>[port] -- front --> [port]<(Object) == back == > (Object)>[port] -- front --> [port]<(Object)
+ *             - Link object backend ports by name (e.g. handy for patch panels)
+ *             - change/create CableID (needs jquery.jeditable.mini.js)
+ *             - change/create Port Reservation Comment (needs jquery.jeditable.mini.js)
+ *             - multiple backend links for supported port types (e.g. AC-in, DC)
+ *             - GraphViz Maps (Objects, Ports and Links) (needs GraphViz_Image 1.3.0)
+ *                     - object,port or link  highligthing (just click on it)
+ *                     - context menu to link and unlink ports
  *
- *  
- * e.g.
- *     (Object)>[port] -- front --> [port]<(Object) == back == > (Object)>[port] -- front --> [port]<(Object)
+ *     Usage:
+ *             1. select "Link Management" tab
+ *             2. you should see link chains of all linked ports
+ *             3. to display all ports hit "Show All Ports" in the left upper corner
+ *             4. to link all ports with the same name of two different objects use "Link Object Ports by Name"
+ *                     a. select the other object you want to backend link to
+ *                     b. "show back ports" gives you the list of possible backend links
+ *                             !! Important port names have to be the same on both objects !!
+ *                             e.g. (Current Object):Portname -?-> Portname:(Selected Object)
+ *                     c. select all backend link to create (Ctrl + a for all)
+ *                     d. Enter backend CableID for all selected links
+ *                     e. "Link back" create the backend links
+ *             5. If you have an backend link within the same Object the link isn't shown until
+ *                "Expand Backend Links on same Object" is hit
+ *             6. "Object Map" displays Graphviz Map of current object
+ *             7. To get a Graphviz Map of a single port click the port name on the left
  *
  *
+ * Requirements:
+ *     PHP 5 (http://php.net/)
+ *     GraphViz_Image 1.3.0 or newer (http://pear.php.net/package/Image_GraphViz)
+ *             GraphViz (http://www.graphviz.org/)
  *
  * INSTALL:
  *
- *     - create LinkBackend Table
+ *     1. create LinkBackend Table in your RackTables database
+ *
+ * Multilink table
 
 CREATE TABLE `LinkBackend` (
   `porta` int(10) unsigned NOT NULL DEFAULT '0',
   `portb` int(10) unsigned NOT NULL DEFAULT '0',
   `cable` char(64) DEFAULT NULL,
   PRIMARY KEY (`porta`,`portb`),
-  UNIQUE KEY `porta` (`porta`),
-  UNIQUE KEY `portb` (`portb`),
-  CONSTRAINT `LinkBackend-FK-a` FOREIGN KEY (`porta`) REFERENCES `Port` (`id`) ON DELETE CASCADE,
-  CONSTRAINT `LinkBackend-FK-b` FOREIGN KEY (`portb`) REFERENCES `Port` (`id`) ON DELETE CASCADE
+  KEY `LinkBackend_FK_b` (`portb`),
+  CONSTRAINT `LinkBackend_FK_a` FOREIGN KEY (`porta`) REFERENCES `Port` (`id`) ON DELETE CASCADE,
+  CONSTRAINT `LinkBackend_FK_b` FOREIGN KEY (`portb`) REFERENCES `Port` (`id`) ON DELETE CASCADE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
- *     - copy linkmgmt.php to inc/ directory
- *     - copy jquery.jeditable.mini.js to js/ directory
- *     - add "include 'inc/linkmgmt.php';" to inc/local.php
+ *     2. copy jquery.jeditable.mini.js to js/ directory (http://www.appelsiini.net/download/jquery.jeditable.mini.js)
+ *     3. copy linkmgmt.php to plugins directory
+ *
+ *      Ready to go!
+ *
+ *
+ * UPDATE TABLE:
+ *
+ * Update from non-multilink table
+ * ALTER TABLE
+
+ALTER TABLE LinkBackend ADD KEY `LinkBackend_FK_b` (`portb`);
+ALTER TABLE LinkBackend DROP INDEX porta;
+ALTER TABLE LinkBackend DROP INDEX portb;
+
+ *
  *
  * TESTED on FreeBSD 9.0, nginx/1.0.11, php 5.3.9
- *     and RackTables 0.19.11
+ *     GraphViz_Image 1.3.0
+ *     and RackTables <= 0.20.3
  *
- * (c)2012 Maik Ehinger <m.ehinger@ltur.de>
+ * (c)2012,2013 Maik Ehinger <m.ehinger@ltur.de>
  */
 
-/*************************
- * Change Log
- * 
- * 15.01.12    new loopdetection
- * 18.01.12    code cleanups
- * 23.01.12    add href to printport
- * 24.01.12    add opHelp
- * 25.01.12    max loop count handling changed
- *             add port label to port tooltip
- * 28.02.12    changed printportlistrow() first from TRUE to FALSE
- *             add portlist::hasbackend()
- * 29.02.12    fix update cable id for backend links
- *                     add linkmgmt_commitUpdatePortLink
- * 04.03.12    add set_reserve_comment and set_link permission handling
+/**
+ * The newest version of this plugin can be found at:
  *
+ * https://github.com/github138/myRT-contribs/tree/develop-0.20.x
  *
  */
 
@@ -63,13 +92,21 @@ CREATE TABLE `LinkBackend` (
  * - code cleanups
  * - bug fixing
  *
- * - csv list
+ * - fix loopdectect for multiport
+ *     MAX_LOOP_COUNT
+ *     loop highlight gv map
  *
- * - fix $opspec_list for unlink
+ * - fix column alignment with multilinks
+ *
+ * - put selected object/port top left of graph
+ * - multlink count for Graphviz maps empty or full dot
+ *
+ * - csv list
  *
  */
 
-require_once 'inc/popup.php';
+/* DEBUG */
+//error_reporting(E_ALL);
 
 $tab['object']['linkmgmt'] = 'Link Management';
 $tabhandler['object']['linkmgmt'] = 'linkmgmt_tabhandler';
@@ -80,6 +117,20 @@ $ophandler['object']['linkmgmt']['unlinkPort'] = 'linkmgmt_opunlinkPort';
 $ophandler['object']['linkmgmt']['PortLinkDialog'] = 'linkmgmt_opPortLinkDialog';
 $ophandler['object']['linkmgmt']['Help'] = 'linkmgmt_opHelp';
 
+$ophandler['object']['linkmgmt']['map'] = 'linkmgmt_opmap';
+$ajaxhandler['lm_mapinfo'] = 'linkmgmt_ajax_mapinfo';
+
+/* ------------------------------------------------- */
+
+Const MULTILINK = TRUE;
+
+/* -------------------------------------------------- */
+
+$lm_multilink_port_types = array(
+                               16, /* AC-in */
+                               //1322, /* AC-out */
+                               1399, /* DC */
+                               );
 
 /* -------------------------------------------------- */
 
@@ -117,20 +168,1124 @@ function linkmgmt_opHelp() {
 
 /* -------------------------------------------------- */
 
+function linkmgmt_ajax_mapinfo() {
+
+       require_once 'inc/interface.php'; /* renderCell */
+
+       $object_id = NULL;
+       $port_id = NULL;
+       $remote_id = NULL;
+       $linktype = NULL;
+
+       if(isset($_REQUEST['object_id']))
+               $object_id = $_REQUEST['object_id'];
+
+       if(isset($_REQUEST['port_id']))
+               $port_id = $_REQUEST['port_id'];
+
+       if(isset($_REQUEST['remote_id']))
+               $remote_id = $_REQUEST['remote_id'];
+
+       if(isset($_REQUEST['linktype']))
+               $linktype = $_REQUEST['linktype'];
+
+       $debug = NULL;
+       if(isset($_REQUEST['debug']))
+               $debug['value'] = $_REQUEST['debug'];
+
+       $info = array();
+
+       echo "<table style=\"font-size:12;\"><tr>";
+
+       if($port_id != NULL)
+       {
+               $port = new linkmgmt_RTport($port_id);
+
+               echo "<td>";
+               $port->printtable('both');
+               echo "</td>";
+
+               if($debug)
+                       $debug['port'] = &$port;
+
+               if($remote_id != NULL)
+               {
+
+                       $remote_port = new linkmgmt_RTport($remote_id);
+
+                       echo "<td><table align=\"center\">";
+
+                       // TODO cableid
+                       echo "<tr><td><pre>".($linktype == 'back' ? ' ===> ' : ' ---> ')."</pre></td></tr>";
+
+                       $port->printunlinktr($linktype, $remote_port);
+
+                       echo "</table></td>";
+
+
+                       echo "<td>";
+                       $remote_port->printtable('both');
+                       echo "</td>";
+
+                       if($debug)
+                               $debug['remote_port'] = &$remote_port;
+
+               }
+               else
+                       $port->printunlinktr();
+
+
+       }
+       echo "</tr><tr>";
+
+       echo "<td>";
+       $object = linkmgmt_RTport::printobjecttable($object_id);
+       echo "</td>";
+
+       if($debug)
+               $debug['object'] = &$object;
+
+       if($remote_id != NULL)
+       {
+
+               echo "<td></td>"; /* link */
+               echo "<td>";
+               $remote_object = linkmgmt_RTport::printobjecttable($remote_port->port['object_id']);
+               echo "</td>";
+
+               if($debug)
+                       $debug['remote_object'] = &$remote_object;
+       }
+
+       echo "</tr></table>";
+
+       if($debug)
+       {
+               echo "<pre>--- Debug ---";
+               var_dump($debug);
+               echo "</pre>";
+       }
+
+       exit;
+}
+
+/* -------------------------------------- */
+
+class linkmgmt_RTport {
+
+       private $port_id = NULL;
+
+       public $port = false;
+
+       function __construct($port_id) {
+
+               $this->port = getPortInfo($port_id);
+
+               if($this->port === false)
+                       return;
+
+               /* successfully get port info */
+               $this->port_id = $port_id;
+
+       } /* __construct */
+
+       function isvalid() {
+               return ($port_id !== NULL);
+       }
+
+       function getlinks($type = 'front') {
+       } /* getlinks */
+
+       function printtable($linktype = 'front') {
+
+               if($this->port_id == NULL)
+                       return;
+
+               echo "<table>";
+
+               $urlparams = array(
+                                       'module' => 'redirect',
+                                       'page' => 'object',
+                                       'tab' => 'linkmgmt',
+                                       'op' => 'map',
+                                       'object_id' => $this->port['object_id'],
+                                       'port_id' => $this->port_id,
+                                       'usemap' => 1,
+                               );
+
+               echo '<tr><td><a title="don\'t highlight port" href="?'.http_build_query($urlparams).'">-phl</a></td>';
+
+               $urlparams['hl'] = 'p';
+               echo '<td><a title="highlight port" href="?'.http_build_query($urlparams).'">+phl</a></td></tr>';
+
+               $this->_printinforow($this->port,
+                                       array(
+                                               'id' => 'Port ID',
+                                               'name' => 'Port Name',
+                                               'oif_name' => 'Port Type',
+                                               'l2address' => 'MAC',
+                                               'reservation_comment' => 'comment',
+                                       )
+               ); /* printinforow */
+
+               $this->printlinktr($linktype);
+
+               echo "</table>";
+       } /* printtable */
+
+       function printlinktr($linktype = 'front') {
+               if($this->port_id === NULL)
+                       return;
+
+                $urlparams = array(
+                               'tab' => 'linkmgmt',
+                               'page' => 'object',
+                                'op'=>'PortLinkDialog',
+                                'port'=>$this->port_id,
+                                'object_id'=>$this->port['object_id'],
+                               'linktype' => $linktype,
+                               );
+
+               echo "<tr><td align=\"center\"><a href='".
+                                makeHrefProcess($urlparams).
+                        "'>";
+                        printImageHREF ('plug', 'Link this port');
+                        echo "</a></td></tr>";
+       } /* link */
+
+       function printunlinktr($linktype = 'front', $remote_port = NULL) {
+               if($this->port_id === NULL)
+                       return;
+
+               $urlparams = array(
+                                       'tab' => 'linkmgmt',
+                                        'op'=>'unlinkPort',
+                                        'port_id'=>$this->port_id,
+                                        'object_id'=>$this->port['object_id'],
+                                       'linktype' => $linktype,
+                               );
+
+               $confirmmsg = "unlink port ".$this->port['name'];
+
+               if($remote_port !== NULL)
+               {
+                       $urlparams['remote_id'] = $remote_port->port['id'];
+                       $confirmmsg .= ' -> '.$remote_port->port['name'];
+               }
+
+               $confirmmsg .= " ($linktype)"; // TODO cableid
+
+               echo "<tr><td align=\"center\"><a href='".makeHrefProcess($urlparams).
+               "' onclick=\"return confirm('$confirmmsg');\">";
+               printImageHREF ('cut', 'Unlink this port');
+               echo "</a></td></tr>";
+
+       } /* unlink */
+
+       /* TODO move to object class */
+       static function printobjecttable($object_id = NULL) {
+
+               if($object_id === NULL)
+                       return;
+
+               $object = spotEntity ('object', $object_id);
+
+               if($object === false)
+                       return;
+
+               if($object['rack_id'])
+               {
+                       $rack = spotEntity('rack', $object['rack_id']);
+
+                       $object['row_name'] = $rack['row_name'];
+                       $object['rack_name'] = $rack['name'];
+               }
+
+               echo "<table><tr><td>";
+               renderCell($object);
+               echo "</td></tr><tr><td><table>";
+
+               self::_printinforow($object,
+                               array(
+                                       'id' => 'ID',
+                                       'dname' => 'Name',
+                                       'label' => 'Label',
+                                       'rack_name' => 'Rack',
+                                       'row_name' => 'Row',
+                               )
+
+               ); /* printinforow */
+
+               $urlparams = array(
+                                       'module' => 'redirect',
+                                       'page' => 'object',
+                                       'tab' => 'linkmgmt',
+                                       'op' => 'map',
+                                       'object_id' => $object_id,
+                                       'usemap' => 1,
+                               );
+
+               echo '<tr><td><a title="don\'t highlight object" href="?'.http_build_query($urlparams).'">-ohl</a></td>';
+
+               $urlparams['hl'] = 'o';
+               echo '<td><a title="highlight object" href="?'.http_build_query($urlparams).'">+ohl</a></td></tr>';
+
+               echo "</table></td></tr></table>";
+
+               return $object;
+
+       } /* printobjecttable */
+
+       static function _printinforow(&$data, $config) {
+
+               foreach($config as $key => $name)
+               {
+                       if(isset($data[$key]))
+                       {
+                               $value = $data[$key];
+                               if(!empty($value))
+                                       echo "<tr><td align=\"right\" nowrap=\"nowrap\" style=\"font-size:10;\">$name:</td><td nowrap=\"nowrap\">$value</td></tr>";
+                       }
+               }
+
+       } /* _printinforow */
+} /* class RTport */
+
+/* -------------------------------------------------- */
+
+function linkmgmt_opmap() {
+
+       /* display require errors  "white screen of death" */
+       $errorlevel = error_reporting();
+       error_reporting(E_ALL);
+
+       require_once 'Image/GraphViz.php';
+
+       error_reporting($errorlevel);
+
+       $object_id = NULL;
+       $port_id = NULL;
+       $remote_id = NULL;
+       $allports = false;
+       $usemap = false;
+       $command = NULL;
+
+       /* highlight object */
+       $hl = NULL;
+       if(isset($_REQUEST['hl']))
+       {
+               $hl = $_REQUEST['hl'];
+               unset($_REQUEST['hl_object_id']);
+               unset($_REQUEST['hl_port_id']);
+
+               if($hl == 'o')
+               {
+                       unset($_GET['port_id']);
+                       unset($_GET['remote_id']);
+               }
+
+       }
+
+       if(!$hl && isset($_REQUEST['hl_object_id']))
+       {
+               $hl = 'o';
+               $object_id = $_REQUEST['hl_object_id'];
+               unset($_REQUEST['object_id']);
+               unset($_REQUEST['hl_port_id']);
+               unset($_REQUEST['port_id']);
+       }
+
+       if(isset($_REQUEST['object_id']))
+               $object_id = $_REQUEST['object_id'];
+
+       if(isset($_REQUEST['type']))
+       {
+               $type = $_REQUEST['type'];
+       }
+       else
+               $type = 'gif';
+
+       /* highlight port */
+       if(!$hl && isset($_REQUEST['hl_port_id']))
+       {
+               $hl = 'p';
+               $port_id = $_REQUEST['hl_port_id'];
+               unset($_REQUEST['port_id']);
+       }
+
+       if(isset($_REQUEST['allports']))
+       {
+               $allports = $_REQUEST['allports'];
+       }
+
+       if(isset($_REQUEST['port_id']))
+       {
+               $port_id = $_REQUEST['port_id'];
+       }
+
+       if(isset($_REQUEST['usemap']))
+               $usemap = $_REQUEST['usemap'];
+
+       if($hl == 'p' && $port_id === NULL)
+       {
+               unset($_GET['hl']);
+               unset($_GET['port_id']);
+               unset($_GET['remote_id']);
+       }
+
+       if($hl == 'o')
+               unset($_GET['remote_id']);
+
+       if(isset($_REQUEST['remote_id']))
+               $remote_id = $_REQUEST['remote_id'];
+
+       /* show all objects */
+       if(isset($_REQUEST['all']))
+       {
+               $object_id = NULL;
+               $port_id = NULL;
+               $hl = NULL;
+               unset($_GET['hl']);
+       }
+
+       if(isset($_REQUEST['cmd']))
+               $command = $_REQUEST['cmd'];
+
+       if(isset($_REQUEST['debug']))
+               $debug = $_REQUEST['debug'];
+       else
+               $debug = False;
+
+       if($debug) echo "-- DEBUG --<br>";
+
+       $gvmap = new linkmgmt_gvmap($object_id, $port_id, $allports, $hl, $remote_id);
+
+       if($debug) echo "-- after gvmap --<br>";
+
+       switch($type) {
+               case 'gif':
+               case 'png':
+               case 'bmp':
+               case 'jpeg':
+               case 'tif':
+               case 'wbmp':
+                       $ctype = "image/$type";
+                       break;
+               case 'jpg':
+                       $ctype = "image/jpeg";
+                       break;
+               case 'svg':
+                       $ctype = 'image/svg+xml';
+                       break;
+               case 'pdf':
+                       $ctype = 'application/pdf';
+                       break;
+               case 'cmapx':
+                       $ctype = 'text/plain';
+                       break;
+
+       }
+
+       if($usemap)
+       {
+
+               if($debug) echo "-- usemap --<br>";
+
+               /* add context menu to Ports, Objects, Links, ...
+                */
+
+               echo "<script>
+                       function initcontextmenu() {
+                               var maps = document.getElementsByTagName('map');
+                                for(var i=0;i<maps.length;i++) {
+                                       var areas = maps[i].childNodes;
+
+                                       for(j=0;j<areas.length;j++) {
+                                               if(areas[j].nodeType == 1)
+                                               {
+                                               //      console.log(areas[j].id);
+                                               //      attr = document.createAttribute('onmouseover','ahh');
+                                               //      areas[j].setAttribute(attr);
+                                               //      areas[j].onmouseover = 'menu(this);';
+
+                                                       areas[j].addEventListener('contextmenu',menu,false);
+                                               //      areas[j].oncontextmenu = 'menu(this, event);';
+                                               //      console.log(areas[j].oncontextmenu);
+                                               }
+                                       }
+
+                                }
+
+                       };
+
+                       function menu(event) {
+
+                       //      console.log('Menu');
+
+                               if(!event)
+                                       event = window.event;
+
+                               var parent = event.target;
+
+                       //      console.log('--' + parent);
+
+                               var ids = parent.id.split('-');
+
+                               if(ids[0] == 'graph1')
+                                       return false;
+
+                               var object_id = ids[0];
+
+                               var url ='?module=ajax&ac=lm_mapinfo&object_id=' + object_id;
+
+                       //      links ='<li><a href=' + object_id + '>Object</a></li>';
+
+                               if(ids[1] != '')
+                               {
+                                       var port_id = ids[1];
+                                       url += '&port_id=' + port_id;
+                               //      links += '<li><a href=' + port_id + '>Port</a></li>';
+
+                                       if(ids[2] != '')
+                                       {
+                                               var remote_id = ids[2];
+
+                                               if(ids[3] != '')
+                                               {
+                                                       var linktype = ids[3];
+                                                       url += '&remote_id=' + remote_id + '&linktype=' + linktype;
+                                               //      links += '<li><a href=' + port_id + '_' + remote_id + '_' + linktype + '>Unlink</a></li>';
+                                               }
+                                       }
+
+                               }
+
+
+                               var xmlHttp = new XMLHttpRequest();
+                               xmlHttp.open('GET', url, false);
+                               xmlHttp.send(null);
+
+                               var infodiv = document.getElementById('info');
+                               infodiv.innerHTML = xmlHttp.responseText;
+
+               //              linkdiv = document.getElementById('link');
+               //              linkdiv.innerHTML = links;
+
+                               var menudiv = document.getElementById('menu');
+                               menudiv.style.position  = 'absolute';
+                               menudiv.style.top  = (event.clientY + document.body.scrollTop) + 'px';
+                               menudiv.style.left  = (event.clientX + document.body.scrollLeft) + 'px';
+                               menudiv.style.display  = '';
+
+                               return false;
+                       };
+
+                       function mousedown(event) {
+                               //      console.log('mouse down');
+
+                               if(!event)
+                                       event = window.event;
+
+                               if(event.button != 2)
+                                       return true;
+
+                               var menudiv = document.getElementById('menu');
+
+                               menudiv.style.display = 'none';
+
+                               return false;
+                       };
+
+                       </script>";
+
+               echo "<body oncontextmenu=\"return false\" onmousedown=\"mousedown(event);\" onload=\"initcontextmenu();\">";
+
+               echo "<div id=\"menu\" style=\"display:none; background-color:#ffff90\">
+                               <div id=\"info\"></div>
+                               <ul id=\"link\" style=\"list-style-type:none\"></ul>
+                       </div>";
+
+               echo $gvmap->fetch('cmapx', $command);
+
+               if($debug) echo "-- after map gvmap --<br>";
+
+               echo "<img src=\"data:$ctype;base64,".
+                       base64_encode($gvmap->fetch($type, $command)).
+                       "\" usemap=#map$object_id />";
+
+               if($debug)
+               {
+                       echo "<pre>";
+                       echo $gvmap->export();
+                       echo "</pre>";
+
+                       echo "<pre>".$gvmap->parse()."</pre>";
+               }
+       }
+       else
+       {
+               $gvmap->image($type);
+       }
+
+       exit;
+
+} /* linkmgmt_opmap */
+
+/* ------------------------------------- */
+class linkmgmt_gvmap {
+
+       private $object_id = NULL;
+       private $port_id = NULL;
+       private $remote_id = NULL;
+
+       private $gv = NULL;
+
+       private $ports = array();
+
+       private $allports = false;
+       private $back = NULL;
+
+       private $alpa = 'ff';
+
+       private $errorlevel = NULL;
+
+       function __construct($object_id = NULL, $port_id = NULL, $allports = false, $hl = NULL, $remote_id = NULL) {
+               $this->allports = $allports;
+
+               $hl_object_id = NULL;
+               $hl_port_id = NULL;
+
+               $hllabel = "";
+
+               /* suppress strict standards warnings for Image_GraphViz and PHP 5.4.0
+                * output would corrupt image data
+                */
+               $this->errorlevel = error_reporting();
+               error_reporting($this->errorlevel & !E_STRICT);
+
+               $graphattr = array(
+                                       'rankdir' => 'RL',
+                               //      'ranksep' => '0',
+                                       'nodesep' => '0',
+                               //      'overlay' => false,
+                               );
+
+               unset($_GET['module']);
+
+               $_GET['all'] = 1;
+
+               $graphattr['URL'] = makeHrefProcess($_GET);
+
+               unset($_GET['all']);
+
+               $this->gv = new Image_GraphViz(true, $graphattr, "map".$object_id);
+
+               switch($hl)
+               {
+                       case 'p':
+                       case 'port':
+                               $hllabel = " (Port highlight)";
+                               $hl_object_id = $object_id;
+                               $hl_port_id = $port_id;
+                               $port_id = NULL;
+                               $this->alpha = '30';
+                               break;
+                       case 'o':
+                       case 'object':
+                               $hllabel = " (Object highlight)";
+                               $hl_object_id = $object_id;
+                               $object_id = NULL;
+                               $this->alpha = '30';
+                               break;
+
+               }
+
+               $this->object_id = $object_id;
+               $this->port_id = $port_id;
+               $this->remote_id = $remote_id;
+
+               if($object_id === NULL)
+               {
+                       unset($_GET['all']);
+                       $_GET['hl'] = 'o';
+
+                       $this->gv->addAttributes(array(
+                                               'label' => 'Showing all objects'.$hllabel,
+                                               'labelloc' => 't',
+                                               )
+                               );
+
+                       $objects = listCells('object');
+
+                       foreach($objects as $obj)
+                               $this->_add($this->gv, $obj['id'], NULL);
+               }
+               else
+               {
+                       $object = spotEntity ('object', $object_id);
+
+                       $this->gv->addAttributes(array(
+                                               'label' => "Graph for ${object['dname']}$hllabel",
+                                               'labelloc' => 't',
+                                               )
+                               );
+
+                       $this->_add($this->gv, $object_id, ($this->allports ? NULL : $port_id));
+
+                       $children = getEntityRelatives ('children', 'object', $object_id); //'entity_id'
+
+                       foreach($children as $child)
+                               $this->_add($this->gv, $child['entity_id'], NULL);
+               }
+
+               /* highlight object/port */
+               if($hl !== NULL)
+               {
+
+                       $this->alpha = 'ff';
+
+                       $this->ports = array();
+                       $this->back = NULL;
+
+                       $this->object_id = $hl_object_id;
+                       $this->port_id = $hl_port_id;
+
+                       $hlgv = new Image_GraphViz(true, $graphattr);
+
+                       $this->_add($hlgv, $hl_object_id , $hl_port_id);
+
+                       /* merge higlight graph */
+                       // edgedfrom - from - to - id
+                       foreach($hlgv->graph['edgesFrom'] as $from => $nodes) {
+                               foreach($nodes as $to => $ports) {
+                               // TODO ports id
+
+                               if(isset($this->gv->graph['edgesFrom'][$from][$to]))
+                                       $this->gv->graph['edgesFrom'][$from][$to] = $hlgv->graph['edgesFrom'][$from][$to];
+                               else
+                                       if(isset($this->gv->graph['edgesFrom'][$to][$from]))
+                                       {
+                                               unset($this->gv->graph['edgesFrom'][$to][$from]);
+                                               $this->gv->graph['edgesFrom'][$from][$to] = $hlgv->graph['edgesFrom'][$from][$to];
+                                       }
+                               }
+                       }
+                       // leads to duplicate edges from->to and to->from
+                       //$this->gv->graph['edgesFrom'] = $hlgv->graph['edgesFrom'] + $this->gv->graph['edgesFrom'];
+
+                       /* merge nodes */
+                       foreach($hlgv->graph['nodes'] as $cluster => $node)
+                       {
+                               $this->gv->graph['nodes'][$cluster] = $hlgv->graph['nodes'][$cluster] + $this->gv->graph['nodes'][$cluster];
+                       }
+
+                       $this->gv->graph['clusters'] = $hlgv->graph['clusters'] + $this->gv->graph['clusters'];
+                       $this->gv->graph['subgraphs'] = $hlgv->graph['subgraphs'] + $this->gv->graph['subgraphs'];
+
+               }
+
+       //      portlist::var_dump_html($this->gv);
+
+       //      echo $this->gv->parse();
+       //      error_reporting( E_ALL ^ E_NOTICE);
+        } /* __construct */
+
+       function __destruct() {
+               error_reporting($this->errorlevel);
+       }
+
+       // !!!recursiv !!!
+       function _add($gv, $object_id, $port_id = NULL) {
+               global $lm_multilink_port_types;
+
+               /* used only for Graphviz ...
+                * !! numeric ids cause Image_Graphviz problems on nested clusters !!
+                */
+               $cluster_id = "c$object_id";
+
+               if($port_id === NULL)
+               {
+                       if(
+                               isset($gv->graph['clusters'][$cluster_id]) ||
+                               isset($gv->graph['subgraphs'][$cluster_id])
+                       )
+                       return;
+               }
+               else
+               {
+                       if(isset($this->ports[$port_id]))
+                               return;
+               }
+
+               $object = spotEntity ('object', $object_id);
+       //      $object['attr'] = getAttrValues($object_id);
+
+               $clusterattr = array();
+
+               $this->_getcolor('cluster', 'default', $this->alpha, $clusterattr, 'color');
+               $this->_getcolor('cluster', 'default', $this->alpha, $clusterattr, 'fontcolor');
+
+               if($this->object_id == $object_id)
+               {
+                       $clusterattr['rank'] = 'source';
+
+                       $this->_getcolor('cluster', 'current', $this->alpha, $clusterattr, 'color');
+                       $this->_getcolor('cluster', 'current', $this->alpha, $clusterattr, 'fontcolor');
+               }
+
+               $clustertitle = "${object['dname']}";
+               $clusterattr['tooltip'] = $clustertitle;
+
+               unset($_GET['module']); // makeHrefProcess adds this
+               unset($_GET['port_id']);
+               unset($_GET['remote_id']);
+               $_GET['object_id'] = $object_id;
+               //$_GET['hl'] = 'o';
+
+               $clusterattr['URL'] = makeHrefProcess($_GET);
+
+               //has_problems
+               if($object['has_problems'] != 'no')
+               {
+                       $clusterattr['style'] = 'filled';
+                       $this->_getcolor('cluster', 'problem', $this->alpha, $clusterattr, 'fillcolor');
+               }
+
+               if(!empty($object['container_name']))
+                       $clustertitle .= "<BR/>${object['container_name']}";
+
+               if($object['rack_id'])
+               {
+                       $rack = spotEntity('rack', $object['rack_id']);
+
+                       if(!empty($rack['row_name']) || !empty($rack['name']))
+                               $clustertitle .= "<BR/>${rack['row_name']} / ${rack['name']}";
+               }
+
+               $embedin = $object['container_id'];
+               if(empty($embedin))
+                       $embedin = 'default';
+               else
+               {
+                       $embedin = "c$embedin"; /* see cluster_id */
+
+                       /* add container / cluster if not already exists */
+                       $this->_add($gv, $object['container_id'], NULL);
+               }
+
+               $clusterattr['id'] = "$object_id----"; /* used for js context menu */
+
+               $gv->addCluster($cluster_id, $clustertitle, $clusterattr, $embedin);
+
+               if($this->back != 'front' || $port_id === NULL || $this->allports)
+               $front = $this->_getObjectPortsAndLinks($object_id, 'front', $port_id, $this->allports);
+               else
+               $front = array();
+
+               if($this->back != 'back' || $port_id === NULL || $this->allports)
+               $backend = $this->_getObjectPortsAndLinks($object_id, 'back', $port_id, $this->allports);
+               else
+               $backend = array();
+
+               $ports = array_merge($front,$backend);
+
+               if(empty($ports))
+               {
+                       /* needed because of  gv_image empty cluster bug (invalid foreach argument) */
+                       $gv->addNode('dummy', array(
+                                       //      'label' =>'No Ports found/connected',
+                                               'label' =>'',
+                                               'fontsize' => 0,
+                                               'size' => 0,
+                                               'width' => 0,
+                                               'height' => 0,
+                                               'shape' => 'point',
+                                               'style' => 'invis',
+                                               ), $cluster_id);
+               }
+
+               foreach($ports as $key => $port) {
+
+                       $this->back = $port['linktype'];
+
+                       $nodelabel = "${port['name']}";
+
+                       if($port['iif_id'] != '1' )
+                               $nodelabel .= "<BR/><FONT POINT-SIZE=\"8\">${port['iif_name']}</FONT>";
+
+                       $nodelabel .= "<BR/><FONT POINT-SIZE=\"8\">${port['oif_name']}</FONT>";
+
+                       $nodeattr = array(
+                                               'label' => $nodelabel,
+                                       );
+
+                       $this->_getcolor('port', 'default',$this->alpha, $nodeattr, 'fontcolor');
+                       $this->_getcolor('oif_id', $port['oif_id'],$this->alpha, $nodeattr, 'color');
+
+                       if($this->port_id == $port['id']) {
+                               $nodeattr['style'] = 'filled';
+                               $nodeattr['fillcolor'] = $this->_getcolor('port', 'current', $this->alpha);
+                       }
+
+                       if($this->remote_id == $port['id']) {
+                               $nodeattr['style'] = 'filled';
+                               $nodeattr['fillcolor'] = $this->_getcolor('port', 'remote', $this->alpha);
+                       }
+
+                       $nodeattr['tooltip'] = "${port['name']}";
+
+                       unset($_GET['module']);
+                       unset($_GET['remote_id']);
+                       $_GET['object_id'] = $port['object_id'];
+                       $_GET['port_id'] = $port['id'];
+                       $_GET['hl'] = 'p';
+
+                       $nodeattr['URL'] = makeHrefProcess($_GET);
+                       $nodeattr['id'] = "${port['object_id']}-${port['id']}--"; /* for js context menu */
+
+                       $gv->addNode($port['id'],
+                                               $nodeattr,
+                                               "c${port['object_id']}"); /* see cluster_id */
+
+                       $this->ports[$port['id']] = true;
+
+                       if(!empty($port['remote_id'])) {
+
+                               //$this->_add($gv, $port['remote_object_id'], ($port_id === NULL ? NULL : $port['remote_id']));
+
+                               $this->_add($gv, $port['remote_object_id'], $port['remote_id']);
+
+                               if(
+                                       !isset($gv->graph['edgesFrom'][$port['id']][$port['remote_id']]) &&
+                                       !isset($gv->graph['edgesFrom'][$port['remote_id']][$port['id']])
+                               ) {
+
+                                       $linktype = $port['linktype'];
+
+                                       $edgetooltip = $port['object_name'].':'.$port['name'].
+                                                       ' - '.$port['cableid'].' -> '.
+                                                       $port['remote_name'].':'.$port['remote_object_name'];
+
+                                       $edgeattr = array(
+                                                       'fontsize' => 8,
+                                                       'label' => $port['cableid'],
+                                                       'tooltip' => $edgetooltip,
+                                                       'sametail' => $linktype,
+                                                       'samehead' => $linktype,
+                                               );
+
+                                       $this->_getcolor('edge', 'default', $this->alpha, $edgeattr, 'color');
+                                       $this->_getcolor('edge', 'default', $this->alpha, $edgeattr, 'fontcolor');
+
+                                       if($linktype == 'back' )
+                                       {
+                                               $edgeattr['style'] =  'dashed';
+                                               $edgeattr['arrowhead'] = 'none';
+                                               $edgeattr['arrowtail'] = 'none';
+
+                                               /* multilink ports */
+                                               if(in_array($port['oif_id'], $lm_multilink_port_types))
+                                               {
+                                                       $edgeattr['dir'] = 'both';
+                                                       $edgeattr['arrowtail'] = 'dot';
+                                               }
+
+                                               if(in_array($port['remote_oif_id'], $lm_multilink_port_types))
+                                               {
+                                                       $edgeattr['dir'] = 'both';
+                                                       $edgeattr['arrowhead'] = 'dot';
+                                               }
+                                       }
+
+                                       if(
+                                               ($port['id'] == $this->port_id && $port['remote_id'] == $this->remote_id) ||
+                                               ($port['id'] == $this->remote_id && $port['remote_id'] == $this->port_id)
+                                       )
+                                       {
+                                               $this->_getcolor('edge', 'highlight', 'ff', $edgeattr, 'color');
+                                               $edgeattr['penwidth'] = 2; /* bold */
+                                       }
+
+                                       unset($_GET['module']);
+                                       $_GET['object_id'] = $port['object_id'];
+                                       $_GET['port_id'] = $port['id'];
+                                       $_GET['remote_id'] = $port['remote_id'];
+
+                                       $edgeattr['URL'] = makeHrefProcess($_GET);
+
+                                       $edgeattr['id'] = $port['object_id']."-".$port['id']."-".$port['remote_id']."-".$linktype; /* for js context menu  */
+
+                                       $gv->addEdge(array($port['id'] => $port['remote_id']),
+                                                               $edgeattr,
+                                                               array(
+                                                                       $port['id'] => $linktype,
+                                                                       $port['remote_id'] => $linktype,
+                                                               )
+                                                       );
+                               }
+                       }
+
+               }
+
+       //      portlist::var_dump_html($port);
+       }
+
+       function fetch($type = 'png', $command = NULL) {
+               $ret = $this->gv->fetch($type, $command);
+               return $ret;
+       }
+
+       function image($type = 'png', $command = NULL) {
+               $this->gv->image($type, $command);
+       }
+
+       function parse() {
+               return $this->gv->parse();
+       }
+
+       /* should be compatible with getObjectPortsAndLinks from RT database.php */
+       function _getObjectPortsAndLinks($object_id, $linktype = 'front', $port_id = NULL, $allports = false) {
+
+               if($linktype == 'front')
+                       $linktable = 'Link';
+               else
+                       $linktable = 'LinkBackend';
+
+               $qparams = array();
+
+               $query = "SELECT
+                               '$linktype' as linktype,
+                               Port.*,
+                               Port.type AS oif_id,
+                               PortInnerInterface.iif_name as iif_name,
+                               Dictionary.dict_value as oif_name,
+                               Object.id as object_id, Object.name as object_name,
+                               LinkTable.cable as cableid,
+                               remoteObject.id as remote_object_id, remoteObject.name as remote_object_name,
+                               remotePort.id as remote_id, remotePort.name as remote_name,
+                               remotePort.type AS remote_oif_id,
+                               remotePortInnerInterface.iif_name as remote_iif_name,
+                               remoteDictionary.dict_value as remote_oif_name
+                       FROM Port";
+
+               // JOIN
+               $join = "       LEFT JOIN PortInnerInterface on PortInnerInterface.id = Port.iif_id
+                               LEFT JOIN Dictionary on Dictionary.dict_key = Port.type
+                               LEFT JOIN $linktable as LinkTable on Port.id in (LinkTable.porta, LinkTable.portb)
+                               LEFT JOIN Object on Object.id = Port.object_id
+                               LEFT JOIN Port as remotePort on remotePort.id = ((LinkTable.porta ^ LinkTable.portb) ^ Port.id)
+                               LEFT JOIN Object as remoteObject on remoteObject.id = remotePort.object_id
+                               LEFT JOIN PortInnerInterface as remotePortInnerInterface on remotePortInnerInterface.id = remotePort.iif_id
+                               LEFT JOIN Dictionary as remoteDictionary on remoteDictionary.dict_key = remotePort.type
+                       ";
+
+               // WHERE
+               if($port_id === NULL)
+               {
+                       $where = " WHERE Object.id = ?";
+                       $qparams[] = $object_id;
+
+                       if(!$allports)
+                               $where .= " AND remotePort.id is not NULL";
+               }
+               else
+               {
+               //      $where = " WHERE Port.id = ? and remotePort.id is not NULL";
+                       $where = " WHERE Port.id = ?";
+                       $qparams[] = $port_id;
+               }
+
+               // ORDER
+               $order = " ORDER by oif_name, Port.Name";
+
+               $query .= $join.$where.$order;
+
+               $result = usePreparedSelectBlade ($query, $qparams);
+
+               $row = $result->fetchAll(PDO::FETCH_ASSOC);
+
+               $result->closeCursor();
+
+               return $row;
+       }
+
+       function _getcolor($type = 'object', $key = 'default', $alpha = 'ff', &$array = NULL , $arraykey = 'color') {
+
+               $object = array(
+                               'current' => '#ff0000',
+                               );
+               $port = array(
+                               'current' => '#ffff90',
+                               'remote' => '#ffffD0',
+                               );
+
+               $cluster = array(
+                               'current' => '#ff0000',
+                               'problem' => '#ff3030',
+                               );
+
+               $edge = array (
+                               'highlight' => '#ff0000',
+                               );
+
+               $oif_id = array(
+                               '16' => '#800000', /* AC-in */
+                               '1322' => '#ff4500', /* AC-out */
+                               '24' => '#000080', /* 1000base-t */
+                               );
+
+               $defaultcolor = '#000000'; /* black */
+               $default = true;
+
+               if(isset(${$type}[$key]))
+               {
+                       $default = false;
+                       $color = ${$type}[$key];
+               }
+               else
+                       $color = $defaultcolor;
+
+
+               if($alpha != 'ff' || $default == false)
+               {
+                       $color .= $alpha;
+
+                       if($array !== NULL)
+                               $array[$arraykey] = $color;
+                       else
+                               return $color;
+               }
+               else
+                       return $defaultcolor;
+
+       } /* _getcolor */
+
+       function dump() {
+               var_dump($this->gv);
+       }
+
+       function export() {
+               var_export($this->gv);
+       }
+
+} /* class gvmap */
+
+/* -------------------------------------------------- */
+
 function linkmgmt_opupdate() {
-       
+
        if(!isset($_POST['id']))
                exit;
-       
+
        $ids = explode('_',$_POST['id'],3);
        $retval = strip_tags($_POST['value']);
 
        if(isset($ids[1])) {
                if(permitted(NULL, NULL, 'set_link'))
                        if(isset($ids[2]) && $ids[2] == 'back')
-                               linkmgmt_commitUpdatePortLink($ids[0], $retval, TRUE);
+                               linkmgmt_commitUpdatePortLink($ids[0], $ids[1], $retval, TRUE);
                        else
-                               linkmgmt_commitUpdatePortLink($ids[0], $retval);
+                               linkmgmt_commitUpdatePortLink($ids[0], $ids[1], $retval);
                else
                        $retval = "Permission denied!";
        } else {
@@ -139,17 +1294,17 @@ function linkmgmt_opupdate() {
                else
                        $retval = "Permission denied!";
        }
-               
+
        /* return what jeditable should display after edit */
        echo $retval;
 
-       exit;   
+       exit;
 } /* opupdate */
 
 /* -------------------------------------------------- */
 
 /* similar to commitUpatePortLink in database.php with backend support */
-function linkmgmt_commitUpdatePortLink($port_id, $cable = NULL, $backend = FALSE) {
+function linkmgmt_commitUpdatePortLink($port_id1, $port_id2, $cable = NULL, $backend = FALSE) {
 
        /* TODO check permissions */
 
@@ -158,13 +1313,15 @@ function linkmgmt_commitUpdatePortLink($port_id, $cable = NULL, $backend = FALSE
        else
                $table = 'Link';
 
-       return usePreparedUpdateBlade
+       return usePreparedExecuteBlade
                (
-                        $table,
-                        array ('cable' => mb_strlen ($cable) ? $cable : NULL),
-                        array ('porta' => $port_id, 'portb' => $port_id),
-                        'OR'
-                );
+                       "UPDATE $table SET cable=\"".(mb_strlen ($cable) ? $cable : NULL).
+                       "\" WHERE ( porta = ? and portb = ?) or (portb = ? and porta = ?)",
+                       array (
+                               $port_id1, $port_id2,
+                               $port_id1, $port_id2)
+               );
+
 } /* linkmgmt_commitUpdatePortLink */
 
 /* -------------------------------------------------- */
@@ -173,24 +1330,44 @@ function linkmgmt_opunlinkPort() {
        $port_id = $_REQUEST['port_id'];
        $linktype = $_REQUEST['linktype'];
 
+       portlist::var_dump_html($_REQUEST);
+
        /* check permissions */
        if(!permitted(NULL, NULL, 'set_link')) {
                exit;
        }
 
        if($linktype == 'back')
+       {
                $table = 'LinkBackend';
+               $remote_id = $_REQUEST['remote_id'];
+
+               $retval =  usePreparedExecuteBlade
+                       (
+                               "DELETE FROM $table WHERE ( porta = ? and portb = ?) or (portb = ? and porta = ?)",
+                               array (
+                                       $port_id, $remote_id,
+                                       $port_id, $remote_id)
+                       );
+       }
        else
+       {
                $table = 'Link';
 
-       $retval = usePreparedDeleteBlade ($table, array('porta' => $port_id, 'portb' => $port_id), 'OR');
-       
+               $retval = usePreparedDeleteBlade ($table, array('porta' => $port_id, 'portb' => $port_id), 'OR');
+       }
+
        if($retval == 0)
                echo " Link not found";
        else
                echo " $retval Links deleted";
 
-       header('Location: ?page='.$_REQUEST['page'].'&tab='.$_REQUEST['tab'].'&object_id='.$_REQUEST['object_id']);
+
+       unset($_GET['module']);
+       unset($_GET['op']);
+
+       header('Location: ?'.http_build_query($_GET));
+       //header('Location: ?page='.$_REQUEST['page'].'&tab='.$_REQUEST['tab'].'&object_id='.$_REQUEST['object_id']);
        exit;
 } /* opunlinkPort */
 
@@ -210,23 +1387,29 @@ function linkmgmt_oplinkPort() {
        if(!isset($_REQUEST['link_list'])) {
                //portlist::var_dump_html($_REQUEST);
                $porta = $_REQUEST['port'];
-               $portb = $_REQUEST['remote_port'];
 
-               $link_list[] = "${porta}_${portb}";
+               foreach($_REQUEST['remote_ports'] as $portb)
+               {
+                       $link_list[] = "${porta}_${portb}";
+
+                       /* with no MULTILINK process first value only */
+                       if(!MULTILINK)
+                               break;
+               }
        } else
                $link_list = $_REQUEST['link_list'];
 
        foreach($link_list as $link){
-       
+
                $ids = preg_split('/[^0-9]/',$link);
                $porta = $ids[0];;
                $portb = $ids[1];
 
                $ret = linkmgmt_linkPorts($porta, $portb, $linktype, $cable);
 
-               error_log("$ret - $porta - $portb");
-               $port_info = getPortInfo ($porta);
-               $remote_port_info = getPortInfo ($portb);
+               //error_log("$ret - $porta - $portb");
+               $port_info = getPortInfo ($porta);
+               $remote_port_info = getPortInfo ($portb);
                showSuccess(
                         sprintf
                         (
@@ -239,8 +1422,6 @@ function linkmgmt_oplinkPort() {
                 );
        }
 
-
-
        addJS (<<<END
 window.opener.location.reload(true);
 window.close();
@@ -261,23 +1442,35 @@ function linkmgmt_linkPorts ($porta, $portb, $linktype, $cable = NULL)
                 throw new InvalidArgException ('porta/portb', $porta, "Ports can't be the same");
 
        if($linktype == 'back')
+       {
                $table = 'LinkBackend';
+               $multilink = MULTILINK;
+       }
        else
+       {
                $table = 'Link';
+               $multilink = false;
+       }
 
         global $dbxlink;
         $dbxlink->exec ('LOCK TABLES '.$table.' WRITE');
-        $result = usePreparedSelectBlade
-        (
-                'SELECT COUNT(*) FROM '.$table.' WHERE porta IN (?,?) OR portb IN (?,?)',
-                array ($porta, $portb, $porta, $portb)
-        );
-        if ($result->fetchColumn () != 0)
-        {
-                $dbxlink->exec ('UNLOCK TABLES');
-                return "$linktype Port ${porta} or ${portb} is already linked";
-        }
-        $result->closeCursor ();
+
+       if(!$multilink)
+       {
+               $result = usePreparedSelectBlade
+               (
+                       'SELECT COUNT(*) FROM '.$table.' WHERE porta IN (?,?) OR portb IN (?,?)',
+                       array ($porta, $portb, $porta, $portb)
+               );
+
+               if ($result->fetchColumn () != 0)
+               {
+                       $dbxlink->exec ('UNLOCK TABLES');
+                       return "$linktype Port ${porta} or ${portb} is already linked";
+               }
+               $result->closeCursor ();
+       }
+
         if ($porta > $portb)
         {
                 $tmp = $porta;
@@ -318,150 +1511,306 @@ header ('Content-Type: text/html; charset=UTF-8');
        $text = '<div style="background-color: #f0f0f0; border: 1px solid #3c78b5; padding: 10px; text-align: center;
  margin: 5px;">';
 
-       if(permitted(NULL,NULL,"set_link"))
-               if (isset ($_REQUEST['do_link'])) {
-                       $text .= getOutputOf ('linkmgmt_oplinkPort');
+       if(permitted(NULL,NULL,"set_link"))
+               if (isset ($_REQUEST['do_link'])) {
+                       $text .= getOutputOf ('linkmgmt_oplinkPort');
+               }
+               else
+                       if(isset($_REQUEST['byname']))
+                               $text .= getOutputOf ('linkmgmt_renderPopupPortSelectorbyName');
+                       else
+                               $text .= getOutputOf ('linkmgmt_renderPopupPortSelector');
+       else
+               $text .= "Permission denied!";
+
+        $text .= '</div>';
+
+       echo '<head><title>RackTables pop-up</title>';
+        printPageHeaders();
+        echo '</head>';
+        echo '<body style="height: 100%;">' . $text . '</body>';
+?>
+</html>
+<?php
+       exit;
+} /* opPortLinkDialog */
+
+/* -------------------------------------------------- */
+
+/*
+ * like findSparePorts in popup.php extended with linktype
+ *
+ * multilink
+ *
+ */
+function linkmgmt_findSparePorts($port_info, $filter, $linktype, $multilink = false, $objectsonly = false, $byname = false, $portcompat = true, $src_object_id = NULL) {
+
+
+       /*
+               $linktable ports that will be returned if not linked in this table
+               $linkinfotable display link for info only show backend links if you want front link a port
+
+               front: select ports no front connection and port compat, filter, ...
+
+               back:
+
+        */
+
+       if($linktype == 'back')
+       {
+               $linktable = 'LinkBackend';
+               $linkinfotable = 'Link';
+       }
+       else
+       {
+               $linktable = 'Link';
+               $linkinfotable = 'LinkBackend';
+       }
+
+       $qparams = array();
+       $whereparams = array();
+
+       // all ports with no link
+       /* port:object -> linked port:object */
+       $query = 'SELECT';
+       $join = "";
+       $where = " WHERE";
+       $group = "";
+       $order = " ORDER BY";
+
+       if($objectsonly)
+       {
+               $query .= " remotePort.object_id, CONCAT(IFNULL(remoteObject.name, CONCAT('[',remoteObjectDictionary.dict_value,']')), ' (', count(remotePort.id), ')') as name";
+               $group .= " GROUP by remoteObject.id";
+       }
+       else
+               if($byname)
+               {
+                       if($linktype == 'back')
+                               $arrow = '=?=>';
+                       else
+                               $arrow = '-?->';
+
+                       $query .= ' CONCAT(localPort.id, "_", remotePort.id),
+                                CONCAT(IFNULL(localObject.name, CONCAT("[",localObjectDictionary.dict_value,"]")), " : ", localPort.Name, " '.$arrow.'", remotePort.name, " : ", IFNULL(remoteObject.name,CONCAT("[",remoteObjectDictionary.dict_value,"]")))';
                }
-               else 
-                       if(isset($_REQUEST['byname']))
-                               $text .= getOutputOf ('linkmgmt_renderPopupPortSelectorbyName');
+               else
+               {
+
+                       if($linktype == 'front')
+                               $arrow = '==';
                        else
-                               $text .= getOutputOf ('linkmgmt_renderPopupPortSelector');
-       else
-               $text .= "Permission denied!";
+                               $arrow = '--';
 
-        $text .= '</div>';
+                       $query .= " remotePort.id, CONCAT(IFNULL(remoteObject.name, CONCAT('[',remoteObjectDictionary.dict_value,']')), ' : ', remotePort.name,
+                               IFNULL(CONCAT(' $arrow ', IFNULL(infolnk.cable,''), ' $arrow> ', InfoPort.name, ' : ', IFNULL(InfoObject.name,CONCAT('[',InfoObjectDictionary.dict_value,']'))),'') ) as Text";
+               }
 
-       echo '<head><title>RackTables pop-up</title>';
-        printPageHeaders();
-        echo '</head>';
-        echo '<body style="height: 100%;">' . $text . '</body>';
-?>
-</html>
-<?php
-       exit;
-} /* opPortLinkDialog */
+       $query .= " FROM Port as remotePort";
+       $join .= " LEFT JOIN Object as remoteObject on remotePort.object_id = remoteObject.id";
+       $order .= " remoteObject.name";
 
-/* -------------------------------------------------- */
+       /* object type name */
+       $join .= " LEFT JOIN Dictionary as remoteObjectDictionary on (remoteObjectDictionary.chapter_id = 1 AND remoteObject.objtype_id = remoteObjectDictionary.dict_key)";
 
-/*
- * like findSparePorts in popup.php extended with linktype
- */
-function linkmgmt_findSparePorts($port_info, $filter, $linktype) {
-       
-       // all ports with no backend link
-       /* port:object -> front linked port:object */   
-       $query = 'select Port.id, CONCAT(RackObject.name, " : ", Port.name, 
-                       IFNULL(CONCAT(" -- ", Link.cable," --> ",lnkPort.name, " : ", lnkObject.name),"") ) 
-               from Port
-               left join LinkBackend on Port.id in (LinkBackend.porta,LinkBackend.portb)
-               left join RackObject on RackObject.id = Port.object_id
-               left join Link on Port.id in (Link.porta, Link.portb)
-               left join Port as lnkPort on lnkPort.id = ((Link.porta ^ Link.portb) ^ Port.id)
-               left join RackObject as lnkObject on lnkObject.id = lnkPort.object_id';
+       if($byname)
+       {
+               /* by name */
+               $join .= " JOIN Port as localPort on remotePort.name = localPort.name";
+               $where .= " remotePort.object_id <> ? AND localPort.object_id = ?";
+               $whereparams[] = $src_object_id;
+               $whereparams[] = $src_object_id;
 
-       $qparams = array();
+               /* own port not linked */
+               $join .= " LEFT JOIN $linktable as localLink on localPort.id in (localLink.porta, localLink.portb)";
+               $join .= " LEFT JOIN Object as localObject on localObject.id = localPort.object_id";
+               $where .= " AND localLink.porta is NULL";
+
+               /* object type name */
+               $join .= " LEFT JOIN Dictionary as localObjectDictionary on (localObject.objtype_id = localObjectDictionary.dict_key  AND localObjectDictionary.chapter_id = 1)";
+       }
+       else
+       {
+               /* exclude current port */
+               $where .= " remotePort.id <> ?";
+               $whereparams[] = $port_info['id'];
+               $order .= " ,remotePort.name";
+
+               /* add info to remoteport */
+               $join .= " LEFT JOIN $linkinfotable as infolnk on remotePort.id in (infolnk.porta, infolnk.portb)";
+               $join .= " LEFT JOIN Port as InfoPort on InfoPort.id = ((infolnk.porta ^ infolnk.portb) ^ remotePort.id)";
+               $join .= " LEFT JOIN Object as InfoObject on InfoObject.id = InfoPort.object_id";
+
+               /* object type name */
+               $join .= " LEFT JOIN Dictionary as InfoObjectDictionary on (InfoObject.objtype_id = InfoObjectDictionary.dict_key  AND InfoObjectDictionary.chapter_id = 1)";
+       }
+
+       /* only ports which are not linked already */
+       $join .= " LEFT JOIN $linktable as lnk on remotePort.id in (lnk.porta, lnk.portb)";
+       $where .= " AND lnk.porta is NULL";
+
+       if($portcompat)
+       {
+               /* port compat */
+               $join .= ' INNER JOIN PortInnerInterface pii ON remotePort.iif_id = pii.id
+                       INNER JOIN Dictionary d ON d.dict_key = remotePort.type';
+               // porttype filter (non-strict match)
+               $join .= ' INNER JOIN (
+                       SELECT Port.id FROM Port
+                       INNER JOIN
+                       (
+                               SELECT DISTINCT pic2.iif_id
+                                       FROM PortInterfaceCompat pic2
+                                       INNER JOIN PortCompat pc ON pc.type2 = pic2.oif_id';
+
+                if ($port_info['iif_id'] != 1)
+                {
+                        $join .= " INNER JOIN PortInterfaceCompat pic ON pic.oif_id = pc.type1 WHERE pic.iif_id = ?";
+                        $qparams[] = $port_info['iif_id'];
+                }
+                else
+                {
+                        $join .= " WHERE pc.type1 = ?";
+                        $qparams[] = $port_info['oif_id'];
+                }
+                $join .= " AND pic2.iif_id <> 1
+                        ) AS sub1 USING (iif_id)
+                       UNION
+                       SELECT Port.id
+                       FROM Port
+                       INNER JOIN PortCompat ON type1 = type
+                       WHERE iif_id = 1 and type2 = ?
+                       ) AS sub2 ON sub2.id = remotePort.id";
+                       $qparams[] = $port_info['oif_id'];
+       }
 
-        // self and linked ports filter
-        $query .= " WHERE Port.id <> ? ". 
-                   "AND LinkBackend.porta is NULL ";
-        $qparams[] = $port_info['id'];
+
+       $qparams = array_merge($qparams, $whereparams);
 
         // rack filter
         if (! empty ($filter['racks']))
         {
-                $query .= 'AND Port.object_id IN (SELECT DISTINCT object_id FROM RackSpace WHERE rack_id IN (' .
+                $where .= ' AND remotePort.object_id IN (SELECT DISTINCT object_id FROM RackSpace WHERE rack_id IN (' .
                         questionMarks (count ($filter['racks'])) . ')) ';
                 $qparams = array_merge ($qparams, $filter['racks']);
         }
 
+       // object_id filter
+        if (! empty ($filter['object_id']))
+        {
+                $where .= ' AND remoteObject.id = ?';
+                $qparams[] = $filter['object_id'];
+        }
+       else
        // objectname filter
         if (! empty ($filter['objects']))
         {
-                $query .= 'AND RackObject.name like ? ';
+                $where .= ' AND remoteObject.name like ? ';
                 $qparams[] = '%' . $filter['objects'] . '%';
         }
+
         // portname filter
         if (! empty ($filter['ports']))
         {
-                $query .= 'AND Port.name LIKE ? ';
+                $where .= ' AND remotePort.name LIKE ? ';
                 $qparams[] = '%' . $filter['ports'] . '%';
         }
-        // ordering
-        $query .= ' ORDER BY RackObject.name';
 
+       $query .= $join.$where.$group.$order;
 
        $result = usePreparedSelectBlade ($query, $qparams);
 
        $row = $result->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_UNIQUE|PDO::FETCH_COLUMN);
 
-       /* [id] => displaystring */     
+       $result->closeCursor();
+
+       /* [id] => displaystring */
        return $row;
-       
+
 } /* findSparePorts */
 
 /* -------------------------------------------------- */
 
 /*
- * similar to findSparePorts but finds Ports with same name
+ * like renderPopupPortSelector in popup.php extenden with linktype
  */
-function linkmgmt_findSparePortsbyName($object_id, $remote_object, $linktype) {
-       
-       // all ports with same name on object and remote_object and without existing backend link
-       $query = 'select CONCAT(Port.id,"_",rPort.id), CONCAT(RackObject.name, " : ", Port.name, " -?-> ", rPort.name, " : ", rObject.name) 
-               from Port
-               left join LinkBackend on Port.id in (LinkBackend.porta,LinkBackend.portb)
-               left join RackObject on RackObject.id = Port.object_id
-               left join Port as rPort on rPort.name = Port.Name
-               left join RackObject as rObject on rObject.id = rPort.object_id
-               left join LinkBackend as rLinkBackend on rPort.id in (rLinkBackend.porta, rLinkBackend.portb)';
+function linkmgmt_renderPopupPortSelector()
+{
+       require_once 'inc/popup.php'; /* getProximateRacks */
 
-       $qparams = array();
+       global $lm_multilink_port_types;
 
-        // self and linked ports filter
-        $query .= " WHERE Port.object_id = ? ". 
-                 "AND rPort.object_id = ? ".
-                 "AND LinkBackend.porta is NULL ".
-                 "AND rLinkBackend.porta is NULL ";
-        $qparams[] = $object_id;
-        $qparams[] = $remote_object;
+        assertUIntArg ('port');
+        $port_id = $_REQUEST['port'];
 
-        // ordering
-        $query .= ' ORDER BY Port.name';
+       $showlinktypeswitch = false;
 
-       $result = usePreparedSelectBlade ($query, $qparams);
+       if(isset($_GET['linktype']))
+               $linktype = $_GET['linktype'];
+       else
+               $linktype = 'front';
 
-       $row = $result->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_UNIQUE|PDO::FETCH_COLUMN);
+       if($linktype == 'both')
+       {
 
-       /* [id] => displaystring */     
-       return $row;
-       
-} /* findSparePortsbyName */
+               /*
+                *      use POST front/back_view to set linktype
+                *      and show linktype switch button
+                */
 
-/* -------------------------------------------------- */
+               $showlinktypeswitch = true;
+
+               if(isset($_POST['front_view']))
+                       $linktype = 'front';
+               else
+               if(isset($_POST['back_view']))
+                       $linktype = 'back';
+               else
+                       $linktype = 'front';
+       }
+
+//     portlist::var_dump_html($_POST);
+
+       $portcompat = true;
+
+       if($linktype == 'back')
+       {
+               if(isset($_POST['portcompat']))
+                       $portcompat = $_POST['portcompat'];
+       }
 
-/*
- * like renderPopupPortSelector in popup.php extenden with linktype
- */
-function linkmgmt_renderPopupPortSelector()
-{
-        assertUIntArg ('port');
-        $port_id = $_REQUEST['port'];
-       $linktype = $_REQUEST['linktype'];
        $object_id = $_REQUEST['object_id'];
         $port_info = getPortInfo ($port_id);
-        $in_rack = isset ($_REQUEST['in_rack']);
+
+       $multilink = MULTILINK && $linktype == 'back' && in_array($port_info['oif_id'], $lm_multilink_port_types);
+
+        if(isset ($_REQUEST['in_rack']))
+               $in_rack = $_REQUEST['in_rack'] != 'off';
+       else
+               $in_rack = true;
 
 //     portlist::var_dump_html($port_info);
-//     portlist::var_dump_html($_REQUEST);
+//     portlist::var_dump_html($_GET);
+//     portlist::var_dump_html($_POST);
 
         // fill port filter structure
         $filter = array
         (
                 'racks' => array(),
                 'objects' => '',
+                'object_id' => '',
                 'ports' => '',
         );
+
+       $remote_object = NULL;
+       if(isset($_REQUEST['remote_object']))
+       {
+               $remote_object = $_REQUEST['remote_object'];
+
+               if($remote_object != 'NULL')
+                       $filter['object_id'] = $remote_object;
+       }
+
         if (isset ($_REQUEST['filter-obj']))
                 $filter['objects'] = $_REQUEST['filter-obj'];
         if (isset ($_REQUEST['filter-port']))
@@ -472,14 +1821,19 @@ function linkmgmt_renderPopupPortSelector()
                 if ($object['rack_id'])
                         $filter['racks'] = getProximateRacks ($object['rack_id'], getConfigVar ('PROXIMITY_RANGE'));
         }
-        $spare_ports = array();
-        if
-        (
-                $in_rack ||
-                ! empty ($filter['objects']) ||
-                ! empty ($filter['ports'])
-        )
-                $spare_ports = linkmgmt_findSparePorts ($port_info, $filter, $linktype);
+
+       $objectlist = array('NULL' => '- Show All -');
+       $objectlist = $objectlist + linkmgmt_findSparePorts($port_info, $filter, $linktype, $multilink, true, false, $portcompat);
+
+       $spare_ports = linkmgmt_findSparePorts ($port_info, $filter, $linktype, $multilink, false, false, $portcompat);
+
+       $maxsize  = getConfigVar('MAXSELSIZE');
+       $objectcount = count($objectlist);
+
+       if($linktype == 'back')
+               $notlinktype = 'front';
+       else
+               $notlinktype = 'back';
 
         // display search form
         echo 'Link '.$linktype.' of ' . formatPort ($port_info) . ' to...';
@@ -489,27 +1843,69 @@ function linkmgmt_renderPopupPortSelector()
        // echo '<input type=hidden name="helper" value="portlist">';
 
         echo '<input type=hidden name="port" value="' . $port_id . '">';
-        echo '<table align="center" valign="bottom"><tr>';
+        echo '<table><tr><td valign="top"><table><tr><td>';
+
+       echo '<table align="center"><tr>';
+
+//     echo '<td nowrap="nowrap"><input type="hidden" name="linktype" value="front" /><input type="checkbox" name="linktype" value="back"'.($linktype == 'back' ? ' checked="checked"' : '' ).'>link backend</input></td></tr><tr>';
         echo '<td class="tdleft"><label>Object name:<br><input type=text size=8 name="filter-obj" value="' . htmlspecialchars ($filter['objects'], ENT_QUOTES) . '"></label></td>';
         echo '<td class="tdleft"><label>Port name:<br><input type=text size=6 name="filter-port" value="' . htmlspecialchars ($filter['ports'], ENT_QUOTES) . '"></label></td>';
-        echo '<td class="tdleft" valign="bottom"><label><input type=checkbox name="in_rack"' . ($in_rack ? ' checked' : '') . '>Nearest racks</label></td>';
-        echo '<td valign="bottom"><input type=submit value="show '.$linktype.' ports"></td>';
+        echo '<td class="tdleft" valign="bottom"><input type="hidden" name="in_rack" value="off" /><label><input type=checkbox value="1" name="in_rack"'.($in_rack ? ' checked="checked"' : '').' onchange="this.form.submit();">Nearest racks</label></td>';
         echo '</tr></table>';
+
+       echo '</td></tr><tr><td>';
+        echo 'Object name (count ports)<br>';
+        echo getSelect ($objectlist, array ('name' => 'remote_object',
+                                               'size' => ($objectcount <= $maxsize ? $objectcount : $maxsize)),
+                                                $remote_object, FALSE);
+
+       echo '</td></tr></table></td>';
+        echo '<td valign="top"><table><tr><td><input type=submit value="update objects / ports"></td></tr>';
+
+       if($showlinktypeswitch)
+               echo '<tr height=150px><td><input type=submit value="Switch to '.$notlinktype.' view" name="'.$notlinktype.'_view"></tr></td>';
+
+       if($linktype == 'back')
+       {
+               echo '<input type="hidden" name="portcompat" value="0">';
+               echo '<tr height=150px><td><input type=checkbox onchange="this.form.submit();" name="portcompat"'.( $portcompat ? 'checked="checked" ' : '' ).'value="1">Port Compatibility</input></tr></td>';
+               echo '<input type="hidden" name="back_view">';
+       }
+
+       echo '</table></td>';
+
         finishPortlet();
+        echo '</td><td>';
 
         // display results
         startPortlet ('Compatible spare '.$linktype.' ports');
-       echo('back Object:Port -- front cableID --> front Port:Object');
+       echo "spare $linktype Object:Port -- $notlinktype cableID -->  $notlinktype Port:Object<br>";
+
+       if($multilink)
+               echo "Multilink<br>";
 
         if (empty ($spare_ports))
                 echo '(nothing found)';
         else
         {
-                echo getSelect ($spare_ports, array ('name' => 'remote_port', 'size' => getConfigVar ('MAXSELSIZE')), NULL, FALSE);
+               $linkcount = count($spare_ports);
+
+                $options = array(
+                               'name' => 'remote_ports[]',
+                               'size' => getConfigVar ('MAXSELSIZE'),
+                               'size' => ($linkcount <= $maxsize ? $linkcount : $maxsize),
+                               );
+
+               if($multilink)
+                       $options['multiple'] = 'multiple';
+
+                echo getSelect ($spare_ports, $options, NULL, FALSE);
+
                 echo "<p>$linktype Cable ID: <input type=text id=cable name=cable>";
                 echo "<p><input type='submit' value='Link $linktype' name='do_link'>";
         }
         finishPortlet();
+        echo '</td></tr></table>';
         echo '</form>';
 
 } /* linkmgmt_renderPopUpPortSelector */
@@ -527,30 +1923,52 @@ function linkmgmt_renderPopupPortSelectorbyName()
 
        $object = spotEntity ('object', $object_id);
 
+       $objectlist = linkmgmt_findSparePorts(NULL, NULL, $linktype, false, true, TRUE, false, $object_id);
+
+       $objectname = $object['dname'];
+
+       /* remove self from list */
+       unset($objectlist[$object_id]);
+
        if(isset($_REQUEST['remote_object']))
                $remote_object = $_REQUEST['remote_object'];
        else
-               $remote_object = NULL;
+       {
+               /* choose first object from list */
+               $keys = array_keys($objectlist);
+
+               if(isset($keys[0]))
+                       $remote_object = $keys[0];
+               else
+                       $remote_object = NULL;
+       }
 
        if($remote_object)
-               $link_list = linkmgmt_findSparePortsbyName($object_id, $remote_object, $linktype);
+       {
+               $filter['object_id'] = $remote_object;
+               $link_list = linkmgmt_findSparePorts(NULL, $filter, $linktype, false, false, TRUE, false, $object_id);
+       }
+       else
+               $link_list = linkmgmt_findSparePorts(NULL, NULL, $linktype, false, false, TRUE, false, $object_id);
 
         // display search form
-        echo 'Link '.$linktype.' of ' . formatPortLink($object_id, $object['name'], NULL, NULL) . ' Ports by Name to...';
+        echo 'Link '.$linktype.' of ' . formatPortLink($object_id, $objectname, NULL, NULL) . ' Ports by Name to...';
         echo '<form method=POST>';
-        startPortlet ('Object list');
 
-       $objectlist = getNarrowObjectList();
+        echo '<table align="center"><tr><td>';
+        startPortlet ('Object list');
 
-       /* remove self from list */
-       unset($objectlist[$object_id]);
+       $maxsize  = getConfigVar('MAXSELSIZE');
+       $objectcount = count($objectlist);
 
-        echo '<table align="center" valign="bottom"><tr>';
-        echo getSelect ($objectlist, array ('name' => 'remote_object', 'size' => getConfigVar ('MAXSELSIZE')), NULL, FALSE);
-        echo '<td valign="bottom"><input type=submit value="show '.$linktype.' ports"></td>';
-        echo '</tr></table>';
+        echo 'Object name (count ports)<br>';
+        echo getSelect ($objectlist, array ('name' => 'remote_object',
+                                               'size' => ($objectcount <= $maxsize ? $objectcount : $maxsize)),
+                                                $remote_object, FALSE);
+        echo '</td><td><input type=submit value="show '.$linktype.' ports>"></td>';
         finishPortlet();
 
+        echo '<td>';
         // display results
         startPortlet ('Possible Backend Link List');
        echo "Select links to create:<br>";
@@ -558,30 +1976,40 @@ function linkmgmt_renderPopupPortSelectorbyName()
                 echo '(nothing found)';
         else
         {
-                echo getSelect ($link_list, array ('name' => 'link_list[]', 'multiple' => 'multiple','size' => getConfigVar ('MAXSELSIZE')), NULL, FALSE);
+               $linkcount = count($link_list);
+
+               $options = array(
+                               'name' => 'link_list[]',
+                               'size' => ($linkcount <= $maxsize ? $linkcount : $maxsize),
+                               'multiple' => 'multiple',
+                               );
+
+                echo getSelect ($link_list,$options, NULL, FALSE);
+
                 echo "<p>$linktype Cable ID: <input type=text id=cable name=cable>";
                 echo "<p><input type='submit' value='Link $linktype' name='do_link'>";
         }
         finishPortlet();
+        echo '</td></tr></table>';
         echo '</form>';
 
-} /* linkmgmt_renderPopUpPortSelector */
+} /* linkmgmt_renderPopUpPortSelectorByName */
 
-/* ------------------------------------------------ */ 
+/* ------------------------------------------------ */
 
 function linkmgmt_tabhandler($object_id) {
        global $lm_cache;
 
        $target = makeHrefProcess(portlist::urlparams('op','update'));
 
-       addJS('js/jquery.jeditable.mini.js');
+       addJS('js/jquery.jeditable.mini.js');
 
-       /* TODO  if (permitted (NULL, 'ports', 'set_reserve_comment')) */ 
+       /* TODO  if (permitted (NULL, 'ports', 'set_reserve_comment')) */
        /* TODO Link / unlink permissions  */
 
        $lm_cache['allowcomment'] = permitted(NULL, NULL, 'set_reserve_comment'); /* RackCode {$op_set_reserve_comment} */
        $lm_cache['allowlink'] = permitted(NULL, NULL, 'set_link'); /* RackCode {$op_set_link} */
-       
+
        //portlist::var_dump_html($lm_cache);
 
        /* init jeditable fields/tags */
@@ -591,7 +2019,6 @@ function linkmgmt_tabhandler($object_id) {
        if($lm_cache['allowlink'])
                addJS('$(document).ready(function() { $(".editcable").editable("'.$target.'",{placeholder : "edit cableID"}); });' , TRUE);
 
-
        /* linkmgmt for current object */
        linkmgmt_renderObjectLinks($object_id);
 
@@ -606,9 +2033,6 @@ function linkmgmt_tabhandler($object_id) {
                linkmgmt_renderObjectLinks($child['entity_id']);
        }
 
-//     $plist->var_dump_html($plist->list);
-
-
        return;
 
 } /* tabhandler */
@@ -616,7 +2040,7 @@ function linkmgmt_tabhandler($object_id) {
 /* -------------------------------------------------- */
 function linkmgmt_renderObjectLinks($object_id) {
 
-       $object = spotEntity ('object', $object_id);
+       $object = spotEntity ('object', $object_id);
         $object['attr'] = getAttrValues($object_id);
 
        /* get ports */
@@ -626,6 +2050,8 @@ function linkmgmt_renderObjectLinks($object_id) {
        //$ports = getObjectPortsAndLinks($object_id);
        $ports = $object['ports'];
 
+       /* reindex array so key starts at 0 */
+       $ports = array_values($ports);
 
        /* URL param handling */
        if(isset($_GET['allports'])) {
@@ -641,7 +2067,7 @@ function linkmgmt_renderObjectLinks($object_id) {
        echo '<table><tr>';
 
        if($allports) {
-                               
+
                echo '<td width=200><a href="'.makeHref(portlist::urlparams('allports','0','0'))
                        .'">Hide Ports without link</a></td>';
        } else
@@ -649,16 +2075,20 @@ function linkmgmt_renderObjectLinks($object_id) {
                        .'">Show All Ports</a></td>';
 
        echo '<td width=200><span onclick=window.open("'.makeHrefProcess(portlist::urlparamsarray(
-                                array('op' => 'PortLinkDialog','linktype' => 'back','byname' => '1'))).'","name","height=800,width=400,scrollbars=yes");><a>Link Object Ports by Name</a></span></td>';
+                                array('op' => 'PortLinkDialog','linktype' => 'back','byname' => '1'))).'","name","height=700,width=800,scrollbars=yes");><a>Link Object Ports by Name</a></span></td>';
 
        if($allback) {
-                               
+
                echo '<td width=200><a href="'.makeHref(portlist::urlparams('allback','0','0'))
                        .'">Collapse Backend Links on same Object</a></td>';
        } else
                echo '<td width=200><a href="'.makeHref(portlist::urlparams('allback','1','0'))
                        .'">Expand Backend Links on same Object</a></td>';
 
+       /* Graphviz map */
+       echo '<td width=100><span onclick=window.open("'.makeHrefProcess(portlist::urlparamsarray(
+                                array('op' => 'map','usemap' => 1))).'","name","height=800,width=800,scrollbars=yes");><a>Object Map</a></span></td>';
+
        /* Help */
        echo '<td width=200><span onclick=window.open("'.makeHrefProcess(portlist::urlparamsarray(
                                 array('op' => 'Help'))).'","name","height=400,width=500");><a>Help</a></span></td>';
@@ -666,33 +2096,37 @@ function linkmgmt_renderObjectLinks($object_id) {
        if(isset($_REQUEST['hl_port_id']))
                $hl_port_id = $_REQUEST['hl_port_id'];
        else
-               $hl_port_id = NULL;     
+               $hl_port_id = NULL;
 
        echo '</tr></table>';
 
-       echo '<br><br><table>';
+
+       echo '<br><br><table id=renderobjectlinks0>';
 
        /*  switch display order depending on backend links */
        $first = portlist::hasbackend($object_id);
 
-       foreach($ports as $port) {
+       $rowcount = 0;
+       foreach($ports as $key => $port) {
 
                $plist = new portlist($port, $object_id, $allports, $allback);
 
-               $plist->printportlistrow($first, $hl_port_id);
-               
+               //echo "<td><img src=\"index.php?module=redirect&page=object&tab=linkmgmt&op=map&object_id=$object_id&port_id=${port['id']}&allports=$allports\" ></td>";
+
+               if($plist->printportlistrow($first, $hl_port_id, ($rowcount % 2 ? portlist::ALTERNATE_ROW_BGCOLOR : "#ffffff")) )
+                       $rowcount++;
+
        }
 
        echo "</table>";
 
 } /* renderObjectLinks */
 
-/* --------------------------------------------------- */
 /* -------------------------------------------------- */
 
 /*
  * Portlist class
- * gets all linked ports to spezified port 
+ * gets all linked ports to spezified port
  * and prints this list as table row
  *
  */
@@ -714,44 +2148,37 @@ class portlist {
 
        private $allback = FALSE;
 
+       private $multilink = MULTILINK;
+
        const B2B_LINK_BGCOLOR = '#d8d8d8';
        const CURRENT_PORT_BGCOLOR = '#ffff99';
        const CURRENT_OBJECT_BGCOLOR = '#ff0000';
        const HL_PORT_BGCOLOR = '#00ff00';
+       const ALTERNATE_ROW_BGCOLOR = '#f0f0f0';
 
        /* Possible LOOP detected after count links print only */
        const MAX_LOOP_COUNT = 13;
 
        private $loopcount;
 
+       private $gv = NULL;
+
        function __construct($port, $object_id, $allports = FALSE, $allback = FALSE) {
-               
 
                $this->object_id = $object_id;
 
                $this->port = $port;
 
                $port_id = $port['id'];
-               
+
                $this->port_id = $port_id;
-               
+
                $this->first_id = $port_id;
                $this->last_id = $port_id;
 
                $this->allback = $allback;
 
-               /* Front Port */
-               $this->count = 0;
-               $this->_getportlist($this->_getportdata($port_id),FALSE);
-               $this->front_count = $this->count;
-
-               /* Back Port */
-               $this->count = 0;
-               $this->_getportlist($this->_getportdata($port_id), TRUE, FALSE);
-               $this->back_count = $this->count;
-
-               $this->count = $this->front_count + $this->back_count;
-
+               $this->_getportlists($port_id);
 
                if(!$allports)
                        if($this->count == 0 || ( ($this->count == 1) && (!empty($this->list[$port_id]['back'])) ) ) {
@@ -759,70 +2186,94 @@ class portlist {
                                $this->first_id = NULL;
                        }
 
-               //$this->var_dump_html($this->list);
+       //      $this->var_dump_html($this->list);
 
        } /* __construct */
 
 
        /*
-         * gets front and back port of src_port
-        * and adds it to the list 
+        * get front and back portlist
+        */
+       function _getportlists($port_id) {
+
+               /* Front Port */
+               $this->count = 0;
+               $this->_getportlist($this->_getportdata($port_id),FALSE, TRUE);
+               $this->front_count = $this->count;
+
+               /* Back Port */
+               $this->count = 0;
+               $this->_getportlist($this->_getportdata($port_id), TRUE, FALSE);
+               $this->back_count = $this->count;
+
+               $this->count = $this->front_count + $this->back_count;
+       }
+
+       /*
+         * gets front or back port of src_port
+        * and adds it to the list
         */
        /* !!! recursive */
        function _getportlist(&$src_port, $back = FALSE, $first = TRUE) {
-               
-               $id = $src_port['id'];
 
-               if($back) 
+               $src_port_id = $src_port['id'];
+
+               if($back)
                        $linktype = 'back';
                else
                        $linktype = 'front';
-               
+
                if(!empty($src_port[$linktype])) {
 
-                       $dst_port_id = $src_port[$linktype]['id'];
+                       /* multilink */
+                       foreach($src_port[$linktype] as &$src_link) {
+                               $dst_port_id = $src_link['id'];
 
-                       if(!$this->_loopdetect($src_port,$dst_port_id,$linktype)) {
-                               //error_log("no loop $linktype>".$dst_port_id);
-                               $this->count++;
-                               $this->_getportlist($this->_getportdata($dst_port_id), !$back, $first);
+                               if(!$this->_loopdetect($src_port,$dst_port_id,$src_link,$linktype)) {
+                                       //error_log("no loop $linktype>".$dst_port_id);
+                                       $this->count++;
+                                       $this->_getportlist($this->_getportdata($dst_port_id), !$back, $first);
+                               }
                        }
+
                } else {
                        if($first) {
-                               $this->first_id = $id;
+                               $this->first_id = $src_port_id;
                        //      $this->front_count = $this->count; /* doesn't work on loops */
                        } else {
-                               $this->last_id = $id;
+                               $this->last_id = $src_port_id;
                        //      $this->back_count = $this->count; /* doesn't work on loops */
                        }
 
                }
 
-       } /* _getportlist */    
+       } /* _getportlist */
 
        /*
         * as name suggested
         */
-       function _loopdetect(&$src_port, $dst_port_id, $linktype) {
+       function _loopdetect(&$src_port, $dst_port_id, &$src_link, $linktype) {
 
-               /* */
+               /* TODO multilink*/
                if(array_key_exists($dst_port_id, $this->list)) {
 
-                       $dst_port = $this->list[$dst_port_id];
+               //      $dst_port = $this->list[$dst_port_id];
 
-                       $src_port[$linktype]['loop'] = $dst_port_id;
+                       //echo "LOOP :".$src_port['id']."-->".$dst_port_id;
 
-               //      echo "LOOP :".$src_port['id']."-->".$dst_port_id;
+                       /* print loop at least once */
+                       if($dst_port_id == $this->port_id)
+                       {
+                               $src_link['loop'] = $dst_port_id;
+                               return TRUE;
+                       }
 
-                       return TRUE;
-       
-               } else {
-                       //error_log(__FUNCTION__."$dst_port_id not exists");
-                       return FALSE;
                }
-               
+
+               return FALSE;
+
        } /* _loopdetect */
-       
+
        /*
         * get all data for one port
         *      name, object, front link, back link
@@ -832,49 +2283,58 @@ class portlist {
                //select cable, ((porta ^ portb) ^ 4556) as port from Link where (4556 in (porta, portb));
 
                //error_log("_getportdata $port_id");
-               
+
                /* TODO single sql ? */
 
-               $result = usePreparedSelectBlade
-                       (
-                               'SELECT Port.id, Port.name, Port.label, Port.type, Port.l2address, Port.object_id, Port.reservation_comment,  
-                                       RackObject.name as "obj_name"
+               $result = usePreparedSelectBlade
+               (
+                       'SELECT Port.id, Port.name, Port.label, Port.type, Port.l2address, Port.object_id, Port.reservation_comment,
+                                       Object.name as "obj_name"
                                 from Port
-                                join RackObject on RackObject.id = Port.object_id
+                                join Object on Object.id = Port.object_id
                                 where Port.id = ?',
                                array($port_id)
-                        );
-                        $datarow = $result->fetchAll(PDO::FETCH_ASSOC);
+               );
+               $datarow = $result->fetchAll(PDO::FETCH_ASSOC);
 
-               $result = usePreparedSelectBlade
-                       (
-                               'SELECT Port.id, Link.cable, Port.name,
+               $result->closeCursor();
+               unset($result);
+
+               $result = usePreparedSelectBlade
+               (
+                               'SELECT Port.id, Link.cable, Port.name, Port.label, Port.type, Port.l2address, Port.object_id,
                                 CONCAT(Link.porta,"_",Link.portb) as link_id from Link
                                 join Port
                                 where (? in (Link.porta,Link.portb)) and ((Link.porta ^ Link.portb) ^ ? ) = Port.id',
                                array($port_id, $port_id)
-                        );
-                        $frontrow = $result->fetchAll(PDO::FETCH_ASSOC);
+               );
+               $frontrow = $result->fetchAll(PDO::FETCH_ASSOC);
+
+               $result->closeCursor();
+               unset($result);
 
-               $result = usePreparedSelectBlade
-                       (
-                               'SELECT Port.id, LinkBackend.cable, Port.name,
+               $result = usePreparedSelectBlade
+               (
+                               'SELECT Port.id, LinkBackend.cable, Port.name, Port.label, Port.type, Port.l2address, Port.object_id,
                                 CONCAT(LinkBackend.porta,"_",LinkBackend.portb,"_back") as link_id from LinkBackend
                                 join Port
                                 where (? in (LinkBackend.porta,LinkBackend.portb)) and ((LinkBackend.porta ^ LinkBackend.portb) ^ ? ) = Port.id',
                                array($port_id, $port_id)
-                        );
-                        $backrow = $result->fetchAll(PDO::FETCH_ASSOC);
+               );
+               $backrow = $result->fetchAll(PDO::FETCH_ASSOC);
+
+               $result->closeCursor();
+               unset($result);
 
                $retval = $datarow[0];
 
                if(!empty($frontrow))
-                       $retval['front']= $frontrow[0];
+                       $retval['front']= $frontrow;
                else
                        $retval['front'] = array();
 
                if(!empty($backrow))
-                       $retval['back'] = $backrow[0];
+                       $retval['back'] = $backrow;
                else
                        $retval['back'] = array();
 
@@ -887,9 +2347,10 @@ class portlist {
 
        /*
         */
-       function printport(&$port) {
+       function printport(&$port, $multilink = false) {
+
                /* set bgcolor for current port */
-               if($port['id'] == $this->port_id) { 
+               if($port['id'] == $this->port_id) {
                        $bgcolor = 'bgcolor='.self::CURRENT_PORT_BGCOLOR;
                        $idtag = ' id='.$port['id'];
                } else {
@@ -899,19 +2360,19 @@ class portlist {
 
                $mac = trim(preg_replace('/(..)/','$1:',$port['l2address']),':');
 
-               $title = "Label: ${port['label']}\nMAC: $mac\nPortID: ${port['id']}";
+               $title = "Label: ${port['label']}\nMAC: $mac\nTypeID: ${port['type']}\nPortID: ${port['id']}";
 
                echo '<td'.$idtag.' align=center '.$bgcolor.' title="'.$title.'"><pre>[<a href="'
                        .makeHref(array('page'=>'object', 'tab' => 'linkmgmt', 'object_id' => $port['object_id'], 'hl_port_id' => $port['id']))
                        .'#'.$port['id']
-                       .'">'.$port['name'].'</a>]</pre></td>';
+                       .'">'.$port['name'].'</a>]</pre>'.($multilink ? $this->_getlinkportsymbol($port['id'], 'back') : '' ).'</td>';
 
        } /* printport */
 
        /*
         */
        function printcomment(&$port) {
-               
+
                if(!empty($port['reservation_comment'])) {
                        $prefix = '<b>Reserved: </b>';
                } else
@@ -940,7 +2401,7 @@ class portlist {
 
        /*
         */
-       function printlink(&$link, $linktype) {
+       function printlink($src_port_id, &$dst_link, $linktype) {
 
                if($linktype == 'back')
                        $arrow = '====>';
@@ -950,9 +2411,9 @@ class portlist {
                /* link */
                echo '<td align=center>';
 
-               echo '<pre><a class="editcable" id='.$link['link_id'].'>'.$link['cable']
+               echo '<pre><a class="editcable" id='.$dst_link['link_id'].'>'.$dst_link['cable']
                        ."</a></pre><pre>$arrow</pre>"
-                       .$this->_printUnLinkPort($link['id'], $linktype);
+                       .$this->_printUnLinkPort($src_port_id, $dst_link, $linktype);
 
                echo '</td>';
        } /* printlink */
@@ -960,57 +2421,88 @@ class portlist {
        /*
         * print cableID dst_port:dst_object
         */
-       function _printportlink($port_id, &$link, $back = FALSE) {
+       function _printportlink($src_port_id, $dst_port_id, &$dst_link, $back = FALSE) {
 
-               //$port_id = $link['id'];
+               global $lm_multilink_port_types;
 
-               $port = $this->list[$port_id];
-               $object_id = $port['object_id'];
-               $obj_name = $port['obj_name'];
+               $multilink = MULTILINK;
 
-               $loop = FALSE;
+       if(!isset($this->list[$dst_port_id]))
+       {
+               /* get port not in list */
+       //      echo "<td>AHHH $src_port_id $dst_port_id --> $back</td>";
+       //      echo "<td>load".$this->var_dump_html($dst_link)." tree</td>";
+//             echo "<td>".$dst_link['cable']." ".$dst_link['name']."</td><td>not displayed</td>";
 
-               if($back) {
-                       $linktype = 'back';
-               } else {
-                       $linktype = 'front';
-               }
+               if($back)
+                       echo "<td>></td>";
+
+               // TODO check if multilink is needed here
+               $this->printport($dst_link, $multilink && in_array($dst_link['type'], $lm_multilink_port_types));
+               echo "<td>...</td>";
 
-               $sameobject = FALSE;
+               return TRUE;
 
-               if(isset($link['loop']))
-                       $loop = TRUE;
+       //      $this->_getportlist($this->list[$src_port_id], $back, !$back);
+       }
 
-               if($link != NULL) {
+       $dst_port = $this->list[$dst_port_id];
+       $object_id = $dst_port['object_id'];
+       $obj_name = $dst_port['obj_name'];
 
-                       $src_port_id = $port[$linktype]['id'];
-                       $src_object_id = $this->list[$src_port_id]['object_id'];
+       if($obj_name == NULL)
+       {
+               $tmpobj = spotEntity('object', $dst_port['object_id']);
+               $dst_port['obj_name'] = $tmpobj['dname'];
+               $obj_name = $tmpobj['dname'];
+       }
 
-                       if(!$this->allback && $object_id == $src_object_id && $back) {
-                               $sameobject = TRUE;
-                       } else {
-                               $this->printlink($link, $linktype);
-                       }
+       $loop = FALSE;
+       $edgeport = ($dst_link == NULL) || empty($dst_port['front']) || empty($dst_port['back']);
+
+       if($back) {
+               $linktype = 'back';
+       } else {
+               $linktype = 'front';
+       }
 
+       $sameobject = FALSE;
+
+       if(isset($dst_link['loop']))
+               $loop = TRUE;
+
+       if($dst_link != NULL) {
+
+               $src_object_id = $this->list[$src_port_id]['object_id'];
+
+               if(!$this->allback && $object_id == $src_object_id && $back) {
+                       $sameobject = TRUE;
                } else {
-                       $this->_LinkPort($port_id, $linktype);
+                       $this->printlink($src_port_id, $dst_link, $linktype);
+               }
+
+       } else {
+               $this->_printlinkportsymbol($dst_port_id, $linktype);
+               $edgeport = true;
 
                        if(!$back)
-                               $this->printcomment($port);
-               }       
+                               $this->printcomment($dst_port);
+               }
 
                if($back) {
                        if(!$sameobject)
                                $this->printobject($object_id,$obj_name);
+
                        echo "<td>></td>";
 
                        /* align ports nicely */
-                       if($port['id'] == $this->port_id)
-                               echo '</td></tr></table></td><td><table align=left><tr>';
+                       if($dst_port['id'] == $this->port_id)
+                               echo '</td></tr></table id=printportlink1></td><td><table align=left><tr>';
                }
 
                /* print [portname] */
-               $this->printport($port);
+               // TODO check multilink symbols front/back edgeports
+               $this->printport($dst_port, $multilink && in_array($dst_port['type'], $lm_multilink_port_types));
 
                if($loop)
                        echo '<td bgcolor=#ff9966>LOOP</td>';
@@ -1018,80 +2510,100 @@ class portlist {
                if(!$back) {
 
                        /* align ports nicely */
-                       if($port['id'] == $this->port_id)
-                               echo '</td></tr></table></td><td><table align=left><tr>';
+                       if($dst_port['id'] == $this->port_id)
+                               echo '</td></tr></table id=printportlink2></td><td><table align=left><tr>';
 
                        echo "<td><</td>";
                        $this->printobject($object_id,$obj_name);
 
-                       if(empty($port['back']))
-                               $this->_LinkPort($port_id, 'back');
+                       if(empty($dst_port['back']))
+                               $this->_printlinkportsymbol($dst_port_id, 'back');
                } else
-                       if( ($port['id'] != $this->port_id) && empty($port['front'])) {
-                               $this->printcomment($port);
-                               $this->_LinkPort($port_id, 'front');
+                       if(empty($dst_port['front'])) {
+                               $this->printcomment($dst_port);
+                               $this->_printlinkportsymbol($dst_port_id, 'front');
                        }
-               
+
                if($loop) {
-                       if(isset($link['loopmaxcount']))
+                       if(isset($dst_link['loopmaxcount']))
                                $reason = " (MAX LOOP COUNT reached)";
                        else
                                $reason = '';
 
-                       showWarning("Possible Loop on Port ($linktype) ".$port['name'].$reason);
+                       showWarning("Possible Loop on Port ($linktype) ".$dst_port['name'].$reason);
                        return FALSE;
                }
 
                return TRUE;
 
-       } /* _printport */
+       } /* _printportlink */
 
        /*
         * print <tr>..</tr>
         */
-       function printportlistrow($first = TRUE, $hl_port_id = NULL) {
-       
+       function printportlistrow($first = TRUE, $hl_port_id = NULL, $rowbgcolor = '#ffffff') {
+
                $this->loopcount = 0;
 
                if($this->first_id == NULL)
-                       return;
+                       return false;
 
                if($first)
                        $id = $this->first_id;
                else
                        $id = $this->last_id;
 
-
                if($hl_port_id == $this->port_id)
                        $hlbgcolor = "bgcolor=".self::HL_PORT_BGCOLOR;
                else
-                       $hlbgcolor = "";
+                       $hlbgcolor = "bgcolor=$rowbgcolor";
 
                $link = NULL;
 
                $port = $this->list[$id];
 
-               $title = "linkcount: ".$this->count." (".$this->front_count."/".$this->back_count.")";
+               $urlparams = array(
+                               'module' => 'redirect',
+                               'page' => 'object',
+                               'tab' => 'linkmgmt',
+                               'op' => 'map',
+                               'usemap' => 1,
+                               'object_id' => $port['object_id'],
+                               );
+
+               if($hl_port_id !== NULL)
+                       $urlparams['hl_port_id'] = $hl_port_id;
+               else
+                       $urlparams['port_id'] = $id;
+
+               $title = "linkcount: ".$this->count." (".$this->front_count."/".$this->back_count.")\nTypeID: ${port['type']}\nPortID: $id";
+
+               $onclick = 'onclick=window.open("'.makeHrefProcess(portlist::urlparamsarray(
+                                $urlparams)).'","Map","height=500,width=800,scrollbars=yes");';
 
                /* Current Port */
-               echo '<tr '.$hlbgcolor.'><td nowrap="nowrap" bgcolor='.self::CURRENT_PORT_BGCOLOR.' title="'.$title.'">'.$this->port['name'].': </td>';
-               
-               echo "<td><table align=right><tr><td>";
-               
-               $back = !empty($this->list[$id]['front']);
+               echo '<tr '.$hlbgcolor.'><td nowrap="nowrap" bgcolor='.self::CURRENT_PORT_BGCOLOR.' title="'.$title.
+                       '"><a '.$onclick.'>'.
+                       $this->port['name'].': </a></td>';
 
-               $this->_printportlink($id, $link, $back);
+               echo "<td><table id=printportlistrow1 align=right><tr><td>";
+
+               $back = empty($this->list[$id]['back']);
+
+               $this->_printportlink(NULL, $id, $link, $back);
 
                $this->_printportlist($id, !$back);
-               echo "</td></tr></table></td></tr>";
+               echo "</td></tr></table id=printportlistrow2></td></tr>";
 
                /* horizontal line */
                 echo '<tr><td height=1 colspan=3 bgcolor=#e0e0e0></td></tr>';
 
+               return true;
+
        } /* printportlist */
 
        /*
-        * print <td> 
+        * print <td>
         * prints all ports in a list starting with start_port_id
         */
        /* !!! recursive */
@@ -1102,35 +2614,56 @@ class portlist {
                 else
                         $linktype = 'front';
 
-               $link = &$this->list[$src_port_id][$linktype];
-       
-               if(!empty($link)) {
-                       $dst_port_id = $link['id'];
+               if(!empty($this->list[$src_port_id][$linktype])) {
 
-                       $this->loopcount++;
+                       $linkcount = count($this->list[$src_port_id][$linktype]);
 
-                       if($this->loopcount > self::MAX_LOOP_COUNT) {
-                       //      $src_port_name = $this->list[$src_port_id]['name'];
-                       //      $dst_port_name = $this->list[$dst_port_id]['name'];
+                       if($linkcount > 1)
+                               echo "<td bgcolor=#f00000></td><td><table id=_printportlist1>";
 
-                               $link['loop'] = $dst_port_id;
-                               $link['loopmaxcount'] = $dst_port_id;
+                       $lastkey = $linkcount - 1;
 
-                               /* loop warning is handeld in _printportlink() */
-                               //showWarning("MAX LOOP COUNT reached $src_port_name -> $dst_port_name".self::MAX_LOOP_COUNT);
-                               //return; /* return after _printportlink */
-                       }
+                       foreach($this->list[$src_port_id][$linktype] as $key => &$link) {
+
+                               if($linkcount > 1) {
+                                       echo "<tr style=\"background-color:".( $key % 2 ? self::ALTERNATE_ROW_BGCOLOR : "#ffffff" )."\"><td><table id=_printportlist2><tr>";
+                               }
+
+                               $dst_port_id = $link['id'];
+
+                               $this->loopcount++;
+
+                               if($this->loopcount > self::MAX_LOOP_COUNT) {
+                               //      $src_port_name = $this->list[$src_port_id]['name'];
+                               //      $dst_port_name = $this->list[$dst_port_id]['name'];
 
-                       if(!$this->_printportlink($dst_port_id, $link, $back))
+                                       $link['loop'] = $dst_port_id;
+                                       $link['loopmaxcount'] = $dst_port_id;
+
+                                       /* loop warning is handeld in _printportlink() */
+                                       //showWarning("MAX LOOP COUNT reached $src_port_name -> $dst_port_name".self::MAX_LOOP_COUNT);
+                                       //return; /* return after _printportlink */
+                               }
+
+                               if(!$this->_printportlink($src_port_id, $dst_port_id, $link, $back))
+                               {
                                        return;
+                               }
 
-                               $this->_printportlist($dst_port_id,!$back);
-                       }
+                               $this->_printportlist($dst_port_id,!$back);
                        
+                               if($linkcount > 1) {
+                                       echo "</tr></table></td></tr>"
+                                               .( $key != $lastkey ? "<tr><td height=1 colspan=100% bgcolor=#c0c0c0><td></tr>" : "");
+                               }
+                       }
 
+                       if($linkcount > 1)
+                               echo "</table></td>";
+               }
        } /* _printportlist */
 
-       /*
+       /*
          *  returns linked Row / Rack Info for object_id
          *
          */
@@ -1142,139 +2675,115 @@ class portlist {
                 /* if not in cache get it */
                 if(!array_key_exists($object_id,$rackinfocache)) {
 
-                        /* SQL from database.php SQLSchema 'object' */
-                        $result = usePreparedSelectBlade
-                        (
-                                'SELECT rack_id, Rack.name as Rack_name, row_id, RackRow.name as Row_name
-                                FROM RackSpace
-                                LEFT JOIN EntityLink on RackSpace.object_id = EntityLink.parent_entity_id
-                                JOIN Rack on Rack.id = RackSpace.rack_id
-                                JOIN RackRow on RackRow.id = Rack.row_id
-                                WHERE ( RackSpace.object_id = ? ) or (EntityLink.child_entity_id = ?)
-                                ORDER by rack_id asc limit 1',
-                                array($object_id, $object_id)
-                         );
-                         $row = $result->fetchAll(PDO::FETCH_ASSOC);
-
-                        if(!empty($row)) {
-
-                                $rackinfocache[$object_id] = $row[0];
-                        }
+                                $object = spotEntity('object', $object_id);
+                               $rack_id = $object['rack_id'];
 
+                               if($rack_id)
+                                       $rackinfocache[$object_id] = spotEntity('rack', $object['rack_id']);
                 }
 
-                $obj = &$rackinfocache[$object_id];
 
-                if(empty($obj))
+                $rack = &$rackinfocache[$object_id];
+
+                if(empty($rack))
                         return  '<span style="'.$style.'">Unmounted</span>';
                 else
-                        return '<a style="'.$style.'" href='.makeHref(array('page'=>'row', 'row_id'=>$obj['row_id'])).'>'.$obj['Row_name']
-                                .'</a>/<a style="'.$style.'" href='.makeHref(array('page'=>'rack', 'rack_id'=>$obj['rack_id'])).'>'
-                                .$obj['Rack_name'].'</a>';
+                        return '<a style="'.$style.'" href='.makeHref(array('page'=>'row', 'row_id'=>$rack['row_id'])).'>'.$rack['row_name']
+                                .'</a>/<a style="'.$style.'" href='.makeHref(array('page'=>'rack', 'rack_id'=>$rack['id'])).'>'
+                                .$rack['name'].'</a>';
 
         } /* _getRackInfo */
 
+       /*
+        * return link symbol
+        */
+       function _getlinkportsymbol($port_id, $linktype) {
+               $retval = '<span onclick=window.open("'.makeHrefProcess(portlist::urlparamsarray(
+                       array('op' => 'PortLinkDialog','port' => $port_id,'linktype' => $linktype ))).'","name","height=800,width=800");'
+                       .'>';
+
+                $img = getImageHREF ('plug', $linktype.' Link this port');
+
+               if($linktype == 'back')
+                       $img = str_replace('<img',
+                               '<img style="transform:rotate(180deg);-o-transform:rotate(180deg);-ms-transform:rotate(180deg);-moz-transform:rotate(180deg);-webkit-transform:rotate(180deg);"',
+                               $img);
+
+               $retval .= $img;
+               $retval .= "</span>";
+               return $retval;
+
+       } /* _getlinkportsymbol */
 
-       /* 
-        * return link symbol
+       /*
+        * print link symbol
         *
         */
-       function _LinkPort($port_id, $linktype = 'front') {
+       function _printlinkportsymbol($port_id, $linktype = 'front') {
                global $lm_cache;
 
                if(!$lm_cache['allowlink'])
                        return;
-       
-               $helper_args = array
-                        (
-                                'port' => $port_id,
-                        );
 
                 echo "<td align=center>";
 
-               if($linktype == 'front') {
-
-                        echo "<span";
-                        $popup_args = 'height=700, width=400, location=no, menubar=no, '.
-                                'resizable=yes, scrollbars=yes, status=no, titlebar=no, toolbar=no';
-                        echo " ondblclick='window.open(\"" . makeHrefForHelper ('portlist', $helper_args);
-                        echo "\",\"findlink\",\"${popup_args}\");'";
-                        // end of onclick=
-                        echo " onclick='window.open(\"" . makeHrefForHelper ('portlist', $helper_args);
-                        echo "\",\"findlink\",\"${popup_args}\");'";
-                        // end of onclick=
-                        echo '>';
-                        printImageHREF ('plug', 'Link this port');
-                        echo "</span>";
-
-               } else {
-                       /* backend link */
-
-                       echo '<span onclick=window.open("'.makeHrefProcess(portlist::urlparamsarray(
-                               array('op' => 'PortLinkDialog','port' => $port_id,'linktype' => $linktype))).'","name","height=800,width=400");'
-                        .'>';
-                        printImageHREF ('plug', $linktype.' Link this port');
-                        echo "</span>";
-                       
-               }
+               echo $this->_getlinkportsymbol($port_id, $linktype);
 
                echo "</td>";
 
-        } /* _LinkPort */
+        } /* _printlinkportsymbol */
 
-       /* 
-        * return link cut symbol
+       /*
+        * return link cut symbol
         *
          * TODO $opspec_list
         */
-       function _printUnLinkPort($port_id, $linktype) {
+       function _printUnLinkPort($src_port_id, &$dst_link, $linktype) {
                global $lm_cache;
 
                if(!$lm_cache['allowlink'])
                        return '';
 
-               $src_port = $this->list[$port_id];
-
-               $link = $src_port[$linktype];
+               $src_port = $this->list[$src_port_id];
 
-               $dst_port = $this->list[$link['id']];
+               $dst_port = $this->list[$dst_link['id']];
 
                /* use RT unlink for front link, linkmgmt unlink for back links */
                if($linktype == 'back')
                        $tab = 'linkmgmt';
                else
                        $tab = 'ports';
-               
 
-               return '<a href='.
+               return '<a href='.
                                makeHrefProcess(array(
                                        'op'=>'unlinkPort',
-                                       'port_id'=>$port_id,
+                                       'port_id'=>$src_port_id,
+                                       'remote_id' => $dst_port['id'],
                                        'object_id'=>$this->object_id,
                                        'tab' => $tab,
                                        'linktype' => $linktype)).
                        ' onclick="return confirm(\'unlink ports '.$src_port['name']. ' -> '.$dst_port['name']
-                                       .' ('.$linktype.') with cable ID: '.$src_port[$linktype]['cable'].'?\');">'.
+                                       .' ('.$linktype.') with cable ID: '.$dst_link['cable'].'?\');">'.
                        getImageHREF ('cut', $linktype.' Unlink this port').'</a>';
 
        } /* _printUnLinkPort */
 
 
        /*
-         * 
+        *
          */
-        function urlparams($name, $value, $defaultvalue = NULL) {
+       static function urlparams($name, $value, $defaultvalue = NULL) {
 
                 $urlparams = $_GET;
 
                if($value == $defaultvalue) {
 
-                       /* remove param */
-                       unset($urlparams[$name]);
+                       /* remove param */
+                       unset($urlparams[$name]);
 
-               } else {
+               } else {
 
-                       $urlparams[$name] = $value;
+                       $urlparams[$name] = $value;
 
                }
 
@@ -1285,7 +2794,7 @@ class portlist {
        /*
          * $params = array('name' => 'value', ...)
          */
-        function urlparamsarray($params) {
+       static function urlparamsarray($params) {
 
                 $urlparams = $_GET;
 
@@ -1293,14 +2802,14 @@ class portlist {
 
                        if($value == NULL) {
 
-                              /* remove param */
-                               unset($urlparams[$name]);
+                               /* remove param */
+                               unset($urlparams[$name]);
 
-                      } else {
+                       } else {
 
-                              $urlparams[$name] = $value;
+                               $urlparams[$name] = $value;
 
-                      }
+                       }
                }
 
                 return $urlparams;
@@ -1308,20 +2817,22 @@ class portlist {
         } /* urlparamsarray */
 
        /* */
-       function hasbackend($object_id) {
+       static function hasbackend($object_id) {
                /* sql bitwise xor: porta ^ portb */
                //select cable, ((porta ^ portb) ^ 4556) as port from Link where (4556 in (porta, portb));
 
-               $result = usePreparedSelectBlade
-                       (
+               $result = usePreparedSelectBlade
+               (
                                'SELECT count(*) from Port
                                 join LinkBackend on (porta = id or portb = id )
                                 where object_id = ?',
                                array($object_id)
-                        );
-               $retval = $result->fetchColumn();
-               
-               return $retval != 0;
+               );
+               $retval = $result->fetchColumn();
+
+               $result->closeCursor();
+
+               return $retval != 0;
 
        } /* hasbackend */