Passed
Push — master ( e1f86a...4e1a3a )
by Siad
05:23
created

PropertyTask::getFile()   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 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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
namespace Phing\Tasks\System;
21
22
use Phing\Exception\BuildException;
23
use Phing\Exception\NullPointerException;
24
use Phing\Io\FileParserFactory;
25
use Phing\Io\FileParserFactoryInterface;
26
use Phing\Io\FileReader;
27
use Phing\Io\FileUtils;
28
use Phing\Io\IOException;
29
use Phing\Io\File;
30
use Phing\Io\StringReader;
31
use Phing\Project;
32
use Phing\PropertyHelper;
33
use Phing\Task;
34
use Phing\Type\Element\FilterChainAware;
35
use Phing\Type\Reference;
36
use Phing\Util\Properties;
37
use Phing\Util\StringHelper;
38
39
/**
40
 * Task for setting properties in buildfiles.
41
 *
42
 * @author  Andreas Aderhold <[email protected]>
43
 * @author  Hans Lellelid <[email protected]>
44
 * @package phing.tasks.system
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 $value 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 465
    public function __construct(FileParserFactoryInterface $fileParserFactory = null)
119
    {
120 465
        parent::__construct();
121 465
        $this->fileParserFactory = $fileParserFactory ?? new FileParserFactory();
122 465
    }
123
124
    /**
125
     * File required or not.
126
     *
127
     * @param string $d
128
     */
129 1
    public function setRequired($d)
130
    {
131 1
        $this->required = $d;
132 1
    }
133
134
    /**
135
     * @return string
136
     */
137
    public function getRequired()
138
    {
139
        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...
140
    }
141
142
    /**
143
     * Sets a the name of current property component
144
     *
145
     * @param string $name
146
     */
147 458
    public function setName(string $name): void
148
    {
149 458
        $this->name = $name;
150 458
    }
151
152
    /**
153
     * Get property component name.
154
     */
155 12
    public function getName()
156
    {
157 12
        return $this->name;
158
    }
159
160
    /**
161
     * Sets a the value of current property component.
162
     *
163
     * @param string $value Value of name, all scalars allowed
164
     */
165 458
    public function setValue(string $value): void
166
    {
167 458
        $this->value = $value;
168 458
    }
169
170
    /**
171
     * Sets value of property to CDATA tag contents.
172
     *
173
     * @param string $value
174
     * @since    2.2.0
175
     */
176
    public function addText(string $value): void
177
    {
178
        $this->setValue($value);
179
    }
180
181
    /**
182
     * Get the value of current property component.
183
     */
184 7
    public function getValue()
185
    {
186 7
        return $this->value;
187
    }
188
189
    /**
190
     * Set a file to use as the source for properties.
191
     *
192
     * @param $file
193
     *
194
     * @throws IOException
195
     * @throws NullPointerException
196
     */
197 18
    public function setFile($file)
198
    {
199 18
        if (is_string($file)) {
200 18
            $file = new File($file);
201
        }
202 18
        $this->file = $file;
203 18
    }
204
205
    /**
206
     * Get the PhingFile that is being used as property source.
207
     */
208 6
    public function getFile()
209
    {
210 6
        return $this->file;
211
    }
212
213
    /**
214
     * @param Reference $ref
215
     */
216
    public function setRefid(Reference $ref): void
217
    {
218
        $this->reference = $ref;
219
    }
220
221 6
    public function getRefid()
222
    {
223 6
        return $this->reference;
224
    }
225
226
    /**
227
     * Prefix to apply to properties loaded using <code>file</code>.
228
     * A "." is appended to the prefix if not specified.
229
     *
230
     * @param  string $prefix prefix string
231
     *
232
     * @since  2.0
233
     */
234 2
    public function setPrefix(string $prefix): void
235
    {
236 2
        $this->prefix = $prefix;
237 2
        if (!StringHelper::endsWith(".", $prefix)) {
238 2
            $this->prefix .= ".";
239
        }
240 2
    }
241
242
    /**
243
     * @return string
244
     * @since 2.0
245
     */
246 6
    public function getPrefix()
247
    {
248 6
        return $this->prefix;
249
    }
250
251
    /**
252
     * the prefix to use when retrieving environment variables.
253
     * Thus if you specify environment="myenv"
254
     * you will be able to access OS-specific
255
     * environment variables via property names "myenv.PATH" or
256
     * "myenv.TERM".
257
     * <p>
258
     * Note that if you supply a property name with a final
259
     * "." it will not be doubled. ie environment="myenv." will still
260
     * allow access of environment variables through "myenv.PATH" and
261
     * "myenv.TERM". This functionality is currently only implemented
262
     * on select platforms. Feel free to send patches to increase the number of platforms
263
     * this functionality is supported on ;).<br>
264
     * Note also that properties are case sensitive, even if the
265
     * environment variables on your operating system are not, e.g. it
266
     * will be ${env.Path} not ${env.PATH} on Windows 2000.
267
     *
268
     * @param string $env
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
     * @param boolean $v
287
     */
288 12
    public function setUserProperty(bool $v): void
