[ Index ]

PHP Cross Reference of phpwcms V1.4.3 _r380 (23.11.09)

title

Body

[close]

/include/inc_ext/fckeditor/editor/plugins/dragresizetable/ -> fckplugin.js (source)

   1  var FCKDragTableHandler =
   2  {
   3      "_DragState" : 0,
   4      "_LeftCell" : null,
   5      "_RightCell" : null,
   6      "_MouseMoveMode" : 0,    // 0 - find candidate cells for resizing, 1 - drag to resize
   7      "_ResizeBar" : null,
   8      "_OriginalX" : null,
   9      "_MinimumX" : null,
  10      "_MaximumX" : null,
  11      "_LastX" : null,
  12      "_TableMap" : null,
  13      "_doc" : document,
  14      "_IsInsideNode" : function( w, domNode, pos )
  15      {
  16          var myCoords = FCKTools.GetWindowPosition( w, domNode ) ;
  17          var xMin = myCoords.x ;
  18          var yMin = myCoords.y ;
  19          var xMax = parseInt( xMin, 10 ) + parseInt( domNode.offsetWidth, 10 ) ;
  20          var yMax = parseInt( yMin, 10 ) + parseInt( domNode.offsetHeight, 10 ) ;
  21          if ( pos.x >= xMin && pos.x <= xMax && pos.y >= yMin && pos.y <= yMax )
  22              return true;
  23          return false;
  24      },
  25      "_GetBorderCells" : function( w, tableNode, tableMap, mouse )
  26      {
  27          // Enumerate all the cells in the table.
  28          var cells = [] ;
  29          for ( var i = 0 ; i < tableNode.rows.length ; i++ )
  30          {
  31              var r = tableNode.rows[i] ;
  32              for ( var j = 0 ; j < r.cells.length ; j++ )
  33                  cells.push( r.cells[j] ) ;
  34          }
  35  
  36          if ( cells.length < 1 )
  37              return null ;
  38  
  39          // Get the cells whose right or left border is nearest to the mouse cursor's x coordinate.
  40          var minRxDist = null ;
  41          var lxDist = null ;
  42          var minYDist = null ;
  43          var rbCell = null ;
  44          var lbCell = null ;
  45          for ( var i = 0 ; i < cells.length ; i++ )
  46          {
  47              var pos = FCKTools.GetWindowPosition( w, cells[i] ) ;
  48              var rightX = pos.x + parseInt( cells[i].clientWidth, 10 ) ;
  49              var rxDist = mouse.x - rightX ;
  50              var yDist = mouse.y - ( pos.y + ( cells[i].clientHeight / 2 ) ) ;
  51              if ( minRxDist == null ||
  52                      ( Math.abs( rxDist ) <= Math.abs( minRxDist ) &&
  53                        ( minYDist == null || Math.abs( yDist ) <= Math.abs( minYDist ) ) ) )
  54              {
  55                  minRxDist = rxDist ;
  56                  minYDist = yDist ;
  57                  rbCell = cells[i] ;
  58              }
  59          }
  60          /*
  61          var rowNode = FCKTools.GetElementAscensor( rbCell, "tr" ) ;
  62          var cellIndex = rbCell.cellIndex + 1 ;
  63          if ( cellIndex >= rowNode.cells.length )
  64              return null ;
  65          lbCell = rowNode.cells.item( cellIndex ) ;
  66          */
  67          var rowIdx = rbCell.parentNode.rowIndex ;
  68          var colIdx = FCKTableHandler._GetCellIndexSpan( tableMap, rowIdx, rbCell ) ;
  69          var colSpan = isNaN( rbCell.colSpan ) ? 1 : rbCell.colSpan ;
  70          lbCell = tableMap[rowIdx][colIdx + colSpan] ;
  71  
  72          if ( ! lbCell )
  73              return null ;
  74  
  75          // Abort if too far from the border.
  76          lxDist = mouse.x - FCKTools.GetWindowPosition( w, lbCell ).x ;
  77          if ( lxDist < 0 && minRxDist < 0 && minRxDist < -2 )
  78              return null ;
  79          if ( lxDist > 0 && minRxDist > 0 && lxDist > 3 )
  80              return null ;
  81  
  82          return { "leftCell" : rbCell, "rightCell" : lbCell } ;
  83      },
  84      "_GetResizeBarPosition" : function()
  85      {
  86          var row = FCKTools.GetElementAscensor( this._RightCell, "tr" ) ;
  87          return FCKTableHandler._GetCellIndexSpan( this._TableMap, row.rowIndex, this._RightCell ) ;
  88      },
  89      "_ResizeBarMouseDownListener" : function( evt )
  90      {
  91          if ( FCKDragTableHandler._LeftCell )
  92              FCKDragTableHandler._MouseMoveMode = 1 ;
  93          if ( FCKBrowserInfo.IsIE )
  94              FCKDragTableHandler._ResizeBar.filters.item("DXImageTransform.Microsoft.Alpha").opacity = 50 ;
  95          else
  96              FCKDragTableHandler._ResizeBar.style.opacity = 0.5 ;
  97          FCKDragTableHandler._OriginalX = evt.clientX ;
  98  
  99          // Calculate maximum and minimum x-coordinate delta.
 100          var borderIndex = FCKDragTableHandler._GetResizeBarPosition() ;
 101          var offset = FCKDragTableHandler._GetIframeOffset();
 102          var table = FCKTools.GetElementAscensor( FCKDragTableHandler._LeftCell, "table" );
 103          var minX = null ;
 104          var maxX = null ;
 105          for ( var r = 0 ; r < FCKDragTableHandler._TableMap.length ; r++ )
 106          {
 107              var leftCell = FCKDragTableHandler._TableMap[r][borderIndex - 1] ;
 108              var rightCell = FCKDragTableHandler._TableMap[r][borderIndex] ;
 109              var leftPosition = FCKTools.GetWindowPosition( FCK.EditorWindow, leftCell ) ;
 110              var rightPosition = FCKTools.GetWindowPosition( FCK.EditorWindow, rightCell ) ;
 111              var leftPadding = FCKDragTableHandler._GetCellPadding( table, leftCell ) ;
 112              var rightPadding = FCKDragTableHandler._GetCellPadding( table, rightCell ) ;
 113              if ( minX == null || leftPosition.x + leftPadding > minX )
 114                  minX = leftPosition.x + leftPadding ;
 115              if ( maxX == null || rightPosition.x + rightCell.clientWidth - rightPadding < maxX )
 116                  maxX = rightPosition.x + rightCell.clientWidth - rightPadding ;
 117          }
 118  
 119          FCKDragTableHandler._MinimumX = minX + offset.x ;
 120          FCKDragTableHandler._MaximumX = maxX + offset.x ;
 121          FCKDragTableHandler._LastX = null ;
 122  
 123          if (evt.preventDefault)
 124              evt.preventDefault();
 125          else
 126              evt.returnValue = false;
 127      },
 128      "_ResizeBarMouseUpListener" : function( evt )
 129      {
 130          FCKDragTableHandler._MouseMoveMode = 0 ;
 131          FCKDragTableHandler._HideResizeBar() ;
 132  
 133          if ( FCKDragTableHandler._LastX == null )
 134              return ;
 135  
 136          // Calculate the delta value.
 137          var deltaX = FCKDragTableHandler._LastX - FCKDragTableHandler._OriginalX ;
 138  
 139          // Then, build an array of current column width values.
 140          // This algorithm can be very slow if the cells have insane colSpan values. (e.g. colSpan=1000).
 141          var table = FCKTools.GetElementAscensor( FCKDragTableHandler._LeftCell, "table" ) ;
 142          var colArray = [] ;
 143          var tableMap = FCKDragTableHandler._TableMap ;
 144          for ( var i = 0 ; i < tableMap.length ; i++ )
 145          {
 146              for ( var j = 0 ; j < tableMap[i].length ; j++ )
 147              {
 148                  var cell = tableMap[i][j] ;
 149                  var width = FCKDragTableHandler._GetCellWidth( table, cell ) ;
 150                  var colSpan = isNaN( cell.colSpan) ? 1 : cell.colSpan ;
 151                  if ( colArray.length <= j )
 152                      colArray.push( { width : width / colSpan, colSpan : colSpan } ) ;
 153                  else
 154                  {
 155                      var guessItem = colArray[j] ;
 156                      if ( guessItem.colSpan > colSpan )
 157                      {
 158                          guessItem.width = width / colSpan ;
 159                          guessItem.colSpan = colSpan ;
 160                      }
 161                  }
 162              }
 163          }
 164  
 165          // Find out the equivalent column index of the two cells selected for resizing.
 166          colIndex = FCKDragTableHandler._GetResizeBarPosition() ;
 167  
 168          // Note that colIndex must be at least 1 here, so it's safe to subtract 1 from it.
 169          colIndex-- ;
 170  
 171          // Modify the widths in the colArray according to the mouse coordinate delta value.
 172          colArray[colIndex].width += deltaX ;
 173          colArray[colIndex + 1].width -= deltaX ;
 174  
 175          // Clear all cell widths, delete all <col> elements from the table.
 176          for ( var r = 0 ; r < table.rows.length ; r++ )
 177          {
 178              var row = table.rows.item( r ) ;
 179              for ( var c = 0 ; c < row.cells.length ; c++ )
 180              {
 181                  var cell = row.cells.item( c ) ;
 182                  cell.width = "" ;
 183                  cell.style.width = "" ;
 184              }
 185          }
 186          var colElements = table.getElementsByTagName( "col" ) ;
 187          for ( var i = colElements.length - 1 ; i >= 0 ; i-- )
 188              colElements[i].parentNode.removeChild( colElements[i] ) ;
 189  
 190          // Set new cell widths.
 191          var processedCells = [] ;
 192          for ( var i = 0 ; i < tableMap.length ; i++ )
 193          {
 194              for ( var j = 0 ; j < tableMap[i].length ; j++ )
 195              {
 196                  var cell = tableMap[i][j] ;
 197                  if ( cell._Processed )
 198                      continue ;
 199                  if ( tableMap[i][j-1] != cell )
 200                      cell.width = colArray[j].width ;
 201                  else
 202                      cell.width = parseInt( cell.width, 10 ) + parseInt( colArray[j].width, 10 ) ;
 203                  if ( tableMap[i][j+1] != cell )
 204                  {
 205                      processedCells.push( cell ) ;
 206                      cell._Processed = true ;
 207                  }
 208              }
 209          }
 210          for ( var i = 0 ; i < processedCells.length ; i++ )
 211          {
 212              if ( FCKBrowserInfo.IsIE )
 213                  processedCells[i].removeAttribute( '_Processed' ) ;
 214              else
 215                  delete processedCells[i]._Processed ;
 216          }
 217  
 218          FCKDragTableHandler._LastX = null ;
 219      },
 220      "_ResizeBarMouseMoveListener" : function( evt )
 221      {
 222          if ( FCKDragTableHandler._MouseMoveMode == 0 )
 223              return FCKDragTableHandler._MouseFindHandler( FCK, evt ) ;
 224          else
 225              return FCKDragTableHandler._MouseDragHandler( FCK, evt ) ;
 226      },
 227      // Calculate the padding of a table cell.
 228      // It returns the value of paddingLeft + paddingRight of a table cell.
 229      // This function is used, in part, to calculate the width parameter that should be used for setting cell widths.
 230      // The equation in question is clientWidth = paddingLeft + paddingRight + width.
 231      // So that width = clientWidth - paddingLeft - paddingRight.
 232      // The return value of this function must be pixel accurate acorss all supported browsers, so be careful if you need to modify it.
 233      "_GetCellPadding" : function( table, cell )
 234      {
 235          var attrGuess = parseInt( table.cellPadding, 10 ) * 2 ;
 236          var cssGuess = null ;
 237          if ( typeof( window.getComputedStyle ) == "function" )
 238          {
 239              var styleObj = window.getComputedStyle( cell, null ) ;
 240              cssGuess = parseInt( styleObj.getPropertyValue( "padding-left" ), 10 ) +
 241                  parseInt( styleObj.getPropertyValue( "padding-right" ), 10 ) ;
 242          }
 243          else
 244              cssGuess = parseInt( cell.currentStyle.paddingLeft, 10 ) + parseInt (cell.currentStyle.paddingRight, 10 ) ;
 245  
 246          var cssRuntime = cell.style.padding ;
 247          if ( isFinite( cssRuntime ) )
 248              cssGuess = parseInt( cssRuntime, 10 ) * 2 ;
 249          else
 250          {
 251              cssRuntime = cell.style.paddingLeft ;
 252              if ( isFinite( cssRuntime ) )
 253                  cssGuess = parseInt( cssRuntime, 10 ) ;
 254              cssRuntime = cell.style.paddingRight ;
 255              if ( isFinite( cssRuntime ) )
 256                  cssGuess += parseInt( cssRuntime, 10 ) ;
 257          }
 258  
 259          attrGuess = parseInt( attrGuess, 10 ) ;
 260          cssGuess = parseInt( cssGuess, 10 ) ;
 261          if ( isNaN( attrGuess ) )
 262              attrGuess = 0 ;
 263          if ( isNaN( cssGuess ) )
 264              cssGuess = 0 ;
 265          return Math.max( attrGuess, cssGuess ) ;
 266      },
 267      // Calculate the real width of the table cell.
 268      // The real width of the table cell is the pixel width that you can set to the width attribute of the table cell and after
 269      // that, the table cell should be of exactly the same width as before.
 270      // The real width of a table cell can be calculated as:
 271      // width = clientWidth - paddingLeft - paddingRight.
 272      "_GetCellWidth" : function( table, cell )
 273      {
 274          var clientWidth = cell.clientWidth ;
 275          if ( isNaN( clientWidth ) )
 276              clientWidth = 0 ;
 277          return clientWidth - this._GetCellPadding( table, cell ) ;
 278      },
 279      "MouseMoveListener" : function( FCK, evt )
 280      {
 281          if ( FCKDragTableHandler._MouseMoveMode == 0 )
 282              return FCKDragTableHandler._MouseFindHandler( FCK, evt ) ;
 283          else
 284              return FCKDragTableHandler._MouseDragHandler( FCK, evt ) ;
 285      },
 286      "_MouseFindHandler" : function( FCK, evt )
 287      {
 288          if ( FCK.MouseDownFlag )
 289              return ;
 290          var node = evt.srcElement || evt.target ;
 291          try
 292          {
 293              if ( ! node || node.nodeType != 1 )
 294              {
 295                  this._HideResizeBar() ;
 296                  return ;
 297              }
 298          }
 299          catch ( e )
 300          {
 301              this._HideResizeBar() ;
 302              return ;
 303          }
 304  
 305          // Since this function might be called from the editing area iframe or the outer fckeditor iframe,
 306          // the mouse point coordinates from evt.clientX/Y can have different reference points.
 307          // We need to resolve the mouse pointer position relative to the editing area iframe.
 308          var mouseX = evt.clientX ;
 309          var mouseY = evt.clientY ;
 310          if ( FCKTools.GetElementDocument( node ) == document )
 311          {
 312              var offset = this._GetIframeOffset() ;
 313              mouseX -= offset.x ;
 314              mouseY -= offset.y ;
 315          }
 316  
 317  
 318          if ( this._ResizeBar && this._LeftCell )
 319          {
 320              var leftPos = FCKTools.GetWindowPosition( FCK.EditorWindow, this._LeftCell ) ;
 321              var rightPos = FCKTools.GetWindowPosition( FCK.EditorWindow, this._RightCell ) ;
 322              var rxDist = mouseX - ( leftPos.x + this._LeftCell.clientWidth ) ;
 323              var lxDist = mouseX - rightPos.x ;
 324              var inRangeFlag = false ;
 325              if ( lxDist >= 0 && rxDist <= 0 )
 326                  inRangeFlag = true ;
 327              else if ( rxDist > 0 && lxDist <= 3 )
 328                  inRangeFlag = true ;
 329              else if ( lxDist < 0 && rxDist >= -2 )
 330                  inRangeFlag = true ;
 331              if ( inRangeFlag )
 332              {
 333                  this._ShowResizeBar( FCK.EditorWindow,
 334                      FCKTools.GetElementAscensor( this._LeftCell, "table" ),
 335                      { "x" : mouseX, "y" : mouseY } ) ;
 336                  return ;
 337              }
 338          }
 339  
 340          var tagName = node.tagName.toLowerCase() ;
 341          if ( tagName != "table" && tagName != "td" && tagName != "th" )
 342          {
 343              if ( this._LeftCell )
 344                  this._LeftCell = this._RightCell = this._TableMap = null ;
 345              this._HideResizeBar() ;
 346              return ;
 347          }
 348          node = FCKTools.GetElementAscensor( node, "table" ) ;
 349          var tableMap = FCKTableHandler._CreateTableMap( node ) ;
 350          var cellTuple = this._GetBorderCells( FCK.EditorWindow, node, tableMap, { "x" : mouseX, "y" : mouseY } ) ;
 351  
 352          if ( cellTuple == null )
 353          {
 354              if ( this._LeftCell )
 355                  this._LeftCell = this._RightCell = this._TableMap = null ;
 356              this._HideResizeBar() ;
 357          }
 358          else
 359          {
 360              this._LeftCell = cellTuple["leftCell"] ;
 361              this._RightCell = cellTuple["rightCell"] ;
 362              this._TableMap = tableMap ;
 363              this._ShowResizeBar( FCK.EditorWindow,
 364                      FCKTools.GetElementAscensor( this._LeftCell, "table" ),
 365                      { "x" : mouseX, "y" : mouseY } ) ;
 366          }
 367      },
 368      "_MouseDragHandler" : function( FCK, evt )
 369      {
 370          var mouse = { "x" : evt.clientX, "y" : evt.clientY } ;
 371  
 372          // Convert mouse coordinates in reference to the outer iframe.
 373          var node = evt.srcElement || evt.target ;
 374          if ( FCKTools.GetElementDocument( node ) == FCK.EditorDocument )
 375          {
 376              var offset = this._GetIframeOffset() ;
 377              mouse.x += offset.x ;
 378              mouse.y += offset.y ;
 379          }
 380  
 381          // Calculate the mouse position delta and see if we've gone out of range.
 382          if ( mouse.x >= this._MaximumX - 5 )
 383              mouse.x = this._MaximumX - 5 ;
 384          if ( mouse.x <= this._MinimumX + 5 )
 385              mouse.x = this._MinimumX + 5 ;
 386  
 387          var docX = mouse.x + FCKTools.GetScrollPosition( window ).X ;
 388          this._ResizeBar.style.left = ( docX - this._ResizeBar.offsetWidth / 2 ) + "px" ;
 389          this._LastX = mouse.x ;
 390      },
 391      "_ShowResizeBar" : function( w, table, mouse )
 392      {
 393          if ( this._ResizeBar == null )
 394          {
 395              this._ResizeBar = this._doc.createElement( "div" ) ;
 396              var paddingBar = this._ResizeBar ;
 397              var paddingStyles = { 'position' : 'absolute', 'cursor' : 'e-resize' } ;
 398              if ( FCKBrowserInfo.IsIE )
 399                  paddingStyles.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=10,enabled=true)" ;
 400              else
 401                  paddingStyles.opacity = 0.10 ;
 402              FCKDomTools.SetElementStyles( paddingBar, paddingStyles ) ;
 403              this._avoidStyles( paddingBar );
 404              paddingBar.setAttribute('_fcktemp', true);
 405              this._doc.body.appendChild( paddingBar ) ;
 406              FCKTools.AddEventListener( paddingBar, "mousemove", this._ResizeBarMouseMoveListener ) ;
 407              FCKTools.AddEventListener( paddingBar, "mousedown", this._ResizeBarMouseDownListener ) ;
 408              FCKTools.AddEventListener( document, "mouseup", this._ResizeBarMouseUpListener ) ;
 409              FCKTools.AddEventListener( FCK.EditorDocument, "mouseup", this._ResizeBarMouseUpListener ) ;
 410  
 411              // IE doesn't let the tranparent part of the padding block to receive mouse events unless there's something inside.
 412              // So we need to create a spacer image to fill the block up.
 413              var filler = this._doc.createElement( "img" ) ;
 414              filler.setAttribute('_fcktemp', true);
 415              filler.border = 0 ;
 416              filler.src = FCKConfig.BasePath + "images/spacer.gif" ;
 417              filler.style.position = "absolute" ;
 418              paddingBar.appendChild( filler ) ;
 419  
 420              // Disable drag and drop, and selection for the filler image.
 421              var disabledListener = function( evt )
 422              {
 423                  if ( evt.preventDefault )
 424                      evt.preventDefault() ;
 425                  else
 426                      evt.returnValue = false ;
 427              }
 428              FCKTools.AddEventListener( filler, "dragstart", disabledListener ) ;
 429              FCKTools.AddEventListener( filler, "selectstart", disabledListener ) ;
 430          }
 431  
 432          var paddingBar = this._ResizeBar ;
 433          var offset = this._GetIframeOffset() ;
 434          var tablePos = this._GetTablePosition( w, table ) ;
 435          var barHeight = table.offsetHeight ;
 436          var barTop = offset.y + tablePos.y ;
 437          // Do not let the resize bar intrude into the toolbar area.
 438          if ( tablePos.y < 0 )
 439          {
 440              barHeight += tablePos.y ;
 441              barTop -= tablePos.y ;
 442          }
 443          var bw = parseInt( table.border, 10 ) ;
 444          if ( isNaN( bw ) )
 445              bw = 0 ;
 446          var cs = parseInt( table.cellSpacing, 10 ) ;
 447          if ( isNaN( cs ) )
 448              cs = 0 ;
 449          var barWidth = Math.max( bw+100, cs+100 ) ;
 450          var paddingStyles =
 451          {
 452              'top'        : barTop + 'px',
 453              'height'    : barHeight + 'px',
 454              'width'        : barWidth + 'px',
 455              'left'        : ( offset.x + mouse.x + FCKTools.GetScrollPosition( w ).X - barWidth / 2 ) + 'px'
 456          } ;
 457          if ( FCKBrowserInfo.IsIE )
 458              paddingBar.filters.item("DXImageTransform.Microsoft.Alpha").opacity = 10 ;
 459          else
 460              paddingStyles.opacity = 0.1 ;
 461  
 462          FCKDomTools.SetElementStyles( paddingBar, paddingStyles ) ;
 463          var filler = paddingBar.getElementsByTagName( "img" )[0] ;
 464  
 465          FCKDomTools.SetElementStyles( filler,
 466              {
 467                  width    : paddingBar.offsetWidth + 'px',
 468                  height    : barHeight + 'px'
 469              } ) ;
 470  
 471          barWidth = Math.max( bw, cs, 3 ) ;
 472          var visibleBar = null ;
 473          if ( paddingBar.getElementsByTagName( "div" ).length < 1 )
 474          {
 475              visibleBar = this._doc.createElement( "div" ) ;
 476              this._avoidStyles( visibleBar );
 477              visibleBar.setAttribute('_fcktemp', true);
 478              paddingBar.appendChild( visibleBar ) ;
 479          }
 480          else
 481              visibleBar = paddingBar.getElementsByTagName( "div" )[0] ;
 482  
 483          FCKDomTools.SetElementStyles( visibleBar,
 484              {
 485                  position        : 'absolute',
 486                  backgroundColor    : 'blue',
 487                  width            : barWidth + 'px',
 488                  height            : barHeight + 'px',
 489                  left            : '50px',
 490                  top                : '0px'
 491              } ) ;
 492      },
 493      "_HideResizeBar" : function()
 494      {
 495          if ( this._ResizeBar )
 496              // IE bug: display : none does not hide the resize bar for some reason.
 497              // so set the position to somewhere invisible.
 498              FCKDomTools.SetElementStyles( this._ResizeBar,
 499                  {
 500                      top        : '-100000px',
 501                      left    : '-100000px'
 502                  } ) ;
 503      },
 504      "_GetIframeOffset" : function ()
 505      {
 506          return FCKTools.GetDocumentPosition( window, FCK.EditingArea.IFrame ) ;
 507      },
 508      "_GetTablePosition" : function ( w, table )
 509      {
 510          return FCKTools.GetWindowPosition( w, table ) ;
 511      },
 512      "_avoidStyles" : function( element )
 513      {
 514          FCKDomTools.SetElementStyles( element,
 515              {
 516                  padding        : '0',
 517                  backgroundImage    : 'none',
 518                  border        : '0'
 519              } ) ;
 520      },
 521      "Reset" : function()
 522      {
 523          FCKDragTableHandler._LeftCell = FCKDragTableHandler._RightCell = FCKDragTableHandler._TableMap = null ;
 524      }
 525  
 526  };
 527  
 528  FCK.Events.AttachEvent( "OnMouseMove", FCKDragTableHandler.MouseMoveListener ) ;
 529  FCK.Events.AttachEvent( "OnAfterSetHTML", FCKDragTableHandler.Reset ) ;


Generated: Wed Dec 30 05:55:15 2009 Cross-referenced by PHPXref 0.7