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
    public function __construct($class)
124
    {
125
        $this->bean = new ReflectionClass($class);
126
127
        //$methods = get_class_methods($bean);
128
        foreach ($this->bean->getMethods() as $method) {
129
            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
                $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
                    'setlocation' === $name
141
                    || 'settasktype' === $name
142
                    || ('addtask' === $name
143
                        && $this->isContainer()
144
                        && 1 === count($method->getParameters())
145
                        && Task::class === $method->getParameters()[0])
146
                ) {
147
                    continue;
148
                }
149
150
                if ('addtext' === $name) {
151
                    $this->methodAddText = $method;
152
                } 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
                    if (1 !== count($method->getParameters())) {
162
                        throw new BuildException(
163
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() must take exactly one parameter.'
164
                        );
165
                    }
166
167
                    $this->slotListeners[$name] = $method;
168
                } elseif (0 === strpos($name, 'set') && 1 === count($method->getParameters())) {
169
                    $this->attributeSetters[$name] = $method;
170
                } elseif (0 === strpos($name, 'create')) {
171
                    if ($method->getNumberOfRequiredParameters() > 0) {
172
                        throw new BuildException(
173
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() may not take any parameters.'
174
                        );
175
                    }
176
177
                    if ($method->hasReturnType()) {
178
                        $this->nestedTypes[$name] = $method->getReturnType();
179
                    } else {
180
                        preg_match('/@return[\s]+([\w]+)/', $method->getDocComment(), $matches);
181
                        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
                            $this->nestedTypes[$name] = $this->getPropertyName($name, 'create');
187
                        }
188
                    }
189
190
                    $this->nestedCreators[$name] = $method;
