Subversion Repositories oidplus

Rev

Rev 506 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
171 daniel-mar 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
505 daniel-mar 11
 *    CHANGES by Daniel Marschall, ViaThinkSoft in 2019-2021:
171 daniel-mar 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.
172 daniel-mar 14
 *      The only important functions are getVersion() and updateWorkingCopy()
505 daniel-mar 15
 *    - The dependency class xml2array was removed and instead, SimpleXML is used
172 daniel-mar 16
 *    - Added "revision log/comment" functionality
171 daniel-mar 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
 
505 daniel-mar 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
}
360 daniel-mar 36
 
171 daniel-mar 37
/**
38
 *  PHP SVN CLIENT
39
 *
360 daniel-mar 40
 *  This class is an SVN client. It can perform read operations
41
 *  to an SVN server (over Web-DAV).
171 daniel-mar 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
 */
251 daniel-mar 48
class phpsvnclient {
49
 
269 daniel-mar 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>';
293 daniel-mar 51
 
269 daniel-mar 52
        /*protected*/ const PHPSVN_VERSION_REQUEST = '<?xml version="1.0" encoding="utf-8"?><propfind xmlns="DAV:"><prop><checked-in xmlns="DAV:"/></prop></propfind>';
293 daniel-mar 53
 
269 daniel-mar 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>';
293 daniel-mar 55
 
505 daniel-mar 56
        /*public*/ const NO_ERROR = 1;
57
        /*public*/ const NOT_FOUND = 2;
58
        /*public*/ const AUTH_REQUIRED = 3;
59
        /*public*/ const UNKNOWN_ERROR = 4;
251 daniel-mar 60
 
505 daniel-mar 61
        public $use_cache = true;
62
        public $cache_dir = __DIR__.'/../../userdata/cache';
63
 
171 daniel-mar 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
386 daniel-mar 84
         *  @var int
171 daniel-mar 85
         */
86
        private $_repVersion;
87
 
88
        /**
89
         *  Last error number
90
         *
505 daniel-mar 91
         *  Possible values are NO_ERROR, NOT_FOUND, AUTH_REQUIRED, UNKOWN_ERROR
171 daniel-mar 92
         *
505 daniel-mar 93
         *  @access protected
171 daniel-mar 94
         *  @var integer
95
         */
505 daniel-mar 96
        protected $errNro = self::NO_ERROR;
97
        public function getLastError() {
98
                return $this->errNro;
99
        }
171 daniel-mar 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.
386 daniel-mar 122
         * @param $path (string) The path to the directory that will be created.
171 daniel-mar 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.
386 daniel-mar 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.
171 daniel-mar 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
        /**
172 daniel-mar 164
        * Updates a working copy
386 daniel-mar 165
        * @param $from_revision (string) Either a revision number or a text file with the
172 daniel-mar 166
        *                       contents "Revision ..." (if it is a file,
167
        *                       the file revision will be updated if everything
168
        *                       was successful)
386 daniel-mar 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 daniel-mar 172
        **/
173
        public function updateWorkingCopy($from_revision='version.txt', $folder = '/trunk/', $outPath = '.', $preview = false)
171 daniel-mar 174
        {
175
                if (!is_dir($outPath)) {
505 daniel-mar 176
                        echo _L("ERROR: Local path %1 not existing",$outPath)."\n";
532 daniel-mar 177
                        //flush();
171 daniel-mar 178
                        return false;
179
                }
180
 
303 daniel-mar 181
                $webbrowser_update = !is_numeric($from_revision);
182
 
172 daniel-mar 183
                if (!is_numeric($from_revision)) {
184
                        $version_file = $from_revision;
185
                        $from_revision = -1;
186
 
187
                        if (!file_exists($version_file)) {
505 daniel-mar 188
                                echo _L("ERROR: %1 missing",$version_file)."\n";
532 daniel-mar 189
                                //flush();
171 daniel-mar 190
                                return false;
172 daniel-mar 191
                        } else {
192
                                //Obtain the number of current version number of the local copy.
193
                                $cont = file_get_contents($version_file);
386 daniel-mar 194
                                $m = array();
505 daniel-mar 195
                                if (!preg_match('@Revision (\d+)@', $cont, $m)) { // do not translate
196
                                        echo _L("ERROR: %1 unknown format",$version_file)."\n";
532 daniel-mar 197
                                        //flush();
172 daniel-mar 198
                                        return false;
199
                                }
200
                                $from_revision = $m[1];
201
 
506 daniel-mar 202
                                echo _L("Found %1 with revision information %2",basename($version_file),$from_revision)."\n";
532 daniel-mar 203
                                //flush();
171 daniel-mar 204
                        }
172 daniel-mar 205
                } else {
206
                        $version_file = '';
207
                }
171 daniel-mar 208
 
172 daniel-mar 209
                $errors_happened = false;
210
 
303 daniel-mar 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);
505 daniel-mar 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";
532 daniel-mar 217
                                //flush();
303 daniel-mar 218
                                if (!$preview) return false;
219
                        }
