SVNHelper::__construct()   A
last analyzed

Complexity

Conditions 5
Paths 6

Size

Total Lines 48
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 26
c 0
b 0
f 0
nc 6
nop 2
dl 0
loc 48
rs 9.1928
1
<?php
2
/**
3
 * File containing the {@see AppUtils\SVNHelper} class.
4
 * 
5
 * @package Application Utils
6
 * @subpackage SVNHelper
7
 * @see SVNHelper
8
 */
9
10
namespace AppUtils;
11
12
/**
13
 * Simple helper class to work with SVN repositories.
14
 * Implements only basic SVN commands, like update and
15
 * commit.
16
 *
17
 * @package Application Utils
18
 * @subpackage SVNHelper
19
 * @author Sebastian Mordziol <[email protected]>
20
 * @deprecated Will be removed in a future release.
21
 * 
22
 * @see SVNHelper_Exception
23
 * @see SVNHelper_CommandException
24
 */
25
class SVNHelper
26
{
27
   /**
28
    * @var integer
29
    */
30
    public const ERROR_LOCAL_PATH_DOES_NOT_EXIST = 22401;
31
    
32
   /**
33
    * @var integer
34
    */
35
    public const ERROR_INVALID_REP_URL = 22402;
36
    
37
   /**
38
    * @var integer
39
    */
40
    public const ERROR_PATH_IS_OUTSIDE_REPOSITORY = 22403;
41
    
42
   /**
43
    * @var integer
44
    */
45
    public const ERROR_TARGET_FOLDER_IS_A_FILE = 22404;
46
    
47
   /**
48
    * @var integer
49
    */
50
    public const ERROR_CANNOT_ADD_INEXISTENT_FILE = 22405;
51
    
52
   /**
53
    * @var integer
54
    */
55
    public const ERROR_TARGET_PATH_NOT_FOUND = 22406;
56
    
57
   /**
58
    * @var integer
59
    */
60
    public const ERROR_INVALID_TARGET_TYPE = 22407;
61
    
62
   /**
63
    * @var integer
64
    */
65
    public const ERROR_INVALID_LOG_CALLBACK = 22408; 
66
    
67
   /**
68
    * @var SVNHelper_Target_Folder
69
    */
70
    protected $target;
71
    
72
   /**
73
    * @var string
74
    */
75
    protected $path;
76
    
77
   /**
78
    * @var string
79
    */
80
    protected $url;
81
    
82
   /**
83
    * @var string
84
    */
85
    protected $user;
86
    
87
   /**
88
    * @var string
89
    */
90
    protected $pass;
91
    
92
   /**
93
    * @var array
94
    */
95
    protected $options = array(
96
        'binaries-path' => ''
97
    );
98
    
99
   /**
100
    * @var boolean
101
    */
102
    protected $isWindows = false;
103
    
104
   /**
105
    * @var array
106
    */
107
    protected $normalize = array(
108
        'from' => '\\',
109
        'to' => '/'
110
    );
111
    
112
   /**
113
    * @var string
114
    */
115
    protected $sourcePath;
116
    
117
   /**
118
    * @param string $repPath The path to the repository
119
    * @param string $repURL The SVN URL to the repository
120
    * @throws SVNHelper_Exception
121
    */
122
    public function __construct(string $repPath, string $repURL)
123
    {
124
        $this->isWindows = substr(PHP_OS, 0, 3) == 'WIN';
125
        
126
        if($this->isWindows) {
127
            $this->normalize['from'] = '/';
128
            $this->normalize['to'] = '\\';
129
        }
130
131
        // in case of symlinks, we need to store the original
132
        // path so we can correctly adjust paths later on.
133
        $this->sourcePath = $this->normalizePath($repPath);
134
        
135
        // ensure that the path exists in the filesystem, thanks to
136
        // realpath with the actual filesystem case even if the source
137
        // path case does not entirely match. 
138
        //
139
        // NOTE: In case of symlinks, this resolves the symlink to its source (WIN/NIX)
140
        $realPath = realpath($this->sourcePath);
141
        if(!is_dir($realPath)) {
142
            throw new SVNHelper_Exception(
143
                'Local repository path does not exist',
144
                sprintf(
145
                    'Could not find the path [%s] on disk.',
146
                    $repPath
147
                ),
148
                self::ERROR_LOCAL_PATH_DOES_NOT_EXIST
149
            );
150
        }
151
        
152
        $this->path = $this->normalizePath($realPath);
153
        $this->target = $this->getFolder('');
154
        $this->url = $repURL;
155
        
156
        $result = array();
157
        preg_match_all('%([^:]+):(.+)@(https|http|svn)://(.+)%sm', $repURL, $result, PREG_PATTERN_ORDER);
158
        
159
        if(!isset($result[1]) || !isset($result[1][0])) {
160
            throw new SVNHelper_Exception(
161
                'Invalid SVN repository URL',
162
                'The SVN URL must have the following format: [username:password@http://domain.com/path/to/rep].',
163
                self::ERROR_INVALID_REP_URL
164
            );
165
        }
166
        
167
        $this->pass = $result[2][0];
168
        $this->user = $result[1][0];
169
        $this->url = $result[3][0].'://'.$result[4][0];
170
    }
171
    
172
    public function getAuthUser()
173
    {
174
        return $this->user;
175
    }
176
    
177
    public function getAuthPassword()
178
    {
179
        return $this->pass;
180
    }
181
    
182
   /**
183
    * Normalizes slashes in the path according to the
184
    * operating system, i.e. forward slashes for NIX-systems
185
    * and backward slashes for Windows.
186
    *
187
    * @param string $path An absolute path to normalize
188
    * @param bool $relativize Whether to return a path relative to the repository
189
    * @throws SVNHelper_Exception
190
    * @return string
191
    */
192
    public function normalizePath($path, $relativize=false)
193
    {
194
        if(empty($path)) {
195
            return '';
196
        }
197
        
198
        if($relativize) 
199
        {
200
            $path = $this->normalizePath($path);
201
202
            // path is absolute, and does not match the realpath or the source path?
203
            if(strstr($path, ':'.$this->getSlash()) && (!stristr($path, $this->path) && !stristr($path, $this->sourcePath))) {
204
                throw new SVNHelper_Exception(
205
                    'Cannot relativize path outside of repository',
206
                    sprintf(
207
                        'The path [%s] is outside of the repository [%s].',
208
                        $path, 
209
                        $this->path
210
                    ),
211
                    self::ERROR_PATH_IS_OUTSIDE_REPOSITORY
212
                );
213
            }
214
            
215
            $path = str_replace(array($this->path, $this->sourcePath), '', $path);
216
            return ltrim($path, $this->normalize['to']);
217
        }
218
        
219
        return str_replace(
220
            $this->normalize['from'], 
221
            $this->normalize['to'], 
222
            $path
223
        );
224
    }
225
    
226
   /**
227
    * Retrieves the path slash style according to the
228
    * current operating system.
229
    * 
230
    * @return string
231
    */
232
    public function getSlash()
233
    {
234
        return $this->normalize['to'];
235
    }
236
    
237
   /**
238
    * Keeps instances of files.
239
    * @var SVNHelper_Target[]
240
    */
241
    protected $targets = array();
242
    
243
   /**
244
    * Retrieves a file instance from the SVN repository:
245
    * this allows all possible operations on the file as
246
    * well as accessing more information on it.
247
    * 
248
    * @param string $path A path to the file, relative to the repository path or absolute.
249
    * @return SVNHelper_Target_File
250
    * @throws SVNHelper_Exception
251
    */
252
    public function getFile(string $path) : SVNHelper_Target_File
253
    {
254
        $path = $this->filterPath($path);
255
        
256
        return $this->getTarget('File', $this->relativizePath($path))
257
            ->requireIsFile();
258
    }
259
260
   /**
261
    * Retrieves a folder instance from the SVN repository:
262
    * This allows all possible operations on the folder as
263
    * well as accessing more information on it.
264
    * 
265
    * @param string $path
266
    * @return SVNHelper_Target_Folder
267
    * @throws SVNHelper_Exception
268
    */
269
    public function getFolder(string $path) : SVNHelper_Target_Folder
270
    {
271
        $path = $this->filterPath($path);
272
        
273
        return $this->getTarget('Folder', $this->relativizePath($path))
274
            ->requireIsFolder();
275
    }
276
    
277
   /**
278
    * Passes the path through realpath and ensures it exists.
279
    *
280
    * @param string $path
281
    * @throws SVNHelper_Exception
282
    * @return string
283
    */
284
    protected function filterPath($path)
285
    {
286
        if(empty($path)) {
287
            return '';
288
        }
289
        
290
        $path = $this->getPath().'/'.$this->relativizePath($path);
291
        
292
        $real = realpath($path);
293
        if($real !== false) {
294
            return $real;
295
        }
296
        
297
        throw new SVNHelper_Exception(
298
            'Target file does not exist',
299
            sprintf(
300
                'Could not find file [%s] on disk in SVN repository [%s].',
301
                $path,
302
                $this->getPath()
303
            ),
304
            self::ERROR_TARGET_PATH_NOT_FOUND
305
        );
306
    }
307
    
308
   /**
309
    * Retrieves a target file or folder within the repository.
310
    *
311
    * @param string $type The target type, "File" or "Folder".
312
    * @param string $relativePath A path relative to the root folder.
313
    * @return SVNHelper_Target
314
    */
315
    protected function getTarget(string $type, string $relativePath) : SVNHelper_Target
316
    {
317
        $key = $type.':'.$relativePath;
318
        
319
        $relativePath = $this->normalizePath($relativePath, true);
320
        if(isset($this->targets[$key])) {
321
            return $this->targets[$key];
322
        }
323
324
        $target = null;
325
        
326
        switch($type)
327
        {
328
            case 'File':
329
                $target = new SVNHelper_Target_File($this, $relativePath);
330
                break;
331
                
332
            case 'Folder':
333
                $target = new SVNHelper_Target_Folder($this, $relativePath);
334
                break;
335
                
336
            default:
337
                throw new SVNHelper_Exception(
338
                    'Unknown target type',
339
                    sprintf(
340
                        'The target type [%s] is not a valid type.',
341
                        $type
342
                    ),
343
                    self::ERROR_INVALID_TARGET_TYPE
344
                );
345
        }
346
        
347
        $this->targets[$key] = $target;
348
        
349
        return $target;
350
    }
351
    
352
    public function getPath()
353
    {
354
        return $this->path;
355
    }
356
    
357
    public function getURL()
358
    {
359
        return $this->url;
360
    }
361
    
362
   /**
363
    * Updates the whole SVN repository from the root folder.
364
    * @return SVNHelper_CommandResult
365
    */
366
    public function runUpdate()
367
    {
368
        return $this->createUpdate($this->target)->execute();
369
    }
370
    
371
   /**
372
    * Creates an update command for the target file or folder.
373
    * This can be configured further before it is executed.
374
    * 
375
    * @param SVNHelper_Target $target
376
    * @return SVNHelper_Command_Update
377
    */
378
    public function createUpdate(SVNHelper_Target $target)
379
    {
380
        return $this->createCommand('Update', $target);
381
    }
382
    
383
   /**
384
    * Creates an add command for the targt file or folder.
385
    * 
386
    * @param SVNHelper_Target $target
387
    * @return SVNHelper_Command_Add
388
    */
389
    public function createAdd(SVNHelper_Target $target)
390
    {
391
        return $this->createCommand('Add', $target);
392
    }
393
394
    /**
395
     * Creates an info command for the target file or folder.
396
     *
397
     * @param SVNHelper_Target $target
398
     * @return SVNHelper_Command_Info
399
     */
400
    public function createInfo(SVNHelper_Target $target)
401
    {
402
        return $this->createCommand('Info', $target);
403
    }
404
    
405
   /**
406
    * Creates a status command for the target file or folder.
407
    * 
408
    * @param SVNHelper_Target $target
409
    * @return SVNHelper_Command_Status
410
    */
411
    public function createStatus(SVNHelper_Target $target)
412
    {
413
        return $this->createCommand('Status', $target);
414
    }
415
416
    /**
417
     * Creates a commit command for the target file or folder.
418
     *
419
     * @param SVNHelper_Target $target
420
     * @return SVNHelper_Command_Commit
421
     */
422
    public function createCommit(SVNHelper_Target $target, $message)
423
    {
424
        return $this->createCommand('Commit', $target)->setMessage($message);
425
    }
426
    
427
    protected function createCommand($type, SVNHelper_Target $target)
428
    {
429
        $class = 'AppUtils\SVNHelper_Command_'.$type;
430
431
        $cmd = new $class($this, $target);
432
        return $cmd;
433
    }
434
    
435
   /**
436
    * Creates a path relative to the repository for the target
437
    * file or folder, from an absolute path.
438
    *
439
    * @param string $path An absolute path.
440
    * @return string
441
    */
442
    public function relativizePath($path)
443
    {
444
        return $this->normalizePath($path, true);
445
    }
446
    
447
   /**
448
    * Adds a folder: creates it as necessary (recursive),
449
    * and adds it to be committed if it is not versioned yet.
450
    * Use this instead of {@link getFolder()} when you are
451
    * not sure that it exists yet, and will need it.
452
    * 
453
    * @param string $path Absolute or relative path to the folder
454
    * @throws SVNHelper_Exception
455
    * @return SVNHelper_Target_Folder
456
    */
457
    public function addFolder($path)
458
    {
459
        if(is_dir($path)) {
460
            return $this->getFolder($path);
461
        }
462
        
463
        $path = $this->relativizePath($path);
464
        $tokens = explode($this->getSlash(), $path);
465
        
466
        $target = $this->path;
467
        foreach($tokens as $folder) 
468
        {
469
            $target .= $this->getSlash().$folder;
470
            if(file_exists($target)) 
471
            {
472
                if(!is_dir($target)) {
473
                    throw new SVNHelper_Exception(
474
                        'Target folder is a file',
475
                        sprintf(
476
                            'The folder [%s] is actually a file.',
477
                            $folder
478
                        ),
479
                        self::ERROR_TARGET_FOLDER_IS_A_FILE
480
                    );
481
                }
482
                
483
                continue;
484
            }
485
            
486
            if(!mkdir($target, 0777)) {
487
                throw new SVNHelper_Exception(
488
                    'Cannot create folder',
489
                    sprintf(
490
                        'Could not create the folder [%s] on disk.',
491
                        $target
492
                    )
493
                );
494
            }
495
            
496
            // we commit the new folder directly, since we
497
            // will need it later.
498
            $this->addFolder($target)->runCommit('Added for nested files.');
499
        }
500
        
501
        return $this->getFolder($path)->runAdd();
502
    }
503
    
504
    protected static $logCallback;
505
506
   /**
507
    * Sets the callback function/method to use for
508
    * SVH helper log messages. This gets the message
509
    * and the SVNHelper instance as parameters.
510
    * 
511
    * @param callable $callback
512
    * @throws SVNHelper_Exception
513
    */
514
    public static function setLogCallback($callback)
515
    {
516
        if(!is_callable($callback)) {
517
            throw new SVNHelper_Exception(
518
                'Not a valid logging callback',
519
                'The specified argument is not callable.',
520
                self::ERROR_INVALID_LOG_CALLBACK
521
            );
522
        }
523
        
524
        self::$logCallback = $callback;
525
    }
526
    
527
    public static function log($message)
528
    {
529
        if(isset(self::$logCallback)) {
530
            call_user_func(self::$logCallback, 'SVNHelper | '.$message);
531
        }
532
    }
533
534
   /**
535
    * Retrieves information about the file, and adds it
536
    * to be committed later if it not versioned yet. 
537
    * 
538
    * @param string $path
539
    * @return SVNHelper_Target_File
540
    */
541
    public function addFile($path)
542
    {
543
        return $this->getFile($path)->runAdd();        
544
    }
545
    
546
   /**
547
    * Commits all changes in the repository.
548
    * @param string $message The commit message to log.
549
    */
550
    public function runCommit($message)
551
    {
552
        $this->createCommit($this->getFolder($this->path), $message)->execute();
553
    }
554
555
    protected static $loggers = array();
556
    
557
    public static function registerExceptionLogger($callback)
558
    {
559
        self::$loggers[] = $callback;
560
    }
561
562
    public static function getExceptionLoggers()
563
    {
564
        return self::$loggers;
565
    }
566
}
567