Issues (557)

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/Project.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\Input\InputHandler;
26
use Phing\Io\File;
27
use Phing\Io\FileSystem;
28
use Phing\Io\FileUtils;
29
use Phing\Io\IOException;
30
use Phing\Listener\BuildEvent;
31
use Phing\Listener\BuildListener;
32
use Phing\Parser\ProjectConfigurator;
33
use Phing\Task\System\Condition\Condition;
34
use Phing\Type\Description;
35
use Phing\Type\PropertyValue;
36
use Phing\Util\StringHelper;
37
use ReflectionException;
38
use ReflectionObject;
39
40
/**
41
 *  The Phing project class. Represents a completely configured Phing project.
42
 *  The class defines the project and all tasks/targets. It also contains
43
 *  methods to start a build as well as some properties and FileSystem
44
 *  abstraction.
45
 *
46
 * @author Andreas Aderhold <[email protected]>
47
 * @author Hans Lellelid <[email protected]>
48
 */
49
class Project
50
{
51
    // Logging level constants.
52
    public const MSG_DEBUG = 4;
53
    public const MSG_VERBOSE = 3;
54
    public const MSG_INFO = 2;
55
    public const MSG_WARN = 1;
56
    public const MSG_ERR = 0;
57
58
    /**
59
     * contains the targets.
60
     *
61
     * @var Target[]
62
     */
63
    private $targets = [];
64
    /**
65
     * global filterset (future use).
66
     */
67
    private $globalFilterSet = [];
68
    /**
69
     * all globals filters (future use).
70
     */
71
    private $globalFilters = [];
0 ignored issues
show
The private property $globalFilters is not used, and could be removed.
Loading history...
72
73
    /**
74
     * holds ref names and a reference to the referred object.
75
     */
76
    private $references = [];
77
78
    /**
79
     * The InputHandler being used by this project.
80
     *
81
     * @var InputHandler
82
     */
83
    private $inputHandler;
84
85
    // -- properties that come in via xml attributes --
86
87
    /**
88
     * basedir (PhingFile object).
89
     */
90
    private $basedir;
91
92
    /**
93
     * the default target name.
94
     */
95
    private $defaultTarget = 'all';
96
97
    /**
98
     * project name (required).
99
     */
100
    private $name;
101
102
    /**
103
     * project description.
104
     */
105
    private $description;
106
107
    /**
108
     * require phing version.
109
     */
110
    private $phingVersion;
111
112
    /**
113
     * project strict mode.
114
     */
115
    private $strictMode = false;
116
117
    /**
118
     * a FileUtils object.
119
     */
120
    private $fileUtils;
121
122
    /**
123
     * Build listeneers.
124
     */
125
    private $listeners = [];
126
127
    /**
128
     * Keep going flag.
129
     */
130
    private $keepGoingMode = false;
131
132
    /**
133
     * @var string[]
134
     */
135
    private $executedTargetNames = [];
136
137
    /**
138
     *  Constructor, sets any default vars.
139
     */
140 958
    public function __construct()
141
    {
142 958
        $this->fileUtils = new FileUtils();
143
    }
144
145
    /**
146
     * Sets the input handler.
147
     *
148
     * @param InputHandler $handler
149
     */
150 37
    public function setInputHandler($handler)
151
    {
152 37
        $this->inputHandler = $handler;
153
    }
154
155
    /**
156
     * Retrieves the current input handler.
157
     *
158
     * @return InputHandler
159
     */
160 37
    public function getInputHandler()
161
    {
162 37
        return $this->inputHandler;
163
    }
164
165
    /**
166
     * inits the project, called from main app.
167
     */
168 906
    public function init()
169
    {
170
        // set builtin properties
171 906
        $this->setSystemProperties();
172
173 906
        $componentHelper = ComponentHelper::getComponentHelper($this);
174
175 906
        $componentHelper->initDefaultDefinitions();
176
    }
177
178
    /**
179
     * Create and initialize a subproject. By default the subproject will be of
180
     * the same type as its parent. If a no-arg constructor is unavailable, the
181
     * <code>Project</code> class will be used.
182
     *
183
     * @return Project instance configured as a subproject of this Project
184
     */
185 40
    public function createSubProject(): Project
186
    {
187
        try {
188 40
            $ref = new ReflectionObject($this);
189 40
            $subProject = $ref->newInstance();
190
        } catch (ReflectionException $e) {
191
            $subProject = new Project();
192
        }
193 40
        $this->initSubProject($subProject);
194
195 40
        return $subProject;
196
    }
197
198
    /**
199
     * Initialize a subproject.
200
     *
201
     * @param Project $subProject the subproject to initialize
202
     */
203 40
    public function initSubProject(Project $subProject): void
204
    {
205 40
        ComponentHelper::getComponentHelper($subProject)
206 40
            ->initSubProject(ComponentHelper::getComponentHelper($this))
207 40
        ;
208 40
        $subProject->setKeepGoingMode($this->isKeepGoingMode());
209 40
        $subProject->setStrictMode($this->strictMode);
210
    }
211
212
    /**
213
     * returns the global filterset (future use).
214
     */
215
    public function getGlobalFilterSet()
216
    {
217
        return $this->globalFilterSet;
218
    }
219
220
    // ---------------------------------------------------------
221
    // Property methods
222
    // ---------------------------------------------------------
223
224
    /**
225
     * Sets a property. Any existing property of the same name
226
     * is overwritten, unless it is a user property.
227
     *
228
     * @param string $name  The name of property to set.
229
     *                      Must not be <code>null</code>.
230
     * @param string $value The new value of the property.
231
     *                      Must not be <code>null</code>.
232
     */
233 258
    public function setProperty(string $name, $value): void
234
    {
235 258
        PropertyHelper::getPropertyHelper($this)->setProperty(null, $name, $value, true);
236
    }
237
238
    /**
239
     * Sets a property if no value currently exists. If the property
240
     * exists already, a message is logged and the method returns with
241
     * no other effect.
242
     *
243
     * @param string $name  The name of property to set.
244
     *                      Must not be <code>null</code>.
245
     * @param string $value The new value of the property.
246
     *                      Must not be <code>null</code>.
247
     *
248
     * @since 2.0
249
     */
250 106
    public function setNewProperty(string $name, $value): void
251
    {
252 106
        PropertyHelper::getPropertyHelper($this)->setNewProperty(null, $name, $value);
253
    }
254
255
    /**
256
     * Sets a user property, which cannot be overwritten by
257
     * set/unset property calls. Any previous value is overwritten.
258
     *
259
     * @param string $name  The name of property to set.
260
     *                      Must not be <code>null</code>.
261
     * @param string $value The new value of the property.
262
     *                      Must not be <code>null</code>.
263
     *
264
     * @see   setProperty()
265
     */
266 906
    public function setUserProperty(string $name, $value): void
267
    {
268 906
        PropertyHelper::getPropertyHelper($this)->setUserProperty(null, $name, $value);
269
    }
270
271
    /**
272
     * Sets a user property, which cannot be overwritten by set/unset
273
     * property calls. Any previous value is overwritten. Also marks
274
     * these properties as properties that have not come from the
275
     * command line.
276
     *
277
     * @param string $name  The name of property to set.
278
     *                      Must not be <code>null</code>.
279
     * @param string $value The new value of the property.
280
     *                      Must not be <code>null</code>.
281
     *
282
     * @see   setProperty()
283
     */
284 14
    public function setInheritedProperty(string $name, $value): void
285
    {
286 14
        PropertyHelper::getPropertyHelper($this)->setInheritedProperty(null, $name, $value);
287
    }
288
289
    /**
290
     * Returns the value of a property, if it is set.
291
     *
292
     * @param null|string $name The name of the property.
293
     *                          May be <code>null</code>, in which case
294
     *                          the return value is also <code>null</code>.
295
     *
296
     * @return mixed the property value, or <code>null</code> for no match
297
     *               or if a <code>null</code> name is provided
298
     */
299 906
    public function getProperty(?string $name)
300
    {
301 906
        return PropertyHelper::getPropertyHelper($this)->getProperty(null, $name);
302
    }
303
304
    /**
305
     * Replaces ${} style constructions in the given value with the
306
     * string value of the corresponding data types.
307
     *
308
     * @param string $value The value string to be scanned for property references.
309
     *                      May be <code>null</code>.
310
     *
311
     * @throws BuildException if the given value has an unclosed
312
     *                        property name, e.g. <code>${xxx</code>
313
     *
314
     * @return string the given string with embedded property names replaced
315
     *                by values, or <code>null</code> if the given string is
316
     *                <code>null</code>
317
     */
318 762
    public function replaceProperties($value)
319
    {
320 762
        return PropertyHelper::getPropertyHelper($this)->replaceProperties($value, $this->getProperties());
321
    }
322
323
    /**
324
     * Returns the value of a user property, if it is set.
325
     *
326
     * @param string $name The name of the property.
327
     *                     May be <code>null</code>, in which case
328
     *                     the return value is also <code>null</code>.
329
     *
330
     * @return string the property value, or <code>null</code> for no match
331
     *                or if a <code>null</code> name is provided
332
     */
333 1
    public function getUserProperty($name)
334
    {
335 1
        return PropertyHelper::getPropertyHelper($this)->getUserProperty(null, $name);
336
    }
337
338
    /**
339
     * Returns a copy of the properties table.
340
     *
341
     * @return array a hashtable containing all properties
342
     *               (including user properties)
343
     */
344 907
    public function getProperties()
345
    {
346 907
        return PropertyHelper::getPropertyHelper($this)->getProperties();
347
    }
348
349
    /**
350
     * Returns a copy of the user property hashtable.
351
     *
352
     * @return array a hashtable containing just the user properties
353
     */
354
    public function getUserProperties()
355
    {
356
        return PropertyHelper::getPropertyHelper($this)->getUserProperties();
357
    }
358
359
    public function getInheritedProperties()
360
    {
361
        return PropertyHelper::getPropertyHelper($this)->getInheritedProperties();
362
    }
363
364
    /**
365
     * Copies all user properties that have been set on the command
366
     * line or a GUI tool from this instance to the Project instance
367
     * given as the argument.
368
     *
369
     * <p>To copy all "user" properties, you will also have to call
370
     * {@link #copyInheritedProperties copyInheritedProperties}.</p>
371
     *
372
     * @param Project $other the project to copy the properties to.  Must not be null.
373
     *
374
     * @since  phing 2.0
375
     */
376 37
    public function copyUserProperties(Project $other)
377
    {
378 37
        PropertyHelper::getPropertyHelper($this)->copyUserProperties($other);
379
    }
380
381
    /**
382
     * Copies all user properties that have not been set on the
383
     * command line or a GUI tool from this instance to the Project
384
     * instance given as the argument.
385
     *
386
     * <p>To copy all "user" properties, you will also have to call
387
     * {@link #copyUserProperties copyUserProperties}.</p>
388
     *
389
     * @param Project $other the project to copy the properties to.  Must not be null.
390
     *
391
     * @since phing 2.0
392
     */
393 37
    public function copyInheritedProperties(Project $other)
394
    {
395 37
        PropertyHelper::getPropertyHelper($this)->copyUserProperties($other);
396
    }
397
398
    // ---------------------------------------------------------
399
    //  END Properties methods
400
    // ---------------------------------------------------------
401
402
    /**
403
     * Sets default target.
404
     *
405
     * @param string $targetName
406
     */
407 906
    public function setDefaultTarget($targetName)
408
    {
409 906
        $this->defaultTarget = (string) trim($targetName);
410
    }
411
412
    /**
413
     * Returns default target.
414
     *
415
     * @return string
416
     */
417 37
    public function getDefaultTarget()
418
    {
419 37
        return (string) $this->defaultTarget;
420
    }
421
422
    /**
423
     * Sets the name of the current project.
424
     *
425
     * @param string $name name of project
426
     *
427
     * @author Andreas Aderhold, [email protected]
428
     */
429 906
    public function setName($name)
430
    {
431 906
        $this->name = (string) trim($name);
432 906
        $this->setUserProperty('phing.project.name', $this->name);
433
    }
434
435
    /**
436
     * Returns the name of this project.
437
     *
438
     * @return string projectname
439
     *
440
     * @author Andreas Aderhold, [email protected]
441
     */
442 5
    public function getName()
443
    {
444 5
        return (string) $this->name;
445
    }
446
447
    /**
448
     * Set the projects description.
449
     *
450
     * @param string $description
451
     */
452 6
    public function setDescription($description)
453
    {
454 6
        $this->description = $description;
455
    }
456
457
    /**
458
     * return the description, null otherwise.
459
     *
460
     * @return null|string
461
     */
462 11
    public function getDescription()
463
    {
464 11
        if (null === $this->description) {
465 10
            $this->description = Description::getAll($this);
466
        }
467
468 11
        return $this->description;
469
    }
470
471
    /**
472
     * Set the minimum required phing version.
473
     *
474
     * @param string $version
475
     */
476 5
    public function setPhingVersion($version)
477
    {
478 5
        $version = str_replace('phing', '', strtolower($version));
479 5
        $this->phingVersion = (string) trim($version);
480
    }
481
482
    /**
483
     * Get the minimum required phing version.
484
     *
485
     * @return string
486
     */
487 5
    public function getPhingVersion()
488
    {
489 5
        if (null === $this->phingVersion) {
490 5
            $this->setPhingVersion(Phing::getPhingVersion());
491
        }
492
493 5
        return $this->phingVersion;
494
    }
495
496
    /**
497
     * Sets the strict-mode (status) for the current project
498
     * (If strict mode is On, all the warnings would be converted to an error
499
     * (and the build will be stopped/aborted).
500
     *
501
     * @author Utsav Handa, [email protected]
502
     */
503 40
    public function setStrictMode(bool $strictmode)
504
    {
505 40
        $this->strictMode = $strictmode;
506 40
        $this->setProperty('phing.project.strictmode', $this->strictMode);
507
    }
508
509
    /**
510
     * Get the strict-mode status for the project.
511
     *
512
     * @return bool
513
     */
514
    public function getStrictmode()
515
    {
516
        return $this->strictMode;
517
    }
518
519
    /**
520
     * Set basedir object from xm.
521
     *
522
     * @param File|string $dir
523
     *
524
     * @throws BuildException
525
     */
526 925
    public function setBasedir($dir)
527
    {
528 925
        if ($dir instanceof File) {
529 108
            $dir = $dir->getAbsolutePath();
530
        }
531
532 925
        $dir = $this->fileUtils->normalize($dir);
533 925
        $dir = FileSystem::getFileSystem()->canonicalize($dir);
534
535 925
        $dir = new File((string) $dir);
536 925
        if (!$dir->exists()) {
537
            throw new BuildException('Basedir ' . $dir->getAbsolutePath() . ' does not exist');
538
        }
539 925
        if (!$dir->isDirectory()) {
540
            throw new BuildException('Basedir ' . $dir->getAbsolutePath() . ' is not a directory');
541
        }
542 925
        $this->basedir = $dir;
543 925
        $this->setPropertyInternal('project.basedir', $this->basedir->getPath());
544 925
        $this->log('Project base dir set to: ' . $this->basedir->getPath(), Project::MSG_VERBOSE);
545
546
        // [HL] added this so that ./ files resolve correctly.  This may be a mistake ... or may be in wrong place.
547 925
        chdir($dir->getAbsolutePath());
548
    }
549
550
    /**
551
     * Returns the basedir of this project.
552
     *
553
     * @throws BuildException
554
     *
555
     * @return File Basedir PhingFile object
556
     *
557
     * @author Andreas Aderhold, [email protected]
558
     */
559 191
    public function getBasedir()
560
    {
561 191
        if (null === $this->basedir) {
562
            try { // try to set it
563
                $this->setBasedir('.');
564
            } catch (BuildException $exc) {
565
                throw new BuildException('Can not set default basedir. ' . $exc->getMessage());
566
            }
567
        }
568
569 191
        return $this->basedir;
570
    }
571
572
    /**
573
     * Set &quot;keep-going&quot; mode. In this mode Ant will try to execute
574
     * as many targets as possible. All targets that do not depend
575
     * on failed target(s) will be executed.  If the keepGoing settor/getter
576
     * methods are used in conjunction with the <code>ant.executor.class</code>
577
     * property, they will have no effect.
578
     *
579
     * @param bool $keepGoingMode &quot;keep-going&quot; mode
580
     */
581 40
    public function setKeepGoingMode($keepGoingMode)
582
    {
583 40
        $this->keepGoingMode = $keepGoingMode;
584
    }
585
586
    /**
587
     * Return the keep-going mode.  If the keepGoing settor/getter
588
     * methods are used in conjunction with the <code>phing.executor.class</code>
589
     * property, they will have no effect.
590
     *
591
     * @return bool &quot;keep-going&quot; mode
592
     */
593 40
    public function isKeepGoingMode()
594
    {
595 40
        return $this->keepGoingMode;
596
    }
597
598
    /**
599
     * Sets system properties and the environment variables for this project.
600
     */
601 906
    public function setSystemProperties()
602
    {
603
        // first get system properties
604 906
        $systemP = array_merge($this->getProperties(), Phing::getProperties());
605 906
        foreach ($systemP as $name => $value) {
606 906
            $this->setPropertyInternal($name, $value);
607
        }
608
609
        // and now the env vars
610 906
        foreach ($_SERVER as $name => $value) {
611
            // skip arrays
612 906
            if (is_array($value)) {
613 906
                continue;
614
            }
615 906
            $this->setPropertyInternal('env.' . $name, $value);
616
        }
617
    }
618
619
    /**
620
     * Adds a task definition.
621
     *
622
     * @param string $name      name of tag
623
     * @param string $class     the class path to use
624
     * @param string $classpath the classpat to use
625
     */
626 81
    public function addTaskDefinition($name, $class, $classpath = null)
627
    {
628 81
        ComponentHelper::getComponentHelper($this)->addTaskDefinition($name, $class, $classpath);
629
    }
630
631
    /**
632
     * Returns the task definitions.
633
     *
634
     * @return array
635
     */
636 50
    public function getTaskDefinitions()
637
    {
638 50
        return ComponentHelper::getComponentHelper($this)->getTaskDefinitions();
639
    }
640
641
    /**
642
     * Adds a data type definition.
643
     *
644
     * @param string $typeName  name of the type
645
     * @param string $typeClass the class to use
646
     * @param string $classpath the classpath to use
647
     */
648 44
    public function addDataTypeDefinition($typeName, $typeClass, $classpath = null)
649
    {
650 44
        ComponentHelper::getComponentHelper($this)->addDataTypeDefinition($typeName, $typeClass, $classpath);
651
    }
652
653
    /**
654
     * Returns the data type definitions.
655
     *
656
     * @return array
657
     */
658 907
    public function getDataTypeDefinitions()
659
    {
660 907
        return ComponentHelper::getComponentHelper($this)->getDataTypeDefinitions();
661
    }
662
663
    /**
664
     * Add a new target to the project.
665
     *
666
     * @param string $targetName
667
     * @param Target $target
668
     *
669
     * @throws BuildException
670
     */
671 906
    public function addTarget($targetName, $target)
672
    {
673 906
        if (isset($this->targets[$targetName])) {
674
            throw new BuildException("Duplicate target: {$targetName}");
675
        }
676 906
        $this->addOrReplaceTarget($targetName, $target);
677
    }
678
679
    /**
680
     * Adds or replaces a target in the project.
681
     *
682
     * @param string $targetName
683
     * @param Target $target
684
     */
685 906
    public function addOrReplaceTarget($targetName, &$target)
686
    {
687 906
        $this->log("  +Target: {$targetName}", Project::MSG_DEBUG);
688 906
        $target->setProject($this);
689 906
        $this->targets[$targetName] = $target;
690
691 906
        $ctx = $this->getReference(ProjectConfigurator::PARSING_CONTEXT_REFERENCE);
692 906
        $current = $ctx->getCurrentTargets();
693 906
        $current[$targetName] = $target;
694
    }
695
696
    /**
697
     * Returns the available targets.
698
     *
699
     * @return Target[]
700
     */
701 906
    public function getTargets()
702
    {
703 906
        return $this->targets;
704
    }
705
706
    /**
707
     * @return string[]
708
     */
709
    public function getExecutedTargetNames()
710
    {
711
        return $this->executedTargetNames;
712
    }
713
714
    /**
715
     * Create a new task instance and return reference to it.
716
     *
717
     * @param string $taskType Task name
718
     *
719
     * @throws BuildException
720
     *
721
     * @return Task A task object
722
     */
723 767
    public function createTask($taskType)
724
    {
725 767
        return ComponentHelper::getComponentHelper($this)->createTask($taskType);
726
    }
727
728
    /**
729
     * Creates a new condition and returns the reference to it.
730
     *
731
     * @param string $conditionType
732
     *
733
     * @throws BuildException
734
     *
735
     * @return Condition
736
     */
737 1
    public function createCondition($conditionType)
738
    {
739 1
        return ComponentHelper::getComponentHelper($this)->createCondition($conditionType);
740
    }
741
742
    /**
743
     * Create a datatype instance and return reference to it
744
     * See createTask() for explanation how this works.
745
     *
746
     * @param string $typeName Type name
747
     *
748
     * @throws BuildException
749
     *                        Exception
750
     *
751
     * @return object A datatype object
752
     */
753 86
    public function createDataType($typeName)
754
    {
755 86
        return ComponentHelper::getComponentHelper($this)->createDataType($typeName);
756
    }
757
758
    /**
759
     * Executes a list of targets.
760
     *
761
     * @param array $targetNames List of target names to execute
762
     *
763
     * @throws BuildException
764
     */
765
    public function executeTargets($targetNames)
766
    {
767
        $this->executedTargetNames = $targetNames;
768
769
        foreach ($targetNames as $tname) {
770
            $this->executeTarget($tname);
771
        }
772
    }
773
774
    /**
775
     * Executes a target.
776
     *
777
     * @param string $targetName Name of Target to execute
778
     *
779
     * @throws BuildException
780
     */
781 717
    public function executeTarget($targetName)
782
    {
783
        // complain about executing void
784 717
        if (null === $targetName) {
0 ignored issues
show
The condition null === $targetName is always false.
Loading history...
785
            throw new BuildException('No target specified');
786
        }
787
788
        // invoke topological sort of the target tree and run all targets
789
        // until targetName occurs.
790 717
        $sortedTargets = $this->topoSort($targetName);
791
792 717
        $curIndex = (int) 0;
793 717
        $curTarget = null;
0 ignored issues
show
The assignment to $curTarget is dead and can be removed.
Loading history...
794 717
        $thrownException = null;
795 717
        $buildException = null;
796
        do {
797
            try {
798 717
                $curTarget = $sortedTargets[$curIndex++];
799 717
                $curTarget->performTasks();
800 180
            } catch (BuildException $exc) {
801 180
                if (!($this->keepGoingMode)) {
802 180
                    throw $exc;
803
                }
804
                $thrownException = $exc;
805
            }
806 594
            if (null != $thrownException) {
807
                if ($thrownException instanceof BuildException) {
808
                    $this->log(
809
                        "Target '" . $curTarget->getName()
810
                        . "' failed with message '"
811
                        . $thrownException->getMessage() . "'.",
812
                        Project::MSG_ERR
813
                    );
814
                    // only the first build exception is reported
815
                    if (null === $buildException) {
816
                        $buildException = $thrownException;
817
                    }
818
                } else {
819
                    $this->log(
820
                        "Target '" . $curTarget->getName()
821
                        . "' failed with message '"
822
                        . $thrownException->getMessage() . "'." . PHP_EOL
823
                        . $thrownException->getTraceAsString(),
824
                        Project::MSG_ERR
825
                    );
826
                    if (null === $buildException) {
827
                        $buildException = new BuildException($thrownException);
828
                    }
829
                }
830
            }
831 594
        } while ($curTarget->getName() !== $targetName);
832
833 594
        if (null !== $buildException) {
834
            throw $buildException;
835
        }
836
    }
837
838
    /**
839
     * Helper function.
840
     *
841
     * @param File|null $rootDir
842
     *
843
     * @throws IOException
844
     */
845 494
    public function resolveFile(string $fileName, ?File $rootDir = null): File
846
    {
847 494
        if (null === $rootDir) {
848 486
            return $this->fileUtils->resolveFile($this->basedir, $fileName);
849
        }
850
851 99
        return $this->fileUtils->resolveFile($rootDir, $fileName);
852
    }
853
854
    /**
855
     * Return the bool equivalent of a string, which is considered
856
     * <code>true</code> if either <code>"on"</code>, <code>"true"</code>,
857
     * or <code>"yes"</code> is found, ignoring case.
858
     *
859
     * @param string $s the string to convert to a bool value
860
     *
861
     * @return <code>true</code> if the given string is <code>"on"</code>,
0 ignored issues
show
Documentation Bug introduced by
The doc comment <code>true</code> at position 0 could not be parsed: Unknown type name '<' at position 0 in <code>true</code>.
Loading history...
862
     *                           <code>"true"</code> or <code>"yes"</code>, or
863
     *                           <code>false</code> otherwise
864
     *
865
     * @deprecated Use \Phing\Util\StringHelper::booleanValue instead
866
     */
867
    public static function toBoolean($s)
868
    {
869
        return StringHelper::booleanValue($s);
870
    }
871
872
    /**
873
     * Topologically sort a set of Targets.
874
     *
875
     * @param string $rootTarget is the (String) name of the root Target. The sort is
876
     *                           created in such a way that the sequence of Targets until the root
877
     *                           target is the minimum possible such sequence.
878
     *
879
     * @throws Exception
880
     * @throws BuildException
881
     *
882
     * @return Target[] targets in sorted order
883
     */
884 717
    public function topoSort($rootTarget)
885
    {
886 717
        $rootTarget = (string) $rootTarget;
887 717
        $ret = [];
888 717
        $state = [];
889 717
        $visiting = [];
890
891
        // We first run a DFS based sort using the root as the starting node.
892
        // This creates the minimum sequence of Targets to the root node.
893
        // We then do a sort on any remaining unVISITED targets.
894
        // This is unnecessary for doing our build, but it catches
895
        // circular dependencies or missing Targets on the entire
896
        // dependency tree, not just on the Targets that depend on the
897
        // build Target.
898
899 717
        $this->tsort($rootTarget, $state, $visiting, $ret);
900
901 717
        $retHuman = '';
902 717
        for ($i = 0, $_i = count($ret); $i < $_i; ++$i) {
903 717
            $retHuman .= (string) $ret[$i] . ' ';
904
        }
905 717
        $this->log("Build sequence for target '{$rootTarget}' is: {$retHuman}", Project::MSG_VERBOSE);
906
907 717
        $keys = array_keys($this->targets);
908 717
        while ($keys) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $keys 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...
909 717
            $curTargetName = (string) array_shift($keys);
910 717
            if (!isset($state[$curTargetName])) {
911 717
                $st = null;
912
            } else {
913 717
                $st = (string) $state[$curTargetName];
914
            }
915
916 717
            if (null === $st) {
917 717
                $this->tsort($curTargetName, $state, $visiting, $ret);
918 717
            } elseif ('VISITING' === $st) {
919
                throw new Exception("Unexpected node in visiting state: {$curTargetName}");
920
            }
921
        }
922
923 717
        $retHuman = '';
924 717
        for ($i = 0, $_i = count($ret); $i < $_i; ++$i) {
925 717
            $retHuman .= (string) $ret[$i] . ' ';
926
        }
927 717
        $this->log("Complete build sequence is: {$retHuman}", Project::MSG_VERBOSE);
928
929 717
        return $ret;
930
    }
