Completed
Push — 3.0 ( 46111f...d5ff83 )
by Daniel
02:06
created

DeploystrategyAbstract::getDeployedFiles()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
/**
3
 * Composer Magento Installer
4
 */
5
6
namespace MagentoHackathon\Composer\Magento\Deploystrategy;
7
8
use Composer\Util\Filesystem;
9
10
/**
11
 * Abstract deploy strategy
12
 */
13
abstract class DeploystrategyAbstract
14
{
15
    /**
16
     * The path mappings to map project's directories to magento's directory structure
17
     *
18
     * @var array
19
     */
20
    protected $mappings = array();
21
22
    /**
23
     * The current mapping of the deployment iteration
24
     *
25
     * @var array
26
     */
27
    protected $currentMapping = array();
28
29
    /**
30
     * The List of entries which files should not get deployed
31
     *
32
     * @var array
33
     */
34
    protected $ignoredMappings = array();
35
36
37
    /**
38
     * The magento installation's base directory
39
     *
40
     * @var string
41
     */
42
    protected $destDir;
43
44
    /**
45
     * The module's base directory
46
     *
47
     * @var string
48
     */
49
    protected $sourceDir;
50
51
    /**
52
     * If set overrides existing files
53
     *
54
     * @var bool
55
     */
56
    protected $isForced = false;
57
58
    /**
59
     * List of files/folders which were
60
     * created via any of the deploy methods
61
     *
62
     * @var array
63
     */
64
    protected $deployedFiles = array();
65
66
    /**
67
     * List of files/folders which were
68
     * remove via this remove operation
69
     *
70
     * @var array
71
     */
72
    protected $removedFiles = array();
73
74
    /**
75
     * @var Filesystem
76
     */
77
    protected $filesystem;
78
79
    /**
80
     * Constructor
81
     *
82
     * @param string $sourceDir
83
     * @param string $destDir
84
     */
85 71
    public function __construct($sourceDir, $destDir)
86
    {
87 71
        $this->destDir      = $destDir;
88 71
        $this->sourceDir    = $sourceDir;
89 71
        $this->filesystem   = new Filesystem;
90 71
    }
91
92
    /**
93
     * Executes the deployment strategy for each mapping
94
     *
95
     * @return \MagentoHackathon\Composer\Magento\Deploystrategy\DeploystrategyAbstract
96
     */
97 16
    public function deploy()
98
    {
99 10
        $this->beforeDeploy();
100 10
        foreach ($this->getMappings() as $data) {
101 5
            list ($source, $dest) = $data;
102 5
            $this->setCurrentMapping($data);
103 5
            $this->create($source, $dest);
104 16
        }
105 10
        $this->afterDeploy();
106 10
        return $this;
107
    }
108
109
    /**
110
     * beforeDeploy
111
     *
112
     * @return void
113
     */
114 10
    protected function beforeDeploy()
115
    {
116
117 10
    }
118
119
    /**
120
     * afterDeploy
121
     *
122
     * @return void
123
     */
124 10
    protected function afterDeploy()
125
    {
126
127 10
    }
128
129
    /**
130
     * Removes the module's files in the given path from the target dir
131
     *
132
     * @return \MagentoHackathon\Composer\Magento\Deploystrategy\DeploystrategyAbstract
133
     */
134 6
    public function clean()
135
    {
136 6
        $this->beforeClean();
137 6
        foreach ($this->getMappings() as $data) {
138 4
            list ($source, $dest) = $data;
139 4
            $this->remove($source, $dest);
140 4
            $this->rmEmptyDirsRecursive(dirname($dest), $this->getDestDir());
141 6
        }
142 6
        $this->afterClean();
143 6
        return $this;
144
    }
145
146
    /**
147
     * beforeClean
148
     *
149
     * @return void
150
     */
151 6
    protected function beforeClean()
152
    {
153
154 6
    }
155
156
    /**
157
     * afterClean
158
     *
159
     * @return void
160
     */
161 6
    protected function afterClean()
162
    {
163
164 6
    }
165
166
    /**
167
     * Returns the destination dir of the magento module
168
     *
169
     * @return string
170
     */
171 48
    protected function getDestDir()
172
    {
173 48
        return $this->destDir;
174
    }
175
176
    /**
177
     * Returns the current path of the extension
178
     *
179
     * @return mixed
180
     */
181 47
    protected function getSourceDir()
182
    {
183 47
        return $this->sourceDir;
184
    }
185
186
    /**
187
     * If set overrides existing files
188
     *
189
     * @return bool
190
     */
191 4
    public function isForced()
192
    {
193 4
        return $this->isForced;
194
    }
195
196
    /**
197
     * Setter for isForced property
198
     *
199
     * @param bool $forced
200
     */
201 16
    public function setIsForced($forced = true)
202
    {
203 16
        $this->isForced = (bool)$forced;
204 16
    }
205
206
    /**
207
     * Returns the path mappings to map project's directories to magento's directory structure
208
     *
209
     * @return array
210
     */
211 18
    public function getMappings()
212
    {
213 18
        return $this->mappings;
214
    }
215
216
    /**
217
     * Sets path mappings to map project's directories to magento's directory structure
218
     *
219
     * @param array $mappings
220
     */
221 17
    public function setMappings(array $mappings)
222
    {
223 17
        $this->mappings = $mappings;
224 17
    }
225
226
    /**
227
     * Gets the current mapping used on the deployment iteration
228
     *
229
     * @return array
230
     */
231 16
    public function getCurrentMapping()
232
    {
233 16
        return $this->currentMapping;
234
    }
235
236
    /**
237
     * Sets the current mapping used on the deployment iteration
238
     *
239
     * @param array $mapping
240
     */
241 41
    public function setCurrentMapping($mapping)
242
    {
243 41
        $this->currentMapping = $mapping;
244 41
    }
245
246
247
    /**
248
     * sets the current ignored mappings
249
     *
250
     * @param $ignoredMappings
251
     */
252 11
    public function setIgnoredMappings($ignoredMappings)
253
    {
254 11
        $this->ignoredMappings = $ignoredMappings;
255 11
    }
256
257
    /**
258
     * gets the current ignored mappings
259
     *
260
     * @return array
261
     */
262
    public function getIgnoredMappings()
263
    {
264
        return $this->ignoredMappings;
265
    }
266
267
268
    /**
269
     * @param string $destination
270
     *
271
     * @return bool
272
     */
273 47
    protected function isDestinationIgnored($destination)
274
    {
275 47
        $destination = '/' . $destination;
276 47
        $destination = str_replace('/./', '/', $destination);
277 47
        $destination = str_replace('//', '/', $destination);
278 47
        foreach ($this->ignoredMappings as $ignored) {
279
            if (0 === strpos($ignored, $destination)) {
280
                return true;
281
            }
282 47
        }
283 47
        return false;
284
    }
285
286
    /**
287
     * Add a key value pair to mapping
288
     */
289 3
    public function addMapping($key, $value)
290
    {
291 3
        $this->mappings[] = array($key, $value);
292 3
    }
293
294
    /**
295
     * @param string $path
296
     * @return string
297
     */
298 47
    protected function removeLeadingSlash($path)
299
    {
300 47
        return ltrim($path, '\\/');
301
    }
302
303
    /**
304
     * @param string $path
305
     * @return string
306
     */
307 47
    protected function removeTrailingSlash($path)
308
    {
309 47
        return rtrim($path, '\\/');
310
    }
311
312
    /**
313
     * @param string $path
314
     * @return string
315
     */
316
    protected function removeLeadingAndTrailingSlash($path)
317
    {
318
        return trim($path, '\\/');
319
    }
320
321
    /**
322
     * Normalize mapping parameters using a glob wildcard.
323
     *
324
     * Delegate the creation of the module's files in the given destination.
325
     *
326
     * @param string $source
327
     * @param string $dest
328
     *
329
     * @throws \ErrorException
330
     * @return bool
331
     */
332 47
    public function create($source, $dest)
333
    {
334 47
        if ($this->isDestinationIgnored($dest)) {
335
            return;
336
        }
337
338 47
        $sourcePath = $this->getSourceDir() . '/' . $this->removeLeadingSlash($source);
339 47
        $destPath = $this->getDestDir() . '/' . $this->removeLeadingSlash($dest);
340
341
        /* List of possible cases, keep around for now, might come in handy again
342
343
        Assume app/etc exists, app/etc/a does not exist unless specified differently
344
345
        dir app/etc/a/ --> link app/etc/a to dir
346
        dir app/etc/a  --> link app/etc/a to dir
347
        dir app/etc/   --> link app/etc/dir to dir
348
        dir app/etc    --> link app/etc/dir to dir
349
350
        dir/* app/etc     --> for each dir/$file create a target link in app/etc
351
        dir/* app/etc/    --> for each dir/$file create a target link in app/etc
352
        dir/* app/etc/a   --> for each dir/$file create a target link in app/etc/a
353
        dir/* app/etc/a/  --> for each dir/$file create a target link in app/etc/a
354
355
        file app/etc    --> link app/etc/file to file
356
        file app/etc/   --> link app/etc/file to file
357
        file app/etc/a  --> link app/etc/a to file
358
        file app/etc/a  --> if app/etc/a is a file throw exception unless force is set, in that case rm and see above
359
        file app/etc/a/ --> link app/etc/a/file to file regardless if app/etc/a exists or not
360
361
        */
362
363
        // Create target directory if it ends with a directory separator
364 47
        if (!file_exists($destPath) && in_array(substr($destPath, -1), array('/', '\\')) && !is_dir($sourcePath)) {
365 3
            mkdir($destPath, 0777, true);
366 3
            $destPath = $this->removeTrailingSlash($destPath);
367 3
        }
368
369
        // If source doesn't exist, check if it's a glob expression, otherwise we have nothing we can do
370 47
        if (!file_exists($sourcePath)) {
371
            // Handle globing
372 8
            $matches = glob($sourcePath);
373 8
            if (!empty($matches)) {
374 8
                foreach ($matches as $match) {
375 8
                    $absolutePath           = sprintf('%s/%s', $this->removeTrailingSlash($destPath), basename($match));
376 8
                    $relativeDestination    = substr($absolutePath, strlen($this->getDestDir())); //strip off dest dir
377 8
                    $relativeDestination    = $this->removeLeadingSlash($relativeDestination);
378 8
                    $relativeSource         = substr($match, strlen($this->getSourceDir()) + 1);
379
380 8
                    $this->create($relativeSource, $relativeDestination);
381 8
                }
382 8
                return true;
383
            }
384
385
            // Source file isn't a valid file or glob
386
            throw new \ErrorException("Source $sourcePath does not exist");
387
        }
388 47
        return $this->createDelegate($source, $dest);
389
    }
390
391
    /**
392
     * Remove (unlink) the destination file
393
     *
394
     * @param string $source
395
     * @param string $dest
396
     *
397
     * @throws \ErrorException
398
     */
399 4
    public function remove($source, $dest)
400
    {
401 4
        $sourcePath = $this->getSourceDir() . '/' . ltrim($this->removeTrailingSlash($source), '\\/');
402 4
        $destPath = $this->getDestDir() . '/' . ltrim($dest, '\\/');
403
404
        // If source doesn't exist, check if it's a glob expression, otherwise we have nothing we can do
405 4
        if (!file_exists($sourcePath)) {
406
            // Handle globing
407
            $matches = glob($sourcePath);
408
            if (!empty($matches)) {
409
                foreach ($matches as $match) {
410
                    $newDest = substr($destPath . '/' . basename($match), strlen($this->getDestDir()));
411
                    $newDest = ltrim($newDest, ' \\/');
412
                    $this->remove(substr($match, strlen($this->getSourceDir()) + 1), $newDest);
413
                }
414
            }
415
            return;
416
        }
417
418
        // MP Avoid removing whole folders in case the modman file is not 100% well-written
419
        // e.g. app/etc/modules/Testmodule.xml  app/etc/modules/ installs correctly,
420
        // but would otherwise delete the whole app/etc/modules folder!
421 4
        if (basename($sourcePath) !== basename($destPath)) {
422
            $destPath .= '/' . basename($source);
423
        }
424 4
        $this->filesystem->remove($destPath);
425 4
        $this->addRemovedFile($destPath);
426 4
    }
427
428
    /**
429
     * Remove an empty directory branch up to $stopDir, or stop at the first non-empty parent.
430
     *
431
     * @param string $dir
432
     * @param string $stopDir
433
     */
434 4
    public function rmEmptyDirsRecursive($dir, $stopDir = null)
435
    {
436 4
        $absoluteDir = $this->getDestDir() . '/' . $dir;
437 4
        if (is_dir($absoluteDir)) {
438 4
            $iterator = new \RecursiveIteratorIterator(
439 4
                new \RecursiveDirectoryIterator($absoluteDir, \RecursiveDirectoryIterator::SKIP_DOTS),
440
                \RecursiveIteratorIterator::CHILD_FIRST
441 4
            );
442
443 4
            if (iterator_count($iterator) > 0) {
444
                // The directory contains something, do not remove
445 3
                return;
446
            }
447
448
            // RecursiveIteratorIterator have opened handle on $absoluteDir
449
            // that cause Windows to block the directory and not remove it until
450
            // the iterator will be destroyed.
451 4
            unset($iterator);
452
453
            // The specified directory is empty
454 4
            if (@rmdir($absoluteDir)) {
455
                // If the parent directory doesn't match the $stopDir and it's empty, remove it, too
456 4
                $parentDir = dirname($dir);
457 4
                $absoluteParentDir = $this->getDestDir() . '/' . $parentDir;
458 4
                if (!isset($stopDir) || (realpath($stopDir) !== realpath($absoluteParentDir))) {
459
                    // Remove the parent directory if it is empty
460 4
                    $this->rmEmptyDirsRecursive($parentDir);
461 4
                }
462 4
            }
463 4
        }
464 4
    }
465
466
    /**
467
     * Create the module's files in the given destination.
468
     *
469
     * NOTE: source and dest have to be passed as relative directories, like they are listed in the mapping
470
     *
471
     * @param string $source
472
     * @param string $dest
473
     *
474
     * @return bool
475
     */
476
    abstract protected function createDelegate($source, $dest);
477
478
    /**
479
     * Add a file/folder to the list of deployed files
480
     * @param string $file
481
     */
482 38
    public function addDeployedFile($file)
483
    {
484 38
        $destination = str_replace('\\', '/', $this->getDestDir());
485
        //strip of destination deploy
486 38
        $quoted = preg_quote($destination, '/');
487 38
        $file   = preg_replace(sprintf('/^%s/', $quoted), '', $file);
488 38
        $this->deployedFiles[] = $file;
489 38
    }
490
491
    /**
492
     * Add a file/folder to the list of removed files
493
     * @param string $file
494
     */
495 4
    public function addRemovedFile($file)
496
    {
497
        //strip of destination deploy location
498 4
        $file = preg_replace(sprintf('/^%s/', preg_quote($this->getDestDir(), '/')), '', $file);
499 4
        $this->removedFiles[] = $file;
500 4
    }
501
502
    /**
503
     * Get all the deployed files
504
     *
505
     * @return array
506
     */
507 10
    public function getDeployedFiles()
508
    {
509 10
        return array_unique($this->deployedFiles);
510
    }
511
512
    /**
513
     * Get all the removed files
514
     *
515
     * @return array
516
     */
517 3
    public function getRemovedFiles()
518
    {
519 3
        return $this->removedFiles;
520
    }
521
}
522