191
                } elseif (0 === strpos($name, 'addconfigured')) {
192
                    // *must* use class hints if using addConfigured ...
193
194
                    // 1 param only
195
                    $params = $method->getParameters();
196
197
                    if (count($params) < 1) {
198
                        throw new BuildException(
199
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() must take at least one parameter.'
200
                        );
201
                    }
202
203
                    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
                    $classname = $this->getClassnameFromParameter($params[0]);
210
211
                    if (null === $classname) {
212
                        throw new BuildException(
213
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() method MUST use a class hint to indicate the class type of parameter.'
214
                        );
215
                    }
216
217
                    $this->nestedTypes[$name] = $classname;
218
219
                    $this->nestedStorers[$name] = $method;
220
                } elseif (0 === strpos($name, 'add')) {
221
                    // *must* use class hints if using add ...
222
223
                    // 1 param only
224
                    $params = $method->getParameters();
225
                    if (count($params) < 1) {
226
                        throw new BuildException(
227
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() must take at least one parameter.'
228
                        );
229
                    }
230
231
                    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
                    $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
                    if (null === $classname) {
242
                        throw new BuildException(
243
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() method MUST use a class hint to indicate the class type of parameter.'
244
                        );
245
                    }
246
247
                    $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
    public static function getHelper($class)
261
    {
262
        if (!isset(self::$helpers[$class])) {
263
            self::$helpers[$class] = new IntrospectionHelper($class);
264
        }
265
266
        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
    public function isContainer()
275
    {
276
        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
    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
        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
            $as = 'set' . strtolower($attributeName);
321
322
            if (!isset($this->attributeSetters[$as])) {
323
                if ($element instanceof DynamicAttribute) {
324
                    $element->setDynamicAttribute($attributeName, (string) $value);
325
326
                    return;
327
                }
328
                $msg = $this->getElementName($project, $element) . " doesn't support the '{$attributeName}' attribute.";
329
330
                throw new BuildException($msg);
331
            }
332
333
            $method = $this->attributeSetters[$as];
334
335
            if ('setrefid' == $as) {
336
                $value = new Reference($project, $value);
337
            } else {
338
                $params = $method->getParameters();
339
340
                /** @var ReflectionType $hint */
341
                $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
                if ('bool' === $reflectedAttr || ('string' !== $reflectedAttr && StringHelper::isBoolean($value))) {
346
                    $value = StringHelper::booleanValue($value);
347
                }
348
349
                // there should only be one param; we'll just assume ....
350
                if (null !== $reflectedAttr) {
351
                    switch ($reflectedAttr) {
352
                        case File::class:
353
                            $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
                            break;
356
357
                        case Path::class:
358
                            $value = new Path($project, $value);
359
360
                            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
            $project->log(
374
                '    -calling setter ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()',
375
                Project::MSG_DEBUG
376
            );
377
            $method->invoke($element, $value);
378
        } catch (Exception $exc) {
379
            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
    public function addText(Project $project, $element, $text)
392
    {
393
        if (null === $this->methodAddText) {
394
            $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
            throw new BuildException($msg);
397
        }
398
399
        try {
400
            $method = $this->methodAddText;
401
            $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
    public function createElement(Project $project, $element, $elementName)
421
    {
422
        $addMethod = 'add' . strtolower($elementName);
423
        $createMethod = 'create' . strtolower($elementName);
424
        $nestedElement = null;
425
426
        if (isset($this->nestedCreators[$createMethod])) {
427
            $method = $this->nestedCreators[$createMethod];
428
429
            try { // try to invoke the creator method on object
430
                $project->log(
431
                    '    -calling creator ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()',
432
                    Project::MSG_DEBUG
433
                );
434
                $nestedElement = $method->invoke($element);
435
            } catch (Exception $exc) {
436
                throw new BuildException($exc->getMessage(), $exc);
437
            }
438
        } elseif (isset($this->nestedCreators[$addMethod])) {
439
            $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
                $project->log(
445
                    '    -calling adder ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()',
446
                    Project::MSG_DEBUG
447
                );
448
                // we've already assured that correct num of params
449
                // exist and that method is using class hints
450
                $params = $method->getParameters();
451
452
                $classname = $this->getClassnameFromParameter($params[0]);
453
454
                // create a new instance of the object and add it via $addMethod
455
                $clazz = new ReflectionClass($classname);
456
                if (null !== $clazz->getConstructor() && $clazz->getConstructor()->getNumberOfRequiredParameters() >= 1) {
457
                    $nestedElement = new $classname(Phing::getCurrentProject() ?? $project);
458
                } else {
459
                    $nestedElement = new $classname();
460
                }
461
462
                if ($nestedElement instanceof Task && $element instanceof Task) {
463
                    $nestedElement->setOwningTarget($element->getOwningTarget());
464
                }
465
466
                $method->invoke($element, $nestedElement);
467
            } catch (Exception $exc) {
468
                throw new BuildException($exc->getMessage(), $exc);
469
            }
470
        } elseif ($this->bean->implementsInterface(CustomChildCreator::class)) {
471
            $method = $this->bean->getMethod('customChildCreator');
472
473
            try {
474
                $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
            $typedefs = $project->getDataTypeDefinitions();
481
            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
            if (null === $nestedElement) {
503
                $msg = $this->getElementName($project, $element) . " doesn't support the '{$elementName}' creator/adder.";
504
505
                throw new BuildException($msg);
506
            }
507
        }
508
509
        if ($nestedElement instanceof ProjectComponent) {
510
            $nestedElement->setProject($project);
511
        }
512
513
        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
    public function supportsCharacters()
555
    {
556
        return null !== $this->methodAddText;
557
    }
558
559
    /**
560
     * Return all attribues supported by the introspected class.
561
     *
562
     * @return string[]
563
     */
564
    public function getAttributes()
565
    {
566
        $attribs = [];
567
        foreach (array_keys($this->attributeSetters) as $setter) {
568
            $attribs[] = $this->getPropertyName($setter, 'set');
569
        }
570
571
        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
    public function getElementName(Project $project, $element)
595
    {
596
        $taskdefs = $project->getTaskDefinitions();
597
        $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
        $elClass = get_class($element);
602
603
        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
            foreach (array_merge($taskdefs, $typedefs) as $elName => $class) {
607
                if (0 === strcasecmp($elClass, $class)) {
608
                    return $class;
609
                }
610
            }
611
612
            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
    public function getPropertyName($methodName, $prefix)
637
    {
638
        $start = strlen($prefix);
639
640
        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
    private function getClassnameFromParameter(\ReflectionParameter $parameter)
660
    {
661
        /** @var ReflectionType $reflectionType */
662
        $reflectionType = $parameter->getType();
663
        if ($reflectionType instanceof \ReflectionNamedType) {
664
            return !$reflectionType->isBuiltin() ? $reflectionType->getName() : null;
665
        }
666
        return null;
667
    }
668
}
669