Passed
Push — master ( 4786a0...bc194a )
by Siad
06:56
created

Phing::importFile()   A

Complexity

Conditions 6
Paths 15

Size

Total Lines 39
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 7.8984

Importance

Changes 0
Metric Value
cc 6
eloc 15
nc 15
nop 2
dl 0
loc 39
ccs 10
cts 16
cp 0.625
crap 7.8984
rs 9.2222
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
use SebastianBergmann\Version;
21
use Symfony\Component\Console\Output\ConsoleOutput;
22
23
/**
24
 * Entry point into Phing.  This class handles the full lifecycle of a build -- from
25
 * parsing & handling commandline arguments to assembling the project to shutting down
26
 * and cleaning up in the end.
27
 *
28
 * If you are invoking Phing from an external application, this is still
29
 * the class to use.  Your application can invoke the start() method, passing
30
 * any commandline arguments or additional properties.
31
 *
32
 * @author Andreas Aderhold <[email protected]>
33
 * @author Hans Lellelid <[email protected]>
34
 *
35
 * @package phing
36
 */
37
class Phing
38
{
39
    /**
40
     * Alias for phar file
41
     */
42
    public const PHAR_ALIAS = 'phing.phar';
43
44
    /**
45
     * The default build file name
46
     */
47
    public const DEFAULT_BUILD_FILENAME = "build.xml";
48
    public const PHING_HOME = 'phing.home';
49
    public const PHP_VERSION = 'php.version';
50
    public const PHP_INTERPRETER = 'php.interpreter';
51
52
    /**
53
     * Our current message output status. Follows Project::MSG_XXX
54
     */
55
    private static $msgOutputLevel = Project::MSG_INFO;
56
57
    /**
58
     * PhingFile that we are using for configuration
59
     */
60
    private $buildFile = null;
61
62
    /**
63
     * The build targets
64
     */
65
    private $targets = [];
66
67
    /**
68
     * Set of properties that are passed in from commandline or invoking code.
69
     *
70
     * @var Properties
71
     */
72
    private static $definedProps;
73
74
    /**
75
     * Names of classes to add as listeners to project
76
     */
77
    private $listeners = [];
78
79
    /**
80
     * keep going mode
81
     *
82
     * @var bool $keepGoingMode
83
     */
84
    private $keepGoingMode = false;
85
86
    private $loggerClassname = null;
87
88
    /**
89
     * The class to handle input (can be only one).
90
     */
91
    private $inputHandlerClassname;
92
93
    /**
94
     * Whether or not log output should be reduced to the minimum.
95
     *
96
     * @var bool $silent
97
     */
98
    private $silent = false;
99
100
    /**
101
     * Indicates whether phing should run in strict mode
102
     */
103
    private $strictMode = false;
104
105
    /**
106
     * Indicates if this phing should be run
107
     */
108
    private $readyToRun = false;
109
110
    /**
111
     * Indicates we should only parse and display the project help information
112
     */
113
    private $projectHelp = false;
114
115
    /**
116
     * Used by utility function getResourcePath()
117
     */
118
    private static $importPaths;
119
120
    /**
121
     * System-wide static properties (moved from System)
122
     */
123
    private static $properties = [];
124
125
    /**
126
     * Static system timer.
127
     */
128
    private static $timer;
129
130
    /**
131
     * The current Project
132
     */
133
    private static $currentProject;
134
135
    /**
136
     * Whether to capture PHP errors to buffer.
137
     */
138
    private static $phpErrorCapture = false;
139
140
    /**
141
     * Whether to values in a property file should override existing values.
142
     */
143
    private $propertyFileOverride = false;
144
145
    /**
146
     * Array of captured PHP errors
147
     */
148
    private static $capturedPhpErrors = [];
149
150
    /**
151
     * @var OUtputStream Stream for standard output.
152
     */
153
    private static $out;
154
155
    /**
156
     * @var OutputStream Stream for error output.
157
     */
158
    private static $err;
159
160
    /**
161
     * @var boolean Whether we are using a logfile.
162
     */
163
    private static $isLogFileUsed = false;
164
165
    /**
166
     * Array to hold original ini settings that Phing changes (and needs
167
     * to restore in restoreIni() method).
168
     *
169
     * @var array Struct of array(setting-name => setting-value)
170
     * @see restoreIni()
171
     */
172
    private static $origIniSettings = [];
173
174
    /**
175
     * Whether or not output to the log is to be unadorned.
176
     */
177
    private $emacsMode = false;
178
179
    /**
180
     * @var string
181
     */
182
    private $searchForThis;
183
    private $propertyFiles = [];
184
185
    /**
186
     * Entry point allowing for more options from other front ends.
187
     *
188
     * This method encapsulates the complete build lifecycle.
189
     *
190
     * @param  array $args The commandline args passed to phing shell script.
191
     * @param  array $additionalUserProperties Any additional properties to be passed to Phing (alternative front-end might implement this).
192
     *                                         These additional properties will be available using the getDefinedProperty() method and will
193
     *                                         be added to the project's "user" properties
194
     * @see    execute()
195
     * @see    runBuild()
196
     * @throws Exception - if there is an error during build
197
     */
198
    public static function start($args, array $additionalUserProperties = null)
199
    {
200
        try {
201
            $m = new Phing();
202
            $m->execute($args);
203
        } catch (Exception $exc) {
204
            self::handleLogfile();
205
            self::printMessage($exc);
206
            self::statusExit(1);
207
            return;
208
        }
209
210
        if ($additionalUserProperties !== null) {
211
            foreach ($additionalUserProperties as $key => $value) {
212
                $m::setDefinedProperty($key, $value);
213
            }
214
        }
215
216
        // expect the worst
217
        $exitCode = 1;
218
        try {
219
            try {
220
                $m->runBuild();
221
                $exitCode = 0;
222
            } catch (ExitStatusException $ese) {
223
                $exitCode = $ese->getCode();
224
                if ($exitCode !== 0) {
225
                    self::handleLogfile();
226
                    throw $ese;
227
                }
228
            }
229
        } catch (BuildException $exc) {
230
            // avoid printing output twice: self::printMessage($exc);
231
        } catch (Throwable $exc) {
232
            self::printMessage($exc);
233
        } finally {
234
            self::handleLogfile();
235
        }
236
        self::statusExit($exitCode);
237
    }
238
239
    /**
240
     * This operation is expected to call `exit($int)`, which
241
     * is what the base version does.
242
     * However, it is possible to do something else.
243
     *
244
     * @param int $exitCode code to exit with
245
     */
246
    protected static function statusExit($exitCode)
247
    {
248
        exit($exitCode);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
249
    }
250
251
    /**
252
     * Prints the message of the Exception if it's not null.
253
     *
254
     * @param Exception $t
255
     */
256
    public static function printMessage(Throwable $t)
257
    {
258
        if (self::$err === null) { // Make sure our error output is initialized
259
            self::initializeOutputStreams();
260
        }
261
        if (self::getMsgOutputLevel() >= Project::MSG_VERBOSE) {
262
            self::$err->write((string) $t . PHP_EOL);
263
        } else {
264
            self::$err->write($t->getMessage() . PHP_EOL);
265
        }
266
    }
267
268
    /**
269
     * Sets the stdout and stderr streams if they are not already set.
270
     */
271 1
    private static function initializeOutputStreams()
272
    {
273 1
        if (self::$out === null) {
274
            if (!defined('STDOUT')) {
275
                self::$out = new OutputStream(fopen('php://stdout', 'w'));
0 ignored issues
show
Bug introduced by
It seems like fopen('php://stdout', 'w') can also be of type false; however, parameter $stream of OutputStream::__construct() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

275
                self::$out = new OutputStream(/** @scrutinizer ignore-type */ fopen('php://stdout', 'w'));
Loading history...
276
            } else {
277
                self::$out = new OutputStream(STDOUT);
278
            }
279
        }
280 1
        if (self::$err === null) {
281
            if (!defined('STDERR')) {
282
                self::$err = new OutputStream(fopen('php://stderr', 'w'));
283
            } else {
284
                self::$err = new OutputStream(STDERR);
285
            }
286
        }
287 1
    }
288
289
    /**
290
     * Sets the stream to use for standard (non-error) output.
291
     *
292
     * @param OutputStream $stream The stream to use for standard output.
293
     */
294 1
    public static function setOutputStream(OutputStream $stream)
295
    {
296 1
        self::$out = $stream;
297 1
    }
298
299
    /**
300
     * Gets the stream to use for standard (non-error) output.
301
     *
302
     * @return OutputStream
303
     */
304
    public static function getOutputStream()
305
    {
306
        return self::$out;
307
    }
308
309
    /**
310
     * Sets the stream to use for error output.
311
     *
312
     * @param OutputStream $stream The stream to use for error output.
313
     */
314
    public static function setErrorStream(OutputStream $stream)
315
    {
316
        self::$err = $stream;
317
    }
318
319
    /**
320
     * Gets the stream to use for error output.
321
     *
322
     * @return OutputStream
323
     */
324
    public static function getErrorStream()
325
    {
326
        return self::$err;
327
    }
328
329
    /**
330
     * Close logfiles, if we have been writing to them.
331
     *
332
     * @since Phing 2.3.0
333
     *
334
     * @return void
335
     */
336
    private static function handleLogfile()
337
    {
338
        if (self::$isLogFileUsed) {
339
            self::$err->close();
340
            self::$out->close();
341
        }
342
    }
343
344
    /**
345
     * Making output level a static property so that this property
346
     * can be accessed by other parts of the system, enabling
347
     * us to display more information -- e.g. backtraces -- for "debug" level.
348
     *
349
     * @return int
350
     */
351
    public static function getMsgOutputLevel()
352
    {
353
        return self::$msgOutputLevel;
354
    }
355
356
    /**
357
     * Command line entry point. This method kicks off the building
358
     * of a project object and executes a build using either a given
359
     * target or the default target.
360
     *
361
     * @param array $args Command line args.
362
     *
363
     * @return void
364
     */
365
    public static function fire($args)
366
    {
367
        self::start($args, null);
368
    }
369
370
    /**
371
     * Setup/initialize Phing environment from commandline args.
372
     *
373
     * @param array $args commandline args passed to phing shell.
374
     *
375
     * @throws ConfigurationException
376
     *
377
     * @return void
378
     */
379
    public function execute($args)
380
    {
381
        self::$definedProps = new Properties();
382
        $this->searchForThis = null;
383
384
        // 1) First handle any options which should always
385
        // Note: The order in which these are executed is important (if multiple of these options are specified)
386
387
        if (in_array('-help', $args) || in_array('-h', $args)) {
388
            static::printUsage();
389
390
            return;
391
        }
392
393
        if (in_array('-version', $args) || in_array('-v', $args)) {
394
            static::printVersion();
395
396
            return;
397
        }
398
399
        if (in_array('-init', $args) || in_array('-i', $args)) {
400
            $key = array_search('-init', $args) ?: array_search('-i', $args);
401
            $path = $args[$key + 1] ?? null;
402
403
            self::init($path);
404
405
            return;
406
        }
407
408
        if (in_array('-diagnostics', $args)) {
409
            Diagnostics::doReport(new PrintStream(self::$out));
410
411
            return;
412
        }
413
414
        // 2) Next pull out stand-alone args.
415
        // Note: The order in which these are executed is important (if multiple of these options are specified)
416
417
        if (false !== ($key = array_search('-quiet', $args, true)) || false !== ($key = array_search(
418
            '-q',
419
            $args,
420
            true
421
        ))
422
        ) {
423
            self::$msgOutputLevel = Project::MSG_WARN;
424
            unset($args[$key]);
425
        }
426
427
        if (false !== ($key = array_search('-emacs', $args, true))
428
            || false !== ($key = array_search('-e', $args, true))
429
        ) {
430
            $this->emacsMode = true;
431
            unset($args[$key]);
432
        }
433
434
        if (false !== ($key = array_search('-verbose', $args, true))) {
435
            self::$msgOutputLevel = Project::MSG_VERBOSE;
436
            unset($args[$key]);
437
        }
438
439
        if (false !== ($key = array_search('-debug', $args, true))) {
440
            self::$msgOutputLevel = Project::MSG_DEBUG;
441
            unset($args[$key]);
442
        }
443
444
        if (false !== ($key = array_search('-silent', $args, true))
445
            || false !== ($key = array_search('-S', $args, true))
446
        ) {
447
            $this->silent = true;
448
            unset($args[$key]);
449
        }
450
451
        if (false !== ($key = array_search('-propertyfileoverride', $args, true))) {
452
            $this->propertyFileOverride = true;
453
            unset($args[$key]);
454
        }
455
456
        // 3) Finally, cycle through to parse remaining args
457
        //
458
        $keys = array_keys($args); // Use keys and iterate to max(keys) since there may be some gaps
459
        $max = $keys ? max($keys) : -1;
460
        for ($i = 0; $i <= $max; $i++) {
461
            if (!array_key_exists($i, $args)) {
462
                // skip this argument, since it must have been removed above.
463
                continue;
464
            }
465
466
            $arg = $args[$i];
467
468
            if ($arg == "-logfile") {
469
                try {
470
                    // see: http://phing.info/trac/ticket/65
471
                    if (!isset($args[$i + 1])) {
472
                        $msg = "You must specify a log file when using the -logfile argument\n";
473
                        throw new ConfigurationException($msg);
474
                    }
475
476
                    $logFile = new PhingFile($args[++$i]);
477
                    $out = new FileOutputStream($logFile); // overwrite
478
                    self::setOutputStream($out);
479
                    self::setErrorStream($out);
480
                    self::$isLogFileUsed = true;
481
                } catch (IOException $ioe) {
482
                    $msg = "Cannot write on the specified log file. Make sure the path exists and you have write permissions.";
483
                    throw new ConfigurationException($msg, $ioe);
484
                }
485
            } elseif ($arg == "-buildfile" || $arg == "-file" || $arg == "-f") {
486
                if (!isset($args[$i + 1])) {
487
                    $msg = "You must specify a buildfile when using the -buildfile argument.";
488
                    throw new ConfigurationException($msg);
489
                }
490
491
                $this->buildFile = new PhingFile($args[++$i]);
492
            } elseif ($arg == "-listener") {
493
                if (!isset($args[$i + 1])) {
494
                    $msg = "You must specify a listener class when using the -listener argument";
495
                    throw new ConfigurationException($msg);
496
                }
497
498
                $this->listeners[] = $args[++$i];
499
            } elseif (StringHelper::startsWith("-D", $arg)) {
500
                // Evaluating the property information //
501
                // Checking whether arg. is not just a switch, and next arg. does not starts with switch identifier
502
                if (('-D' == $arg) && (!StringHelper::startsWith('-', $args[$i + 1]))) {
503
                    $name = $args[++$i];
504
                } else {
505
                    $name = substr($arg, 2);
506
                }
507
508
                $value = null;
509
                $posEq = strpos($name, "=");
510
                if ($posEq !== false) {
511
                    $value = substr($name, $posEq + 1);
512
                    $name = substr($name, 0, $posEq);
513
                } elseif ($i < count($args) - 1 && !StringHelper::startsWith("-D", $args[$i + 1])) {
514
                    $value = $args[++$i];
515
                }
516
                self::$definedProps->setProperty($name, $value);
517
            } elseif ($arg == "-logger") {
518
                if (!isset($args[$i + 1])) {
519
                    $msg = "You must specify a classname when using the -logger argument";
520
                    throw new ConfigurationException($msg);
521
                }
522
523
                $this->loggerClassname = $args[++$i];
524
            } elseif ($arg == "-no-strict") {
525
                $this->strictMode = false;
526
            } elseif ($arg == "-strict") {
527
                $this->strictMode = true;
528
            } elseif ($arg == "-inputhandler") {
529
                if ($this->inputHandlerClassname !== null) {
530
                    throw new ConfigurationException("Only one input handler class may be specified.");
531
                }
532
                if (!isset($args[$i + 1])) {
533
                    $msg = "You must specify a classname when using the -inputhandler argument";
534
                    throw new ConfigurationException($msg);
535
                }
536
537
                $this->inputHandlerClassname = $args[++$i];
538
            } elseif ($arg === "-propertyfile") {
539
                $i = $this->handleArgPropertyFile($args, $i);
540
            } elseif ($arg === "-keep-going" || $arg === "-k") {
541
                $this->keepGoingMode = true;
542
            } elseif ($arg == "-longtargets") {
543
                self::$definedProps->setProperty('phing.showlongtargets', 1);
544
            } elseif ($arg == "-projecthelp" || $arg == "-targets" || $arg == "-list" || $arg == "-l" || $arg == "-p") {
545
                // set the flag to display the targets and quit
546
                $this->projectHelp = true;
547
            } elseif ($arg == "-find") {
548
                // eat up next arg if present, default to build.xml
549
                if ($i < count($args) - 1) {
550
                    $this->searchForThis = $args[++$i];
551
                } else {
552
                    $this->searchForThis = self::DEFAULT_BUILD_FILENAME;
553
                }
554
            } elseif (substr($arg, 0, 1) == "-") {
555
                // we don't have any more args
556
                self::printUsage();
557
                self::$err->write(PHP_EOL);
558
                throw new ConfigurationException("Unknown argument: " . $arg);
559
            } else {
560
                // if it's no other arg, it may be the target
561
                $this->targets[] = $arg;
562
            }
563
        }
564
565
        // if buildFile was not specified on the command line,
566
        if ($this->buildFile === null) {
567
            // but -find then search for it
568
            if ($this->searchForThis !== null) {
569
                $this->buildFile = $this->_findBuildFile(self::getProperty("user.dir"), $this->searchForThis);
570
            } else {
571
                $this->buildFile = new PhingFile(self::DEFAULT_BUILD_FILENAME);
572
            }
573
        }
574
575
        try {
576
            // make sure buildfile (or buildfile.dist) exists
577
            if (!$this->buildFile->exists()) {
578
                $distFile = new PhingFile($this->buildFile->getAbsolutePath() . ".dist");
579
                if (!$distFile->exists()) {
580
                    throw new ConfigurationException(
581
                        "Buildfile: " . $this->buildFile->__toString() . " does not exist!"
582
                    );
583
                }
584
                $this->buildFile = $distFile;
585
            }
586
587
            // make sure it's not a directory
588
            if ($this->buildFile->isDirectory()) {
589
                throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is a dir!");
590
            }
591
        } catch (IOException $e) {
592
            // something else happened, buildfile probably not readable
593
            throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is not readable!");
594
        }
595
596
        $this->loadPropertyFiles();
597
598
        $this->readyToRun = true;
599
    }
600
601
    /**
602
     * Handle the -propertyfile argument.
603
     *
604
     * @param array $args
605
     * @param int $pos
606
     *
607
     * @return int
608
     *
609
     * @throws ConfigurationException
610
     * @throws IOException
611
     */
612
    private function handleArgPropertyFile(array $args, int $pos): int
613
    {
614
        if (!isset($args[$pos + 1])) {
615
            throw new ConfigurationException('You must specify a filename when using the -propertyfile argument');
616
        }
617
618
        $this->propertyFiles[] = $args[++$pos];
619
620
        return $pos;
621
    }
622
623
    /**
624
     * @throws IOException
625
     */
626
    private function loadPropertyFiles()
627
    {
628
        foreach ($this->propertyFiles as $filename) {
629
            $fileParserFactory = new FileParserFactory();
630
            $fileParser = $fileParserFactory->createParser(pathinfo($filename, PATHINFO_EXTENSION));
631
            $p = new Properties(null, $fileParser);
632
            try {
633
                $p->load(new PhingFile($filename));
634
            } catch (IOException $e) {
635
                self::$out->write('Could not load property file ' . $filename . ': ' . $e->getMessage());
636
            }
637
            foreach ($p->getProperties() as $prop => $value) {
638
                self::$definedProps->setProperty($prop, $value);
639
            }
640
        }
641
    }
642
643
    /**
644
     * Helper to get the parent file for a given file.
645
     *
646
     * @param PhingFile $file
647
     *
648
     * @return PhingFile Parent file or null if none
649
     */
650
    private function _getParentFile(PhingFile $file)
651
    {
652
        $filename = $file->getAbsolutePath();
653
        $file = new PhingFile($filename);
654
        $filename = $file->getParent();
655
656
        return ($filename === null) ? null : new PhingFile($filename);
0 ignored issues
show
introduced by
The condition $filename === null is always false.
Loading history...
657
    }
658
659
    /**
660
     * Search parent directories for the build file.
661
     *
662
     * Takes the given target as a suffix to append to each
663
     * parent directory in search of a build file.  Once the
664
     * root of the file-system has been reached an exception
665
     * is thrown.
666
     *
667
     * @param string $start Start file path.
668
     * @param string $suffix Suffix filename to look for in parents.
669
     *
670
     * @throws ConfigurationException
671
     *
672
     * @return PhingFile A handle to the build file
673
     */
674
    private function _findBuildFile($start, $suffix)
675
    {
676
        $startf = new PhingFile($start);
677
        $parent = new PhingFile($startf->getAbsolutePath());
678
        $file = new PhingFile($parent, $suffix);
679
680
        // check if the target file exists in the current directory
681
        while (!$file->exists()) {
682
            // change to parent directory
683
            $parent = $this->_getParentFile($parent);
684
685
            // if parent is null, then we are at the root of the fs,
686
            // complain that we can't find the build file.
687
            if ($parent === null) {
688
                throw new ConfigurationException("Could not locate a build file!");
689
            }
690
            // refresh our file handle
691
            $file = new PhingFile($parent, $suffix);
692
        }
693
694
        return $file;
695
    }
696
697
    /**
698
     * Executes the build.
699
     *
700
     * @throws ConfigurationException
701
     * @throws Exception
702
     *
703
     * @return void
704
     */
705
    public function runBuild()
706
    {
707
        if (!$this->readyToRun) {
708
            return;
709
        }
710
711
        $project = new Project();
712
713
        self::setCurrentProject($project);
714
        set_error_handler(['Phing', 'handlePhpError']);
715
716
        $error = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $error is dead and can be removed.
Loading history...
717
718
        $this->addBuildListeners($project);
719
        $this->addInputHandler($project);
720
721
        // set this right away, so that it can be used in logging.
722
        $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
723
        $project->setUserProperty("phing.dir", dirname($this->buildFile->getAbsolutePath()));
724
725
        try {
726
            $project->fireBuildStarted();
727
            $project->init();
728
        } catch (Exception $exc) {
729
            $project->fireBuildFinished($exc);
730
            throw $exc;
731
        }
732
733
        $project->setKeepGoingMode($this->keepGoingMode);
734
735
        $project->setUserProperty("phing.version", static::getPhingVersion());
736
737
        $e = self::$definedProps->keys();
738
        while (count($e)) {
739
            $arg = (string) array_shift($e);
740
            $value = (string) self::$definedProps->getProperty($arg);
741
            $project->setUserProperty($arg, $value);
742
        }
743
        unset($e);
744
745
        $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
746
747
        // first use the Configurator to create the project object
748
        // from the given build file.
749
750
        try {
751
            ProjectConfigurator::configureProject($project, $this->buildFile);
752
        } catch (Exception $exc) {
753
            $project->fireBuildFinished($exc);
754
            restore_error_handler();
755
            self::unsetCurrentProject();
756
            throw $exc;
757
        }
758
759
        // Set the project mode
760
        $project->setStrictMode(StringHelper::booleanValue($this->strictMode));
761
762
        // make sure that we have a target to execute
763
        if (count($this->targets) === 0) {
764
            $this->targets[] = $project->getDefaultTarget();
765
        }
766
767
        // make sure that minimum required phing version is satisfied
768
        try {
769
            $this->comparePhingVersion($project->getPhingVersion());
770
        } catch (Exception $exc) {
771
            $project->fireBuildFinished($exc);
772
            restore_error_handler();
773
            self::unsetCurrentProject();
774
            throw $exc;
775
        }
776
777
        // execute targets if help param was not given
778
        if (!$this->projectHelp) {
779
            try {
780
                $project->executeTargets($this->targets);
781
            } catch (Exception $exc) {
782
                $project->fireBuildFinished($exc);
783
                restore_error_handler();
784
                self::unsetCurrentProject();
785
                throw $exc;
786
            }
787
        }
788
        // if help is requested print it
789
        if ($this->projectHelp) {
790
            try {
791
                $this->printDescription($project);
792
                $this->printTargets($project);
793
            } catch (Exception $exc) {
794
                $project->fireBuildFinished($exc);
795
                restore_error_handler();
796
                self::unsetCurrentProject();
797
                throw $exc;
798
            }
799
        }
800
801
        // finally {
802
        if (!$this->projectHelp) {
803
            $project->fireBuildFinished(null);
804
        }
805
806
        restore_error_handler();
807
        self::unsetCurrentProject();
808
    }
809
810
    /**
811
     * @param string $version
812
     *
813
     * @return int
814
     *
815
     * @throws BuildException
816
     * @throws ConfigurationException
817
     */
818
    private function comparePhingVersion($version)
819
    {
820
        $current = strtolower(self::getPhingVersion());
821
        $current = trim(str_replace('phing', '', $current));
822
823
        // make sure that version checks are not applied to trunk
824
        if ('dev' === $current) {
825
            return 1;
826
        }
827
828
        if (-1 == version_compare($current, $version)) {
829
            throw new BuildException(
830
                sprintf('Incompatible Phing version (%s). Version "%s" required.', $current, $version)
831
            );
832
        }
833
    }
834
835
    /**
836
     * Bind any registered build listeners to this project.
837
     *
838
     * This means adding the logger and any build listeners that were specified
839
     * with -listener arg.
840
     *
841
     * @param  Project $project
842
     * @throws BuildException
843
     * @throws ConfigurationException
844
     * @return void
845
     */
846
    private function addBuildListeners(Project $project)
847
    {
848
        // Add the default listener
849
        $project->addBuildListener($this->createLogger());
850
851
        foreach ($this->listeners as $listenerClassname) {
852
            try {
853
                $clz = Phing::import($listenerClassname);
854
            } catch (Exception $e) {
855
                $msg = "Unable to instantiate specified listener "
856
                    . "class " . $listenerClassname . " : "
857
                    . $e->getMessage();
858
                throw new ConfigurationException($msg);
859
            }
860
861
            $listener = new $clz();
862
863
            if ($listener instanceof StreamRequiredBuildLogger) {
864
                throw new ConfigurationException("Unable to add " . $listenerClassname . " as a listener, since it requires explicit error/output streams. (You can specify it as a -logger.)");
865
            }
866
            $project->addBuildListener($listener);
867
        }
868
    }
869
870
    /**
871
     * Creates the InputHandler and adds it to the project.
872
     *
873
     * @param Project $project the project instance.
874
     *
875
     * @throws ConfigurationException
876
     */
877
    private function addInputHandler(Project $project)
878
    {
879
        if ($this->inputHandlerClassname === null) {
880
            $handler = new ConsoleInputHandler(STDIN, new ConsoleOutput());
881
        } else {
882
            try {
883
                $clz = Phing::import($this->inputHandlerClassname);
884
                $handler = new $clz();
885
                if ($project !== null && method_exists($handler, 'setProject')) {
886
                    $handler->setProject($project);
887
                }
888
            } catch (Exception $e) {
889
                $msg = "Unable to instantiate specified input handler "
890
                    . "class " . $this->inputHandlerClassname . " : "
891
                    . $e->getMessage();
892
                throw new ConfigurationException($msg);
893
            }
894
        }
895
        $project->setInputHandler($handler);
896
    }
897
898
    /**
899
     * Creates the default build logger for sending build events to the log.
900
     *
901
     * @throws BuildException
902
     * @return BuildLogger The created Logger
903
     */
904
    private function createLogger()
905
    {
906
        if ($this->silent) {
907
            $logger = new SilentLogger();
908
            self::$msgOutputLevel = Project::MSG_WARN;
909
        } elseif ($this->loggerClassname !== null) {
910
            self::import($this->loggerClassname);
911
            // get class name part
912
            $classname = self::import($this->loggerClassname);
913
            $logger = new $classname();
914
            if (!($logger instanceof BuildLogger)) {
915
                throw new BuildException($classname . ' does not implement the BuildLogger interface.');
916
            }
917
        } else {
918
            $logger = new DefaultLogger();
919
        }
920
        $logger->setMessageOutputLevel(self::$msgOutputLevel);
921
        $logger->setOutputStream(self::$out);
922
        $logger->setErrorStream(self::$err);
923
        $logger->setEmacsMode($this->emacsMode);
924
925
        return $logger;
926
    }
927
928
    /**
929
     * Sets the current Project
930
     *
931
     * @param Project $p
932
     */
933 1
    public static function setCurrentProject($p)
934
    {
935 1
        self::$currentProject = $p;
936 1
    }
937
938
    /**
939
     * Unsets the current Project
940
     */
941 1
    public static function unsetCurrentProject()
942
    {
943 1
        self::$currentProject = null;
944 1
    }
945
946
    /**
947
     * Gets the current Project.
948
     *
949
     * @return Project Current Project or NULL if none is set yet/still.
950
     */
951 4
    public static function getCurrentProject()
952
    {
953 4
        return self::$currentProject;
954
    }
955
956
    /**
957
     * A static convenience method to send a log to the current (last-setup) Project.
958
     * If there is no currently-configured Project, then this will do nothing.
959
     *
960
     * @param string $message
961
     * @param int $priority Project::MSG_INFO, etc.
962
     */
963
    public static function log($message, $priority = Project::MSG_INFO)
964
    {
965
        $p = self::getCurrentProject();
966
        if ($p) {
0 ignored issues
show
introduced by
$p is of type Project, thus it always evaluated to true.
Loading history...
967
            $p->log($message, $priority);
968
        }
969
    }
970
971
    /**
972
     * Error handler for PHP errors encountered during the build.
973
     * This uses the logging for the currently configured project.
974
     *
975
     * @param $level
976
     * @param string $message
977
     * @param $file
978
     * @param $line
979
     */
980
    public static function handlePhpError($level, $message, $file, $line)
981
    {
982
983
        // don't want to print suppressed errors
984
        if (error_reporting() > 0) {
985
            if (self::$phpErrorCapture) {
986
                self::$capturedPhpErrors[] = [
987
                    'message' => $message,
988
                    'level' => $level,
989
                    'line' => $line,
990
                    'file' => $file
991
                ];
992
            } else {
993
                $message = '[PHP Error] ' . $message;
994
                $message .= ' [line ' . $line . ' of ' . $file . ']';
995
996
                switch ($level) {
997
                    case E_USER_DEPRECATED:
998
                    case E_DEPRECATED:
999
                    case E_STRICT:
1000
                    case E_NOTICE:
1001
                    case E_USER_NOTICE:
1002
                        self::log($message, Project::MSG_VERBOSE);
1003
                        break;
1004
                    case E_WARNING:
1005
                    case E_USER_WARNING:
1006
                        self::log($message, Project::MSG_WARN);
1007
                        break;
1008
                    case E_ERROR:
1009
                    case E_USER_ERROR:
1010
                    default:
1011
                        self::log($message, Project::MSG_ERR);
1012
                } // switch
1013
            } // if phpErrorCapture
1014
        } // if not @
1015
    }
1016
1017
    /**
1018
     * Begins capturing PHP errors to a buffer.
1019
     * While errors are being captured, they are not logged.
1020
     */
1021
    public static function startPhpErrorCapture()
1022
    {
1023
        self::$phpErrorCapture = true;
1024
        self::$capturedPhpErrors = [];
1025
    }
1026
1027
    /**
1028
     * Stops capturing PHP errors to a buffer.
1029
     * The errors will once again be logged after calling this method.
1030
     */
1031
    public static function stopPhpErrorCapture()
1032
    {
1033
        self::$phpErrorCapture = false;
1034
    }
1035
1036
    /**
1037
     * Clears the captured errors without affecting the starting/stopping of the capture.
1038
     */
1039
    public static function clearCapturedPhpErrors()
1040
    {
1041
        self::$capturedPhpErrors = [];
1042
    }
1043
1044
    /**
1045
     * Gets any PHP errors that were captured to buffer.
1046
     *
1047
     * @return array array('message' => message, 'line' => line number, 'file' => file name, 'level' => error level)
1048
     */
1049
    public static function getCapturedPhpErrors()
1050
    {
1051
        return self::$capturedPhpErrors;
1052
    }
1053
1054
    /**
1055
     * Prints the usage of how to use this class
1056
     */
1057
    public static function printUsage()
1058
    {
1059
        $msg = "";
1060
        $msg .= "phing [options] [target [target2 [target3] ...]]" . PHP_EOL;
1061
        $msg .= "Options: " . PHP_EOL;
1062
        $msg .= "  -h -help               print this message" . PHP_EOL;
1063
        $msg .= "  -l -list               list available targets in this project" . PHP_EOL;
1064
        $msg .= "  -i -init [file]        generates an initial buildfile" . PHP_EOL;
1065
        $msg .= "  -v -version            print the version information and exit" . PHP_EOL;
1066
        $msg .= "  -q -quiet              be extra quiet" . PHP_EOL;
1067
        $msg .= "  -S -silent             print nothing but task outputs and build failures" . PHP_EOL;
1068
        $msg .= "  -verbose               be extra verbose" . PHP_EOL;
1069
        $msg .= "  -debug                 print debugging information" . PHP_EOL;
1070
        $msg .= "  -emacs, -e             produce logging information without adornments" . PHP_EOL;
1071
        $msg .= "  -diagnostics           print diagnostics information" . PHP_EOL;
1072
        $msg .= "  -strict                runs build in strict mode, considering a warning as error" . PHP_EOL;
1073
        $msg .= "  -no-strict             runs build normally (overrides buildfile attribute)" . PHP_EOL;
1074
        $msg .= "  -longtargets           show target descriptions during build" . PHP_EOL;
1075
        $msg .= "  -logfile <file>        use given file for log" . PHP_EOL;
1076
        $msg .= "  -logger <classname>    the class which is to perform logging" . PHP_EOL;
1077
        $msg .= "  -listener <classname>  add an instance of class as a project listener" . PHP_EOL;
1078
        $msg .= "  -f -buildfile <file>   use given buildfile" . PHP_EOL;
1079
        $msg .= "  -D<property>=<value>   use value for given property" . PHP_EOL;
1080
        $msg .= "  -keep-going, -k        execute all targets that do not depend" . PHP_EOL;
1081
        $msg .= "                         on failed target(s)" . PHP_EOL;
1082
        $msg .= "  -propertyfile <file>   load all properties from file" . PHP_EOL;
1083
        $msg .= "  -propertyfileoverride  values in property file override existing values" . PHP_EOL;
1084
        $msg .= "  -find <file>           search for buildfile towards the root of the" . PHP_EOL;
1085
        $msg .= "                         filesystem and use it" . PHP_EOL;
1086
        $msg .= "  -inputhandler <file>   the class to use to handle user input" . PHP_EOL;
1087
        //$msg .= "  -recursive <file>      search for buildfile downwards and use it" . PHP_EOL;
1088
        $msg .= PHP_EOL;
1089
        $msg .= "Report bugs to <[email protected]>" . PHP_EOL;
1090
        self::$err->write($msg);
1091
    }
1092
1093
    /**
1094
     * Prints the current Phing version.
1095
     */
1096
    public static function printVersion()
1097
    {
1098
        self::$out->write(self::getPhingVersion() . PHP_EOL);
1099
    }
1100
1101
    /**
1102
     * Creates generic buildfile
1103
     *
1104
     * @param string $path
1105
     */
1106
    public static function init($path)
1107
    {
1108
        if ($buildfilePath = self::initPath($path)) {
1109
            self::initWrite($buildfilePath);
1110
        }
1111
    }
1112
1113
1114
    /**
1115
     * Returns buildfile's path
1116
     *
1117
     * @param $path
1118
     *
1119
     * @return string
1120
     * @throws ConfigurationException
1121
     */
1122
    protected static function initPath($path)
1123
    {
1124
        // Fallback
1125
        if (empty($path)) {
1126
            $defaultDir = self::getProperty('application.startdir');
1127
            $path = $defaultDir . DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1128
        }
1129
1130
        // Adding filename if necessary
1131
        if (is_dir($path)) {
1132
            $path .= DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1133
        }
1134
1135
        // Check if path is available
1136
        $dirname = dirname($path);
1137
        if (is_dir($dirname) && !is_file($path)) {
1138
            return $path;
1139
        }
1140
1141
        // Path is valid, but buildfile already exists
1142
        if (is_file($path)) {
1143
            throw new ConfigurationException('Buildfile already exists.');
1144
        }
1145
1146
        throw new ConfigurationException('Invalid path for sample buildfile.');
1147
    }
1148
1149
1150
    /**
1151
     * Writes sample buildfile
1152
     *
1153
     * If $buildfilePath does not exist, the buildfile is created.
1154
     *
1155
     * @param $buildfilePath buildfile's location
0 ignored issues
show
Documentation Bug introduced by
The doc comment buildfile's at position 0 could not be parsed: Unknown type name 'buildfile's' at position 0 in buildfile's.
Loading history...
1156
     *
1157
     * @return null
1158
     * @throws ConfigurationException
1159
     */
1160
    protected static function initWrite($buildfilePath)
1161
    {
1162
        // Overwriting protection
1163
        if (file_exists($buildfilePath)) {
1164
            throw new ConfigurationException('Cannot overwrite existing file.');
1165
        }
1166
1167
        $content = '<?xml version="1.0" encoding="UTF-8" ?>' . PHP_EOL;
1168
        $content .= '' . PHP_EOL;
1169
        $content .= '<project name="" description="" default="">' . PHP_EOL;
1170
        $content .= '    ' . PHP_EOL;
1171
        $content .= '    <target name="" description="">' . PHP_EOL;
1172
        $content .= '        ' . PHP_EOL;
1173
        $content .= '    </target>' . PHP_EOL;
1174
        $content .= '    ' . PHP_EOL;
1175
        $content .= '</project>' . PHP_EOL;
1176
1177
        file_put_contents($buildfilePath, $content);
1178
    }
1179
1180
    /**
1181
     * Gets the current Phing version based on VERSION.TXT file.
1182
     *
1183
     * @throws ConfigurationException
1184
     *
1185
     * @return string
1186
     */
1187 6
    public static function getPhingVersion()
1188
    {
1189 6
        $versionPath = self::getResourcePath("phing/etc/VERSION.TXT");
1190 6
        if ($versionPath === null) {
0 ignored issues
show
introduced by
The condition $versionPath === null is always false.
Loading history...
1191 6
            $versionPath = self::getResourcePath("etc/VERSION.TXT");
1192
        }
1193 6
        if ($versionPath === null) {
0 ignored issues
show
introduced by
The condition $versionPath === null is always false.
Loading history...
1194
            throw new ConfigurationException("No VERSION.TXT file found; try setting phing.home environment variable.");
1195
        }
1196
        try { // try to read file
1197 6
            $file = new PhingFile($versionPath);
1198 6
            $reader = new FileReader($file);
1199 6
            $phingVersion = trim($reader->read());
1200
        } catch (IOException $iox) {
1201
            throw new ConfigurationException("Can't read version information file");
1202
        }
1203
1204 6
        $basePath = dirname(__DIR__, 2);
1205
1206 6
        $version = new Version($phingVersion, $basePath);
1207
1208 6
        return "Phing " . $version->getVersion();
1209
    }
1210
1211
    /**
1212
     * Print the project description, if any
1213
     *
1214
     * @param Project $project
1215
     *
1216
     * @throws IOException
1217
     */
1218
    public function printDescription(Project $project)
1219
    {
1220
        if ($project->getDescription() !== null) {
1221
            self::$out->write($project->getDescription() . PHP_EOL);
1222
        }
1223
    }
1224
1225
    /**
1226
     * Print out a list of all targets in the current buildfile
1227
     *
1228
     * @param $project
1229
     */
1230 1
    public function printTargets($project)
1231
    {
1232
        // find the target with the longest name
1233 1
        $maxLength = 0;
1234 1
        $targets = $project->getTargets();
1235 1
        $targetName = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $targetName is dead and can be removed.
Loading history...
1236 1
        $targetDescription = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $targetDescription is dead and can be removed.
Loading history...
1237 1
        $currentTarget = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $currentTarget is dead and can be removed.
Loading history...
1238
1239
        // split the targets in top-level and sub-targets depending
1240
        // on the presence of a description
1241
1242 1
        $subNames = [];
1243 1
        $topNameDescMap = [];
1244
1245 1
        foreach ($targets as $currentTarget) {
1246 1
            $targetName = $currentTarget->getName();
1247 1
            $targetDescription = $currentTarget->getDescription();
1248 1
            if ($currentTarget->isHidden()) {
1249
                continue;
1250
            }
1251
1252
            // subtargets are targets w/o descriptions
1253 1
            if ($targetDescription === null) {
1254 1
                $subNames[] = $targetName;
1255
            } else {
1256
                // topNames and topDescriptions are handled later
1257
                // here we store in hash map (for sorting purposes)
1258
                $topNameDescMap[$targetName] = $targetDescription;
1259
                if (strlen($targetName) > $maxLength) {
1260
                    $maxLength = strlen($targetName);
1261
                }
1262
            }
1263
        }
1264
1265
        // Sort the arrays
1266 1
        sort($subNames); // sort array values, resetting keys (which are numeric)
1267 1
        ksort($topNameDescMap); // sort the keys (targetName) keeping key=>val associations
1268
1269 1
        $topNames = array_keys($topNameDescMap);
1270 1
        $topDescriptions = array_values($topNameDescMap);
1271
1272 1
        $defaultTarget = $project->getDefaultTarget();
1273
1274 1
        if ($defaultTarget !== null && $defaultTarget !== "") {
1275
            $defaultName = [];
1276
            $defaultDesc = [];
1277
            $defaultName[] = $defaultTarget;
1278
1279
            $indexOfDefDesc = array_search($defaultTarget, $topNames, true);
1280
            if ($indexOfDefDesc !== false && $indexOfDefDesc >= 0) {
1281
                $defaultDesc = [];
1282
                $defaultDesc[] = $topDescriptions[$indexOfDefDesc];
1283
            }
1284
1285
            $this->_printTargets($defaultName, $defaultDesc, "Default target:", $maxLength);
1286
        }
1287 1
        $this->_printTargets($topNames, $topDescriptions, "Main targets:", $maxLength);
1288 1
        $this->_printTargets($subNames, null, "Subtargets:", 0);
1289 1
    }
1290
1291
    /**
1292
     * Writes a formatted list of target names with an optional description.
1293
     *
1294
     * @param array $names The names to be printed.
1295
     *                             Must not be <code>null</code>.
1296
     * @param array $descriptions The associated target descriptions.
1297
     *                             May be <code>null</code>, in which case
1298
     *                             no descriptions are displayed.
1299
     *                             If non-<code>null</code>, this should have
1300
     *                             as many elements as <code>names</code>.
1301
     * @param string $heading The heading to display.
1302
     *                             Should not be <code>null</code>.
1303
     * @param int $maxlen The maximum length of the names of the targets.
1304
     *                             If descriptions are given, they are padded to this
1305
     *                             position so they line up (so long as the names really
1306
     *                             <i>are</i> shorter than this).
1307
     */
1308 1
    private function _printTargets($names, $descriptions, $heading, $maxlen)
1309
    {
1310 1
        $spaces = '  ';
1311 1
        while (strlen($spaces) < $maxlen) {
1312
            $spaces .= $spaces;
1313
        }
1314 1
        $msg = "";
1315 1
        $msg .= $heading . PHP_EOL;
1316 1
        $msg .= str_repeat("-", 79) . PHP_EOL;
1317
1318 1
        $total = count($names);
1319 1
        for ($i = 0; $i < $total; $i++) {
1320 1
            $msg .= " ";
1321 1
            $msg .= $names[$i];
1322 1
            if (!empty($descriptions)) {
1323
                $msg .= substr($spaces, 0, $maxlen - strlen($names[$i]) + 2);
1324
                $msg .= $descriptions[$i];
1325
            }
1326 1
            $msg .= PHP_EOL;
1327
        }
1328 1
        if ($total > 0) {
1329 1
            self::$out->write($msg . PHP_EOL);
1330
        }
1331 1
    }
1332
1333
    /**
1334
     * Import a path, supporting the following conventions:
1335
     * - PEAR style (@link http://pear.php.net/manual/en/standards.naming.php)
1336
     * - PSR-0 (@link https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
1337
     * - dot-path
1338
     *
1339
     * @param string $dotPath Path
1340
     * @param mixed $classpath String or object supporting __toString()
1341
     *
1342
     * @return string         The unqualified classname (which can be instantiated).
1343
     *
1344
     * @throws BuildException - if cannot find the specified file
1345
     */
1346 758
    public static function import($dotPath, $classpath = null)
1347
    {
1348 758
        if (strpos($dotPath, '.') !== false) {
1349 757
            $classname = StringHelper::unqualify($dotPath);
1350
        } else {
1351 20
            $classname = $dotPath;
1352 20
            $dotPath = '';
1353 20
            $shortClassName = $classname;
1354 20
            if (($lastNsPos = strrpos($shortClassName, '\\'))) {
1355 1
                $namespace = substr($shortClassName, 0, $lastNsPos);
1356 1
                $shortClassName = substr($shortClassName, $lastNsPos + 1);
1357 1
                $dotPath = str_replace('\\', '.', $namespace) . '.';
1358
            }
1359 20
            $dotPath .= str_replace('_', '.', $shortClassName);
1360
        }
1361
1362
        // first check to see that the class specified hasn't already been included.
1363
        // (this also handles case where this method is called w/ a classname rather than dotpath)
1364 758
        if (class_exists($classname)) {
1365 756
            return $classname;
1366
        }
1367
1368 7
        $dotClassname = basename($dotPath);
1369 7
        $dotClassnamePos = strlen($dotPath) - strlen($dotClassname);
1370
1371
        // 1- temporarily replace escaped '.' with another illegal char (#)
1372 7
        $tmp = str_replace('\.', '##', $dotClassname);
1373
        // 2- swap out the remaining '.' with DIR_SEP
1374 7
        $tmp = strtr($tmp, '.', DIRECTORY_SEPARATOR);
1375
        // 3- swap back the escaped '.'
1376 7
        $tmp = str_replace('##', '.', $tmp);
1377
1378 7
        $classFile = $tmp . ".php";
1379
1380 7
        $path = substr_replace($dotPath, $classFile, $dotClassnamePos);
1381
1382 7
        Phing::importFile($path, $classpath);
1383
1384 5
        return $classname;
1385
    }
1386
1387
    /**
1388
     * Import a PHP file
1389
     *
1390
     * This used to be named __import, however PHP has reserved all method names
1391
     * with a double underscore prefix for future use.
1392
     *
1393
     * @param string $path Path to the PHP file
1394
     * @param mixed $classpath String or object supporting __toString()
1395
     *
1396
     * @throws ConfigurationException
1397
     */
1398 10
    public static function importFile($path, $classpath = null)
1399
    {
1400 10
        if ($classpath) {
1401
            // Apparently casting to (string) no longer invokes __toString() automatically.
1402 2
            if (is_object($classpath)) {
1403
                $classpath = $classpath->__toString();
1404
            }
1405
1406
            // classpaths are currently additive, but we also don't want to just
1407
            // indiscriminantly prepand/append stuff to the include_path.  This means
1408
            // we need to parse current incldue_path, and prepend any
1409
            // specified classpath locations that are not already in the include_path.
1410
            //
1411
            // NOTE:  the reason why we do it this way instead of just changing include_path
1412
            // and then changing it back, is that in many cases applications (e.g. Propel) will
1413
            // include/require class files from within method calls.  This means that not all
1414
            // necessary files will be included in this import() call, and hence we can't
1415
            // change the include_path back without breaking those apps.  While this method could
1416
            // be more expensive than switching & switching back (not sure, but maybe), it makes it
1417
            // possible to write far less expensive run-time applications (e.g. using Propel), which is
1418
            // really where speed matters more.
1419
1420 2
            $curr_parts = Phing::explodeIncludePath();
1421 2
            $add_parts = Phing::explodeIncludePath($classpath);
1422 2
            $new_parts = array_diff($add_parts, $curr_parts);
1423 2
            if ($new_parts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $new_parts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1424 1
                set_include_path(implode(PATH_SEPARATOR, array_merge($new_parts, $curr_parts)));
1425
            }
1426
        }
1427
1428 10
        $ret = include_once $path;
1429
1430 8
        if ($ret === false) {
1431
            $msg = "Error importing $path";
1432
            if (self::getMsgOutputLevel() >= Project::MSG_DEBUG) {
1433
                $x = new Exception("for-path-trace-only");
1434
                $msg .= $x->getTraceAsString();
1435
            }
1436
            throw new ConfigurationException($msg);
1437
        }
1438 8
    }
1439
1440
    /**
1441
     * Looks on include path for specified file.
1442
     *
1443
     * @param string $path
1444
     *
1445
     * @return string File found (null if no file found).
1446
     */
1447 756
    public static function getResourcePath($path)
1448
    {
1449 756
        if (self::$importPaths === null) {
1450
            self::$importPaths = self::explodeIncludePath();
1451
        }
1452
1453 756
        $path = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $path);
1454
1455 756
        foreach (self::$importPaths as $prefix) {
1456 756
            $testPath = $prefix . DIRECTORY_SEPARATOR . $path;
1457 756
            if (file_exists($testPath)) {
1458 755
                return $testPath;
1459
            }
1460
        }
1461
1462
        // Check for the property phing.home
1463 6
        $homeDir = self::getProperty(self::PHING_HOME);
1464 6
        if ($homeDir) {
1465 6
            $testPath = $homeDir . DIRECTORY_SEPARATOR . $path;
1466 6
            if (file_exists($testPath)) {
1467 6
                return $testPath;
1468
            }
1469
        }
1470
1471
        // Check for the phing home of phar archive
1472 6
        if (strpos(self::$importPaths[0], 'phar://') === 0) {
1473
            $testPath = self::$importPaths[0] . '/../' . $path;
1474
            if (file_exists($testPath)) {
1475
                return $testPath;
1476
            }
1477
        }
1478
1479
        // Do one additional check based on path of current file (Phing.php)
1480 6
        $maybeHomeDir = realpath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..');
1481 6
        $testPath = $maybeHomeDir . DIRECTORY_SEPARATOR . $path;
1482 6
        if (file_exists($testPath)) {
1483
            return $testPath;
1484
        }
1485
1486 6
        return null;
1487
    }
