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
/**
21
 * A phing copy task.  Copies a file or directory to a new file
22
 * or directory.  Files are only copied if the source file is newer
23
 * than the destination file, or when the destination file does not
24
 * exist. It is possible to explicitly overwrite existing files.
25
 *
26
 * @author Andreas Aderhold, andi@binarycloud.com
27
 *
28
 * @package phing.tasks.system
29
 */
30
class CopyTask extends Task
31
{
32
    use ResourceAware;
33
    use FilterChainAware;
34

35
    /**
36
     * @var PhingFile
37
     */
38
    protected $file = null; // the source file (from xml attribute)
39

40
    /**
41
     * @var PhingFile
42
     */
43
    protected $destFile = null; // the destiantion file (from xml attribute)
44

45
    /**
46
     * @var PhingFile
47
     */
48
    protected $destDir = null; // the destination dir (from xml attribute)
49

50
    protected $overwrite = false; // overwrite destination (from xml attribute)
51
    protected $preserveLMT = false; // sync timestamps (from xml attribute)
52
    protected $preservePermissions = true; // sync permissions (from xml attribute)
53
    protected $includeEmpty = true; // include empty dirs? (from XML)
54
    protected $flatten = false; // apply the FlattenMapper right way (from XML)
55

56
    /**
57
     * @var Mapper
58
     */
59
    protected $mapperElement = null;
60

61
    protected $fileCopyMap = []; // asoc array containing mapped file names
62
    protected $dirCopyMap = []; // asoc array containing mapped file names
63
    protected $completeDirMap = []; // asoc array containing complete dir names
64

65
    /**
66
     * @var FileUtils
67
     */
68
    protected $fileUtils = null; // a instance of fileutils
69

70
    protected $verbosity = Project::MSG_VERBOSE;
71

72
    /**
73
     * @var int $mode
74
     */
75
    protected $mode = 0; // mode to create directories with
76

77
    /**
78
     * @var bool $haltonerror
79
     */
80
    protected $haltonerror = true; // stop build on errors
81

82
    protected $enableMultipleMappings = false;
83

84
    /** @var int $granularity */
85
    protected $granularity = 0;
86

87
    /**
88
     * Sets up this object internal stuff.
89
     * i.e. the Fileutils instance and default mode.
90
     */
91 1
    public function __construct()
92
    {
93 1
        parent::__construct();
94 1
        $this->fileUtils = new FileUtils();
95 1
        $this->mode = 0777 - umask();
96
    }
97

98
    /**
99
     * Set the number of seconds leeway to give before deciding a
100
     * target is out of date.
101
     *
102
     * @param int $granularity the granularity used to decide if a target is out of date.
103
     */
104 1
    public function setGranularity(int $granularity): void
105
    {
106 1
        $this->granularity = $granularity;
107
    }
108

109
    /**
110
     * Set the overwrite flag. IntrospectionHelper takes care of
111
     * booleans in set* methods so we can assume that the right
112
     * value (boolean primitive) is coming in here.
113
     *
114
     * @param boolean $bool Overwrite the destination file(s) if it/they already exist
115
     *
116
     * @return void
117
     */
118 1
    public function setOverwrite($bool)
119
    {
120 1
        $this->overwrite = (bool) $bool;
121
    }
122

123
    /**
124
     * Set whether files copied from directory trees will be "flattened"
125
     * into a single directory.  If there are multiple files with
126
     * the same name in the source directory tree, only the first
127
     * file will be copied into the "flattened" directory, unless
128
     * the forceoverwrite attribute is true.
129
     *
130
     * @param bool $flatten if true flatten the destination directory. Default
131
     *                is false.
132
     */
133 0
    public function setFlatten($flatten)
134
    {
135 0
        $this->flatten = $flatten;
136
    }
137

138
    /**
139
     * Used to force listing of all names of copied files.
140
     *
141
     * @param boolean $verbosity
142
     */
143 1
    public function setVerbose($verbosity)
144
    {
145 1
        if ($verbosity) {
146 1
            $this->verbosity = Project::MSG_INFO;
147
        } else {
148 0
            $this->verbosity = Project::MSG_VERBOSE;
149
        }
150
    }
151

152
    /**
153
     * @see CopyTask::setPreserveLastModified
154
     * @param $bool
155
     */
156 0
    public function setTstamp($bool)
157
    {
158 0
        $this->setPreserveLastModified($bool);
159
    }
160

161
    /**
162
     * Set the preserve timestamp flag. IntrospectionHelper takes care of
163
     * booleans in set* methods so we can assume that the right
164
     * value (boolean primitive) is coming in here.
165
     *
166
     * @param  boolean $bool Preserve the timestamp on the destination file
167
     * @return void
168
     */
169 1
    public function setPreserveLastModified($bool)
170
    {
171 1
        $this->preserveLMT = (bool) $bool;
172
    }
173

174
    /**
175
     * Set the preserve permissions flag. IntrospectionHelper takes care of
176
     * booleans in set* methods so we can assume that the right
177
     * value (boolean primitive) is coming in here.
178
     *
179
     * @param  boolean $bool Preserve the timestamp on the destination file
180
     * @return void
181
     */
182 0
    public function setPreservepermissions($bool)
183
    {
184 0
        $this->preservePermissions = (bool) $bool;
185
    }
186

187
    /**
188
     * @param $bool
189
     */
190 0
    public function setPreservemode($bool)
191
    {
192 0
        $this->setPreservepermissions($bool);
193
    }
194

195
    /**
196
     * Set the include empty dirs flag. IntrospectionHelper takes care of
197
     * booleans in set* methods so we can assume that the right
198
     * value (boolean primitive) is coming in here.
199
     *
200
     * @param  boolean $bool Flag if empty dirs should be cpoied too
201
     * @return void
202
     */
203 0
    public function setIncludeEmptyDirs($bool)
204
    {
205 0
        $this->includeEmpty = (bool) $bool;
206
    }
207

208
    /**
209
     * Set the file. We have to manually take care of the
210
     * type that is coming due to limited type support in php
211
     * in and convert it manually if necessary.
212
     *
213
     * @param PhingFile $file The source file. Either a string or an PhingFile object
214
     *
215
     * @return void
216
     */
217 1
    public function setFile(PhingFile $file)
218
    {
219 1
        $this->file = $file;
220
    }
221

222
    /**
223
     * Set the toFile. We have to manually take care of the
224
     * type that is coming due to limited type support in php
225
     * in and convert it manually if necessary.
226
     *
227
     * @param PhingFile $file The dest file. Either a string or an PhingFile object
228
     *
229
     * @return void
230
     */
231 1
    public function setTofile(PhingFile $file)
232
    {
233 1
        $this->destFile = $file;
234
    }
235

236
    /**
237
     * Sets the mode to create destination directories with (ignored on Windows).
238
     * Default mode is taken from umask()
239
     *
240
     * @param integer $mode Octal mode
241
     *
242
     * @return void
243
     */
244 0
    public function setMode($mode)
245
    {
246 0
        $this->mode = (int) base_convert($mode, 8, 10);
247
    }
248

249
    /**
250
     * Set the toDir. We have to manually take care of the
251
     * type that is coming due to limited type support in php
252
     * in and convert it manually if necessary.
253
     *
254
     * @param PhingFile $dir The directory, either a string or an PhingFile object
255
     *
256
     * @return void
257
     */
258 1
    public function setTodir(PhingFile $dir)
259
    {
260 1
        $this->destDir = $dir;
261
    }
262

263 1
    public function setEnableMultipleMappings($enableMultipleMappings)
264
    {
265 1
        $this->enableMultipleMappings = (bool) $enableMultipleMappings;
266
    }
267

268 0
    public function isEnabledMultipleMappings()
269
    {
270 0
        return $this->enableMultipleMappings;
271
    }
272

273
    /**
274
     * Set the haltonerror attribute - when true, will
275
     * make the build fail when errors are detected.
276
     *
277
     * @param boolean $haltonerror Flag if the build should be stopped on errors
278
     *
279
     * @return void
280
     */
281 1
    public function setHaltonerror($haltonerror)
282
    {
283 1
        $this->haltonerror = (bool) $haltonerror;
284
    }
285

286
    /**
287
     * Nested creator, creates one Mapper for this task
288
     *
289
     * @return Mapper         The created Mapper type object
290
     * @throws BuildException
291
     */
292 1
    public function createMapper()
293
    {
294 1
        if ($this->mapperElement !== null) {
295 0
            throw new BuildException("Cannot define more than one mapper", $this->getLocation());
296
        }
297 1
        $this->mapperElement = new Mapper($this->project);
298

299 1
        return $this->mapperElement;
300
    }
301

302
    /**
303
     * The main entry point where everything gets in motion.
304
     *
305
     * @return true           on success
306
     * @throws BuildException
307
     */
308 1
    public function main()
309
    {
310 1
        $this->validateAttributes();
311

312 1
        if ($this->file !== null) {
313 1
            if ($this->file->exists()) {
314 1
                if ($this->destFile === null) {
315 1
                    $this->destFile = new PhingFile($this->destDir, (string) $this->file->getName());
316
                }
317
                if (
318 1
                    $this->overwrite === true
319 1
                    || ($this->file->lastModified() - $this->granularity > $this->destFile->lastModified())
320
                ) {
321 1
                    $this->fileCopyMap[$this->file->getAbsolutePath()] = $this->destFile->getAbsolutePath();
322
                } else {
323 1
                    $this->log($this->file->getName() . " omitted, " . $this->destFile->getName() . " is up to date");
324
                }
325
            } else {
326
                // terminate build
327 1
                $this->logError("Could not find file " . $this->file->__toString() . " to copy.");
328
            }
329
        }
330

331 1
        $project = $this->getProject();
332

333
        // process filelists
334 1
        foreach ($this->filelists as $fl) {
335 1
            $fromDir = $fl->getDir($project);
336 1
            $srcFiles = $fl->getFiles($project);
337 1
            $srcDirs = [$fl->getDir($project)];
338

339 1
            if (!$this->flatten && $this->mapperElement === null) {
340 1
                $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
341
            }
342

343 1
            $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
344
        }
345

346 1
        foreach ($this->dirsets as $dirset) {
347
            try {
348 1
                $ds = $dirset->getDirectoryScanner($project);
349 1
                $fromDir = $dirset->getDir($project);
350 1
                $srcDirs = $ds->getIncludedDirectories();
351

352 1
                $srcFiles = [];
353 1
                foreach ($srcDirs as $srcDir) {
354 1
                    $srcFiles[] = $srcDir;
355
                }
356

357
                if (
358 1
                    !$this->flatten &&
359 1
                    $this->mapperElement === null &&
360 1
                    $ds->isEverythingIncluded()
361
                ) {
362 0
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
363
                }
364

365 1
                $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
366 0
            } catch (BuildException $e) {
367 0
                if ($this->haltonerror === true) {
368 0
                    throw $e;
369
                }
370

371 0
                $this->logError($e->getMessage());
372
            }
373
        }
374

375
        // process filesets
376 1
        foreach ($this->filesets as $fs) {
377
            try {
378 1
                $ds = $fs->getDirectoryScanner($project);
379 1
                $fromDir = $fs->getDir($project);
380 1
                $srcFiles = $ds->getIncludedFiles();
381 1
                $srcDirs = $ds->getIncludedDirectories();
382

383
                if (
384 1
                    !$this->flatten
385 1
                    && $this->mapperElement === null
386 1
                    && $ds->isEverythingIncluded()
387
                ) {
388 1
                    $this->completeDirMap[$fromDir->getAbsolutePath()] = $this->destDir->getAbsolutePath();
389
                }
390

391 1
                $this->_scan($fromDir, $this->destDir, $srcFiles, $srcDirs);
392 0
            } catch (BuildException $e) {
393 0
                if ($this->haltonerror == true) {
394 0
                    throw $e;
395
                }
396

397 0
                $this->logError($e->getMessage());
398
            }
399
        }
400

401
        // go and copy the stuff
402 1
        $this->doWork();
403

404 1
        if ($this->destFile !== null) {
405 1
            $this->destDir = null;
406
        }
407
    }
408

409
    /**
410
     * Validates attributes coming in from XML
411
     *
412
     * @return void
413
     * @throws BuildException
414
     */
415 1
    protected function validateAttributes()
416
    {
417 1
        if ($this->file === null && count($this->dirsets) === 0 && count($this->filesets) === 0 && count($this->filelists) === 0) {
418 0
            throw new BuildException("CopyTask. Specify at least one source - a file, fileset or filelist.");
419
        }
420

421 1
        if ($this->destFile !== null && $this->destDir !== null) {
422 0
            throw new BuildException("Only one of destfile and destdir may be set.");
423
        }
424

425 1
        if ($this->destFile === null && $this->destDir === null) {
426 0
            throw new BuildException("One of destfile or destdir must be set.");
427
        }
428

429 1
        if ($this->file !== null && $this->file->exists() && $this->file->isDirectory()) {
430 0
            throw new BuildException("Use a fileset to copy directories.");
431
        }
432

433 1
        if ($this->destFile !== null && (count($this->filesets) > 0 || count($this->dirsets) > 0)) {
434 0
            throw new BuildException("Cannot concatenate multiple files into a single file.");
435
        }
436

437 1
        if ($this->destFile !== null) {
438 1
            $this->destDir = new PhingFile($this->destFile->getParent());
439
        }
440
    }
441

442
    /**
443
     * Compares source files to destination files to see if they
444
     * should be copied.
445
     *
446
     * @param $fromDir
447
     * @param $toDir
448
     * @param $files
449
     * @param $dirs
450
     *
451
     * @return void
452
     */
453 1
    private function _scan(&$fromDir, &$toDir, &$files, &$dirs)
454
    {
455
        /* mappers should be generic, so we get the mappers here and
456
        pass them on to builMap. This method is not redundan like it seems */
457 1
        $mapper = $this->getMapper();
458

459 1
        $this->buildMap($fromDir, $toDir, $files, $mapper, $this->fileCopyMap);
460

461 1
        if ($this->includeEmpty) {
462 1
            $this->buildMap($fromDir, $toDir, $dirs, $mapper, $this->dirCopyMap);
463
        }
464
    }
465

466 1
    private function getMapper()
467
    {
468 1
        $mapper = null;
469 1
        if ($this->mapperElement !== null) {
470 1
            $mapper = $this->mapperElement->getImplementation();
471 1
        } elseif ($this->flatten) {
472 0
            $mapper = new FlattenMapper();
473
        } else {
474 1
            $mapper = new IdentityMapper();
475
        }
476 1
        return $mapper;
477
    }
478

479
    /**
480
     * Builds a map of filenames (from->to) that should be copied
481
     *
482
     * @param $fromDir
483
     * @param $toDir
484
     * @param $names
485
     * @param FileNameMapper $mapper
486
     * @param $map
487
     *
488
     * @return void
489
     */
490 1
    private function buildMap(&$fromDir, &$toDir, &$names, &$mapper, &$map)
491
    {
492 1
        $toCopy = null;
493 1
        if ($this->overwrite) {
494 1
            $v = [];
495 1
            foreach ($names as $name) {
496 1
                $result = $mapper->main($name);
497 1
                if ($result !== null) {
498 1
                    $v[] = $name;
499
                }
500
            }
501 1
            $toCopy = $v;
502
        } else {
503 1
            $ds = new SourceFileScanner($this);
504 1
            $toCopy = $ds->restrict($names, $fromDir, $toDir, $mapper);
505
        }
506

507 1
        for ($i = 0, $_i = count($toCopy); $i < $_i; $i++) {
508 1
            $src = new PhingFile($fromDir, $toCopy[$i]);
509 1
            $mapped = $mapper->main($toCopy[$i]);
510 1
            if (!$this->enableMultipleMappings) {
511 1
                $dest = new PhingFile($toDir, $mapped[0]);
512 1
                $map[$src->getAbsolutePath()] = $dest->getAbsolutePath();
513
            } else {
514 1
                $mappedFiles = [];
515

516 1
                foreach ($mapped as $mappedFile) {
517 1
                    if ($mappedFile === null) {
518 0
                        continue;
519
                    }
520 1
                    $dest = new PhingFile($toDir, $mappedFile);
521 1
                    $mappedFiles[] = $dest->getAbsolutePath();
522
                }
523 1
                $map[$src->getAbsolutePath()] = $mappedFiles;
524
            }
525
        }
526
    }
527

528
    /**
529
     * Actually copies the files
530
     *
531
     * @return void
532
     * @throws BuildException
533
     */
534 1
    protected function doWork()
535
    {
536

537
        // These "slots" allow filters to retrieve information about the currently-being-process files
538 1
        $fromSlot = $this->getRegisterSlot("currentFromFile");
539 1
        $fromBasenameSlot = $this->getRegisterSlot("currentFromFile.basename");
540

541 1
        $toSlot = $this->getRegisterSlot("currentToFile");
542 1
        $toBasenameSlot = $this->getRegisterSlot("currentToFile.basename");
543

544 1
        $mapSize = count($this->fileCopyMap);
545 1
        $total = $mapSize;
546

547
        // handle empty dirs if appropriate
548 1
        if ($this->includeEmpty) {
549 1
            $count = 0;
550 1
            foreach ($this->dirCopyMap as $srcdir => $destdir) {
551 1
                $s = new PhingFile((string) $srcdir);
552 1
                $d = new PhingFile((string) $destdir);
553 1
                if (!$d->exists()) {
554
                    // Setting source directory permissions to target
555
                    // (On permissions preservation, the target directory permissions
556
                    // will be inherited from the source directory, otherwise the 'mode'
557
                    // will be used)
558 1
                    $dirMode = ($this->preservePermissions ? $s->getMode() : $this->mode);
559

560
                    // Directory creation with specific permission mode
561 1
                    if (!$d->mkdirs($dirMode)) {
562 0
                        $this->logError("Unable to create directory " . $d->__toString());
563
                    } else {
564 1
                        if ($this->preserveLMT) {
565 0
                            $d->setLastModified($s->lastModified());
566
                        }
567

568 1
                        $count++;
569
                    }
570
                }
571
            }
572 1
            if ($count > 0) {
573 1
                $this->log(
574 1
                    "Created " . $count . " empty director" . ($count == 1 ? "y" : "ies") . " in " . $this->destDir->getAbsolutePath()
575
                );
576
            }
577
        }
578

579 1
        if ($mapSize == 0) {
580 1
            return;
581
        }
582

583 1
        $this->log(
584 1
            "Copying " . $mapSize . " file" . (($mapSize) === 1 ? '' : 's') . " to " . $this->destDir->getAbsolutePath()
585
        );
586
        // walks the map and actually copies the files
587 1
        $count = 0;
588 1
        foreach ($this->fileCopyMap as $from => $toFiles) {
589 1
            if (is_array($toFiles)) {
590 1
                foreach ($toFiles as $to) {
591 1
                    $this->copyToSingleDestination(
592 1
                        $from,
593
                        $to,
594
                        $fromSlot,
595
                        $fromBasenameSlot,
596
                        $toSlot,
597
                        $toBasenameSlot,
598
                        $count,
599
                        $total
600
                    );
601
                }
602
            } else {
603 1
                $this->copyToSingleDestination(
604 1
                    $from,
605
                    $toFiles,
606
                    $fromSlot,
607
                    $fromBasenameSlot,
608
                    $toSlot,
609
                    $toBasenameSlot,
610
                    $count,
611
                    $total
612
                );
613
            }
614
        }
615
    }
616

617
    /**
618
     * @param $from
619
     * @param $to
620
     * @param RegisterSlot $fromSlot
621
     * @param RegisterSlot $fromBasenameSlot
622
     * @param RegisterSlot $toSlot
623
     * @param RegisterSlot $toBasenameSlot
624
     * @param $count
625
     * @param $total
626
     */
627 1
    private function copyToSingleDestination(
628
        $from,
629
        $to,
630
        $fromSlot,
631
        $fromBasenameSlot,
632
        $toSlot,
633
        $toBasenameSlot,
634
        &$count,
635
        &$total
636
    ) {
637 1
        if ($from === $to) {
638 0
            $this->log("Skipping self-copy of " . $from, $this->verbosity);
639 0
            $total--;
640 0
            return;
641
        }
642 1
        $this->log("From " . $from . " to " . $to, $this->verbosity);
643
        try { // try to copy file
644 1
            $fromFile = new PhingFile($from);
645 1
            $toFile = new PhingFile($to);
646

647 1
            $fromSlot->setValue($fromFile->getPath());
648 1
            $fromBasenameSlot->setValue($fromFile->getName());
649

650 1
            $toSlot->setValue($toFile->getPath());
651 1
            $toBasenameSlot->setValue($toFile->getName());
652

653 1
            $this->fileUtils->copyFile(
654 1
                $fromFile,
655
                $toFile,
656 1
                $this->getProject(),
657 1
                $this->overwrite,
658 1
                $this->preserveLMT,
659 1
                $this->filterChains,
660 1
                $this->mode,
661 1
                $this->preservePermissions,
662 1
                $this->granularity
663
            );
664

665 1
            $count++;
666 0
        } catch (IOException $ioe) {
667 0
            $this->logError("Failed to copy " . $from . " to " . $to . ": " . $ioe->getMessage());
668
        }
669
    }
670

671
    /**
672
     * @param string $message
673
     * @param null $location
674
     *
675
     * @throws BuildException
676
     */
677 1
    protected function logError($message, $location = null)
678
    {
679 1
        if ($this->haltonerror) {
680 0
            throw new BuildException($message, $location);
681
        }
682

683 1
        $this->log($message, Project::MSG_ERR);
684
    }
685
}

Read our documentation on viewing source code .

Loading