Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 /*
|
Chris@0
|
4 * This file is part of the Symfony package.
|
Chris@0
|
5 *
|
Chris@0
|
6 * (c) Fabien Potencier <fabien@symfony.com>
|
Chris@0
|
7 *
|
Chris@0
|
8 * For the full copyright and license information, please view the LICENSE
|
Chris@0
|
9 * file that was distributed with this source code.
|
Chris@0
|
10 */
|
Chris@0
|
11
|
Chris@0
|
12 namespace Symfony\Component\BrowserKit;
|
Chris@0
|
13
|
Chris@0
|
14 use Symfony\Component\DomCrawler\Crawler;
|
Chris@17
|
15 use Symfony\Component\DomCrawler\Form;
|
Chris@0
|
16 use Symfony\Component\DomCrawler\Link;
|
Chris@0
|
17 use Symfony\Component\Process\PhpProcess;
|
Chris@0
|
18
|
Chris@0
|
19 /**
|
Chris@0
|
20 * Client simulates a browser.
|
Chris@0
|
21 *
|
Chris@0
|
22 * To make the actual request, you need to implement the doRequest() method.
|
Chris@0
|
23 *
|
Chris@0
|
24 * If you want to be able to run requests in their own process (insulated flag),
|
Chris@0
|
25 * you need to also implement the getScript() method.
|
Chris@0
|
26 *
|
Chris@0
|
27 * @author Fabien Potencier <fabien@symfony.com>
|
Chris@0
|
28 */
|
Chris@0
|
29 abstract class Client
|
Chris@0
|
30 {
|
Chris@0
|
31 protected $history;
|
Chris@0
|
32 protected $cookieJar;
|
Chris@17
|
33 protected $server = [];
|
Chris@0
|
34 protected $internalRequest;
|
Chris@0
|
35 protected $request;
|
Chris@0
|
36 protected $internalResponse;
|
Chris@0
|
37 protected $response;
|
Chris@0
|
38 protected $crawler;
|
Chris@0
|
39 protected $insulated = false;
|
Chris@0
|
40 protected $redirect;
|
Chris@0
|
41 protected $followRedirects = true;
|
Chris@0
|
42
|
Chris@0
|
43 private $maxRedirects = -1;
|
Chris@0
|
44 private $redirectCount = 0;
|
Chris@17
|
45 private $redirects = [];
|
Chris@0
|
46 private $isMainRequest = true;
|
Chris@0
|
47
|
Chris@0
|
48 /**
|
Chris@0
|
49 * @param array $server The server parameters (equivalent of $_SERVER)
|
Chris@0
|
50 * @param History $history A History instance to store the browser history
|
Chris@0
|
51 * @param CookieJar $cookieJar A CookieJar instance to store the cookies
|
Chris@0
|
52 */
|
Chris@17
|
53 public function __construct(array $server = [], History $history = null, CookieJar $cookieJar = null)
|
Chris@0
|
54 {
|
Chris@0
|
55 $this->setServerParameters($server);
|
Chris@0
|
56 $this->history = $history ?: new History();
|
Chris@0
|
57 $this->cookieJar = $cookieJar ?: new CookieJar();
|
Chris@0
|
58 }
|
Chris@0
|
59
|
Chris@0
|
60 /**
|
Chris@0
|
61 * Sets whether to automatically follow redirects or not.
|
Chris@0
|
62 *
|
Chris@0
|
63 * @param bool $followRedirect Whether to follow redirects
|
Chris@0
|
64 */
|
Chris@0
|
65 public function followRedirects($followRedirect = true)
|
Chris@0
|
66 {
|
Chris@0
|
67 $this->followRedirects = (bool) $followRedirect;
|
Chris@0
|
68 }
|
Chris@0
|
69
|
Chris@0
|
70 /**
|
Chris@0
|
71 * Returns whether client automatically follows redirects or not.
|
Chris@0
|
72 *
|
Chris@0
|
73 * @return bool
|
Chris@0
|
74 */
|
Chris@0
|
75 public function isFollowingRedirects()
|
Chris@0
|
76 {
|
Chris@0
|
77 return $this->followRedirects;
|
Chris@0
|
78 }
|
Chris@0
|
79
|
Chris@0
|
80 /**
|
Chris@13
|
81 * Sets the maximum number of redirects that crawler can follow.
|
Chris@0
|
82 *
|
Chris@0
|
83 * @param int $maxRedirects
|
Chris@0
|
84 */
|
Chris@0
|
85 public function setMaxRedirects($maxRedirects)
|
Chris@0
|
86 {
|
Chris@0
|
87 $this->maxRedirects = $maxRedirects < 0 ? -1 : $maxRedirects;
|
Chris@0
|
88 $this->followRedirects = -1 != $this->maxRedirects;
|
Chris@0
|
89 }
|
Chris@0
|
90
|
Chris@0
|
91 /**
|
Chris@13
|
92 * Returns the maximum number of redirects that crawler can follow.
|
Chris@0
|
93 *
|
Chris@0
|
94 * @return int
|
Chris@0
|
95 */
|
Chris@0
|
96 public function getMaxRedirects()
|
Chris@0
|
97 {
|
Chris@0
|
98 return $this->maxRedirects;
|
Chris@0
|
99 }
|
Chris@0
|
100
|
Chris@0
|
101 /**
|
Chris@0
|
102 * Sets the insulated flag.
|
Chris@0
|
103 *
|
Chris@0
|
104 * @param bool $insulated Whether to insulate the requests or not
|
Chris@0
|
105 *
|
Chris@0
|
106 * @throws \RuntimeException When Symfony Process Component is not installed
|
Chris@0
|
107 */
|
Chris@0
|
108 public function insulate($insulated = true)
|
Chris@0
|
109 {
|
Chris@0
|
110 if ($insulated && !class_exists('Symfony\\Component\\Process\\Process')) {
|
Chris@0
|
111 throw new \RuntimeException('Unable to isolate requests as the Symfony Process Component is not installed.');
|
Chris@0
|
112 }
|
Chris@0
|
113
|
Chris@0
|
114 $this->insulated = (bool) $insulated;
|
Chris@0
|
115 }
|
Chris@0
|
116
|
Chris@0
|
117 /**
|
Chris@0
|
118 * Sets server parameters.
|
Chris@0
|
119 *
|
Chris@0
|
120 * @param array $server An array of server parameters
|
Chris@0
|
121 */
|
Chris@0
|
122 public function setServerParameters(array $server)
|
Chris@0
|
123 {
|
Chris@17
|
124 $this->server = array_merge([
|
Chris@0
|
125 'HTTP_USER_AGENT' => 'Symfony BrowserKit',
|
Chris@17
|
126 ], $server);
|
Chris@0
|
127 }
|
Chris@0
|
128
|
Chris@0
|
129 /**
|
Chris@0
|
130 * Sets single server parameter.
|
Chris@0
|
131 *
|
Chris@0
|
132 * @param string $key A key of the parameter
|
Chris@0
|
133 * @param string $value A value of the parameter
|
Chris@0
|
134 */
|
Chris@0
|
135 public function setServerParameter($key, $value)
|
Chris@0
|
136 {
|
Chris@0
|
137 $this->server[$key] = $value;
|
Chris@0
|
138 }
|
Chris@0
|
139
|
Chris@0
|
140 /**
|
Chris@0
|
141 * Gets single server parameter for specified key.
|
Chris@0
|
142 *
|
Chris@0
|
143 * @param string $key A key of the parameter to get
|
Chris@0
|
144 * @param string $default A default value when key is undefined
|
Chris@0
|
145 *
|
Chris@0
|
146 * @return string A value of the parameter
|
Chris@0
|
147 */
|
Chris@0
|
148 public function getServerParameter($key, $default = '')
|
Chris@0
|
149 {
|
Chris@0
|
150 return isset($this->server[$key]) ? $this->server[$key] : $default;
|
Chris@0
|
151 }
|
Chris@0
|
152
|
Chris@0
|
153 /**
|
Chris@0
|
154 * Returns the History instance.
|
Chris@0
|
155 *
|
Chris@0
|
156 * @return History A History instance
|
Chris@0
|
157 */
|
Chris@0
|
158 public function getHistory()
|
Chris@0
|
159 {
|
Chris@0
|
160 return $this->history;
|
Chris@0
|
161 }
|
Chris@0
|
162
|
Chris@0
|
163 /**
|
Chris@0
|
164 * Returns the CookieJar instance.
|
Chris@0
|
165 *
|
Chris@0
|
166 * @return CookieJar A CookieJar instance
|
Chris@0
|
167 */
|
Chris@0
|
168 public function getCookieJar()
|
Chris@0
|
169 {
|
Chris@0
|
170 return $this->cookieJar;
|
Chris@0
|
171 }
|
Chris@0
|
172
|
Chris@0
|
173 /**
|
Chris@0
|
174 * Returns the current Crawler instance.
|
Chris@0
|
175 *
|
Chris@0
|
176 * @return Crawler|null A Crawler instance
|
Chris@0
|
177 */
|
Chris@0
|
178 public function getCrawler()
|
Chris@0
|
179 {
|
Chris@0
|
180 return $this->crawler;
|
Chris@0
|
181 }
|
Chris@0
|
182
|
Chris@0
|
183 /**
|
Chris@0
|
184 * Returns the current BrowserKit Response instance.
|
Chris@0
|
185 *
|
Chris@0
|
186 * @return Response|null A BrowserKit Response instance
|
Chris@0
|
187 */
|
Chris@0
|
188 public function getInternalResponse()
|
Chris@0
|
189 {
|
Chris@0
|
190 return $this->internalResponse;
|
Chris@0
|
191 }
|
Chris@0
|
192
|
Chris@0
|
193 /**
|
Chris@0
|
194 * Returns the current origin response instance.
|
Chris@0
|
195 *
|
Chris@0
|
196 * The origin response is the response instance that is returned
|
Chris@0
|
197 * by the code that handles requests.
|
Chris@0
|
198 *
|
Chris@0
|
199 * @return object|null A response instance
|
Chris@0
|
200 *
|
Chris@0
|
201 * @see doRequest()
|
Chris@0
|
202 */
|
Chris@0
|
203 public function getResponse()
|
Chris@0
|
204 {
|
Chris@0
|
205 return $this->response;
|
Chris@0
|
206 }
|
Chris@0
|
207
|
Chris@0
|
208 /**
|
Chris@0
|
209 * Returns the current BrowserKit Request instance.
|
Chris@0
|
210 *
|
Chris@0
|
211 * @return Request|null A BrowserKit Request instance
|
Chris@0
|
212 */
|
Chris@0
|
213 public function getInternalRequest()
|
Chris@0
|
214 {
|
Chris@0
|
215 return $this->internalRequest;
|
Chris@0
|
216 }
|
Chris@0
|
217
|
Chris@0
|
218 /**
|
Chris@0
|
219 * Returns the current origin Request instance.
|
Chris@0
|
220 *
|
Chris@0
|
221 * The origin request is the request instance that is sent
|
Chris@0
|
222 * to the code that handles requests.
|
Chris@0
|
223 *
|
Chris@0
|
224 * @return object|null A Request instance
|
Chris@0
|
225 *
|
Chris@0
|
226 * @see doRequest()
|
Chris@0
|
227 */
|
Chris@0
|
228 public function getRequest()
|
Chris@0
|
229 {
|
Chris@0
|
230 return $this->request;
|
Chris@0
|
231 }
|
Chris@0
|
232
|
Chris@0
|
233 /**
|
Chris@0
|
234 * Clicks on a given link.
|
Chris@0
|
235 *
|
Chris@0
|
236 * @return Crawler
|
Chris@0
|
237 */
|
Chris@0
|
238 public function click(Link $link)
|
Chris@0
|
239 {
|
Chris@0
|
240 if ($link instanceof Form) {
|
Chris@0
|
241 return $this->submit($link);
|
Chris@0
|
242 }
|
Chris@0
|
243
|
Chris@0
|
244 return $this->request($link->getMethod(), $link->getUri());
|
Chris@0
|
245 }
|
Chris@0
|
246
|
Chris@0
|
247 /**
|
Chris@0
|
248 * Submits a form.
|
Chris@0
|
249 *
|
Chris@0
|
250 * @param Form $form A Form instance
|
Chris@0
|
251 * @param array $values An array of form field values
|
Chris@0
|
252 *
|
Chris@0
|
253 * @return Crawler
|
Chris@0
|
254 */
|
Chris@17
|
255 public function submit(Form $form, array $values = [])
|
Chris@0
|
256 {
|
Chris@0
|
257 $form->setValues($values);
|
Chris@0
|
258
|
Chris@0
|
259 return $this->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $form->getPhpFiles());
|
Chris@0
|
260 }
|
Chris@0
|
261
|
Chris@0
|
262 /**
|
Chris@0
|
263 * Calls a URI.
|
Chris@0
|
264 *
|
Chris@0
|
265 * @param string $method The request method
|
Chris@0
|
266 * @param string $uri The URI to fetch
|
Chris@0
|
267 * @param array $parameters The Request parameters
|
Chris@0
|
268 * @param array $files The files
|
Chris@0
|
269 * @param array $server The server parameters (HTTP headers are referenced with a HTTP_ prefix as PHP does)
|
Chris@0
|
270 * @param string $content The raw body data
|
Chris@0
|
271 * @param bool $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload())
|
Chris@0
|
272 *
|
Chris@0
|
273 * @return Crawler
|
Chris@0
|
274 */
|
Chris@17
|
275 public function request($method, $uri, array $parameters = [], array $files = [], array $server = [], $content = null, $changeHistory = true)
|
Chris@0
|
276 {
|
Chris@0
|
277 if ($this->isMainRequest) {
|
Chris@0
|
278 $this->redirectCount = 0;
|
Chris@0
|
279 } else {
|
Chris@0
|
280 ++$this->redirectCount;
|
Chris@0
|
281 }
|
Chris@0
|
282
|
Chris@17
|
283 $originalUri = $uri;
|
Chris@17
|
284
|
Chris@0
|
285 $uri = $this->getAbsoluteUri($uri);
|
Chris@0
|
286
|
Chris@0
|
287 $server = array_merge($this->server, $server);
|
Chris@0
|
288
|
Chris@17
|
289 if (!empty($server['HTTP_HOST']) && null === parse_url($originalUri, PHP_URL_HOST)) {
|
Chris@17
|
290 $uri = preg_replace('{^(https?\://)'.preg_quote($this->extractHost($uri)).'}', '${1}'.$server['HTTP_HOST'], $uri);
|
Chris@17
|
291 }
|
Chris@17
|
292
|
Chris@17
|
293 if (isset($server['HTTPS']) && null === parse_url($originalUri, PHP_URL_SCHEME)) {
|
Chris@0
|
294 $uri = preg_replace('{^'.parse_url($uri, PHP_URL_SCHEME).'}', $server['HTTPS'] ? 'https' : 'http', $uri);
|
Chris@0
|
295 }
|
Chris@0
|
296
|
Chris@0
|
297 if (!$this->history->isEmpty()) {
|
Chris@0
|
298 $server['HTTP_REFERER'] = $this->history->current()->getUri();
|
Chris@0
|
299 }
|
Chris@0
|
300
|
Chris@0
|
301 if (empty($server['HTTP_HOST'])) {
|
Chris@0
|
302 $server['HTTP_HOST'] = $this->extractHost($uri);
|
Chris@0
|
303 }
|
Chris@0
|
304
|
Chris@0
|
305 $server['HTTPS'] = 'https' == parse_url($uri, PHP_URL_SCHEME);
|
Chris@0
|
306
|
Chris@0
|
307 $this->internalRequest = new Request($uri, $method, $parameters, $files, $this->cookieJar->allValues($uri), $server, $content);
|
Chris@0
|
308
|
Chris@0
|
309 $this->request = $this->filterRequest($this->internalRequest);
|
Chris@0
|
310
|
Chris@0
|
311 if (true === $changeHistory) {
|
Chris@0
|
312 $this->history->add($this->internalRequest);
|
Chris@0
|
313 }
|
Chris@0
|
314
|
Chris@0
|
315 if ($this->insulated) {
|
Chris@0
|
316 $this->response = $this->doRequestInProcess($this->request);
|
Chris@0
|
317 } else {
|
Chris@0
|
318 $this->response = $this->doRequest($this->request);
|
Chris@0
|
319 }
|
Chris@0
|
320
|
Chris@0
|
321 $this->internalResponse = $this->filterResponse($this->response);
|
Chris@0
|
322
|
Chris@0
|
323 $this->cookieJar->updateFromResponse($this->internalResponse, $uri);
|
Chris@0
|
324
|
Chris@0
|
325 $status = $this->internalResponse->getStatus();
|
Chris@0
|
326
|
Chris@0
|
327 if ($status >= 300 && $status < 400) {
|
Chris@0
|
328 $this->redirect = $this->internalResponse->getHeader('Location');
|
Chris@0
|
329 } else {
|
Chris@0
|
330 $this->redirect = null;
|
Chris@0
|
331 }
|
Chris@0
|
332
|
Chris@0
|
333 if ($this->followRedirects && $this->redirect) {
|
Chris@12
|
334 $this->redirects[serialize($this->history->current())] = true;
|
Chris@12
|
335
|
Chris@0
|
336 return $this->crawler = $this->followRedirect();
|
Chris@0
|
337 }
|
Chris@0
|
338
|
Chris@0
|
339 return $this->crawler = $this->createCrawlerFromContent($this->internalRequest->getUri(), $this->internalResponse->getContent(), $this->internalResponse->getHeader('Content-Type'));
|
Chris@0
|
340 }
|
Chris@0
|
341
|
Chris@0
|
342 /**
|
Chris@0
|
343 * Makes a request in another process.
|
Chris@0
|
344 *
|
Chris@0
|
345 * @param object $request An origin request instance
|
Chris@0
|
346 *
|
Chris@0
|
347 * @return object An origin response instance
|
Chris@0
|
348 *
|
Chris@0
|
349 * @throws \RuntimeException When processing returns exit code
|
Chris@0
|
350 */
|
Chris@0
|
351 protected function doRequestInProcess($request)
|
Chris@0
|
352 {
|
Chris@12
|
353 $deprecationsFile = tempnam(sys_get_temp_dir(), 'deprec');
|
Chris@12
|
354 putenv('SYMFONY_DEPRECATIONS_SERIALIZE='.$deprecationsFile);
|
Chris@12
|
355 $_ENV['SYMFONY_DEPRECATIONS_SERIALIZE'] = $deprecationsFile;
|
Chris@0
|
356 $process = new PhpProcess($this->getScript($request), null, null);
|
Chris@0
|
357 $process->run();
|
Chris@0
|
358
|
Chris@12
|
359 if (file_exists($deprecationsFile)) {
|
Chris@12
|
360 $deprecations = file_get_contents($deprecationsFile);
|
Chris@12
|
361 unlink($deprecationsFile);
|
Chris@17
|
362 foreach ($deprecations ? unserialize($deprecations) : [] as $deprecation) {
|
Chris@12
|
363 if ($deprecation[0]) {
|
Chris@17
|
364 @trigger_error($deprecation[1], E_USER_DEPRECATED);
|
Chris@12
|
365 } else {
|
Chris@12
|
366 @trigger_error($deprecation[1], E_USER_DEPRECATED);
|
Chris@12
|
367 }
|
Chris@12
|
368 }
|
Chris@12
|
369 }
|
Chris@12
|
370
|
Chris@0
|
371 if (!$process->isSuccessful() || !preg_match('/^O\:\d+\:/', $process->getOutput())) {
|
Chris@0
|
372 throw new \RuntimeException(sprintf('OUTPUT: %s ERROR OUTPUT: %s', $process->getOutput(), $process->getErrorOutput()));
|
Chris@0
|
373 }
|
Chris@0
|
374
|
Chris@0
|
375 return unserialize($process->getOutput());
|
Chris@0
|
376 }
|
Chris@0
|
377
|
Chris@0
|
378 /**
|
Chris@0
|
379 * Makes a request.
|
Chris@0
|
380 *
|
Chris@0
|
381 * @param object $request An origin request instance
|
Chris@0
|
382 *
|
Chris@0
|
383 * @return object An origin response instance
|
Chris@0
|
384 */
|
Chris@0
|
385 abstract protected function doRequest($request);
|
Chris@0
|
386
|
Chris@0
|
387 /**
|
Chris@0
|
388 * Returns the script to execute when the request must be insulated.
|
Chris@0
|
389 *
|
Chris@0
|
390 * @param object $request An origin request instance
|
Chris@0
|
391 *
|
Chris@0
|
392 * @throws \LogicException When this abstract class is not implemented
|
Chris@0
|
393 */
|
Chris@0
|
394 protected function getScript($request)
|
Chris@0
|
395 {
|
Chris@0
|
396 throw new \LogicException('To insulate requests, you need to override the getScript() method.');
|
Chris@0
|
397 }
|
Chris@0
|
398
|
Chris@0
|
399 /**
|
Chris@0
|
400 * Filters the BrowserKit request to the origin one.
|
Chris@0
|
401 *
|
Chris@0
|
402 * @param Request $request The BrowserKit Request to filter
|
Chris@0
|
403 *
|
Chris@0
|
404 * @return object An origin request instance
|
Chris@0
|
405 */
|
Chris@0
|
406 protected function filterRequest(Request $request)
|
Chris@0
|
407 {
|
Chris@0
|
408 return $request;
|
Chris@0
|
409 }
|
Chris@0
|
410
|
Chris@0
|
411 /**
|
Chris@0
|
412 * Filters the origin response to the BrowserKit one.
|
Chris@0
|
413 *
|
Chris@0
|
414 * @param object $response The origin response to filter
|
Chris@0
|
415 *
|
Chris@0
|
416 * @return Response An BrowserKit Response instance
|
Chris@0
|
417 */
|
Chris@0
|
418 protected function filterResponse($response)
|
Chris@0
|
419 {
|
Chris@0
|
420 return $response;
|
Chris@0
|
421 }
|
Chris@0
|
422
|
Chris@0
|
423 /**
|
Chris@0
|
424 * Creates a crawler.
|
Chris@0
|
425 *
|
Chris@0
|
426 * This method returns null if the DomCrawler component is not available.
|
Chris@0
|
427 *
|
Chris@0
|
428 * @param string $uri A URI
|
Chris@0
|
429 * @param string $content Content for the crawler to use
|
Chris@0
|
430 * @param string $type Content type
|
Chris@0
|
431 *
|
Chris@0
|
432 * @return Crawler|null
|
Chris@0
|
433 */
|
Chris@0
|
434 protected function createCrawlerFromContent($uri, $content, $type)
|
Chris@0
|
435 {
|
Chris@0
|
436 if (!class_exists('Symfony\Component\DomCrawler\Crawler')) {
|
Chris@0
|
437 return;
|
Chris@0
|
438 }
|
Chris@0
|
439
|
Chris@0
|
440 $crawler = new Crawler(null, $uri);
|
Chris@0
|
441 $crawler->addContent($content, $type);
|
Chris@0
|
442
|
Chris@0
|
443 return $crawler;
|
Chris@0
|
444 }
|
Chris@0
|
445
|
Chris@0
|
446 /**
|
Chris@0
|
447 * Goes back in the browser history.
|
Chris@0
|
448 *
|
Chris@0
|
449 * @return Crawler
|
Chris@0
|
450 */
|
Chris@0
|
451 public function back()
|
Chris@0
|
452 {
|
Chris@12
|
453 do {
|
Chris@12
|
454 $request = $this->history->back();
|
Chris@18
|
455 } while (\array_key_exists(serialize($request), $this->redirects));
|
Chris@12
|
456
|
Chris@12
|
457 return $this->requestFromRequest($request, false);
|
Chris@0
|
458 }
|
Chris@0
|
459
|
Chris@0
|
460 /**
|
Chris@0
|
461 * Goes forward in the browser history.
|
Chris@0
|
462 *
|
Chris@0
|
463 * @return Crawler
|
Chris@0
|
464 */
|
Chris@0
|
465 public function forward()
|
Chris@0
|
466 {
|
Chris@12
|
467 do {
|
Chris@12
|
468 $request = $this->history->forward();
|
Chris@18
|
469 } while (\array_key_exists(serialize($request), $this->redirects));
|
Chris@12
|
470
|
Chris@12
|
471 return $this->requestFromRequest($request, false);
|
Chris@0
|
472 }
|
Chris@0
|
473
|
Chris@0
|
474 /**
|
Chris@0
|
475 * Reloads the current browser.
|
Chris@0
|
476 *
|
Chris@0
|
477 * @return Crawler
|
Chris@0
|
478 */
|
Chris@0
|
479 public function reload()
|
Chris@0
|
480 {
|
Chris@0
|
481 return $this->requestFromRequest($this->history->current(), false);
|
Chris@0
|
482 }
|
Chris@0
|
483
|
Chris@0
|
484 /**
|
Chris@0
|
485 * Follow redirects?
|
Chris@0
|
486 *
|
Chris@0
|
487 * @return Crawler
|
Chris@0
|
488 *
|
Chris@0
|
489 * @throws \LogicException If request was not a redirect
|
Chris@0
|
490 */
|
Chris@0
|
491 public function followRedirect()
|
Chris@0
|
492 {
|
Chris@0
|
493 if (empty($this->redirect)) {
|
Chris@0
|
494 throw new \LogicException('The request was not redirected.');
|
Chris@0
|
495 }
|
Chris@0
|
496
|
Chris@0
|
497 if (-1 !== $this->maxRedirects) {
|
Chris@0
|
498 if ($this->redirectCount > $this->maxRedirects) {
|
Chris@12
|
499 $this->redirectCount = 0;
|
Chris@0
|
500 throw new \LogicException(sprintf('The maximum number (%d) of redirections was reached.', $this->maxRedirects));
|
Chris@0
|
501 }
|
Chris@0
|
502 }
|
Chris@0
|
503
|
Chris@0
|
504 $request = $this->internalRequest;
|
Chris@0
|
505
|
Chris@17
|
506 if (\in_array($this->internalResponse->getStatus(), [301, 302, 303])) {
|
Chris@0
|
507 $method = 'GET';
|
Chris@17
|
508 $files = [];
|
Chris@0
|
509 $content = null;
|
Chris@0
|
510 } else {
|
Chris@0
|
511 $method = $request->getMethod();
|
Chris@0
|
512 $files = $request->getFiles();
|
Chris@0
|
513 $content = $request->getContent();
|
Chris@0
|
514 }
|
Chris@0
|
515
|
Chris@0
|
516 if ('GET' === strtoupper($method)) {
|
Chris@0
|
517 // Don't forward parameters for GET request as it should reach the redirection URI
|
Chris@17
|
518 $parameters = [];
|
Chris@0
|
519 } else {
|
Chris@0
|
520 $parameters = $request->getParameters();
|
Chris@0
|
521 }
|
Chris@0
|
522
|
Chris@0
|
523 $server = $request->getServer();
|
Chris@0
|
524 $server = $this->updateServerFromUri($server, $this->redirect);
|
Chris@0
|
525
|
Chris@0
|
526 $this->isMainRequest = false;
|
Chris@0
|
527
|
Chris@0
|
528 $response = $this->request($method, $this->redirect, $parameters, $files, $server, $content);
|
Chris@0
|
529
|
Chris@0
|
530 $this->isMainRequest = true;
|
Chris@0
|
531
|
Chris@0
|
532 return $response;
|
Chris@0
|
533 }
|
Chris@0
|
534
|
Chris@0
|
535 /**
|
Chris@0
|
536 * Restarts the client.
|
Chris@0
|
537 *
|
Chris@0
|
538 * It flushes history and all cookies.
|
Chris@0
|
539 */
|
Chris@0
|
540 public function restart()
|
Chris@0
|
541 {
|
Chris@0
|
542 $this->cookieJar->clear();
|
Chris@0
|
543 $this->history->clear();
|
Chris@0
|
544 }
|
Chris@0
|
545
|
Chris@0
|
546 /**
|
Chris@0
|
547 * Takes a URI and converts it to absolute if it is not already absolute.
|
Chris@0
|
548 *
|
Chris@0
|
549 * @param string $uri A URI
|
Chris@0
|
550 *
|
Chris@0
|
551 * @return string An absolute URI
|
Chris@0
|
552 */
|
Chris@0
|
553 protected function getAbsoluteUri($uri)
|
Chris@0
|
554 {
|
Chris@0
|
555 // already absolute?
|
Chris@0
|
556 if (0 === strpos($uri, 'http://') || 0 === strpos($uri, 'https://')) {
|
Chris@0
|
557 return $uri;
|
Chris@0
|
558 }
|
Chris@0
|
559
|
Chris@0
|
560 if (!$this->history->isEmpty()) {
|
Chris@0
|
561 $currentUri = $this->history->current()->getUri();
|
Chris@0
|
562 } else {
|
Chris@0
|
563 $currentUri = sprintf('http%s://%s/',
|
Chris@0
|
564 isset($this->server['HTTPS']) ? 's' : '',
|
Chris@0
|
565 isset($this->server['HTTP_HOST']) ? $this->server['HTTP_HOST'] : 'localhost'
|
Chris@0
|
566 );
|
Chris@0
|
567 }
|
Chris@0
|
568
|
Chris@0
|
569 // protocol relative URL
|
Chris@0
|
570 if (0 === strpos($uri, '//')) {
|
Chris@0
|
571 return parse_url($currentUri, PHP_URL_SCHEME).':'.$uri;
|
Chris@0
|
572 }
|
Chris@0
|
573
|
Chris@0
|
574 // anchor or query string parameters?
|
Chris@0
|
575 if (!$uri || '#' == $uri[0] || '?' == $uri[0]) {
|
Chris@0
|
576 return preg_replace('/[#?].*?$/', '', $currentUri).$uri;
|
Chris@0
|
577 }
|
Chris@0
|
578
|
Chris@0
|
579 if ('/' !== $uri[0]) {
|
Chris@0
|
580 $path = parse_url($currentUri, PHP_URL_PATH);
|
Chris@0
|
581
|
Chris@0
|
582 if ('/' !== substr($path, -1)) {
|
Chris@0
|
583 $path = substr($path, 0, strrpos($path, '/') + 1);
|
Chris@0
|
584 }
|
Chris@0
|
585
|
Chris@0
|
586 $uri = $path.$uri;
|
Chris@0
|
587 }
|
Chris@0
|
588
|
Chris@0
|
589 return preg_replace('#^(.*?//[^/]+)\/.*$#', '$1', $currentUri).$uri;
|
Chris@0
|
590 }
|
Chris@0
|
591
|
Chris@0
|
592 /**
|
Chris@0
|
593 * Makes a request from a Request object directly.
|
Chris@0
|
594 *
|
Chris@0
|
595 * @param Request $request A Request instance
|
Chris@0
|
596 * @param bool $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload())
|
Chris@0
|
597 *
|
Chris@0
|
598 * @return Crawler
|
Chris@0
|
599 */
|
Chris@0
|
600 protected function requestFromRequest(Request $request, $changeHistory = true)
|
Chris@0
|
601 {
|
Chris@0
|
602 return $this->request($request->getMethod(), $request->getUri(), $request->getParameters(), $request->getFiles(), $request->getServer(), $request->getContent(), $changeHistory);
|
Chris@0
|
603 }
|
Chris@0
|
604
|
Chris@0
|
605 private function updateServerFromUri($server, $uri)
|
Chris@0
|
606 {
|
Chris@0
|
607 $server['HTTP_HOST'] = $this->extractHost($uri);
|
Chris@0
|
608 $scheme = parse_url($uri, PHP_URL_SCHEME);
|
Chris@0
|
609 $server['HTTPS'] = null === $scheme ? $server['HTTPS'] : 'https' == $scheme;
|
Chris@0
|
610 unset($server['HTTP_IF_NONE_MATCH'], $server['HTTP_IF_MODIFIED_SINCE']);
|
Chris@0
|
611
|
Chris@0
|
612 return $server;
|
Chris@0
|
613 }
|
Chris@0
|
614
|
Chris@0
|
615 private function extractHost($uri)
|
Chris@0
|
616 {
|
Chris@0
|
617 $host = parse_url($uri, PHP_URL_HOST);
|
Chris@0
|
618
|
Chris@0
|
619 if ($port = parse_url($uri, PHP_URL_PORT)) {
|
Chris@0
|
620 return $host.':'.$port;
|
Chris@0
|
621 }
|
Chris@0
|
622
|
Chris@0
|
623 return $host;
|
Chris@0
|
624 }
|
Chris@0
|
625 }
|