Issues (557)

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

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