220
                        @unlink($file);
221
                        if (file_exists($file)) {
505 daniel-mar 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";
532 daniel-mar 223
                                //flush();
303 daniel-mar 224
                                if (!$preview) return false;
225
                        }
172 daniel-mar 226
                }
171 daniel-mar 227
 
172 daniel-mar 228
                //Get a list of objects to be updated.
505 daniel-mar 229
                $objects_list = $this->getLogsForUpdate($folder, $from_revision + 1);
172 daniel-mar 230
                if (!is_null($objects_list)) {
231
                        // Output version information
232
                        foreach ($objects_list['revisions'] as $revision) {
505 daniel-mar 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'])).") ";
216 daniel-mar 235
                                echo trim($tex . str_replace("\n", "\n".str_repeat(' ', strlen($tex)), $comment));
186 daniel-mar 236
                                echo "\n";
171 daniel-mar 237
                        }
238
 
172 daniel-mar 239
                        // Add dirs
200 daniel-mar 240
                        sort($objects_list['dirs']); // <-- added by Daniel Marschall: Sort folder list, so that directories will be created in the correct hierarchical order
172 daniel-mar 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);
171 daniel-mar 245
 
505 daniel-mar 246
                                        echo _L("Added or modified directory: %1",$file)."\n";
532 daniel-mar 247
                                        //flush();
172 daniel-mar 248
                                        if (!$preview) {
249
                                                $this->createDirs($localPath);
250
                                                if (!is_dir($localPath)) {
251
                                                        $errors_happened = true;
505 daniel-mar 252
                                                        echo "=> "._L("FAILED")."\n";
532 daniel-mar 253
                                                        //flush();
171 daniel-mar 254
                                                }
255
                                        }
256
                                }
172 daniel-mar 257
                        }
171 daniel-mar 258
 
172 daniel-mar 259
                        // Add files
200 daniel-mar 260
                        sort($objects_list['files']); // <-- added by Daniel Marschall: Sort list, just for cosmetic improvement
172 daniel-mar 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);
171 daniel-mar 265
 
505 daniel-mar 266
                                        echo _L("Added or modified file: %1",$file)."\n";
532 daniel-mar 267
                                        //flush();
172 daniel-mar 268
                                        if (!$preview) {
269
                                                $contents = $this->getFile($file);
270
                                                if (@file_put_contents($localFile, $contents) === false) {
271
                                                        $errors_happened = true;
505 daniel-mar 272
                                                        echo "=> "._L("FAILED")."\n";
532 daniel-mar 273
                                                        //flush();
171 daniel-mar 274
                                                }
275
                                        }
276
                                }
172 daniel-mar 277
                        }
216 daniel-mar 278
 
279
                        // Remove files
200 daniel-mar 280
                        sort($objects_list['filesDelete']); // <-- added by Daniel Marschall: Sort list, just for cosmetic improvement
172 daniel-mar 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);
171 daniel-mar 285
 
505 daniel-mar 286
                                        echo _L("Removed file: %1",$file)."\n";
532 daniel-mar 287
                                        //flush();
171 daniel-mar 288
 
172 daniel-mar 289
                                        if (!$preview) {
290
                                                @unlink($localFile);
291
                                                if (file_exists($localFile)) {
292
                                                        $errors_happened = true;
505 daniel-mar 293
                                                        echo "=> "._L("FAILED")."\n";
532 daniel-mar 294
                                                        //flush();
171 daniel-mar 295
                                                }
296
                                        }
297
                                }
172 daniel-mar 298
                        }
171 daniel-mar 299
 
172 daniel-mar 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
200 daniel-mar 302
                        rsort($objects_list['dirsDelete']); // <-- added by Daniel Marschall: Sort list in reverse order, so that directories get deleted in the correct hierarchical order
172 daniel-mar 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);
171 daniel-mar 307
 
505 daniel-mar 308
                                        echo _L("Removed directory: %1",$file)."\n";
532 daniel-mar 309
                                        //flush();
171 daniel-mar 310
 