1488
1489
    /**
1490
     * Explode an include path into an array
1491
     *
1492
     * If no path provided, uses current include_path. Works around issues that
1493
     * occur when the path includes stream schemas.
1494
     *
1495
     * Pulled from Zend_Loader::explodeIncludePath() in ZF1.
1496
     *
1497
     * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
1498
     * @license   http://framework.zend.com/license/new-bsd New BSD License
1499
     * @param     string|null $path
1500
     * @return    array
1501
     */
1502 16
    public static function explodeIncludePath($path = null)
1503
    {
1504 16
        if (null === $path) {
1505 16
            $path = get_include_path();
1506
        }
1507
1508 16
        if (PATH_SEPARATOR == ':') {
1509
            // On *nix systems, include_paths which include paths with a stream
1510
            // schema cannot be safely explode'd, so we have to be a bit more
1511
            // intelligent in the approach.
1512 16
            $paths = preg_split('#:(?!//)#', $path);
1513
        } else {
1514
            $paths = explode(PATH_SEPARATOR, $path);
1515
        }
1516
1517 16
        return $paths;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $paths could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
1518
    }
1519
1520
    // -------------------------------------------------------------------------------------------
1521
    // System-wide methods (moved from System class, which had namespace conflicts w/ PEAR System)
1522
    // -------------------------------------------------------------------------------------------