931
932
    /**
933
     * Adds a reference to an object. This method is called when the parser
934
     * detects a id="foo" attribute. It passes the id as $name and a reference
935
     * to the object assigned to this id as $value.
936
     *
937
     * @param string $name
938
     * @param object $object
939
     */
940 930
    public function addReference($name, $object)
941
    {
942 930
        $ref = $this->references[$name] ?? null;
943 930
        if ($ref === $object) {
944
            return;
945
        }
946 930
        if (null !== $ref && !$ref instanceof UnknownElement) {
947 43
            $this->log("Overriding previous definition of reference to {$name}", Project::MSG_VERBOSE);
948
        }
949 930
        $refName = (is_scalar($object) || $object instanceof PropertyValue) ? (string) $object : get_class($object);
950 930
        $this->log("Adding reference: {$name} -> " . $refName, Project::MSG_DEBUG);
951 930
        $this->references[$name] = $object;
952
    }
953
954
    /**
955
     * Returns the references array.
956
     *
957
     * @return array
958
     */
959 36
    public function getReferences()
960
    {
961 36
        return $this->references;
962
    }
963
964
    /**
965
     * Returns a specific reference.
966
     *
967
     * @param string $key the reference id/key
968
     *
969
     * @return object Reference or null if not defined
970
     */
