Completed
Push — master ( 89abc8...a320a7 )
by Siad
16:11
created

Phing::setSystemConstants()   B

Complexity

Conditions 8
Paths 48

Size

Total Lines 78
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 8.7868

Importance

Changes 0
Metric Value
cc 8
eloc 43
nc 48
nop 0
dl 0
loc 78
ccs 30
cts 39
cp 0.7692
crap 8.7868
rs 7.9875
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 self();
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
        Phing::shutdown();
249
        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...
250
    }
251
252
    /**
253
     * Prints the message of the Exception if it's not null.
254
     *
255
     * @param Throwable $t
256
     */
257
    public static function printMessage(Throwable $t)
258
    {
259
        if (self::$err === null) { // Make sure our error output is initialized
260
            self::initializeOutputStreams();
261
        }
262
        if (self::getMsgOutputLevel() >= Project::MSG_VERBOSE) {
263
            self::$err->write((string) $t . PHP_EOL);
264
        } else {
265
            self::$err->write($t->getMessage() . PHP_EOL);
266
        }
267
    }
268
269
    /**
270
     * Sets the stdout and stderr streams if they are not already set.
271
     */
272 1
    private static function initializeOutputStreams()
273
    {
274 1
        if (self::$out === null) {
275
            if (!defined('STDOUT')) {
276
                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

276
                self::$out = new OutputStream(/** @scrutinizer ignore-type */ fopen('php://stdout', 'w'));
Loading history...
277
            } else {
278
                self::$out = new OutputStream(STDOUT);
279
            }
280
        }
281 1
        if (self::$err === null) {
282
            if (!defined('STDERR')) {
283
                self::$err = new OutputStream(fopen('php://stderr', 'w'));
284
            } else {
285
                self::$err = new OutputStream(STDERR);
286
            }
287
        }
288 1
    }
289
290
    /**
291
     * Sets the stream to use for standard (non-error) output.
292
     *
293
     * @param OutputStream $stream The stream to use for standard output.
294
     */
295 1
    public static function setOutputStream(OutputStream $stream)
296
    {
297 1
        self::$out = $stream;
298 1
    }
299
300
    /**
301
     * Gets the stream to use for standard (non-error) output.
302
     *
303
     * @return OutputStream
304
     */
305
    public static function getOutputStream()
306
    {
307
        return self::$out;
308
    }
309
310
    /**
311
     * Sets the stream to use for error output.
312
     *
313
     * @param OutputStream $stream The stream to use for error output.
314
     */
315 1
    public static function setErrorStream(OutputStream $stream)
316
    {
317 1
        self::$err = $stream;
318 1
    }
319
320
    /**
321
     * Gets the stream to use for error output.
322
     *
323
     * @return OutputStream
324
     */
325
    public static function getErrorStream()
326
    {
327
        return self::$err;
328
    }
329
330
    /**
331
     * Close logfiles, if we have been writing to them.
332
     *
333
     * @since Phing 2.3.0
334
     *
335
     * @return void
336
     */
337
    private static function handleLogfile()
338
    {
339
        if (self::$isLogFileUsed) {
340
            self::$err->close();
341
            self::$out->close();
342
        }
343
    }
344
345
    /**
346
     * Making output level a static property so that this property
347
     * can be accessed by other parts of the system, enabling
348
     * us to display more information -- e.g. backtraces -- for "debug" level.
349
     *
350
     * @return int
351
     */
352
    public static function getMsgOutputLevel()
353
    {
354
        return self::$msgOutputLevel;
355
    }
356
357
    /**
358
     * Command line entry point. This method kicks off the building
359
     * of a project object and executes a build using either a given
360
     * target or the default target.
361
     *
362
     * @param array $args Command line args.
363
     *
364
     * @return void
365
     */
366
    public static function fire($args)
367
    {
368
        self::start($args, null);
369
    }
370
371
    /**
372
     * Setup/initialize Phing environment from commandline args.
373
     *
374
     * @param array $args commandline args passed to phing shell.
375
     *
376
     * @throws ConfigurationException
377
     *
378
     * @return void
379
     */
380
    public function execute($args)