172 daniel-mar 311
                                        if (!$preview) {
312
                                                $this->removeDirs($localPath);
313
                                                if (is_dir($localPath)) {
314
                                                        $errors_happened = true;
505 daniel-mar 315
                                                        echo "=> "._L("FAILED")."\n";
532 daniel-mar 316
                                                        //flush();
171 daniel-mar 317
                                                }
318
                                        }
319
                                }
172 daniel-mar 320
                        }
171 daniel-mar 321
 
216 daniel-mar 322
                        // Update version file
172 daniel-mar 323
                        // Changed by Daniel Marschall: Added $errors_happened
324
                        if (!$preview && !empty($version_file)) {
325
                                if (!$errors_happened) {
505 daniel-mar 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";
532 daniel-mar 328
                                                //flush();
172 daniel-mar 329
                                                return false;
171 daniel-mar 330
                                        } else {
505 daniel-mar 331
                                                echo _L("Set revision to %1", $this->actVersion) . "\n";
532 daniel-mar 332
                                                //flush();
172 daniel-mar 333
                                                return true;
171 daniel-mar 334
                                        }
172 daniel-mar 335
                                } else {
505 daniel-mar 336
                                        echo _L("Revision NOT set to %1 because some files/dirs could not be updated. Please try again.",$this->actVersion)."\n";
532 daniel-mar 337
                                        //flush();
172 daniel-mar 338
                                        return false;
171 daniel-mar 339
                                }
172 daniel-mar 340
                        } else {
341
                                return true;
171 daniel-mar 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
                }
505 daniel-mar 360
 
171 daniel-mar 361
                $url = $this->cleanURL($this->_url . "/!svn/bc/" . $version . "/" . $folder . "/");
386 daniel-mar 362
                $args = array();
171 daniel-mar 363
                $this->initQuery($args, "PROPFIND", $url);
251 daniel-mar 364
                $args['Body']                      = self::PHPSVN_NORMAL_REQUEST;
365
                $args['Headers']['Content-Length'] = strlen(self::PHPSVN_NORMAL_REQUEST);
171 daniel-mar 366
 
505 daniel-mar 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
                }
171 daniel-mar 379
 
216 daniel-mar 380
                return self::xmlParse($body);
171 daniel-mar 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
        {
505 daniel-mar 395
                $responses = $this->rawDirectoryDump($folder, $version);
396
 
397
                if ($responses) {
171 daniel-mar 398
                        $files = array();
505 daniel-mar 399
                        foreach ($responses as $response) {
172 daniel-mar 400
 
505 daniel-mar 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
                                }
172 daniel-mar 406
 
505 daniel-mar 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
                                );
172 daniel-mar 414
 
505 daniel-mar 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);
171 daniel-mar 429
                        }
505 daniel-mar 430
 
171 daniel-mar 431
                        return $files;
505 daniel-mar 432
 
433
                } else {
434
 
435
                        throw new Exception(_L("Error communicating with SVN server"));
436
 
171 daniel-mar 437
                }
438
        }
439
 
306 daniel-mar 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) {
350 daniel-mar 458
                        $c = str_replace('\\', '/', $c);
306 daniel-mar 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') {
316 daniel-mar 473
                                $svn_cont[] = '/'.urldecode($cont['path']).'/';
306 daniel-mar 474
                        } else if ($cont['type'] == 'file') {
316 daniel-mar 475
                                $svn_cont[] = '/'.urldecode($cont['path']);
306 daniel-mar 476
                        }
477
                }
478
                foreach ($svn_cont as $key => &$c) {
350 daniel-mar 479
                        $c = str_replace('\\', '/', $c);
306 daniel-mar 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
 
171 daniel-mar 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
 
386 daniel-mar 562
                $args = array();
171 daniel-mar 563
                $url = $this->cleanURL($this->_url . "/!svn/bc/" . $version . "/" . $file . "/");
564
                $this->initQuery($args, "GET", $url);
386 daniel-mar 565
                $headers = array();
566
                $body = '';
171 daniel-mar 567
                if (!$this->Request($args, $headers, $body))
505 daniel-mar 568
                        throw new Exception(_L("Cannot call getFile (Request failed)"));
171 daniel-mar 569
 
570
                return $body;
571
        }
572
 
505 daniel-mar 573
        private function getLogsForUpdate($file, $vini = 0, $vend = -1)
