[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/includes/ -> functions_transfer.php (source)

   1  <?php
   2  /**
   3  *
   4  * @package phpBB3
   5  * @version $Id$
   6  * @copyright (c) 2005 phpBB Group
   7  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
   8  *
   9  */
  10  
  11  /**
  12  * @ignore
  13  */
  14  if (!defined('IN_PHPBB'))
  15  {
  16      exit;
  17  }
  18  
  19  /**
  20  * Transfer class, wrapper for ftp/sftp/ssh
  21  * @package phpBB3
  22  */
  23  class transfer
  24  {
  25      var $connection;
  26      var $host;
  27      var $port;
  28      var $username;
  29      var $password;
  30      var $timeout;
  31      var $root_path;
  32      var $tmp_path;
  33      var $file_perms;
  34      var $dir_perms;
  35  
  36      /**
  37      * Constructor - init some basic values
  38      */
  39  	function transfer()
  40      {
  41          global $phpbb_root_path;
  42  
  43          $this->file_perms    = 0644;
  44          $this->dir_perms    = 0777;
  45  
  46          // We use the store directory as temporary path to circumvent open basedir restrictions
  47          $this->tmp_path = $phpbb_root_path . 'store/';
  48      }
  49  
  50      /**
  51      * Write file to location
  52      */
  53  	function write_file($destination_file = '', $contents = '')
  54      {
  55          global $phpbb_root_path;
  56  
  57          $destination_file = $this->root_path . str_replace($phpbb_root_path, '', $destination_file);
  58  
  59          // need to create a temp file and then move that temp file.
  60          // ftp functions can only move files around and can't create.
  61          // This means that the users will need to have access to write
  62          // temporary files or have write access on a folder within phpBB
  63          // like the cache folder. If the user can't do either, then
  64          // he/she needs to use the fsock ftp method
  65          $temp_name = tempnam($this->tmp_path, 'transfer_');
  66          @unlink($temp_name);
  67  
  68          $fp = @fopen($temp_name, 'w');
  69  
  70          if (!$fp)
  71          {
  72              trigger_error('Unable to create temporary file ' . $temp_name, E_USER_ERROR);
  73          }
  74  
  75          @fwrite($fp, $contents);
  76          @fclose($fp);
  77  
  78          $result = $this->overwrite_file($temp_name, $destination_file);
  79  
  80          // remove temporary file now
  81          @unlink($temp_name);
  82  
  83          return $result;
  84      }
  85  
  86      /**
  87      * Moving file into location. If the destination file already exists it gets overwritten
  88      */
  89  	function overwrite_file($source_file, $destination_file)
  90      {
  91          /**
  92          * @todo generally think about overwriting files in another way, by creating a temporary file and then renaming it
  93          * @todo check for the destination file existance too
  94          */
  95          $this->_delete($destination_file);
  96          $result = $this->_put($source_file, $destination_file);
  97          $this->_chmod($destination_file, $this->file_perms);
  98  
  99          return $result;
 100      }
 101  
 102      /**
 103      * Create directory structure
 104      */
 105  	function make_dir($dir)
 106      {
 107          global $phpbb_root_path;
 108  
 109          $dir = str_replace($phpbb_root_path, '', $dir);
 110          $dir = explode('/', $dir);
 111          $dirs = '';
 112  
 113          for ($i = 0, $total = sizeof($dir); $i < $total; $i++)
 114          {
 115              $result = true;
 116  
 117              if (strpos($dir[$i], '.') === 0)
 118              {
 119                  continue;
 120              }
 121              $cur_dir = $dir[$i] . '/';
 122  
 123              if (!file_exists($phpbb_root_path . $dirs . $cur_dir))
 124              {
 125                  // create the directory
 126                  $result = $this->_mkdir($dir[$i]);
 127                  $this->_chmod($dir[$i], $this->dir_perms);
 128              }
 129  
 130              $this->_chdir($this->root_path . $dirs . $dir[$i]);
 131              $dirs .= $cur_dir;
 132          }
 133  
 134          $this->_chdir($this->root_path);
 135  
 136          /**
 137          * @todo stack result into array to make sure every path creation has been taken care of
 138          */
 139          return $result;
 140      }
 141  
 142      /**
 143      * Copy file from source location to destination location
 144      */
 145  	function copy_file($from_loc, $to_loc)
 146      {
 147          global $phpbb_root_path;
 148  
 149          $from_loc = ((strpos($from_loc, $phpbb_root_path) !== 0) ? $phpbb_root_path : '') . $from_loc;
 150          $to_loc = $this->root_path . str_replace($phpbb_root_path, '', $to_loc);
 151  
 152          if (!file_exists($from_loc))
 153          {
 154              return false;
 155          }
 156  
 157          $result = $this->overwrite_file($from_loc, $to_loc);
 158  
 159          return $result;
 160      }
 161  
 162      /**
 163      * Remove file
 164      */
 165  	function delete_file($file)
 166      {
 167          global $phpbb_root_path;
 168  
 169          $file = $this->root_path . str_replace($phpbb_root_path, '', $file);
 170  
 171          return $this->_delete($file);
 172      }
 173  
 174      /**
 175      * Remove directory
 176      * @todo remove child directories?
 177      */
 178  	function remove_dir($dir)
 179      {
 180          global $phpbb_root_path;
 181  
 182          $dir = $this->root_path . str_replace($phpbb_root_path, '', $dir);
 183  
 184          return $this->_rmdir($dir);
 185      }
 186  
 187      /**
 188      * Rename a file or folder
 189      */
 190  	function rename($old_handle, $new_handle)
 191      {
 192          global $phpbb_root_path;
 193  
 194          $old_handle = $this->root_path . str_replace($phpbb_root_path, '', $old_handle);
 195  
 196          return $this->_rename($old_handle, $new_handle);
 197      }
 198  
 199      /**
 200      * Check if a specified file exist...
 201      */
 202  	function file_exists($directory, $filename)
 203      {
 204          global $phpbb_root_path;
 205  
 206          $directory = $this->root_path . str_replace($phpbb_root_path, '', $directory);
 207  
 208          $this->_chdir($directory);
 209          $result = $this->_ls();
 210  
 211          if ($result !== false && is_array($result))
 212          {
 213              return (in_array($filename, $result)) ? true : false;
 214          }
 215  
 216          return false;
 217      }
 218  
 219      /**
 220      * Open session
 221      */
 222  	function open_session()
 223      {
 224          return $this->_init();
 225      }
 226  
 227      /**
 228      * Close current session
 229      */
 230  	function close_session()
 231      {
 232          return $this->_close();
 233      }
 234  
 235      /**
 236      * Determine methods able to be used
 237      */
 238  	function methods()
 239      {
 240          $methods = array();
 241          $disabled_functions = explode(',', @ini_get('disable_functions'));
 242  
 243          if (@extension_loaded('ftp'))
 244          {
 245              $methods[] = 'ftp';
 246          }
 247  
 248          if (!in_array('fsockopen', $disabled_functions))
 249          {
 250              $methods[] = 'ftp_fsock';
 251          }
 252  
 253          return $methods;
 254      }
 255  }
 256  
 257  /**
 258  * FTP transfer class
 259  * @package phpBB3
 260  */
 261  class ftp extends transfer
 262  {
 263      /**
 264      * Standard parameters for FTP session
 265      */
 266  	function ftp($host, $username, $password, $root_path, $port = 21, $timeout = 10)
 267      {
 268          $this->host            = $host;
 269          $this->port            = $port;
 270          $this->username        = $username;
 271          $this->password        = $password;
 272          $this->timeout        = $timeout;
 273  
 274          // Make sure $this->root_path is layed out the same way as the $user->page['root_script_path'] value (/ at the end)
 275          $this->root_path    = str_replace('\\', '/', $this->root_path);
 276  
 277          if (!empty($root_path))
 278          {
 279              $this->root_path = (($root_path[0] != '/' ) ? '/' : '') . $root_path . ((substr($root_path, -1, 1) == '/') ? '' : '/');
 280          }
 281  
 282          // Init some needed values
 283          transfer::transfer();
 284  
 285          return;
 286      }
 287  
 288      /**
 289      * Requests data
 290      */
 291  	function data()
 292      {
 293          global $user;
 294  
 295          return array(
 296              'host'        => 'localhost',
 297              'username'    => 'anonymous',
 298              'password'    => '',
 299              'root_path'    => $user->page['root_script_path'],
 300              'port'        => 21,
 301              'timeout'    => 10
 302          );
 303      }
 304  
 305      /**
 306      * Init FTP Session
 307      * @access private
 308      */
 309  	function _init()
 310      {
 311          // connect to the server
 312          $this->connection = @ftp_connect($this->host, $this->port, $this->timeout);
 313  
 314          if (!$this->connection)
 315          {
 316              return 'ERR_CONNECTING_SERVER';
 317          }
 318  
 319          // login to the server
 320          if (!@ftp_login($this->connection, $this->username, $this->password))
 321          {
 322              return 'ERR_UNABLE_TO_LOGIN';
 323          }
 324  
 325          // attempt to turn pasv mode on
 326          @ftp_pasv($this->connection, true);
 327  
 328          // change to the root directory
 329          if (!$this->_chdir($this->root_path))
 330          {
 331              return 'ERR_CHANGING_DIRECTORY';
 332          }
 333  
 334          return true;
 335      }
 336  
 337      /**
 338      * Create Directory (MKDIR)
 339      * @access private
 340      */
 341  	function _mkdir($dir)
 342      {
 343          return @ftp_mkdir($this->connection, $dir);
 344      }
 345  
 346      /**
 347      * Remove directory (RMDIR)
 348      * @access private
 349      */
 350  	function _rmdir($dir)
 351      {
 352          return @ftp_rmdir($this->connection, $dir);
 353      }
 354  
 355      /**
 356      * Rename file
 357      * @access private
 358      */
 359  	function _rename($old_handle, $new_handle)
 360      {
 361          return @ftp_rename($this->connection, $old_handle, $new_handle);
 362      }
 363  
 364      /**
 365      * Change current working directory (CHDIR)
 366      * @access private
 367      */
 368  	function _chdir($dir = '')
 369      {
 370          if ($dir && $dir !== '/')
 371          {
 372              if (substr($dir, -1, 1) == '/')
 373              {
 374                  $dir = substr($dir, 0, -1);
 375              }
 376          }
 377  
 378          return @ftp_chdir($this->connection, $dir);
 379      }
 380  
 381      /**
 382      * change file permissions (CHMOD)
 383      * @access private
 384      */
 385  	function _chmod($file, $perms)
 386      {
 387          if (function_exists('ftp_chmod'))
 388          {
 389              $err = @ftp_chmod($this->connection, $perms, $file);
 390          }
 391          else
 392          {
 393              // Unfortunatly CHMOD is not expecting an octal value...
 394              // We need to transform the integer (which was an octal) to an octal representation (to get the int) and then pass as is. ;)
 395              $chmod_cmd = 'CHMOD ' . base_convert($perms, 10, 8) . ' ' . $file;
 396              $err = $this->_site($chmod_cmd);
 397          }
 398  
 399          return $err;
 400      }
 401  
 402      /**
 403      * Upload file to location (PUT)
 404      * @access private
 405      */
 406  	function _put($from_file, $to_file)
 407      {
 408          // get the file extension
 409          $file_extension = strtolower(substr(strrchr($to_file, '.'), 1));
 410  
 411          // We only use the BINARY file mode to cicumvent rewrite actions from ftp server (mostly linefeeds being replaced)
 412          $mode = FTP_BINARY;
 413  
 414          $to_dir = dirname($to_file);
 415          $to_file = basename($to_file);
 416          $this->_chdir($to_dir);
 417  
 418          $result = @ftp_put($this->connection, $to_file, $from_file, $mode);
 419          $this->_chdir($this->root_path);
 420  
 421          return $result;
 422      }
 423  
 424      /**
 425      * Delete file (DELETE)
 426      * @access private
 427      */
 428  	function _delete($file)
 429      {
 430          return @ftp_delete($this->connection, $file);
 431      }
 432  
 433      /**
 434      * Close ftp session (CLOSE)
 435      * @access private
 436      */
 437  	function _close()
 438      {
 439          if (!$this->connection)
 440          {
 441              return false;
 442          }
 443  
 444          return @ftp_quit($this->connection);
 445      }
 446  
 447      /**
 448      * Return current working directory (CWD)
 449      * At the moment not used by parent class
 450      * @access private
 451      */
 452  	function _cwd()
 453      {
 454          return @ftp_pwd($this->connection);
 455      }
 456  
 457      /**
 458      * Return list of files in a given directory (LS)
 459      * @access private
 460      */
 461  	function _ls($dir = './')
 462      {
 463          $list = @ftp_nlist($this->connection, $dir);
 464  
 465          // See bug #46295 - Some FTP daemons don't like './'
 466          if ($dir === './')
 467          {
 468              // Let's try some alternatives
 469              $list = (empty($list)) ? @ftp_nlist($this->connection, '.') : $list;
 470              $list = (empty($list)) ? @ftp_nlist($this->connection, '') : $list;
 471          }
 472  
 473          // Return on error
 474          if ($list === false)
 475          {
 476              return false;
 477          }
 478  
 479          // Remove path if prepended
 480          foreach ($list as $key => $item)
 481          {
 482              // Use same separator for item and dir
 483              $item = str_replace('\\', '/', $item);
 484              $dir = str_replace('\\', '/', $dir);
 485  
 486              if (!empty($dir) && strpos($item, $dir) === 0)
 487              {
 488                  $item = substr($item, strlen($dir));
 489              }
 490  
 491              $list[$key] = $item;
 492          }
 493  
 494          return $list;
 495      }
 496  
 497      /**
 498      * FTP SITE command (ftp-only function)
 499      * @access private
 500      */
 501  	function _site($command)
 502      {
 503          return @ftp_site($this->connection, $command);
 504      }
 505  }
 506  
 507  /**
 508  * FTP fsock transfer class
 509  *
 510  * @author wGEric
 511  * @package phpBB3
 512  */
 513  class ftp_fsock extends transfer
 514  {
 515      var $data_connection;
 516  
 517      /**
 518      * Standard parameters for FTP session
 519      */
 520  	function ftp_fsock($host, $username, $password, $root_path, $port = 21, $timeout = 10)
 521      {
 522          $this->host            = $host;
 523          $this->port            = $port;
 524          $this->username        = $username;
 525          $this->password        = $password;
 526          $this->timeout        = $timeout;
 527  
 528          // Make sure $this->root_path is layed out the same way as the $user->page['root_script_path'] value (/ at the end)
 529          $this->root_path    = str_replace('\\', '/', $this->root_path);
 530  
 531          if (!empty($root_path))
 532          {
 533              $this->root_path = (($root_path[0] != '/' ) ? '/' : '') . $root_path . ((substr($root_path, -1, 1) == '/') ? '' : '/');
 534          }
 535  
 536          // Init some needed values
 537          transfer::transfer();
 538  
 539          return;
 540      }
 541  
 542      /**
 543      * Requests data
 544      */
 545  	function data()
 546      {
 547          global $user;
 548  
 549          return array(
 550              'host'        => 'localhost',
 551              'username'    => 'anonymous',
 552              'password'    => '',
 553              'root_path'    => $user->page['root_script_path'],
 554              'port'        => 21,
 555              'timeout'    => 10
 556          );
 557      }
 558  
 559      /**
 560      * Init FTP Session
 561      * @access private
 562      */
 563  	function _init()
 564      {
 565          $errno = 0;
 566          $errstr = '';
 567  
 568          // connect to the server
 569          $this->connection = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
 570  
 571          if (!$this->connection || !$this->_check_command())
 572          {
 573              return 'ERR_CONNECTING_SERVER';
 574          }
 575  
 576          @stream_set_timeout($this->connection, $this->timeout);
 577  
 578          // login
 579          if (!$this->_send_command('USER', $this->username))
 580          {
 581              return 'ERR_UNABLE_TO_LOGIN';
 582          }
 583  
 584          if (!$this->_send_command('PASS', $this->password))
 585          {
 586              return 'ERR_UNABLE_TO_LOGIN';
 587          }
 588  
 589          // change to the root directory
 590          if (!$this->_chdir($this->root_path))
 591          {
 592              return 'ERR_CHANGING_DIRECTORY';
 593          }
 594  
 595          return true;
 596      }
 597  
 598      /**
 599      * Create Directory (MKDIR)
 600      * @access private
 601      */
 602  	function _mkdir($dir)
 603      {
 604          return $this->_send_command('MKD', $dir);
 605      }
 606  
 607      /**
 608      * Remove directory (RMDIR)
 609      * @access private
 610      */
 611  	function _rmdir($dir)
 612      {
 613          return $this->_send_command('RMD', $dir);
 614      }
 615  
 616      /**
 617      * Rename File
 618      * @access private
 619      */
 620  	function _rename($old_handle, $new_handle)
 621      {
 622          $this->_send_command('RNFR', $old_handle);
 623          return $this->_send_command('RNTO', $new_handle);
 624      }
 625  
 626      /**
 627      * Change current working directory (CHDIR)
 628      * @access private
 629      */
 630  	function _chdir($dir = '')
 631      {
 632          if ($dir && $dir !== '/')
 633          {
 634              if (substr($dir, -1, 1) == '/')
 635              {
 636                  $dir = substr($dir, 0, -1);
 637              }
 638          }
 639  
 640          return $this->_send_command('CWD', $dir);
 641      }
 642  
 643      /**
 644      * change file permissions (CHMOD)
 645      * @access private
 646      */
 647  	function _chmod($file, $perms)
 648      {
 649          // Unfortunatly CHMOD is not expecting an octal value...
 650          // We need to transform the integer (which was an octal) to an octal representation (to get the int) and then pass as is. ;)
 651          return $this->_send_command('SITE CHMOD', base_convert($perms, 10, 8) . ' ' . $file);
 652      }
 653  
 654      /**
 655      * Upload file to location (PUT)
 656      * @access private
 657      */
 658  	function _put($from_file, $to_file)
 659      {
 660          // We only use the BINARY file mode to cicumvent rewrite actions from ftp server (mostly linefeeds being replaced)
 661          // 'I' == BINARY
 662          // 'A' == ASCII
 663          if (!$this->_send_command('TYPE', 'I'))
 664          {
 665              return false;
 666          }
 667  
 668          // open the connection to send file over
 669          if (!$this->_open_data_connection())
 670          {
 671              return false;
 672          }
 673  
 674          $this->_send_command('STOR', $to_file, false);
 675  
 676          // send the file
 677          $fp = @fopen($from_file, 'rb');
 678          while (!@feof($fp))
 679          {
 680              @fwrite($this->data_connection, @fread($fp, 4096));
 681          }
 682          @fclose($fp);
 683  
 684          // close connection
 685          $this->_close_data_connection();
 686  
 687          return $this->_check_command();
 688      }
 689  
 690      /**
 691      * Delete file (DELETE)
 692      * @access private
 693      */
 694  	function _delete($file)
 695      {
 696          return $this->_send_command('DELE', $file);
 697      }
 698  
 699      /**
 700      * Close ftp session (CLOSE)
 701      * @access private
 702      */
 703  	function _close()
 704      {
 705          if (!$this->connection)
 706          {
 707              return false;
 708          }
 709  
 710          return $this->_send_command('QUIT');
 711      }
 712  
 713      /**
 714      * Return current working directory (CWD)
 715      * At the moment not used by parent class
 716      * @access private
 717      */
 718  	function _cwd()
 719      {
 720          $this->_send_command('PWD', '', false);
 721          return preg_replace('#^[0-9]{3} "(.+)" .+\r\n#', '\\1', $this->_check_command(true));
 722      }
 723  
 724      /**
 725      * Return list of files in a given directory (LS)
 726      * @access private
 727      */
 728  	function _ls($dir = './')
 729      {
 730          if (!$this->_open_data_connection())
 731          {
 732              return false;
 733          }
 734  
 735          $this->_send_command('NLST', $dir);
 736  
 737          $list = array();
 738          while (!@feof($this->data_connection))
 739          {
 740              $filename = preg_replace('#[\r\n]#', '', @fgets($this->data_connection, 512));
 741  
 742              if ($filename !== '')
 743              {
 744                  $list[] = $filename;
 745              }
 746          }
 747          $this->_close_data_connection();
 748  
 749          // Clear buffer
 750          $this->_check_command();
 751  
 752          // See bug #46295 - Some FTP daemons don't like './'
 753          if ($dir === './' && empty($list))
 754          {
 755              // Let's try some alternatives
 756              $list = $this->_ls('.');
 757  
 758              if (empty($list))
 759              {
 760                  $list = $this->_ls('');
 761              }
 762  
 763              return $list;
 764          }
 765  
 766          // Remove path if prepended
 767          foreach ($list as $key => $item)
 768          {
 769              // Use same separator for item and dir
 770              $item = str_replace('\\', '/', $item);
 771              $dir = str_replace('\\', '/', $dir);
 772  
 773              if (!empty($dir) && strpos($item, $dir) === 0)
 774              {
 775                  $item = substr($item, strlen($dir));
 776              }
 777  
 778              $list[$key] = $item;
 779          }
 780  
 781          return $list;
 782      }
 783  
 784      /**
 785      * Send a command to server (FTP fsock only function)
 786      * @access private
 787      */
 788  	function _send_command($command, $args = '', $check = true)
 789      {
 790          if (!empty($args))
 791          {
 792              $command = "$command $args";
 793          }
 794  
 795          fwrite($this->connection, $command . "\r\n");
 796  
 797          if ($check === true && !$this->_check_command())
 798          {
 799              return false;
 800          }
 801  
 802          return true;
 803      }
 804  
 805      /**
 806      * Opens a connection to send data (FTP fosck only function)
 807      * @access private
 808      */
 809  	function _open_data_connection()
 810      {
 811          // Try to find out whether we have a IPv4 or IPv6 (control) connection
 812          if (function_exists('stream_socket_get_name'))
 813          {
 814              $socket_name = stream_socket_get_name($this->connection, true);
 815              $server_ip = substr($socket_name, 0, strrpos($socket_name, ':'));
 816          }
 817  
 818          if (!isset($server_ip) || preg_match(get_preg_expression('ipv4'), $server_ip))
 819          {
 820              // Passive mode
 821              $this->_send_command('PASV', '', false);
 822  
 823              if (!$ip_port = $this->_check_command(true))
 824              {
 825                  return false;
 826              }
 827  
 828              // open the connection to start sending the file
 829              if (!preg_match('#[0-9]{1,3},[0-9]{1,3},[0-9]{1,3},[0-9]{1,3},[0-9]+,[0-9]+#', $ip_port, $temp))
 830              {
 831                  // bad ip and port
 832                  return false;
 833              }
 834  
 835              $temp = explode(',', $temp[0]);
 836              $server_ip = $temp[0] . '.' . $temp[1] . '.' . $temp[2] . '.' . $temp[3];
 837              $server_port = $temp[4] * 256 + $temp[5];
 838          }
 839          else
 840          {
 841              // Extended Passive Mode - RFC2428
 842              $this->_send_command('EPSV', '', false);
 843  
 844              if (!$epsv_response = $this->_check_command(true))
 845              {
 846                  return false;
 847              }
 848  
 849              // Response looks like "229 Entering Extended Passive Mode (|||12345|)"
 850              // where 12345 is the tcp port for the data connection
 851              if (!preg_match('#\(\|\|\|([0-9]+)\|\)#', $epsv_response, $match))
 852              {
 853                  return false;
 854              }
 855              $server_port = (int) $match[1];
 856  
 857              // fsockopen expects IPv6 address in square brackets
 858              $server_ip = "[$server_ip]";
 859          }
 860  
 861          $errno = 0;
 862          $errstr = '';
 863  
 864          if (!$this->data_connection = @fsockopen($server_ip, $server_port, $errno, $errstr, $this->timeout))
 865          {
 866              return false;
 867          }
 868          @stream_set_timeout($this->data_connection, $this->timeout);
 869  
 870          return true;
 871      }
 872  
 873      /**
 874      * Closes a connection used to send data
 875      * @access private
 876      */
 877  	function _close_data_connection()
 878      {
 879          return @fclose($this->data_connection);
 880      }
 881  
 882      /**
 883      * Check to make sure command was successful (FTP fsock only function)
 884      * @access private
 885      */
 886  	function _check_command($return = false)
 887      {
 888          $response = '';
 889  
 890          do
 891          {
 892              $result = @fgets($this->connection, 512);
 893              $response .= $result;
 894          }
 895          while (substr($result, 3, 1) !== ' ');
 896  
 897          if (!preg_match('#^[123]#', $response))
 898          {
 899              return false;
 900          }
 901  
 902          return ($return) ? $response : true;
 903      }
 904  }
 905  
 906  ?>


Generated: Wed Oct 2 15:03:47 2013 Cross-referenced by PHPXref 0.7.1