Subversion Repositories oidplus

Rev

Rev 506 | Go to most recent revision | Blame | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * This file includes:
  5.  *
  6.  * 1. "PHP SVN CLIENT" class
  7.  *    Copyright (C) 2007-2008 by Sixdegrees <cesar@sixdegrees.com.br>
  8.  *    Cesar D. Rodas
  9.  *    https://code.google.com/archive/p/phpsvnclient/
  10.  *    License: BSD License
  11.  *    CHANGES by Daniel Marschall, ViaThinkSoft in 2019-2021:
  12.  *    - The class has been customized and contains specific changes for the software "OIDplus"
  13.  *    - Functions which are not used in the "SVN checkout" were removed.
  14.  *      The only important functions are getVersion() and updateWorkingCopy()
  15.  *    - The dependency class xml2array was removed and instead, SimpleXML is used
  16.  *    - Added "revision log/comment" functionality
  17.  *
  18.  * 2. "xml2array" class
  19.  *    Taken from http://www.php.net/manual/en/function.xml-parse.php#52567
  20.  *    Modified by Martin Guppy <http://www.deadpan110.com/>
  21.  *    CHANGES by Daniel Marschall, ViaThinkSoft in 2019:
  22.  *    - Converted class into a single function and added that function into the phpsvnclient class
  23.  */
  24.  
  25. if (!function_exists('_L')) {
  26.         function _L($str, ...$sprintfArgs) {
  27.                 $n = 1;
  28.                 foreach ($sprintfArgs as $val) {
  29.                         $str = str_replace("%$n", $val, $str);
  30.                         $n++;
  31.                 }
  32.                 $str = str_replace("%%", "%", $str);
  33.                 return $str;
  34.         }
  35. }
  36.  
  37. /**
  38.  *  PHP SVN CLIENT
  39.  *
  40.  *  This class is an SVN client. It can perform read operations
  41.  *  to an SVN server (over Web-DAV).
  42.  *  It can get directory files, file contents, logs. All the operaration
  43.  *  could be done for a specific version or for the last version.
  44.  *
  45.  *  @author Cesar D. Rodas <cesar@sixdegrees.com.br>
  46.  *  @license BSD License
  47.  */
  48. class phpsvnclient {
  49.  
  50.         /*protected*/ const PHPSVN_NORMAL_REQUEST = '<?xml version="1.0" encoding="utf-8"?><propfind xmlns="DAV:"><prop><getlastmodified xmlns="DAV:"/> <checked-in xmlns="DAV:"/><version-name xmlns="DAV:"/><version-controlled-configuration xmlns="DAV:"/><resourcetype xmlns="DAV:"/><baseline-relative-path xmlns="http://subversion.tigris.org/xmlns/dav/"/><repository-uuid xmlns="http://subversion.tigris.org/xmlns/dav/"/></prop></propfind>';
  51.  
  52.         /*protected*/ const PHPSVN_VERSION_REQUEST = '<?xml version="1.0" encoding="utf-8"?><propfind xmlns="DAV:"><prop><checked-in xmlns="DAV:"/></prop></propfind>';
  53.  
  54.         /*protected*/ const PHPSVN_LOGS_REQUEST = '<?xml version="1.0" encoding="utf-8"?> <S:log-report xmlns:S="svn:"> <S:start-revision>%d</S:start-revision><S:end-revision>%d</S:end-revision><S:path></S:path><S:discover-changed-paths/></S:log-report>';
  55.  
  56.         /*public*/ const NO_ERROR = 1;
  57.         /*public*/ const NOT_FOUND = 2;
  58.         /*public*/ const AUTH_REQUIRED = 3;
  59.         /*public*/ const UNKNOWN_ERROR = 4;
  60.  
  61.         public $use_cache = true;
  62.         public $cache_dir = __DIR__.'/../../userdata/cache';
  63.  
  64.         /**
  65.          *  SVN Repository URL
  66.          *
  67.          *  @var string
  68.          *  @access private
  69.          */
  70.         private $_url;
  71.  
  72.         /**
  73.          *  HTTP Client object
  74.          *
  75.          *  @var object
  76.          *  @access private
  77.          */
  78.         private $_http;
  79.  
  80.         /**
  81.          *  Respository Version.
  82.          *
  83.          *  @access private
  84.          *  @var int
  85.          */
  86.         private $_repVersion;
  87.  
  88.         /**
  89.          *  Last error number
  90.          *
  91.          *  Possible values are NO_ERROR, NOT_FOUND, AUTH_REQUIRED, UNKOWN_ERROR
  92.          *
  93.          *  @access protected
  94.          *  @var integer
  95.          */
  96.         protected $errNro = self::NO_ERROR;
  97.         public function getLastError() {
  98.                 return $this->errNro;
  99.         }
  100.  
  101.         /**
  102.          * Number of actual revision local repository.
  103.          * @var Integer, Long
  104.          */
  105.         private $actVersion;
  106.         private $file_size;
  107.         private $file_size_founded = false;
  108.  
  109.         public function __construct($url)
  110.         {
  111.                 $http =& $this->_http;
  112.                 $http             = new http_class;
  113.                 $http->user_agent = "phpsvnclient (https://code.google.com/archive/p/phpsvnclient/)";
  114.  
  115.                 $this->_url = $url;
  116.  
  117.                 $this->actVersion = $this->getVersion();
  118.         }
  119.  
  120.         /**
  121.          * Function for creating directories.
  122.          * @param $path (string) The path to the directory that will be created.
  123.          */
  124.         private function createDirs($path)
  125.         {
  126.                 $dirs = explode("/", $path);
  127.  
  128.                 foreach ($dirs as $dir) {
  129.                         if ($dir != "") {
  130.                                 $createDir = substr($path, 0, strpos($path, $dir) + strlen($dir));
  131.                                 @mkdir($createDir);
  132.                         }
  133.                 }
  134.         }
  135.  
  136.         /**
  137.          * Function for the recursive removal of directories.
  138.          * @param $path (string) The path to the directory to be deleted.
  139.          * @return (string) Returns the status of a function or function rmdir unlink.
  140.          */
  141.         private function removeDirs($path)
  142.         {
  143.                 if (is_dir($path)) {
  144.                         $entries = scandir($path);
  145.                         if ($entries === false) {
  146.                                 $entries = array();
  147.                         }
  148.                         foreach ($entries as $entry) {
  149.                                 if ($entry != '.' && $entry != '..') {
  150.                                         $this->removeDirs($path . '/' . $entry);
  151.                                 }
  152.                         }
  153.                         return @rmdir($path);
  154.                 } else {
  155.                         return @unlink($path);
  156.                 }
  157.         }
  158.  
  159.         /**
  160.          *  Public Functions
  161.          */
  162.  
  163.         /**
  164.         * Updates a working copy
  165.         * @param $from_revision (string) Either a revision number or a text file with the
  166.         *                       contents "Revision ..." (if it is a file,
  167.         *                       the file revision will be updated if everything
  168.         *                       was successful)
  169.         * @param $folder        (string) SVN remote folder
  170.         * @param $outpath       (string) Local path of the working copy
  171.         * @param $preview       (bool) Only simulate, do not write to files
  172.         **/
  173.         public function updateWorkingCopy($from_revision='version.txt', $folder = '/trunk/', $outPath = '.', $preview = false)
  174.         {
  175.                 if (!is_dir($outPath)) {
  176.                         echo _L("ERROR: Local path %1 not existing",$outPath)."\n";
  177.                         //flush();
  178.                         return false;
  179.                 }
  180.  
  181.                 $webbrowser_update = !is_numeric($from_revision);
  182.  
  183.                 if (!is_numeric($from_revision)) {
  184.                         $version_file = $from_revision;
  185.                         $from_revision = -1;
  186.  
  187.                         if (!file_exists($version_file)) {
  188.                                 echo _L("ERROR: %1 missing",$version_file)."\n";
  189.                                 //flush();
  190.                                 return false;
  191.                         } else {
  192.                                 //Obtain the number of current version number of the local copy.
  193.                                 $cont = file_get_contents($version_file);
  194.                                 $m = array();
  195.                                 if (!preg_match('@Revision (\d+)@', $cont, $m)) { // do not translate
  196.                                         echo _L("ERROR: %1 unknown format",$version_file)."\n";
  197.                                         //flush();
  198.                                         return false;
  199.                                 }
  200.                                 $from_revision = $m[1];
  201.  
  202.                                 echo _L("Found %1 with revision information %2",basename($version_file),$from_revision)."\n";
  203.                                 //flush();
  204.                         }
  205.                 } else {
  206.                         $version_file = '';
  207.                 }
  208.  
  209.                 $errors_happened = false;
  210.  
  211.                 if ($webbrowser_update) {
  212.                         // First, do some read/write test (even if we are in preview mode, because we want to detect errors before it is too late)
  213.                         $file = $outPath . '/dummy_'.uniqid().'.tmp';
  214.                         $file = str_replace("///", "/", $file);
  215.                         if (@file_put_contents($file, 'Write Test') === false) { // do not translate
  216.                                 echo (!$preview ? _L("ERROR") : _L("WARNING")).": "._L("Cannot write test file %1 ! An update through the web browser will NOT be possible.",$file)."\n";
  217.                                 //flush();
  218.                                 if (!$preview) return false;
  219.                         }
  220.                         @unlink($file);
  221.                         if (file_exists($file)) {
  222.                                 echo (!$preview ? _L("ERROR") : _L("WARNING")).": "._L("Cannot delete test file %1 ! An update through the web browser will NOT be possible.",$file)."\n";
  223.                                 //flush();
  224.                                 if (!$preview) return false;
  225.                         }
  226.                 }
  227.  
  228.                 //Get a list of objects to be updated.
  229.                 $objects_list = $this->getLogsForUpdate($folder, $from_revision + 1);
  230.                 if (!is_null($objects_list)) {
  231.                         // Output version information
  232.                         foreach ($objects_list['revisions'] as $revision) {
  233.                                 $comment = empty($revision['comment']) ? _L('No comment') : $revision['comment'];
  234.                                 $tex = _L("New revision %1 by %2",$revision['versionName'],$revision['creator'])." (".date('Y-m-d H:i:s', strtotime($revision['date'])).") ";
  235.                                 echo trim($tex . str_replace("\n", "\n".str_repeat(' ', strlen($tex)), $comment));
  236.                                 echo "\n";
  237.                         }
  238.  
  239.                         // Add dirs
  240.                         sort($objects_list['dirs']); // <-- added by Daniel Marschall: Sort folder list, so that directories will be created in the correct hierarchical order
  241.                         foreach ($objects_list['dirs'] as $file) {
  242.                                 if ($file != '') {
  243.                                         $localPath = str_replace($folder, "", $file);
  244.                                         $localPath = rtrim($outPath,DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . ltrim($localPath,DIRECTORY_SEPARATOR);
  245.  
  246.                                         echo _L("Added or modified directory: %1",$file)."\n";
  247.                                         //flush();
  248.                                         if (!$preview) {
  249.                                                 $this->createDirs($localPath);
  250.                                                 if (!is_dir($localPath)) {
  251.                                                         $errors_happened = true;
  252.                                                         echo "=> "._L("FAILED")."\n";
  253.                                                         //flush();
  254.                                                 }
  255.                                         }
  256.                                 }
  257.                         }
  258.  
  259.                         // Add files
  260.                         sort($objects_list['files']); // <-- added by Daniel Marschall: Sort list, just for cosmetic improvement
  261.                         foreach ($objects_list['files'] as $file) {
  262.                                 if ($file != '') {
  263.                                         $localFile = str_replace($folder, "", $file);
  264.                                         $localFile = rtrim($outPath,DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . ltrim($localFile,DIRECTORY_SEPARATOR);
  265.  
  266.                                         echo _L("Added or modified file: %1",$file)."\n";
  267.                                         //flush();
  268.                                         if (!$preview) {
  269.                                                 $contents = $this->getFile($file);
  270.                                                 if (@file_put_contents($localFile, $contents) === false) {
  271.                                                         $errors_happened = true;
  272.                                                         echo "=> "._L("FAILED")."\n";
  273.                                                         //flush();
  274.                                                 }
  275.                                         }
  276.                                 }
  277.                         }
  278.  
  279.                         // Remove files
  280.                         sort($objects_list['filesDelete']); // <-- added by Daniel Marschall: Sort list, just for cosmetic improvement
  281.                         foreach ($objects_list['filesDelete'] as $file) {
  282.                                 if ($file != '') {
  283.                                         $localFile = str_replace($folder, "", $file);
  284.                                         $localFile = rtrim($outPath,DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . ltrim($localFile,DIRECTORY_SEPARATOR);
  285.  
  286.                                         echo _L("Removed file: %1",$file)."\n";
  287.                                         //flush();
  288.  
  289.                                         if (!$preview) {
  290.                                                 @unlink($localFile);
  291.                                                 if (file_exists($localFile)) {
  292.                                                         $errors_happened = true;
  293.                                                         echo "=> "._L("FAILED")."\n";
  294.                                                         //flush();
  295.                                                 }
  296.                                         }
  297.                                 }
  298.                         }
  299.  
  300.                         // Remove dirs
  301.                         // Changed by Daniel Marschall: moved this to the end, because "add/update" requests for this directory might happen before the directory gets removed
  302.                         rsort($objects_list['dirsDelete']); // <-- added by Daniel Marschall: Sort list in reverse order, so that directories get deleted in the correct hierarchical order
  303.                         foreach ($objects_list['dirsDelete'] as $file) {
  304.                                 if ($file != '') {
  305.                                         $localPath = str_replace($folder, "", $file);
  306.                                         $localPath = rtrim($outPath,DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . ltrim($localPath,DIRECTORY_SEPARATOR);
  307.  
  308.                                         echo _L("Removed directory: %1",$file)."\n";
  309.                                         //flush();
  310.  
  311.                                         if (!$preview) {
  312.                                                 $this->removeDirs($localPath);
  313.                                                 if (is_dir($localPath)) {
  314.                                                         $errors_happened = true;
  315.                                                         echo "=> "._L("FAILED")."\n";
  316.                                                         //flush();
  317.                                                 }
  318.                                         }
  319.                                 }
  320.                         }
  321.  
  322.                         // Update version file
  323.                         // Changed by Daniel Marschall: Added $errors_happened
  324.                         if (!$preview && !empty($version_file)) {
  325.                                 if (!$errors_happened) {
  326.                                         if (@file_put_contents($version_file, "Revision ".$this->actVersion."\n") === false) { // do not translate
  327.                                                 echo _L("ERROR: Could not set the revision")."\n";
  328.                                                 //flush();
  329.                                                 return false;
  330.                                         } else {
  331.                                                 echo _L("Set revision to %1", $this->actVersion) . "\n";
  332.                                                 //flush();
  333.                                                 return true;
  334.                                         }
  335.                                 } else {
  336.                                         echo _L("Revision NOT set to %1 because some files/dirs could not be updated. Please try again.",$this->actVersion)."\n";
  337.                                         //flush();
  338.                                         return false;
  339.                                 }
  340.                         } else {
  341.                                 return true;
  342.                         }
  343.                 }
  344.         }
  345.  
  346.         /**
  347.          *  rawDirectoryDump
  348.          *
  349.          * Dumps SVN data for $folder in the version $version of the repository.
  350.          *
  351.          *  @param string  $folder Folder to get data
  352.          *  @param integer $version Repository version, -1 means actual
  353.          *  @return array SVN data dump.
  354.          */
  355.         private function rawDirectoryDump($folder = '/trunk/', $version = -1)
  356.         {
  357.                 if ($version == -1 || $version > $this->actVersion) {
  358.                         $version = $this->actVersion;
  359.                 }
  360.  
  361.                 $url = $this->cleanURL($this->_url . "/!svn/bc/" . $version . "/" . $folder . "/");
  362.                 $args = array();
  363.                 $this->initQuery($args, "PROPFIND", $url);
  364.                 $args['Body']                      = self::PHPSVN_NORMAL_REQUEST;
  365.                 $args['Headers']['Content-Length'] = strlen(self::PHPSVN_NORMAL_REQUEST);
  366.  
  367.                 $cache_file = $this->cache_dir.'/svnpropfind_'.md5($url.'|'.$args['Body']).'.xml';
  368.                 if ($this->use_cache && file_exists($cache_file)) {
  369.                         $body = file_get_contents($cache_file);
  370.                 } else {
  371.                         $headers = array();
  372.                         $body = '';
  373.                         if (!$this->Request($args, $headers, $body))
  374.                                 throw new Exception(_L("Cannot get rawDirectoryDump (Request failed)"));
  375.                         if (is_dir($this->cache_dir)) {
  376.                                 if ($body) @file_put_contents($cache_file, $body);
  377.                         }
  378.                 }
  379.  
  380.                 return self::xmlParse($body);
  381.         }
  382.  
  383.         /**
  384.          *  getDirectoryFiles
  385.          *
  386.          *  Returns all the files in $folder in the version $version of
  387.          *  the repository.
  388.          *
  389.          *  @param string  $folder Folder to get files
  390.          *  @param integer $version Repository version, -1 means actual
  391.          *  @return array List of files.
  392.          */
  393.         private function getDirectoryFiles($folder = '/trunk/', $version = -1)
  394.         {
  395.                 $responses = $this->rawDirectoryDump($folder, $version);
  396.  
  397.                 if ($responses) {
  398.                         $files = array();
  399.                         foreach ($responses as $response) {
  400.  
  401.                                 if ((string)$response->{'D__propstat'}->{'D__prop'}->{'lp3__baseline-relative-path'} != '') {
  402.                                         $fn = (string)$response->{'D__propstat'}->{'D__prop'}->{'lp3__baseline-relative-path'};
  403.                                 } else {
  404.                                         $fn = (string)$response->{'D__propstat'}->{'D__prop'}->{'lp2__baseline-relative-path'};
  405.                                 }
  406.  
  407.                                 $storeDirectoryFiles = array(
  408.                                         'type' => (string)$response->{'D__href'},
  409.                                         'path' => $fn,
  410.                                         'last-mod' => (string)$response->{'D__propstat'}->{'D__prop'}->{'lp1__getlastmodified'},
  411.                                         'version' => (string)$response->{'D__propstat'}->{'D__prop'}->{'lp1__version-name'},
  412.                                         'status' => (string)$response->{'D__propstat'}->{'D__status'},
  413.                                 );
  414.  
  415.                                 // Detect 'type' as either a 'directory' or 'file'
  416.                                 if (substr($storeDirectoryFiles['type'], strlen($storeDirectoryFiles['type']) - strlen($storeDirectoryFiles['path'])) == $storeDirectoryFiles['path']) {
  417.                                         // Example:
  418.                                         // <D:href>/svn/oidplus/!svn/bc/504/trunk/3p/vts_fileformats/VtsFileTypeDetect.class.php</D:href>
  419.                                         // <lp2:baseline-relative-path>trunk/3p/vts_fileformats/VtsFileTypeDetect.class.php</lp2:baseline-relative-path>
  420.                                         $storeDirectoryFiles['type'] = 'file';
  421.                                 } else {
  422.                                         // Example:
  423.                                         // <D:href>/svn/oidplus/!svn/bc/504/trunk/plugins/publicPages/820_login_facebook/</D:href>
  424.                                         // <lp2:baseline-relative-path>trunk/plugins/publicPages/820_login_facebook</lp2:baseline-relative-path>
  425.                                         $storeDirectoryFiles['type'] = 'directory';
  426.                                 }
  427.  
  428.                                 array_push($files, $storeDirectoryFiles);
  429.                         }
  430.  
  431.                         return $files;
  432.  
  433.                 } else {
  434.  
  435.                         throw new Exception(_L("Error communicating with SVN server"));
  436.  
  437.                 }
  438.         }
  439.  
  440.         private static function dirToArray($dir, &$result) {
  441.                 $cdir = scandir($dir);
  442.                 foreach ($cdir as $key => $value) {
  443.                         if (!in_array($value,array('.','..'))) {
  444.                                 if (is_dir($dir . DIRECTORY_SEPARATOR . $value)) {
  445.                                         $result[] = $dir.DIRECTORY_SEPARATOR.$value.DIRECTORY_SEPARATOR;
  446.                                         self::dirToArray($dir.DIRECTORY_SEPARATOR.$value, $result);
  447.                                 } else {
  448.                                         $result[] = $dir.DIRECTORY_SEPARATOR.$value;
  449.                                 }
  450.                         }
  451.                 }
  452.         }
  453.  
  454.         public function compareToDirectory($local_folder, $svn_folder='/trunk/', $version=-1) {
  455.                 $local_cont = array();
  456.                 self::dirToArray($local_folder, $local_cont);
  457.                 foreach ($local_cont as $key => &$c) {
  458.                         $c = str_replace('\\', '/', $c);
  459.                         $c = substr($c, strlen($local_folder));
  460.                         if (substr($c,0,1) === '/') $c = substr($c, 1);
  461.                         if ($c === '') unset($local_cont[$key]);
  462.                         if (strpos($c,'.svn/') === 0) unset($local_cont[$key]);
  463.                         if ((strpos($c,'userdata/') === 0) && ($c !== 'userdata/info.txt') && ($c !== 'userdata/.htaccess') && ($c !== 'userdata/index.html') && (substr($c,-1) !== '/')) unset($local_cont[$key]);
  464.                 }
  465.                 unset($key);
  466.                 unset($c);
  467.                 natsort($local_cont);
  468.  
  469.                 $svn_cont = array();
  470.                 $contents = $this->getDirectoryTree($svn_folder, $version, true);
  471.                 foreach ($contents as $cont) {
  472.                         if ($cont['type'] == 'directory') {
  473.                                 $svn_cont[] = '/'.urldecode($cont['path']).'/';
  474.                         } else if ($cont['type'] == 'file') {
  475.                                 $svn_cont[] = '/'.urldecode($cont['path']);
  476.                         }
  477.                 }
  478.                 foreach ($svn_cont as $key => &$c) {
  479.                         $c = str_replace('\\', '/', $c);
  480.                         $c = substr($c, strlen($svn_folder));
  481.                         if (substr($c,0,1) === '/') $c = substr($c, 1);
  482.                         if ($c === '') unset($svn_cont[$key]);
  483.                         if ((strpos($c,'userdata/') === 0) && ($c !== 'userdata/info.txt') && ($c !== 'userdata/.htaccess') && ($c !== 'userdata/index.html') && (substr($c,-1) !== '/')) unset($svn_cont[$key]);
  484.                 }
  485.                 unset($key);
  486.                 unset($c);
  487.                 unset($contents);
  488.                 unset($cont);
  489.                 natsort($svn_cont);
  490.  
  491.                 $only_svn = array_diff($svn_cont, $local_cont);
  492.                 $only_local = array_diff($local_cont, $svn_cont);
  493.                 return array($svn_cont, $local_cont);
  494.         }
  495.  
  496.         /**
  497.          *  getDirectoryTree
  498.          *
  499.          *  Returns the complete tree of files and directories in $folder from the
  500.          *  version $version of the repository. Can also be used to get the info
  501.          *  for a single file or directory.
  502.          *
  503.          *  @param string  $folder Folder to get tree
  504.          *  @param integer $version Repository version, -1 means current
  505.          *  @param boolean $recursive Whether to get the tree recursively, or just
  506.          *  the specified directory/file.
  507.          *
  508.          *  @return array List of files and directories.
  509.          */
  510.         private function getDirectoryTree($folder = '/trunk/', $version = -1, $recursive = true)
  511.         {
  512.                 $directoryTree = array();
  513.  
  514.                 if (!($arrOutput = $this->getDirectoryFiles($folder, $version)))
  515.                         return false;
  516.  
  517.                 if (!$recursive)
  518.                         return $arrOutput[0];
  519.  
  520.                 while (count($arrOutput) && is_array($arrOutput)) {
  521.                         $array = array_shift($arrOutput);
  522.  
  523.                         array_push($directoryTree, $array);
  524.  
  525.                         if (trim($array['path'], '/') == trim($folder, '/'))
  526.                                 continue;
  527.  
  528.                         if ($array['type'] == 'directory') {
  529.                                 $walk = $this->getDirectoryFiles($array['path'], $version);
  530.                                 array_shift($walk);
  531.  
  532.                                 foreach ($walk as $step) {
  533.                                         array_unshift($arrOutput, $step);
  534.                                 }
  535.                         }
  536.                 }
  537.                 return $directoryTree;
  538.         }
  539.  
  540.         /**
  541.          *  Returns file contents
  542.          *
  543.          *  @param      string  $file File pathname
  544.          *  @param      integer $version File Version
  545.          *  @return     string  File content and information, false on error, or if a
  546.          *              directory is requested
  547.          */
  548.         private function getFile($file, $version = -1)
  549.         {
  550.                 if ($version == -1 || $version > $this->actVersion) {
  551.                         $version = $this->actVersion;
  552.                 }
  553.  
  554.                 // check if this is a directory... if so, return false, otherwise we
  555.                 // get the HTML output of the directory listing from the SVN server.
  556.                 // This is maybe a bit heavy since it makes another connection to the
  557.                 // SVN server. Maybe add this as an option/parameter? ES 23/06/08
  558.                 $fileInfo = $this->getDirectoryTree($file, $version, false);
  559.                 if ($fileInfo["type"] == "directory")
  560.                         return false;
  561.  
  562.                 $args = array();
  563.                 $url = $this->cleanURL($this->_url . "/!svn/bc/" . $version . "/" . $file . "/");
  564.                 $this->initQuery($args, "GET", $url);
  565.                 $headers = array();
  566.                 $body = '';
  567.                 if (!$this->Request($args, $headers, $body))
  568.                         throw new Exception(_L("Cannot call getFile (Request failed)"));
  569.  
  570.                 return $body;
  571.         }
  572.  
  573.         private function getLogsForUpdate($file, $vini = 0, $vend = -1)
  574.         {
  575.                 $fileLogs = array();
  576.  
  577.                 if ($vend == -1) {
  578.                         $vend = $this->actVersion;
  579.                 }
  580.  
  581.                 if ($vini < 0)
  582.                         $vini = 0;
  583.  
  584.                 if ($vini > $vend) {
  585.                         $vini = $vend;
  586.                         echo _L("Nothing updated")."\n";
  587.                         //flush();
  588.                         return null;
  589.                 }
  590.  
  591.                 $cache_file = $this->cache_dir.'/svnlog_'.md5($file).'_'.$vini.'_'.$vend.'.ser';
  592.                 if ($this->use_cache && file_exists($cache_file)) {
  593.                         return unserialize(file_get_contents($cache_file));
  594.                 }
  595.  
  596.                 $url = $this->cleanURL($this->_url . "/!svn/bc/" . $this->actVersion . "/" . $file . "/");
  597.                 $args = array();
  598.                 $this->initQuery($args, "REPORT", $url);
  599.                 $args['Body']                      = sprintf(self::PHPSVN_LOGS_REQUEST, $vini, $vend);
  600.                 $args['Headers']['Content-Length'] = strlen($args['Body']);
  601.                 $args['Headers']['Depth']          = 1;
  602.  
  603.                 $cache_file2 = $this->cache_dir.'/svnreport_'.md5($url.'|'.$args['Body']).'.xml';
  604.                 if ($this->use_cache && file_exists($cache_file2)) {
  605.                         $body = file_get_contents($cache_file2);
  606.                 } else {
  607.                         $headers = array();
  608.                         $body = '';
  609.                         if (!$this->Request($args, $headers, $body))
  610.                                 throw new Exception(_L("Cannot call getLogsForUpdate (Request failed)"));
  611.                         if (is_dir($this->cache_dir)) {
  612.                                 if ($body) @file_put_contents($cache_file2, $body);
  613.                         }
  614.                 }
  615.  
  616.                 $arrOutput = self::xmlParse($body);
  617.  
  618.                 $revlogs = array();
  619.  
  620.                 $array = array();
  621.                 foreach ($arrOutput as $xmlLogItem) {
  622.                         /*
  623.                         <S:log-item>
  624.                         <D:version-name>164</D:version-name>
  625.                         <S:date>2019-08-13T13:12:13.915920Z</S:date>
  626.                         <D:comment>Update assistant bugfix</D:comment>
  627.                         <D:creator-displayname>daniel-marschall</D:creator-displayname>
  628.                         <S:modified-path node-kind="file" text-mods="true" prop-mods="false">/trunk/update/index.php</S:modified-path>
  629.                         <S:modified-path node-kind="file" text-mods="true" prop-mods="false">/trunk/update/phpsvnclient.inc.php</S:modified-path>
  630.                         </S:log-item>
  631.                         */
  632.  
  633.                         $versionName = '';
  634.                         $date = '';
  635.                         $comment = '';
  636.                         $creator = '';
  637.  
  638.                         foreach ($xmlLogItem as $tagName => $data) {
  639.                                 $tagName = strtoupper($tagName);
  640.                                 $tagName = str_replace('__', ':', $tagName);
  641.                                 if (($tagName == 'S:ADDED-PATH') || ($tagName == 'S:MODIFIED-PATH') || ($tagName == 'S:DELETED-PATH')) {
  642.                                         if ($data->attributes()['node-kind'] == "file") {
  643.                                                 $array['objects'][] = array(
  644.                                                         'object_name' => (string)$data,
  645.                                                         'action' => $tagName,
  646.                                                         'type' => 'file'
  647.                                                 );
  648.                                         } else if ($data->attributes()['node-kind'] == "dir") {
  649.                                                 $array['objects'][] = array(
  650.                                                         'object_name' => (string)$data,
  651.                                                         'action' => $tagName,
  652.                                                         'type' => 'dir'
  653.                                                 );
  654.                                         }
  655.                                 } else if ($tagName == 'D:VERSION-NAME') {
  656.                                         $versionName = (string)$data;
  657.                                 } else if ($tagName == 'S:DATE') {
  658.                                         $date = (string)$data;
  659.                                 } else if ($tagName == 'D:COMMENT') {
  660.                                         $comment = (string)$data;
  661.                                 } else if ($tagName == 'D:CREATOR-DISPLAYNAME') {
  662.                                         $creator = (string)$data;
  663.                                 }
  664.                         }
  665.                         $revlogs[] = array('versionName' => $versionName,
  666.                                            'date' => $date,
  667.                                            'comment' => $comment,
  668.                                            'creator' => $creator);
  669.                 }
  670.  
  671.                 $files       = array();
  672.                 $filesDelete = array();
  673.                 $dirs        = array();
  674.                 $dirsNew     = array();
  675.                 $dirsMod     = array();
  676.                 $dirsDelete  = array();
  677.  
  678.                 if (!isset($array['objects'])) $array['objects'] = array();
  679.                 foreach ($array['objects'] as $objects) {
  680.                         // This section was completely changed by Daniel Marschall
  681.                         if ($objects['type'] == "file") {
  682.                                 if ($objects['action'] == "S:ADDED-PATH" || $objects['action'] == "S:MODIFIED-PATH") {
  683.                                         self::xarray_add($objects['object_name'], $files);
  684.                                         self::xarray_remove($objects['object_name'], $filesDelete);
  685.                                 }
  686.                                 if ($objects['action'] == "S:DELETED-PATH") {
  687.                                         self::xarray_add($objects['object_name'], $filesDelete);
  688.                                         self::xarray_remove($objects['object_name'], $files);
  689.                                 }
  690.                         }
  691.                         if ($objects['type'] == "dir") {
  692.                                 if ($objects['action'] == "S:ADDED-PATH") {
  693.                                         self::xarray_add($objects['object_name'], $dirs);
  694.                                         self::xarray_add($objects['object_name'], $dirsNew);
  695.                                         self::xarray_remove($objects['object_name'], $dirsDelete);
  696.                                 }
  697.                                 if ($objects['action'] == "S:MODIFIED-PATH") {
  698.                                         self::xarray_add($objects['object_name'], $dirs);
  699.                                         self::xarray_add($objects['object_name'], $dirsMod);
  700.                                         self::xarray_remove($objects['object_name'], $dirsDelete);
  701.                                 }
  702.                                 if ($objects['action'] == "S:DELETED-PATH") {
  703.                                         // Delete files from filelist
  704.                                         $files_copy = $files;
  705.                                         foreach ($files_copy as $file) {
  706.                                                 if (strpos($file, $objects['object_name'].'/') === 0) self::xarray_remove($file, $files);
  707.                                         }
  708.                                         // END OF Delete files from filelist
  709.                                         // Delete dirs from dirslist
  710.                                         self::xarray_add($objects['object_name'], $dirsDelete);
  711.                                         self::xarray_remove($objects['object_name'], $dirs);
  712.                                         self::xarray_remove($objects['object_name'], $dirsMod);
  713.                                         self::xarray_remove($objects['object_name'], $dirsNew);
  714.                                         // END OF Delete dirs from dirslist
  715.                                 }
  716.                         }
  717.                 }
  718.                 foreach ($dirsNew as $dir) {
  719.                         // For new directories, also download all its contents
  720.                         try {
  721.                                 $contents = $this->getDirectoryTree($dir, $vend, true);
  722.                         } catch (Exception $e) {
  723.                                 // This can happen when you update from a very old version and a directory was new which is not existing in the newest ($vend) version
  724.                                 // In this case, we don't need it and can ignore the error
  725.                                 $contents = array();
  726.                         }
  727.                         foreach ($contents as $cont) {
  728.                                 if ($cont['type'] == 'directory') {
  729.                                         $dirname = '/'.urldecode($cont['path']);
  730.                                         self::xarray_add($dirname, $dirs);
  731.                                         self::xarray_remove($dirname, $dirsDelete);
  732.                                 } else if ($cont['type'] == 'file') {
  733.                                         $filename = '/'.urldecode($cont['path']);
  734.                                         self::xarray_add($filename, $files);
  735.                                         self::xarray_remove($filename, $filesDelete);
  736.                                 }
  737.                         }
  738.                 }
  739.                 $out                = array();
  740.                 $out['files']       = $files;
  741.                 $out['filesDelete'] = $filesDelete;
  742.                 $out['dirs']        = $dirs;
  743.                 $out['dirsDelete']  = $dirsDelete;
  744.                 $out['revisions']   = $revlogs;
  745.  
  746.                 if (is_dir($this->cache_dir)) {
  747.                         $data = serialize($out);
  748.                         if ($data) @file_put_contents($cache_file, $data);
  749.                 }
  750.  
  751.                 return $out;
  752.         }
  753.  
  754.         /**
  755.          *  Returns the repository version
  756.          *
  757.          *  @return integer Repository version
  758.          *  @access public
  759.          */
  760.         public function getVersion()
  761.         {
  762.                 if ($this->_repVersion > 0)
  763.                         return $this->_repVersion;
  764.  
  765.                 $this->_repVersion = -1;
  766.                 $args = array();
  767.                 $this->initQuery($args, "PROPFIND", $this->cleanURL($this->_url . "/!svn/vcc/default"));
  768.                 $args['Body']                      = self::PHPSVN_VERSION_REQUEST;
  769.                 $args['Headers']['Content-Length'] = strlen(self::PHPSVN_NORMAL_REQUEST);
  770.                 $args['Headers']['Depth']          = 0;
  771.  
  772.                 $tmp = array();
  773.                 $body = '';
  774.                 if (!$this->Request($args, $tmp, $body))
  775.                         throw new Exception(_L("Cannot get repository revision (Request failed)"));
  776.  
  777.                 $this->_repVersion = null;
  778.                 $m = array();
  779.                 if (preg_match('@/(\d+)\s*</D:href>@ismU', $body, $m)) {
  780.                         $this->_repVersion = $m[1];
  781.                 } else {
  782.                         throw new Exception(_L("Cannot get repository revision (RegEx failed)"));
  783.                 }
  784.  
  785.                 return $this->_repVersion;
  786.         }
  787.  
  788.         /**
  789.          *  Private Functions
  790.          */
  791.  
  792.         /**
  793.          *  Prepare HTTP CLIENT object
  794.          *
  795.          *  @param array &$arguments Byreferences variable.
  796.          *  @param string $method Method for the request (GET,POST,PROPFIND, REPORT,ETC).
  797.          *  @param string $url URL for the action.
  798.          *  @access private
  799.          */
  800.         private function initQuery(&$arguments, $method, $url)
  801.         {
  802.                 $http =& $this->_http;
  803.                 $http->GetRequestArguments($url, $arguments);
  804.                 $arguments["RequestMethod"]           = $method;
  805.                 $arguments["Headers"]["Content-Type"] = "text/xml";
  806.                 $arguments["Headers"]["Depth"]        = 1;
  807.         }
  808.  
  809.         /**
  810.          *  Open a connection, send request, read header
  811.          *  and body.
  812.          *
  813.          *  @param Array $args Connetion's argument
  814.          *  @param Array &$headers Array with the header response.
  815.          *  @param string &$body Body response.
  816.          *  @return boolean True is query success
  817.          *  @access private
  818.          */
  819.         private function Request($args, &$headers, &$body)
  820.         {
  821.                 $args['RequestURI'] = str_replace(' ', '%20', $args['RequestURI']); //Hack to make filenames with spaces work.
  822.                 $http =& $this->_http;
  823.                 $http->Open($args);
  824.                 $http->SendRequest($args);
  825.                 $http->ReadReplyHeaders($headers);
  826.                 if (substr($http->response_status,0,1) != 2) {
  827.                         switch ($http->response_status) {
  828.                                 case 404:
  829.                                         $this->errNro = self::NOT_FOUND;
  830.                                         break;
  831.                                 case 401:
  832.                                         $this->errNro = self::AUTH_REQUIRED;
  833.                                         break;
  834.                                 default:
  835.                                         $this->errNro = self::UNKNOWN_ERROR;
  836.                                         break;
  837.                         }
  838.                         //throw new Exception(_L('HTTP Error: %1 at %2,$http->response_status,$args['RequestURI']));
  839.                         $http->close();
  840.                         return false;
  841.                 }
  842.                 $this->errNro = self::NO_ERROR;
  843.                 $body         = '';
  844.                 $tbody        = '';
  845.                 for (;;) {
  846.                         $error = $http->ReadReplyBody($tbody, 1000);
  847.                         if ($error != "" || strlen($tbody) == 0) {
  848.                                 break;
  849.                         }
  850.                         $body .= ($tbody);
  851.                 }
  852.                 $http->close();
  853.                 return true;
  854.         }
  855.  
  856.         /**
  857.          *  Returns $url stripped of '//'
  858.          *
  859.          *  Delete "//" on URL requests.
  860.          *
  861.          *  @param string $url URL
  862.          *  @return string New cleaned URL.
  863.          *  @access private
  864.          */
  865.         private function cleanURL($url)
  866.         {
  867.                 return preg_replace("/((^:)\/\/)/", "//", $url);
  868.         }
  869.  
  870.         private static function xmlParse($strInputXML) {
  871.                 $strInputXML = preg_replace('@<([^>]+):@ismU','<\\1__',$strInputXML);
  872.                 return simplexml_load_string($strInputXML);
  873.         }
  874.  
  875.         /*
  876.           Small helper functions
  877.         */
  878.  
  879.         private static function xarray_add($needle, &$array) {
  880.                 $key = array_search($needle, $array);
  881.                 if ($key === false) {
  882.                         $array[] = $needle;
  883.                 }
  884.         }
  885.  
  886.         private static function xarray_remove($needle, &$array) {
  887.                 while (true) {
  888.                         $key = array_search($needle, $array);
  889.                         if ($key === false) break;
  890.                         unset($array[$key]);
  891.                 }
  892.         }
  893. }
  894.