Issues (555)

Branch: main

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  Header Injection
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Phing/Phing.php (6 issues)

1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing;
22
23
use Exception;
24
use Phing\Exception\BuildException;
25
use Phing\Exception\ConfigurationException;
26
use Phing\Exception\ExitStatusException;
27
use Phing\Input\ConsoleInputHandler;
28
use Phing\Io\File;
29
use Phing\Io\FileOutputStream;
30
use Phing\Io\FileParserFactory;
31
use Phing\Io\FileReader;
32
use Phing\Io\FileSystem;
33
use Phing\Io\FileUtils;
34
use Phing\Io\IOException;
35
use Phing\Io\OutputStream;
36
use Phing\Io\PrintStream;
37
use Phing\Listener\BuildLogger;
38
use Phing\Listener\DefaultLogger;
39
use Phing\Listener\SilentLogger;
40
use Phing\Listener\StreamRequiredBuildLogger;
41
use Phing\Parser\ProjectConfigurator;
42
use Phing\Util\DefaultClock;
43
use Phing\Util\Diagnostics;
44
use Phing\Util\Properties;
45
use Phing\Util\SizeHelper;
46
use Phing\Util\StringHelper;
47
use SebastianBergmann\Version;
48
use Symfony\Component\Console\Output\ConsoleOutput;
49
use Throwable;
50
51
use function array_filter;
52
use function array_map;
53
use function array_reduce;
54
use function gmdate;
55
use function implode;
56
use function method_exists;
57
use function sprintf;
58
use function strlen;
59
use function strval;
60
use function trim;
61
62
use const PHP_EOL;
63
64
/**
65
 * Entry point into Phing.  This class handles the full lifecycle of a build -- from
66
 * parsing & handling commandline arguments to assembling the project to shutting down
67
 * and cleaning up in the end.
68
 *
69
 * If you are invoking Phing from an external application, this is still
70
 * the class to use.  Your application can invoke the start() method, passing
71
 * any commandline arguments or additional properties.
72
 *
73
 * @author Andreas Aderhold <[email protected]>
74
 * @author Hans Lellelid <[email protected]>
75
 */
