CopyTask::setPreservepermissions()   A
last analyzed

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
eloc 1
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing\Task\System;
22
23
use Phing\Exception\BuildException;
24
use Phing\Io\File;
25
use Phing\Io\FileUtils;
26
use Phing\Io\IOException;
27
use Phing\Io\SourceFileScanner;
28
use Phing\Mapper\FileNameMapper;
29
use Phing\Mapper\FlattenMapper;
30
use Phing\Mapper\IdentityMapper;
31
use Phing\Project;
32
use Phing\Task;
33
use Phing\Type\Element\FilterChainAware;
34
use Phing\Type\Element\ResourceAware;
35
use Phing\Type\Mapper;
36
use Phing\Util\RegisterSlot;
37
38
/**
39
 * A phing copy task.  Copies a file or directory to a new file
40
 * or directory.  Files are only copied if the source file is newer
41
 * than the destination file, or when the destination file does not
42
 * exist. It is possible to explicitly overwrite existing files.
43
 *
44
 * @author Andreas Aderhold, [email protected]
45
 */
46
class CopyTask extends Task
47
{
48
    use ResourceAware;
49
    use FilterChainAware;
50
51
    /**
52
     * @var File
53
     */
54
    protected $file; // the source file (from xml attribute)
55
56
    /**
57
     * @var File
58
     */
59
    protected $destFile; // the destiantion file (from xml attribute)
60
61
    /**
62
     * @var File
63
     */
64
    protected $destDir; // the destination dir (from xml attribute)
65
66
    protected $overwrite = false; // overwrite destination (from xml attribute)
67
    protected $preserveLMT = false; // sync timestamps (from xml attribute)
68
    protected $preservePermissions = true; // sync permissions (from xml attribute)
69
    protected $includeEmpty = true; // include empty dirs? (from XML)
70
    protected $flatten = false; // apply the FlattenMapper right way (from XML)
71
72
    /**
73
     * @var Mapper
74
     */
75
    protected $mapperElement;
76
77
    protected $fileCopyMap = []; // asoc array containing mapped file names
78
    protected $dirCopyMap = []; // asoc array containing mapped file names
79
    protected $completeDirMap = []; // asoc array containing complete dir names
80
81
    /**
82
     * @var FileUtils
83
     */
84
    protected $fileUtils; // a instance of fileutils
85
86
    protected $verbosity = Project::MSG_VERBOSE;
87
88
    /**
89
     * @var int
90
     */
91
    protected $mode = 0; // mode to create directories with
92
93
    /**
94
     * @var bool
95
     */
96
    protected $haltonerror = true; // stop build on errors
97
98
    protected $enableMultipleMappings = false;
99
100
    /** @var int */
101
    protected $granularity = 0;
102
103
    /**
104
     * Sets up this object internal stuff.
105
     * i.e. the Fileutils instance and default mode.
106
     */
107 66
    public function __construct()
108
    {
109 66
        parent::__construct();
110 66
        $this->fileUtils = new FileUtils();
111 66
        $this->mode = 0777 - umask();
112
    }
113
114
    /**
115
     * Set the number of seconds leeway to give before deciding a
116
     * target is out of date.
117
     *
118
     * @param int $granularity the granularity used to decide if a target is out of date
119
     */
120 2
    public function setGranularity(int $granularity): void
121
    {
122 2
        $this->granularity = $granularity;
123
    }
124
125
    /**
126
     * Set the overwrite flag. IntrospectionHelper takes care of
127
     * booleans in set* methods so we can assume that the right
128
     * value (bool primitive) is coming in here.
129
     *
130
     * @param bool $bool Overwrite the destination file(s) if it/they already exist
131
     */
132 12
    public function setOverwrite($bool)
133
    {
134 12
        $this->overwrite = (bool) $bool;
135
    }
136
137
    /**
138
     * Set whether files copied from directory trees will be "flattened"
139
     * into a single directory.  If there are multiple files with
140
     * the same name in the source directory tree, only the first
141
     * file will be copied into the "flattened" directory, unless
142
     * the forceoverwrite attribute is true.
143
     *
144
     * @param bool $flatten if true flatten the destination directory. Default
145
     *                      is false.
146
     */
147
    public function setFlatten($flatten)
148
    {
149
        $this->flatten = $flatten;
150
    }
151
152
    /**
153
     * Used to force listing of all names of copied files.
154
     *
155
     * @param bool $verbosity
156
     */
157 1
    public function setVerbose($verbosity)
158
    {
159 1
        if ($verbosity) {
160 1
            $this->verbosity = Project::MSG_INFO;
161
        } else {
162
            $this->verbosity = Project::MSG_VERBOSE;
163
        }
164
    }
165
166
    /**
167
     * @see CopyTask::setPreserveLastModified
168
     */
169
    public function setTstamp(bool $preserveLastModified)
170
    {
171
        $this->setPreserveLastModified($preserveLastModified);
172
    }
173
174
    /**
175
     * Set the preserve timestamp flag. IntrospectionHelper takes care of
176
     * booleans in set* methods so we can assume that the right
177
     * value (bool primitive) is coming in here.
178
     */
179 1
    public function setPreserveLastModified(bool $preserveLastModified)
180
    {
181 1
        $this->preserveLMT = $preserveLastModified;
182
    }
183
184
    /**
185
     * Set the preserve permissions flag. IntrospectionHelper takes care of
186
     * booleans in set* methods so we can assume that the right
187
     * value (bool primitive) is coming in here.
188
     */
189
    public function setPreservepermissions(bool $preservePermissions)
190
    {
191
        $this->preservePermissions = $preservePermissions;
192
    }
193
194
    public function setPreservemode(bool $preserveMode)
195
    {
196
        $this->setPreservepermissions($preserveMode);
197
    }
198
199
    /**
200
     * Set the include empty dirs flag. IntrospectionHelper takes care of
201
     * booleans in set* methods so we can assume that the right
202
     * value (bool primitive) is coming in here.
203
     */
204
    public function setIncludeEmptyDirs(bool $includeEmptyDirs)
205
    {
206
        $this->includeEmpty = $includeEmptyDirs;
207
    }
208
209
    /**
210
     * Set the file. We have to manually take care of the
211
     * type that is coming due to limited type support in php
212
     * in and convert it manually if necessary.
213
     *
214
     * @param File $file The source file. Either a string or an PhingFile object
215
     */
216 18
    public function setFile(File $file)
217
    {
218 18
        $this->file = $file;
219
    }
220
221
    /**
222
     * Set the toFile. We have to manually take care of the
223
     * type that is coming due to limited type support in php
224
     * in and convert it manually if necessary.
225
     *
226
     * @param File $file The dest file. Either a string or an PhingFile object
227
     */
228 9
    public function setTofile(File $file)
229
    {
230 9
        $this->destFile = $file;
231
    }
232
233
    /**
234
     * Sets the mode to create destination directories with (ignored on Windows).
235
     * Default mode is taken from umask().
236
     *
237
     * @param int $mode Octal mode
238
     */
239
    public function setMode($mode)
240
    {
241
        $this->mode = (int) base_convert($mode, 8, 10);
242
    }
243
244
    /**
245
     * Set the toDir. We have to manually take care of the
246
     * type that is coming due to limited type support in php
247
     * in and convert it manually if necessary.
248
     *
249
     * @param File $dir The directory, either a string or an PhingFile object
250
     */
251 57
    public function setTodir(File $dir)
252
    {
253 57
        $this->destDir = $dir;
254
    }
255
256 2
    public function setEnableMultipleMappings($enableMultipleMappings)
257
    {
258 2
        $this->enableMultipleMappings = (bool) $enableMultipleMappings;
259
    }
260
261
    public function isEnabledMultipleMappings()
262
    {
263
        return $this->enableMultipleMappings;
264
    }
265
266
    /**
267
     * Set the haltonerror attribute - when true, will
268
     * make the build fail when errors are detected.
269
     *
270
     * @param bool $haltonerror Flag if the build should be stopped on errors
271
     */
272 2
    public function setHaltonerror($haltonerror)
273
    {
274 2
        $this->haltonerror = (bool) $haltonerror;
275
    }
276
277
    /**
278
     * Nested creator, creates one Mapper for this task.
279
     *
280
     * @throws BuildException
281
     *
282
     * @return Mapper The created Mapper type object
283
     */
284 6
    public function createMapper()
285
    {
286 6
        if (null !== $this->mapperElement) {
287
            throw new BuildException('Cannot define more than one mapper', $this->getLocation());
288
        }
289 6
        $this->mapperElement = new Mapper($this->project);
290
291 6
        return $this->mapperElement;
292
    }
293
294
    /**
295
     * The main entry point where everything gets in motion.
296
     *
297
     * @throws BuildException
298
     */
299 65
    public function main()
300
    {
301 65
        $this->validateAttributes();
302
303 65
        if (null !== $this->file) {
304 17
            if ($this->file->exists()) {
305 16
                if (null === $this->destFile) {
306 9
                    $this->destFile = new File($this->destDir, (string) $this->file->getName());
307
                }
308
                if (
309 16
                    true === $this->overwrite
310 16
                    || ($this->file->lastModified() - $this->granularity > $this->destFile->lastModified())
311
                ) {
312 14
                    $this->fileCopyMap[$this->file->getAbsolutePath()] = $this->destFile->getAbsolutePath();
313
                } else {
314 2
                    $this->log($this->file->getName() . ' omitted, ' . $this->destFile->getName() . ' is up to date');
315
                }
316
            } else {
317
                // terminate build
318 1
                $this->logError('Could not find file ' . $this->file->__toString() . ' to copy.');
319
            }
320
        }
321
322 65
        $project = $this->getProject();
323
324
        // process filelists
325 65
        foreach ($this->filelists as $fl) {
326 2
            $fromDir = $fl->getDir($project);
327 2
            $srcFiles = $fl->getFiles($project);
328 2
            $srcDirs = [$fl->getDir($project)];
329
330 2
            if (!$this->flatten && null === $this->mapperElement) {
331 2
                $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
332
            }
333
334 2
            $this->scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
335
        }
336
337 65
        foreach ($this->dirsets as $dirset) {
338
            try {
339 1
                $ds = $dirset->getDirectoryScanner($project);
340 1
                $fromDir = $dirset->getDir($project);
341 1
                $srcDirs = $ds->getIncludedDirectories();
342
343 1
                $srcFiles = [];
344 1
                foreach ($srcDirs as $srcDir) {
345 1
                    $srcFiles[] = $srcDir;
346
                }
347
348
                if (
349 1
                    !$this->flatten
350 1
                    && null === $this->mapperElement
351 1
                    && $ds->isEverythingIncluded()
352
                ) {
353
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
354
                }
355
356 1
                $this->scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
357
            } catch (BuildException $e) {
358
                if (true === $this->haltonerror) {
359
                    throw $e;
360
                }
361
362
                $this->logError($e->getMessage());
363
            }
364
        }
365
366
        // process filesets
367 65
        foreach ($this->filesets as $fs) {
368
            try {
369 48
                $ds = $fs->getDirectoryScanner($project);
370 44
                $fromDir = $fs->getDir($project);
371 44
                $srcFiles = $ds->getIncludedFiles();
372 44
                $srcDirs = $ds->getIncludedDirectories();
373
374
                if (
375 44
                    !$this->flatten
376 44
                    && null === $this->mapperElement
377 44
                    && $ds->isEverythingIncluded()
378
                ) {
379 18
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
380
                }
381
382 44
                $this->scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
383 4
            } catch (BuildException $e) {
384 4
                if (true == $this->haltonerror) {
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...
385 4
                    throw $e;
386
                }
387
388
                $this->logError($e->getMessage());
389
            }
390
        }
391
392
        // go and copy the stuff
393 61
        $this->doWork();
394
395 61
        if (null !== $this->destFile) {
396 18
            $this->destDir = null;
397
        }
398
    }
399
400
    /**
401
     * Validates attributes coming in from XML.
402
     *
403
     * @throws BuildException
404
     */
405 64
    protected function validateAttributes()
406
    {
407 64
        if (null === $this->file && 0 === count($this->dirsets) && 0 === count($this->filesets) && 0 === count($this->filelists)) {
408
            throw new BuildException('CopyTask. Specify at least one source - a file, fileset or filelist.');
409
        }
410
411 64
        if (null !== $this->destFile && null !== $this->destDir) {
412
            throw new BuildException('Only one of destfile and destdir may be set.');
413
        }
414
415 64
        if (null === $this->destFile && null === $this->destDir) {
416
            throw new BuildException('One of destfile or destdir must be set.');
417
        }
418
419 64
        if (null !== $this->file && $this->file->exists() && $this->file->isDirectory()) {
420
            throw new BuildException('Use a fileset to copy directories.');
421
        }
422
423 64
        if (null !== $this->destFile && (count($this->filesets) > 0 || count($this->dirsets) > 0)) {
424
            throw new BuildException('Cannot concatenate multiple files into a single file.');
425
        }
426
427 64
        if (null !== $this->destFile) {
428 8
            $this->destDir = new File($this->destFile->getParent());
429
        }
430
    }
431
432
    /**
433
     * Actually copies the files.
434
     *
435
     * @throws BuildException
436
     */
437 55
    protected function doWork()
438
    {
439
        // These "slots" allow filters to retrieve information about the currently-being-process files
440 55
        $fromSlot = $this->getRegisterSlot('currentFromFile');
441 55
        $fromBasenameSlot = $this->getRegisterSlot('currentFromFile.basename');
442
443 55
        $toSlot = $this->getRegisterSlot('currentToFile');
444 55
        $toBasenameSlot = $this->getRegisterSlot('currentToFile.basename');
445
446 55
        $mapSize = count($this->fileCopyMap);
447 55
        $total = $mapSize;
448
449
        // handle empty dirs if appropriate
450 55
        if ($this->includeEmpty) {
451 55
            $count = 0;
452 55
            foreach ($this->dirCopyMap as $srcdir => $destdir) {
453 4
                $s = new File((string) $srcdir);
454 4
                $d = new File((string) $destdir);
455 4
                if (!$d->exists()) {
456
                    // Setting source directory permissions to target
457
                    // (On permissions preservation, the target directory permissions
458
                    // will be inherited from the source directory, otherwise the 'mode'
459
                    // will be used)
460 4
                    $dirMode = ($this->preservePermissions ? $s->getMode() : $this->mode);
461
462
                    // Directory creation with specific permission mode
463 4
                    if (!$d->mkdirs($dirMode)) {
464
                        $this->logError('Unable to create directory ' . $d->__toString());
465
                    } else {
466 4
                        if ($this->preserveLMT) {
467
                            $d->setLastModified($s->lastModified());
468
                        }
469
470 4
                        ++$count;
471
                    }
472
                }
473
            }
474 55
            if ($count > 0) {
475 4
                $this->log(
476 4
                    'Created ' . $count . ' empty director' . (1 == $count ? 'y' : 'ies') . ' in ' . $this->destDir->getAbsolutePath()
477 4
                );
478
            }
479
        }
480
481 55
        if (0 == $mapSize) {
482 6
            return;
483
        }
484
485 49
        $this->log(
486 49
            'Copying ' . $mapSize . ' file' . (($mapSize) === 1 ? '' : 's') . ' to ' . $this->destDir->getAbsolutePath()
487 49
        );
488
        // walks the map and actually copies the files
489 49
        $count = 0;
490 49
        foreach ($this->fileCopyMap as $from => $toFiles) {
491 49
            if (is_array($toFiles)) {
492 2
                foreach ($toFiles as $to) {
493 2
                    $this->copyToSingleDestination(
494 2
                        $from,
495 2
                        $to,
496 2
                        $fromSlot,
497 2
                        $fromBasenameSlot,
498 2
                        $toSlot,
499 2
                        $toBasenameSlot,
500 2
                        $count,
501 2
                        $total
502 2
                    );
503
                }
504
            } else {
505 47
                $this->copyToSingleDestination(
506 47
                    $from,
507 47
                    $toFiles,
508 47
                    $fromSlot,
509 47
                    $fromBasenameSlot,
510 47
                    $toSlot,
511 47
                    $toBasenameSlot,
512 47
                    $count,
513 47
                    $total
514 47
                );
515
            }
516
        }
517
    }
518
519
    /**
520
     * @param string $message
521
     * @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...
522
     *
523
     * @throws BuildException
524
     */
525 1
    protected function logError($message, $location = null)
526
    {
527 1
        if ($this->haltonerror) {
528
            throw new BuildException($message, $location);
529
        }
530
531 1
        $this->log($message, Project::MSG_ERR);
532
    }
533
534
    /**
535
     * Compares source files to destination files to see if they
536
     * should be copied.
537
     *
538
     * @param string $fromDir
539
     * @param string $toDir
540
     * @param array  $files
541
     * @param array  $dirs
542
     */
543 47
    private function scan(&$fromDir, &$toDir, &$files, &$dirs)
544
    {
545
        /* mappers should be generic, so we get the mappers here and
546
        pass them on to builMap. This method is not redundan like it seems */
547 47
        $mapper = $this->getMapper();
548
549 47
        $this->buildMap($fromDir, $toDir, $files, $mapper, $this->fileCopyMap);
550
551 47
        if ($this->includeEmpty) {
552 47
            $this->buildMap($fromDir, $toDir, $dirs, $mapper, $this->dirCopyMap);
553
        }
554
    }
555
556 47
    private function getMapper()
557
    {
558 47
        $mapper = null;
559 47
        if (null !== $this->mapperElement) {
560 6
            $mapper = $this->mapperElement->getImplementation();
561 41
        } elseif ($this->flatten) {
562
            $mapper = new FlattenMapper();
563
        } else {
564 41
            $mapper = new IdentityMapper();
565
        }
566
567 47
        return $mapper;
568
    }
569
570
    /**
571
     * Builds a map of filenames (from->to) that should be copied.
572
     *
573
     * @param string         $fromDir
574
     * @param string         $toDir
575
     * @param array          $names
576
     * @param FileNameMapper $mapper
577
     * @param array          $map
578
     */
579 47
    private function buildMap($fromDir, $toDir, &$names, $mapper, &$map)
580
    {
581 47
        $toCopy = null;
582 47
        if ($this->overwrite) {
583 3
            $v = [];
584 3
            foreach ($names as $name) {
585 3
                $result = $mapper->main($name);
586 3
                if (null !== $result) {
587 3
                    $v[] = $name;
588
                }
589
            }
590 3
            $toCopy = $v;
591
        } else {
592 44
            $ds = new SourceFileScanner($this);
593 44
            $toCopy = $ds->restrict($names, $fromDir, $toDir, $mapper);
0 ignored issues
show
Bug introduced by
$toDir of type string is incompatible with the type Phing\Io\File expected by parameter $destDir of Phing\Io\SourceFileScanner::restrict(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

593
            $toCopy = $ds->restrict($names, $fromDir, /** @scrutinizer ignore-type */ $toDir, $mapper);
Loading history...
Bug introduced by
$fromDir of type string is incompatible with the type Phing\Io\File expected by parameter $srcDir of Phing\Io\SourceFileScanner::restrict(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

593
            $toCopy = $ds->restrict($names, /** @scrutinizer ignore-type */ $fromDir, $toDir, $mapper);
Loading history...
594
        }
595
596 47
        for ($i = 0, $_i = count($toCopy); $i < $_i; ++$i) {
597 42
            $src = new File($fromDir, $toCopy[$i]);
598 42
            $mapped = $mapper->main($toCopy[$i]);
599 42
            if (!$this->enableMultipleMappings) {
600 40
                $dest = new File($toDir, $mapped[0]);
601 40
                $map[$src->getAbsolutePath()] = $dest->getAbsolutePath();
602
            } else {
603 2
                $mappedFiles = [];
604
605 2
                foreach ($mapped as $mappedFile) {
606 2
                    if (null === $mappedFile) {
607
                        continue;
608
                    }
609 2
                    $dest = new File($toDir, $mappedFile);
610 2
                    $mappedFiles[] = $dest->getAbsolutePath();
611
                }
612 2
                $map[$src->getAbsolutePath()] = $mappedFiles;
613
            }
614
        }
615
    }
616
617
    /**
618
     * @param string       $from
619
     * @param string       $to
620
     * @param RegisterSlot $fromSlot
621
     * @param RegisterSlot $fromBasenameSlot
622
     * @param RegisterSlot $toSlot
623
     * @param RegisterSlot $toBasenameSlot
624
     * @param int          $count
625
     * @param int          $total
626
     */
627 49
    private function copyToSingleDestination(
628
        $from,
629
        $to,
630
        $fromSlot,
631
        $fromBasenameSlot,
632
        $toSlot,
633
        $toBasenameSlot,
634
        &$count,
635
        &$total
636
    ) {
637 49
        if ($from === $to) {
638
            $this->log('Skipping self-copy of ' . $from, $this->verbosity);
639
            --$total;
640
641
            return;
642
        }
643 49
        $this->log('From ' . $from . ' to ' . $to, $this->verbosity);
644
645
        try { // try to copy file
646 49
            $fromFile = new File($from);
647 49
            $toFile = new File($to);
648
649 49
            $fromSlot->setValue($fromFile->getPath());
650 49
            $fromBasenameSlot->setValue($fromFile->getName());
651
652 49
            $toSlot->setValue($toFile->getPath());
653 49
            $toBasenameSlot->setValue($toFile->getName());
654
655 49
            $this->fileUtils->copyFile(
656 49
                $fromFile,
657 49
                $toFile,
658 49
                $this->getProject(),
659 49
                $this->overwrite,
660 49
                $this->preserveLMT,
661 49
                $this->filterChains,
662 49
                $this->mode,
663 49
                $this->preservePermissions,
664 49
                $this->granularity
665 49
            );
666
667 49
            ++$count;
668
        } catch (IOException $ioe) {
669
            $this->logError('Failed to copy ' . $from . ' to ' . $to . ': ' . $ioe->getMessage());
670
        }
671
    }
672
}
673