1523
1524
    /**
1525
     * Set System constants which can be retrieved by calling Phing::getProperty($propName).
1526
     *
1527
     * @return void
1528
     */
1529 1
    private static function setSystemConstants()
1530
    {
1531
1532
        /*
1533
         * PHP_OS returns on
1534
         *   WindowsNT4.0sp6  => WINNT
1535
         *   Windows2000      => WINNT
1536
         *   Windows ME       => WIN32
1537
         *   Windows 98SE     => WIN32
1538
         *   FreeBSD 4.5p7    => FreeBSD
1539
         *   Redhat Linux     => Linux
1540
         *   Mac OS X         => Darwin
1541
         */
1542 1
        self::setProperty('host.os', PHP_OS);
1543
1544
        // this is used by some tasks too
1545 1
        self::setProperty('os.name', PHP_OS);
1546
1547
        // it's still possible this won't be defined,
1548
        // e.g. if Phing is being included in another app w/o
1549
        // using the phing.php script.
1550 1
        if (!defined('PHP_CLASSPATH')) {
1551
            define('PHP_CLASSPATH', get_include_path());
1552
        }
1553
1554 1
        self::setProperty('php.classpath', PHP_CLASSPATH);
1555
1556
        // try to determine the host filesystem and set system property
1557
        // used by Fileself::getFileSystem to instantiate the correct
1558
        // abstraction layer
1559
1560 1
        switch (strtoupper(PHP_OS)) {
1561 1
            case 'WINNT':
1562 1
            case 'WIN32':
1563
                self::setProperty('host.fstype', 'WINDOWS');
1564
                break;
1565
            default:
1566 1
                self::setProperty('host.fstype', 'UNIX');
1567 1
                break;
1568
        }
1569
1570 1
        if (defined('PHP_BINARY')) {
1571 1
            self::setProperty(self::PHP_INTERPRETER, PHP_BINARY);
1572
        } else {
1573
            self::setProperty(self::PHP_INTERPRETER, getenv('PHP_COMMAND'));
1574
        }
1575 1
        $file = new PhingFile('.');
1576 1
        self::setProperty('file.separator', $file::$separator);
1577 1
        self::setProperty('line.separator', PHP_EOL);
1578 1
        self::setProperty('path.separator', $file::$pathSeparator);
1579 1
        self::setProperty(self::PHP_VERSION, PHP_VERSION);
1580 1
        self::setProperty('php.tmpdir', sys_get_temp_dir());
1581 1
        if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
1582 1
            self::setProperty('user.home', getenv('HOME'));
1583
        } else {
1584
            self::setProperty('user.home', getenv('HOMEDRIVE') . getenv('HOMEPATH'));
1585
        }