76
class Phing
77
{
78
    /**
79
     * Alias for phar file.
80
     */
81
    public const PHAR_ALIAS = 'phing.phar';
82
83
    /**
84
     * The default build file name.
85
     */
86
    public const DEFAULT_BUILD_FILENAME = 'build.xml';
87
    public const DEFAULT_BUILD_CONTENT = <<<'XML'
88
        <?xml version="1.0" encoding="UTF-8" ?>
89
90
        <project name="" description="" default="">
91
92
            <target name="" description="">
93
94
            </target>
95
96
        </project>
97
        XML;
98
    public const PHING_HOME = 'phing.home';
99
    public const PHP_VERSION = 'php.version';
100
    public const PHP_INTERPRETER = 'php.interpreter';
101
102
    /**
103
     * Our current message output status. Follows Project::MSG_XXX.
104
     */
105
    private static $msgOutputLevel = Project::MSG_INFO;
106
    /**
107
     * Set of properties that are passed in from commandline or invoking code.
108
     *
109
     * @var Properties
110
     */
111
    private static $definedProps;
112
    /**
113
     * Used by utility function getResourcePath().
114
     */
115
    private static $importPaths;
116
    /**
117
     * Cache of the Phing version information when it has been loaded.
118
     */
119
    private static $phingVersion;
120
    /**
121
     * Cache of the short Phing version information when it has been loaded.
122
     */
123
    private static $phingShortVersion;
124
    /**
125
     * System-wide static properties (moved from System).
126
     */
127
    private static $properties = [];
128
    /**
129
     * Static system timer.
130
     */
131
    private static $timer;
132
    /**
133
     * The current Project.
134
     */
135
    private static $currentProject;
136
    /**
137
     * Whether to capture PHP errors to buffer.
138
     */
139
    private static $phpErrorCapture = false;
140
    /**
141
     * Array of captured PHP errors.
142
     */
143
    private static $capturedPhpErrors = [];
144
    /**
145
     * @var OUtputStream stream for standard output
146
     */
147
    private static $out;
148
    /**
149
     * @var OutputStream stream for error output
150
     */
151
    private static $err;
152
    /**
153
     * @var bool whether we are using a logfile
154
     */
155
    private static $isLogFileUsed = false;
156
    /**
157
     * Array to hold original ini settings that Phing changes (and needs
158
     * to restore in restoreIni() method).
159
     *
160
     * @var array Struct of array(setting-name => setting-value)
161
     *
162
     * @see restoreIni()
163
     */
164
    private static $origIniSettings = [];
165
    /**
166
     * PhingFile that we are using for configuration.
167
     */
168
    private $buildFile;
169
    /**
170
     * The build targets.
171
     */
172
    private $targets = [];
173
    /**
174
     * Names of classes to add as listeners to project.
175
     */
176
    private $listeners = [];
177
    /**
178
     * keep going mode.
179
     *
180
     * @var bool
181
     */
182
    private $keepGoingMode = false;
183
    private $loggerClassname;
184
    /**
185
     * The class to handle input (can be only one).
186
     */
187
    private $inputHandlerClassname;
188
    /**
189
     * Whether or not log output should be reduced to the minimum.
190
     *
191
     * @var bool
192
     */
193
    private $silent = false;
194
    /**
195
     * Indicates whether phing should run in strict mode.
196
     */
197
    private $strictMode = false;
198
    /**
199
     * Indicates if this phing should be run.
200
     */
201
    private $readyToRun = false;
202
    /**
203
     * Indicates we should only parse and display the project help information.
204
     */
205
    private $projectHelp = false;
206
    /**
207
     * Whether to values in a property file should override existing values.
208
     */
209
    private $propertyFileOverride = false;
210
    /**
211
     * Whether or not output to the log is to be unadorned.
212
     */
213
    private $emacsMode = false;
214
215
    /**
216
     * What to search for, passed as arguments.
217
     *
218
     * @var ?string
219
     */
220
    private $searchForThis;
221
    private $propertyFiles = [];
222
223
    /**
224
     * Gets the stream to use for standard (non-error) output.
225
     *
226
     * @return OutputStream
227
     */
228
    public static function getOutputStream()
229
    {
230
        return self::$out;
231
    }
232
233
    /**
234
     * Gets the stream to use for error output.
235
     *
236
     * @return OutputStream
237
     */
238
    public static function getErrorStream()
239
    {
240
        return self::$err;
241
    }
242
243
    /**
244
     * Command line entry point. This method kicks off the building
245
     * of a project object and executes a build using either a given
246
     * target or the default target.
247
     *
248
     * @param array $args command line args
249
     *
250
     * @throws Exception
251
     */
252
    public static function fire($args): void
253
    {
254
        self::start($args);
255
    }
256
257
    /**
258
     * Entry point allowing for more options from other front ends.
259
     *
260
     * This method encapsulates the complete build lifecycle.
261
     *
262
     * @param array      $args                     the commandline args passed to phing shell script
263
     * @param array|null $additionalUserProperties Any additional properties to be passed to Phing (alternative front-end might implement this).
264
     *                                             These additional properties will be available using the getDefinedProperty() method and will
265
     *                                             be added to the project's "user" properties
266
     *
267
     * @throws Exception - if there is an error during build
268
     *
269
     * @see    runBuild()
270
     * @see    execute()
271
     */
272
    public static function start($args, ?array $additionalUserProperties = null)
273
    {
274
        try {
275
            $m = new self();
276
            $m->execute($args);
277
        } catch (Exception $exc) {
278
            self::handleLogfile();
279
            self::printMessage($exc);
280
            self::statusExit(1);
281
282
            return;
283
        }
284
285
        if (null !== $additionalUserProperties) {
286
            foreach ($additionalUserProperties as $key => $value) {
287
                $m::setDefinedProperty($key, $value);
288
            }
289
        }
290
291
        // expect the worst
292
        $exitCode = 1;
293
294
        try {
295
            try {
296
                $m->runBuild();
297
                $exitCode = 0;
298
            } catch (ExitStatusException $ese) {
299
                $exitCode = $ese->getCode();
300
                if (0 !== $exitCode) {
301
                    self::handleLogfile();
302
303
                    throw $ese;
304
                }
305
            }
306
        } catch (BuildException $exc) {
307
            // avoid printing output twice: self::printMessage($exc);
308
        } catch (Throwable $exc) {
309
            self::printMessage($exc);
310
        } finally {
311
            self::handleLogfile();
312
        }
313
        self::statusExit($exitCode);
314
    }
315
316
    /**
317
     * Setup/initialize Phing environment from commandline args.
318
     *
319
     * @param array $args commandline args passed to phing shell
320
     *
321
     * @throws ConfigurationException
322
     */
323
    public function execute($args): void
324
    {
325
        self::$definedProps = new Properties();
326
        $this->searchForThis = null;
327
328
        // 1) First handle any options which should always
329
        // Note: The order in which these are executed is important (if multiple of these options are specified)
330
331
        if (in_array('-help', $args) || in_array('-h', $args)) {
332
            static::printUsage();
333
334
            return;
335
        }
336
337
        if (in_array('-version', $args) || in_array('-v', $args)) {
338
            static::printVersion();
339
340
            return;
341
        }
342
343
        if (in_array('-init', $args) || in_array('-i', $args)) {
344
            $key = array_search('-init', $args) ?: array_search('-i', $args);
345
            $path = $args[$key + 1] ?? null;
346
347
            self::init($path);
348
349
            return;
350
        }
351
352
        if (in_array('-diagnostics', $args)) {
353
            Diagnostics::doReport(new PrintStream(self::$out));
354
355
            return;
356
        }
357
358
        // 2) Next pull out stand-alone args.
359
        // Note: The order in which these are executed is important (if multiple of these options are specified)
360
361
        if (
362
            false !== ($key = array_search('-quiet', $args, true))
363
            || false !== ($key = array_search(
364
                '-q',
365
                $args,
366
                true
367
            ))
368
        ) {
369
            self::$msgOutputLevel = Project::MSG_WARN;
370
            unset($args[$key]);
371
        }
372
373
        if (
374
            false !== ($key = array_search('-emacs', $args, true))
375
            || false !== ($key = array_search('-e', $args, true))
376
        ) {
377
            $this->emacsMode = true;
378
            unset($args[$key]);
379
        }
380
381
        if (false !== ($key = array_search('-verbose', $args, true))) {
382
            self::$msgOutputLevel = Project::MSG_VERBOSE;
383
            unset($args[$key]);
384
        }
385
386
        if (false !== ($key = array_search('-debug', $args, true))) {
387
            self::$msgOutputLevel = Project::MSG_DEBUG;
388
            unset($args[$key]);
389
        }
390
391
        if (
392
            false !== ($key = array_search('-silent', $args, true))
393
            || false !== ($key = array_search('-S', $args, true))
394
        ) {
395
            $this->silent = true;
396
            unset($args[$key]);
397
        }
398
399
        if (false !== ($key = array_search('-propertyfileoverride', $args, true))) {
400
            $this->propertyFileOverride = true;
401
            unset($args[$key]);
402
        }
403
404
        // 3) Finally, cycle through to parse remaining args
405
        //
406
        $keys = array_keys($args); // Use keys and iterate to max(keys) since there may be some gaps
407
        $max = $keys ? max($keys) : -1;
408
        for ($i = 0; $i <= $max; ++$i) {
409
            if (!array_key_exists($i, $args)) {
410
                // skip this argument, since it must have been removed above.
411
                continue;
412
            }
413
414
            $arg = $args[$i];
415
416
            if ('-logfile' == $arg) {
417
                try {
418
                    // see: http://phing.info/trac/ticket/65
419
                    if (!isset($args[$i + 1])) {
420
                        $msg = "You must specify a log file when using the -logfile argument\n";
421
422
                        throw new ConfigurationException($msg);
423
                    }
424
425
                    $logFile = new File($args[++$i]);
426
                    $out = new FileOutputStream($logFile); // overwrite
427
                    self::setOutputStream($out);
428
                    self::setErrorStream($out);
429
                    self::$isLogFileUsed = true;
430
                } catch (IOException $ioe) {
431
                    $msg = 'Cannot write on the specified log file. Make sure the path exists and you have write permissions.';
432
433
                    throw new ConfigurationException($msg, $ioe);
434
                }
435
            } elseif ('-buildfile' == $arg || '-file' == $arg || '-f' == $arg) {
436
                if (!isset($args[$i + 1])) {
437
                    $msg = 'You must specify a buildfile when using the -buildfile argument.';
438
439
                    throw new ConfigurationException($msg);
440
                }
441
442
                $this->buildFile = new File($args[++$i]);
443
            } elseif ('-listener' == $arg) {
444
                if (!isset($args[$i + 1])) {
445
                    $msg = 'You must specify a listener class when using the -listener argument';
446
447
                    throw new ConfigurationException($msg);
448
                }
449
450
                $this->listeners[] = $args[++$i];
451
            } elseif (StringHelper::startsWith('-D', $arg)) {
452
                // Evaluating the property information //
453
                // Checking whether arg. is not just a switch, and next arg. does not starts with switch identifier
454
                if (('-D' == $arg) && (!StringHelper::startsWith('-', $args[$i + 1]))) {
455
                    $name = $args[++$i];
456
                } else {
457
                    $name = substr($arg, 2);
458
                }
459
460
                $value = null;
461
                $posEq = strpos($name, '=');
462
                if (false !== $posEq) {
463
                    $value = substr($name, $posEq + 1);
464
                    $name = substr($name, 0, $posEq);
465
                } elseif ($i < count($args) - 1 && !StringHelper::startsWith('-D', $args[$i + 1])) {
466
                    $value = $args[++$i];
467
                }
468
                self::$definedProps->setProperty($name, $value);
469
            } elseif ('-logger' == $arg) {
470
                if (!isset($args[$i + 1])) {
471
                    $msg = 'You must specify a classname when using the -logger argument';
472
473
                    throw new ConfigurationException($msg);
474
                }
475
476
                $this->loggerClassname = $args[++$i];
477
            } elseif ('-no-strict' == $arg) {
478
                $this->strictMode = false;
479
            } elseif ('-strict' == $arg) {
480
                $this->strictMode = true;
481
            } elseif ('-inputhandler' == $arg) {
482
                if (null !== $this->inputHandlerClassname) {
483
                    throw new ConfigurationException('Only one input handler class may be specified.');
484
                }
485
                if (!isset($args[$i + 1])) {
486
                    $msg = 'You must specify a classname when using the -inputhandler argument';
487
488
                    throw new ConfigurationException($msg);
489
                }
490
491
                $this->inputHandlerClassname = $args[++$i];
492
            } elseif ('-propertyfile' === $arg) {
493
                $i = $this->handleArgPropertyFile($args, $i);
494
            } elseif ('-keep-going' === $arg || '-k' === $arg) {
495
                $this->keepGoingMode = true;
496
            } elseif ('-longtargets' == $arg) {
497
                self::$definedProps->setProperty('phing.showlongtargets', 1);
498
            } elseif ('-projecthelp' == $arg || '-targets' == $arg || '-list' == $arg || '-l' == $arg || '-p' == $arg) {
499
                // set the flag to display the targets and quit
500
                $this->projectHelp = true;
501
            } elseif ('-find' == $arg) {
502
                // eat up next arg if present, default to build.xml
503
                if ($i < count($args) - 1) {
504
                    $this->searchForThis = $args[++$i];
505
                } else {
506
                    $this->searchForThis = self::DEFAULT_BUILD_FILENAME;
507
                }
508
            } elseif ('-' == substr($arg, 0, 1)) {
509
                // we don't have any more args
510
                self::printUsage();
511
                self::$err->write(PHP_EOL);
512
513
                throw new ConfigurationException('Unknown argument: ' . $arg);
514
            } else {
515
                // if it's no other arg, it may be the target
516
                $this->targets[] = $arg;
517
            }
518
        }
519
520
        // if buildFile was not specified on the command line,
521
        if (null === $this->buildFile) {
522
            // but -find then search for it
523
            if (null !== $this->searchForThis) {
524
                $this->buildFile = $this->findBuildFile(self::getProperty('user.dir'), $this->searchForThis);
0 ignored issues
show
$this->searchForThis of type void is incompatible with the type string expected by parameter $suffix of Phing\Phing::findBuildFile(). ( Ignorable by Annotation )

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

524
                $this->buildFile = $this->findBuildFile(self::getProperty('user.dir'), /** @scrutinizer ignore-type */ $this->searchForThis);
Loading history...
525
            } else {
526
                $this->buildFile = new File(self::DEFAULT_BUILD_FILENAME);
527
            }
528
        }
529
530
        try {
531
            // make sure buildfile (or buildfile.dist) exists
532
            if (!$this->buildFile->exists()) {
533
                $distFile = new File($this->buildFile->getAbsolutePath() . '.dist');
534
                if (!$distFile->exists()) {
535
                    throw new ConfigurationException(
536
                        'Buildfile: ' . $this->buildFile->__toString() . ' does not exist!'
537
                    );
538
                }
539
                $this->buildFile = $distFile;
540
            }
541
542
            // make sure it's not a directory
543
            if ($this->buildFile->isDirectory()) {
544
                throw new ConfigurationException('Buildfile: ' . $this->buildFile->__toString() . ' is a dir!');
545
            }
546
        } catch (IOException $e) {
547
            // something else happened, buildfile probably not readable
548
            throw new ConfigurationException('Buildfile: ' . $this->buildFile->__toString() . ' is not readable!');
549
        }
550
551
        $this->loadPropertyFiles();
552
553
        $this->readyToRun = true;
554
    }
555
556
    /**
557
     * Prints the usage of how to use this class.
558
     */
559 1
    public static function printUsage()
560
    {
561 1
        $msg = <<<'TEXT'
562
            phing [options] [target [target2 [target3] ...]]
563
            Options:
564
              -h -help                     print this message
565
              -l -list                     list available targets in this project
566
              -i -init [file]              generates an initial buildfile
567
              -v -version                  print the version information and exit
568
              -q -quiet                    be extra quiet
569
              -S -silent                   print nothing but task outputs and build failures
570
                 -verbose                  be extra verbose
571
                 -debug                    print debugging information
572
              -e -emacs                    produce logging information without adornments
573
                 -diagnostics              print diagnostics information
574
                 -strict                   runs build in strict mode, considering a warning as error
575
                 -no-strict                runs build normally (overrides buildfile attribute)
576
                 -longtargets              show target descriptions during build
577
                 -logfile <file>           use given file for log
578
                 -logger <classname>       the class which is to perform logging
579
                 -listener <classname>     add an instance of class as a project listener
580
              -f -buildfile <file>         use given buildfile
581
                 -D<property>=<value>      use value for given property
582
              -k -keep-going               execute all targets that do not depend on failed target(s)
583
                 -propertyfile <file>      load all properties from file
584
                 -propertyfileoverride     values in property file override existing values
585
                 -find <file>              search for buildfile towards the root of the filesystem and use it
586
                 -inputhandler <classname> the class to use to handle user input
587
588
            Report bugs to https://github.com/phingofficial/phing/issues
589
590 1
            TEXT;
591
592 1
        self::$err->write($msg);
593
    }
594
595
    /**
596
     * Prints the current Phing version.
597
     */
598
    public static function printVersion()
599
    {
600
        self::$out->write(self::getPhingVersion() . PHP_EOL);
601
    }
602
603
    /**
604
     * Gets the current Phing version based on VERSION.TXT file. Once the information
605
     * has been loaded once, it's cached and returned from the cache on future
606
     * calls.
607
     *
608
     * @throws ConfigurationException
609
     */
610 6
    public static function getPhingVersion(): string
611
    {
612 6
        if (self::$phingVersion === null) {
613
            $versionPath = self::getResourcePath('phing/etc/VERSION.TXT');
614
            if (null === $versionPath) {
615
                $versionPath = self::getResourcePath('etc/VERSION.TXT');
616
            }
617
            if (null === $versionPath) {
618
                throw new ConfigurationException('No VERSION.TXT file found; try setting phing.home environment variable.');
619
            }
620
621
            try { // try to read file
622
                $file = new File($versionPath);
623
                $reader = new FileReader($file);
624
                $phingVersion = trim($reader->read());
625
            } catch (IOException $iox) {
626
                throw new ConfigurationException("Can't read version information file");
627
            }
628
629
            $basePath = dirname(__DIR__, 2);
630
631
            $version = new Version($phingVersion, $basePath);
632
            self::$phingShortVersion = (method_exists($version, 'asString') ? $version->asString() : $version->getVersion());
633
634
            self::$phingVersion = 'Phing ' . self::$phingShortVersion;
635
        }
636 6
        return self::$phingVersion;
637
    }
638
639
    /**
640
     * Returns the short Phing version information, if available. Once the information
641
     * has been loaded once, it's cached and returned from the cache on future
642
     * calls.
643
     *
644
     * @return the short Phing version information as a string
0 ignored issues
show
The type Phing\the was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
645
     *
646
     * @throws ConfigurationException
647
     */
648
    public static function getPhingShortVersion(): string
649
    {
650
        if (self::$phingShortVersion === null) {
651
            self::getPhingVersion();
652
        }
653
        return self::$phingShortVersion;
654
    }
655
656
    /**
657
     * Looks on include path for specified file.
658
     *
659
     * @param string $path
660
     *
661
     * @return null|string file found (null if no file found)
662
     */
663 908
    public static function getResourcePath($path): ?string
664
    {
665 908
        if (null === self::$importPaths) {
666
            self::$importPaths = self::explodeIncludePath();
667
        }
668
669 908
        $path = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $path);
670
671 908
        foreach (self::$importPaths as $prefix) {
672 908
            $testPath = $prefix . DIRECTORY_SEPARATOR . $path;
673 908
            if (file_exists($testPath)) {
674
                return $testPath;
675
            }
676
        }
677
678
        // Check for the property phing.home
679 908
        $homeDir = self::getProperty(self::PHING_HOME);
680 908
        if ($homeDir) {
681 908
            $testPath = $homeDir . DIRECTORY_SEPARATOR . $path;
682 908
            if (file_exists($testPath)) {
683 908
                return $testPath;
684
            }
685
        }
686
687
        // Check for the phing home of phar archive
688
        if (0 === strpos(self::$importPaths[0], 'phar://')) {
689
            $testPath = self::$importPaths[0] . '/../' . $path;
690
            if (file_exists($testPath)) {
691
                return $testPath;
692
            }
693
        }
694
695
        // Do one additional check based on path of current file (Phing.php)
696
        $maybeHomeDir = realpath(__DIR__ . DIRECTORY_SEPARATOR);
697
        $testPath = $maybeHomeDir . DIRECTORY_SEPARATOR . $path;
698
        if (file_exists($testPath)) {
699
            return $testPath;
700
        }
701
702
        return null;
703
    }