971 931
    public function getReference($key)
972
    {
973 931
        return $this->references[$key] ?? null; // just to be explicit
974
    }
975
976
    /**
977
     * Does the project know this reference?
978
     *
979
     * @param string $key the reference id/key
980
     */
981 5
    public function hasReference(string $key): bool
982
    {
983 5
        return isset($this->references[$key]);
984
    }
985
986
    /**
987
     * Abstracting and simplifyling Logger calls for project messages.
988
     *
989
     * @param string $msg
990
     * @param int    $level
991
     */
992 931
    public function log($msg, $level = Project::MSG_INFO)
993
    {
994 931
        $this->logObject($this, $msg, $level);
995
    }
996
997
    /**
998
     * @param string $msg
999
     * @param int    $level
1000
     * @param mixed  $obj
1001
     */
1002 934
    public function logObject($obj, $msg, $level, ?Exception $t = null)
1003
    {
1004 934
        $this->fireMessageLogged($obj, $msg, $level, $t);
1005
1006
        // Checking whether the strict-mode is On, then consider all the warnings
1007
        // as errors.
1008 934
        if (($this->strictMode) && (Project::MSG_WARN == $level)) {
1009
            throw new BuildException('Build contains warnings, considered as errors in strict mode', null);
1010
        }
1011
    }