381
    {
382
        self::$definedProps = new Properties();
383
        $this->searchForThis = null;
384
385
        // 1) First handle any options which should always
386
        // Note: The order in which these are executed is important (if multiple of these options are specified)
387
388
        if (in_array('-help', $args) || in_array('-h', $args)) {
389
            static::printUsage();
390
391
            return;
392
        }
393
394
        if (in_array('-version', $args) || in_array('-v', $args)) {
395
            static::printVersion();
396
397
            return;
398
        }
399
400
        if (in_array('-init', $args) || in_array('-i', $args)) {
401
            $key = array_search('-init', $args) ?: array_search('-i', $args);
402
            $path = $args[$key + 1] ?? null;
403
404
            self::init($path);
405
406
            return;
407
        }
408
409
        if (in_array('-diagnostics', $args)) {
410
            Diagnostics::doReport(new PrintStream(self::$out));
411
412
            return;
413
        }
414
415
        // 2) Next pull out stand-alone args.
416
        // Note: The order in which these are executed is important (if multiple of these options are specified)
417
418
        if (
419
            false !== ($key = array_search('-quiet', $args, true)) ||
420
            false !== ($key = array_search(
421
                '-q',
422
                $args,
423
                true
424
            ))
425
        ) {
426
            self::$msgOutputLevel = Project::MSG_WARN;
427
            unset($args[$key]);
428
        }
429
430
        if (
431
            false !== ($key = array_search('-emacs', $args, true))
432
            || false !== ($key = array_search('-e', $args, true))
433
        ) {
434
            $this->emacsMode = true;
435
            unset($args[$key]);
436
        }
437
438
        if (false !== ($key = array_search('-verbose', $args, true))) {
439
            self::$msgOutputLevel = Project::MSG_VERBOSE;
440
            unset($args[$key]);
441
        }
442
443
        if (false !== ($key = array_search('-debug', $args, true))) {
444
            self::$msgOutputLevel = Project::MSG_DEBUG;
445
            unset($args[$key]);
446
        }
447
448
        if (
449
            false !== ($key = array_search('-silent', $args, true))
450
            || false !== ($key = array_search('-S', $args, true))
451
        ) {
452
            $this->silent = true;
453
            unset($args[$key]);
454
        }
455
456
        if (false !== ($key = array_search('-propertyfileoverride', $args, true))) {
457
            $this->propertyFileOverride = true;
458
            unset($args[$key]);
459
        }
460
461
        // 3) Finally, cycle through to parse remaining args
462
        //
463
        $keys = array_keys($args); // Use keys and iterate to max(keys) since there may be some gaps
464
        $max = $keys ? max($keys) : -1;
465
        for ($i = 0; $i <= $max; $i++) {
466
            if (!array_key_exists($i, $args)) {
467
                // skip this argument, since it must have been removed above.
468
                continue;
469
            }
470
471
            $arg = $args[$i];
472
473
            if ($arg == "-logfile") {
474
                try {
475
                    // see: http://phing.info/trac/ticket/65
476
                    if (!isset($args[$i + 1])) {
477
                        $msg = "You must specify a log file when using the -logfile argument\n";
478
                        throw new ConfigurationException($msg);
479
                    }
480
481
                    $logFile = new PhingFile($args[++$i]);
482
                    $out = new FileOutputStream($logFile); // overwrite
483
                    self::setOutputStream($out);
484
                    self::setErrorStream($out);
485
                    self::$isLogFileUsed = true;
486
                } catch (IOException $ioe) {
487
                    $msg = "Cannot write on the specified log file. Make sure the path exists and you have write permissions.";
488
                    throw new ConfigurationException($msg, $ioe);
489
                }
490
            } elseif ($arg == "-buildfile" || $arg == "-file" || $arg == "-f") {
491
                if (!isset($args[$i + 1])) {
492
                    $msg = "You must specify a buildfile when using the -buildfile argument.";
493
                    throw new ConfigurationException($msg);
494
                }
495
496
                $this->buildFile = new PhingFile($args[++$i]);
497
            } elseif ($arg == "-listener") {
498
                if (!isset($args[$i + 1])) {
499
                    $msg = "You must specify a listener class when using the -listener argument";
500
                    throw new ConfigurationException($msg);
501
                }
502
503
                $this->listeners[] = $args[++$i];
504
            } elseif (StringHelper::startsWith("-D", $arg)) {
505
                // Evaluating the property information //
506
                // Checking whether arg. is not just a switch, and next arg. does not starts with switch identifier
507
                if (('-D' == $arg) && (!StringHelper::startsWith('-', $args[$i + 1]))) {
508
                    $name = $args[++$i];
509
                } else {
510
                    $name = substr($arg, 2);
511
                }
512
513
                $value = null;
514
                $posEq = strpos($name, "=");
515
                if ($posEq !== false) {
516
                    $value = substr($name, $posEq + 1);
517
                    $name = substr($name, 0, $posEq);
518
                } elseif ($i < count($args) - 1 && !StringHelper::startsWith("-D", $args[$i + 1])) {
519
                    $value = $args[++$i];
520
                }
521
                self::$definedProps->setProperty($name, $value);
522
            } elseif ($arg == "-logger") {
523
                if (!isset($args[$i + 1])) {
524
                    $msg = "You must specify a classname when using the -logger argument";
525
                    throw new ConfigurationException($msg);
526
                }
527
528
                $this->loggerClassname = $args[++$i];
529
            } elseif ($arg == "-no-strict") {
530
                $this->strictMode = false;
531
            } elseif ($arg == "-strict") {
532
                $this->strictMode = true;
533
            } elseif ($arg == "-inputhandler") {
534
                if ($this->inputHandlerClassname !== null) {
535
                    throw new ConfigurationException("Only one input handler class may be specified.");
536
                }
537
                if (!isset($args[$i + 1])) {
538
                    $msg = "You must specify a classname when using the -inputhandler argument";
539
                    throw new ConfigurationException($msg);
540
                }
541
542
                $this->inputHandlerClassname = $args[++$i];
543
            } elseif ($arg === "-propertyfile") {
544
                $i = $this->handleArgPropertyFile($args, $i);
545
            } elseif ($arg === "-keep-going" || $arg === "-k") {
546
                $this->keepGoingMode = true;
547
            } elseif ($arg == "-longtargets") {
548
                self::$definedProps->setProperty('phing.showlongtargets', 1);
549
            } elseif ($arg == "-projecthelp" || $arg == "-targets" || $arg == "-list" || $arg == "-l" || $arg == "-p") {
550
                // set the flag to display the targets and quit
551
                $this->projectHelp = true;
552
            } elseif ($arg == "-find") {
553
                // eat up next arg if present, default to build.xml
554
                if ($i < count($args) - 1) {
555
                    $this->searchForThis = $args[++$i];
556
                } else {
557
                    $this->searchForThis = self::DEFAULT_BUILD_FILENAME;
558
                }
559
            } elseif (substr($arg, 0, 1) == "-") {
560
                // we don't have any more args
561
                self::printUsage();
562
                self::$err->write(PHP_EOL);
563
                throw new ConfigurationException("Unknown argument: " . $arg);
564
            } else {
565
                // if it's no other arg, it may be the target
566
                $this->targets[] = $arg;
567
            }
568
        }
569
570
        // if buildFile was not specified on the command line,
571
        if ($this->buildFile === null) {
572
            // but -find then search for it
573
            if ($this->searchForThis !== null) {
574
                $this->buildFile = $this->_findBuildFile(self::getProperty("user.dir"), $this->searchForThis);
575
            } else {
576
                $this->buildFile = new PhingFile(self::DEFAULT_BUILD_FILENAME);
577
            }
578
        }
579
580
        try {
581
            // make sure buildfile (or buildfile.dist) exists
582
            if (!$this->buildFile->exists()) {
583
                $distFile = new PhingFile($this->buildFile->getAbsolutePath() . ".dist");
584
                if (!$distFile->exists()) {
585
                    throw new ConfigurationException(
586
                        "Buildfile: " . $this->buildFile->__toString() . " does not exist!"
587
                    );
588
                }
589
                $this->buildFile = $distFile;
590
            }
591
592
            // make sure it's not a directory
593
            if ($this->buildFile->isDirectory()) {
594
                throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is a dir!");
595
            }
596
        } catch (IOException $e) {
597
            // something else happened, buildfile probably not readable
598
            throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is not readable!");
599
        }
600
601
        $this->loadPropertyFiles();
602
603
        $this->readyToRun = true;
604
    }
605
606
    /**
607
     * Handle the -propertyfile argument.
608
     *
609
     * @param array $args
610
     * @param int $pos
611
     *
612
     * @return int
613
     *
614
     * @throws ConfigurationException
615
     * @throws IOException
616
     */
617
    private function handleArgPropertyFile(array $args, int $pos): int
618
    {
619
        if (!isset($args[$pos + 1])) {
620
            throw new ConfigurationException('You must specify a filename when using the -propertyfile argument');
621
        }
622
623
        $this->propertyFiles[] = $args[++$pos];
624
625
        return $pos;
626
    }
627
628
    /**
629
     * @throws IOException
630
     */
631
    private function loadPropertyFiles()
632
    {
633
        foreach ($this->propertyFiles as $filename) {
634
            $fileParserFactory = new FileParserFactory();
635
            $fileParser = $fileParserFactory->createParser(pathinfo($filename, PATHINFO_EXTENSION));
636
            $p = new Properties(null, $fileParser);
637
            try {
638
                $p->load(new PhingFile($filename));
639
            } catch (IOException $e) {
640
                self::$out->write('Could not load property file ' . $filename . ': ' . $e->getMessage());
641
            }
642
            foreach ($p->getProperties() as $prop => $value) {
643
                self::$definedProps->setProperty($prop, $value);
644
            }
645
        }
646
    }
647
648
    /**
649
     * Helper to get the parent file for a given file.
650
     *
651
     * @param PhingFile $file
652
     *
653
     * @return PhingFile Parent file or null if none
654
     */
655
    private function _getParentFile(PhingFile $file)