704
705
    /**
706
     * Explode an include path into an array.
707
     *
708
     * If no path provided, uses current include_path. Works around issues that
709
     * occur when the path includes stream schemas.
710
     *
711
     * Pulled from Zend_Loader::explodeIncludePath() in ZF1.
712
     *
713
     * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
714
     * @license   http://framework.zend.com/license/new-bsd New BSD License
715
     *
716
     * @param null|string $path
717
     *
718
     * @return array
719
     */
720 17
    public static function explodeIncludePath($path = null)
721
    {
722 17
        if (null === $path) {
723 17
            $path = get_include_path();
724
        }
725
726 17
        if (PATH_SEPARATOR === ':') {
727
            // On *nix systems, include_paths which include paths with a stream
728
            // schema cannot be safely explode'd, so we have to be a bit more
729
            // intelligent in the approach.
730 17
            $paths = preg_split('#:(?!//)#', $path);
731
        } else {
732
            $paths = explode(PATH_SEPARATOR, $path);
733
        }
734
735 17
        return $paths;
736
    }
737
738
    /**
739
     * Returns property value for a System property.
740
     * System properties are "global" properties like application.startdir,
741
     * and user.dir.  Many of these correspond to similar properties in Java
742
     * or Ant.
743
     *
744
     * @param string $propName
745
     *
746
     * @return string value of found property (or null, if none found)
747
     */