1012
1013 906
    public function addBuildListener(BuildListener $listener)
1014
    {
1015 906
        $this->listeners[] = $listener;
1016
    }
1017
1018 9
    public function removeBuildListener(BuildListener $listener)
1019
    {
1020 9
        $newarray = [];
1021 9
        for ($i = 0, $size = count($this->listeners); $i < $size; ++$i) {
1022 9
            if ($this->listeners[$i] !== $listener) {
1023 9
                $newarray[] = $this->listeners[$i];
1024
            }
1025
        }
1026 9
        $this->listeners = $newarray;
1027
    }
1028
1029
    /**
1030
     * @return array
1031
     */
1032 37
    public function getBuildListeners()
1033
    {
1034 37
        return $this->listeners;
1035
    }
1036
1037
    public function fireBuildStarted()
1038
    {
1039
        $event = new BuildEvent($this);
1040
        foreach ($this->listeners as $listener) {
1041
            $listener->buildStarted($event);
1042
        }
1043
1044
        $this->log((string) $event, Project::MSG_DEBUG);
1045
    }
1046
1047
    /**
1048
     * @param Exception $exception
1049
     */
1050
    public function fireBuildFinished($exception)
1051
    {
1052
        $event = new BuildEvent($this);
1053
        $event->setException($exception);
1054
        foreach ($this->listeners as $listener) {
1055
            $listener->buildFinished($event);
1056
        }
1057
1058
        $this->log((string) $event, Project::MSG_DEBUG);
1059
    }