656
    {
657
        $filename = $file->getAbsolutePath();
658
        $file = new PhingFile($filename);
659
        $filename = $file->getParent();
660
661
        return ($filename === null) ? null : new PhingFile($filename);
0 ignored issues
show
introduced by
The condition $filename === null is always false.
Loading history...
662
    }
663
664
    /**
665
     * Search parent directories for the build file.
666
     *
667
     * Takes the given target as a suffix to append to each
668
     * parent directory in search of a build file.  Once the
669
     * root of the file-system has been reached an exception
670
     * is thrown.
671
     *
672
     * @param string $start Start file path.
673
     * @param string $suffix Suffix filename to look for in parents.
674
     *
675
     * @throws ConfigurationException
676
     *
677
     * @return PhingFile A handle to the build file
678
     */
679
    private function _findBuildFile($start, $suffix)
680
    {
681
        $startf = new PhingFile($start);
682
        $parent = new PhingFile($startf->getAbsolutePath());
683
        $file = new PhingFile($parent, $suffix);
684
685
        // check if the target file exists in the current directory
686
        while (!$file->exists()) {
687
            // change to parent directory
688
            $parent = $this->_getParentFile($parent);
689
690
            // if parent is null, then we are at the root of the fs,
691
            // complain that we can't find the build file.
692
            if ($parent === null) {
693
                throw new ConfigurationException("Could not locate a build file!");
694
            }
695
            // refresh our file handle
696
            $file = new PhingFile($parent, $suffix);
697
        }
698
699
        return $file;
700
    }
701
702
    /**
703
     * Executes the build.
704
     *
705
     * @throws ConfigurationException
706
     * @throws Exception
707
     *
708
     * @return void
709
     */
710
    public function runBuild()
711
    {
712
        if (!$this->readyToRun) {
713
            return;
714
        }
715
716
        $project = new Project();
717
718
        self::setCurrentProject($project);
719
        set_error_handler(['Phing', 'handlePhpError']);
720
721
        $error = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $error is dead and can be removed.
Loading history...
722
723
        $this->addBuildListeners($project);
724
        $this->addInputHandler($project);
725
726
        // set this right away, so that it can be used in logging.
727
        $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
728
        $project->setUserProperty("phing.dir", dirname($this->buildFile->getAbsolutePath()));
729
730
        try {
731
            $project->fireBuildStarted();
732
            $project->init();
733
        } catch (Exception $exc) {
734
            $project->fireBuildFinished($exc);
735
            throw $exc;
736
        }
737
738
        $project->setKeepGoingMode($this->keepGoingMode);
739
740
        $project->setUserProperty("phing.version", static::getPhingVersion());
741
742
        $e = self::$definedProps->keys();
743
        while (count($e)) {
744
            $arg = (string) array_shift($e);
745
            $value = (string) self::$definedProps->getProperty($arg);
746
            $project->setUserProperty($arg, $value);
747
        }
748
        unset($e);
749
750
        $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
751
752
        // first use the Configurator to create the project object
753
        // from the given build file.
754
755
        try {
756
            ProjectConfigurator::configureProject($project, $this->buildFile);
757
        } catch (Exception $exc) {
758
            $project->fireBuildFinished($exc);
759
            restore_error_handler();
760
            self::unsetCurrentProject();
761
            throw $exc;
762
        }
763
764
        // Set the project mode
765
        $project->setStrictMode(StringHelper::booleanValue($this->strictMode));
766
767
        // make sure that we have a target to execute
768
        if (count($this->targets) === 0) {
769
            $this->targets[] = $project->getDefaultTarget();
770
        }
771
772
        // make sure that minimum required phing version is satisfied
773
        try {
774
            $this->comparePhingVersion($project->getPhingVersion());
775
        } catch (Exception $exc) {
776
            $project->fireBuildFinished($exc);
777
            restore_error_handler();
778
            self::unsetCurrentProject();
779
            throw $exc;
780
        }
781
782
        // execute targets if help param was not given
783
        if (!$this->projectHelp) {
784
            try {
785
                $project->executeTargets($this->targets);
786
            } catch (Exception $exc) {
787
                $project->fireBuildFinished($exc);
788
                restore_error_handler();
789
                self::unsetCurrentProject();
790
                throw $exc;
791
            }
792
        }
793
        // if help is requested print it
794
        if ($this->projectHelp) {
795
            try {
796
                $this->printDescription($project);
797
                $this->printTargets($project);
798
            } catch (Exception $exc) {
799
                $project->fireBuildFinished($exc);
800
                restore_error_handler();
801
                self::unsetCurrentProject();
802
                throw $exc;
803
            }
804
        }
805
806
        // finally {
807
        if (!$this->projectHelp) {
808
            $project->fireBuildFinished(null);
809
        }
810
811
        restore_error_handler();
812
        self::unsetCurrentProject();
813
    }
814
815
    /**
816
     * @param string $version
817
     *
818
     * @return int
819
     *
820
     * @throws BuildException
821
     * @throws ConfigurationException
822
     */
823
    private function comparePhingVersion($version)
824
    {
825
        $current = strtolower(self::getPhingVersion());
826
        $current = trim(str_replace('phing', '', $current));
827
828
        // make sure that version checks are not applied to trunk
829
        if ('dev' === $current) {
830
            return 1;
831
        }
832
833
        if (-1 == version_compare($current, $version)) {
834
            throw new BuildException(
835
                sprintf('Incompatible Phing version (%s). Version "%s" required.', $current, $version)
836
            );
837
        }
838
    }
839
840
    /**
841
     * Bind any registered build listeners to this project.
842
     *
843
     * This means adding the logger and any build listeners that were specified
844
     * with -listener arg.
845
     *
846
     * @param  Project $project
847
     * @throws BuildException
848
     * @throws ConfigurationException
849
     * @return void
850
     */
851
    private function addBuildListeners(Project $project)
852
    {
853
        // Add the default listener
854
        $project->addBuildListener($this->createLogger());
855
856
        foreach ($this->listeners as $listenerClassname) {
857
            try {
858
                $clz = Phing::import($listenerClassname);
859
            } catch (Exception $e) {
860
                $msg = "Unable to instantiate specified listener "
861
                    . "class " . $listenerClassname . " : "
862
                    . $e->getMessage();
863
                throw new ConfigurationException($msg);
864
            }
865
866
            $listener = new $clz();
867
868
            if ($listener instanceof StreamRequiredBuildLogger) {
869
                throw new ConfigurationException("Unable to add " . $listenerClassname . " as a listener, since it requires explicit error/output streams. (You can specify it as a -logger.)");
870
            }
871
            $project->addBuildListener($listener);
872
        }
873
    }
874
875
    /**
876
     * Creates the InputHandler and adds it to the project.
877
     *
878
     * @param Project $project the project instance.
879
     *
880
     * @throws ConfigurationException
881
     */
882
    private function addInputHandler(Project $project)
883
    {
884
        if ($this->inputHandlerClassname === null) {
885
            $handler = new ConsoleInputHandler(STDIN, new ConsoleOutput());
886
        } else {
887
            try {
888
                $clz = Phing::import($this->inputHandlerClassname);
889
                $handler = new $clz();
890
                if ($project !== null && method_exists($handler, 'setProject')) {
891
                    $handler->setProject($project);
892
                }
893
            } catch (Exception $e) {
894
                $msg = "Unable to instantiate specified input handler "
895
                    . "class " . $this->inputHandlerClassname . " : "
896
                    . $e->getMessage();
897
                throw new ConfigurationException($msg);
898
            }
899
        }
900
        $project->setInputHandler($handler);
901
    }
