Completed
Push — master ( bc194a...d90c8b )
by Siad
17:01
created

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