1060
1061
    /**
1062
     * @param $target
1063
     */
1064 717
    public function fireTargetStarted($target)
1065
    {
1066 717
        $event = new BuildEvent($target);
1067 717
        foreach ($this->listeners as $listener) {
1068 717
            $listener->targetStarted($event);
1069
        }
1070
1071 717
        $this->log((string) $event, Project::MSG_DEBUG);
1072
    }
1073
1074
    /**
1075
     * @param $target
1076
     * @param $exception
1077
     */
1078 717
    public function fireTargetFinished($target, $exception)
1079
    {
1080 717
        $event = new BuildEvent($target);
1081 717
        $event->setException($exception);
1082 717
        foreach ($this->listeners as $listener) {
1083 717
            $listener->targetFinished($event);
1084
        }
1085
1086 717
        $this->log((string) $event, Project::MSG_DEBUG);
1087
    }
1088
1089
    /**
1090
     * @param $task
1091
     */
1092 770
    public function fireTaskStarted($task)
1093
    {
1094 770
        $event = new BuildEvent($task);
1095 770
        foreach ($this->listeners as $listener) {
1096 770
            $listener->taskStarted($event);
1097
        }
1098
1099 770
        $this->log((string) $event, Project::MSG_DEBUG);
1100
    }
1101
1102
    /**
1103
     * @param $task
1104
     * @param $exception
1105
     */
