|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* RestfulService class allows you to consume various RESTful APIs. |
|
4
|
|
|
* Through this you could connect and aggregate data of various web services. |
|
5
|
|
|
* For more info visit wiki documentation - http://doc.silverstripe.org/doku.php?id=restfulservice |
|
6
|
|
|
* |
|
7
|
|
|
* @package framework |
|
8
|
|
|
* @subpackage integration |
|
9
|
|
|
*/ |
|
10
|
|
|
class RestfulService extends ViewableData implements Flushable { |
|
11
|
|
|
|
|
12
|
|
|
protected $baseURL; |
|
13
|
|
|
protected $queryString; |
|
14
|
|
|
protected $errorTag; |
|
15
|
|
|
protected $checkErrors; |
|
16
|
|
|
protected $cache_expire; |
|
17
|
|
|
protected $authUsername, $authPassword; |
|
|
|
|
|
|
18
|
|
|
protected $customHeaders = array(); |
|
19
|
|
|
protected $proxy; |
|
20
|
|
|
|
|
21
|
|
|
/** |
|
22
|
|
|
* @config |
|
23
|
|
|
* @var array |
|
24
|
|
|
*/ |
|
25
|
|
|
private static $default_proxy; |
|
26
|
|
|
|
|
27
|
|
|
/** |
|
28
|
|
|
* @config |
|
29
|
|
|
* @var array |
|
30
|
|
|
*/ |
|
31
|
|
|
private static $default_curl_options = array(); |
|
32
|
|
|
|
|
33
|
|
|
/** |
|
34
|
|
|
* @config |
|
35
|
|
|
* @var bool Flushes caches if set to true. This is set by {@link flush()} |
|
36
|
|
|
*/ |
|
37
|
|
|
private static $flush = false; |
|
38
|
|
|
|
|
39
|
|
|
/** |
|
40
|
|
|
* Triggered early in the request when someone requests a flush. |
|
41
|
|
|
*/ |
|
42
|
|
|
public static function flush() { |
|
43
|
|
|
self::$flush = true; |
|
44
|
|
|
} |
|
45
|
|
|
|
|
46
|
|
|
/** |
|
47
|
|
|
* set a curl option that will be applied to all requests as default |
|
48
|
|
|
* {@see http://php.net/manual/en/function.curl-setopt.php#refsect1-function.curl-setopt-parameters} |
|
49
|
|
|
* |
|
50
|
|
|
* @deprecated 4.0 Use the "RestfulService.default_curl_options" config setting instead |
|
51
|
|
|
* @param int $option The cURL opt Constant |
|
52
|
|
|
* @param mixed $value The cURL opt value |
|
53
|
|
|
*/ |
|
54
|
|
|
public static function set_default_curl_option($option, $value) { |
|
55
|
|
|
Deprecation::notice('4.0', 'Use the "RestfulService.default_curl_options" config setting instead'); |
|
56
|
|
|
Config::inst()->update('RestfulService', 'default_curl_options', array($option => $value)); |
|
57
|
|
|
} |
|
58
|
|
|
|
|
59
|
|
|
/** |
|
60
|
|
|
* set many defauly curl options at once |
|
61
|
|
|
* |
|
62
|
|
|
* @deprecated 4.0 Use the "RestfulService.default_curl_options" config setting instead |
|
63
|
|
|
*/ |
|
64
|
|
|
public static function set_default_curl_options($optionArray) { |
|
65
|
|
|
Deprecation::notice('4.0', 'Use the "RestfulService.default_curl_options" config setting instead'); |
|
66
|
|
|
Config::inst()->update('RestfulService', 'default_curl_options', $optionArray); |
|
67
|
|
|
} |
|
68
|
|
|
|
|
69
|
|
|
/** |
|
70
|
|
|
* Sets default proxy settings for outbound RestfulService connections |
|
71
|
|
|
* |
|
72
|
|
|
* @param string $proxy The URL of the proxy to use. |
|
73
|
|
|
* @param int $port Proxy port |
|
74
|
|
|
* @param string $user The proxy auth user name |
|
75
|
|
|
* @param string $password The proxy auth password |
|
76
|
|
|
* @param boolean $socks Set true to use socks5 proxy instead of http |
|
77
|
|
|
* @deprecated 4.0 Use the "RestfulService.default_curl_options" config setting instead, |
|
78
|
|
|
* with direct reference to the CURL_* options |
|
79
|
|
|
*/ |
|
80
|
|
|
public static function set_default_proxy($proxy, $port = 80, $user = "", $password = "", $socks = false) { |
|
81
|
|
|
Deprecation::notice( |
|
82
|
|
|
'4.0', |
|
83
|
|
|
'Use the "RestfulService.default_curl_options" config setting instead, ' |
|
84
|
|
|
. 'with direct reference to the CURL_* options' |
|
85
|
|
|
); |
|
86
|
|
|
config::inst()->update('RestfulService', 'default_proxy', array( |
|
87
|
|
|
CURLOPT_PROXY => $proxy, |
|
88
|
|
|
CURLOPT_PROXYUSERPWD => "{$user}:{$password}", |
|
89
|
|
|
CURLOPT_PROXYPORT => $port, |
|
90
|
|
|
CURLOPT_PROXYTYPE => ($socks ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP) |
|
91
|
|
|
)); |
|
92
|
|
|
} |
|
93
|
|
|
|
|
94
|
|
|
/** |
|
95
|
|
|
* Creates a new restful service. |
|
96
|
|
|
* @param string $base Base URL of the web service eg: api.example.com |
|
97
|
|
|
* @param int $expiry Set the cache expiry interva. Defaults to 1 hour (3600 seconds) |
|
98
|
|
|
*/ |
|
99
|
|
|
public function __construct($base, $expiry=3600){ |
|
100
|
|
|
$this->baseURL = $base; |
|
101
|
|
|
$this->cache_expire = $expiry; |
|
102
|
|
|
parent::__construct(); |
|
103
|
|
|
$this->proxy = $this->config()->default_proxy; |
|
|
|
|
|
|
104
|
|
|
} |
|
105
|
|
|
|
|
106
|
|
|
/** |
|
107
|
|
|
* Sets the Query string parameters to send a request. |
|
108
|
|
|
* @param array $params An array passed with necessary parameters. |
|
109
|
|
|
*/ |
|
110
|
|
|
public function setQueryString($params=NULL){ |
|
111
|
|
|
$this->queryString = http_build_query($params,'','&'); |
|
112
|
|
|
} |
|
113
|
|
|
|
|
114
|
|
|
/** |
|
115
|
|
|
* Set proxy settings for this RestfulService instance |
|
116
|
|
|
* |
|
117
|
|
|
* @param string $proxy The URL of the proxy to use. |
|
118
|
|
|
* @param int $port Proxy port |
|
119
|
|
|
* @param string $user The proxy auth user name |
|
120
|
|
|
* @param string $password The proxy auth password |
|
121
|
|
|
* @param boolean $socks Set true to use socks5 proxy instead of http |
|
122
|
|
|
*/ |
|
123
|
|
|
public function setProxy($proxy, $port = 80, $user = "", $password = "", $socks = false) { |
|
124
|
|
|
$this->proxy = array( |
|
125
|
|
|
CURLOPT_PROXY => $proxy, |
|
126
|
|
|
CURLOPT_PROXYUSERPWD => "{$user}:{$password}", |
|
127
|
|
|
CURLOPT_PROXYPORT => $port, |
|
128
|
|
|
CURLOPT_PROXYTYPE => ($socks ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP) |
|
129
|
|
|
); |
|
130
|
|
|
} |
|
131
|
|
|
|
|
132
|
|
|
/** |
|
133
|
|
|
* Set basic authentication |
|
134
|
|
|
*/ |
|
135
|
|
|
public function basicAuth($username, $password) { |
|
136
|
|
|
$this->authUsername = $username; |
|
137
|
|
|
$this->authPassword = $password; |
|
138
|
|
|
} |
|
139
|
|
|
|
|
140
|
|
|
/** |
|
141
|
|
|
* Set a custom HTTP header |
|
142
|
|
|
*/ |
|
143
|
|
|
public function httpHeader($header) { |
|
144
|
|
|
$this->customHeaders[] = $header; |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
/** |
|
148
|
|
|
* @deprecated since version 4.0 |
|
149
|
|
|
*/ |
|
150
|
|
|
protected function constructURL(){ |
|
151
|
|
|
Deprecation::notice('4.0', 'constructURL is deprecated, please use `getAbsoluteRequestURL` instead'); |
|
152
|
|
|
return Controller::join_links($this->baseURL, '?' . $this->queryString); |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
/** |
|
156
|
|
|
* Makes a request to the RESTful server, and return a {@link RestfulService_Response} object for parsing of the |
|
157
|
|
|
* result. |
|
158
|
|
|
* |
|
159
|
|
|
* @todo Better POST, PUT, DELETE, and HEAD support |
|
160
|
|
|
* @todo Caching of requests - probably only GET and HEAD requestst |
|
161
|
|
|
* @todo JSON support in RestfulService_Response |
|
162
|
|
|
* @todo Pass the response headers to RestfulService_Response |
|
163
|
|
|
* |
|
164
|
|
|
* This is a replacement of {@link connect()}. |
|
165
|
|
|
* |
|
166
|
|
|
* @return RestfulService_Response - If curl request produces error, the returned response's status code will |
|
167
|
|
|
* be 500 |
|
168
|
|
|
*/ |
|
169
|
|
|
public function request($subURL = '', $method = "GET", $data = null, $headers = null, $curlOptions = array()) { |
|
170
|
|
|
|
|
171
|
|
|
$url = $this->getAbsoluteRequestURL($subURL); |
|
172
|
|
|
$method = strtoupper($method); |
|
173
|
|
|
|
|
174
|
|
|
assert(in_array($method, array('GET','POST','PUT','DELETE','HEAD','OPTIONS','PATCH'))); |
|
175
|
|
|
|
|
176
|
|
|
$cache_path = $this->getCachePath(array( |
|
177
|
|
|
$url, |
|
178
|
|
|
$method, |
|
179
|
|
|
$data, |
|
180
|
|
|
array_merge((array)$this->customHeaders, (array)$headers), |
|
181
|
|
|
$curlOptions + (array)$this->config()->default_curl_options, |
|
182
|
|
|
$this->getBasicAuthString() |
|
183
|
|
|
)); |
|
184
|
|
|
|
|
185
|
|
|
// Check for unexpired cached feed (unless flush is set) |
|
186
|
|
|
//assume any cache_expire that is 0 or less means that we dont want to |
|
187
|
|
|
// cache |
|
188
|
|
|
if($this->cache_expire > 0 && !self::$flush |
|
189
|
|
|
&& @file_exists($cache_path) |
|
190
|
|
|
&& @filemtime($cache_path) + $this->cache_expire > time()) { |
|
191
|
|
|
|
|
192
|
|
|
$store = file_get_contents($cache_path); |
|
193
|
|
|
$response = unserialize($store); |
|
194
|
|
|
|
|
195
|
|
|
} else { |
|
196
|
|
|
$response = $this->curlRequest($url, $method, $data, $headers, $curlOptions); |
|
197
|
|
|
|
|
198
|
|
|
if(!$response->isError()) { |
|
199
|
|
|
// Serialise response object and write to cache |
|
200
|
|
|
$store = serialize($response); |
|
201
|
|
|
file_put_contents($cache_path, $store); |
|
202
|
|
|
} |
|
203
|
|
|
else { |
|
204
|
|
|
// In case of curl or/and http indicate error, populate response's cachedBody property |
|
205
|
|
|
// with cached response body with the cache file exists |
|
206
|
|
|
if (@file_exists($cache_path)) { |
|
207
|
|
|
$store = file_get_contents($cache_path); |
|
208
|
|
|
$cachedResponse = unserialize($store); |
|
209
|
|
|
|
|
210
|
|
|
$response->setCachedResponse($cachedResponse); |
|
211
|
|
|
} |
|
212
|
|
|
else { |
|
213
|
|
|
$response->setCachedResponse(false); |
|
214
|
|
|
} |
|
215
|
|
|
} |
|
216
|
|
|
} |
|
217
|
|
|
|
|
218
|
|
|
return $response; |
|
219
|
|
|
} |
|
220
|
|
|
|
|
221
|
|
|
/** |
|
222
|
|
|
* Actually performs a remote service request using curl. This is used by |
|
223
|
|
|
* {@link RestfulService::request()}. |
|
224
|
|
|
* |
|
225
|
|
|
* @param string $url |
|
226
|
|
|
* @param string $method |
|
227
|
|
|
* @param array $data |
|
228
|
|
|
* @param array $headers |
|
229
|
|
|
* @param array $curlOptions |
|
230
|
|
|
* @return RestfulService_Response |
|
231
|
|
|
*/ |
|
232
|
|
|
public function curlRequest($url, $method, $data = null, $headers = null, $curlOptions = array()) { |
|
233
|
|
|
$ch = curl_init(); |
|
234
|
|
|
$timeout = 5; |
|
235
|
|
|
$sapphireInfo = new SapphireInfo(); |
|
236
|
|
|
$useragent = 'SilverStripe/' . $sapphireInfo->Version(); |
|
237
|
|
|
$curlOptions = $curlOptions + (array)$this->config()->default_curl_options; |
|
238
|
|
|
|
|
239
|
|
|
curl_setopt($ch, CURLOPT_URL, $url); |
|
240
|
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); |
|
241
|
|
|
curl_setopt($ch, CURLOPT_USERAGENT, $useragent); |
|
242
|
|
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout); |
|
243
|
|
|
if(!ini_get('open_basedir')) curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1); |
|
244
|
|
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); |
|
245
|
|
|
|
|
246
|
|
|
|
|
247
|
|
|
// Write headers to a temporary file |
|
248
|
|
|
$headerfd = tmpfile(); |
|
249
|
|
|
curl_setopt($ch, CURLOPT_WRITEHEADER, $headerfd); |
|
250
|
|
|
|
|
251
|
|
|
// Add headers |
|
252
|
|
|
if($this->customHeaders) { |
|
|
|
|
|
|
253
|
|
|
$headers = array_merge((array)$this->customHeaders, (array)$headers); |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
if($headers) curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); |
|
257
|
|
|
|
|
258
|
|
|
// Add authentication |
|
259
|
|
|
if($this->authUsername) curl_setopt($ch, CURLOPT_USERPWD, $this->getBasicAuthString()); |
|
260
|
|
|
|
|
261
|
|
|
// Add fields to POST and PUT requests |
|
262
|
|
|
if($method == 'POST' || $method == 'PATCH') { |
|
263
|
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $data); |
|
264
|
|
|
} elseif($method == 'PUT') { |
|
265
|
|
|
$put = fopen("php://temp", 'r+'); |
|
266
|
|
|
fwrite($put, $data); |
|
267
|
|
|
fseek($put, 0); |
|
268
|
|
|
|
|
269
|
|
|
curl_setopt($ch, CURLOPT_PUT, 1); |
|
270
|
|
|
curl_setopt($ch, CURLOPT_INFILE, $put); |
|
271
|
|
|
curl_setopt($ch, CURLOPT_INFILESIZE, strlen($data)); |
|
272
|
|
|
} |
|
273
|
|
|
|
|
274
|
|
|
// Apply proxy settings |
|
275
|
|
|
if(is_array($this->proxy)) { |
|
276
|
|
|
curl_setopt_array($ch, $this->proxy); |
|
277
|
|
|
} |
|
278
|
|
|
|
|
279
|
|
|
// Set any custom options passed to the request() function |
|
280
|
|
|
curl_setopt_array($ch, $curlOptions); |
|
281
|
|
|
|
|
282
|
|
|
// Run request |
|
283
|
|
|
$body = curl_exec($ch); |
|
284
|
|
|
|
|
285
|
|
|
rewind($headerfd); |
|
286
|
|
|
$headers = stream_get_contents($headerfd); |
|
287
|
|
|
fclose($headerfd); |
|
288
|
|
|
|
|
289
|
|
|
$response = $this->extractResponse($ch, $headers, $body); |
|
290
|
|
|
curl_close($ch); |
|
291
|
|
|
|
|
292
|
|
|
return $response; |
|
293
|
|
|
} |
|
294
|
|
|
|
|
295
|
|
|
/** |
|
296
|
|
|
* A function to return the auth string. This helps consistency through the |
|
297
|
|
|
* class but also allows tests to pull it out when generating the expected |
|
298
|
|
|
* cache keys |
|
299
|
|
|
* |
|
300
|
|
|
* @see {self::getCachePath()} |
|
301
|
|
|
* @see {RestfulServiceTest::createFakeCachedResponse()} |
|
302
|
|
|
* |
|
303
|
|
|
* @return string The auth string to be base64 encoded |
|
304
|
|
|
*/ |
|
305
|
|
|
protected function getBasicAuthString() { |
|
306
|
|
|
return $this->authUsername . ':' . $this->authPassword; |
|
307
|
|
|
} |
|
308
|
|
|
|
|
309
|
|
|
/** |
|
310
|
|
|
* Generate a cache key based on any cache data sent. The cache data can be |
|
311
|
|
|
* any type |
|
312
|
|
|
* |
|
313
|
|
|
* @param mixed $cacheData The cache seed for generating the key |
|
314
|
|
|
* @param string the md5 encoded cache seed. |
|
315
|
|
|
*/ |
|
316
|
|
|
protected function generateCacheKey($cacheData) { |
|
317
|
|
|
return md5(var_export($cacheData, true)); |
|
318
|
|
|
} |
|
319
|
|
|
|
|
320
|
|
|
/** |
|
321
|
|
|
* Generate the cache path |
|
322
|
|
|
* |
|
323
|
|
|
* This is mainly so that the cache path can be generated in a consistent |
|
324
|
|
|
* way in tests without having to hard code the cachekey generate function |
|
325
|
|
|
* in tests |
|
326
|
|
|
* |
|
327
|
|
|
* @param mixed $cacheData The cache seed {@see self::generateCacheKey} |
|
328
|
|
|
* |
|
329
|
|
|
* @return string The path to the cache file |
|
330
|
|
|
*/ |
|
331
|
|
|
protected function getCachePath($cacheData) { |
|
332
|
|
|
return TEMP_FOLDER . "/xmlresponse_" . $this->generateCacheKey($cacheData); |
|
333
|
|
|
} |
|
334
|
|
|
|
|
335
|
|
|
/** |
|
336
|
|
|
* Extracts the response body and headers from a full curl response |
|
337
|
|
|
* |
|
338
|
|
|
* @param curl_handle $ch The curl handle for the request |
|
339
|
|
|
* @param string $rawResponse The raw response text |
|
|
|
|
|
|
340
|
|
|
* |
|
341
|
|
|
* @return RestfulService_Response The response object |
|
342
|
|
|
*/ |
|
343
|
|
|
protected function extractResponse($ch, $rawHeaders, $rawBody) { |
|
344
|
|
|
//get the status code |
|
345
|
|
|
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); |
|
346
|
|
|
//get a curl error if there is one |
|
347
|
|
|
$curlError = curl_error($ch); |
|
348
|
|
|
//normalise the status code |
|
349
|
|
|
if(curl_error($ch) !== '' || $statusCode == 0) $statusCode = 500; |
|
350
|
|
|
//parse the headers |
|
351
|
|
|
$parts = array_filter(explode("\r\n\r\n", $rawHeaders)); |
|
352
|
|
|
$lastHeaders = array_pop($parts); |
|
353
|
|
|
$headers = $this->parseRawHeaders($lastHeaders); |
|
354
|
|
|
//return the response object |
|
355
|
|
|
return new RestfulService_Response($rawBody, $statusCode, $headers); |
|
356
|
|
|
} |
|
357
|
|
|
|
|
358
|
|
|
/** |
|
359
|
|
|
* Takes raw headers and parses them to turn them to an associative array |
|
360
|
|
|
* |
|
361
|
|
|
* Any header that we see more than once is turned into an array. |
|
362
|
|
|
* |
|
363
|
|
|
* This is meant to mimic http_parse_headers {@link http://php.net/manual/en/function.http-parse-headers.php} |
|
364
|
|
|
* thanks to comment #77241 on that page for foundation of this |
|
365
|
|
|
* |
|
366
|
|
|
* @param string $rawHeaders The raw header string |
|
367
|
|
|
* @return array The assosiative array of headers |
|
368
|
|
|
*/ |
|
369
|
|
|
protected function parseRawHeaders($rawHeaders) { |
|
370
|
|
|
$headers = array(); |
|
371
|
|
|
$fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $rawHeaders)); |
|
372
|
|
|
foreach( $fields as $field ) { |
|
373
|
|
|
if( preg_match('/([^:]+): (.+)/m', $field, $match) ) { |
|
374
|
|
|
$match[1] = preg_replace_callback( |
|
375
|
|
|
'/(?<=^|[\x09\x20\x2D])./', |
|
376
|
|
|
function($matches) { |
|
377
|
|
|
return strtoupper($matches[0]); |
|
378
|
|
|
}, |
|
379
|
|
|
trim($match[1]) |
|
380
|
|
|
); |
|
381
|
|
|
if( isset($headers[$match[1]]) ) { |
|
382
|
|
|
if (!is_array($headers[$match[1]])) { |
|
383
|
|
|
$headers[$match[1]] = array($headers[$match[1]]); |
|
384
|
|
|
} |
|
385
|
|
|
$headers[$match[1]][] = $match[2]; |
|
386
|
|
|
} else { |
|
387
|
|
|
$headers[$match[1]] = trim($match[2]); |
|
388
|
|
|
} |
|
389
|
|
|
} |
|
390
|
|
|
} |
|
391
|
|
|
return $headers; |
|
392
|
|
|
} |
|
393
|
|
|
|
|
394
|
|
|
|
|
395
|
|
|
/** |
|
396
|
|
|
* Returns a full request url |
|
397
|
|
|
* @param string |
|
398
|
|
|
*/ |
|
399
|
|
|
public function getAbsoluteRequestURL($subURL = '') { |
|
400
|
|
|
$url = Controller::join_links($this->baseURL, $subURL, '?' . $this->queryString); |
|
401
|
|
|
|
|
402
|
|
|
return str_replace(' ', '%20', $url); // Encode spaces |
|
403
|
|
|
} |
|
404
|
|
|
|
|
405
|
|
|
/** |
|
406
|
|
|
* Gets attributes as an array, of a particular type of element. |
|
407
|
|
|
* Example : <photo id="2636" owner="123" secret="ab128" server="2"> |
|
408
|
|
|
* returns id, owner,secret and sever attribute values of all such photo elements. |
|
409
|
|
|
* @param string $xml The source xml to parse, this could be the original response received. |
|
410
|
|
|
* @param string $collection The name of parent node which wraps the elements, if available |
|
411
|
|
|
* @param string $element The element we need to extract the attributes. |
|
412
|
|
|
*/ |
|
413
|
|
|
|
|
414
|
|
|
public function getAttributes($xml, $collection=NULL, $element=NULL){ |
|
415
|
|
|
$xml = new SimpleXMLElement($xml); |
|
416
|
|
|
$output = new ArrayList(); |
|
417
|
|
|
|
|
418
|
|
|
if($collection) |
|
|
|
|
|
|
419
|
|
|
$childElements = $xml->{$collection}; |
|
420
|
|
|
if($element) |
|
|
|
|
|
|
421
|
|
|
$childElements = $xml->{$collection}->{$element}; |
|
422
|
|
|
|
|
423
|
|
|
if(isset($childElements) && $childElements){ |
|
424
|
|
|
foreach($childElements as $child){ |
|
425
|
|
|
$data = array(); |
|
426
|
|
|
foreach($child->attributes() as $key => $value){ |
|
427
|
|
|
$data["$key"] = Convert::raw2xml($value); |
|
428
|
|
|
} |
|
429
|
|
|
$output->push(new ArrayData($data)); |
|
430
|
|
|
} |
|
431
|
|
|
} |
|
432
|
|
|
return $output; |
|
433
|
|
|
|
|
434
|
|
|
} |
|
435
|
|
|
|
|
436
|
|
|
/** |
|
437
|
|
|
* Gets an attribute of a particular element. |
|
438
|
|
|
* @param string $xml The source xml to parse, this could be the original response received. |
|
439
|
|
|
* @param string $collection The name of the parent node which wraps the element, if available |
|
440
|
|
|
* @param string $element The element we need to extract the attribute |
|
441
|
|
|
* @param string $attr The name of the attribute |
|
442
|
|
|
*/ |
|
443
|
|
|
|
|
444
|
|
|
public function getAttribute($xml, $collection=NULL, $element=NULL, $attr){ |
|
445
|
|
|
$xml = new SimpleXMLElement($xml); |
|
446
|
|
|
$attr_value = ""; |
|
447
|
|
|
|
|
448
|
|
|
if($collection) |
|
|
|
|
|
|
449
|
|
|
$childElements = $xml->{$collection}; |
|
450
|
|
|
if($element) |
|
|
|
|
|
|
451
|
|
|
$childElements = $xml->{$collection}->{$element}; |
|
452
|
|
|
|
|
453
|
|
|
if(isset($childElements[$attr])) |
|
454
|
|
|
$attr_value = (string) $childElements[$attr]; |
|
455
|
|
|
|
|
456
|
|
|
return Convert::raw2xml($attr_value); |
|
457
|
|
|
|
|
458
|
|
|
} |
|
459
|
|
|
|
|
460
|
|
|
|
|
461
|
|
|
/** |
|
462
|
|
|
* Gets set of node values as an array. |
|
463
|
|
|
* When you get to the depth in the hierarchy use node_child_subchild syntax to get the value. |
|
464
|
|
|
* @param string $xml The the source xml to parse, this could be the original response received. |
|
465
|
|
|
* @param string $collection The name of parent node which wraps the elements, if available |
|
466
|
|
|
* @param string $element The element we need to extract the node values. |
|
467
|
|
|
*/ |
|
468
|
|
|
|
|
469
|
|
|
public function getValues($xml, $collection=NULL, $element=NULL){ |
|
470
|
|
|
$xml = new SimpleXMLElement($xml); |
|
471
|
|
|
$output = new ArrayList(); |
|
472
|
|
|
|
|
473
|
|
|
$childElements = $xml; |
|
474
|
|
|
if($collection) |
|
|
|
|
|
|
475
|
|
|
$childElements = $xml->{$collection}; |
|
476
|
|
|
if($element) |
|
|
|
|
|
|
477
|
|
|
$childElements = $xml->{$collection}->{$element}; |
|
478
|
|
|
|
|
479
|
|
|
if(isset($childElements) && $childElements){ |
|
480
|
|
|
foreach($childElements as $child){ |
|
481
|
|
|
$data = array(); |
|
482
|
|
|
$this->getRecurseValues($child,$data); |
|
483
|
|
|
$output->push(new ArrayData($data)); |
|
484
|
|
|
} |
|
485
|
|
|
} |
|
486
|
|
|
return $output; |
|
487
|
|
|
} |
|
488
|
|
|
|
|
489
|
|
|
protected function getRecurseValues($xml,&$data,$parent=""){ |
|
490
|
|
|
$conv_value = ""; |
|
491
|
|
|
$child_count = 0; |
|
492
|
|
|
foreach($xml as $key=>$value) |
|
493
|
|
|
{ |
|
494
|
|
|
$child_count++; |
|
495
|
|
|
$k = ($parent == "") ? (string)$key : $parent . "_" . (string)$key; |
|
496
|
|
|
if($this->getRecurseValues($value,$data,$k) == 0){ // no childern, aka "leaf node" |
|
497
|
|
|
$conv_value = Convert::raw2xml($value); |
|
498
|
|
|
} |
|
499
|
|
|
//Review the fix for similar node names overriding it's predecessor |
|
500
|
|
|
if(array_key_exists($k, $data) == true) { |
|
|
|
|
|
|
501
|
|
|
$data[$k] = $data[$k] . ",". $conv_value; |
|
502
|
|
|
} |
|
503
|
|
|
else { |
|
504
|
|
|
$data[$k] = $conv_value; |
|
505
|
|
|
} |
|
506
|
|
|
|
|
507
|
|
|
|
|
508
|
|
|
} |
|
509
|
|
|
return $child_count; |
|
510
|
|
|
|
|
511
|
|
|
} |
|
512
|
|
|
|
|
513
|
|
|
/** |
|
514
|
|
|
* Gets a single node value. |
|
515
|
|
|
* @param string $xml The source xml to parse, this could be the original response received. |
|
516
|
|
|
* @param string $collection The name of parent node which wraps the elements, if available |
|
517
|
|
|
* @param string $element The element we need to extract the node value. |
|
518
|
|
|
*/ |
|
519
|
|
|
|
|
520
|
|
|
public function getValue($xml, $collection=NULL, $element=NULL){ |
|
521
|
|
|
$xml = new SimpleXMLElement($xml); |
|
522
|
|
|
|
|
523
|
|
|
if($collection) |
|
|
|
|
|
|
524
|
|
|
$childElements = $xml->{$collection}; |
|
525
|
|
|
if($element) |
|
|
|
|
|
|
526
|
|
|
$childElements = $xml->{$collection}->{$element}; |
|
527
|
|
|
|
|
528
|
|
|
if(isset($childElements) && $childElements) |
|
529
|
|
|
return Convert::raw2xml($childElements); |
|
530
|
|
|
} |
|
531
|
|
|
|
|
532
|
|
|
/** |
|
533
|
|
|
* Searches for a node in document tree and returns it value. |
|
534
|
|
|
* @param string $xml source xml to parse, this could be the original response received. |
|
535
|
|
|
* @param string $node Node to search for |
|
536
|
|
|
*/ |
|
537
|
|
|
public function searchValue($xml, $node=NULL){ |
|
538
|
|
|
$xml = new SimpleXMLElement($xml); |
|
539
|
|
|
$childElements = $xml->xpath($node); |
|
540
|
|
|
|
|
541
|
|
|
if($childElements) |
|
|
|
|
|
|
542
|
|
|
return Convert::raw2xml($childElements[0]); |
|
543
|
|
|
} |
|
544
|
|
|
|
|
545
|
|
|
/** |
|
546
|
|
|
* Searches for a node in document tree and returns its attributes. |
|
547
|
|
|
* @param string $xml the source xml to parse, this could be the original response received. |
|
548
|
|
|
* @param string $node Node to search for |
|
549
|
|
|
*/ |
|
550
|
|
|
public function searchAttributes($xml, $node=NULL){ |
|
551
|
|
|
$xml = new SimpleXMLElement($xml); |
|
552
|
|
|
$output = new ArrayList(); |
|
553
|
|
|
|
|
554
|
|
|
$childElements = $xml->xpath($node); |
|
555
|
|
|
|
|
556
|
|
|
if($childElements) |
|
|
|
|
|
|
557
|
|
|
foreach($childElements as $child){ |
|
558
|
|
|
$data = array(); |
|
559
|
|
|
foreach($child->attributes() as $key => $value){ |
|
560
|
|
|
$data["$key"] = Convert::raw2xml($value); |
|
561
|
|
|
} |
|
562
|
|
|
|
|
563
|
|
|
$output->push(new ArrayData($data)); |
|
564
|
|
|
} |
|
565
|
|
|
|
|
566
|
|
|
return $output; |
|
567
|
|
|
} |
|
568
|
|
|
} |
|
569
|
|
|
|
|
570
|
|
|
/** |
|
571
|
|
|
* @package framework |
|
572
|
|
|
* @subpackage integration |
|
573
|
|
|
*/ |
|
574
|
|
|
class RestfulService_Response extends SS_HTTPResponse { |
|
575
|
|
|
protected $simpleXML; |
|
576
|
|
|
|
|
577
|
|
|
/** |
|
578
|
|
|
* @var RestfulService_Response|false It should be populated with cached request |
|
579
|
|
|
* when a request referring to this response was unsuccessful |
|
580
|
|
|
*/ |
|
581
|
|
|
protected $cachedResponse = false; |
|
582
|
|
|
|
|
583
|
|
|
public function __construct($body, $statusCode = 200, $headers = null) { |
|
584
|
|
|
$this->setbody($body); |
|
585
|
|
|
$this->setStatusCode($statusCode); |
|
586
|
|
|
$this->headers = $headers; |
|
587
|
|
|
} |
|
588
|
|
|
|
|
589
|
|
|
public function simpleXML() { |
|
590
|
|
|
if(!$this->simpleXML) { |
|
591
|
|
|
try { |
|
592
|
|
|
$this->simpleXML = new SimpleXMLElement($this->body); |
|
593
|
|
|
} |
|
594
|
|
|
catch(Exception $e) { |
|
595
|
|
|
user_error("String could not be parsed as XML. " . $e, E_USER_WARNING); |
|
596
|
|
|
} |
|
597
|
|
|
} |
|
598
|
|
|
return $this->simpleXML; |
|
599
|
|
|
} |
|
600
|
|
|
|
|
601
|
|
|
/** |
|
602
|
|
|
* get the cached response object. This allows you to access the cached |
|
603
|
|
|
* eaders, not just the cached body. |
|
604
|
|
|
* |
|
605
|
|
|
* @return RestfulService_Response|false The cached response object |
|
606
|
|
|
*/ |
|
607
|
|
|
public function getCachedResponse() { |
|
608
|
|
|
return $this->cachedResponse; |
|
609
|
|
|
} |
|
610
|
|
|
|
|
611
|
|
|
/** |
|
612
|
|
|
* @return string|false |
|
613
|
|
|
*/ |
|
614
|
|
|
public function getCachedBody() { |
|
615
|
|
|
if ($this->cachedResponse) { |
|
616
|
|
|
return $this->cachedResponse->getBody(); |
|
617
|
|
|
} |
|
618
|
|
|
return false; |
|
619
|
|
|
} |
|
620
|
|
|
|
|
621
|
|
|
/** |
|
622
|
|
|
* @param string |
|
623
|
|
|
* @deprecated since version 4.0 |
|
624
|
|
|
*/ |
|
625
|
|
|
public function setCachedBody($content) { |
|
626
|
|
|
Deprecation::notice('4.0', 'Setting the response body is now deprecated, set the cached request instead'); |
|
627
|
|
|
if (!$this->cachedResponse) { |
|
628
|
|
|
$this->cachedResponse = new RestfulService_Response($content); |
|
629
|
|
|
} |
|
630
|
|
|
else { |
|
631
|
|
|
$this->cachedResponse->setBody($content); |
|
632
|
|
|
} |
|
633
|
|
|
} |
|
634
|
|
|
|
|
635
|
|
|
/** |
|
636
|
|
|
* @param string |
|
637
|
|
|
*/ |
|
638
|
|
|
public function setCachedResponse($response) { |
|
639
|
|
|
$this->cachedResponse = $response; |
|
640
|
|
|
} |
|
641
|
|
|
|
|
642
|
|
|
/** |
|
643
|
|
|
* Return an array of xpath matches |
|
644
|
|
|
*/ |
|
645
|
|
|
public function xpath($xpath) { |
|
646
|
|
|
return $this->simpleXML()->xpath($xpath); |
|
647
|
|
|
} |
|
648
|
|
|
|
|
649
|
|
|
/** |
|
650
|
|
|
* Return the first xpath match |
|
651
|
|
|
*/ |
|
652
|
|
|
public function xpath_one($xpath) { |
|
653
|
|
|
$items = $this->xpath($xpath); |
|
654
|
|
|
if (isset($items[0])) { |
|
655
|
|
|
return $items[0]; |
|
656
|
|
|
} |
|
657
|
|
|
} |
|
658
|
|
|
} |
|
659
|
|
|
|
Only declaring a single property per statement allows you to later on add doc comments more easily.
It is also recommended by PSR2, so it is a common style that many people expect.