1586 1
        self::setProperty('application.startdir', getcwd());
1587 1
        self::setProperty('phing.startTime', gmdate('D, d M Y H:i:s', time()) . ' GMT');
1588
1589
        // try to detect machine dependent information
1590 1
        $sysInfo = [];
1591 1
        if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN' && function_exists("posix_uname")) {
1592 1
            $sysInfo = posix_uname();
1593
        } else {
1594
            $sysInfo['nodename'] = php_uname('n');
1595
            $sysInfo['machine'] = php_uname('m');
1596
            //this is a not so ideal substition, but maybe better than nothing
1597
            $sysInfo['domain'] = $_SERVER['SERVER_NAME'] ?? "unknown";
1598
            $sysInfo['release'] = php_uname('r');
1599
            $sysInfo['version'] = php_uname('v');
1600
        }
1601
1602 1
        self::setProperty("host.name", $sysInfo['nodename'] ?? "unknown");
1603 1
        self::setProperty("host.arch", $sysInfo['machine'] ?? "unknown");
1604 1
        self::setProperty("host.domain", $sysInfo['domain'] ?? "unknown");
1605 1
        self::setProperty("host.os.release", $sysInfo['release'] ?? "unknown");
1606 1
        self::setProperty("host.os.version", $sysInfo['version'] ?? "unknown");