1106 770
    public function fireTaskFinished($task, $exception)
1107
    {
1108 770
        $event = new BuildEvent($task);
1109 770
        $event->setException($exception);
1110 770
        foreach ($this->listeners as $listener) {
1111 770
            $listener->taskFinished($event);
1112
        }
1113
1114 770
        $this->log((string) $event, Project::MSG_DEBUG);
1115
    }
1116
1117
    /**
1118
     * @param $event
1119
     * @param $message
1120
     * @param $priority
1121
     */
1122 934
    public function fireMessageLoggedEvent(BuildEvent $event, $message, $priority)
1123
    {
1124 934
        $event->setMessage($message, $priority);
1125 934
        foreach ($this->listeners as $listener) {
1126 907
            $listener->messageLogged($event);
1127
        }
1128
    }
1129
1130
    /**
1131
     * @param string         $message
1132
     * @param int            $priority
1133
     * @param Exception|null $t
1134
     * @param mixed          $object
1135
     *
1136
     * @throws Exception
1137
     */
1138 934
    public function fireMessageLogged($object, $message, $priority, ?Exception $t = null)
1139
    {
1140 934
        $event = new BuildEvent($object);
1141 934
        if (null !== $t) {
1142
            $event->setException($t);
1143
        }
1144 934
        $this->fireMessageLoggedEvent($event, $message, $priority);
1145
    }
