[TASK] Update PEAR packages
[Packages/TYPO3.CMS.git] / typo3 / contrib / pear / Net / URL2.php
1 <?php
2 /**
3 * Net_URL2, a class representing a URL as per RFC 3986.
4 *
5 * PHP version 5
6 *
7 * LICENSE:
8 *
9 * Copyright (c) 2007-2009, Peytz & Co. A/S
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 *
16 * * Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * * Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in
20 * the documentation and/or other materials provided with the distribution.
21 * * Neither the name of the Net_URL2 nor the names of its contributors may
22 * be used to endorse or promote products derived from this software
23 * without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
26 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
27 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
30 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
31 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
32 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
33 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
34 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 *
37 * @category Networking
38 * @package Net_URL2
39 * @author Christian Schmidt <schmidt@php.net>
40 * @copyright 2007-2009 Peytz & Co. A/S
41 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
42 * @version CVS: $Id: URL2.php 309223 2011-03-14 14:26:32Z till $
43 * @link http://www.rfc-editor.org/rfc/rfc3986.txt
44 */
45
46 /**
47 * Represents a URL as per RFC 3986.
48 *
49 * @category Networking
50 * @package Net_URL2
51 * @author Christian Schmidt <schmidt@php.net>
52 * @copyright 2007-2009 Peytz & Co. A/S
53 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
54 * @version Release: @package_version@
55 * @link http://pear.php.net/package/Net_URL2
56 */
57 class Net_URL2
58 {
59 /**
60 * Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default
61 * is true.
62 */
63 const OPTION_STRICT = 'strict';
64
65 /**
66 * Represent arrays in query using PHP's [] notation. Default is true.
67 */
68 const OPTION_USE_BRACKETS = 'use_brackets';
69
70 /**
71 * URL-encode query variable keys. Default is true.
72 */
73 const OPTION_ENCODE_KEYS = 'encode_keys';
74
75 /**
76 * Query variable separators when parsing the query string. Every character
77 * is considered a separator. Default is "&".
78 */
79 const OPTION_SEPARATOR_INPUT = 'input_separator';
80
81 /**
82 * Query variable separator used when generating the query string. Default
83 * is "&".
84 */
85 const OPTION_SEPARATOR_OUTPUT = 'output_separator';
86
87 /**
88 * Default options corresponds to how PHP handles $_GET.
89 */
90 private $_options = array(
91 self::OPTION_STRICT => true,
92 self::OPTION_USE_BRACKETS => true,
93 self::OPTION_ENCODE_KEYS => true,
94 self::OPTION_SEPARATOR_INPUT => '&',
95 self::OPTION_SEPARATOR_OUTPUT => '&',
96 );
97
98 /**
99 * @var string|bool
100 */
101 private $_scheme = false;
102
103 /**
104 * @var string|bool
105 */
106 private $_userinfo = false;
107
108 /**
109 * @var string|bool
110 */
111 private $_host = false;
112
113 /**
114 * @var string|bool
115 */
116 private $_port = false;
117
118 /**
119 * @var string
120 */
121 private $_path = '';
122
123 /**
124 * @var string|bool
125 */
126 private $_query = false;
127
128 /**
129 * @var string|bool
130 */
131 private $_fragment = false;
132
133 /**
134 * Constructor.
135 *
136 * @param string $url an absolute or relative URL
137 * @param array $options an array of OPTION_xxx constants
138 *
139 * @return $this
140 * @uses self::parseUrl()
141 */
142 public function __construct($url, array $options = array())
143 {
144 foreach ($options as $optionName => $value) {
145 if (array_key_exists($optionName, $this->_options)) {
146 $this->_options[$optionName] = $value;
147 }
148 }
149
150 $this->parseUrl($url);
151 }
152
153 /**
154 * Magic Setter.
155 *
156 * This method will magically set the value of a private variable ($var)
157 * with the value passed as the args
158 *
159 * @param string $var The private variable to set.
160 * @param mixed $arg An argument of any type.
161 * @return void
162 */
163 public function __set($var, $arg)
164 {
165 $method = 'set' . $var;
166 if (method_exists($this, $method)) {
167 $this->$method($arg);
168 }
169 }
170
171 /**
172 * Magic Getter.
173 *
174 * This is the magic get method to retrieve the private variable
175 * that was set by either __set() or it's setter...
176 *
177 * @param string $var The property name to retrieve.
178 * @return mixed $this->$var Either a boolean false if the
179 * property is not set or the value
180 * of the private property.
181 */
182 public function __get($var)
183 {
184 $method = 'get' . $var;
185 if (method_exists($this, $method)) {
186 return $this->$method();
187 }
188
189 return false;
190 }
191
192 /**
193 * Returns the scheme, e.g. "http" or "urn", or false if there is no
194 * scheme specified, i.e. if this is a relative URL.
195 *
196 * @return string|bool
197 */
198 public function getScheme()
199 {
200 return $this->_scheme;
201 }
202
203 /**
204 * Sets the scheme, e.g. "http" or "urn". Specify false if there is no
205 * scheme specified, i.e. if this is a relative URL.
206 *
207 * @param string|bool $scheme e.g. "http" or "urn", or false if there is no
208 * scheme specified, i.e. if this is a relative
209 * URL
210 *
211 * @return $this
212 * @see getScheme()
213 */
214 public function setScheme($scheme)
215 {
216 $this->_scheme = $scheme;
217 return $this;
218 }
219
220 /**
221 * Returns the user part of the userinfo part (the part preceding the first
222 * ":"), or false if there is no userinfo part.
223 *
224 * @return string|bool
225 */
226 public function getUser()
227 {
228 return $this->_userinfo !== false
229 ? preg_replace('@:.*$@', '', $this->_userinfo)
230 : false;
231 }
232
233 /**
234 * Returns the password part of the userinfo part (the part after the first
235 * ":"), or false if there is no userinfo part (i.e. the URL does not
236 * contain "@" in front of the hostname) or the userinfo part does not
237 * contain ":".
238 *
239 * @return string|bool
240 */
241 public function getPassword()
242 {
243 return $this->_userinfo !== false
244 ? substr(strstr($this->_userinfo, ':'), 1)
245 : false;
246 }
247
248 /**
249 * Returns the userinfo part, or false if there is none, i.e. if the
250 * authority part does not contain "@".
251 *
252 * @return string|bool
253 */
254 public function getUserinfo()
255 {
256 return $this->_userinfo;
257 }
258
259 /**
260 * Sets the userinfo part. If two arguments are passed, they are combined
261 * in the userinfo part as username ":" password.
262 *
263 * @param string|bool $userinfo userinfo or username
264 * @param string|bool $password optional password, or false
265 *
266 * @return $this
267 */
268 public function setUserinfo($userinfo, $password = false)
269 {
270 $this->_userinfo = $userinfo;
271 if ($password !== false) {
272 $this->_userinfo .= ':' . $password;
273 }
274 return $this;
275 }
276
277 /**
278 * Returns the host part, or false if there is no authority part, e.g.
279 * relative URLs.
280 *
281 * @return string|bool a hostname, an IP address, or false
282 */
283 public function getHost()
284 {
285 return $this->_host;
286 }
287
288 /**
289 * Sets the host part. Specify false if there is no authority part, e.g.
290 * relative URLs.
291 *
292 * @param string|bool $host a hostname, an IP address, or false
293 *
294 * @return $this
295 */
296 public function setHost($host)
297 {
298 $this->_host = $host;
299 return $this;
300 }
301
302 /**
303 * Returns the port number, or false if there is no port number specified,
304 * i.e. if the default port is to be used.
305 *
306 * @return string|bool
307 */
308 public function getPort()
309 {
310 return $this->_port;
311 }
312
313 /**
314 * Sets the port number. Specify false if there is no port number specified,
315 * i.e. if the default port is to be used.
316 *
317 * @param string|bool $port a port number, or false
318 *
319 * @return $this
320 */
321 public function setPort($port)
322 {
323 $this->_port = $port;
324 return $this;
325 }
326
327 /**
328 * Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or
329 * false if there is no authority.
330 *
331 * @return string|bool
332 */
333 public function getAuthority()
334 {
335 if (!$this->_host) {
336 return false;
337 }
338
339 $authority = '';
340
341 if ($this->_userinfo !== false) {
342 $authority .= $this->_userinfo . '@';
343 }
344
345 $authority .= $this->_host;
346
347 if ($this->_port !== false) {
348 $authority .= ':' . $this->_port;
349 }
350
351 return $authority;
352 }
353
354 /**
355 * Sets the authority part, i.e. [ userinfo "@" ] host [ ":" port ]. Specify
356 * false if there is no authority.
357 *
358 * @param string|false $authority a hostname or an IP addresse, possibly
359 * with userinfo prefixed and port number
360 * appended, e.g. "foo:bar@example.org:81".
361 *
362 * @return $this
363 */
364 public function setAuthority($authority)
365 {
366 $this->_userinfo = false;
367 $this->_host = false;
368 $this->_port = false;
369 if (preg_match('@^(([^\@]*)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) {
370 if ($reg[1]) {
371 $this->_userinfo = $reg[2];
372 }
373
374 $this->_host = $reg[3];
375 if (isset($reg[5])) {
376 $this->_port = $reg[5];
377 }
378 }
379 return $this;
380 }
381
382 /**
383 * Returns the path part (possibly an empty string).
384 *
385 * @return string
386 */
387 public function getPath()
388 {
389 return $this->_path;
390 }
391
392 /**
393 * Sets the path part (possibly an empty string).
394 *
395 * @param string $path a path
396 *
397 * @return $this
398 */
399 public function setPath($path)
400 {
401 $this->_path = $path;
402 return $this;
403 }
404
405 /**
406 * Returns the query string (excluding the leading "?"), or false if "?"
407 * is not present in the URL.
408 *
409 * @return string|bool
410 * @see self::getQueryVariables()
411 */
412 public function getQuery()
413 {
414 return $this->_query;
415 }
416
417 /**
418 * Sets the query string (excluding the leading "?"). Specify false if "?"
419 * is not present in the URL.
420 *
421 * @param string|bool $query a query string, e.g. "foo=1&bar=2"
422 *
423 * @return $this
424 * @see self::setQueryVariables()
425 */
426 public function setQuery($query)
427 {
428 $this->_query = $query;
429 return $this;
430 }
431
432 /**
433 * Returns the fragment name, or false if "#" is not present in the URL.
434 *
435 * @return string|bool
436 */
437 public function getFragment()
438 {
439 return $this->_fragment;
440 }
441
442 /**
443 * Sets the fragment name. Specify false if "#" is not present in the URL.
444 *
445 * @param string|bool $fragment a fragment excluding the leading "#", or
446 * false
447 *
448 * @return $this
449 */
450 public function setFragment($fragment)
451 {
452 $this->_fragment = $fragment;
453 return $this;
454 }
455
456 /**
457 * Returns the query string like an array as the variables would appear in
458 * $_GET in a PHP script. If the URL does not contain a "?", an empty array
459 * is returned.
460 *
461 * @return array
462 */
463 public function getQueryVariables()
464 {
465 $pattern = '/[' .
466 preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') .
467 ']/';
468 $parts = preg_split($pattern, $this->_query, -1, PREG_SPLIT_NO_EMPTY);
469 $return = array();
470
471 foreach ($parts as $part) {
472 if (strpos($part, '=') !== false) {
473 list($key, $value) = explode('=', $part, 2);
474 } else {
475 $key = $part;
476 $value = null;
477 }
478
479 if ($this->getOption(self::OPTION_ENCODE_KEYS)) {
480 $key = rawurldecode($key);
481 }
482 $value = rawurldecode($value);
483
484 if ($this->getOption(self::OPTION_USE_BRACKETS) &&
485 preg_match('#^(.*)\[([0-9a-z_-]*)\]#i', $key, $matches)) {
486
487 $key = $matches[1];
488 $idx = $matches[2];
489
490 // Ensure is an array
491 if (empty($return[$key]) || !is_array($return[$key])) {
492 $return[$key] = array();
493 }
494
495 // Add data
496 if ($idx === '') {
497 $return[$key][] = $value;
498 } else {
499 $return[$key][$idx] = $value;
500 }
501 } elseif (!$this->getOption(self::OPTION_USE_BRACKETS)
502 && !empty($return[$key])
503 ) {
504 $return[$key] = (array) $return[$key];
505 $return[$key][] = $value;
506 } else {
507 $return[$key] = $value;
508 }
509 }
510
511 return $return;
512 }
513
514 /**
515 * Sets the query string to the specified variable in the query string.
516 *
517 * @param array $array (name => value) array
518 *
519 * @return $this
520 */
521 public function setQueryVariables(array $array)
522 {
523 if (!$array) {
524 $this->_query = false;
525 } else {
526 $this->_query = $this->buildQuery(
527 $array,
528 $this->getOption(self::OPTION_SEPARATOR_OUTPUT)
529 );
530 }
531 return $this;
532 }
533
534 /**
535 * Sets the specified variable in the query string.
536 *
537 * @param string $name variable name
538 * @param mixed $value variable value
539 *
540 * @return $this
541 */
542 public function setQueryVariable($name, $value)
543 {
544 $array = $this->getQueryVariables();
545 $array[$name] = $value;
546 $this->setQueryVariables($array);
547 return $this;
548 }
549
550 /**
551 * Removes the specifed variable from the query string.
552 *
553 * @param string $name a query string variable, e.g. "foo" in "?foo=1"
554 *
555 * @return void
556 */
557 public function unsetQueryVariable($name)
558 {
559 $array = $this->getQueryVariables();
560 unset($array[$name]);
561 $this->setQueryVariables($array);
562 }
563
564 /**
565 * Returns a string representation of this URL.
566 *
567 * @return string
568 */
569 public function getURL()
570 {
571 // See RFC 3986, section 5.3
572 $url = "";
573
574 if ($this->_scheme !== false) {
575 $url .= $this->_scheme . ':';
576 }
577
578 $authority = $this->getAuthority();
579 if ($authority !== false) {
580 $url .= '//' . $authority;
581 }
582 $url .= $this->_path;
583
584 if ($this->_query !== false) {
585 $url .= '?' . $this->_query;
586 }
587
588 if ($this->_fragment !== false) {
589 $url .= '#' . $this->_fragment;
590 }
591
592 return $url;
593 }
594
595 /**
596 * Returns a string representation of this URL.
597 *
598 * @return string
599 * @see toString()
600 */
601 public function __toString()
602 {
603 return $this->getURL();
604 }
605
606 /**
607 * Returns a normalized string representation of this URL. This is useful
608 * for comparison of URLs.
609 *
610 * @return string
611 */
612 public function getNormalizedURL()
613 {
614 $url = clone $this;
615 $url->normalize();
616 return $url->getUrl();
617 }
618
619 /**
620 * Returns a normalized Net_URL2 instance.
621 *
622 * @return Net_URL2
623 */
624 public function normalize()
625 {
626 // See RFC 3886, section 6
627
628 // Schemes are case-insensitive
629 if ($this->_scheme) {
630 $this->_scheme = strtolower($this->_scheme);
631 }
632
633 // Hostnames are case-insensitive
634 if ($this->_host) {
635 $this->_host = strtolower($this->_host);
636 }
637
638 // Remove default port number for known schemes (RFC 3986, section 6.2.3)
639 if ($this->_port &&
640 $this->_scheme &&
641 $this->_port == getservbyname($this->_scheme, 'tcp')) {
642
643 $this->_port = false;
644 }
645
646 // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1)
647 foreach (array('_userinfo', '_host', '_path') as $part) {
648 if ($this->$part) {
649 $this->$part = preg_replace('/%[0-9a-f]{2}/ie',
650 'strtoupper("\0")',
651 $this->$part);
652 }
653 }
654
655 // Path segment normalization (RFC 3986, section 6.2.2.3)
656 $this->_path = self::removeDotSegments($this->_path);
657
658 // Scheme based normalization (RFC 3986, section 6.2.3)
659 if ($this->_host && !$this->_path) {
660 $this->_path = '/';
661 }
662 }
663
664 /**
665 * Returns whether this instance represents an absolute URL.
666 *
667 * @return bool
668 */
669 public function isAbsolute()
670 {
671 return (bool) $this->_scheme;
672 }
673
674 /**
675 * Returns an Net_URL2 instance representing an absolute URL relative to
676 * this URL.
677 *
678 * @param Net_URL2|string $reference relative URL
679 *
680 * @return Net_URL2
681 */
682 public function resolve($reference)
683 {
684 if (!$reference instanceof Net_URL2) {
685 $reference = new self($reference);
686 }
687 if (!$this->isAbsolute()) {
688 throw new Exception('Base-URL must be absolute');
689 }
690
691 // A non-strict parser may ignore a scheme in the reference if it is
692 // identical to the base URI's scheme.
693 if (!$this->getOption(self::OPTION_STRICT) && $reference->_scheme == $this->_scheme) {
694 $reference->_scheme = false;
695 }
696
697 $target = new self('');
698 if ($reference->_scheme !== false) {
699 $target->_scheme = $reference->_scheme;
700 $target->setAuthority($reference->getAuthority());
701 $target->_path = self::removeDotSegments($reference->_path);
702 $target->_query = $reference->_query;
703 } else {
704 $authority = $reference->getAuthority();
705 if ($authority !== false) {
706 $target->setAuthority($authority);
707 $target->_path = self::removeDotSegments($reference->_path);
708 $target->_query = $reference->_query;
709 } else {
710 if ($reference->_path == '') {
711 $target->_path = $this->_path;
712 if ($reference->_query !== false) {
713 $target->_query = $reference->_query;
714 } else {
715 $target->_query = $this->_query;
716 }
717 } else {
718 if (substr($reference->_path, 0, 1) == '/') {
719 $target->_path = self::removeDotSegments($reference->_path);
720 } else {
721 // Merge paths (RFC 3986, section 5.2.3)
722 if ($this->_host !== false && $this->_path == '') {
723 $target->_path = '/' . $this->_path;
724 } else {
725 $i = strrpos($this->_path, '/');
726 if ($i !== false) {
727 $target->_path = substr($this->_path, 0, $i + 1);
728 }
729 $target->_path .= $reference->_path;
730 }
731 $target->_path = self::removeDotSegments($target->_path);
732 }
733 $target->_query = $reference->_query;
734 }
735 $target->setAuthority($this->getAuthority());
736 }
737 $target->_scheme = $this->_scheme;
738 }
739
740 $target->_fragment = $reference->_fragment;
741
742 return $target;
743 }
744
745 /**
746 * Removes dots as described in RFC 3986, section 5.2.4, e.g.
747 * "/foo/../bar/baz" => "/bar/baz"
748 *
749 * @param string $path a path
750 *
751 * @return string a path
752 */
753 public static function removeDotSegments($path)
754 {
755 $output = '';
756
757 // Make sure not to be trapped in an infinite loop due to a bug in this
758 // method
759 $j = 0;
760 while ($path && $j++ < 100) {
761 if (substr($path, 0, 2) == './') {
762 // Step 2.A
763 $path = substr($path, 2);
764 } elseif (substr($path, 0, 3) == '../') {
765 // Step 2.A
766 $path = substr($path, 3);
767 } elseif (substr($path, 0, 3) == '/./' || $path == '/.') {
768 // Step 2.B
769 $path = '/' . substr($path, 3);
770 } elseif (substr($path, 0, 4) == '/../' || $path == '/..') {
771 // Step 2.C
772 $path = '/' . substr($path, 4);
773 $i = strrpos($output, '/');
774 $output = $i === false ? '' : substr($output, 0, $i);
775 } elseif ($path == '.' || $path == '..') {
776 // Step 2.D
777 $path = '';
778 } else {
779 // Step 2.E
780 $i = strpos($path, '/');
781 if ($i === 0) {
782 $i = strpos($path, '/', 1);
783 }
784 if ($i === false) {
785 $i = strlen($path);
786 }
787 $output .= substr($path, 0, $i);
788 $path = substr($path, $i);
789 }
790 }
791
792 return $output;
793 }
794
795 /**
796 * Percent-encodes all non-alphanumeric characters except these: _ . - ~
797 * Similar to PHP's rawurlencode(), except that it also encodes ~ in PHP
798 * 5.2.x and earlier.
799 *
800 * @param $raw the string to encode
801 * @return string
802 */
803 public static function urlencode($string)
804 {
805 $encoded = rawurlencode($string);
806
807 // This is only necessary in PHP < 5.3.
808 $encoded = str_replace('%7E', '~', $encoded);
809 return $encoded;
810 }
811
812 /**
813 * Returns a Net_URL2 instance representing the canonical URL of the
814 * currently executing PHP script.
815 *
816 * @return string
817 */
818 public static function getCanonical()
819 {
820 if (!isset($_SERVER['REQUEST_METHOD'])) {
821 // ALERT - no current URL
822 throw new Exception('Script was not called through a webserver');
823 }
824
825 // Begin with a relative URL
826 $url = new self($_SERVER['PHP_SELF']);
827 $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
828 $url->_host = $_SERVER['SERVER_NAME'];
829 $port = $_SERVER['SERVER_PORT'];
830 if ($url->_scheme == 'http' && $port != 80 ||
831 $url->_scheme == 'https' && $port != 443) {
832
833 $url->_port = $port;
834 }
835 return $url;
836 }
837
838 /**
839 * Returns the URL used to retrieve the current request.
840 *
841 * @return string
842 */
843 public static function getRequestedURL()
844 {
845 return self::getRequested()->getUrl();
846 }
847
848 /**
849 * Returns a Net_URL2 instance representing the URL used to retrieve the
850 * current request.
851 *
852 * @return Net_URL2
853 */
854 public static function getRequested()
855 {
856 if (!isset($_SERVER['REQUEST_METHOD'])) {
857 // ALERT - no current URL
858 throw new Exception('Script was not called through a webserver');
859 }
860
861 // Begin with a relative URL
862 $url = new self($_SERVER['REQUEST_URI']);
863 $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
864 // Set host and possibly port
865 $url->setAuthority($_SERVER['HTTP_HOST']);
866 return $url;
867 }
868
869 /**
870 * Returns the value of the specified option.
871 *
872 * @param string $optionName The name of the option to retrieve
873 *
874 * @return mixed
875 */
876 public function getOption($optionName)
877 {
878 return isset($this->_options[$optionName])
879 ? $this->_options[$optionName] : false;
880 }
881
882 /**
883 * A simple version of http_build_query in userland. The encoded string is
884 * percentage encoded according to RFC 3986.
885 *
886 * @param array $data An array, which has to be converted into
887 * QUERY_STRING. Anything is possible.
888 * @param string $seperator See {@link self::OPTION_SEPARATOR_OUTPUT}
889 * @param string $key For stacked values (arrays in an array).
890 *
891 * @return string
892 */
893 protected function buildQuery(array $data, $separator, $key = null)
894 {
895 $query = array();
896 foreach ($data as $name => $value) {
897 if ($this->getOption(self::OPTION_ENCODE_KEYS) === true) {
898 $name = rawurlencode($name);
899 }
900 if ($key !== null) {
901 if ($this->getOption(self::OPTION_USE_BRACKETS) === true) {
902 $name = $key . '[' . $name . ']';
903 } else {
904 $name = $key;
905 }
906 }
907 if (is_array($value)) {
908 $query[] = $this->buildQuery($value, $separator, $name);
909 } else {
910 $query[] = $name . '=' . rawurlencode($value);
911 }
912 }
913 return implode($separator, $query);
914 }
915
916 /**
917 * This method uses a funky regex to parse the url into the designated parts.
918 *
919 * @param string $url
920 *
921 * @return void
922 * @uses self::$_scheme, self::setAuthority(), self::$_path, self::$_query,
923 * self::$_fragment
924 * @see self::__construct()
925 */
926 protected function parseUrl($url)
927 {
928 // The regular expression is copied verbatim from RFC 3986, appendix B.
929 // The expression does not validate the URL but matches any string.
930 preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!',
931 $url,
932 $matches);
933
934 // "path" is always present (possibly as an empty string); the rest
935 // are optional.
936 $this->_scheme = !empty($matches[1]) ? $matches[2] : false;
937 $this->setAuthority(!empty($matches[3]) ? $matches[4] : false);
938 $this->_path = $matches[5];
939 $this->_query = !empty($matches[6]) ? $matches[7] : false;
940 $this->_fragment = !empty($matches[8]) ? $matches[9] : false;
941 }
942 }