1607 1
        unset($sysInfo);
1608 1
    }
1609
1610
    /**
1611
     * This gets a property that was set via command line or otherwise passed into Phing.
1612
     * "Defined" in this case means "externally defined".  The reason this method exists is to
1613
     * provide a public means of accessing commandline properties for (e.g.) logger or listener
1614
     * scripts.  E.g. to specify which logfile to use, PearLogger needs to be able to access
1615
     * the pear.log.name property.
1616
     *
1617
     * @param  string $name
1618
     * @return string value of found property (or null, if none found).
1619
     */
1620
    public static function getDefinedProperty($name)
1621
    {
1622
        return self::$definedProps->getProperty($name);
1623
    }
1624
1625
    /**
1626
     * This sets a property that was set via command line or otherwise passed into Phing.
1627
     *
1628
     * @param  string $name
1629
     * @param  mixed $value
1630
     * @return mixed value of found property (or null, if none found).
1631
     */
1632
    public static function setDefinedProperty($name, $value)
1633
    {
1634
        return self::$definedProps->setProperty($name, $value);
1635
    }
1636
1637
    /**
1638
     * Returns property value for a System property.
1639
     * System properties are "global" properties like application.startdir,
1640
     * and user.dir.  Many of these correspond to similar properties in Java
1641
     * or Ant.
1642
     *
1643
     * @param  string $propName
1644
     * @return string Value of found property (or null, if none found).
1645
     */
