1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<?php
/**
* Exception class for Services_Twilio_Twiml.
*/
class Services_Twilio_TwimlException extends Exception {}
/**
* Twiml response generator.
*
* Author: Neuman Vong <neuman at ashmoremusic dot com>
* License: http://creativecommons.org/licenses/MIT/ MIT
*/
class Services_Twilio_Twiml {
protected $element;
/**
* Constructs a Twiml response.
*
* :param SimpleXmlElement|array $arg: Can be any of
*
* - the element to wrap
* - attributes to add to the element
* - if null, initialize an empty element named 'Response'
*/
public function __construct($arg = null) {
switch (true) {
case $arg instanceof SimpleXmlElement:
$this->element = $arg;
break;
case $arg === null:
$this->element = new SimpleXmlElement('<Response/>');
break;
case is_array($arg):
$this->element = new SimpleXmlElement('<Response/>');
foreach ($arg as $name => $value) {
$this->element->addAttribute($name, $value);
}
break;
default:
throw new Services_Twilio_TwimlException('Invalid argument');
}
}
/**
* Converts method calls into Twiml verbs.
*
* A basic example:
*
* .. code-block:: php
*
* php> print $this->say('hello');
* <Say>hello</Say>
*
* An example with attributes:
*
* .. code-block:: php
*
* print $this->say('hello', array('voice' => 'woman'));
* <Say voice="woman">hello</Say>
*
* You could even just pass in an attributes array, omitting the noun:
*
* .. code-block:: php
*
* print $this->gather(array('timeout' => '20'));
* <Gather timeout="20"/>
*
* :param string $verb: The Twiml verb.
* :param array $args:
* - (noun string)
* - (noun string, attributes array)
* - (attributes array)
*
* :return: A SimpleXmlElement
* :rtype: SimpleXmlElement
*/
public function __call($verb, array $args)
{
list($noun, $attrs) = $args + array('', array());
if (is_array($noun)) {
list($attrs, $noun) = array($noun, '');
}
/* addChild does not escape XML, while addAttribute does. This means if
* you pass unescaped ampersands ("&") to addChild, you will generate
* an error.
*
* Some inexperienced developers will pass in unescaped ampersands, and
* we want to make their code work, by escaping the ampersands for them
* before passing the string to addChild. (with htmlentities)
*
* However other people will know what to do, and their code
* already escapes ampersands before passing them to addChild. We don't
* want to break their existing code by turning their &'s into
* &amp;
*
* We also want to use numeric entities, not named entities so that we
* are fully compatible with XML
*
* The following lines accomplish the desired behavior.
*/
$decoded = html_entity_decode($noun, ENT_COMPAT, 'UTF-8');
$normalized = htmlspecialchars($decoded, ENT_COMPAT, 'UTF-8', false);
$child = empty($noun)
? $this->element->addChild(ucfirst($verb))
: $this->element->addChild(ucfirst($verb), $normalized);
foreach ($attrs as $name => $value) {
/* Note that addAttribute escapes raw ampersands by default, so we
* haven't touched its implementation. So this is the matrix for
* addAttribute:
*
* & turns into &
* & turns into &amp;
*/
if (is_bool($value)) {
$value = ($value === true) ? 'true' : 'false';
}
$child->addAttribute($name, $value);
}
return new static($child);
}
/**
* Returns the object as XML.
*
* :return: The response as an XML string
* :rtype: string
*/
public function __toString()
{
$xml = $this->element->asXml();
return str_replace(
'<?xml version="1.0"?>',
'<?xml version="1.0" encoding="UTF-8"?>', $xml);
}
}