1146
1147
    /**
1148
     * Sets a property unless it is already defined as a user property
1149
     * (in which case the method returns silently).
1150
     *
1151
     * @param string $name  The name of the property.
1152
     *                      Must not be
1153
     *                      <code>null</code>.
1154
     * @param string $value The property value. Must not be <code>null</code>.
1155
     */
1156 925
    private function setPropertyInternal($name, $value)
1157
    {
1158 925
        PropertyHelper::getPropertyHelper($this)->setProperty(null, $name, $value, false);
1159
    }
1160
1161
    // one step in a recursive DFS traversal of the target dependency tree.
1162
    // - The array "state" contains the state (VISITED or VISITING or null)
1163
    //   of all the target names.
1164
    // - The stack "visiting" contains a stack of target names that are
1165
    //   currently on the DFS stack. (NB: the target names in "visiting" are
1166
    //    exactly the target names in "state" that are in the VISITING state.)
1167
    // 1. Set the current target to the VISITING state, and push it onto
1168
    //    the "visiting" stack.
1169
    // 2. Throw a BuildException if any child of the current node is
1170
    //    in the VISITING state (implies there is a cycle.) It uses the
1171
    //    "visiting" Stack to construct the cycle.
1172
    // 3. If any children have not been VISITED, tsort() the child.