1646 343
    public static function getProperty($propName)
1647
    {
1648
1649
        // some properties are detemined on each access
1650
        // some are cached, see below
1651
1652
        // default is the cached value:
1653 343
        $val = self::$properties[$propName] ?? null;
1654
1655
        // special exceptions
1656
        switch ($propName) {
1657 343
            case 'user.dir':
1658 127
                $val = getcwd();
1659 127
                break;
1660
        }
1661
1662 343
        return $val;
1663
    }
1664
1665
    /**
1666
     * Retuns reference to all properties
1667
     */
1668 755
    public static function &getProperties()
1669
    {
1670 755
        return self::$properties;
1671
    }
1672
1673
    /**
1674
     * @param $propName
1675
     * @param $propValue
1676
     * @return string
1677
     */
1678 7
    public static function setProperty($propName, $propValue)
1679
    {
1680 7
        $propName = (string) $propName;
1681 7
        $oldValue = self::getProperty($propName);
1682 7
        self::$properties[$propName] = $propValue;
1683
1684 7
        return $oldValue;
1685
    }
1686
1687
    /**
1688
     * @return float
1689
     */
1690 63
    public static function currentTimeMillis()
1691
    {
1692 63
        [$usec, $sec] = explode(" ", microtime());
1693
1694 63
        return ((float) $usec + (float) $sec);
1695
    }