289
    {
290 12
        $this->userProperty = $v;
291 12
    }
292
293
    /**
294
     * @return bool
295
     */
296 6
    public function getUserProperty()
297
    {
298 6
        return $this->userProperty;
299
    }
300
301
    /**
302
     * @param $v
303
     */
304 8
    public function setOverride(bool $v): void
305
    {
306 8
        $this->override = $v;
307 8
    }
308
309
    /**
310
     * @return bool
311
     */
312 6
    public function getOverride()
313
    {
314 6
        return $this->override;
315
    }
316
317
    /**
318
     * @return string
319
     */
320 1
    public function __toString()
321
    {
322 1
        return $this->value;
323
    }
324
325
    /**
326
     * @param Project $p
327
     */
328 12
    public function setFallback($p): void
329
    {
330 12
        $this->fallback = $p;
331 12
    }
332
333
    public function getFallback()
334
    {
335
        return $this->fallback;
336
    }
337
338
    /**
339
     * @param $logOutput
340
     */
341 6
    public function setLogoutput(bool $logOutput): void
342
    {
343 6
        $this->logOutput = $logOutput;
344 6
    }
345
346
    /**
347
     * @return bool
348
     */
349 6
    public function getLogoutput()
350
    {
351 6
        return $this->logOutput;
352
    }
353
354
    /**
355
     * Set quiet mode, which suppresses warnings if chmod() fails.
356
     *
357
     * @see   setFailonerror()
358
     * @param $bool
359
     */
360 6
    public function setQuiet(bool $bool): void
361
    {
362 6
        $this->quiet = $bool;
363 6
    }
364
365
    /**
366
     * @return bool
367
     */
368 6
    public function getQuiet(): bool
369
    {
370 6
        return $this->quiet;
371
    }
372
373
    /**
374
     * set the property in the project to the value.
375
     * if the task was give a file or env attribute
376
     * here is where it is loaded
377
     */
378 465
    public function main()
379
    {
380 465
        $this->validate();
381
382 464
        if ($this->name !== null && $this->value !== null) {
383 457
            $this->addProperty($this->name, $this->value);
384
        }
385
386 464
        if ($this->file !== null) {
387 17
            $this->loadFile($this->file);
388
        }
389
390 461
        if ($this->env !== null) {
391 2
            $this->loadEnvironment($this->env);
392
        }
393
394 461
        if ($this->name !== null && $this->reference !== null) {
395
            // get the refereced property
396
            try {
397
                $referencedObject = $this->reference->getReferencedObject($this->project);
398
399
                if ($referencedObject instanceof \Exception) {
400
                    $reference = $referencedObject->getMessage();
401
                } else {
402
                    $reference = (string) $referencedObject;
403
                }
404
405
                $this->addProperty($this->name, $reference);
406
            } catch (BuildException $be) {
407
                if ($this->fallback !== null) {
408
                    $referencedObject = $this->reference->getReferencedObject($this->fallback);
409
410
                    if ($referencedObject instanceof \Exception) {
411
                        $reference = $referencedObject->getMessage();
412
                    } else {
413
                        $reference = (string) $referencedObject;
414
                    }
415
                    $this->addProperty($this->name, $reference);
416
                } else {
417
                    throw $be;
418
                }
419
            }
420
        }
421 461
    }
422
423
    /**
424
     * @throws BuildException
425
     */
426 465
    private function validate(): void
427
    {
428 465
        if ($this->name !== null) {
429 458
            if ($this->value === null && $this->reference === null) {
430
                throw new BuildException(
431
                    "You must specify value or refid with the name attribute",
432 458
                    $this->getLocation()
433
                );
434
            }
435 19
        } elseif ($this->file === null && $this->env === null) {
436
            throw new BuildException(
437
                "You must specify file or environment when not using the name attribute",
438
                $this->getLocation()
439
            );
440
        }
441
442 465
        if ($this->file === null && $this->prefix !== null) {
443 1
            throw new BuildException('Prefix is only valid when loading from a file.', $this->getLocation());
444
        }
445 464
    }
446
447
    /**
448
     * load the environment values
449
     *
450
     * @param string $prefix prefix to place before them
451
     */
452 2
    protected function loadEnvironment(string $prefix)
453
    {
454 2
        $props = new Properties();
455 2
        if (substr($prefix, strlen($prefix) - 1) === '.') {
456
            $prefix .= ".";
457
        }
458 2
        $this->log("Loading Environment $prefix", Project::MSG_VERBOSE);
459 2
        foreach ($_ENV as $key => $value) {
460
            $props->setProperty($prefix . '.' . $key, $value);
461
        }
462 2
        $this->addProperties($props);
463 2
    }
464
465
    /**
466
     * iterate through a set of properties,
467
     * resolve them then assign them
468
     *
469
     * @param Properties $props
470
     * @throws BuildException
471
     */
472 18
    protected function addProperties($props)
473
    {
474 18
        $this->resolveAllProperties($props);
475 15
        foreach ($props->keys() as $name) {
476 13
            $value = $props->getProperty($name);
477 13
            $v = $this->project->replaceProperties($value);
478 13
            if ($this->prefix !== null) {
479 1
                $name = $this->prefix . $name;
480
            }
481 13
            $this->addProperty($name, $v);
482
        }
483 15
    }