902
903
    /**
904
     * Creates the default build logger for sending build events to the log.
905
     *
906
     * @throws BuildException
907
     * @return BuildLogger The created Logger
908
     */
909
    private function createLogger()
910
    {
911
        if ($this->silent) {
912
            $logger = new SilentLogger();
913
            self::$msgOutputLevel = Project::MSG_WARN;
914
        } elseif ($this->loggerClassname !== null) {
915
            self::import($this->loggerClassname);
916
            // get class name part
917
            $classname = self::import($this->loggerClassname);
918
            $logger = new $classname();
919
            if (!($logger instanceof BuildLogger)) {
920
                throw new BuildException($classname . ' does not implement the BuildLogger interface.');
921
            }
922
        } else {
923
            $logger = new DefaultLogger();
924
        }
925
        $logger->setMessageOutputLevel(self::$msgOutputLevel);
926
        $logger->setOutputStream(self::$out);
927
        $logger->setErrorStream(self::$err);
928
        $logger->setEmacsMode($this->emacsMode);
929
930
        return $logger;
931
    }
932
933
    /**
934
     * Sets the current Project
935
     *
936
     * @param Project $p
937
     */
938 1
    public static function setCurrentProject($p)
939
    {
940 1
        self::$currentProject = $p;
941 1
    }
942
943
    /**
944
     * Unsets the current Project
945
     */
946 1
    public static function unsetCurrentProject()
947
    {
948 1
        self::$currentProject = null;
949 1
    }
950
951
    /**
952
     * Gets the current Project.
953
     *
954
     * @return Project Current Project or NULL if none is set yet/still.
955
     */
956 4
    public static function getCurrentProject()
957
    {
958 4
        return self::$currentProject;
959
    }
960
961
    /**
962
     * A static convenience method to send a log to the current (last-setup) Project.
963
     * If there is no currently-configured Project, then this will do nothing.
964
     *
965
     * @param string $message
966
     * @param int $priority Project::MSG_INFO, etc.
967
     */
968
    public static function log($message, $priority = Project::MSG_INFO)
969
    {
970
        $p = self::getCurrentProject();
971
        if ($p) {
0 ignored issues
show
introduced by
$p is of type Project, thus it always evaluated to true.
Loading history...
972
            $p->log($message, $priority);
973
        }
974
    }
975
976
    /**
977
     * Error handler for PHP errors encountered during the build.
978
     * This uses the logging for the currently configured project.
979
     *
980
     * @param $level
981
     * @param string $message
982
     * @param $file
983
     * @param $line
984
     */
985
    public static function handlePhpError($level, $message, $file, $line)
986
    {
987
988
        // don't want to print suppressed errors
989
        if (error_reporting() > 0) {
990
            if (self::$phpErrorCapture) {
991
                self::$capturedPhpErrors[] = [
992
                    'message' => $message,
993
                    'level' => $level,
994
                    'line' => $line,
995
                    'file' => $file
996
                ];
997
            } else {
998
                $message = '[PHP Error] ' . $message;
999
                $message .= ' [line ' . $line . ' of ' . $file . ']';
1000
1001
                switch ($level) {
1002
                    case E_USER_DEPRECATED:
1003
                    case E_DEPRECATED:
1004
                    case E_STRICT:
1005
                    case E_NOTICE:
1006
                    case E_USER_NOTICE:
1007
                        self::log($message, Project::MSG_VERBOSE);
1008
                        break;
1009
                    case E_WARNING:
1010
                    case E_USER_WARNING:
1011
                        self::log($message, Project::MSG_WARN);
1012
                        break;
1013
                    case E_ERROR:
1014
                    case E_USER_ERROR:
1015
                    default:
1016
                        self::log($message, Project::MSG_ERR);
1017
                } // switch
1018
            } // if phpErrorCapture
1019
        } // if not @
1020
    }
1021
1022
    /**
1023
     * Begins capturing PHP errors to a buffer.
1024
     * While errors are being captured, they are not logged.
1025
     */
1026
    public static function startPhpErrorCapture()
1027
    {
1028
        self::$phpErrorCapture = true;
1029
        self::$capturedPhpErrors = [];
1030
    }
1031
1032
    /**
1033
     * Stops capturing PHP errors to a buffer.
1034
     * The errors will once again be logged after calling this method.
1035
     */
1036
    public static function stopPhpErrorCapture()
1037
    {
1038
        self::$phpErrorCapture = false;
1039
    }
1040
1041
    /**
1042
     * Clears the captured errors without affecting the starting/stopping of the capture.
1043
     */
1044
    public static function clearCapturedPhpErrors()
1045
    {
1046
        self::$capturedPhpErrors = [];
1047
    }
1048
1049
    /**
1050
     * Gets any PHP errors that were captured to buffer.
1051
     *
1052
     * @return array array('message' => message, 'line' => line number, 'file' => file name, 'level' => error level)
1053
     */
1054
    public static function getCapturedPhpErrors()
1055
    {
1056
        return self::$capturedPhpErrors;
1057
    }
1058
1059
    /**
1060
     * Prints the usage of how to use this class
1061
     */
1062 1
    public static function printUsage()
1063
    {
1064 1
        $msg = "";
1065 1
        $msg .= "phing [options] [target [target2 [target3] ...]]" . PHP_EOL;
1066 1
        $msg .= "Options: " . PHP_EOL;
1067 1
        $msg .= "  -h -help               print this message" . PHP_EOL;
1068 1
        $msg .= "  -l -list               list available targets in this project" . PHP_EOL;
1069 1
        $msg .= "  -i -init [file]        generates an initial buildfile" . PHP_EOL;
1070 1
        $msg .= "  -v -version            print the version information and exit" . PHP_EOL;
1071 1
        $msg .= "  -q -quiet              be extra quiet" . PHP_EOL;
1072 1
        $msg .= "  -S -silent             print nothing but task outputs and build failures" . PHP_EOL;
1073 1
        $msg .= "  -verbose               be extra verbose" . PHP_EOL;
1074 1
        $msg .= "  -debug                 print debugging information" . PHP_EOL;
1075 1
        $msg .= "  -emacs, -e             produce logging information without adornments" . PHP_EOL;
1076 1
        $msg .= "  -diagnostics           print diagnostics information" . PHP_EOL;
1077 1
        $msg .= "  -strict                runs build in strict mode, considering a warning as error" . PHP_EOL;
1078 1
        $msg .= "  -no-strict             runs build normally (overrides buildfile attribute)" . PHP_EOL;
1079 1
        $msg .= "  -longtargets           show target descriptions during build" . PHP_EOL;
1080 1
        $msg .= "  -logfile <file>        use given file for log" . PHP_EOL;
1081 1
        $msg .= "  -logger <classname>    the class which is to perform logging" . PHP_EOL;
1082 1
        $msg .= "  -listener <classname>  add an instance of class as a project listener" . PHP_EOL;
1083 1
        $msg .= "  -f -buildfile <file>   use given buildfile" . PHP_EOL;
1084 1
        $msg .= "  -D<property>=<value>   use value for given property" . PHP_EOL;
1085 1
        $msg .= "  -keep-going, -k        execute all targets that do not depend" . PHP_EOL;
1086 1
        $msg .= "                         on failed target(s)" . PHP_EOL;
1087 1
        $msg .= "  -propertyfile <file>   load all properties from file" . PHP_EOL;
1088 1
        $msg .= "  -propertyfileoverride  values in property file override existing values" . PHP_EOL;
1089 1
        $msg .= "  -find <file>           search for buildfile towards the root of the" . PHP_EOL;
1090 1
        $msg .= "                         filesystem and use it" . PHP_EOL;
1091 1
        $msg .= "  -inputhandler <file>   the class to use to handle user input" . PHP_EOL;
1092
        //$msg .= "  -recursive <file>      search for buildfile downwards and use it" . PHP_EOL;
1093 1
        $msg .= PHP_EOL;
1094 1
        $msg .= "Report bugs to <[email protected]>" . PHP_EOL;
1095 1
        self::$err->write($msg);
1096 1
    }
