Passed
Push — master ( eabf33...342b63 )
by Michiel
05:53
created

PropertyTask::setEnvironment()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
/**
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19
20
use Phing\Exception\BuildException;
21
use Phing\Exception\NullPointerException;
22
use Phing\Io\FileParserFactory;
23
use Phing\Io\FileParserFactoryInterface;
24
use Phing\Io\FileReader;
25
use Phing\Io\FileUtils;
26
use Phing\Io\IOException;
27
use Phing\Io\File;
28
use Phing\Io\StringReader;
29
use Phing\Project;
30
use Phing\PropertyHelper;
31
use Phing\Type\Reference;
32
use Phing\Util\Properties;
33
use Phing\Util\StringHelper;
34
35
/**
36
 * Task for setting properties in buildfiles.
37
 *
38
 * @author  Andreas Aderhold <[email protected]>
39
 * @author  Hans Lellelid <[email protected]>
40
 * @package phing.tasks.system
41
 */
42
class PropertyTask extends Task
43
{
44
    use FilterChainAware;
45
46
    /**
47
     * @var string name of the property
48
     */
49
    protected $name;
50
51
    /**
52
     * @var string $value of the property
53
     */
54
    protected $value;
55
56
    /**
57
     * @var Reference
58
     */
59
    protected $reference;
60
61
    /**
62
     * @var string environment
63
     */
64
    protected $env;
65
66
    /**
67
     * @var File
68
     */
69
    protected $file;
70
71
    /**
72
     * @var string
73
     */
74
    protected $prefix;
75
76
    /**
77
     * @var Project
78
     */
79
    protected $fallback;
80
81
    /**
82
     * Whether to force overwrite of existing property.
83
     */
84
    protected $override = false;
85
86
    /**
87
     * Whether property should be treated as "user" property.
88
     */
89
    protected $userProperty = false;
90
91
    /**
92
     * Whether to log messages as INFO or VERBOSE
93
     */
94
    protected $logOutput = true;
95
96
    /**
97
     * @var FileParserFactoryInterface
98
     */
99
    private $fileParserFactory;
100
101
    /**
102
     * Whether a warning should be displayed when the property ismissing.
103
     */
104
    private $quiet = false;
105
106
    /**
107
     * Whether the task should fail when the property file is not found.
108
     */
109
    private $required = false;
110
111
    /**
112
     * @param FileParserFactoryInterface $fileParserFactory
113
     */
114 462
    public function __construct(FileParserFactoryInterface $fileParserFactory = null)
115
    {
116 462
        parent::__construct();
117 462
        $this->fileParserFactory = $fileParserFactory ?? new FileParserFactory();
118 462
    }
119
120
    /**
121
     * File required or not.
122
     *
123
     * @param string $d
124
     */
125 1
    public function setRequired($d)
126
    {
127 1
        $this->required = $d;
128 1
    }
129
130
    /**
131
     * @return string
132
     */
133
    public function getRequired()
134
    {
135
        return $this->required;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->required returns the type boolean which is incompatible with the documented return type string.
Loading history...
136
    }
137
138
    /**
139
     * Sets a the name of current property component
140
     *
141
     * @param string $name
142
     */
143 455
    public function setName(string $name): void
144
    {
145 455
        $this->name = $name;
146 455
    }
147
148
    /**
149
     * Get property component name.
150
     */
151 12
    public function getName()
152
    {
153 12
        return $this->name;
154
    }
155
156
    /**
157
     * Sets a the value of current property component.
158
     *
159
     * @param string $value Value of name, all scalars allowed
160
     */
161 455
    public function setValue(string $value): void
162
    {
163 455
        $this->value = $value;
164 455
    }
165
166
    /**
167
     * Sets value of property to CDATA tag contents.
168
     *
169
     * @param string $value
170
     * @since    2.2.0
171
     */
172
    public function addText(string $value): void
173
    {
174
        $this->setValue($value);
175
    }
176
177
    /**
178
     * Get the value of current property component.
179
     */
180 7
    public function getValue()
181
    {
182 7
        return $this->value;
183
    }
184
185
    /**
186
     * Set a file to use as the source for properties.
187
     *
188
     * @param $file
189
     *
190
     * @throws IOException
191
     * @throws NullPointerException
192
     */
193 18
    public function setFile($file)
194
    {
195 18
        if (is_string($file)) {
196 18
            $file = new File($file);
197
        }
198 18
        $this->file = $file;
199 18
    }
200
201
    /**
202
     * Get the PhingFile that is being used as property source.
203
     */
204 6
    public function getFile()
205
    {
206 6
        return $this->file;
207
    }
208
209
    /**
210
     * @param Reference $ref
211
     */
212
    public function setRefid(Reference $ref): void
213
    {
214
        $this->reference = $ref;
215
    }
216
217 6
    public function getRefid()
218
    {
219 6
        return $this->reference;
220
    }
221
222
    /**
223
     * Prefix to apply to properties loaded using <code>file</code>.
224
     * A "." is appended to the prefix if not specified.
225
     *
226
     * @param  string $prefix prefix string
227
     *
228
     * @since  2.0
229
     */
230 2
    public function setPrefix(string $prefix): void
231
    {
232 2
        $this->prefix = $prefix;
233 2
        if (!StringHelper::endsWith(".", $prefix)) {
234 2
            $this->prefix .= ".";
235
        }
236 2
    }
237
238
    /**
239
     * @return string
240
     * @since 2.0
241
     */
242 6
    public function getPrefix()
243
    {
244 6
        return $this->prefix;
245
    }
246
247
    /**
248
     * the prefix to use when retrieving environment variables.
249
     * Thus if you specify environment="myenv"
250
     * you will be able to access OS-specific
251
     * environment variables via property names "myenv.PATH" or
252
     * "myenv.TERM".
253
     * <p>
254
     * Note that if you supply a property name with a final
255
     * "." it will not be doubled. ie environment="myenv." will still
256
     * allow access of environment variables through "myenv.PATH" and
257
     * "myenv.TERM". This functionality is currently only implemented
258
     * on select platforms. Feel free to send patches to increase the number of platforms
259
     * this functionality is supported on ;).<br>
260
     * Note also that properties are case sensitive, even if the
261
     * environment variables on your operating system are not, e.g. it
262
     * will be ${env.Path} not ${env.PATH} on Windows 2000.
263
     *
264
     * @param string $env
265
     */
266 1
    public function setEnvironment(string $env): void
267
    {
268 1
        $this->env = $env;
269 1
    }
270
271 6
    public function getEnvironment()
272
    {
273 6
        return $this->env;
274
    }
275
276
    /**
277
     * Set whether this is a user property (ro).
278
     * This is deprecated in Ant 1.5, but the userProperty attribute
279
     * of the class is still being set via constructor, so Phing will
280
     * allow this method to function.
281
     *
282
     * @param boolean $v
283
     */
284 12
    public function setUserProperty(bool $v): void
285
    {
286 12
        $this->userProperty = $v;
287 12
    }
288
289
    /**
290
     * @return bool
291
     */
292 6
    public function getUserProperty()
293
    {
294 6
        return $this->userProperty;
295
    }
296
297
    /**
298
     * @param $v
299
     */
300 8
    public function setOverride(bool $v): void
301
    {
302 8
        $this->override = $v;
303 8
    }
304
305
    /**
306
     * @return bool
307
     */
308 6
    public function getOverride()
309
    {
310 6
        return $this->override;
311
    }
312
313
    /**
314
     * @return string
315
     */
316 1
    public function __toString()
317
    {
318 1
        return $this->value;
319
    }
320
321
    /**
322
     * @param Project $p
323
     */
324 12
    public function setFallback($p): void
325
    {
326 12
        $this->fallback = $p;
327 12
    }
328
329
    public function getFallback()
330
    {
331
        return $this->fallback;
332
    }
333
334
    /**
335
     * @param $logOutput
336
     */
337 6
    public function setLogoutput(bool $logOutput): void
338
    {
339 6
        $this->logOutput = $logOutput;
340 6
    }
341
342
    /**
343
     * @return bool
344
     */
345 6
    public function getLogoutput()
346
    {
347 6
        return $this->logOutput;
348
    }
349
350
    /**
351
     * Set quiet mode, which suppresses warnings if chmod() fails.
352
     *
353
     * @see   setFailonerror()
354
     * @param $bool
355
     */
356 6
    public function setQuiet(bool $bool): void
357
    {
358 6
        $this->quiet = $bool;
359 6
    }
360
361
    /**
362
     * @return bool
363
     */
364 6
    public function getQuiet(): bool
365
    {
366 6
        return $this->quiet;
367
    }
368
369
    /**
370
     * set the property in the project to the value.
371
     * if the task was give a file or env attribute
372
     * here is where it is loaded
373
     */
374 462
    public function main()
375
    {
376 462
        $this->validate();
377
378 461
        if ($this->name !== null && $this->value !== null) {
379 454
            $this->addProperty($this->name, $this->value);
380
        }
381
382 461
        if ($this->file !== null) {
383 17
            $this->loadFile($this->file);
384
        }
385
386 458
        if ($this->env !== null) {
387 1
            $this->loadEnvironment($this->env);
388
        }
389
390 458
        if ($this->name !== null && $this->reference !== null) {
391
            // get the refereced property
392
            try {
393
                $referencedObject = $this->reference->getReferencedObject($this->project);
394
395
                if ($referencedObject instanceof Exception) {
396
                    $reference = $referencedObject->getMessage();
397
                } else {
398
                    $reference = (string) $referencedObject;
399
                }
400
401
                $this->addProperty($this->name, $reference);
402
            } catch (BuildException $be) {
403
                if ($this->fallback !== null) {
404
                    $referencedObject = $this->reference->getReferencedObject($this->fallback);
405
406
                    if ($referencedObject instanceof Exception) {
407
                        $reference = $referencedObject->getMessage();
408
                    } else {
409
                        $reference = (string) $referencedObject;
410
                    }
411
                    $this->addProperty($this->name, $reference);
412
                } else {
413
                    throw $be;
414
                }
415
            }
416
        }
417 458
    }
418
419
    /**
420
     * @throws BuildException
421
     */
422 462
    private function validate(): void
423
    {
424 462
        if ($this->name !== null) {
425 455
            if ($this->value === null && $this->reference === null) {
426
                throw new BuildException(
427
                    "You must specify value or refid with the name attribute",
428 455
                    $this->getLocation()
429
                );
430
            }
431 18
        } elseif ($this->file === null && $this->env === null) {
432
            throw new BuildException(
433
                "You must specify file or environment when not using the name attribute",
434
                $this->getLocation()
435
            );
436
        }
437
438 462
        if ($this->file === null && $this->prefix !== null) {
439 1
            throw new BuildException('Prefix is only valid when loading from a file.', $this->getLocation());
440
        }
441 461
    }
442
443
    /**
444
     * load the environment values
445
     *
446
     * @param string $prefix prefix to place before them
447
     */
448 1
    protected function loadEnvironment(string $prefix)
449
    {
450 1
        $props = new Properties();
451 1
        if (substr($prefix, strlen($prefix) - 1) === '.') {
452
            $prefix .= ".";
453
        }
454 1
        $this->log("Loading Environment $prefix", Project::MSG_VERBOSE);
455 1
        foreach ($_ENV as $key => $value) {
456
            $props->setProperty($prefix . '.' . $key, $value);
457
        }
458 1
        $this->addProperties($props);
459 1
    }
460
461
    /**
462
     * iterate through a set of properties,
463
     * resolve them then assign them
464
     *
465
     * @param Properties $props
466
     * @throws BuildException
467
     */
468 17
    protected function addProperties($props)
469
    {
470 17
        $this->resolveAllProperties($props);
471 14
        foreach ($props->keys() as $name) {
472 13
            $value = $props->getProperty($name);
473 13
            $v = $this->project->replaceProperties($value);
474 13
            if ($this->prefix !== null) {
475 1
                $name = $this->prefix . $name;
476
            }
477 13
            $this->addProperty($name, $v);
478
        }
479 14
    }
480
481
    /**
482
     * add a name value pair to the project property set
483
     *
484
     * @param string $name name of property
485
     * @param string $value value to set
486
     */
487 457
    protected function addProperty($name, $value)
488
    {
489 457
        if ($this->file === null && count($this->filterChains) > 0) {
490 1
            $in = FileUtils::getChainedReader(new StringReader($value), $this->filterChains, $this->project);
491 1
            $value = $in->read();
492
        }
493
494 457
        $ph = PropertyHelper::getPropertyHelper($this->getProject());
495 457
        if ($this->userProperty) {
496 12
            if ($ph->getUserProperty(null, $name) === null || $this->override) {
497 10
                $ph->setInheritedProperty(null, $name, $value);
498
            } else {
499 12
                $this->log('Override ignored for ' . $name, Project::MSG_VERBOSE);
500
            }
501
        } else {
502 454
            if ($this->override) {
503 2
                $ph->setProperty(null, $name, $value, true);
504
            } else {
505 454
                $ph->setNewProperty(null, $name, $value);
506
            }
507
        }
508 457
    }
509
510
    /**
511
     * load properties from a file.
512
     *
513
     * @param  File $file
514
     * @throws BuildException
515
     */
516 15
    protected function loadFile(File $file)
517
    {
518 15
        $fileParser = $this->fileParserFactory->createParser($file->getFileExtension());
519 15
        $props = new Properties(null, $fileParser);
520 15
        $this->log("Loading " . $file->getAbsolutePath(), $this->logOutput ? Project::MSG_INFO : Project::MSG_VERBOSE);
521
        try { // try to load file
522 15
            if ($file->exists()) {
523 14
                $value = null;
524 14
                if (count($this->filterChains) > 0) {
525 1
                    $in = FileUtils::getChainedReader(new FileReader($file), $this->filterChains, $this->project);
526 1
                    $value = $in->read();
527
                }
528 14
                if ($value) {
529 1
                    foreach (array_filter(explode(PHP_EOL, $value)) as $line) {
530 1
                        [$key, $prop] = explode('=', $line);
531 1
                        $props->setProperty($key, $prop);
532
                    }
533
                } else {
534 13
                    $props->load($file);
535
                }
536 14
                $this->addProperties($props);
537
            } else {
538 1
                if ($this->required) {
539 1
                    throw new BuildException("Unable to find property file: " . $file->getAbsolutePath());
540
                }
541
542
                $this->log(
543
                    "Unable to find property file: " . $file->getAbsolutePath() . "... skipped",
544 13
                    $this->quiet ? Project::MSG_VERBOSE : Project::MSG_WARN
545
                );
546
            }
547 2
        } catch (IOException $ioe) {
548
            throw new BuildException("Could not load properties from file.", $ioe);
549
        }
550 13
    }
551
552
    /**
553
     * Given a Properties object, this method goes through and resolves
554
     * any references to properties within the object.
555
     *
556
     * @param  Properties $props The collection of Properties that need to be resolved.
557
     * @throws BuildException
558
     * @return void
559
     */
560 17
    protected function resolveAllProperties(Properties $props)
561
    {
562 17
        foreach ($props->keys() as $name) {
563
            // There may be a nice regex/callback way to handle this
564
            // replacement, but at the moment it is pretty complex, and
565
            // would probably be a lot uglier to work into a preg_replace_callback()
566
            // system.  The biggest problem is the fact that a resolution may require
567
            // multiple passes.
568
569 16
            $value = $props->getProperty($name);
570 16
            $resolved = false;
571 16
            $resolveStack = [];
572
573 16
            while (!$resolved) {
574 16
                $fragments = [];
575 16
                $propertyRefs = [];
576
577 16
                PropertyHelper::getPropertyHelper($this->project)->parsePropertyString(
578 16
                    $value,
579
                    $fragments,
580
                    $propertyRefs
581
                );
582
583 16
                $resolved = true;
584 16
                if (count($propertyRefs) === 0) {
585 13
                    continue;
586
                }
587
588 6
                $sb = "";
589
590 6
                $j = $propertyRefs;
591
592 6
                foreach ($fragments as $fragment) {
593 6
                    if ($fragment !== null) {
594 6
                        $sb .= $fragment;
595 6
                        continue;
596
                    }
597
598 6
                    $propertyName = array_shift($j);
599 6
                    if (in_array($propertyName, $resolveStack)) {
600
                        // Should we maybe just log this as an error & move on?
601
                        // $this->log("Property ".$name." was circularly defined.", Project::MSG_ERR);
602 3
                        throw new BuildException("Property " . $propertyName . " was circularly defined.");
603
                    }
604
605 6
                    $fragment = $this->getProject()->getProperty($propertyName);
606 6
                    if ($fragment !== null) {
607 2
                        $sb .= $fragment;
608 2
                        continue;
609
                    }
610
611 5
                    if ($props->containsKey($propertyName)) {
612 5
                        $fragment = $props->getProperty($propertyName);
613 5
                        if (strpos($fragment, '${') !== false) {
0 ignored issues
show
Bug introduced by
It seems like $fragment can also be of type null; however, parameter $haystack of strpos() 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

613
                        if (strpos(/** @scrutinizer ignore-type */ $fragment, '${') !== false) {
Loading history...
614 3
                            $resolveStack[] = $propertyName;
615 5
                            $resolved = false; // parse again (could have been replaced w/ another var)
616
                        }
617
                    } else {
618
                        $fragment = "\${" . $propertyName . "}";
619
                    }
620
621 5
                    $sb .= $fragment;
622
                }
623
624 6
                $this->log("Resolved Property \"$value\" to \"$sb\"", Project::MSG_DEBUG);
625 6
                $value = $sb;
626 6
                $props->setProperty($name, $value);
627
            } // while (!$resolved)
628
        } // while (count($keys)
629 14
    }
630
}
631