748 926
    public static function getProperty($propName)
749
    {
750
        // some properties are detemined on each access
751
        // some are cached, see below
752
753
        // default is the cached value:
754 926
        $val = self::$properties[$propName] ?? null;
755
756
        // special exceptions
757
        switch ($propName) {
758 926
            case 'user.dir':
759 153
                $val = getcwd();
760
761 153
                break;
762
        }
763
764 926
        return $val;
765
    }
766
767
    /**
768
     * Creates generic buildfile.
769
     *
770
     * @param string $path
771
     */
772
    public static function init($path)
773
    {
774
        if ($buildfilePath = self::initPath($path)) {
775
            self::initWrite($buildfilePath);
776
        }
777
    }
778
779
    /**
780
     * Sets the stream to use for standard (non-error) output.
781
     *
782
     * @param OutputStream $stream the stream to use for standard output
783
     */
784 1
    public static function setOutputStream(OutputStream $stream)
785
    {
786 1
        self::$out = $stream;
787
    }
788
789
    /**
790
     * Sets the stream to use for error output.
791
     *
792
     * @param OutputStream $stream the stream to use for error output
793
     */
794 1
    public static function setErrorStream(OutputStream $stream): void
795
    {
796 1
        self::$err = $stream;
797
    }
798
799
    /**
800
     * Making output level a static property so that this property
801
     * can be accessed by other parts of the system, enabling
802
     * us to display more information -- e.g. backtraces -- for "debug" level.
803
     *
804
     * @return int
805
     */
806 9
    public static function getMsgOutputLevel()
807
    {
808 9
        return self::$msgOutputLevel;
809
    }
810
811
    /**
812
     * Prints the message of the Exception if it's not null.
813
     */
814
    public static function printMessage(Throwable $t): void
815
    {
816
        if (null === self::$err) { // Make sure our error output is initialized
817
            self::initializeOutputStreams();
818
        }
819
        if (self::getMsgOutputLevel() >= Project::MSG_VERBOSE) {
820
            self::$err->write((string) $t . PHP_EOL);
821
        } else {
822
            self::$err->write($t->getMessage() . PHP_EOL);
823
        }
824
    }
825
826
    /**
827
     * Performs any shutdown routines, such as stopping timers.
828
     *
829
     * @throws IOException
830
     */
831 1
    public static function shutdown(): void
832
    {
833 1
        FileSystem::getFileSystem()::deleteFilesOnExit();
834 1
        self::$msgOutputLevel = Project::MSG_INFO;
835 1
        self::restoreIni();
836 1
        self::getTimer()->stop();
837
    }
838
839
    /**
840
     * Returns reference to DefaultClock object.
841
     */
842 2
    public static function getTimer(): DefaultClock
843
    {
844 2
        if (null === self::$timer) {
845
            self::$timer = new DefaultClock();
846
        }
847
848 2
        return self::$timer;
849
    }
850
851
    /**
852
     * This sets a property that was set via command line or otherwise passed into Phing.
853
     *
854
     * @param string $name
855
     * @param mixed  $value
856
     *
857
     * @return mixed value of found property (or null, if none found)
858
     */
859
    public static function setDefinedProperty($name, $value)
860
    {
861
        return self::$definedProps->setProperty($name, $value);
862
    }
863
864
    /**
865
     * Executes the build.
866
     *
867
     * @throws IOException
868
     * @throws Throwable
869
     */
870
    public function runBuild(): void
871
    {
872
        if (!$this->readyToRun) {
873
            return;
874
        }
875
876
        $project = new Project();
877
878
        self::setCurrentProject($project);
879
        set_error_handler(['Phing\Phing', 'handlePhpError']);
880
881
        $error = null;
882
883
        try {
884
            $this->addBuildListeners($project);
885
            $this->addInputHandler($project);
886
887
            // set this right away, so that it can be used in logging.
888
            $project->setUserProperty('phing.file', $this->buildFile->getAbsolutePath());
889
            $project->setUserProperty('phing.dir', dirname($this->buildFile->getAbsolutePath()));
890
            $project->setUserProperty('phing.version', static::getPhingVersion());
891
            $project->fireBuildStarted();
892
            $project->init();
893
            $project->setKeepGoingMode($this->keepGoingMode);
894
895
            $e = self::$definedProps->keys();
896
            while (count($e)) {
897
                $arg = (string) array_shift($e);
898
                $value = (string) self::$definedProps->getProperty($arg);
899
                $project->setUserProperty($arg, $value);
900
            }
901
            unset($e);
902
903
            // first use the Configurator to create the project object
904
            // from the given build file.
905
906
            ProjectConfigurator::configureProject($project, $this->buildFile);
907
908
            // Set the project mode
909
            $project->setStrictMode(StringHelper::booleanValue($this->strictMode));
910
911
            // make sure that minimum required phing version is satisfied
912
            $this->comparePhingVersion($project->getPhingVersion());
913
914
            if ($this->projectHelp) {
915
                $this->printDescription($project);
916
                $this->printTargets($project);
917
918
                return;
919
            }
920
921
            // make sure that we have a target to execute
922
            if (0 === count($this->targets)) {
923
                $this->targets[] = $project->getDefaultTarget();
924
            }
925
926
            $project->executeTargets($this->targets);
927
        } catch (Throwable $t) {
928
            $error = $t;
929
930
            throw $t;
931
        } finally {
932
            if (!$this->projectHelp) {
933
                try {
934
                    $project->fireBuildFinished($error);
935
                } catch (Exception $e) {
936
                    self::$err->write('Caught an exception while logging the end of the build.  Exception was:' . PHP_EOL);
937
                    self::$err->write($e->getTraceAsString());
938
                    if (null !== $error) {
939
                        self::$err->write('There has been an error prior to that:' . PHP_EOL);
940
                        self::$err->write($error->getTraceAsString());
941
                    }
942
943
                    throw new BuildException($error);
944
                }
945
            } elseif (null !== $error) {
946
                $project->log($error->getMessage(), Project::MSG_ERR);
947
            }
948
949
            restore_error_handler();
950
            self::unsetCurrentProject();
951
        }
952
    }