1097
1098
    /**
1099
     * Prints the current Phing version.
1100
     */
1101
    public static function printVersion()
1102
    {
1103
        self::$out->write(self::getPhingVersion() . PHP_EOL);
1104
    }
1105
1106
    /**
1107
     * Creates generic buildfile
1108
     *
1109
     * @param string $path
1110
     */
1111
    public static function init($path)
1112
    {
1113
        if ($buildfilePath = self::initPath($path)) {
1114
            self::initWrite($buildfilePath);
1115
        }
1116
    }
1117
1118
1119
    /**
1120
     * Returns buildfile's path
1121
     *
1122
     * @param $path
1123
     *
1124
     * @return string
1125
     * @throws ConfigurationException
1126
     */
1127
    protected static function initPath($path)
1128
    {
1129
        // Fallback
1130
        if (empty($path)) {
1131
            $defaultDir = self::getProperty('application.startdir');
1132
            $path = $defaultDir . DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1133
        }
1134
1135
        // Adding filename if necessary
1136
        if (is_dir($path)) {
1137
            $path .= DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1138
        }
1139
1140
        // Check if path is available
1141
        $dirname = dirname($path);
1142
        if (is_dir($dirname) && !is_file($path)) {
1143
            return $path;
1144
        }
1145
1146
        // Path is valid, but buildfile already exists
1147
        if (is_file($path)) {
1148
            throw new ConfigurationException('Buildfile already exists.');
1149
        }
1150
1151
        throw new ConfigurationException('Invalid path for sample buildfile.');
1152
    }
1153
1154
1155
    /**
1156
     * Writes sample buildfile
1157
     *
1158
     * If $buildfilePath does not exist, the buildfile is created.
1159
     *
1160
     * @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...
1161
     *
1162
     * @return null
1163
     * @throws ConfigurationException
1164
     */
1165
    protected static function initWrite($buildfilePath)
1166
    {
1167
        // Overwriting protection
1168
        if (file_exists($buildfilePath)) {
1169
            throw new ConfigurationException('Cannot overwrite existing file.');
1170
        }
1171
1172
        $content = '<?xml version="1.0" encoding="UTF-8" ?>' . PHP_EOL;
1173
        $content .= '' . PHP_EOL;
1174
        $content .= '<project name="" description="" default="">' . PHP_EOL;
1175
        $content .= '    ' . PHP_EOL;
1176
        $content .= '    <target name="" description="">' . PHP_EOL;
1177
        $content .= '        ' . PHP_EOL;
1178
        $content .= '    </target>' . PHP_EOL;
1179
        $content .= '    ' . PHP_EOL;
1180
        $content .= '</project>' . PHP_EOL;
1181
1182
        file_put_contents($buildfilePath, $content);
1183
    }
1184
1185
    /**
1186
     * Gets the current Phing version based on VERSION.TXT file.
1187
     *
1188
     * @throws ConfigurationException
1189
     *
1190
     * @return string
1191
     */
1192 6
    public static function getPhingVersion()
1193
    {
1194 6
        $versionPath = self::getResourcePath("phing/etc/VERSION.TXT");
1195 6
        if ($versionPath === null) {
0 ignored issues
show
introduced by
The condition $versionPath === null is always false.
Loading history...
1196 6
            $versionPath = self::getResourcePath("etc/VERSION.TXT");
1197
        }
1198 6
        if ($versionPath === null) {
0 ignored issues
show
introduced by
The condition $versionPath === null is always false.
Loading history...
1199
            throw new ConfigurationException("No VERSION.TXT file found; try setting phing.home environment variable.");
1200
        }
1201
        try { // try to read file
1202 6
            $file = new PhingFile($versionPath);
1203 6
            $reader = new FileReader($file);
1204 6
            $phingVersion = trim($reader->read());
1205
        } catch (IOException $iox) {
1206
            throw new ConfigurationException("Can't read version information file");
1207
        }
1208
1209 6
        $basePath = dirname(__DIR__, 2);
1210
1211 6
        $version = new Version($phingVersion, $basePath);
1212
1213 6
        return "Phing " . $version->getVersion();
1214
    }
1215
1216
    /**
1217
     * Print the project description, if any
1218
     *
1219
     * @param Project $project
1220
     *
1221
     * @throws IOException
1222
     */
1223
    public function printDescription(Project $project)
1224
    {
1225
        if ($project->getDescription() !== null) {
1226
            self::$out->write($project->getDescription() . PHP_EOL);
1227
        }
1228
    }
1229
1230
    /**
1231
     * Print out a list of all targets in the current buildfile
1232
     *
1233
     * @param $project
1234
     */
1235 1
    public function printTargets($project)