1696
1697
    /**
1698
     * Sets the include path to PHP_CLASSPATH constant (if this has been defined).
1699
     *
1700
     * @return void
1701
     * @throws ConfigurationException - if the include_path could not be set (for some bizarre reason)
1702
     */
1703 1
    private static function setIncludePaths()
1704
    {
1705 1
        if (defined('PHP_CLASSPATH')) {
1706 1
            $result = set_include_path(PHP_CLASSPATH);
1707 1
            if ($result === false) {
1708
                throw new ConfigurationException("Could not set PHP include_path.");
1709
            }
1710 1
            self::$origIniSettings['include_path'] = $result; // save original value for setting back later
1711
        }
1712 1
    }
1713
1714
    /**
1715
     * Converts shorthand notation values as returned by ini_get()
1716
     *
1717
     * @see    http://www.php.net/ini_get
1718
     * @param  string $val
1719
     * @return int
1720
     */
1721 2
    public static function convertShorthand($val)
1722
    {
1723 2
        $val = trim($val);
1724 2
        $last = strtolower($val[strlen($val) - 1]);
1725
1726 2
        if (!is_numeric($last)) {
1727 2
            $val = (int) substr($val, 0, strlen($val) - 1);
1728
1729 2
            switch ($last) {
1730
                // The 'G' modifier is available since PHP 5.1.0
1731 2
                case 'g':
1732 2
                    $val *= 1024;
1733
                // no break
1734 1
                case 'm':
1735 2
                    $val *= 1024;
1736
                // no break
1737 1
                case 'k':
1738 2
                    $val *= 1024;
1739
            }
1740
        }
1741
1742 2
        return $val;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $val also could return the type string which is incompatible with the documented return type integer.
Loading history...
1743
    }