953
954
    /**
955
     * Import a class, supporting the following conventions:
956
     * - PEAR style (@see http://pear.php.net/manual/en/standards.naming.php)
957
     * - PSR-0 (@see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md).
958
     *
959
     * @param string $classname Name of class
960
     * @param mixed  $classpath String or object supporting __toString()
961
     *
962
     * @throws BuildException - if cannot find the specified file
963
     *
964
     * @return string the unqualified classname (which can be instantiated)
965
     */
966 911
    public static function import($classname, $classpath = null)
967
    {
968
        // first check to see that the class specified hasn't already been included.
969 911
        if (class_exists($classname)) {
970 909
            return $classname;
971
        }
972
973 7
        $filename = strtr($classname, ['_' => DIRECTORY_SEPARATOR, '\\' => DIRECTORY_SEPARATOR]) . '.php';
974
975 7
        Phing::importFile($filename, $classpath);
976
977 5
        return $classname;
978
    }
979
980
    /**
981
     * Import a PHP file.
982
     *
983
     * This used to be named __import, however PHP has reserved all method names
984
     * with a double underscore prefix for future use.
985
     *
986
     * @param string $path      Path to the PHP file
987
     * @param mixed  $classpath String or object supporting __toString()
988
     *
989
     * @throws ConfigurationException
990
     */
991 10
    public static function importFile($path, $classpath = null)
992
    {
993 10
        if ($classpath) {
994
            // Apparently casting to (string) no longer invokes __toString() automatically.
995 2
            if (is_object($classpath)) {
996
                $classpath = $classpath->__toString();
997
            }
998
999
            // classpaths are currently additive, but we also don't want to just
1000
            // indiscriminantly prepand/append stuff to the include_path.  This means
1001
            // we need to parse current incldue_path, and prepend any
1002
            // specified classpath locations that are not already in the include_path.
1003
            //
1004
            // NOTE:  the reason why we do it this way instead of just changing include_path
1005
            // and then changing it back, is that in many cases applications (e.g. Propel) will
1006
            // include/require class files from within method calls.  This means that not all
1007
            // necessary files will be included in this import() call, and hence we can't
1008
            // change the include_path back without breaking those apps.  While this method could
1009
            // be more expensive than switching & switching back (not sure, but maybe), it makes it
1010
            // possible to write far less expensive run-time applications (e.g. using Propel), which is
1011
            // really where speed matters more.
1012
1013 2
            $curr_parts = Phing::explodeIncludePath();
1014 2
            $add_parts = Phing::explodeIncludePath($classpath);
1015 2
            $new_parts = array_diff($add_parts, $curr_parts);
1016 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...
1017 1
                set_include_path(implode(PATH_SEPARATOR, array_merge($new_parts, $curr_parts)));
1018
            }
1019
        }
1020
1021 10
        $ret = include_once $path;
1022
1023 8
        if (false === $ret) {
1024
            $msg = "Error importing {$path}";
1025
            if (self::getMsgOutputLevel() >= Project::MSG_DEBUG) {
1026
                $x = new Exception('for-path-trace-only');
1027
                $msg .= $x->getTraceAsString();
1028
            }
1029
1030
            throw new ConfigurationException($msg);
1031
        }
1032
    }
1033
1034
    /**
1035
     * Print the project description, if any.
1036
     *
1037
     * @throws IOException
1038
     */
1039
    public function printDescription(Project $project): void
1040
    {
1041
        if (null !== $project->getDescription()) {
1042
            $project->log($project->getDescription());
1043
        }
1044
    }
1045
1046
    /**
1047
     * Print out a list of all targets in the current buildfile.
1048
     */
1049 1
    public function printTargets(Project $project)
1050
    {
1051 1
        $visibleTargets = array_filter($project->getTargets(), function (Target $target) {
1052 1
            return !$target->isHidden() && !empty($target->getName());
1053 1
        });
1054 1
        $padding = array_reduce($visibleTargets, function (int $carry, Target $target) {
1055
            return max(strlen($target->getName()), $carry);
1056 1
        }, 0);
1057 1
        $categories = [
1058 1
            'Default target:' => array_filter($visibleTargets, function (Target $target) use ($project) {
1059
                return trim(strval($target)) === $project->getDefaultTarget();
1060 1
            }),
1061 1
            'Main targets:' => array_filter($visibleTargets, function (Target $target) {
1062
                return !empty($target->getDescription());
1063 1
            }),
1064 1
            'Subtargets:' => array_filter($visibleTargets, function (Target $target) {
1065
                return empty($target->getDescription());
1066 1
            }),
1067 1
        ];
1068 1
        foreach ($categories as $title => $targets) {
1069 1
            $targetList = $this->generateTargetList($title, $targets, $padding);
1070 1
            $project->log($targetList, Project::MSG_WARN);
1071
        }
1072
    }
1073
1074
    /**
1075
     * Unsets the current Project.
1076
     */
1077 1
    public static function unsetCurrentProject(): void
1078
    {
1079 1
        self::$currentProject = null;
1080
    }
1081
1082
    /**
1083
     * Error handler for PHP errors encountered during the build.
1084
     * This uses the logging for the currently configured project.
1085
     *
1086
     * @param $level
1087
     * @param string $message
1088
     * @param $file
1089
     * @param $line
1090
     */
1091
    public static function handlePhpError($level, $message, $file, $line)
1092
    {
1093
        // don't want to print suppressed errors
1094
        if (!(error_reporting() & $level)) {
1095
            return true;
1096
        }
1097
        if (self::$phpErrorCapture) {
1098
            self::$capturedPhpErrors[] = [
1099
                'message' => $message,
1100
                'level' => $level,
1101
                'line' => $line,
1102
                'file' => $file,
1103
            ];
1104
        } else {
1105
            $message = '[PHP Error] ' . $message;
1106
            $message .= ' [line ' . $line . ' of ' . $file . ']';
1107
1108
            switch ($level) {
1109
                case E_USER_DEPRECATED:
1110
                case E_DEPRECATED:
1111
                case E_NOTICE:
1112
                case E_USER_NOTICE:
1113
                    self::log($message, Project::MSG_VERBOSE);
1114
1115
                    break;
1116
1117
                case E_WARNING:
1118
                case E_USER_WARNING:
1119
                    self::log($message, Project::MSG_WARN);
1120
1121
                    break;
1122
1123
                case E_ERROR:
1124
                case E_USER_ERROR:
1125
                default:
1126
                    self::log($message, Project::MSG_ERR);
1127
            } // switch
1128
        } // if phpErrorCapture
1129
    }
