Passed
Push — main ( 966a82...5d15cf )
by Siad
05:49
created

src/Phing/Task/System/PropertyTask.php (1 issue)

Labels
Severity
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\Task\System;
22
23
use Exception;
24
use Phing\Exception\BuildException;
25
use Phing\Io\File;
26
use Phing\Io\FileParserFactory;
27
use Phing\Io\FileParserFactoryInterface;
28
use Phing\Io\FileReader;
29
use Phing\Io\FileUtils;
30
use Phing\Io\IOException;
31
use Phing\Io\StringReader;
32
use Phing\Project;
33
use Phing\PropertyHelper;
34
use Phing\Task;
35
use Phing\Type\Element\FilterChainAware;
36
use Phing\Type\Reference;
37
use Phing\Util\Properties;
38
use Phing\Util\StringHelper;
39
40
/**
41
 * Task for setting properties in buildfiles.
42
 *
43
 * @author  Andreas Aderhold <[email protected]>
44
 * @author  Hans Lellelid <[email protected]>
45
 */
46
class PropertyTask extends Task
47
{
48
    use FilterChainAware;
49
50
    /**
51
     * @var string name of the property
52
     */
53
    protected $name;
54
55
    /**
56
     * @var string of the property
57
     */
58
    protected $value;
59
60
    /**
61
     * @var Reference
62
     */
63
    protected $reference;
64
65
    /**
66
     * @var string environment
67
     */
68
    protected $env;
69
70
    /**
71
     * @var File
72
     */
73
    protected $file;
74
75
    /**
76
     * @var string
77
     */
78
    protected $prefix;
79
80
    /**
81
     * @var Project
82
     */
83
    protected $fallback;
84
85
    /**
86
     * Whether to force overwrite of existing property.
87
     */
88
    protected $override = false;
89
90
    /**
91
     * Whether property should be treated as "user" property.
92
     */
93
    protected $userProperty = false;
94
95
    /**
96
     * Whether to log messages as INFO or VERBOSE.
97
     */
98
    protected $logOutput = true;
99
100
    /**
101
     * @var FileParserFactoryInterface
102
     */
103
    private $fileParserFactory;
104
105
    /**
106
     * Whether a warning should be displayed when the property ismissing.
107
     */
108
    private $quiet = false;
109
110
    /**
111
     * Whether the task should fail when the property file is not found.
112
     */
113
    private $required = false;
114
115
    /**
116
     * @param FileParserFactoryInterface $fileParserFactory
117
     */
118 478
    public function __construct(FileParserFactoryInterface $fileParserFactory = null)
119
    {
120 478
        parent::__construct();
121 478
        $this->fileParserFactory = $fileParserFactory ?? new FileParserFactory();
122 478
    }
123
124
    /**
125
     * @return string
126
     */
127 1
    public function __toString()
128
    {
129 1
        return $this->value;
130
    }
131
132
    /**
133
     * File required or not.
134
     *
135
     * @param string $d
136
     */
137 1
    public function setRequired($d)
138
    {
139 1
        $this->required = $d;
140 1
    }
141
142
    /**
143
     * @return string
144
     */
145
    public function getRequired()
146
    {
147
        return $this->required;
148
    }
149
150
    /**
151
     * Sets a the name of current property component.
152
     */
153 471
    public function setName(string $name): void
154
    {
155 471
        $this->name = $name;
156 471
    }
157
158
    /**
159
     * Get property component name.
160
     */
161 12
    public function getName()
162
    {
163 12
        return $this->name;
164
    }
165
166
    /**
167
     * Sets a the value of current property component.
168
     *
169
     * @param string $value Value of name, all scalars allowed
170
     */
171 471
    public function setValue(string $value): void
172
    {
173 471
        $this->value = $value;
174 471
    }
175
176
    /**
177
     * Sets value of property to CDATA tag contents.
178
     *
179
     * @since    2.2.0
180
     */
181
    public function addText(string $value): void
182
    {
183
        $this->setValue($value);
184
    }
185
186
    /**
187
     * Get the value of current property component.
188
     */
189 7
    public function getValue()
190
    {
191 7
        return $this->value;
192
    }
193
194
    /**
195
     * Set a file to use as the source for properties.
196
     *
197
     * @param File|string $file
198
     *
199
     * @throws IOException
200
     */
201 18
    public function setFile($file)
202
    {
203 18
        if (is_string($file)) {
204 18
            $file = new File($file);
205
        }
206 18
        $this->file = $file;
207 18
    }
208
209
    /**
210
     * Get the PhingFile that is being used as property source.
211
     */
212 6
    public function getFile()
213
    {
214 6
        return $this->file;
215
    }
216
217
    public function setRefid(Reference $ref): void
218
    {
219
        $this->reference = $ref;
220
    }
221
222 6
    public function getRefid()
223
    {
224 6
        return $this->reference;
225
    }
226
227
    /**
228
     * Prefix to apply to properties loaded using <code>file</code>.
229
     * A "." is appended to the prefix if not specified.
230
     *
231
     * @param string $prefix prefix string
232
     *
233
     * @since  2.0
234
     */
235 2
    public function setPrefix(string $prefix): void
236
    {
237 2
        $this->prefix = $prefix;
238 2
        if (!StringHelper::endsWith('.', $prefix)) {
239 2
            $this->prefix .= '.';
240
        }
241 2
    }
242
243
    /**
244
     * @return string
245
     *
246
     * @since 2.0
247
     */
248 6
    public function getPrefix()
249
    {
250 6
        return $this->prefix;
251
    }
252
253
    /**
254
     * the prefix to use when retrieving environment variables.
255
     * Thus if you specify environment="myenv"
256
     * you will be able to access OS-specific
257
     * environment variables via property names "myenv.PATH" or
258
     * "myenv.TERM".
259
     * <p>
260
     * Note that if you supply a property name with a final
261
     * "." it will not be doubled. ie environment="myenv." will still
262
     * allow access of environment variables through "myenv.PATH" and
263
     * "myenv.TERM". This functionality is currently only implemented
264
     * on select platforms. Feel free to send patches to increase the number of platforms
265
     * this functionality is supported on ;).<br>
266
     * Note also that properties are case sensitive, even if the
267
     * environment variables on your operating system are not, e.g. it
268
     * will be ${env.Path} not ${env.PATH} on Windows 2000.
269
     */
270 2
    public function setEnvironment(string $env): void
271
    {
272 2
        $this->env = $env;
273 2
    }
274
275 6
    public function getEnvironment()
276
    {
277 6
        return $this->env;
278
    }
279
280
    /**
281
     * Set whether this is a user property (ro).
282
     * This is deprecated in Ant 1.5, but the userProperty attribute
283
     * of the class is still being set via constructor, so Phing will
284
     * allow this method to function.
285
     */
286 12
    public function setUserProperty(bool $v): void
287
    {
288 12
        $this->userProperty = $v;
289 12
    }
290
291
    /**
292
     * @return bool
293
     */
294 6
    public function getUserProperty()
295
    {
296 6
        return $this->userProperty;
297
    }
298
299 8
    public function setOverride(bool $override): void
300
    {
301 8
        $this->override = $override;
302 8
    }
303
304
    /**
305
     * @return bool
306
     */
307 6
    public function getOverride()
308
    {
309 6
        return $this->override;
310
    }
311
312
    /**
313
     * @param Project $p
314
     */
315 12
    public function setFallback($p): void
316
    {
317 12
        $this->fallback = $p;
318 12
    }
319
320
    public function getFallback()
321
    {
322
        return $this->fallback;
323
    }
324
325 6
    public function setLogoutput(bool $logOutput): void
326
    {
327 6
        $this->logOutput = $logOutput;
328 6
    }
329
330
    /**
331
     * @return bool
332
     */
333 6
    public function getLogoutput()
334
    {
335 6
        return $this->logOutput;
336
    }
337
338
    /**
339
     * Set quiet mode, which suppresses warnings if chmod() fails.
340
     *
341
     * @see   setFailonerror()
342
     */
343 6
    public function setQuiet(bool $bool): void
344
    {
345 6
        $this->quiet = $bool;
346 6
    }
347
348 6
    public function getQuiet(): bool
349
    {
350 6
        return $this->quiet;
351
    }
352
353
    /**
354
     * set the property in the project to the value.
355
     * if the task was give a file or env attribute
356
     * here is where it is loaded.
357
     */
358 478
    public function main()
359
    {
360 478
        $this->validate();
361
362 477
        if (null !== $this->name && null !== $this->value) {
363 470
            $this->addProperty($this->name, $this->value);
364
        }
365
366 477
        if (null !== $this->file) {
367 17
            $this->loadFile($this->file);
368
        }
369
370 474
        if (null !== $this->env) {
371 2
            $this->loadEnvironment($this->env);
372
        }
373
374 474
        if (null !== $this->name && null !== $this->reference) {
375
            // get the refereced property
376
            try {
377
                $referencedObject = $this->reference->getReferencedObject($this->project);
378
379
                if ($referencedObject instanceof Exception) {
380
                    $reference = $referencedObject->getMessage();
381
                } else {
382
                    $reference = (string) $referencedObject;
383
                }
384
385
                $this->addProperty($this->name, $reference);
386
            } catch (BuildException $be) {
387
                if (null !== $this->fallback) {
388
                    $referencedObject = $this->reference->getReferencedObject($this->fallback);
389
390
                    if ($referencedObject instanceof Exception) {
391
                        $reference = $referencedObject->getMessage();
392
                    } else {
393
                        $reference = (string) $referencedObject;
394
                    }
395
                    $this->addProperty($this->name, $reference);
396
                } else {
397
                    throw $be;
398
                }
399
            }
400
        }
401 474
    }
402
403
    /**
404
     * load the environment values.
405
     *
406
     * @param string $prefix prefix to place before them
407
     */
408 2
    protected function loadEnvironment(string $prefix)
409
    {
410 2
        $props = new Properties();
411 2
        if ('.' === substr($prefix, strlen($prefix) - 1)) {
412
            $prefix .= '.';
413
        }
414 2
        $this->log("Loading Environment {$prefix}", Project::MSG_VERBOSE);
415 2
        foreach ($_ENV as $key => $value) {
416
            $props->setProperty($prefix . '.' . $key, $value);
417
        }
418 2
        $this->addProperties($props);
419 2
    }
420
421
    /**
422
     * iterate through a set of properties,
423
     * resolve them then assign them.
424
     *
425
     * @param Properties $props
426
     *
427
     * @throws BuildException
428
     */
429 18
    protected function addProperties($props)
430
    {
431 18
        $this->resolveAllProperties($props);
432 15
        foreach ($props->keys() as $name) {
433 13
            $value = $props->getProperty($name);
434 13
            $v = $this->project->replaceProperties($value);
435 13
            if (null !== $this->prefix) {
436 1
                $name = $this->prefix . $name;
437
            }
438 13
            $this->addProperty($name, $v);
439
        }
440 15
    }
441
442
    /**
443
     * add a name value pair to the project property set.
444
     *
445
     * @param string $name  name of property
446
     * @param string $value value to set
447
     */
448 473
    protected function addProperty($name, $value)
449
    {
450 473
        if (null === $this->file && count($this->filterChains) > 0) {
451 1
            $in = FileUtils::getChainedReader(new StringReader($value), $this->filterChains, $this->project);
452 1
            $value = $in->read();
453
        }
454
455 473
        $ph = PropertyHelper::getPropertyHelper($this->getProject());
456 473
        if ($this->userProperty) {
457 12
            if (null === $ph->getUserProperty(null, $name) || $this->override) {
458 10
                $ph->setInheritedProperty(null, $name, $value);
459
            } else {
460 12
                $this->log('Override ignored for ' . $name, Project::MSG_VERBOSE);
461
            }
462
        } else {
463 470
            if ($this->override) {
464 2
                $ph->setProperty(null, $name, $value, true);
465
            } else {
466 470
                $ph->setNewProperty(null, $name, $value);
467
            }
468
        }
469 473
    }
470
471
    /**
472
     * load properties from a file.
473
     *
474
     * @throws BuildException
475
     */
476 15
    protected function loadFile(File $file)
477
    {
478 15
        $fileParser = $this->fileParserFactory->createParser($file->getFileExtension());
479 15
        $props = new Properties(null, $fileParser);
480 15
        $this->log('Loading ' . $file->getAbsolutePath(), $this->logOutput ? Project::MSG_INFO : Project::MSG_VERBOSE);
481
482
        try { // try to load file
483 15
            if ($file->exists()) {
484 14
                $value = null;
485 14
                if (count($this->filterChains) > 0) {
486 1
                    $in = FileUtils::getChainedReader(new FileReader($file), $this->filterChains, $this->project);
487 1
                    $value = $in->read();
488
                }
489 14
                if ($value) {
490 1
                    foreach (array_filter(explode(PHP_EOL, $value)) as $line) {
491 1
                        [$key, $prop] = explode('=', $line);
492 1
                        $props->setProperty($key, $prop);
493
                    }
494
                } else {
495 13
                    $props->load($file);
496
                }
497 14
                $this->addProperties($props);
498
            } else {
499 1
                if ($this->required) {
500 1
                    throw new BuildException('Unable to find property file: ' . $file->getAbsolutePath());
501
                }
502
503
                $this->log(
504
                    'Unable to find property file: ' . $file->getAbsolutePath() . '... skipped',
505 13
                    $this->quiet ? Project::MSG_VERBOSE : Project::MSG_WARN
506
                );
507
            }
508 2
        } catch (IOException $ioe) {
509
            throw new BuildException('Could not load properties from file.', $ioe);
510
        }
511 13
    }
512
513
    /**
514
     * Given a Properties object, this method goes through and resolves
515
     * any references to properties within the object.
516
     *
517
     * @param Properties $props the collection of Properties that need to be resolved
518
     *
519
     * @throws BuildException
520
     */
521 18
    protected function resolveAllProperties(Properties $props)
522
    {
523 18
        foreach ($props->keys() as $name) {
524
            // There may be a nice regex/callback way to handle this
525
            // replacement, but at the moment it is pretty complex, and
526
            // would probably be a lot uglier to work into a preg_replace_callback()
527
            // system.  The biggest problem is the fact that a resolution may require
528
            // multiple passes.
529
530 16
            $value = $props->getProperty($name);
531 16
            $resolved = false;
532 16
            $resolveStack = [];
533
534 16
            while (!$resolved) {
535 16
                $fragments = [];
536 16
                $propertyRefs = [];
537
538 16
                PropertyHelper::getPropertyHelper($this->project)->parsePropertyString(
539 16
                    $value,
0 ignored issues
show
It seems like $value can also be of type null; however, parameter $value of Phing\PropertyHelper::parsePropertyString() 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

539
                    /** @scrutinizer ignore-type */ $value,
Loading history...
540
                    $fragments,
541
                    $propertyRefs
542
                );
543
544 16
                $resolved = true;
545 16
                if (0 === count($propertyRefs)) {
546 13
                    continue;
547
                }
548
549 6
                $sb = '';
550
551 6
                $j = $propertyRefs;
552
553 6
                foreach ($fragments as $fragment) {
554 6
                    if (null !== $fragment) {
555 6
                        $sb .= $fragment;
556
557 6
                        continue;
558
                    }
559
560 6
                    $propertyName = array_shift($j);
561 6
                    if (in_array($propertyName, $resolveStack)) {
562
                        // Should we maybe just log this as an error & move on?
563
                        // $this->log("Property ".$name." was circularly defined.", Project::MSG_ERR);
564 3
                        throw new BuildException('Property ' . $propertyName . ' was circularly defined.');
565
                    }
566
567 6
                    $fragment = $this->getProject()->getProperty($propertyName);
568 6
                    if (null !== $fragment) {
569 2
                        $sb .= $fragment;
570
571 2
                        continue;
572
                    }
573
574 5
                    if ($props->containsKey($propertyName)) {
575 5
                        $fragment = $props->getProperty($propertyName);
576 5
                        if (false !== strpos($fragment, '${')) {
577 3
                            $resolveStack[] = $propertyName;
578 5
                            $resolved = false; // parse again (could have been replaced w/ another var)
579
                        }
580
                    } else {
581
                        $fragment = '${' . $propertyName . '}';
582
                    }
583
584 5
                    $sb .= $fragment;
585
                }
586
587 6
                $this->log("Resolved Property \"{$value}\" to \"{$sb}\"", Project::MSG_DEBUG);
588 6
                $value = $sb;
589 6
                $props->setProperty($name, $value);
590
            } // while (!$resolved)
591
        } // while (count($keys)
592 15
    }
593
594
    /**
595
     * @throws BuildException
596
     */
597 478
    private function validate(): void
598
    {
599 478
        if (null !== $this->name) {
600 471
            if (null === $this->value && null === $this->reference) {
601
                throw new BuildException(
602
                    'You must specify value or refid with the name attribute',
603 471
                    $this->getLocation()
604
                );
605
            }
606 19
        } elseif (null === $this->file && null === $this->env) {
607
            throw new BuildException(
608
                'You must specify file or environment when not using the name attribute',
609
                $this->getLocation()
610
            );
611
        }
612
613 478
        if (null === $this->file && null !== $this->prefix) {
614 1
            throw new BuildException('Prefix is only valid when loading from a file.', $this->getLocation());
615
        }
616 477
    }
617
}
618