171 daniel-mar 574
        {
575
                $fileLogs = array();
576
 
172 daniel-mar 577
                if ($vend == -1) {
171 daniel-mar 578
                        $vend = $this->actVersion;
579
                }
580
 
581
                if ($vini < 0)
582
                        $vini = 0;
583
 
584
                if ($vini > $vend) {
585
                        $vini = $vend;
505 daniel-mar 586
                        echo _L("Nothing updated")."\n";
532 daniel-mar 587
                        //flush();
171 daniel-mar 588
                        return null;
589
                }
590
 
505 daniel-mar 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));
493 daniel-mar 594
                }
595
 
171 daniel-mar 596
                $url = $this->cleanURL($this->_url . "/!svn/bc/" . $this->actVersion . "/" . $file . "/");
386 daniel-mar 597
                $args = array();
171 daniel-mar 598
                $this->initQuery($args, "REPORT", $url);
251 daniel-mar 599
                $args['Body']                      = sprintf(self::PHPSVN_LOGS_REQUEST, $vini, $vend);
171 daniel-mar 600
                $args['Headers']['Content-Length'] = strlen($args['Body']);
601
                $args['Headers']['Depth']          = 1;
602
 
505 daniel-mar 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
                }
171 daniel-mar 615
 
216 daniel-mar 616
                $arrOutput = self::xmlParse($body);
171 daniel-mar 617
 
172 daniel-mar 618
                $revlogs = array();
619
 
171 daniel-mar 620
                $array = array();
505 daniel-mar 621
                foreach ($arrOutput as $xmlLogItem) {
172 daniel-mar 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 = '';
505 daniel-mar 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") {
171 daniel-mar 643
                                                $array['objects'][] = array(
505 daniel-mar 644
                                                        'object_name' => (string)$data,
645
                                                        'action' => $tagName,
171 daniel-mar 646
                                                        'type' => 'file'
647
                                                );
505 daniel-mar 648
                                        } else if ($data->attributes()['node-kind'] == "dir") {
171 daniel-mar 649
                                                $array['objects'][] = array(
505 daniel-mar 650
                                                        'object_name' => (string)$data,
651
                                                        'action' => $tagName,
171 daniel-mar 652
                                                        'type' => 'dir'
653
                                                );
654
                                        }
505 daniel-mar 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;
171 daniel-mar 663
                                }
664
                        }
172 daniel-mar 665
                        $revlogs[] = array('versionName' => $versionName,
666
                                           'date' => $date,
505 daniel-mar 667
                                           'comment' => $comment,
172 daniel-mar 668
                                           'creator' => $creator);
171 daniel-mar 669
                }
505 daniel-mar 670
 
216 daniel-mar 671
                $files       = array();
672
                $filesDelete = array();
673
                $dirs        = array();
315 daniel-mar 674
                $dirsNew     = array();
675
                $dirsMod     = array();
216 daniel-mar 676
                $dirsDelete  = array();
171 daniel-mar 677
 
227 daniel-mar 678
                if (!isset($array['objects'])) $array['objects'] = array();
171 daniel-mar 679
                foreach ($array['objects'] as $objects) {
216 daniel-mar 680
                        // This section was completely changed by Daniel Marschall
171 daniel-mar 681
                        if ($objects['type'] == "file") {
682
                                if ($objects['action'] == "S:ADDED-PATH" || $objects['action'] == "S:MODIFIED-PATH") {
216 daniel-mar 683
                                        self::xarray_add($objects['object_name'], $files);
684
                                        self::xarray_remove($objects['object_name'], $filesDelete);
171 daniel-mar 685
                                }
686
                                if ($objects['action'] == "S:DELETED-PATH") {
216 daniel-mar 687
                                        self::xarray_add($objects['object_name'], $filesDelete);
688
                                        self::xarray_remove($objects['object_name'], $files);
171 daniel-mar 689
                                }
690
                        }
691
                        if ($objects['type'] == "dir") {
315 daniel-mar 692
                                if ($objects['action'] == "S:ADDED-PATH") {
216 daniel-mar 693
                                        self::xarray_add($objects['object_name'], $dirs);
315 daniel-mar 694
                                        self::xarray_add($objects['object_name'], $dirsNew);
216 daniel-mar 695
                                        self::xarray_remove($objects['object_name'], $dirsDelete);
171 daniel-mar 696
                                }
315 daniel-mar 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
                                }
171 daniel-mar 702
                                if ($objects['action'] == "S:DELETED-PATH") {
703
                                        // Delete files from filelist
216 daniel-mar 704
                                        $files_copy = $files;
705
                                        foreach ($files_copy as $file) {
706
                                                if (strpos($file, $objects['object_name'].'/') === 0) self::xarray_remove($file, $files);
171 daniel-mar 707
                                        }
708
                                        // END OF Delete files from filelist
709
                                        // Delete dirs from dirslist
216 daniel-mar 710
                                        self::xarray_add($objects['object_name'], $dirsDelete);
711
                                        self::xarray_remove($objects['object_name'], $dirs);
315 daniel-mar 712
                                        self::xarray_remove($objects['object_name'], $dirsMod);
713
                                        self::xarray_remove($objects['object_name'], $dirsNew);
171 daniel-mar 714
                                        // END OF Delete dirs from dirslist
715
                                }
716
                        }