484
485
    /**
486
     * add a name value pair to the project property set
487
     *
488
     * @param string $name name of property
489
     * @param string $value value to set
490
     */
491 460
    protected function addProperty($name, $value)
492
    {
493 460
        if ($this->file === null && count($this->filterChains) > 0) {
494 1
            $in = FileUtils::getChainedReader(new StringReader($value), $this->filterChains, $this->project);
495 1
            $value = $in->read();
496
        }
497
498 460
        $ph = PropertyHelper::getPropertyHelper($this->getProject());
499 460
        if ($this->userProperty) {
500 12
            if ($ph->getUserProperty(null, $name) === null || $this->override) {
501 10
                $ph->setInheritedProperty(null, $name, $value);
502
            } else {
503 12
                $this->log('Override ignored for ' . $name, Project::MSG_VERBOSE);
504
            }
505
        } else {
506 457
            if ($this->override) {
507 2
                $ph->setProperty(null, $name, $value, true);
508
            } else {
509 457
                $ph->setNewProperty(null, $name, $value);
510
            }
511
        }
512 460
    }
513
514
    /**
515
     * load properties from a file.
516
     *
517
     * @param  File $file
518
     * @throws BuildException
519
     */
520 15
    protected function loadFile(File $file)
521
    {
522 15
        $fileParser = $this->fileParserFactory->createParser($file->getFileExtension());
523 15
        $props = new Properties(null, $fileParser);
524 15
        $this->log("Loading " . $file->getAbsolutePath(), $this->logOutput ? Project::MSG_INFO : Project::MSG_VERBOSE);
525
        try { // try to load file
526 15
            if ($file->exists()) {
527 14
                $value = null;
528 14
                if (count($this->filterChains) > 0) {
529 1
                    $in = FileUtils::getChainedReader(new FileReader($file), $this->filterChains, $this->project);
530 1
                    $value = $in->read();
531
                }
532 14
                if ($value) {
533 1
                    foreach (array_filter(explode(PHP_EOL, $value)) as $line) {
534 1
                        [$key, $prop] = explode('=', $line);
535 1
                        $props->setProperty($key, $prop);
536
                    }
537
                } else {
538 13
                    $props->load($file);
539
                }
540 14
                $this->addProperties($props);
541
            } else {
542 1
                if ($this->required) {
543 1
                    throw new BuildException("Unable to find property file: " . $file->getAbsolutePath());
544
                }
545
546
                $this->log(
547
                    "Unable to find property file: " . $file->getAbsolutePath() . "... skipped",
548 13
                    $this->quiet ? Project::MSG_VERBOSE : Project::MSG_WARN
549
                );
550
            }
551 2
        } catch (IOException $ioe) {
552
            throw new BuildException("Could not load properties from file.", $ioe);
553
        }
554 13
    }
555
556
    /**
557
     * Given a Properties object, this method goes through and resolves
558
     * any references to properties within the object.
559
     *
560
     * @param  Properties $props The collection of Properties that need to be resolved.
561
     * @throws BuildException
562
     * @return void
563
     */
564 18
    protected function resolveAllProperties(Properties $props)
565
    {
566 18
        foreach ($props->keys() as $name) {
567
            // There may be a nice regex/callback way to handle this
568
            // replacement, but at the moment it is pretty complex, and
569
            // would probably be a lot uglier to work into a preg_replace_callback()
570
            // system.  The biggest problem is the fact that a resolution may require
571
            // multiple passes.
572
573 16
            $value = $props->getProperty($name);
574 16
            $resolved = false;
575 16
            $resolveStack = [];
576
577 16
            while (!$resolved) {
578 16
                $fragments = [];
579 16
                $propertyRefs = [];
580
581 16
                PropertyHelper::getPropertyHelper($this->project)->parsePropertyString(
582 16
                    $value,
583
                    $fragments,
584
                    $propertyRefs
585
                );
586
587 16
                $resolved = true;
588 16
                if (count($propertyRefs) === 0) {
589 13
                    continue;
590
                }
591
592 6
                $sb = "";
593
594 6
                $j = $propertyRefs;
595
596 6
                foreach ($fragments as $fragment) {
597 6
                    if ($fragment !== null) {
598 6
                        $sb .= $fragment;
599 6
                        continue;
600
                    }
601
602 6
                    $propertyName = array_shift($j);
603 6
                    if (in_array($propertyName, $resolveStack)) {
604
                        // Should we maybe just log this as an error & move on?
605
                        // $this->log("Property ".$name." was circularly defined.", Project::MSG_ERR);
606 3
                        throw new BuildException("Property " . $propertyName . " was circularly defined.");
607
                    }
608
609 6
                    $fragment = $this->getProject()->getProperty($propertyName);
610 6
                    if ($fragment !== null) {
611 2
                        $sb .= $fragment;
612 2
                        continue;
613
                    }
614
615 5
                    if ($props->containsKey($propertyName)) {
616 5
                        $fragment = $props->getProperty($propertyName);
617 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

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