1236
    {
1237
        // find the target with the longest name
1238 1
        $maxLength = 0;
1239 1
        $targets = $project->getTargets();
1240 1
        $targetName = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $targetName is dead and can be removed.
Loading history...
1241 1
        $targetDescription = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $targetDescription is dead and can be removed.
Loading history...
1242 1
        $currentTarget = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $currentTarget is dead and can be removed.
Loading history...
1243
1244
        // split the targets in top-level and sub-targets depending
1245
        // on the presence of a description
1246
1247 1
        $subNames = [];
1248 1
        $topNameDescMap = [];
1249
1250 1
        foreach ($targets as $currentTarget) {
1251 1
            $targetName = $currentTarget->getName();
1252 1
            $targetDescription = $currentTarget->getDescription();
1253 1
            if ($currentTarget->isHidden()) {
1254
                continue;
1255
            }
1256
1257
            // subtargets are targets w/o descriptions
1258 1
            if ($targetDescription === null) {
1259 1
                $subNames[] = $targetName;
1260
            } else {
1261
                // topNames and topDescriptions are handled later
1262
                // here we store in hash map (for sorting purposes)
1263
                $topNameDescMap[$targetName] = $targetDescription;
1264
                if (strlen($targetName) > $maxLength) {
1265
                    $maxLength = strlen($targetName);
1266
                }
1267
            }
1268
        }
1269
1270
        // Sort the arrays
1271 1
        sort($subNames); // sort array values, resetting keys (which are numeric)
1272 1
        ksort($topNameDescMap); // sort the keys (targetName) keeping key=>val associations
1273
1274 1
        $topNames = array_keys($topNameDescMap);
1275 1
        $topDescriptions = array_values($topNameDescMap);
1276
1277 1
        $defaultTarget = $project->getDefaultTarget();
1278
1279 1
        if ($defaultTarget !== null && $defaultTarget !== "") {
1280
            $defaultName = [];
1281
            $defaultDesc = [];
1282
            $defaultName[] = $defaultTarget;
1283
1284
            $indexOfDefDesc = array_search($defaultTarget, $topNames, true);
1285
            if ($indexOfDefDesc !== false && $indexOfDefDesc >= 0) {
1286
                $defaultDesc = [];
1287
                $defaultDesc[] = $topDescriptions[$indexOfDefDesc];
1288
            }
1289
1290
            $this->_printTargets($defaultName, $defaultDesc, "Default target:", $maxLength);
1291
        }
1292 1
        $this->_printTargets($topNames, $topDescriptions, "Main targets:", $maxLength);
1293 1
        $this->_printTargets($subNames, null, "Subtargets:", 0);
1294 1
    }
1295
1296
    /**
1297
     * Writes a formatted list of target names with an optional description.
1298
     *
1299
     * @param array $names The names to be printed.
1300
     *                             Must not be <code>null</code>.
1301
     * @param array $descriptions The associated target descriptions.
1302
     *                             May be <code>null</code>, in which case
1303
     *                             no descriptions are displayed.
1304
     *                             If non-<code>null</code>, this should have
1305
     *                             as many elements as <code>names</code>.
1306
     * @param string $heading The heading to display.
1307
     *                             Should not be <code>null</code>.
1308
     * @param int $maxlen The maximum length of the names of the targets.
1309
     *                             If descriptions are given, they are padded to this
1310
     *                             position so they line up (so long as the names really
1311
     *                             <i>are</i> shorter than this).
1312
     */
1313 1
    private function _printTargets($names, $descriptions, $heading, $maxlen)
1314
    {
1315 1
        $spaces = '  ';
1316 1
        while (strlen($spaces) < $maxlen) {
1317
            $spaces .= $spaces;
1318
        }
1319 1
        $msg = "";
1320 1
        $msg .= $heading . PHP_EOL;
1321 1
        $msg .= str_repeat("-", 79) . PHP_EOL;
1322
1323 1
        $total = count($names);
1324 1
        for ($i = 0; $i < $total; $i++) {
1325 1
            $msg .= " ";
1326 1
            $msg .= $names[$i];
1327 1
            if (!empty($descriptions)) {
1328
                $msg .= substr($spaces, 0, $maxlen - strlen($names[$i]) + 2);
1329
                $msg .= $descriptions[$i];
1330
            }
1331 1
            $msg .= PHP_EOL;
1332
        }
1333 1
        if ($total > 0) {
1334 1
            self::$out->write($msg . PHP_EOL);
1335
        }
1336 1
    }
1337
1338
    /**
1339
     * Import a path, supporting the following conventions:
1340
     * - PEAR style (@link http://pear.php.net/manual/en/standards.naming.php)
1341
     * - PSR-0 (@link https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
1342
     * - dot-path
1343
     *
1344
     * @param string $dotPath Path
1345
     * @param mixed $classpath String or object supporting __toString()
1346
     *
1347
     * @return string         The unqualified classname (which can be instantiated).
1348
     *
1349
     * @throws BuildException - if cannot find the specified file
1350
     */
1351 766
    public static function import($dotPath, $classpath = null)
1352
    {
1353 766
        if (strpos($dotPath, '.') !== false) {
1354 765
            $classname = StringHelper::unqualify($dotPath);
1355
        } else {
1356 20
            $classname = $dotPath;
1357 20
            $dotPath = '';
1358 20
            $shortClassName = $classname;
1359 20
            if (($lastNsPos = strrpos($shortClassName, '\\'))) {
1360 1
                $namespace = substr($shortClassName, 0, $lastNsPos);
1361 1
                $shortClassName = substr($shortClassName, $lastNsPos + 1);
1362 1
                $dotPath = str_replace('\\', '.', $namespace) . '.';
1363
            }
1364 20
            $dotPath .= str_replace('_', '.', $shortClassName);
1365
        }
1366
1367
        // first check to see that the class specified hasn't already been included.
1368
        // (this also handles case where this method is called w/ a classname rather than dotpath)
1369 766
        if (class_exists($classname)) {
1370 764
            return $classname;
1371
        }
1372
1373 7
        $dotClassname = basename($dotPath);
1374 7
        $dotClassnamePos = strlen($dotPath) - strlen($dotClassname);
1375
1376
        // 1- temporarily replace escaped '.' with another illegal char (#)
1377 7
        $tmp = str_replace('\.', '##', $dotClassname);
1378
        // 2- swap out the remaining '.' with DIR_SEP
1379 7
        $tmp = strtr($tmp, '.', DIRECTORY_SEPARATOR);
1380
        // 3- swap back the escaped '.'
1381 7
        $tmp = str_replace('##', '.', $tmp);
1382
1383 7
        $classFile = $tmp . ".php";
1384
1385 7
        $path = substr_replace($dotPath, $classFile, $dotClassnamePos);
1386
1387 7
        Phing::importFile($path, $classpath);
1388
1389 5
        return $classname;
1390
    }
1391
1392
    /**
1393
     * Import a PHP file
1394
     *
1395
     * This used to be named __import, however PHP has reserved all method names
1396
     * with a double underscore prefix for future use.
1397
     *
1398
     * @param string $path Path to the PHP file
1399
     * @param mixed $classpath String or object supporting __toString()
1400
     *
1401
     * @throws ConfigurationException
1402
     */
1403 10
    public static function importFile($path, $classpath = null)
1404
    {
1405 10
        if ($classpath) {
1406
            // Apparently casting to (string) no longer invokes __toString() automatically.
1407 2
            if (is_object($classpath)) {
1408
                $classpath = $classpath->__toString();
1409
            }
1410
1411
            // classpaths are currently additive, but we also don't want to just
1412
            // indiscriminantly prepand/append stuff to the include_path.  This means
1413
            // we need to parse current incldue_path, and prepend any
1414
            // specified classpath locations that are not already in the include_path.
1415
            //
1416
            // NOTE:  the reason why we do it this way instead of just changing include_path
1417
            // and then changing it back, is that in many cases applications (e.g. Propel) will
1418
            // include/require class files from within method calls.  This means that not all
1419
            // necessary files will be included in this import() call, and hence we can't
1420
            // change the include_path back without breaking those apps.  While this method could
1421
            // be more expensive than switching & switching back (not sure, but maybe), it makes it
1422
            // possible to write far less expensive run-time applications (e.g. using Propel), which is
1423
            // really where speed matters more.
1424
1425 2
            $curr_parts = Phing::explodeIncludePath();
1426 2
            $add_parts = Phing::explodeIncludePath($classpath);
1427 2
            $new_parts = array_diff($add_parts, $curr_parts);
1428 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...
1429 1
                set_include_path(implode(PATH_SEPARATOR, array_merge($new_parts, $curr_parts)));
1430
            }
1431
        }
