Make WordPress Core

Ticket #12935: controller.php

File controller.php, 25.7 KB (added by jacobsantos, 15 years ago)

Prototype for router.

Line 
1<?php
2/* WordPress Controller Implementation.
3 *
4 * The goals surrounding the controller implementation are the following:
5 *    * Easier additions to routes using an API.
6 *    * Demystify the black art around wp-rewrite.
7 *    * Consolidate the controller from the three main classes in WordPress and have those classes
8 *      use the controller implementation.
9 *    * Implement a system similar and compatible to the other MVC frameworks out there with a few
10 *      exceptions.
11 *    * Allow for the system in WordPress to be replaced by another Controller class library, but
12 *      not for example Code Igniter which is a MVC framework. The Controller part of any MVC
13 *      framework or class library would have to be able to be independent of the rest of the system
14 *      in order to replace this implementation.
15 *
16 * The exception for the MVC, when compared to other MVC frameworks is that the "module" feature
17 * will not be implemented. Given the dynamic plugin structure of WordPress it would be unwise to
18 * place such a restriction. The system will thus work more like MVC class libraries, in the sense
19 * that you will specify the route and WordPress will iterate through the routes in order to check
20 * which controller applies.
21 *
22 * No code has been used from the Zend Framework, nor is this a PHP4 compatible version of ZF
23 * controller. While certain concepts or patterns may have been taken from ZF, the functionality of
24 * ZF and this controller implementation are vastly different. It should be assumed that the base
25 * implementation is of the most basic for a working controller.
26 *
27 * As the controller implementation needs to be simple, quick, and efficient, certain parts of the
28 * controller pattern will be minimized or stripped out altogether. The parts of the controller that
29 * are going to be implemented are the router, routes, and dispatcher.
30 *
31 * Actions will be implemented as part of the Plugin API and thus may not conform completely to Zend
32 * Framework, Yii or other MVC class libraries. Actions as part of the Controller pattern may be
33 * implemented later, when a need arises for functionality that is different from the Plugin API.
34 *
35 * @package WordPress
36 * @subpackage Controller
37 * @since {unknown}
38 */
39
40/**
41 * Contains all of the available Routes for processing.
42 *
43 * The way the router works is that will run through all of the routes from the top to the bottom.
44 * Once a route is reached that is matched, then the processing is stopped and that route is
45 * returned. It is completely possible for two routes to be set to be matched against, and cause
46 * confusion when the "right" one is not returned. The dispatcher may only process one controller
47 * and if there are two or more, then a "bug" in the routes may be expected.
48 *
49 * The way to get around this is to place the most specific routes above the least specific routes.
50 * So say, you match against '/my-base/page/(.*)', '/my-base/page' and '/my-base/'. You will want to
51 * place them in that order. If you placed '/my-base' first, then the other two will never be
52 * matched and the dispatcher will never run the controller for those two.
53 *
54 * The routes will have different implementations to allow for multiple choices for how the routes
55 * will be checked. The basic check is against the full URL (after removing the base site URL), so
56 * for example: does '/my-base/my-page/' == URL and if so then load the controller. This does not
57 * allow for regex. It does lead to the second option, which is a regular expression route:
58 * '/my-base/.*' == URL or '/my-base/(.*)' == URL, which will allow for checking against the
59 * subclass.
60 *
61 * The additional of the Multi-Site feature in WordPress, the hostname will also be a route that
62 * can be checked. For compatibility with Zend Framework, a chaining mechanism will also be
63 * supported.
64 *
65 * @package WordPress
66 * @subpackage Controller
67 * @since {unknown}
68 */
69class WP_Router {
70
71        /**
72         * Add a route.
73         *
74         * Supports adding multiple routes to the same route name, but be aware that if that route name
75         * is removed, then the entire set of routes will be removed as well. This is to allow for
76         * name clashes and still support working routes with minimal bugs resulting, unless the route
77         * is removed.
78         *
79         * @since {unknown}
80         * @access public
81         *
82         * @param string $name Reference for route.
83         * @param WP_Routes $route Not by reference. Requires route that extends WP_Routes.
84         */
85        function add_route( $name, $route ) {
86                if ( is_a( $route, 'WP_Routes' ) ) {
87                        if ( ! isset( $this->_routes[ $name ] ) ) {
88                                $this->_routes[ $name ] = $route;
89                        } else {
90                                if ( ! is_array( $this->_routes[ $name ] ) )
91                                        $this->_routes[ $name ][] = $this->_routes[ $name ];
92
93                                $this->_routes[ $name ][] = $route;
94                        }
95                }
96        }
97
98        /**
99         * Add a list of routes.
100         *
101         * @param array $routes Name should be key with value the route.
102         * @return null Returns before processing if empty list or not an array.
103         */
104        function add_routes( $routes ) {
105                if ( ! is_array($routes) || empty($routes) )
106                        return;
107
108                foreach( $routes as $name => $route ) {
109                        $this->add_route( $name, $route );
110                }
111        }
112
113        /**
114         * Used to remove route to prevent execution.
115         *
116         * @since {unknown}
117         * @access public
118         *
119         * @param string $name Reference for route.
120         */
121        function remove_route($name) {
122                if ( isset( $this->_routes[ $name ] ) )
123                        unset( $this->_routes[ $name ] );
124        }
125}
126
127/**
128 * Class for checking route classes.
129 *
130 * @abstract
131 * @package WordPress
132 * @subpackage Controller
133 * @since {unknown}
134 */
135class WP_Routes {
136
137        /**
138         * The map is class specific, so it might be a string or an array or another type.
139         *
140         * @since {unknown}
141         * @access private
142         * @var mixed
143         */
144        var $_map = '';
145
146        /**
147         * The controller will always be a callback type to be handled by the dispatcher.
148         *
149         * @since {unknown}
150         * @access private
151         * @var callback
152         */
153        var $_controller = null;
154
155        /**
156         * PHP4 backwards compatible constructor calls PHP5 constructor.
157         *
158         * @since {unknown}
159         * @access public
160         *
161         * @param string $map Class specific.
162         * @param callback $callback Callback to run in the dispatcher.
163         */
164        function WP_Routes( $map, $callback ) {
165                $this->__construct( $map, $callback );
166        }
167
168        /**
169         * Routes consist of the mapping between the URL and a callback.
170         *
171         * The callback should also support PHP5.3 closures as well.
172         *
173         * @since {unknown}
174         * @access public
175         *
176         * @param string $map Class specific.
177         * @param callback $callback Callback to run in the dispatcher.
178         */
179        function __construct( $map, $callback ) {
180                $this->_map = $map;
181
182                if ( is_callable($callback) ) {
183                        $this->_controller = $callback;
184                }
185        }
186
187        /**
188         * Checks whether the dispatcher may use this route.
189         *
190         * In the event that the constructor was not given a valid callback for the dispatcher, then the
191         * match may not be run. There is unfortunately no way to tell the creator of the route that the
192         * route will not be processed, unless this method is called before passing on to the router. It
193         * is assumed that the callback is going to be valid and that the passing of the route will be
194         * without assigning to a variable first.
195         *
196         * This method can be used during debugging to ensure that the callback is correct when passing
197         * to the router.
198         *
199         * @since {unknown}
200         * @access public
201         *
202         * @return bool Whether the dispatcher may use this route.
203         */
204        function is_route() {
205                if ( is_callable( $this->_controller ) )
206                        return true;
207
208                return false;
209        }
210
211        /**
212         * Retrieve controller for dispatcher.
213         *
214         * The match method will return a boolean, therefore the controller has to be retrieved using a
215         * getter method.
216         *
217         * @since {unknown}
218         * @access public
219         *
220         * @return callback
221         */
222        function get_dispatcher() {
223                return $this->_controller;
224        }
225
226        /**
227         * Matches against specific functionality or processing in each class that implements method.
228         *
229         * This method must be implemented in each class, because this method will be used in the router
230         * to check whether the route matches and then run the dispatch.
231         *
232         * @abstract
233         * @access public
234         * @since {unknown}
235         *
236         * @return WP_Error
237         */
238        function match() {
239                return new WP_Error('wp-routes', sprintf( __('Class %s did not implement match() method.'), __CLASS__ ) );
240        }
241
242}
243
244/**
245 * Matches against URL path exactly.
246 *
247 * The URL path will not include the full URI, it will be minus the hostname and minus the base site
248 * URL. So if the base site URL is 'http://example.com/wordpress' and the URL is
249 * 'example.com/wordpress/my-base/controller/action', then this class will match against
250 * '/my-base/controller/action'. Therefore, the map will have to be set to
251 * '/my-base//controller/action' for match() to return true.
252 *
253 * Does not allow regular expressions! Use {@link WP_Route_Regex} for regular expressions.
254 *
255 * @package WordPress
256 * @subpackage Controller
257 * @since {unknown}
258 */
259class WP_Route_Static extends WP_Routes {
260
261        /**
262         * Whether the match is for a specific string.
263         *
264         * @access public
265         * @since {unknown}
266         *
267         * @return bool Whether route is successful.
268         */
269        function match() {
270                // The URL map must have the slash at the beginning or it won't ever match. Add it and
271                // remove the ending slash because the processed URL will not have it either.
272                $url_static = '/'. trim($this->_map, '/');
273
274                return ( $url_static == _get_uri_request() );
275        }
276
277}
278
279class WP_Route_Regex extends WP_Routes {
280
281        /**
282         * Matches against specific functionality or processing in each class that implements method.
283         *
284         * This method must be implemented in each class, because this method will be used in the router
285         * to check whether the route matches and then run the dispatch.
286         *
287         * @access public
288         * @since {unknown}
289         *
290         * @return bool Whether route is successful.
291         */
292        function match() {
293                /** @todo $matches must be passed to the controller through the dispatcher. */
294                if ( preg_match( '|'. $this->_map .'|', _get_uri_request(), $matches ) ) {
295                        return true;
296                }
297        }
298
299}
300
301/**
302 * Splits the URL into 'segments' to be matched with the remainder passed on.
303 *
304 * There will be a URL segment that will split the URL and allow for checking against different
305 * paths. For example,* for a URL path that could be '/my-base/controller/action/option1',
306  '/my-base/controller/' or '/my-base/controller/action/option1/option2' should all go to the
307 * controller set for '/my-base/controller/' with the action and options as arguments. The segment
308 * route would allow for that functionality.
309 *
310 * The specifications for this class is found in a ticket which will need to be referenced for how
311 * this class should function.
312 *
313 */
314class WP_Route_Segment extends WP_Routes {
315
316        /**
317         * Matches against specific functionality or processing in each class that implements method.
318         *
319         * This method must be implemented in each class, because this method will be used in the router
320         * to check whether the route matches and then run the dispatch.
321         *
322         * @access public
323         * @since {unknown}
324         *
325         * @return bool Whether route is successful.
326         */
327        function match() {
328                return new WP_Error('wp-routes', sprintf( __('Class %s did not implement match() method.'), __CLASS__ ) );
329        }
330
331}
332
333/**
334 * Checks against an option in the table.
335 *
336 * This class can be extended for customize processing of options that require it. This base class
337 * may only be used on options that are scalar (any value from the table will be a string). It will
338 * fail against options are arrays or serialized.
339 *
340 * @package WordPress
341 * @subpackage Controller
342 * @extends WP_Routes
343 * @since {unknown}
344 */
345class WP_Route_Option extends WP_Routes {
346
347        /**
348         * Checks against option in the table.
349         *
350         * The way to use this route is by passing the map parameter an array that includes the option
351         * name as the key and the value as the value to check against the option. Only works with
352         * scalar options.
353         *
354         * <code>
355         * $route = new WP_Route_Option( array( 'home_url' => 'http://example.com/' ), 'my_callback' );
356         * </code>
357         *
358         * @access public
359         * @since {unknown}
360         *
361         * @param array $map Should have the option name as key 'option_name' and what the value should be as the value.
362         * @param callback $callback Controller callback.
363         */
364        function __construct( $map, $callback ) {
365                parent::__construct( $map, $callback );
366        }
367
368        /**
369         * Matches against specific functionality or processing in each class that implements method.
370         *
371         * This method must be implemented in each class, because this method will be used in the router
372         * to check whether the route matches and then run the dispatch.
373         *
374         * @access public
375         * @since {unknown}
376         *
377         * @return bool Whether route is successful.
378         */
379        function match() {
380                global $blog_id;
381
382                // Will only fetch the first key, so any other pairs will be ignored. Also, the key to
383                // check must be the first value.
384                if ( empty( $blog_id ) || ! is_multisite() ) {
385                        $option = get_option( key($this->_map) );
386                } else {
387                        $option = get_blog_option( $blog_id, key($this->_map) );
388                }
389
390                if ( ! $option )
391                        return false;
392
393                if ( current( $this->_map ) == $option )
394                        return true;
395
396                return false;
397        }
398
399}
400
401/**
402 * Processing against the rewrite_rules option in the database.
403 *
404 * Given that WordPress works differently than all other MVC libraries and frameworks, custom code
405 * will be required to allow for what WordPress likes to place in the 'rewrite_rules' option in the
406 * DB. This special code will be available for those wishing to assign additional dynamic rewrites.
407 *
408 * For an example, if the permalink structure is /page-name/, then WordPress will place each page
409 * name in the 'rewrite_rules' option. Therefore, WordPress will then be required to check for the
410 * slug for each page. That does not include the 7 or 8 extra rules for page numbers, etc that will
411 * also have to be checked against. This leads to highly inefficient check against pages. However,
412 * it is quite useful for those who wish to keep the structure of their former web site.
413 *
414 * The class is special for WordPress, in that it may only be used for WordPress. In the event that
415 * another class library is used, this will have to be implemented to allow for backwards
416 * compatibility, with how WordPress works.
417 *
418 * For the most part, this class will take over parts of the {@link WP::parse_request()) method. At
419 * least the parts where routes are processed. Parts of the process will also be found in other
420 * classes.
421 *
422 * @package WordPress
423 * @subpackage Controller
424 * @extends WP_Route_Option
425 * @since {unknown}
426 */
427class WP_Route_Option_Rewrite_Rules extends WP_Route_Option {
428
429        /**
430         * Matches against specific functionality or processing in each class that implements method.
431         *
432         * This method must be implemented in each class, because this method will be used in the router
433         * to check whether the route matches and then run the dispatch.
434         *
435         * @access public
436         * @since {unknown}
437         *
438         * @return bool Whether route is successful.
439         */
440        function match() {
441                $rewrite = get_option('rewrite_rules');
442
443                if ( empty( $rewrite ) )
444                        return false;
445
446                // Must keep track of the processed query vars for the rest of WordPress.
447                global $_wp_perma_query_vars, $_wp_matched_rule, $_wp_matched_query;
448
449                $request_match = $req_uri = _get_uri_request();
450
451                // Don't try to match against AtomPub calls
452                if ( $req_uri == 'wp-app.php' )
453                        return false;
454
455                foreach ( (array) $rewrite as $match => $query) {
456                        if ( preg_match("#^$match#", $request_match, $matches) ||
457                                preg_match("#^$match#", urldecode($request_match), $matches) ) {
458                                // Got a match.
459                                $_wp_matched_rule = $match;
460
461                                // Trim the query of everything up to the '?'.
462                                $query = preg_replace("!^.+\?!", '', $query);
463
464                                // Substitute the substring matches into the query.
465                                $query = addslashes(WP_MatchesMapRegex::apply($query, $matches));
466
467                                $_wp_matched_query = $query;
468
469                                // Parse the query.
470                                parse_str($query, $perma_query_vars);
471
472                                // If we're processing a 404 request, clear the error var
473                                // since we found something.
474                                if ( isset($_GET['error']) )
475                                        unset($_GET['error']);
476
477                                break;
478                        }
479                }
480
481                $home_path = get_home_url_path();
482
483                // If req_uri is empty or if it is a request for ourself, unset error.
484                if ( empty($req_uri) || $req_uri == $self || strpos($_SERVER['PHP_SELF'], 'wp-admin/') !== false ) {
485                        if ( isset($_GET['error']) )
486                                unset($_GET['error']);
487
488                        if ( isset($perma_query_vars) && strpos($_SERVER['PHP_SELF'], 'wp-admin/') !== false )
489                                unset($perma_query_vars);
490                }
491        }
492
493}
494
495class WP_Route_Hostname extends WP_Routes {
496
497        /**
498         * Matches against multi-site blog hostname or blog ID.
499         *
500         * This should not be restricted based on supported host names in multi-site. This allows for
501         * a plugin to be implemented that supports other host names to create a simple site using
502         * WordPress.
503         *
504         * Checks against HTTP_HOST, SERVER_NAME and REQUEST_URI to ensure accuracy.
505         *
506         * @access public
507         * @since {unknown}
508         *
509         * @return bool Whether route is successful.
510         */
511        function match() {
512                $uri = parse_url($_SERVER['REQUEST_URI']);
513               
514                return ( $this->_map == $_SERVER['HTTP_HOST'] || $this->_map == $_SERVER['SERVER_NAME'] || $this->_map == $uri['host'] );
515        }
516
517}
518
519/**
520 * Chaining for rules and processing.
521 *
522 * I'm unsure how the chaining works in Zend Framework, so there are going to be incompatibilities
523 * between the two, I'm sure. I don't believe that it matters much, someone using Zend Framework,
524 * will know the difference by reading the below notes.
525 *
526 * The reason it does not matter how Zend Framework works in this respect is two folds. one is
527 * copyright, which isn't much of an issue since ZF supports GPLv2 (unless they have increased that)
528 * or at least New BSD, which is compatible with GPLv2, but also the fact that if code was copied
529 * from the Zend Framework to maintain compatibility, then the copyright from Zend Framework would
530 * have to be referenced as well. Rest assured that no code from Zend Framework has been used.
531 *
532 * The second is while specifications from the web site on how the Zend Framework Chain Route is
533 * available, it isn't avaailble at the time of this implementation. The final, bonus reason, is
534 * that if the Zend Framework doesn't work like this, then in my opinion it should.
535 *
536 * How the chaining works in this class is to process each of the routes and if all of the routes
537 * are true, then the chain will return true. If any of the routes returns false, then the complete
538 * route will return false.
539 *
540 * The reason this is useful, is for multi-site, where on one hostname, a special "module" is
541 * implemented for a specific hostname. See example below:
542 *
543 * <code>
544 * $chain = new WP_Route_Chain;
545 * $chain->route( new WP_Route_Hostname('special.example.com') );
546 * $chain->route( new WP_Route_Regex('/gallery/picture/(.*)') );
547 * </code>
548 *
549 * In the above example, if the hostname is not 'special.example.com', then there is no reason to
550 * check any further. This in some cases can be used to speed up routes where it is dependent on
551 * another condition being true.
552 *
553 * Since the chain can be any route that extends the {@link WP_Routes} class, a custom route can be
554 * written that checks different options in the database in order to dynamically customize the chain
555 * based on options for the plugin or option or anything.
556 *
557 * @package WordPress
558 * @subpackage Controller
559 * @extends WP_Routes
560 * @since {unknown}
561 */
562class WP_Route_Chain extends WP_Routes {
563
564        /**
565         * PHP4 backwards compatible constructor calls PHP5 constructor.
566         *
567         * @since {unknown}
568         * @access public
569         * @see WP_Route_Chain::__construct()
570         *
571         * @param array $map Array of routes for processing.
572         * @param callback $callback Callback to run in the dispatcher.
573         */
574        function WP_Route_Chain( $map, $callback ) {
575                $this->__construct( $map, $callback );
576        }
577
578        /**
579         * Supports chaining routes for more advanced checks for whether controller should run.
580         *
581         * The callback also supports PHP5.3 closures as well.
582         *
583         * @since {unknown}
584         * @access public
585         *
586         * @param array $map Array of routes for processing.
587         * @param callback $callback Callback to run in the dispatcher.
588         */
589        function __construct( $map, $callback ) {
590                if ( is_array( $map ) ) {
591                        parent::__construct( $map, $callback );
592                } else {
593                        $this->_map = array();
594                }
595        }
596
597        /**
598         *
599         *
600         * @param WP_Routes $route Not by reference. Requires route that extends WP_Routes.
601         * @return WP_Route_Chain
602         */
603        function add_route($route) {
604                if ( is_a( $route, 'WP_Routes' ) ) {
605                        $this->_map[] = $route;
606                }
607                return $this;
608        }
609
610        /**
611         * Matches against specific functionality or processing in each class that implements method.
612         *
613         * This method must be implemented in each class, because this method will be used in the router
614         * to check whether the route matches and then run the dispatch.
615         *
616         * @access public
617         * @since {unknown}
618         *
619         * @return bool Whether route is successful.
620         */
621        function match() {
622                if ( ! is_array( $this->_map ) )
623                        return false;
624
625                foreach ( $this->_map as $route ) {
626                        if ( ! $route->match() )
627                                return false;
628                }
629
630                // Assume that if any of the routes did not fail, that everything passed.
631                return true;
632        }
633
634}
635
636class WP_Uri_Paths {
637
638        function WP_Controller_Uri() {
639                $this->__construct();
640        }
641
642        function __construct() {
643                $this->process();
644        }
645
646        /**
647         *
648         * @global WP_Rewrite $wp_rewrite
649         * @staticvar string $_uri_request Stores processed URI from either $_req_uri or $_pathinfo.
650         * @staticvar string $_req_uri Stores the processed URI from REQUEST_URI server variable.
651         * @staticvar string $_pathinfo Stores the processed URI from PATH_INFO server variable.
652         * @staticvar string $_self Stores the processed URI from PHP_SELF server variable.
653         * @param string $type (Optional) What path to return: either request, pathinfo, php_self, or processed_uri.
654         * @return string
655         */
656        function process($type = null) {
657                global $wp_rewrite;
658                static $_uri_request, $_req_uri, $_pathinfo, $_self;
659
660                if ( ! isset( $_uri_request ) ) {
661                        $pathinfo = '';
662                        if ( isset($_SERVER['PATH_INFO']) )
663                                $pathinfo = $_SERVER['PATH_INFO'];
664
665                        $pathinfo_array = explode('?', $pathinfo);
666                        $pathinfo = str_replace("%", "%25", $pathinfo_array[0]);
667                        $req_uri = $_SERVER['REQUEST_URI'];
668                        $req_uri_array = explode('?', $req_uri);
669                        $req_uri = $req_uri_array[0];
670
671                        // The home path is needed to strip out the base site URL and keep just the part of WordPress
672                        // for checking against.
673                        $home_path = parse_url( get_home_url() );
674
675                        if ( isset($home_path['path']) ) {
676                                $home_path = trim( $home_path['path'], '/');
677                        } else
678                                $home_path = '';
679
680                        // Trim path info from the end and the leading home path from the
681                        // front.  For path info requests, this leaves us with the requesting
682                        // filename, if any.  For 404 requests, this leaves us with the
683                        // requested permalink.
684                        $req_uri = str_replace($pathinfo, '', rawurldecode($req_uri));
685                        $req_uri = trim($req_uri, '/');
686                        $req_uri = preg_replace("|^$home_path|", '', $req_uri);
687                        $req_uri = rtrim($req_uri, '/');
688                        $_req_uri = $req_uri;
689                        $pathinfo = trim($pathinfo, '/');
690                        $pathinfo = preg_replace("|^$home_path|", '', $pathinfo);
691                        $pathinfo = rtrim($pathinfo, '/');
692                        $_pathinfo = $pathinfo;
693
694                        $self = $_SERVER['PHP_SELF'];
695                        $self = trim($self, '/');
696                        $self = preg_replace("|^$home_path|", '', $self);
697                        $self = trim($self, '/');
698                        $_self = $self;
699
700                        // The requested permalink is in $pathinfo for path info requests and
701                        //  $req_uri for other requests.
702                        if ( ! empty($pathinfo) && !preg_match('|^.*' . $wp_rewrite->index . '$|', $pathinfo) ) {
703                                $_uri_request = $pathinfo;
704                        } else {
705                                // If the request uri is the index, blank it out so that we don't try to match it against a rule.
706                                if ( $req_uri == $wp_rewrite->index )
707                                        $req_uri = '';
708                                $_uri_request = $req_uri;
709                        }
710                }
711
712                switch ( $type ) {
713                        case 'req_uri':
714                        case 'request':
715                                return $_req_uri;
716                        case 'pathinfo':
717                                return $_pathinfo;
718                        case 'php_self':
719                                return $_self;
720                        case 'processed_uri':
721                        default:
722                                return $_uri_request;
723                }
724        }
725
726        function request_uri() {
727                return self::process('request');
728        }
729
730        function pathinfo() {
731                return self::process('pathinfo');
732        }
733
734        function processed_uri() {
735                return self::process('processed_uri');
736        }
737
738        function php_self() {
739                return self::process('php_self');
740        }
741}
742
743/**
744 * Processes and stores the request URL for the routes matching.
745 *
746 * The code was taken from {@link WP::parse_request()} and thus should require minimal testing.
747 *
748 * @todo Remove dependency of WP_Rewrite class and global.
749 * @since {unknown}
750 * @global WP_Rewrite $wp_rewrite WP_Rewrite object container.
751 * @staticvar string $_uri_request Stores the value for the processed request URL.
752 *
753 * @return string The processed URL request from either pathinfo or request_uri.
754 */
755function _get_uri_request() {
756        $uri = new WP_Uri_Paths;
757        return $uri->processed_uri();
758}
759
760/**
761 * Retrieve the home url for a given site.
762 *
763 * Returns the 'home' option with the appropriate protocol,  'https' if is_ssl() and 'http'
764 * otherwise. If $scheme is 'http' or 'https', is_ssl() is overridden.
765 *
766 * @package WordPress
767 * @since 3.0.0
768 *
769 * @param  int $blog_id   (optional) Blog ID. Defaults to current blog.
770 * @param  string $path   (optional) Path relative to the home url.
771 * @param  string $scheme (optional) Scheme to give the home url context. Currently 'http','https'
772 * @return string Home url link with optional path appended.
773 */
774function get_home_url( $blog_id = null, $path = '', $scheme = null ) {
775        $orig_scheme = $scheme;
776
777        if ( !in_array( $scheme, array( 'http', 'https' ) ) )
778                $scheme = is_ssl() && !is_admin() ? 'https' : 'http';
779
780        if ( empty( $blog_id ) || !is_multisite() )
781                $home = get_option( 'home' );
782        else
783                $home = get_blog_option( $blog_id, 'home' );
784
785        $url = str_replace( 'http://', "$scheme://", $home );
786
787        if ( !empty( $path ) && is_string( $path ) && strpos( $path, '..' ) === false )
788                $url .= '/' . ltrim( $path, '/' );
789
790        return apply_filters( 'home_url', $url, $path, $orig_scheme, $blog_id );
791}
792
793
794/*
795 * Tests for the Controller implementation.
796 */
797
798function __test_controller() {
799        var_dump( _get_uri_request() );
800}
801add_action('init', '__test_controller');
802
803// PHP close tag has been intentionally left out.