Passed
Push — master ( ec5d4d...d68b8d )
by Siad
11:29
created

PropertyTask::addProperty()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 7

Importance

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