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
 * Component creation and configuration
22
 *
23
 * @author Siad Ardroumli <siad.ardroumli@gmail.com>
24
 *
25
 * @package phing
26
 */
27
class PropertyHelper
28
{
29
    /**
30
     * @var Project $project
31
     */
32
    private $project;
33
    private $next;
34

35
    /**
36
     * Project properties map (usually String to String).
37
     */
38
    private $properties = [];
39

40
    /**
41
     * Map of "user" properties (as created in the Ant task, for example).
42
     * Note that these key/value pairs are also always put into the
43
     * project properties, so only the project properties need to be queried.
44
     * Mapping is String to String.
45
     */
46
    private $userProperties = [];
47

48
    /**
49
     * Map of inherited "user" properties - that are those "user"
50
     * properties that have been created by tasks and not been set
51
     * from the command line or a GUI tool.
52
     * Mapping is String to String.
53
     */
54
    private $inheritedProperties = [];
55

56
    // --------------------  Hook management  --------------------
57

58
    /**
59
     * Set the project for which this helper is performing property resolution
60
     *
61
     * @param Project $p the project instance.
62
     */
63 1
    private function setProject(Project $p)
64
    {
65 1
        $this->project = $p;
66
    }
67

68
    /**
69
     * There are 2 ways to hook into property handling:
70
     *  - you can replace the main PropertyHelper. The replacement is required
71
     * to support the same semantics (of course :-)
72
     *
73
     *  - you can chain a property helper capable of storing some properties.
74
     *  Again, you are required to respect the immutability semantics (at
75
     *  least for non-dynamic properties)
76
     *
77
     * @param PropertyHelper $next the next property helper in the chain.
78
     */
79 0
    public function setNext(PropertyHelper $next)
80
    {
81 0
        $this->next = $next;
82
    }
83

84
    /**
85
     * Get the next property helper in the chain.
86
     *
87
     * @return PropertyHelper the next property helper.
88
     */
89 1
    public function getNext()
90
    {
91 1
        return $this->next;
92
    }
93

94
    /**
95
     * Factory method to create a property processor.
96
     * Users can provide their own or replace it using "ant.PropertyHelper"
97
     * reference. User tasks can also add themselves to the chain, and provide
98
     * dynamic properties.
99
     *
100
     * @param Project $project the project fro which the property helper is required.
101
     *
102
     * @return PropertyHelper the project's property helper.
103
     */
104 1
    public static function getPropertyHelper(Project $project)
105
    {
106
        /**
107
         * @var PropertyHelper $helper
108
         */
109 1
        $helper = $project->getReference('phing.PropertyHelper');
110 1
        if ($helper !== null) {
111 1
            return $helper;
112
        }
113 1
        $helper = new self();
114 1
        $helper->setProject($project);
115

116 1
        $project->addReference('phing.PropertyHelper', $helper);
117 1
        return $helper;
118
    }
119

120
    // --------------------  Methods to override  --------------------
121

122
    /**
123
     * Sets a property. Any existing property of the same name
124
     * is overwritten, unless it is a user property. Will be called
125
     * from setProperty().
126
     *
127
     * If all helpers return false, the property will be saved in
128
     * the default properties table by setProperty.
129
     *
130
     * @param  string $ns The namespace that the property is in (currently
131
     *                          not used.
132
     * @param  string $name The name of property to set.
133
     *                          Must not be
134
     *                          <code>null</code>.
135
     * @param  string $value The new value of the property.
136
     *                          Must not be <code>null</code>.
137
     * @param  bool $inherited True if this property is inherited (an [sub]ant[call] property).
138
     * @param  bool $user True if this property is a user property.
139
     * @param  bool $isNew True is this is a new property.
140
     * @return bool true if this helper has stored the property, false if it
141
     *    couldn't. Each helper should delegate to the next one (unless it
142
     *    has a good reason not to).
143
     */
144 1
    public function setPropertyHook($ns, $name, $value, $inherited, $user, $isNew)
145
    {
146 1
        return $this->getNext() !== null
147 1
            && $this->getNext()->setPropertyHook($ns, $name, $value, $inherited, $user, $isNew);
148
    }
149

150
    /**
151
     * Get a property. If all hooks return null, the default
152
     * tables will be used.
153
     *
154
     * @param  string $ns namespace of the sought property.
155
     * @param  string $name name of the sought property.
156
     * @param  bool $user True if this is a user property.
157
     * @return string The property, if returned by a hook, or null if none.
158
     */
159 1
    public function getPropertyHook($ns, $name, $user)
160
    {
161 1
        if ($this->getNext() !== null) {
162 0
            $o = $this->getNext()->getPropertyHook($ns, $name, $user);
163 0
            if ($o !== null) {
164 0
                return $o;
165
            }
166
        }
167

168 1
        if ($this->project !== null && StringHelper::startsWith('toString:', $name)) {
169 1
            $name = StringHelper::substring($name, strlen('toString:'));
170 1
            $v = $this->project->getReference($name);
171 1
            return ($v === null) ? null : (string) $v;
172
        }
173

174 1
        return null;
175
    }
176

177
    // -------------------- Optional methods   --------------------
178
    // You can override those methods if you want to optimize or
179
    // do advanced things (like support a special syntax).
180
    // The methods do not chain - you should use them when embedding ant
181
    // (by replacing the main helper)
182

183
    /**
184
     * Replaces <code>${xxx}</code> style constructions in the given value
185
     * with the string value of the corresponding data types.
186
     *
187
     * @param string $value The string to be scanned for property references.
188
     *                        May be <code>null</code>, in which case this
189
     *                        method returns immediately with no effect.
190
     * @param string[] $keys Mapping (String to String) of property names to their
191
     *              values. If <code>null</code>, only project properties will
192
     *              be used.
193
     *
194
     * @return    string the original string with the properties replaced, or
195
     *         <code>null</code> if the original string is <code>null</code>.
196
     * @throws BuildException if the string contains an opening
197
     *                           <code>${</code> without a closing
198
     *                           <code>}</code>
199
     */
200 1
    public function replaceProperties($value, $keys): ?string
201
    {
202 1
        if ($value === null) {
203 0
            return null;
204
        }
205 1
        if ($keys === null) {
206 1
            $keys = $this->project->getProperties();
207
        }
208
        // Because we're not doing anything special (like multiple passes),
209
        // regex is the simplest / fastest.  PropertyTask, though, uses
210
        // the old parsePropertyString() method, since it has more stringent
211
        // requirements.
212

213 1
        $sb = $value;
214 1
        $iteration = 0;
215
        // loop to recursively replace tokens
216 1
        while (strpos($sb, '${') !== false) {
217 1
            $sb = preg_replace_callback(
218 1
                '/\$\{([^\$}]+)\}/',
219
                function ($matches) use ($keys) {
220 1
                    $propertyName = $matches[1];
221

222 1
                    $replacement = null;
223 1
                    if (array_key_exists($propertyName, $keys)) {
224 1
                        $replacement = $keys[$propertyName];
225
                    }
226

227 1
                    if ($replacement === null) {
228 1
                        $replacement = $this->getProperty(null, $propertyName);
229
                    }
230

231 1
                    if ($replacement === null) {
232 1
                        $this->project->log(
233 1
                            'Property ${' . $propertyName . '} has not been set.',
234 1
                            Project::MSG_VERBOSE
235
                        );
236

237 1
                        return $matches[0];
238
                    }
239

240 1
                    $this->project->log(
241 1
                        'Property ${' . $propertyName . '} => ' . (string) $replacement,
242 1
                        Project::MSG_VERBOSE
243
                    );
244

245 1
                    return $replacement;
246 1
                },
247 1
                $sb
248
            );
249

250
            // keep track of iterations so we can break out of otherwise infinite loops.
251 1
            $iteration++;
252 1
            if ($iteration === 5) {
253 1
                return $sb;
254
            }
255
        }
256

257 1
        return $sb;
258
    }
259

260
    // -------------------- Default implementation  --------------------
261
    // Methods used to support the default behavior and provide backward
262
    // compatibility. Some will be deprecated, you should avoid calling them.
263

264

265
    /**
266
     * Default implementation of setProperty. Will be called from Project.
267
     *  This is the original 1.5 implementation, with calls to the hook
268
     *  added.
269
     *
270
     * @param  string $ns The namespace for the property (currently not used).
271
     * @param  string $name The name of the property.
272
     * @param  string $value The value to set the property to.
273
     * @param  bool $verbose If this is true output extra log messages.
274
     * @return bool true if the property is set.
275
     */
276 1
    public function setProperty($ns, $name, $value, $verbose)
277
    {
278
        // user (CLI) properties take precedence
279 1
        if (isset($this->userProperties[$name])) {
280 1
            if ($verbose) {
281 0
                $this->project->log('Override ignored for user property ' . $name, Project::MSG_VERBOSE);
282
            }
283 1
            return false;
284
        }
285

286 1
        $done = $this->setPropertyHook($ns, $name, $value, false, false, false);
287 1
        if ($done) {
288 0
            return true;
289
        }
290

291 1
        if ($verbose && isset($this->properties[$name])) {
292 1
            $this->project->log(
293 1
                'Overriding previous definition of property ' . $name,
294 1
                Project::MSG_VERBOSE
295
            );
296
        }
297

298 1
        if ($verbose) {
299 1
            $this->project->log(
300 1
                'Setting project property: ' . $name . " -> "
301 1
                . $value,
302 1
                Project::MSG_DEBUG
303
            );
304
        }
305 1
        $this->properties[$name] = $value;
306 1
        $this->project->addReference($name, new PropertyValue($value));
307 1
        return true;
308
    }
309

310
    /**
311
     * Sets a property if no value currently exists. If the property
312
     * exists already, a message is logged and the method returns with
313
     * no other effect.
314
     *
315
     * @param string $ns The namespace for the property (currently not used).
316
     * @param string $name The name of property to set.
317
     *                      Must not be
318
     *                      <code>null</code>.
319
     * @param string $value The new value of the property.
320
     *              Must not be <code>null</code>.
321
     */
322 1
    public function setNewProperty($ns, $name, $value)
323
    {
324 1
        if (isset($this->properties[$name])) {
325 1
            $this->project->log('Override ignored for property ' . $name, Project::MSG_VERBOSE);
326 1
            return;
327
        }
328

329 1
        $done = $this->setPropertyHook($ns, $name, $value, false, false, true);
330 1
        if ($done) {
331 0
            return;
332
        }
333

334 1
        $this->project->log('Setting project property: ' . $name . " -> " . $value, Project::MSG_DEBUG);
335 1
        if ($name !== null && $value !== null) {
336 1
            $this->properties[$name] = $value;
337 1
            $this->project->addReference($name, new PropertyValue($value));
338
        }
339
    }
340

341
    /**
342
     * Sets a user property, which cannot be overwritten by
343
     * set/unset property calls. Any previous value is overwritten.
344
     *
345
     * @param string $ns The namespace for the property (currently not used).
346
     * @param string $name The name of property to set.
347
     *                      Must not be
348
     *                      <code>null</code>.
349
     * @param string $value The new value of the property.
350
     *              Must not be <code>null</code>.
351
     */
352 1
    public function setUserProperty($ns, $name, $value)
353
    {
354 1
        if ($name === null || $value === null) {
355 0
            return;
356
        }
357 1
        $this->project->log('Setting ro project property: ' . $name . ' -> ' . $value, Project::MSG_DEBUG);
358 1
        $this->userProperties[$name] = $value;
359

360 1
        $done = $this->setPropertyHook($ns, $name, $value, false, true, false);
361 1
        if ($done) {
362 0
            return;
363
        }
364 1
        $this->properties[$name] = $value;
365 1
        $this->project->addReference($name, new PropertyValue($value));
366
    }
367

368
    /**
369
     * Sets an inherited user property, which cannot be overwritten by set/unset
370
     * property calls. Any previous value is overwritten. Also marks
371
     * these properties as properties that have not come from the
372
     * command line.
373
     *
374
     * @param string $ns The namespace for the property (currently not used).
375
     * @param string $name The name of property to set.
376
     *                      Must not be
377
     *                      <code>null</code>.
378
     * @param string $value The new value of the property.
379
     *              Must not be <code>null</code>.
380
     */
381 1
    public function setInheritedProperty($ns, $name, $value)
382
    {
383 1
        if ($name === null || $value === null) {
384 0
            return;
385
        }
386 1
        $this->inheritedProperties[$name] = $value;
387

388 1
        $this->project->log(
389 1
            "Setting ro project property: " . $name . " -> "
390 1
            . $value,
391 1
            Project::MSG_DEBUG
392
        );
393 1
        $this->userProperties[$name] = $value;
394

395 1
        $done = $this->setPropertyHook($ns, $name, $value, true, false, false);
396 1
        if ($done) {
397 0
            return;
398
        }
399 1
        $this->properties[$name] = $value;
400 1
        $this->project->addReference($name, new PropertyValue($value));
401
    }
402

403
    // -------------------- Getting properties  --------------------
404

405
    /**
406
     * Returns the value of a property, if it is set.  You can override
407
     * this method in order to plug your own storage.
408
     *
409
     * @param  string $ns The namespace for the property (currently not used).
410
     * @param  string $name The name of the property.
411
     *             May be <code>null</code>, in which case
412
     *             the return value is also <code>null</code>.
413
     * @return string the property value, or <code>null</code> for no match
414
     *         or if a <code>null</code> name is provided.
415
     */
416 1
    public function getProperty($ns, $name)
417
    {
418 1
        if ($name === null) {
419 0
            return null;
420
        }
421 1
        $o = $this->getPropertyHook($ns, $name, false);
422 1
        if ($o !== null) {
423 1
            return $o;
424
        }
425

426 1
        $found = $this->properties[$name] ?? null;
427
        // check to see if there are unresolved property references
428 1
        if (false !== strpos($found, '${')) {
429
            // attempt to resolve properties
430 1
            $found = $this->replaceProperties($found, null);
431
            // save resolved value
432 1
            $this->properties[$name] = $found;
433
        }
434

435 1
        return $found;
436
    }
437

438
    /**
439
     * Returns the value of a user property, if it is set.
440
     *
441
     * @param  string $ns The namespace for the property (currently not used).
442
     * @param  string $name The name of the property.
443
     *             May be <code>null</code>, in which case
444
     *             the return value is also <code>null</code>.
445
     * @return string the property value, or <code>null</code> for no match
446
     *         or if a <code>null</code> name is provided.
447
     */
448 1
    public function getUserProperty($ns, $name)
449
    {
450 1
        if ($name === null) {
451 0
            return null;
452
        }
453 1
        $o = $this->getPropertyHook($ns, $name, true);
454 1
        if ($o !== null) {
455 0
            return $o;
456
        }
457 1
        return $this->userProperties[$name] ?? null;
458
    }
459

460

461
    // -------------------- Access to property tables  --------------------
462
    // This is used to support ant call and similar tasks. It should be
463
    // deprecated, it is possible to use a better (more efficient)
464
    // mechanism to preserve the context.
465

466
    /**
467
     * Returns a copy of the properties table.
468
     *
469
     * @return array a hashtable containing all properties
470
     *         (including user properties).
471
     */
472 1
    public function getProperties()
473
    {
474 1
        return $this->properties;
475
    }
476

477
    /**
478
     * Returns a copy of the user property hashtable
479
     *
480
     * @return array a hashtable containing just the user properties
481
     */
482 0
    public function getUserProperties()
483
    {
484 0
        return $this->userProperties;
485
    }
486

487 0
    public function getInheritedProperties()
488
    {
489 0
        return $this->inheritedProperties;
490
    }
491

492
    /**
493
     * Copies all user properties that have not been set on the
494
     * command line or a GUI tool from this instance to the Project
495
     * instance given as the argument.
496
     *
497
     * <p>To copy all "user" properties, you will also have to call
498
     * {@link #copyUserProperties copyUserProperties}.</p>
499
     *
500
     * @param Project $other the project to copy the properties to.  Must not be null.
501
     */
502 0
    public function copyInheritedProperties(Project $other)
503
    {
504 0
        foreach ($this->inheritedProperties as $arg => $value) {
505 0
            if ($other->getUserProperty($arg) === null) {
506 0
                $other->setInheritedProperty($arg, (string) $this->inheritedProperties[$arg]);
507
            }
508
        }
509
    }
510

511
    /**
512
     * Copies all user properties that have been set on the command
513
     * line or a GUI tool from this instance to the Project instance
514
     * given as the argument.
515
     *
516
     * <p>To copy all "user" properties, you will also have to call
517
     * {@link #copyInheritedProperties copyInheritedProperties}.</p>
518
     *
519
     * @param Project $other the project to copy the properties to.  Must not be null.
520
     */
521 1
    public function copyUserProperties(Project $other)
522
    {
523 1
        foreach ($this->userProperties as $arg => $value) {
524 1
            if (!isset($this->inheritedProperties[$arg])) {
525 1
                $other->setUserProperty($arg, $value);
526
            }
527
        }
528
    }
529

530
    /**
531
     * Parses a string containing <code>${xxx}</code> style property
532
     * references into two lists. The first list is a collection
533
     * of text fragments, while the other is a set of string property names.
534
     * <code>null</code> entries in the first list indicate a property
535
     * reference from the second list.
536
     *
537
     * It can be overridden with a more efficient or customized version.
538
     *
539
     * @param string $value Text to parse. Must not be <code>null</code>.
540
     * @param array $fragments List to add text fragments to.
541
     *                             Must not be <code>null</code>.
542
     * @param array $propertyRefs List to add property names to.
543
     *                             Must not be <code>null</code>.
544
     *
545
     * @throws BuildException if the string contains an opening
546
     *                           <code>${</code> without a closing
547
     *                           <code>}</code>
548
     */
549 1
    public function parsePropertyString($value, &$fragments, &$propertyRefs)
550
    {
551 1
        $prev = 0;
552 1
        $pos = 0;
553

554 1
        while (($pos = strpos($value, '$', $prev)) !== false) {
555 1
            if ($pos > $prev) {
556 1
                $fragments[] = StringHelper::substring($value, $prev, $pos - 1);
557
            }
558 1
            if ($pos === (strlen($value) - 1)) {
559 0
                $fragments[] = '$';
560 0
                $prev = $pos + 1;
561 1
            } elseif ($value[$pos + 1] !== '{') {
562
                // the string positions were changed to value-1 to correct
563
                // a fatal error coming from function substring()
564 0
                $fragments[] = StringHelper::substring($value, $pos, $pos + 1);
565 0
                $prev = $pos + 2;
566
            } else {
567 1
                $endName = strpos($value, '}', $pos);
568 1
                if ($endName === false) {
569 0
                    throw new BuildException("Syntax error in property: $value");
570
                }
571 1
                $propertyName = StringHelper::substring($value, $pos + 2, $endName - 1);
572 1
                $fragments[] = null;
573 1
                $propertyRefs[] = $propertyName;
574 1
                $prev = $endName + 1;
575
            }
576
        }
577

578 1
        if ($prev < strlen($value)) {
579 1
            $fragments[] = StringHelper::substring($value, $prev);
580
        }
581
    }
582
}

Read our documentation on viewing source code .

Loading