1130
1131
    /**
1132
     * A static convenience method to send a log to the current (last-setup) Project.
1133
     * If there is no currently-configured Project, then this will do nothing.
1134
     *
1135
     * @param string $message
1136
     * @param int    $priority project::MSG_INFO, etc
1137
     */
1138 1
    public static function log($message, $priority = Project::MSG_INFO): void
1139
    {
1140 1
        $p = self::getCurrentProject();
1141 1
        if ($p) {
0 ignored issues
show
$p is of type Phing\Project, thus it always evaluated to true.
Loading history...
1142 1
            $p->log($message, $priority);
1143
        }
1144
    }
1145
1146
    /**
1147
     * Gets the current Project.
1148
     *
1149
     * @return Project current Project or NULL if none is set yet/still
1150
     */
1151 8
    public static function getCurrentProject()
1152
    {
1153 8
        return self::$currentProject;
1154
    }
1155
1156
    /**
1157
     * Sets the current Project.
1158
     *
1159
     * @param Project $p
1160
     */
1161 1
    public static function setCurrentProject($p): void
1162
    {
1163 1
        self::$currentProject = $p;
1164
    }
1165
1166
    /**
1167
     * Begins capturing PHP errors to a buffer.
1168
     * While errors are being captured, they are not logged.
1169
     */
1170
    public static function startPhpErrorCapture(): void
1171
    {
1172
        self::$phpErrorCapture = true;
1173
        self::$capturedPhpErrors = [];
1174
    }
1175
1176
    /**
1177
     * Stops capturing PHP errors to a buffer.
1178
     * The errors will once again be logged after calling this method.
1179
     */
1180
    public static function stopPhpErrorCapture(): void
1181
    {
1182
        self::$phpErrorCapture = false;
1183
    }
1184
1185
    /**
1186
     * Clears the captured errors without affecting the starting/stopping of the capture.
1187
     */
1188
    public static function clearCapturedPhpErrors(): void
1189
    {
1190
        self::$capturedPhpErrors = [];
1191
    }
1192
1193
    /**
1194
     * Gets any PHP errors that were captured to buffer.
1195
     *
1196
     * @return array array('message' => message, 'line' => line number, 'file' => file name, 'level' => error level)
1197
     */
1198
    public static function getCapturedPhpErrors()
1199
    {
1200
        return self::$capturedPhpErrors;
1201
    }
1202
1203
    /**
1204
     * This gets a property that was set via command line or otherwise passed into Phing.
1205
     * "Defined" in this case means "externally defined".  The reason this method exists is to
1206
     * provide a public means of accessing commandline properties for (e.g.) logger or listener
1207
     * scripts.  E.g. to specify which logfile to use, PearLogger needs to be able to access
1208
     * the pear.log.name property.
1209
     *
1210
     * @param string $name
1211
     *
1212
     * @return string value of found property (or null, if none found)
1213
     */
1214
    public static function getDefinedProperty($name)
1215
    {
1216
        return self::$definedProps->getProperty($name);
1217
    }
1218
1219
    /**
1220
     * Retuns reference to all properties.
1221
     */
1222 908
    public static function &getProperties()
1223
    {
1224 908
        return self::$properties;
1225
    }
1226
1227
    /**
1228
     * Start up Phing.
1229
     * Sets up the Phing environment but does not initiate the build process.
1230
     *
1231
     * @throws exception - If the Phing environment cannot be initialized
1232
     */
1233 1
    public static function startup(): void
1234
    {
1235
        // setup STDOUT and STDERR defaults
1236 1
        self::initializeOutputStreams();
1237
1238
        // some init stuff
1239 1
        self::getTimer()->start();
1240
1241 1
        self::setSystemConstants();
1242 1
        self::setIncludePaths();
1243 1
        self::setIni();
1244
    }
1245
1246
    /**
1247
     * @param $propName
1248
     * @param $propValue
1249
     *
1250
     * @return string
1251
     */
1252 8
    public static function setProperty($propName, $propValue)
1253
    {
1254 8
        $propName = (string) $propName;
1255 8
        $oldValue = self::getProperty($propName);
1256 8
        self::$properties[$propName] = $propValue;
1257
1258 8
        return $oldValue;
1259
    }
1260
1261
    /**
1262
     * Returns buildfile's path.
1263
     *
1264
     * @param $path
1265
     *
1266
     * @throws ConfigurationException
1267
     *
1268
     * @return string
1269
     */
1270
    protected static function initPath($path)
1271
    {
1272
        // Fallback
1273
        if (empty($path)) {
1274
            $defaultDir = self::getProperty('application.startdir');
1275
            $path = $defaultDir . DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1276
        }
1277
1278
        // Adding filename if necessary
1279
        if (is_dir($path)) {
1280
            $path .= DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1281
        }
1282
1283
        // Check if path is available
1284
        $dirname = dirname($path);
1285
        if (is_dir($dirname) && !is_file($path)) {
1286
            return $path;
1287
        }
1288
1289
        // Path is valid, but buildfile already exists
1290
        if (is_file($path)) {
1291
            throw new ConfigurationException('Buildfile already exists.');
1292
        }
1293
1294
        throw new ConfigurationException('Invalid path for sample buildfile.');
1295
    }
1296
1297
    /**
1298
     * Writes sample buildfile.
1299
     *
1300
     * If $buildfilePath does not exist, the buildfile is created.
1301
     *
1302
     * @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...
1303
     *
1304
     * @throws ConfigurationException
1305
     */
1306
    protected static function initWrite($buildfilePath): void
1307
    {
1308
        // Overwriting protection
1309
        if (file_exists($buildfilePath)) {
1310
            throw new ConfigurationException('Cannot overwrite existing file.');
1311
        }
1312
1313
        file_put_contents($buildfilePath, self::DEFAULT_BUILD_CONTENT);
1314
    }
1315
1316
    /**
1317
     * This operation is expected to call `exit($int)`, which
1318
     * is what the base version does.
1319
     * However, it is possible to do something else.
1320
     *
1321
     * @param int $exitCode code to exit with
1322
     */
1323
    protected static function statusExit($exitCode): void
1324
    {
1325
        Phing::shutdown();
1326
1327
        exit($exitCode);
0 ignored issues
show
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...
1328
    }
1329
1330
    /**
1331
     * Handle the -propertyfile argument.
1332
     *
1333
     * @throws ConfigurationException
1334
     * @throws IOException
1335
     */
1336
    private function handleArgPropertyFile(array $args, int $pos): int
1337
    {
1338
        if (!isset($args[$pos + 1])) {
1339
            throw new ConfigurationException('You must specify a filename when using the -propertyfile argument');
1340
        }
1341
1342
        $this->propertyFiles[] = $args[++$pos];
1343
1344
        return $pos;
1345
    }
1346
1347
    /**
1348
     * Search parent directories for the build file.
1349
     *
1350
     * Takes the given target as a suffix to append to each
1351
     * parent directory in search of a build file.  Once the
1352
     * root of the file-system has been reached an exception
1353
     * is thrown.
1354
     *
1355
     * @param string $start  start file path
1356
     * @param string $suffix suffix filename to look for in parents
1357
     *
1358
     * @throws ConfigurationException
1359
     *
1360
     * @return File A handle to the build file
1361
     */
