[ Index ]

PHP Cross Reference of phpBB-3.1.10-deutsch

title

Body

[close]

/includes/ -> functions_transfer.php (source)

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


Generated: Sun Feb 19 19:52:41 2017 Cross-referenced by PHPXref 0.7.1