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/IntrospectionHelper.php (4 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\Io\File;
26
use Phing\Parser\CustomChildCreator;
27
use Phing\Parser\DynamicAttribute;
28
use Phing\Type\Path;
29
use Phing\Type\Reference;
30
use Phing\Util\Register;
31
use Phing\Util\StringHelper;
32
use ReflectionClass;
33
use ReflectionType;
34
35
/**
36
 * Helper class that collects the methods that a task or nested element
37
 * holds to set attributes, create nested elements or hold PCDATA
38
 * elements.
39
 *
40
 *<ul>
41
 * <li><strong>SMART-UP INLINE DOCS</strong></li>
42
 * <li><strong>POLISH-UP THIS CLASS</strong></li>
43
 *</ul>
44
 *
45
 * @author    Andreas Aderhold <[email protected]>
46
 * @author    Hans Lellelid <[email protected]>
47
 * @copyright 2001,2002 THYRELL. All rights reserved
48
 */
49
class IntrospectionHelper
50
{
51
    /**
52
     * Holds the attribute setter methods.
53
     *
54
     * @var array string[]
55
     */
56
    private $attributeSetters = [];
57
58
    /**
59
     * Holds methods to create nested elements.
60
     *
61
     * @var array string[]
62
     */
63
    private $nestedCreators = [];
64
65
    /**
66
     * Holds methods to store configured nested elements.
67
     *
68
     * @var array string[]
69
     */
70
    private $nestedStorers = [];
71
72
    /**
73
     * Map from attribute names to nested types.
74
     */
75
    private $nestedTypes = [];
76
77
    /**
78
     * New idea in phing: any class can register certain
79
     * keys -- e.g. "task.current_file" -- which can be used in
80
     * task attributes, if supported.  In the build XML these
81
     * are referred to like this:
82
     *         <regexp pattern="\n" replace="%{task.current_file}"/>
83
     * In the type/task a listener method must be defined:
84
     *         function setListeningReplace($slot) {}.
85
     *
86
     * @var array string[]
87
     */
88
    private $slotListeners = [];
89
90
    /**
91
     * The method to add PCDATA stuff.
92
     *
93
     * @var string Method name of the addText (redundant?) method, if class supports it :)
94
     */
95
    private $methodAddText;
96
97
    /**
98
     * The Class that's been introspected.
99
     *
100
     * @var object
101
     */
102
    private $bean;
103
104
    /**
105
     * The cache of IntrospectionHelper classes instantiated by getHelper().
106
     *
107
     * @var array IntrospectionHelpers[]
108
     */
109
    private static $helpers = [];
110
111
    /**
112
     * This function constructs a new introspection helper for a specific class.
113
     *
114
     * This method loads all methods for the specified class and categorizes them
115
     * as setters, creators, slot listeners, etc.  This way, the setAttribue() doesn't
116
     * need to perform any introspection -- either the requested attribute setter/creator
117
     * exists or it does not & a BuildException is thrown.
118
     *
119
     * @param string $class the classname for this IH
120
     *
121
     * @throws BuildException
122
     */
123 158
    public function __construct($class)
124
    {
125 158
        $this->bean = new ReflectionClass($class);
126
127
        //$methods = get_class_methods($bean);
128 158
        foreach ($this->bean->getMethods() as $method) {
129 158
            if ($method->isPublic()) {
130
                // We're going to keep case-insensitive method names
131
                // for as long as we're allowed :)  It makes it much
132
                // easier to map XML attributes to PHP class method names.
133 158
                $name = strtolower($method->getName());
134
135
                // There are a few "reserved" names that might look like attribute setters
136
                // but should actually just be skipped.  (Note: this means you can't ever
137
                // have an attribute named "location" or "tasktype" or a nested element container
138
                // named "task" [TaskContainer::addTask(Task)].)
139
                if (
140 158
                    'setlocation' === $name
141 158
                    || 'settasktype' === $name
142 158
                    || ('addtask' === $name
143 158
                        && $this->isContainer()
144 158
                        && 1 === count($method->getParameters())
145 158
                        && Task::class === $method->getParameters()[0])
146
                ) {
147 129
                    continue;
148
                }
149
150 158
                if ('addtext' === $name) {
151 18
                    $this->methodAddText = $method;
152 158
                } elseif (0 === strpos($name, 'setlistening')) {
153
                    // Phing supports something unique called "RegisterSlots"
154
                    // These are dynamic values that use a basic slot system so that
155
                    // classes can register to listen to specific slots, and the value
156
                    // will always be grabbed from the slot (and never set in the project
157
                    // component).  This is useful for things like tracking the current
158
                    // file being processed by a filter (e.g. AppendTask sets an append.current_file
159
                    // slot, which can be ready by the XSLTParam type.)
160
161 1
                    if (1 !== count($method->getParameters())) {
162
                        throw new BuildException(
163
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() must take exactly one parameter.'
164
                        );
165
                    }
166
167 1
                    $this->slotListeners[$name] = $method;
168 158
                } elseif (0 === strpos($name, 'set') && 1 === count($method->getParameters())) {
169 150
                    $this->attributeSetters[$name] = $method;
170 157
                } elseif (0 === strpos($name, 'create')) {
171 54
                    if ($method->getNumberOfRequiredParameters() > 0) {
172 1
                        throw new BuildException(
173 1
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() may not take any parameters.'
174 1
                        );
175
                    }
176
177 53
                    if ($method->hasReturnType()) {
178 13
                        $this->nestedTypes[$name] = $method->getReturnType();
179
                    } else {
180 43
                        preg_match('/@return[\s]+([\w]+)/', $method->getDocComment(), $matches);
181 43
                        if (!empty($matches[1]) && class_exists($matches[1], false)) {
182
                            $this->nestedTypes[$name] = $matches[1];
183
                        } else {
184
                            // assume that method createEquals() creates object of type "Equals"
185
                            // (that example would be false, of course)
186 43
                            $this->nestedTypes[$name] = $this->getPropertyName($name, 'create');
187
                        }
188
                    }
189
190 53
                    $this->nestedCreators[$name] = $method;
191 157
                } elseif (0 === strpos($name, 'addconfigured')) {
192
                    // *must* use class hints if using addConfigured ...
193
194
                    // 1 param only
195 2
                    $params = $method->getParameters();
196
197 2
                    if (count($params) < 1) {
198
                        throw new BuildException(
199
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() must take at least one parameter.'
200
                        );
201
                    }
202
203 2
                    if (count($params) > 1) {
204
                        $this->warn(
205
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() takes more than one parameter. (IH only uses the first)'
206
                        );
207
                    }
208
209 2
                    $classname = $this->getClassnameFromParameter($params[0]);
210
211 2
                    if (null === $classname) {
212 1
                        throw new BuildException(
213 1
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() method MUST use a class hint to indicate the class type of parameter.'
214 1
                        );
215
                    }
216
217 1
                    $this->nestedTypes[$name] = $classname;
218
219 1
                    $this->nestedStorers[$name] = $method;
220 157
                } elseif (0 === strpos($name, 'add')) {
221
                    // *must* use class hints if using add ...
222
223
                    // 1 param only
224 58
                    $params = $method->getParameters();
225 58
                    if (count($params) < 1) {
226
                        throw new BuildException(
227
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() must take at least one parameter.'
228
                        );
229
                    }
230
231 58
                    if (count($params) > 1) {
232
                        $this->warn(
233
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() takes more than one parameter. (IH only uses the first)'
234
                        );
235
                    }
236
237 58
                    $classname = $this->getClassnameFromParameter($params[0]);
238
239
                    // we don't use the classname here, but we need to make sure it exists before
240
                    // we later try to instantiate a non-existent class
241 58
                    if (null === $classname) {
242 1
                        throw new BuildException(
243 1
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() method MUST use a class hint to indicate the class type of parameter.'
244 1
                        );
245
                    }
246
247 57
                    $this->nestedCreators[$name] = $method;
248
                }
249
            } // if $method->isPublic()
250
        } // foreach
251
    }
252
253
    /**
254
     * Factory method for helper objects.
255
     *
256
     * @param string $class The class to create a Helper for
257
     *
258
     * @return IntrospectionHelper
259
     */
260 772
    public static function getHelper($class)
261
    {
262 772
        if (!isset(self::$helpers[$class])) {
263 158
            self::$helpers[$class] = new IntrospectionHelper($class);
264
        }
265
266 772
        return self::$helpers[$class];
267
    }
268
269
    /**
270
     * Indicates whether the introspected class is a task container, supporting arbitrary nested tasks/types.
271
     *
272
     * @return bool true if the introspected class is a container; false otherwise
273
     */
274 3
    public function isContainer()
275
    {
276 3
        return $this->bean->implementsInterface(TaskContainer::class);
277
    }
278
279
    /**
280
     * Sets the named attribute.
281
     *
282
     * @param object $element
283
     * @param string $attributeName
284
     * @param mixed  $value
285
     *
286
     * @throws BuildException
287
     */
288 752
    public function setAttribute(Project $project, $element, $attributeName, &$value)
289
    {
290
        // we want to check whether the value we are setting looks like
291
        // a slot-listener variable:  %{task.current_file}
292
        //
293
        // slot-listener variables are not like properties, in that they cannot be mixed with
294
        // other text values.  The reason for this disparity is that properties are only
295
        // set when first constructing objects from XML, whereas slot-listeners are always dynamic.
296
        //
297
        // This is made possible by PHP5 (objects automatically passed by reference) and PHP's loose
298
        // typing.
299 752
        if (StringHelper::isSlotVar($value)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression Phing\Util\StringHelper::isSlotVar($value) of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
300
            $as = 'setlistening' . strtolower($attributeName);
301
302
            if (!isset($this->slotListeners[$as])) {
303
                $msg = $this->getElementName(
304
                    $project,
305
                    $element
306
                ) . " doesn't support a slot-listening '{$attributeName}' attribute.";
307
308
                throw new BuildException($msg);
309
            }
310
311
            $method = $this->slotListeners[$as];
312
313
            $key = StringHelper::slotVar($value);
314
            $value = Register::getSlot(
315
                $key
316
            ); // returns a RegisterSlot object which will hold current value of that register (accessible using getValue())
317
        } else {
318
            // Traditional value options
319
320 752
            $as = 'set' . strtolower($attributeName);
321
322 752
            if (!isset($this->attributeSetters[$as])) {
323 7
                if ($element instanceof DynamicAttribute) {
324 2
                    $element->setDynamicAttribute($attributeName, (string) $value);
325
326 2
                    return;
327
                }
328 5
                $msg = $this->getElementName($project, $element) . " doesn't support the '{$attributeName}' attribute.";
329
330 5
                throw new BuildException($msg);
331
            }
332
333 752
            $method = $this->attributeSetters[$as];
334
335 752
            if ('setrefid' == $as) {
336 24
                $value = new Reference($project, $value);
337
            } else {
338 752
                $params = $method->getParameters();
339
340
                /** @var ReflectionType $hint */
341 752
                $reflectedAttr = ($hint = $params[0]->getType()) ? $hint->getName() : null;
0 ignored issues
show
The method getName() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType. ( Ignorable by Annotation )

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

341
                $reflectedAttr = ($hint = $params[0]->getType()) ? $hint->/** @scrutinizer ignore-call */ getName() : null;
Loading history...
342
343
                // value is a string representation of a bool type,
344
                // convert it to primitive
345 752
                if ('bool' === $reflectedAttr || ('string' !== $reflectedAttr && StringHelper::isBoolean($value))) {
346 281
                    $value = StringHelper::booleanValue($value);
347
                }
348
349
                // there should only be one param; we'll just assume ....
350 752
                if (null !== $reflectedAttr) {
351
                    switch ($reflectedAttr) {
352
                        case File::class:
353 354
                            $value = $project->resolveFile($value);
0 ignored issues
show
It seems like $value can also be of type boolean; however, parameter $fileName of Phing\Project::resolveFile() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

353
                            $value = $project->resolveFile(/** @scrutinizer ignore-type */ $value);
Loading history...
354
355 354
                            break;
356
357
                        case Path::class:
358 15
                            $value = new Path($project, $value);
359
360 15
                            break;
361
362
                        case Reference::class:
363
                            $value = new Reference($project, $value);
364
365
                            break;
366
                        // any other object params we want to support should go here ...
367
                    }
368
                } // if hint !== null
369
            } // if not setrefid
370
        } // if is slot-listener
371
372
        try {
373 752
            $project->log(
374 752
                '    -calling setter ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()',
375 752
                Project::MSG_DEBUG
376 752
            );
377 752
            $method->invoke($element, $value);
378 12
        } catch (Exception $exc) {
379 12
            throw new BuildException($exc->getMessage(), $exc);
380
        }
381
    }
382
383
    /**
384
     * Adds PCDATA areas.
385
     *
386
     * @param string $element
387
     * @param string $text
388
     *
389
     * @throws BuildException
390
     */
391 85
    public function addText(Project $project, $element, $text)
392
    {
393 85
        if (null === $this->methodAddText) {
394 1
            $msg = $this->getElementName($project, $element) . " doesn't support nested text data.";
0 ignored issues
show
$element of type string is incompatible with the type object expected by parameter $element of Phing\IntrospectionHelper::getElementName(). ( Ignorable by Annotation )

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

394
            $msg = $this->getElementName($project, /** @scrutinizer ignore-type */ $element) . " doesn't support nested text data.";
Loading history...
395
396 1
            throw new BuildException($msg);
397
        }
398
399
        try {
400 85
            $method = $this->methodAddText;
401 85
            $method->invoke($element, $text);
402
        } catch (Exception $exc) {
403
            throw new BuildException($exc->getMessage(), $exc);
404
        }
405
    }
406
407
    /**
408
     * Creates a named nested element.
409
     *
410
     * Valid creators can be in the form createFoo() or addFoo(Bar).
411
     *
412
     * @param object $element     Object the XML tag is child of.
413
     *                            Often a task object.
414
     * @param string $elementName XML tag name
415
     *
416
     * @throws BuildException
417
     *
418
     * @return object returns the nested element
419
     */
420 471
    public function createElement(Project $project, $element, $elementName)
421
    {
422 471
        $addMethod = 'add' . strtolower($elementName);
423 471
        $createMethod = 'create' . strtolower($elementName);
424 471
        $nestedElement = null;
425
426 471
        if (isset($this->nestedCreators[$createMethod])) {
427 367
            $method = $this->nestedCreators[$createMethod];
428
429
            try { // try to invoke the creator method on object
430 367
                $project->log(
431 367
                    '    -calling creator ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()',
432 367
                    Project::MSG_DEBUG
433 367
                );
434 367
                $nestedElement = $method->invoke($element);
435 1
            } catch (Exception $exc) {
436 1
                throw new BuildException($exc->getMessage(), $exc);
437
            }
438 368
        } elseif (isset($this->nestedCreators[$addMethod])) {
439 365
            $method = $this->nestedCreators[$addMethod];
440
441
            // project components must use class hints to support the add methods
442
443
            try { // try to invoke the adder method on object
444 365
                $project->log(
445 365
                    '    -calling adder ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()',
446 365
                    Project::MSG_DEBUG
447 365
                );
448
                // we've already assured that correct num of params
449
                // exist and that method is using class hints
450 365
                $params = $method->getParameters();
451
452 365
                $classname = $this->getClassnameFromParameter($params[0]);
453
454
                // create a new instance of the object and add it via $addMethod
455 365
                $clazz = new ReflectionClass($classname);
456 365
                if (null !== $clazz->getConstructor() && $clazz->getConstructor()->getNumberOfRequiredParameters() >= 1) {
457 6
                    $nestedElement = new $classname(Phing::getCurrentProject() ?? $project);
458
                } else {
459 364
                    $nestedElement = new $classname();
460
                }
461
462 365
                if ($nestedElement instanceof Task && $element instanceof Task) {
463 6
                    $nestedElement->setOwningTarget($element->getOwningTarget());
464
                }
465
466 365
                $method->invoke($element, $nestedElement);
467 2
            } catch (Exception $exc) {
468 2
                throw new BuildException($exc->getMessage(), $exc);
469
            }
470 4
        } elseif ($this->bean->implementsInterface(CustomChildCreator::class)) {
471 3
            $method = $this->bean->getMethod('customChildCreator');
472
473
            try {
474 3
                $nestedElement = $method->invoke($element, strtolower($elementName), $project);
475
            } catch (Exception $exc) {
476
                throw new BuildException($exc->getMessage(), $exc);
477
            }
478
        } else {
479
            //try the add method for the element's parent class
480 1
            $typedefs = $project->getDataTypeDefinitions();
481 1
            if (isset($typedefs[$elementName])) {
482
                $elementClass = Phing::import($typedefs[$elementName]);
483
                $parentClass = get_parent_class($elementClass);
484
                $addMethod = 'add' . strtolower($parentClass);
485
486
                if (isset($this->nestedCreators[$addMethod])) {
487
                    $method = $this->nestedCreators[$addMethod];
488
489
                    try {
490
                        $project->log(
491
                            '    -calling parent adder '
492
                            . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()',
493
                            Project::MSG_DEBUG
494
                        );
495
                        $nestedElement = new $elementClass();
496
                        $method->invoke($element, $nestedElement);
497
                    } catch (Exception $exc) {
498
                        throw new BuildException($exc->getMessage(), $exc);
499
                    }
500
                }
501
            }
502 1
            if (null === $nestedElement) {
503 1
                $msg = $this->getElementName($project, $element) . " doesn't support the '{$elementName}' creator/adder.";
504
505 1
                throw new BuildException($msg);
506
            }
507
        }
508
509 471
        if ($nestedElement instanceof ProjectComponent) {
510 423
            $nestedElement->setProject($project);
511
        }
512
513 471
        return $nestedElement;
514
    }
515
516
    /**
517
     * Creates a named nested element.
518
     *
519
     * @param Project     $project
520
     * @param string      $element
521
     * @param string      $child
522
     * @param null|string $elementName
523
     *
524
     * @throws BuildException
525
     */
526
    public function storeElement($project, $element, $child, $elementName = null)
527
    {
528
        if (null === $elementName) {
529
            return;
530
        }
531
532
        $storer = 'addconfigured' . strtolower($elementName);
533
534
        if (isset($this->nestedStorers[$storer])) {
535
            $method = $this->nestedStorers[$storer];
536
537
            try {
538
                $project->log(
539
                    '    -calling storer ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()',
540
                    Project::MSG_DEBUG
541
                );
542
                $method->invoke($element, $child);
543
            } catch (Exception $exc) {
544
                throw new BuildException($exc->getMessage(), $exc);
545
            }
546
        }
547
    }
548
549
    /**
550
     * Does the introspected class support PCDATA?
551
     *
552
     * @return bool
553
     */
554 1
    public function supportsCharacters()
555
    {
556 1
        return null !== $this->methodAddText;
557
    }
558
559
    /**
560
     * Return all attribues supported by the introspected class.
561
     *
562
     * @return string[]
563
     */
564 1
    public function getAttributes()
565
    {
566 1
        $attribs = [];
567 1
        foreach (array_keys($this->attributeSetters) as $setter) {
568 1
            $attribs[] = $this->getPropertyName($setter, 'set');
569
        }
570
571 1
        return $attribs;
572
    }
573
574
    /**
575
     * Return all nested elements supported by the introspected class.
576
     *
577
     * @return string[]
578
     */
579
    public function getNestedElements()
580
    {
581
        return $this->nestedTypes;
582
    }
583
584
    /**
585
     * Get the name for an element.
586
     * When possible the full classnam (phing.tasks.system.PropertyTask) will
587
     * be returned.  If not available (loaded in taskdefs or typedefs) then the
588
     * XML element name will be returned.
589
     *
590
     * @param object $element the Task or type element
591
     *
592
     * @return string fully qualified class name of element when possible
593
     */
594 7
    public function getElementName(Project $project, $element)
595
    {
596 7
        $taskdefs = $project->getTaskDefinitions();
597 7
        $typedefs = $project->getDataTypeDefinitions();
598
599
        // check if class of element is registered with project (tasks & types)
600
        // most element types don't have a getTag() method
601 7
        $elClass = get_class($element);
602
603 7
        if (!in_array('getTag', get_class_methods($elClass))) {
604
            // loop through taskdefs and typesdefs and see if the class name
605
            // matches (case-insensitive) any of the classes in there
606 7
            foreach (array_merge($taskdefs, $typedefs) as $elName => $class) {
607 6
                if (0 === strcasecmp($elClass, $class)) {
608 4
                    return $class;
609
                }
610
            }
611
612 3
            return "{$elClass} (unknown)";
613
        }
614
615
        // ->getTag() method does exist, so use it
616
        $elName = $element->getTag();
617
        if (isset($taskdefs[$elName])) {
618
            return $taskdefs[$elName];
619
        }
620
621
        if (isset($typedefs[$elName])) {
622
            return $typedefs[$elName];
623
        }
624
625
        return "{$elName} (unknown)";
626
    }
627
628
    /**
629
     * Extract the name of a property from a method name - subtracting  a given prefix.
630
     *
631
     * @param string $methodName
632
     * @param string $prefix
633
     *
634
     * @return string
635
     */
636 44
    public function getPropertyName($methodName, $prefix)
637
    {
638 44
        $start = strlen($prefix);
639
640 44
        return strtolower(substr($methodName, $start));
641
    }
642
643
    /**
644
     * Prints warning message to screen if -debug was used.
645
     *
646
     * @param string $msg
647
     */
648
    public function warn($msg)
649
    {
650
        if (Project::MSG_DEBUG === Phing::getMsgOutputLevel()) {
651
            echo '[IntrospectionHelper] ' . $msg . "\n";
652
        }
653
    }
654
655
    /**
656
     * @param \ReflectionParameter $parameter
657
     * @return mixed|null
658
     */
659 402
    private function getClassnameFromParameter(\ReflectionParameter $parameter)
660
    {
661
        /** @var ReflectionType $reflectionType */
662 402
        $reflectionType = $parameter->getType();
663 402
        if ($reflectionType instanceof \ReflectionNamedType) {
664 402
            return !$reflectionType->isBuiltin() ? $reflectionType->getName() : null;
665
        }
666 1
        return null;
667
    }
668
}
669