1362
    private function findBuildFile($start, $suffix)
1363
    {
1364
        if (self::getMsgOutputLevel() >= Project::MSG_INFO) {
1365
            self::$out->write('Searching for ' . $suffix . ' ...' . PHP_EOL);
1366
        }
1367
1368
        $parent = new File((new File($start))->getAbsolutePath());
1369
        $file = new File($parent, $suffix);
1370
1371
        // check if the target file exists in the current directory
1372
        while (!$file->exists()) {
1373
            // change to parent directory
1374
            $parent = $parent->getParentFile();
1375
1376
            // if parent is null, then we are at the root of the fs,
1377
            // complain that we can't find the build file.
1378
            if (null === $parent) {
1379
                throw new ConfigurationException('Could not locate a build file!');
1380
            }
1381
            // refresh our file handle
1382
            $file = new File($parent, $suffix);
1383
        }
1384
1385
        return $file;
1386
    }
1387
1388
    /**
1389
     * @throws IOException
1390
     */
1391
    private function loadPropertyFiles(): void
1392
    {
1393
        foreach ($this->propertyFiles as $filename) {
1394
            $fileParserFactory = new FileParserFactory();
1395
            $fileParser = $fileParserFactory->createParser(pathinfo($filename, PATHINFO_EXTENSION));
1396
            $p = new Properties(null, $fileParser);
1397
1398
            try {
1399
                $p->load(new File($filename));
1400
            } catch (IOException $e) {
1401
                self::$out->write('Could not load property file ' . $filename . ': ' . $e->getMessage());
1402
            }
1403
            foreach ($p->getProperties() as $prop => $value) {
1404
                self::$definedProps->setProperty($prop, $value);
1405
            }
1406
        }
1407
    }
1408
1409
    /**
1410
     * Close logfiles, if we have been writing to them.
1411
     *
1412
     * @since Phing 2.3.0
1413
     */
1414
    private static function handleLogfile(): void
1415
    {
1416
        if (self::$isLogFileUsed) {
1417
            self::$err->close();
1418
            self::$out->close();
1419
        }
1420
    }
1421
1422
    /**
1423
     * Sets the stdout and stderr streams if they are not already set.
1424
     */
1425 1
    private static function initializeOutputStreams()
1426
    {
1427 1
        if (null === self::$out) {
1428
            if (!defined('STDOUT')) {
1429
                self::$out = new OutputStream(fopen('php://stdout', 'w'));
1430
            } else {
1431
                self::$out = new OutputStream(STDOUT);
1432
            }
1433
        }
1434 1
        if (null === self::$err) {
1435
            if (!defined('STDERR')) {
1436
                self::$err = new OutputStream(fopen('php://stderr', 'w'));
1437
            } else {
1438
                self::$err = new OutputStream(STDERR);
1439
            }
1440
        }
1441
    }
1442
1443
    /**
1444
     * Restores [most] PHP INI values to their pre-Phing state.
1445
     *
1446
     * Currently the following settings are not restored:
1447
     *  - max_execution_time (because getting current time limit is not possible)
1448
     *  - memory_limit (which may have been increased by Phing)
1449
     */
1450 1
    private static function restoreIni(): void
1451
    {
1452 1
        foreach (self::$origIniSettings as $settingName => $settingValue) {
1453
            switch ($settingName) {
1454 1
                case 'error_reporting':
1455 1
                    error_reporting($settingValue);
1456
1457 1
                    break;
1458
1459
                default:
1460 1
                    ini_set($settingName, $settingValue);
1461
            }
1462
        }
1463
    }
1464
1465
    /**
1466
     * Bind any registered build listeners to this project.
1467
     *
1468
     * This means adding the logger and any build listeners that were specified
1469
     * with -listener arg.
1470
     *
1471
     * @throws BuildException
1472
     * @throws ConfigurationException
1473
     */
1474
    private function addBuildListeners(Project $project)
1475
    {
1476
        // Add the default listener
1477
        $project->addBuildListener($this->createLogger());
1478
1479
        foreach ($this->listeners as $listenerClassname) {
1480
            try {
1481
                $clz = Phing::import($listenerClassname);
1482
            } catch (Exception $e) {
1483
                $msg = 'Unable to instantiate specified listener '
1484
                    . 'class ' . $listenerClassname . ' : '
1485
                    . $e->getMessage();
1486
1487
                throw new ConfigurationException($msg);
1488
            }
1489
1490
            $listener = new $clz();
1491
1492
            if ($listener instanceof StreamRequiredBuildLogger) {
1493
                throw new ConfigurationException('Unable to add ' . $listenerClassname . ' as a listener, since it requires explicit error/output streams. (You can specify it as a -logger.)');
1494
            }
1495
            $project->addBuildListener($listener);
1496
        }
1497
    }
1498
1499
    /**
1500
     * Creates the default build logger for sending build events to the log.
1501
     *
1502
     * @throws BuildException
1503
     *
1504
     * @return BuildLogger The created Logger
1505
     */
1506
    private function createLogger()
1507
    {
1508
        if ($this->silent) {
1509
            $logger = new SilentLogger();
1510
            self::$msgOutputLevel = Project::MSG_WARN;
1511
        } elseif (null !== $this->loggerClassname) {
1512
            self::import($this->loggerClassname);
1513
            // get class name part
1514
            $classname = self::import($this->loggerClassname);
1515
            $logger = new $classname();
1516
            if (!($logger instanceof BuildLogger)) {
1517
                throw new BuildException($classname . ' does not implement the BuildLogger interface.');
1518
            }
1519
        } else {
1520
            $logger = new DefaultLogger();
1521
        }
1522
        $logger->setMessageOutputLevel(self::$msgOutputLevel);
1523
        $logger->setOutputStream(self::$out);
1524
        $logger->setErrorStream(self::$err);
1525
        $logger->setEmacsMode($this->emacsMode);
1526
1527
        return $logger;
1528
    }
1529
1530
    /**
1531
     * Creates the InputHandler and adds it to the project.
1532
     *
1533
     * @param Project $project the project instance
1534
     *
1535
     * @throws ConfigurationException
1536
     */
1537
    private function addInputHandler(Project $project): void
1538
    {
1539
        if (null === $this->inputHandlerClassname) {
1540
            $handler = new ConsoleInputHandler(STDIN, new ConsoleOutput());
1541
        } else {
1542
            try {
1543
                $clz = Phing::import($this->inputHandlerClassname);
1544
                $handler = new $clz();
1545
                if (null !== $project && method_exists($handler, 'setProject')) {
1546
                    $handler->setProject($project);
1547
                }
1548
            } catch (Exception $e) {
1549
                $msg = 'Unable to instantiate specified input handler '
1550
                    . 'class ' . $this->inputHandlerClassname . ' : '
1551
                    . $e->getMessage();
1552
1553
                throw new ConfigurationException($msg);
1554
            }
1555
        }
1556
        $project->setInputHandler($handler);
1557
    }
1558
1559
    /**
1560
     * @param string $version
1561
     *
1562
     * @throws BuildException
1563
     * @throws ConfigurationException
1564
     */
1565
    private function comparePhingVersion($version): void
1566
    {
1567
        $current = self::getPhingShortVersion();
1568
1569
        // make sure that version checks are not applied to trunk
1570
        if ('dev' === $current) {
1571
            return;
1572
        }
1573
1574
        if (-1 == version_compare($current, $version)) {
1575
            throw new BuildException(
1576
                sprintf('Incompatible Phing version (%s). Version "%s" required.', $current, $version)
1577
            );
1578
        }
1579
    }
