Completed
Push — master ( 50d944...054c72 )
by Siad
12:51
created

PropertyTask::setRequired()   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 1
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
/**
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 401
    public function __construct(FileParserFactoryInterface $fileParserFactory = null)
100
    {
101 401
        parent::__construct();
102 401
        $this->fileParserFactory = $fileParserFactory ?? new FileParserFactory();
103 401
    }
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 395
    public function setName(string $name): void
129
    {
130 395
        $this->name = $name;
131 395
    }
132
133
    /**
134
     * Get property component name.
135
     */
136 6
    public function getName()
137
    {
138 6
        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 395
    public function setValue(string $value): void
147
    {
148 395
        $this->value = $value;
149 395
    }
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 17
    public function setFile($file)
179
    {
180 17
        if (is_string($file)) {
181 17
            $file = new PhingFile($file);
182
        }
183 17
        $this->file = $file;
184 17
    }
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 7
    public function setUserProperty(bool $v): void
270
    {
271 7
        $this->userProperty = $v;
272 7
    }
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 9
    public function setOverride(bool $v): void
286
    {
287 9
        $this->override = $v;
288 9
    }
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 3
    public function __toString()
302
    {
303 3
        return $this->value;
304
    }
305
306
    /**
307
     * @param Project $p
308
     */
309 7
    public function setFallback($p): void
310
    {
311 7
        $this->fallback = $p;
312 7
    }
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 401
    public function main()
360
    {
361 401
        if ($this->name !== null) {
362 395
            if ($this->value === null && $this->reference === null) {
363
                throw new BuildException(
364
                    "You must specify value or refid with the name attribute",
365
                    $this->getLocation()
366
                );
367
            }
368
        } else {
369 18
            if ($this->file === null && $this->env === null) {
370
                throw new BuildException(
371
                    "You must specify file or environment when not using the name attribute",
372
                    $this->getLocation()
373
                );
374
            }
375
        }
376
377 401
        if ($this->file === null && $this->prefix !== null) {
378 1
            throw new BuildException("Prefix is only valid when loading from a file.", $this->getLocation());
379
        }
380
381 400
        if ($this->name !== null && $this->value !== null) {
382 394
            $this->addProperty($this->name, $this->value);
383
        }
384
385 400
        if ($this->file !== null) {
386 16
            $this->loadFile($this->file);
387
        }
388
389 397
        if ($this->env !== null) {
390 2
            $this->loadEnvironment($this->env);
391
        }
392
393 397
        if ($this->name !== null && $this->reference !== null) {
394
            // get the refereced property
395
            try {
396
                $referencedObject = $this->reference->getReferencedObject($this->project);
397
398
                if ($referencedObject instanceof Exception) {
399
                    $reference = $referencedObject->getMessage();
400
                } else {
401
                    $reference = (string) $referencedObject;
402
                }
403
404
                $this->addProperty($this->name, $reference);
405
            } catch (BuildException $be) {
406
                if ($this->fallback !== null) {
407
                    $referencedObject = $this->reference->getReferencedObject($this->fallback);
408
409
                    if ($referencedObject instanceof Exception) {
410
                        $reference = $referencedObject->getMessage();
411
                    } else {
412
                        $reference = (string) $referencedObject;
413
                    }
414
                    $this->addProperty($this->name, $reference);
415
                } else {
416
                    throw $be;
417
                }
418
            }
419
        }
420 397
    }
421
422
    /**
423
     * load the environment values
424
     *
425
     * @param string $prefix prefix to place before them
426
     */
427 2
    protected function loadEnvironment(string $prefix)
428
    {
429 2
        $props = new Properties();
430 2
        if (substr($prefix, strlen($prefix) - 1) === '.') {
431
            $prefix .= ".";
432
        }
433 2
        $this->log("Loading Environment $prefix", Project::MSG_VERBOSE);
434 2
        foreach ($_ENV as $key => $value) {
435
            $props->setProperty($prefix . '.' . $key, $value);
436
        }
437 2
        $this->addProperties($props);
438 2
    }
439
440
    /**
441
     * iterate through a set of properties,
442
     * resolve them then assign them
443
     *
444
     * @param  $props
445
     * @throws BuildException
446
     */
447 17
    protected function addProperties($props)
448
    {
449 17
        $this->resolveAllProperties($props);
450 14
        foreach ($props->keys() as $name) {
451 12
            $value = $props->getProperty($name);
452 12
            $v = $this->project->replaceProperties($value);
453 12
            if ($this->prefix !== null) {
454 1
                $name = $this->prefix . $name;
455
            }
456 12
            $this->addProperty($name, $v);
457
        }
458 14
    }
459
460
    /**
461
     * add a name value pair to the project property set
462
     *
463
     * @param string $name name of property
464
     * @param string $value value to set
465
     */
466 396
    protected function addProperty($name, $value)
467
    {
468 396
        if (count($this->filterChains) > 0) {
469 1
            $in = FileUtils::getChainedReader(new StringReader($value), $this->filterChains, $this->project);
470 1
            $value = $in->read();
471
        }
472
473 396
        $ph = PropertyHelper::getPropertyHelper($this->getProject());
474 396
        if ($this->userProperty) {
475 7
            if ($ph->getUserProperty(null, $name) === null || $this->override) {
476 7
                $ph->setInheritedProperty(null, $name, $value);
477
            } else {
478
                $this->log('Override ignored for ' . $name, Project::MSG_VERBOSE);
479
            }
480
        } else {
481 394
            if ($this->override) {
482 2
                $ph->setProperty(null, $name, $value, true);
483
            } else {
484 394
                $ph->setNewProperty(null, $name, $value);
485
            }
486
        }
487 396
    }
488
489
    /**
490
     * load properties from a file.
491
     *
492
     * @param  PhingFile $file
493
     * @throws BuildException
494
     */
495 14
    protected function loadFile(PhingFile $file)
496
    {
497 14
        $fileParser = $this->fileParserFactory->createParser($file->getFileExtension());
498 14
        $props = new Properties(null, $fileParser);
499 14
        $this->log("Loading " . $file->getAbsolutePath(), $this->logOutput ? Project::MSG_INFO : Project::MSG_VERBOSE);
500
        try { // try to load file
501 14
            if ($file->exists()) {
502 13
                $props->load($file);
503 13
                $this->addProperties($props);
504
            } else {
505 1
                if ($this->required) {
506 1
                    throw new BuildException("Unable to find property file: " . $file->getAbsolutePath());
507
                }
508
509
                $this->log(
510
                    "Unable to find property file: " . $file->getAbsolutePath() . "... skipped",
511
                    $this->quiet ? Project::MSG_VERBOSE : Project::MSG_WARN
512
                );
513
            }
514 2
        } catch (IOException $ioe) {
515
            throw new BuildException("Could not load properties from file.", $ioe);
516
        }
517 12
    }
518
519
    /**
520
     * Given a Properties object, this method goes through and resolves
521
     * any references to properties within the object.
522
     *
523
     * @param  Properties $props The collection of Properties that need to be resolved.
524
     * @throws BuildException
525
     * @return void
526
     */
527 17
    protected function resolveAllProperties(Properties $props)
528
    {
529 17
        foreach ($props->keys() as $name) {
530
            // There may be a nice regex/callback way to handle this
531
            // replacement, but at the moment it is pretty complex, and
532
            // would probably be a lot uglier to work into a preg_replace_callback()
533
            // system.  The biggest problem is the fact that a resolution may require
534
            // multiple passes.
535
536 15
            $value = $props->getProperty($name);
537 15
            $resolved = false;
538 15
            $resolveStack = [];
539
540 15
            while (!$resolved) {
541 15
                $fragments = [];
542 15
                $propertyRefs = [];
543
544 15
                PropertyHelper::getPropertyHelper($this->project)->parsePropertyString(
545 15
                    $value,
546 15
                    $fragments,
547 15
                    $propertyRefs
548
                );
549
550 15
                $resolved = true;
551 15
                if (count($propertyRefs) === 0) {
552 12
                    continue;
553
                }
554
555 6
                $sb = "";
556
557 6
                $j = $propertyRefs;
558
559 6
                foreach ($fragments as $fragment) {
560 6
                    if ($fragment !== null) {
561 6
                        $sb .= $fragment;
562 6
                        continue;
563
                    }
564
565 6
                    $propertyName = array_shift($j);
566 6
                    if (in_array($propertyName, $resolveStack)) {
567
                        // Should we maybe just log this as an error & move on?
568
                        // $this->log("Property ".$name." was circularly defined.", Project::MSG_ERR);
569 3
                        throw new BuildException("Property " . $propertyName . " was circularly defined.");
570
                    }
571
572 6
                    $fragment = $this->getProject()->getProperty($propertyName);
573 6
                    if ($fragment !== null) {
574 3
                        $sb .= $fragment;
575 3
                        continue;
576
                    }
577
578 5
                    if ($props->containsKey($propertyName)) {
579 5
                        $fragment = $props->getProperty($propertyName);
580 5
                        if (strpos($fragment, '${') !== false) {
581 3
                            $resolveStack[] = $propertyName;
582 3
                            $resolved = false; // parse again (could have been replaced w/ another var)
583
                        }
584
                    } else {
585
                        $fragment = "\${" . $propertyName . "}";
586
                    }
587
588 5
                    $sb .= $fragment;
589
                }
590
591 6
                $this->log("Resolved Property \"$value\" to \"$sb\"", Project::MSG_DEBUG);
592 6
                $value = $sb;
593 6
                $props->setProperty($name, $value);
594
            } // while (!$resolved)
595
        } // while (count($keys)
596 14
    }
597
}
598