1173
    // 4. Add the current target to the Vector "ret" after the children
1174
    //    have been visited. Move the current target to the VISITED state.
1175
    //    "ret" now contains the sorted sequence of Targets up to the current
1176
    //    Target.
1177
1178
    /**
1179
     * @param $root
1180
     * @param $state
1181
     * @param $visiting
1182
     * @param $ret
1183
     *
1184
     * @throws BuildException
1185
     * @throws Exception
1186
     */
1187 717
    private function tsort($root, &$state, &$visiting, &$ret)
1188
    {
1189 717
        $state[$root] = 'VISITING';
1190 717
        $visiting[] = $root;
1191
1192 717
        if (!isset($this->targets[$root]) || !($this->targets[$root] instanceof Target)) {
1193 3
            $target = null;
1194
        } else {
1195 717
            $target = $this->targets[$root];
1196
        }
1197
1198
        // make sure we exist
1199 717
        if (null === $target) {
1200 3
            $sb = "Target '{$root}' does not exist in this project.";
1201 3
            array_pop($visiting);
1202 3
            if (!empty($visiting)) {
1203
                $parent = (string) $visiting[count($visiting) - 1];
1204
                $sb .= " It is a dependency of target '{$parent}'.";
1205
            }
1206 3
            if ($suggestion = $this->findSuggestion($root)) {
1207 2
                $sb .= sprintf(" Did you mean '%s'?", $suggestion);
1208
            }
1209
1210 3
            throw new BuildException($sb);
1211
        }
1212
1213 717
        $deps = $target->getDependencies();
1214
1215 717
        while ($deps) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $deps 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...
1216 152
            $cur = (string) array_shift($deps);
1217 152
            if (!isset($state[$cur])) {
1218 85
                $m = null;
1219
            } else {
1220 132
                $m = (string) $state[$cur];
1221
            }
1222 152
            if (null === $m) {
1223
                // not been visited
1224 85
                $this->tsort($cur, $state, $visiting, $ret);
1225 132
            } elseif ('VISITING' == $m) {
1226
                // currently visiting this node, so have a cycle
1227
                throw $this->makeCircularException($cur, $visiting);
1228
            }
1229
        }
1230
1231 717
        $p = (string) array_pop($visiting);
1232 717
        if ($root !== $p) {
1233
            throw new Exception("Unexpected internal error: expected to pop {$root} but got {$p}");
1234
        }
1235
1236 717
        $state[$root] = 'VISITED';
1237 717
        $ret[] = $target;
1238
    }
1239
1240
    /**
1241
     * @param string $end
1242
     * @param array  $stk
1243
     *
1244
     * @return BuildException
1245
     */
1246
    private function makeCircularException($end, $stk)
1247
    {
1248
        $sb = "Circular dependency: {$end}";
1249
        do {
1250
            $c = (string) array_pop($stk);
1251
            $sb .= ' <- ' . $c;
1252
        } while ($c != $end);
1253
1254
        return new BuildException($sb);
1255
    }
1256
1257
    /**
1258
     * Finds the Target with the most similar name to function's argument.
1259
     *
1260
     * Will return null if buildfile has no targets.
1261
     *
1262
     * @see https://www.php.net/manual/en/function.levenshtein.php
1263
     *
1264
     * @param string $unknownTarget Target name
1265
     *
1266
     * @return Target
1267
     */
1268 3
    private function findSuggestion(string $unknownTarget): ?Target
1269
    {
1270 3
        return array_reduce($this->targets, function (?Target $carry, Target $current) use ($unknownTarget): ?Target {
1271
            // Omit target with empty name (there's always one)
1272 3
            if (empty(strval($current))) {
1273 3
                return $carry;
1274
            }
1275
            // $carry is null the first time
1276 2
            if (is_null($carry)) {
1277 2
                return $current;
1278
            }
1279
1280 2
            return levenshtein($unknownTarget, $carry) < levenshtein($unknownTarget, $current) ? $carry : $current;
1281 3
        });
1282
    }
1283
}
1284