1580
1581
    /**
1582
     * Returns a formatted list of target names with an optional description.
1583
     *
1584
     * @param string   $title   Title for this list
1585
     * @param Target[] $targets Targets in this list
1586
     * @param int      $padding Padding for name column
1587
     */
1588 1
    private function generateTargetList(string $title, array $targets, int $padding): string
1589
    {
1590 1
        usort($targets, function (Target $a, Target $b) {
1591
            return $a->getName() <=> $b->getName();
1592 1
        });
1593
1594 1
        $header = <<<HEADER
1595 1
            {$title}
1596
            -------------------------------------------------------------------------------
1597
1598 1
            HEADER;
1599
1600 1
        $getDetails = function (Target $target) use ($padding): string {
1601
            $details = [];
1602
            if (!empty($target->getDescription())) {
1603
                $details[] = $target->getDescription();
1604
            }
1605
            if (!empty($target->getDependencies())) {
1606
                $details[] = ' - depends on: ' . implode(', ', $target->getDependencies());
1607
            }
1608
            if (!empty($target->getIf())) {
1609
                $details[] = ' - if property: ' . $target->getIf();
1610
            }
1611
            if (!empty($target->getUnless())) {
1612
                $details[] = ' - unless property: ' . $target->getUnless();
1613
            }
1614
            $detailsToString = function (?string $name, ?string $detail) use ($padding): string {
1615
                return sprintf(" %-{$padding}s  %s", $name, $detail);
1616
            };
1617
1618
            return implode(PHP_EOL, array_map($detailsToString, [$target->getName()], $details));
1619 1
        };
1620
1621 1
        return $header . implode(PHP_EOL, array_map($getDetails, $targets)) . PHP_EOL;
1622
    }
1623
1624
    /**
1625
     * Set System constants which can be retrieved by calling Phing::getProperty($propName).
1626
     */
1627 1
    private static function setSystemConstants(): void
1628
    {
1629
        /*
1630
         * PHP_OS returns on
1631
         *   WindowsNT4.0sp6  => WINNT
1632
         *   Windows2000      => WINNT
1633
         *   Windows ME       => WIN32
1634
         *   Windows 98SE     => WIN32
1635
         *   FreeBSD 4.5p7    => FreeBSD
1636
         *   Redhat Linux     => Linux
1637
         *   Mac OS X         => Darwin
1638
         */
1639 1
        self::setProperty('host.os', PHP_OS);
1640
1641
        // this is used by some tasks too
1642 1
        self::setProperty('os.name', PHP_OS);
1643
1644
        // it's still possible this won't be defined,
1645
        // e.g. if Phing is being included in another app w/o
1646
        // using the phing.php script.
1647 1
        if (!defined('PHP_CLASSPATH')) {
1648
            define('PHP_CLASSPATH', get_include_path());
1649
        }
1650
1651 1
        self::setProperty('php.classpath', PHP_CLASSPATH);
1652
1653
        // try to determine the host filesystem and set system property
1654
        // used by Fileself::getFileSystem to instantiate the correct
1655
        // abstraction layer
1656
1657 1
        if (PHP_OS_FAMILY === 'Windows') {
1658
            self::setProperty('host.fstype', 'WINDOWS');
1659
            self::setProperty('user.home', getenv('HOMEDRIVE') . getenv('HOMEPATH'));
1660
        } else {
1661 1
            self::setProperty('host.fstype', 'UNIX');
1662 1
            self::setProperty('user.home', getenv('HOME'));
1663
        }
1664 1
        self::setProperty(self::PHP_INTERPRETER, PHP_BINARY);
1665 1
        self::setProperty('file.separator', FileUtils::getSeparator());
1666 1
        self::setProperty('line.separator', PHP_EOL);
1667 1
        self::setProperty('path.separator', FileUtils::getPathSeparator());
1668 1
        self::setProperty(self::PHP_VERSION, PHP_VERSION);
1669 1
        self::setProperty('php.tmpdir', sys_get_temp_dir());
1670 1
        self::setProperty('application.startdir', getcwd());
1671 1
        self::setProperty('phing.startTime', gmdate(\DateTimeInterface::RFC2822));
1672
1673
        // try to detect machine dependent information
1674 1
        $sysInfo = [];
1675 1
        if (function_exists('posix_uname') && 0 !== stripos(PHP_OS, 'WIN')) {
1676 1
            $sysInfo = posix_uname();
1677
        } else {
1678
            $sysInfo['nodename'] = php_uname('n');
1679
            $sysInfo['machine'] = php_uname('m');
1680
            //this is a not so ideal substition, but maybe better than nothing
1681
            $sysInfo['domain'] = $_SERVER['SERVER_NAME'] ?? 'unknown';
1682
            $sysInfo['release'] = php_uname('r');
1683
            $sysInfo['version'] = php_uname('v');
1684
        }
1685
1686 1
        self::setProperty('host.name', $sysInfo['nodename'] ?? 'unknown');
1687 1
        self::setProperty('host.arch', $sysInfo['machine'] ?? 'unknown');
1688 1
        self::setProperty('host.domain', $sysInfo['domain'] ?? 'unknown');
1689 1
        self::setProperty('host.os.release', $sysInfo['release'] ?? 'unknown');
1690 1
        self::setProperty('host.os.version', $sysInfo['version'] ?? 'unknown');
1691 1
        unset($sysInfo);
1692
    }
1693
1694
    /**
1695
     * Sets the include path to PHP_CLASSPATH constant (if this has been defined).
1696
     *
1697
     * @throws ConfigurationException - if the include_path could not be set (for some bizarre reason)
1698
     */
1699 1
    private static function setIncludePaths(): void
1700
    {
1701 1
        if (defined('PHP_CLASSPATH')) {
1702 1
            $result = set_include_path(PHP_CLASSPATH);
1703 1
            if (false === $result) {
1704
                throw new ConfigurationException('Could not set PHP include_path.');
1705
            }
1706 1
            self::$origIniSettings['include_path'] = $result; // save original value for setting back later
1707
        }
1708
    }
1709
1710
    /**
1711
     * Sets PHP INI values that Phing needs.
1712
     */
1713 1
    private static function setIni(): void
1714
    {
1715 1
        self::$origIniSettings['error_reporting'] = error_reporting(E_ALL);
1716
1717
        // We won't bother storing original max_execution_time, since 1) the value in
1718
        // php.ini may be wrong (and there's no way to get the current value) and
1719
        // 2) it would mean something very strange to set it to a value less than time script
1720
        // has already been running, which would be the likely change.
1721
1722 1
        set_time_limit(0);
1723
1724 1
        self::$origIniSettings['short_open_tag'] = ini_set('short_open_tag', 'off');
1725 1
        self::$origIniSettings['default_charset'] = ini_set('default_charset', 'iso-8859-1');
1726
1727 1
        $mem_limit = (int) SizeHelper::fromHumanToBytes(ini_get('memory_limit'));
1728 1
        if ($mem_limit < (32 * 1024 * 1024) && $mem_limit > -1) {
1729
            // We do *not* need to save the original value here, since we don't plan to restore
1730
            // this after shutdown (we don't trust the effectiveness of PHP's garbage collection).
1731
            ini_set('memory_limit', '32M'); // nore: this may need to be higher for many projects
1732
        }
1733
    }
1734
}
1735