1432
1433 10
        $ret = include_once $path;
1434
1435 8
        if ($ret === false) {
1436
            $msg = "Error importing $path";
1437
            if (self::getMsgOutputLevel() >= Project::MSG_DEBUG) {
1438
                $x = new Exception("for-path-trace-only");
1439
                $msg .= $x->getTraceAsString();
1440
            }
1441
            throw new ConfigurationException($msg);
1442
        }
1443 8
    }
1444
1445
    /**
1446
     * Looks on include path for specified file.
1447
     *
1448
     * @param string $path
1449
     *
1450
     * @return string File found (null if no file found).
1451
     */
1452 764
    public static function getResourcePath($path)
1453
    {
1454 764
        if (self::$importPaths === null) {
1455
            self::$importPaths = self::explodeIncludePath();
1456
        }
1457
1458 764
        $path = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $path);
1459
1460 764
        foreach (self::$importPaths as $prefix) {
1461 764
            $testPath = $prefix . DIRECTORY_SEPARATOR . $path;
1462 764
            if (file_exists($testPath)) {
1463 763
                return $testPath;
1464
            }
1465
        }
1466
1467
        // Check for the property phing.home
1468 6
        $homeDir = self::getProperty(self::PHING_HOME);
1469 6
        if ($homeDir) {
1470 6
            $testPath = $homeDir . DIRECTORY_SEPARATOR . $path;
1471 6
            if (file_exists($testPath)) {
1472 6
                return $testPath;
1473
            }
1474
        }
1475
1476
        // Check for the phing home of phar archive
1477 6
        if (strpos(self::$importPaths[0], 'phar://') === 0) {
1478
            $testPath = self::$importPaths[0] . '/../' . $path;
1479
            if (file_exists($testPath)) {
1480
                return $testPath;
1481
            }
1482
        }
1483
1484
        // Do one additional check based on path of current file (Phing.php)
1485 6
        $maybeHomeDir = realpath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..');
1486 6
        $testPath = $maybeHomeDir . DIRECTORY_SEPARATOR . $path;
1487 6
        if (file_exists($testPath)) {
1488
            return $testPath;
1489
        }
1490
1491 6
        return null;
1492
    }
1493
1494
    /**
1495
     * Explode an include path into an array
1496
     *
1497
     * If no path provided, uses current include_path. Works around issues that
1498
     * occur when the path includes stream schemas.
1499
     *
1500
     * Pulled from Zend_Loader::explodeIncludePath() in ZF1.
1501
     *
1502
     * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
1503
     * @license   http://framework.zend.com/license/new-bsd New BSD License
1504
     * @param     string|null $path
1505
     * @return    array
1506
     */
1507 16
    public static function explodeIncludePath($path = null)
1508
    {
1509 16
        if (null === $path) {
1510 16
            $path = get_include_path();
1511
        }
1512
1513 16
        if (PATH_SEPARATOR == ':') {
1514
            // On *nix systems, include_paths which include paths with a stream
1515
            // schema cannot be safely explode'd, so we have to be a bit more
1516
            // intelligent in the approach.
1517 16
            $paths = preg_split('#:(?!//)#', $path);
1518
        } else {
1519
            $paths = explode(PATH_SEPARATOR, $path);
1520
        }
1521
1522 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...
1523
    }
1524
1525
    // -------------------------------------------------------------------------------------------
1526
    // System-wide methods (moved from System class, which had namespace conflicts w/ PEAR System)
1527
    // -------------------------------------------------------------------------------------------
1528
1529
    /**
1530
     * Set System constants which can be retrieved by calling Phing::getProperty($propName).
1531
     *
1532
     * @return void
1533
     */
1534 1
    private static function setSystemConstants()
1535
    {
1536
1537
        /*
1538
         * PHP_OS returns on
1539
         *   WindowsNT4.0sp6  => WINNT
1540
         *   Windows2000      => WINNT
1541
         *   Windows ME       => WIN32
1542
         *   Windows 98SE     => WIN32
1543
         *   FreeBSD 4.5p7    => FreeBSD
1544
         *   Redhat Linux     => Linux
1545
         *   Mac OS X         => Darwin
1546
         */
1547 1
        self::setProperty('host.os', PHP_OS);
1548
1549
        // this is used by some tasks too
1550 1
        self::setProperty('os.name', PHP_OS);
1551
1552
        // it's still possible this won't be defined,
1553
        // e.g. if Phing is being included in another app w/o
1554
        // using the phing.php script.
1555 1
        if (!defined('PHP_CLASSPATH')) {
1556
            define('PHP_CLASSPATH', get_include_path());
1557
        }
1558
1559 1
        self::setProperty('php.classpath', PHP_CLASSPATH);
1560
1561
        // try to determine the host filesystem and set system property
1562
        // used by Fileself::getFileSystem to instantiate the correct
1563
        // abstraction layer
1564
1565 1
        switch (strtoupper(PHP_OS)) {
1566 1
            case 'WINNT':
1567 1
            case 'WIN32':
1568
                self::setProperty('host.fstype', 'WINDOWS');
1569
                break;
1570
            default:
1571 1
                self::setProperty('host.fstype', 'UNIX');
1572 1
                break;
1573
        }
1574
1575 1
        if (defined('PHP_BINARY')) {
1576 1
            self::setProperty(self::PHP_INTERPRETER, PHP_BINARY);
1577
        } else {
1578
            self::setProperty(self::PHP_INTERPRETER, getenv('PHP_COMMAND'));
1579
        }
1580 1
        self::setProperty('file.separator', FileUtils::$separator);
1581 1
        self::setProperty('line.separator', PHP_EOL);
1582 1
        self::setProperty('path.separator', FileUtils::$pathSeparator);
1583 1
        self::setProperty(self::PHP_VERSION, PHP_VERSION);
1584 1
        self::setProperty('php.tmpdir', sys_get_temp_dir());
1585 1
        if (stripos(PHP_OS, 'WIN') !== 0) {
1586 1
            self::setProperty('user.home', getenv('HOME'));
1587
        } else {
1588
            self::setProperty('user.home', getenv('HOMEDRIVE') . getenv('HOMEPATH'));
1589
        }
1590 1
        self::setProperty('application.startdir', getcwd());
1591 1
        self::setProperty('phing.startTime', gmdate('D, d M Y H:i:s', time()) . ' GMT');
1592
1593
        // try to detect machine dependent information
1594 1
        $sysInfo = [];
1595 1
        if (function_exists("posix_uname") && stripos(PHP_OS, 'WIN') !== 0) {
1596 1
            $sysInfo = posix_uname();
1597
        } else {
1598
            $sysInfo['nodename'] = php_uname('n');
1599
            $sysInfo['machine'] = php_uname('m');
1600
            //this is a not so ideal substition, but maybe better than nothing
1601
            $sysInfo['domain'] = $_SERVER['SERVER_NAME'] ?? "unknown";
1602
            $sysInfo['release'] = php_uname('r');
1603
            $sysInfo['version'] = php_uname('v');
1604
        }
1605
1606 1
        self::setProperty("host.name", $sysInfo['nodename'] ?? "unknown");
1607 1
        self::setProperty("host.arch", $sysInfo['machine'] ?? "unknown");
1608 1
        self::setProperty("host.domain", $sysInfo['domain'] ?? "unknown");
1609 1
        self::setProperty("host.os.release", $sysInfo['release'] ?? "unknown");
1610 1
        self::setProperty("host.os.version", $sysInfo['version'] ?? "unknown");
1611 1
        unset($sysInfo);
1612 1
    }