1744
1745
    /**
1746
     * Sets PHP INI values that Phing needs.
1747
     *
1748
     * @return void
1749
     */
1750 1
    private static function setIni()
1751
    {
1752 1
        self::$origIniSettings['error_reporting'] = error_reporting(E_ALL);
1753
1754
        // We won't bother storing original max_execution_time, since 1) the value in
1755
        // php.ini may be wrong (and there's no way to get the current value) and
1756
        // 2) it would mean something very strange to set it to a value less than time script
1757
        // has already been running, which would be the likely change.
1758
1759 1
        set_time_limit(0);
1760
1761 1
        self::$origIniSettings['short_open_tag'] = ini_set('short_open_tag', 'off');
1762 1
        self::$origIniSettings['default_charset'] = ini_set('default_charset', 'iso-8859-1');
1763 1
        self::$origIniSettings['track_errors'] = ini_set('track_errors', 1);
1764
1765 1
        $mem_limit = (int) self::convertShorthand(ini_get('memory_limit'));
1766 1
        if ($mem_limit < (32 * 1024 * 1024) && $mem_limit > -1) {
1767
            // We do *not* need to save the original value here, since we don't plan to restore
1768
            // this after shutdown (we don't trust the effectiveness of PHP's garbage collection).
1769
            ini_set('memory_limit', '32M'); // nore: this may need to be higher for many projects
1770
        }
1771 1
    }
1772
1773
    /**
1774
     * Restores [most] PHP INI values to their pre-Phing state.
1775
     *
1776
     * Currently the following settings are not restored:
1777
     *  - max_execution_time (because getting current time limit is not possible)
1778
     *  - memory_limit (which may have been increased by Phing)
1779
     *
1780
     * @return void
1781
     */
1782 1
    private static function restoreIni()
1783
    {
1784 1
        foreach (self::$origIniSettings as $settingName => $settingValue) {
1785 1
            switch ($settingName) {
1786 1
                case 'error_reporting':
1787 1
                    error_reporting($settingValue);
1788 1
                    break;
1789
                default:
1790 1
                    ini_set($settingName, $settingValue);
1791
            }
1792
        }
1793 1
    }
1794
1795
    /**
1796
     * Returns reference to Timer object.
1797
     *
1798
     * @return Timer
1799
     */
1800 2
    public static function getTimer()
1801
    {
1802 2
        if (self::$timer === null) {
1803
            self::$timer = new Timer();
1804
        }
1805
1806 2
        return self::$timer;
1807
    }
1808
1809
    /**
1810
     * Start up Phing.
1811
     * Sets up the Phing environment but does not initiate the build process.
1812
     *
1813
     * @return void
1814
     * @throws Exception - If the Phing environment cannot be initialized.
1815
     */
1816 1
    public static function startup()
1817
    {
1818
1819
        // setup STDOUT and STDERR defaults
1820 1
        self::initializeOutputStreams();
1821
1822
        // some init stuff
1823 1
        self::getTimer()->start();
1824
1825 1
        self::setSystemConstants();
1826 1
        self::setIncludePaths();
1827 1
        self::setIni();
1828 1
    }
1829
1830
    /**
1831
     * Performs any shutdown routines, such as stopping timers.
1832
     *
1833
     * @return void
1834
     */
1835 1
    public static function shutdown()
1836
    {
1837 1
        self::$msgOutputLevel = Project::MSG_INFO;
1838 1
        self::restoreIni();
1839 1
        self::getTimer()->stop();
1840 1
    }
1841
}
1842