Passed
Push — master ( e1f86a...4e1a3a )
by Siad
05:23
created

CopyTask::setFlatten()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19
20
namespace Phing\Tasks\System;
21
22
use Phing\Exception\BuildException;
23
use Phing\Io\FileUtils;
24
use Phing\Io\IOException;
25
use Phing\Io\File;
26
use Phing\Io\SourceFileScanner;
27
use Phing\Mapper\FileNameMapper;
28
use Phing\Mapper\FlattenMapper;
29
use Phing\Mapper\IdentityMapper;
30
use Phing\Project;
31
use Phing\Task;
32
use Phing\Type\Element\FilterChainAware;
33
use Phing\Type\Element\ResourceAware;
34
use Phing\Type\Mapper;
35
use Phing\Util\RegisterSlot;
36
37
/**
38
 * A phing copy task.  Copies a file or directory to a new file
39
 * or directory.  Files are only copied if the source file is newer
40
 * than the destination file, or when the destination file does not
41
 * exist. It is possible to explicitly overwrite existing files.
42
 *
43
 * @author Andreas Aderhold, [email protected]
44
 *
45
 * @package phing.tasks.system
46
 */
47
class CopyTask extends Task
48
{
49
    use ResourceAware;
50
    use FilterChainAware;
51
52
    /**
53
     * @var File
54
     */
55
    protected $file = null; // the source file (from xml attribute)
56
57
    /**
58
     * @var File
59
     */
60
    protected $destFile = null; // the destiantion file (from xml attribute)
61
62
    /**
63
     * @var File
64
     */
65
    protected $destDir = null; // the destination dir (from xml attribute)
66
67
    protected $overwrite = false; // overwrite destination (from xml attribute)
68
    protected $preserveLMT = false; // sync timestamps (from xml attribute)
69
    protected $preservePermissions = true; // sync permissions (from xml attribute)
70
    protected $includeEmpty = true; // include empty dirs? (from XML)
71
    protected $flatten = false; // apply the FlattenMapper right way (from XML)
72
73
    /**
74
     * @var Mapper
75
     */
76
    protected $mapperElement = null;
77
78
    protected $fileCopyMap = []; // asoc array containing mapped file names
79
    protected $dirCopyMap = []; // asoc array containing mapped file names
80
    protected $completeDirMap = []; // asoc array containing complete dir names
81
82
    /**
83
     * @var FileUtils
84
     */
85
    protected $fileUtils = null; // a instance of fileutils
86
87
    protected $verbosity = Project::MSG_VERBOSE;
88
89
    /**
90
     * @var int $mode
91
     */
92
    protected $mode = 0; // mode to create directories with
93
94
    /**
95
     * @var bool $haltonerror
96
     */
97
    protected $haltonerror = true; // stop build on errors
98
99
    protected $enableMultipleMappings = false;
100
101
    /** @var int $granularity */
102
    protected $granularity = 0;
103
104
    /**
105
     * Sets up this object internal stuff.
106
     * i.e. the Fileutils instance and default mode.
107
     */
108 60
    public function __construct()
109
    {
110 60
        parent::__construct();
111 60
        $this->fileUtils = new FileUtils();
112 60
        $this->mode = 0777 - umask();
113 60
    }
114
115
    /**
116
     * Set the number of seconds leeway to give before deciding a
117
     * target is out of date.
118
     *
119
     * @param int $granularity the granularity used to decide if a target is out of date.
120
     */
121 2
    public function setGranularity(int $granularity): void
122
    {
123 2
        $this->granularity = $granularity;
124 2
    }
125
126
    /**
127
     * Set the overwrite flag. IntrospectionHelper takes care of
128
     * booleans in set* methods so we can assume that the right
129
     * value (boolean primitive) is coming in here.
130
     *
131
     * @param boolean $bool Overwrite the destination file(s) if it/they already exist
132
     *
133
     * @return void
134
     */
135 11
    public function setOverwrite($bool)
136
    {
137 11
        $this->overwrite = (bool) $bool;
138 11
    }
139
140
    /**
141
     * Set whether files copied from directory trees will be "flattened"
142
     * into a single directory.  If there are multiple files with
143
     * the same name in the source directory tree, only the first
144
     * file will be copied into the "flattened" directory, unless
145
     * the forceoverwrite attribute is true.
146
     *
147
     * @param bool $flatten if true flatten the destination directory. Default
148
     *                is false.
149
     */
150
    public function setFlatten($flatten)
151
    {
152
        $this->flatten = $flatten;
153
    }
154
155
    /**
156
     * Used to force listing of all names of copied files.
157
     *
158
     * @param boolean $verbosity
159
     */
160 1
    public function setVerbose($verbosity)
161
    {
162 1
        if ($verbosity) {
163 1
            $this->verbosity = Project::MSG_INFO;
164
        } else {
165
            $this->verbosity = Project::MSG_VERBOSE;
166
        }
167 1
    }
168
169
    /**
170
     * @see CopyTask::setPreserveLastModified
171
     * @param $bool
172
     */
173
    public function setTstamp($bool)
174
    {
175
        $this->setPreserveLastModified($bool);
176
    }
177
178
    /**
179
     * Set the preserve timestamp flag. IntrospectionHelper takes care of
180
     * booleans in set* methods so we can assume that the right
181
     * value (boolean primitive) is coming in here.
182
     *
183
     * @param  boolean $bool Preserve the timestamp on the destination file
184
     * @return void
185
     */
186 1
    public function setPreserveLastModified($bool)
187
    {
188 1
        $this->preserveLMT = (bool) $bool;
189 1
    }
190
191
    /**
192
     * Set the preserve permissions flag. IntrospectionHelper takes care of
193
     * booleans in set* methods so we can assume that the right
194
     * value (boolean primitive) is coming in here.
195
     *
196
     * @param  boolean $bool Preserve the timestamp on the destination file
197
     * @return void
198
     */
199
    public function setPreservepermissions($bool)
200
    {
201
        $this->preservePermissions = (bool) $bool;
202
    }
203
204
    /**
205
     * @param $bool
206
     */
207
    public function setPreservemode($bool)
208
    {
209
        $this->setPreservepermissions($bool);
210
    }
211
212
    /**
213
     * Set the include empty dirs flag. IntrospectionHelper takes care of
214
     * booleans in set* methods so we can assume that the right
215
     * value (boolean primitive) is coming in here.
216
     *
217
     * @param  boolean $bool Flag if empty dirs should be cpoied too
218
     * @return void
219
     */
220
    public function setIncludeEmptyDirs($bool)
221
    {
222
        $this->includeEmpty = (bool) $bool;
223
    }
224
225
    /**
226
     * Set the file. We have to manually take care of the
227
     * type that is coming due to limited type support in php
228
     * in and convert it manually if necessary.
229
     *
230
     * @param File $file The source file. Either a string or an PhingFile object
231
     *
232
     * @return void
233
     */
234 16
    public function setFile(File $file)
235
    {
236 16
        $this->file = $file;
237 16
    }
238
239
    /**
240
     * Set the toFile. We have to manually take care of the
241
     * type that is coming due to limited type support in php
242
     * in and convert it manually if necessary.
243
     *
244
     * @param File $file The dest file. Either a string or an PhingFile object
245
     *
246
     * @return void
247
     */
248 9
    public function setTofile(File $file)
249
    {
250 9
        $this->destFile = $file;
251 9
    }
252
253
    /**
254
     * Sets the mode to create destination directories with (ignored on Windows).
255
     * Default mode is taken from umask()
256
     *
257
     * @param integer $mode Octal mode
258
     *
259
     * @return void
260
     */
261
    public function setMode($mode)
262
    {
263
        $this->mode = (int) base_convert($mode, 8, 10);
264
    }
265
266
    /**
267
     * Set the toDir. We have to manually take care of the
268
     * type that is coming due to limited type support in php
269
     * in and convert it manually if necessary.
270
     *
271
     * @param File $dir The directory, either a string or an PhingFile object
272
     *
273
     * @return void
274
     */
275 51
    public function setTodir(File $dir)
276
    {
277 51
        $this->destDir = $dir;
278 51
    }
279
280 2
    public function setEnableMultipleMappings($enableMultipleMappings)
281
    {
282 2
        $this->enableMultipleMappings = (bool) $enableMultipleMappings;
283 2
    }
284
285
    public function isEnabledMultipleMappings()
286
    {
287
        return $this->enableMultipleMappings;
288
    }
289
290
    /**
291
     * Set the haltonerror attribute - when true, will
292
     * make the build fail when errors are detected.
293
     *
294
     * @param boolean $haltonerror Flag if the build should be stopped on errors
295
     *
296
     * @return void
297
     */
298 1
    public function setHaltonerror($haltonerror)
299
    {
300 1
        $this->haltonerror = (bool) $haltonerror;
301 1
    }
302
303
    /**
304
     * Nested creator, creates one Mapper for this task
305
     *
306
     * @return Mapper         The created Mapper type object
307
     * @throws BuildException
308
     */
309 3
    public function createMapper()
310
    {
311 3
        if ($this->mapperElement !== null) {
312
            throw new BuildException("Cannot define more than one mapper", $this->getLocation());
313
        }
314 3
        $this->mapperElement = new Mapper($this->project);
315
316 3
        return $this->mapperElement;
317
    }
318
319
    /**
320
     * The main entry point where everything gets in motion.
321
     *
322
     * @return true           on success
323
     * @throws BuildException
324
     */
325 59
    public function main()
326
    {
327 59
        $this->validateAttributes();
328
329 59
        if ($this->file !== null) {
330 15
            if ($this->file->exists()) {
331 14
                if ($this->destFile === null) {
332 7
                    $this->destFile = new File($this->destDir, (string) $this->file->getName());
333
                }
334
                if (
335 14
                    $this->overwrite === true
336 14
                    || ($this->file->lastModified() - $this->granularity > $this->destFile->lastModified())
337
                ) {
338 12
                    $this->fileCopyMap[$this->file->getAbsolutePath()] = $this->destFile->getAbsolutePath();
339
                } else {
340 14
                    $this->log($this->file->getName() . " omitted, " . $this->destFile->getName() . " is up to date");
341
                }
342
            } else {
343
                // terminate build
344 1
                $this->logError("Could not find file " . $this->file->__toString() . " to copy.");
345
            }
346
        }
347
348 59
        $project = $this->getProject();
349
350
        // process filelists
351 59
        foreach ($this->filelists as $fl) {
352 2
            $fromDir = $fl->getDir($project);
353 2
            $srcFiles = $fl->getFiles($project);
354 2
            $srcDirs = [$fl->getDir($project)];
355
356 2
            if (!$this->flatten && $this->mapperElement === null) {
357 2
                $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
358
            }
359
360 2
            $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
361
        }
362
363 59
        foreach ($this->dirsets as $dirset) {
364
            try {
365 1
                $ds = $dirset->getDirectoryScanner($project);
366 1
                $fromDir = $dirset->getDir($project);
367 1
                $srcDirs = $ds->getIncludedDirectories();
368
369 1
                $srcFiles = [];
370 1
                foreach ($srcDirs as $srcDir) {
371 1
                    $srcFiles[] = $srcDir;
372
                }
373
374
                if (
375 1
                    !$this->flatten &&
376 1
                    $this->mapperElement === null &&
377 1
                    $ds->isEverythingIncluded()
378
                ) {
379
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
380
                }
381
382 1
                $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
383
            } catch (BuildException $e) {
384
                if ($this->haltonerror === true) {
385
                    throw $e;
386
                }
387
388
                $this->logError($e->getMessage());
389
            }
390
        }
391
392
        // process filesets
393 59
        foreach ($this->filesets as $fs) {
394
            try {
395 44
                $ds = $fs->getDirectoryScanner($project);
396 40
                $fromDir = $fs->getDir($project);
397 40
                $srcFiles = $ds->getIncludedFiles();
398 40
                $srcDirs = $ds->getIncludedDirectories();
399
400
                if (
401 40
                    !$this->flatten
402 40
                    && $this->mapperElement === null
403 40
                    && $ds->isEverythingIncluded()
404
                ) {
405 18
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
406
                }
407
408 40
                $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
409 4
            } catch (BuildException $e) {
410 4
                if ($this->haltonerror == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
411 4
                    throw $e;
412
                }
413
414
                $this->logError($e->getMessage());
415
            }
416
        }
417
418
        // go and copy the stuff
419 55
        $this->doWork();
420
421 55
        if ($this->destFile !== null) {
422 16
            $this->destDir = null;
423
        }
424 55
    }
425
426
    /**
427
     * Validates attributes coming in from XML
428
     *
429
     * @return void
430
     * @throws BuildException
431
     */
432 58
    protected function validateAttributes()
433
    {
434 58
        if ($this->file === null && count($this->dirsets) === 0 && count($this->filesets) === 0 && count($this->filelists) === 0) {
435
            throw new BuildException("CopyTask. Specify at least one source - a file, fileset or filelist.");
436
        }
437
438 58
        if ($this->destFile !== null && $this->destDir !== null) {
439
            throw new BuildException("Only one of destfile and destdir may be set.");
440
        }
441
442 58
        if ($this->destFile === null && $this->destDir === null) {
443
            throw new BuildException("One of destfile or destdir must be set.");
444
        }
445
446 58
        if ($this->file !== null && $this->file->exists() && $this->file->isDirectory()) {
447
            throw new BuildException("Use a fileset to copy directories.");
448
        }
449
450 58
        if ($this->destFile !== null && (count($this->filesets) > 0 || count($this->dirsets) > 0)) {
451
            throw new BuildException("Cannot concatenate multiple files into a single file.");
452
        }
453
454 58
        if ($this->destFile !== null) {
455 8
            $this->destDir = new File($this->destFile->getParent());
456
        }
457 58
    }
458
459
    /**
460
     * Compares source files to destination files to see if they
461
     * should be copied.
462
     *
463
     * @param $fromDir
464
     * @param $toDir
465
     * @param $files
466
     * @param $dirs
467
     *
468
     * @return void
469
     */
470 43
    private function _scan(&$fromDir, &$toDir, &$files, &$dirs)
471
    {
472
        /* mappers should be generic, so we get the mappers here and
473
        pass them on to builMap. This method is not redundan like it seems */
474 43
        $mapper = $this->getMapper();
475
476 43
        $this->buildMap($fromDir, $toDir, $files, $mapper, $this->fileCopyMap);
477
478 43
        if ($this->includeEmpty) {
479 43
            $this->buildMap($fromDir, $toDir, $dirs, $mapper, $this->dirCopyMap);
480
        }
481 43
    }
482
483 43
    private function getMapper()
484
    {
485 43
        $mapper = null;
486 43
        if ($this->mapperElement !== null) {
487 3
            $mapper = $this->mapperElement->getImplementation();
488 40
        } elseif ($this->flatten) {
489
            $mapper = new FlattenMapper();
490
        } else {
491 40
            $mapper = new IdentityMapper();
492
        }
493 43
        return $mapper;
494
    }
495
496
    /**
497
     * Builds a map of filenames (from->to) that should be copied
498
     *
499
     * @param $fromDir
500
     * @param $toDir
501
     * @param $names
502
     * @param FileNameMapper $mapper
503
     * @param $map
504
     *
505
     * @return void
506
     */
507 43
    private function buildMap(&$fromDir, &$toDir, &$names, &$mapper, &$map)
508
    {
509 43
        $toCopy = null;
510 43
        if ($this->overwrite) {
511 3
            $v = [];
512 3
            foreach ($names as $name) {
513 3
                $result = $mapper->main($name);
514 3
                if ($result !== null) {
515 3
                    $v[] = $name;
516
                }
517
            }
518 3
            $toCopy = $v;
519
        } else {
520 40
            $ds = new SourceFileScanner($this);
521 40
            $toCopy = $ds->restrict($names, $fromDir, $toDir, $mapper);
522
        }
523
524 43
        for ($i = 0, $_i = count($toCopy); $i < $_i; $i++) {
525 40
            $src = new File($fromDir, $toCopy[$i]);
526 40
            $mapped = $mapper->main($toCopy[$i]);
527 40
            if (!$this->enableMultipleMappings) {
528 38
                $dest = new File($toDir, $mapped[0]);
529 38
                $map[$src->getAbsolutePath()] = $dest->getAbsolutePath();
530
            } else {
531 2
                $mappedFiles = [];
532
533 2
                foreach ($mapped as $mappedFile) {
534 2
                    if ($mappedFile === null) {
535
                        continue;
536
                    }
537 2
                    $dest = new File($toDir, $mappedFile);
538 2
                    $mappedFiles[] = $dest->getAbsolutePath();
539
                }
540 2
                $map[$src->getAbsolutePath()] = $mappedFiles;
541
            }
542
        }
543 43
    }
544
545
    /**
546
     * Actually copies the files
547
     *
548
     * @return void
549
     * @throws BuildException
550
     */
551 49
    protected function doWork()
552
    {
553
554
        // These "slots" allow filters to retrieve information about the currently-being-process files
555 49
        $fromSlot = $this->getRegisterSlot("currentFromFile");
556 49
        $fromBasenameSlot = $this->getRegisterSlot("currentFromFile.basename");
557
558 49
        $toSlot = $this->getRegisterSlot("currentToFile");
559 49
        $toBasenameSlot = $this->getRegisterSlot("currentToFile.basename");
560
561 49
        $mapSize = count($this->fileCopyMap);
562 49
        $total = $mapSize;
563
564
        // handle empty dirs if appropriate
565 49
        if ($this->includeEmpty) {
566 49
            $count = 0;
567 49
            foreach ($this->dirCopyMap as $srcdir => $destdir) {
568 4
                $s = new File((string) $srcdir);
569 4
                $d = new File((string) $destdir);
570 4
                if (!$d->exists()) {
571
                    // Setting source directory permissions to target
572
                    // (On permissions preservation, the target directory permissions
573
                    // will be inherited from the source directory, otherwise the 'mode'
574
                    // will be used)
575 4
                    $dirMode = ($this->preservePermissions ? $s->getMode() : $this->mode);
576
577
                    // Directory creation with specific permission mode
578 4
                    if (!$d->mkdirs($dirMode)) {
579
                        $this->logError("Unable to create directory " . $d->__toString());
580
                    } else {
581 4
                        if ($this->preserveLMT) {
582
                            $d->setLastModified($s->lastModified());
583
                        }
584
585 4
                        $count++;
586
                    }
587
                }
588
            }
589 49
            if ($count > 0) {
590 4
                $this->log(
591 4
                    "Created " . $count . " empty director" . ($count == 1 ? "y" : "ies") . " in " . $this->destDir->getAbsolutePath()
592
                );
593
            }
594
        }
595
596 49
        if ($mapSize == 0) {
597 4
            return;
598
        }
599
600 45
        $this->log(
601 45
            "Copying " . $mapSize . " file" . (($mapSize) === 1 ? '' : 's') . " to " . $this->destDir->getAbsolutePath()
602
        );
603
        // walks the map and actually copies the files
604 45
        $count = 0;
605 45
        foreach ($this->fileCopyMap as $from => $toFiles) {
606 45
            if (is_array($toFiles)) {
607 2
                foreach ($toFiles as $to) {
608 2
                    $this->copyToSingleDestination(
609 2
                        $from,
610
                        $to,
611
                        $fromSlot,
612
                        $fromBasenameSlot,
613
                        $toSlot,
614
                        $toBasenameSlot,
615
                        $count,
616
                        $total
617
                    );
618
                }
619
            } else {
620 43
                $this->copyToSingleDestination(
621 43
                    $from,
622
                    $toFiles,
623
                    $fromSlot,
624
                    $fromBasenameSlot,
625
                    $toSlot,
626
                    $toBasenameSlot,
627
                    $count,
628
                    $total
629
                );
630
            }
631
        }
632 45
    }
633
634
    /**
635
     * @param $from
636
     * @param $to
637
     * @param RegisterSlot $fromSlot
638
     * @param RegisterSlot $fromBasenameSlot
639
     * @param RegisterSlot $toSlot
640
     * @param RegisterSlot $toBasenameSlot
641
     * @param $count
642
     * @param $total
643
     */
644 45
    private function copyToSingleDestination(
645
        $from,
646
        $to,
647
        $fromSlot,
648
        $fromBasenameSlot,
649
        $toSlot,
650
        $toBasenameSlot,
651
        &$count,
652
        &$total
653
    ) {
654 45
        if ($from === $to) {
655
            $this->log("Skipping self-copy of " . $from, $this->verbosity);
656
            $total--;
657
            return;
658
        }
659 45
        $this->log("From " . $from . " to " . $to, $this->verbosity);
660
        try { // try to copy file
661 45
            $fromFile = new File($from);
662 45
            $toFile = new File($to);
663
664 45
            $fromSlot->setValue($fromFile->getPath());
665 45
            $fromBasenameSlot->setValue($fromFile->getName());
666
667 45
            $toSlot->setValue($toFile->getPath());
668 45
            $toBasenameSlot->setValue($toFile->getName());
669
670 45
            $this->fileUtils->copyFile(
671 45
                $fromFile,
672
                $toFile,
673 45
                $this->getProject(),
674 45
                $this->overwrite,
675 45
                $this->preserveLMT,
676 45
                $this->filterChains,
677 45
                $this->mode,
678 45
                $this->preservePermissions,
679 45
                $this->granularity
680
            );
681
682 45
            $count++;
683
        } catch (IOException $ioe) {
684
            $this->logError("Failed to copy " . $from . " to " . $to . ": " . $ioe->getMessage());
685
        }
686 45
    }
687
688
    /**
689
     * @param string $message
690
     * @param null $location
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $location is correct as it would always require null to be passed?
Loading history...
691
     *
692
     * @throws BuildException
693
     */
694 1
    protected function logError($message, $location = null)
695
    {
696 1
        if ($this->haltonerror) {
697
            throw new BuildException($message, $location);
698
        }
699
700 1
        $this->log($message, Project::MSG_ERR);
701 1
    }
702
}
703