1613
1614
    /**
1615
     * This gets a property that was set via command line or otherwise passed into Phing.
1616
     * "Defined" in this case means "externally defined".  The reason this method exists is to
1617
     * provide a public means of accessing commandline properties for (e.g.) logger or listener
1618
     * scripts.  E.g. to specify which logfile to use, PearLogger needs to be able to access
1619
     * the pear.log.name property.
1620
     *
1621
     * @param  string $name
1622
     * @return string value of found property (or null, if none found).
1623
     */
1624
    public static function getDefinedProperty($name)
1625
    {
1626
        return self::$definedProps->getProperty($name);
1627
    }
1628
1629
    /**
1630
     * This sets a property that was set via command line or otherwise passed into Phing.
1631
     *
1632
     * @param  string $name
1633
     * @param  mixed $value
1634
     * @return mixed value of found property (or null, if none found).
1635
     */
1636
    public static function setDefinedProperty($name, $value)
1637
    {
1638
        return self::$definedProps->setProperty($name, $value);
1639
    }
1640
1641
    /**
1642
     * Returns property value for a System property.
1643
     * System properties are "global" properties like application.startdir,
1644
     * and user.dir.  Many of these correspond to similar properties in Java
1645
     * or Ant.
1646
     *
1647
     * @param  string $propName
1648
     * @return string Value of found property (or null, if none found).
1649
     */
1650 351
    public static function getProperty($propName)
1651
    {
1652
1653
        // some properties are detemined on each access
1654
        // some are cached, see below
1655
1656
        // default is the cached value:
1657 351
        $val = self::$properties[$propName] ?? null;
1658
1659
        // special exceptions
1660
        switch ($propName) {
1661 351
            case 'user.dir':
1662 133
                $val = getcwd();
1663 133
                break;
1664
        }
1665
1666 351
        return $val;
1667
    }
1668
1669
    /**
1670
     * Retuns reference to all properties
1671
     */
1672 763
    public static function &getProperties()
1673
    {
1674 763
        return self::$properties;
1675
    }
1676
1677
    /**
1678
     * @param $propName
1679
     * @param $propValue
1680
     * @return string
1681
     */
1682 7
    public static function setProperty($propName, $propValue)
1683
    {
1684 7
        $propName = (string) $propName;
1685 7
        $oldValue = self::getProperty($propName);
1686 7
        self::$properties[$propName] = $propValue;
1687
1688 7
        return $oldValue;
1689
    }
1690
1691
    /**
1692
     * @return float
1693
     */
1694 66
    public static function currentTimeMillis()
1695
    {
1696 66
        [$usec, $sec] = explode(" ", microtime());
1697
1698 66
        return ((float) $usec + (float) $sec);
1699
    }
1700
1701
    /**
1702
     * Sets the include path to PHP_CLASSPATH constant (if this has been defined).
1703
     *
1704
     * @return void
1705
     * @throws ConfigurationException - if the include_path could not be set (for some bizarre reason)
1706
     */
1707 1
    private static function setIncludePaths()
1708
    {
1709 1
        if (defined('PHP_CLASSPATH')) {
1710 1
            $result = set_include_path(PHP_CLASSPATH);
1711 1
            if ($result === false) {
1712
                throw new ConfigurationException("Could not set PHP include_path.");
1713
            }
1714 1
            self::$origIniSettings['include_path'] = $result; // save original value for setting back later
1715
        }
1716 1
    }
1717
1718
    /**
1719
     * Converts shorthand notation values as returned by ini_get()
1720
     *
1721
     * @see    http://www.php.net/ini_get
1722
     * @param  string|int $val
1723
     * @return int
1724
     */
1725 2
    public static function convertShorthand($val): int
1726
    {
1727 2
        $val = trim($val);
1728 2
        $last = strtolower($val[strlen($val) - 1]);
1729
1730 2
        if (!is_numeric($last)) {
1731 2
            $val = (int) substr($val, 0, -1);
1732
1733 2
            switch ($last) {
1734
                // The 'G' modifier is available since PHP 5.1.0
1735 2
                case 'g':
1736 2
                    $val *= 1024;
1737
                // no break
1738 1
                case 'm':
1739 2
                    $val *= 1024;
1740
                // no break
1741 1
                case 'k':
1742 2
                    $val *= 1024;
1743
            }
1744
        }
1745
1746 2
        return $val;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $val could return the type string which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
1747
    }
1748
1749
    /**
1750
     * Sets PHP INI values that Phing needs.
1751
     */
1752 1
    private static function setIni(): void
1753
    {
1754 1
        self::$origIniSettings['error_reporting'] = error_reporting(E_ALL);
1755
1756
        // We won't bother storing original max_execution_time, since 1) the value in
1757
        // php.ini may be wrong (and there's no way to get the current value) and
1758
        // 2) it would mean something very strange to set it to a value less than time script
1759
        // has already been running, which would be the likely change.
1760
1761 1
        set_time_limit(0);
1762
1763 1
        self::$origIniSettings['short_open_tag'] = ini_set('short_open_tag', 'off');
1764 1
        self::$origIniSettings['default_charset'] = ini_set('default_charset', 'iso-8859-1');
1765 1
        self::$origIniSettings['track_errors'] = ini_set('track_errors', 1);
1766
1767 1
        $mem_limit = (int) self::convertShorthand(ini_get('memory_limit'));
1768 1
        if ($mem_limit < (32 * 1024 * 1024) && $mem_limit > -1) {
1769
            // We do *not* need to save the original value here, since we don't plan to restore
1770
            // this after shutdown (we don't trust the effectiveness of PHP's garbage collection).
1771
            ini_set('memory_limit', '32M'); // nore: this may need to be higher for many projects
1772
        }
1773 1
    }
1774
1775
    /**
1776
     * Restores [most] PHP INI values to their pre-Phing state.
1777
     *
1778
     * Currently the following settings are not restored:
1779
     *  - max_execution_time (because getting current time limit is not possible)
1780
     *  - memory_limit (which may have been increased by Phing)
1781
     */
1782 1
    private static function restoreIni(): void
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(): Timer
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(): void
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
     * @throws IOException
1834
     */
1835 1
    public static function shutdown(): void
1836
    {
1837 1
        FileSystem::getFileSystem()::deleteFilesOnExit();
1838 1
        self::$msgOutputLevel = Project::MSG_INFO;
1839 1
        self::restoreIni();
1840 1
        self::getTimer()->stop();
1841 1
    }
1842
}
1843