717
                }
315 daniel-mar 718
                foreach ($dirsNew as $dir) {
719
                        // For new directories, also download all its contents
316 daniel-mar 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
                        }
302 daniel-mar 727
                        foreach ($contents as $cont) {
728
                                if ($cont['type'] == 'directory') {
316 daniel-mar 729
                                        $dirname = '/'.urldecode($cont['path']);
302 daniel-mar 730
                                        self::xarray_add($dirname, $dirs);
731
                                        self::xarray_remove($dirname, $dirsDelete);
732
                                } else if ($cont['type'] == 'file') {
316 daniel-mar 733
                                        $filename = '/'.urldecode($cont['path']);
302 daniel-mar 734
                                        self::xarray_add($filename, $files);
735
                                        self::xarray_remove($filename, $filesDelete);
736
                                }
737
                        }
738
                }
171 daniel-mar 739
                $out                = array();
740
                $out['files']       = $files;
741
                $out['filesDelete'] = $filesDelete;
742
                $out['dirs']        = $dirs;
743
                $out['dirsDelete']  = $dirsDelete;
172 daniel-mar 744
                $out['revisions']   = $revlogs;
493 daniel-mar 745
 
505 daniel-mar 746
                if (is_dir($this->cache_dir)) {
747
                        $data = serialize($out);
748
                        if ($data) @file_put_contents($cache_file, $data);
493 daniel-mar 749
                }
750
 
171 daniel-mar 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;
386 daniel-mar 766
                $args = array();
171 daniel-mar 767
                $this->initQuery($args, "PROPFIND", $this->cleanURL($this->_url . "/!svn/vcc/default"));
251 daniel-mar 768
                $args['Body']                      = self::PHPSVN_VERSION_REQUEST;
769
                $args['Headers']['Content-Length'] = strlen(self::PHPSVN_NORMAL_REQUEST);
171 daniel-mar 770
                $args['Headers']['Depth']          = 0;
771
 
386 daniel-mar 772
                $tmp = array();
773
                $body = '';
171 daniel-mar 774
                if (!$this->Request($args, $tmp, $body))
505 daniel-mar 775
                        throw new Exception(_L("Cannot get repository revision (Request failed)"));
171 daniel-mar 776
 
777
                $this->_repVersion = null;
386 daniel-mar 778
                $m = array();
171 daniel-mar 779
                if (preg_match('@/(\d+)\s*</D:href>@ismU', $body, $m)) {
780
                        $this->_repVersion = $m[1];
781
                } else {
505 daniel-mar 782
                        throw new Exception(_L("Cannot get repository revision (RegEx failed)"));
171 daniel-mar 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);
415 daniel-mar 826
                if (substr($http->response_status,0,1) != 2) {
171 daniel-mar 827
                        switch ($http->response_status) {
828
                                case 404:
251 daniel-mar 829
                                        $this->errNro = self::NOT_FOUND;
171 daniel-mar 830
                                        break;
831
                                case 401:
251 daniel-mar 832
                                        $this->errNro = self::AUTH_REQUIRED;
171 daniel-mar 833
                                        break;
834
                                default:
251 daniel-mar 835
                                        $this->errNro = self::UNKNOWN_ERROR;
171 daniel-mar 836
                                        break;
837
                        }
505 daniel-mar 838
                        //throw new Exception(_L('HTTP Error: %1 at %2,$http->response_status,$args['RequestURI']));
171 daniel-mar 839
                        $http->close();
840
                        return false;
841
                }
251 daniel-mar 842
                $this->errNro = self::NO_ERROR;
171 daniel-mar 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) {
505 daniel-mar 871
                $strInputXML = preg_replace('@<([^>]+):@ismU','<\\1__',$strInputXML);
872
                return simplexml_load_string($strInputXML);
171 daniel-mar 873
        }
216 daniel-mar 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
        }
493 daniel-mar 893
}