]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/rewrite.php
Wordpress 3.3
[autoinstalls/wordpress.git] / wp-includes / rewrite.php
1 <?php
2 /**
3  * WordPress Rewrite API
4  *
5  * @package WordPress
6  * @subpackage Rewrite
7  */
8
9 /**
10  * Add a straight rewrite rule.
11  *
12  * @see WP_Rewrite::add_rule() for long description.
13  * @since 2.1.0
14  *
15  * @param string $regex Regular Expression to match request against.
16  * @param string $redirect Page to redirect to.
17  * @param string $after Optional, default is 'bottom'. Where to add rule, can also be 'top'.
18  */
19 function add_rewrite_rule($regex, $redirect, $after = 'bottom') {
20         global $wp_rewrite;
21         $wp_rewrite->add_rule($regex, $redirect, $after);
22 }
23
24 /**
25  * Add a new tag (like %postname%).
26  *
27  * Warning: you must call this on init or earlier, otherwise the query var
28  * addition stuff won't work.
29  *
30  * @since 2.1.0
31  *
32  * @param string $tagname
33  * @param string $regex
34  */
35 function add_rewrite_tag($tagname, $regex) {
36         //validation
37         if ( strlen($tagname) < 3 || $tagname[0] != '%' || $tagname[strlen($tagname)-1] != '%' )
38                 return;
39
40         $qv = trim($tagname, '%');
41
42         global $wp_rewrite, $wp;
43         $wp->add_query_var($qv);
44         $wp_rewrite->add_rewrite_tag($tagname, $regex, $qv . '=');
45 }
46
47 /**
48  * Add permalink structure.
49  *
50  * @see WP_Rewrite::add_permastruct()
51  * @since 3.0.0
52  *
53  * @param string $name Name for permalink structure.
54  * @param string $struct Permalink structure.
55  * @param bool $with_front Prepend front base to permalink structure.
56  */
57 function add_permastruct( $name, $struct, $with_front = true, $ep_mask = EP_NONE ) {
58         global $wp_rewrite;
59         return $wp_rewrite->add_permastruct( $name, $struct, $with_front, $ep_mask );
60 }
61
62 /**
63  * Add a new feed type like /atom1/.
64  *
65  * @since 2.1.0
66  *
67  * @param string $feedname
68  * @param callback $function Callback to run on feed display.
69  * @return string Feed action name.
70  */
71 function add_feed($feedname, $function) {
72         global $wp_rewrite;
73         if ( ! in_array($feedname, $wp_rewrite->feeds) ) //override the file if it is
74                 $wp_rewrite->feeds[] = $feedname;
75         $hook = 'do_feed_' . $feedname;
76         // Remove default function hook
77         remove_action($hook, $hook, 10, 1);
78         add_action($hook, $function, 10, 1);
79         return $hook;
80 }
81
82 /**
83  * Remove rewrite rules and then recreate rewrite rules.
84  *
85  * @see WP_Rewrite::flush_rules()
86  * @since 3.0.0
87  *
88  * @param bool $hard Whether to update .htaccess (hard flush) or just update
89  *      rewrite_rules transient (soft flush). Default is true (hard).
90  */
91 function flush_rewrite_rules( $hard = true ) {
92         global $wp_rewrite;
93         $wp_rewrite->flush_rules( $hard );
94 }
95
96 //pseudo-places
97 /**
98  * Endpoint Mask for default, which is nothing.
99  *
100  * @since 2.1.0
101  */
102 define('EP_NONE', 0);
103
104 /**
105  * Endpoint Mask for Permalink.
106  *
107  * @since 2.1.0
108  */
109 define('EP_PERMALINK', 1);
110
111 /**
112  * Endpoint Mask for Attachment.
113  *
114  * @since 2.1.0
115  */
116 define('EP_ATTACHMENT', 2);
117
118 /**
119  * Endpoint Mask for date.
120  *
121  * @since 2.1.0
122  */
123 define('EP_DATE', 4);
124
125 /**
126  * Endpoint Mask for year
127  *
128  * @since 2.1.0
129  */
130 define('EP_YEAR', 8);
131
132 /**
133  * Endpoint Mask for month.
134  *
135  * @since 2.1.0
136  */
137 define('EP_MONTH', 16);
138
139 /**
140  * Endpoint Mask for day.
141  *
142  * @since 2.1.0
143  */
144 define('EP_DAY', 32);
145
146 /**
147  * Endpoint Mask for root.
148  *
149  * @since 2.1.0
150  */
151 define('EP_ROOT', 64);
152
153 /**
154  * Endpoint Mask for comments.
155  *
156  * @since 2.1.0
157  */
158 define('EP_COMMENTS', 128);
159
160 /**
161  * Endpoint Mask for searches.
162  *
163  * @since 2.1.0
164  */
165 define('EP_SEARCH', 256);
166
167 /**
168  * Endpoint Mask for categories.
169  *
170  * @since 2.1.0
171  */
172 define('EP_CATEGORIES', 512);
173
174 /**
175  * Endpoint Mask for tags.
176  *
177  * @since 2.3.0
178  */
179 define('EP_TAGS', 1024);
180
181 /**
182  * Endpoint Mask for authors.
183  *
184  * @since 2.1.0
185  */
186 define('EP_AUTHORS', 2048);
187
188 /**
189  * Endpoint Mask for pages.
190  *
191  * @since 2.1.0
192  */
193 define('EP_PAGES', 4096);
194
195 /**
196  * Endpoint Mask for everything.
197  *
198  * @since 2.1.0
199  */
200 define('EP_ALL', 8191);
201
202 /**
203  * Add an endpoint, like /trackback/.
204  *
205  * The endpoints are added to the end of the request. So a request matching
206  * "/2008/10/14/my_post/myep/", the endpoint will be "/myep/".
207  *
208  * Be sure to flush the rewrite rules (wp_rewrite->flush_rules()) when your plugin gets
209  * activated (register_activation_hook()) and deactivated (register_deactivation_hook())
210  *
211  * @since 2.1.0
212  * @see WP_Rewrite::add_endpoint() Parameters and more description.
213  * @uses $wp_rewrite
214  *
215  * @param unknown_type $name
216  * @param unknown_type $places
217  */
218 function add_rewrite_endpoint($name, $places) {
219         global $wp_rewrite;
220         $wp_rewrite->add_endpoint($name, $places);
221 }
222
223 /**
224  * Filter the URL base for taxonomies.
225  *
226  * To remove any manually prepended /index.php/.
227  *
228  * @access private
229  * @since 2.6.0
230  *
231  * @param string $base The taxonomy base that we're going to filter
232  * @return string
233  */
234 function _wp_filter_taxonomy_base( $base ) {
235         if ( !empty( $base ) ) {
236                 $base = preg_replace( '|^/index\.php/|', '', $base );
237                 $base = trim( $base, '/' );
238         }
239         return $base;
240 }
241
242 /**
243  * Examine a url and try to determine the post ID it represents.
244  *
245  * Checks are supposedly from the hosted site blog.
246  *
247  * @since 1.0.0
248  *
249  * @param string $url Permalink to check.
250  * @return int Post ID, or 0 on failure.
251  */
252 function url_to_postid($url) {
253         global $wp_rewrite;
254
255         $url = apply_filters('url_to_postid', $url);
256
257         // First, check to see if there is a 'p=N' or 'page_id=N' to match against
258         if ( preg_match('#[?&](p|page_id|attachment_id)=(\d+)#', $url, $values) )       {
259                 $id = absint($values[2]);
260                 if ( $id )
261                         return $id;
262         }
263
264         // Check to see if we are using rewrite rules
265         $rewrite = $wp_rewrite->wp_rewrite_rules();
266
267         // Not using rewrite rules, and 'p=N' and 'page_id=N' methods failed, so we're out of options
268         if ( empty($rewrite) )
269                 return 0;
270
271         // Get rid of the #anchor
272         $url_split = explode('#', $url);
273         $url = $url_split[0];
274
275         // Get rid of URL ?query=string
276         $url_split = explode('?', $url);
277         $url = $url_split[0];
278
279         // Add 'www.' if it is absent and should be there
280         if ( false !== strpos(home_url(), '://www.') && false === strpos($url, '://www.') )
281                 $url = str_replace('://', '://www.', $url);
282
283         // Strip 'www.' if it is present and shouldn't be
284         if ( false === strpos(home_url(), '://www.') )
285                 $url = str_replace('://www.', '://', $url);
286
287         // Strip 'index.php/' if we're not using path info permalinks
288         if ( !$wp_rewrite->using_index_permalinks() )
289                 $url = str_replace('index.php/', '', $url);
290
291         if ( false !== strpos($url, home_url()) ) {
292                 // Chop off http://domain.com
293                 $url = str_replace(home_url(), '', $url);
294         } else {
295                 // Chop off /path/to/blog
296                 $home_path = parse_url(home_url());
297                 $home_path = isset( $home_path['path'] ) ? $home_path['path'] : '' ;
298                 $url = str_replace($home_path, '', $url);
299         }
300
301         // Trim leading and lagging slashes
302         $url = trim($url, '/');
303
304         $request = $url;
305
306         // Look for matches.
307         $request_match = $request;
308         foreach ( (array)$rewrite as $match => $query) {
309
310                 // If the requesting file is the anchor of the match, prepend it
311                 // to the path info.
312                 if ( !empty($url) && ($url != $request) && (strpos($match, $url) === 0) )
313                         $request_match = $url . '/' . $request;
314
315                 if ( preg_match("!^$match!", $request_match, $matches) ) {
316
317                         if ( $wp_rewrite->use_verbose_page_rules && preg_match( '/pagename=\$matches\[([0-9]+)\]/', $query, $varmatch ) ) {
318                                 // this is a verbose page match, lets check to be sure about it
319                                 if ( ! get_page_by_path( $matches[ $varmatch[1] ] ) )
320                                         continue;
321                         }
322
323                         // Got a match.
324                         // Trim the query of everything up to the '?'.
325                         $query = preg_replace("!^.+\?!", '', $query);
326
327                         // Substitute the substring matches into the query.
328                         $query = addslashes(WP_MatchesMapRegex::apply($query, $matches));
329
330                         // Filter out non-public query vars
331                         global $wp;
332                         parse_str($query, $query_vars);
333                         $query = array();
334                         foreach ( (array) $query_vars as $key => $value ) {
335                                 if ( in_array($key, $wp->public_query_vars) )
336                                         $query[$key] = $value;
337                         }
338
339                         // Do the query
340                         $query = new WP_Query($query);
341                         if ( !empty($query->posts) && $query->is_singular )
342                                 return $query->post->ID;
343                         else
344                                 return 0;
345                 }
346         }
347         return 0;
348 }
349
350 /**
351  * WordPress Rewrite Component.
352  *
353  * The WordPress Rewrite class writes the rewrite module rules to the .htaccess
354  * file. It also handles parsing the request to get the correct setup for the
355  * WordPress Query class.
356  *
357  * The Rewrite along with WP class function as a front controller for WordPress.
358  * You can add rules to trigger your page view and processing using this
359  * component. The full functionality of a front controller does not exist,
360  * meaning you can't define how the template files load based on the rewrite
361  * rules.
362  *
363  * @since 1.5.0
364  */
365 class WP_Rewrite {
366         /**
367          * Default permalink structure for WordPress.
368          *
369          * @since 1.5.0
370          * @access private
371          * @var string
372          */
373         var $permalink_structure;
374
375         /**
376          * Whether to add trailing slashes.
377          *
378          * @since 2.2.0
379          * @access private
380          * @var bool
381          */
382         var $use_trailing_slashes;
383
384         /**
385          * Permalink author request base ( example.com/author/authorname ).
386          *
387          * @since 1.5.0
388          * @access private
389          * @var string
390          */
391         var $author_base = 'author';
392
393         /**
394          * Permalink request structure for author pages.
395          *
396          * @since 1.5.0
397          * @access private
398          * @var string
399          */
400         var $author_structure;
401
402         /**
403          * Permalink request structure for dates.
404          *
405          * @since 1.5.0
406          * @access private
407          * @var string
408          */
409         var $date_structure;
410
411         /**
412          * Permalink request structure for pages.
413          *
414          * @since 1.5.0
415          * @access private
416          * @var string
417          */
418         var $page_structure;
419
420         /**
421          * Search permalink base ( example.com/search/query ).
422          *
423          * @since 1.5.0
424          * @access private
425          * @var string
426          */
427         var $search_base = 'search';
428
429         /**
430          * Permalink request structure for searches.
431          *
432          * @since 1.5.0
433          * @access private
434          * @var string
435          */
436         var $search_structure;
437
438         /**
439          * Comments permalink base.
440          *
441          * @since 1.5.0
442          * @access private
443          * @var string
444          */
445         var $comments_base = 'comments';
446
447         /**
448          * Pagination permalink base.
449          *
450          * @since 3.1.0
451          * @access private
452          * @var string
453          */
454         var $pagination_base = 'page';
455
456         /**
457          * Feed permalink base.
458          *
459          * @since 1.5.0
460          * @access private
461          * @var string
462          */
463         var $feed_base = 'feed';
464
465         /**
466          * Comments feed request structure permalink.
467          *
468          * @since 1.5.0
469          * @access private
470          * @var string
471          */
472         var $comments_feed_structure;
473
474         /**
475          * Feed request structure permalink.
476          *
477          * @since 1.5.0
478          * @access private
479          * @var string
480          */
481         var $feed_structure;
482
483         /**
484          * Front URL path.
485          *
486          * The difference between the root property is that WordPress might be
487          * located at example/WordPress/index.php, if permalinks are turned off. The
488          * WordPress/index.php will be the front portion. If permalinks are turned
489          * on, this will most likely be empty or not set.
490          *
491          * @since 1.5.0
492          * @access private
493          * @var string
494          */
495         var $front;
496
497         /**
498          * Root URL path to WordPress (without domain).
499          *
500          * The difference between front property is that WordPress might be located
501          * at example.com/WordPress/. The root is the 'WordPress/' portion.
502          *
503          * @since 1.5.0
504          * @access private
505          * @var string
506          */
507         var $root = '';
508
509         /**
510          * Permalink to the home page.
511          *
512          * @since 1.5.0
513          * @access public
514          * @var string
515          */
516         var $index = 'index.php';
517
518         /**
519          * Request match string.
520          *
521          * @since 1.5.0
522          * @access private
523          * @var string
524          */
525         var $matches = '';
526
527         /**
528          * Rewrite rules to match against the request to find the redirect or query.
529          *
530          * @since 1.5.0
531          * @access private
532          * @var array
533          */
534         var $rules;
535
536         /**
537          * Additional rules added external to the rewrite class.
538          *
539          * Those not generated by the class, see add_rewrite_rule().
540          *
541          * @since 2.1.0
542          * @access private
543          * @var array
544          */
545         var $extra_rules = array(); //
546
547         /**
548          * Additional rules that belong at the beginning to match first.
549          *
550          * Those not generated by the class, see add_rewrite_rule().
551          *
552          * @since 2.3.0
553          * @access private
554          * @var array
555          */
556         var $extra_rules_top = array(); //
557
558         /**
559          * Rules that don't redirect to WP's index.php.
560          *
561          * These rules are written to the mod_rewrite portion of the .htaccess.
562          *
563          * @since 2.1.0
564          * @access private
565          * @var array
566          */
567         var $non_wp_rules = array(); //
568
569         /**
570          * Extra permalink structures.
571          *
572          * @since 2.1.0
573          * @access private
574          * @var array
575          */
576         var $extra_permastructs = array();
577
578         /**
579          * Endpoints permalinks
580          *
581          * @since 2.1.0
582          * @access private
583          * @var array
584          */
585         var $endpoints;
586
587         /**
588          * Whether to write every mod_rewrite rule for WordPress.
589          *
590          * This is off by default, turning it on might print a lot of rewrite rules
591          * to the .htaccess file.
592          *
593          * @since 2.0.0
594          * @access public
595          * @var bool
596          */
597         var $use_verbose_rules = false;
598
599         /**
600          * Whether to write every mod_rewrite rule for WordPress pages.
601          *
602          * @since 2.5.0
603          * @access public
604          * @var bool
605          */
606         var $use_verbose_page_rules = true;
607
608         /**
609          * Permalink structure search for preg_replace.
610          *
611          * @since 1.5.0
612          * @access private
613          * @var array
614          */
615         var $rewritecode =
616                 array(
617                                         '%year%',
618                                         '%monthnum%',
619                                         '%day%',
620                                         '%hour%',
621                                         '%minute%',
622                                         '%second%',
623                                         '%postname%',
624                                         '%post_id%',
625                                         '%author%',
626                                         '%pagename%',
627                                         '%search%'
628                                         );
629
630         /**
631          * Preg_replace values for the search, see {@link WP_Rewrite::$rewritecode}.
632          *
633          * @since 1.5.0
634          * @access private
635          * @var array
636          */
637         var $rewritereplace =
638                 array(
639                                         '([0-9]{4})',
640                                         '([0-9]{1,2})',
641                                         '([0-9]{1,2})',
642                                         '([0-9]{1,2})',
643                                         '([0-9]{1,2})',
644                                         '([0-9]{1,2})',
645                                         '([^/]+)',
646                                         '([0-9]+)',
647                                         '([^/]+)',
648                                         '([^/]+?)',
649                                         '(.+)'
650                                         );
651
652         /**
653          * Search for the query to look for replacing.
654          *
655          * @since 1.5.0
656          * @access private
657          * @var array
658          */
659         var $queryreplace =
660                 array (
661                                         'year=',
662                                         'monthnum=',
663                                         'day=',
664                                         'hour=',
665                                         'minute=',
666                                         'second=',
667                                         'name=',
668                                         'p=',
669                                         'author_name=',
670                                         'pagename=',
671                                         's='
672                                         );
673
674         /**
675          * Supported default feeds.
676          *
677          * @since 1.5.0
678          * @access private
679          * @var array
680          */
681         var $feeds = array ( 'feed', 'rdf', 'rss', 'rss2', 'atom' );
682
683         /**
684          * Whether permalinks are being used.
685          *
686          * This can be either rewrite module or permalink in the HTTP query string.
687          *
688          * @since 1.5.0
689          * @access public
690          *
691          * @return bool True, if permalinks are enabled.
692          */
693         function using_permalinks() {
694                 return ! empty($this->permalink_structure);
695         }
696
697         /**
698          * Whether permalinks are being used and rewrite module is not enabled.
699          *
700          * Means that permalink links are enabled and index.php is in the URL.
701          *
702          * @since 1.5.0
703          * @access public
704          *
705          * @return bool
706          */
707         function using_index_permalinks() {
708                 if ( empty($this->permalink_structure) )
709                         return false;
710
711                 // If the index is not in the permalink, we're using mod_rewrite.
712                 if ( preg_match('#^/*' . $this->index . '#', $this->permalink_structure) )
713                         return true;
714
715                 return false;
716         }
717
718         /**
719          * Whether permalinks are being used and rewrite module is enabled.
720          *
721          * Using permalinks and index.php is not in the URL.
722          *
723          * @since 1.5.0
724          * @access public
725          *
726          * @return bool
727          */
728         function using_mod_rewrite_permalinks() {
729                 if ( $this->using_permalinks() && ! $this->using_index_permalinks() )
730                         return true;
731                 else
732                         return false;
733         }
734
735         /**
736          * Index for matches for usage in preg_*() functions.
737          *
738          * The format of the string is, with empty matches property value, '$NUM'.
739          * The 'NUM' will be replaced with the value in the $number parameter. With
740          * the matches property not empty, the value of the returned string will
741          * contain that value of the matches property. The format then will be
742          * '$MATCHES[NUM]', with MATCHES as the value in the property and NUM the
743          * value of the $number parameter.
744          *
745          * @since 1.5.0
746          * @access public
747          *
748          * @param int $number Index number.
749          * @return string
750          */
751         function preg_index($number) {
752                 $match_prefix = '$';
753                 $match_suffix = '';
754
755                 if ( ! empty($this->matches) ) {
756                         $match_prefix = '$' . $this->matches . '[';
757                         $match_suffix = ']';
758                 }
759
760                 return "$match_prefix$number$match_suffix";
761         }
762
763         /**
764          * Retrieve all page and attachments for pages URIs.
765          *
766          * The attachments are for those that have pages as parents and will be
767          * retrieved.
768          *
769          * @since 2.5.0
770          * @access public
771          *
772          * @return array Array of page URIs as first element and attachment URIs as second element.
773          */
774         function page_uri_index() {
775                 global $wpdb;
776
777                 //get pages in order of hierarchy, i.e. children after parents
778                 $posts = get_page_hierarchy( $wpdb->get_results("SELECT ID, post_name, post_parent FROM $wpdb->posts WHERE post_type = 'page' AND post_status != 'auto-draft'") );
779
780                 // If we have no pages get out quick
781                 if ( !$posts )
782                         return array( array(), array() );
783
784                 //now reverse it, because we need parents after children for rewrite rules to work properly
785                 $posts = array_reverse($posts, true);
786
787                 $page_uris = array();
788                 $page_attachment_uris = array();
789
790                 foreach ( $posts as $id => $post ) {
791                         // URL => page name
792                         $uri = get_page_uri($id);
793                         $attachments = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_name, post_parent FROM $wpdb->posts WHERE post_type = 'attachment' AND post_parent = %d", $id ));
794                         if ( !empty($attachments) ) {
795                                 foreach ( $attachments as $attachment ) {
796                                         $attach_uri = get_page_uri($attachment->ID);
797                                         $page_attachment_uris[$attach_uri] = $attachment->ID;
798                                 }
799                         }
800
801                         $page_uris[$uri] = $id;
802                 }
803
804                 return array( $page_uris, $page_attachment_uris );
805         }
806
807         /**
808          * Retrieve all of the rewrite rules for pages.
809          *
810          * If the 'use_verbose_page_rules' property is false, then there will only
811          * be a single rewrite rule for pages for those matching '%pagename%'. With
812          * the property set to true, the attachments and the pages will be added for
813          * each individual attachment URI and page URI, respectively.
814          *
815          * @since 1.5.0
816          * @access public
817          *
818          * @return array
819          */
820         function page_rewrite_rules() {
821                 $rewrite_rules = array();
822                 $page_structure = $this->get_page_permastruct();
823
824                 // the extra .? at the beginning prevents clashes with other regular expressions in the rules array
825                 $this->add_rewrite_tag('%pagename%', "(.?.+?)", 'pagename=');
826                 $rewrite_rules = array_merge($rewrite_rules, $this->generate_rewrite_rules($page_structure, EP_PAGES));
827                 return $rewrite_rules;
828         }
829
830         /**
831          * Retrieve date permalink structure, with year, month, and day.
832          *
833          * The permalink structure for the date, if not set already depends on the
834          * permalink structure. It can be one of three formats. The first is year,
835          * month, day; the second is day, month, year; and the last format is month,
836          * day, year. These are matched against the permalink structure for which
837          * one is used. If none matches, then the default will be used, which is
838          * year, month, day.
839          *
840          * Prevents post ID and date permalinks from overlapping. In the case of
841          * post_id, the date permalink will be prepended with front permalink with
842          * 'date/' before the actual permalink to form the complete date permalink
843          * structure.
844          *
845          * @since 1.5.0
846          * @access public
847          *
848          * @return bool|string False on no permalink structure. Date permalink structure.
849          */
850         function get_date_permastruct() {
851                 if ( isset($this->date_structure) )
852                         return $this->date_structure;
853
854                 if ( empty($this->permalink_structure) ) {
855                         $this->date_structure = '';
856                         return false;
857                 }
858
859                 // The date permalink must have year, month, and day separated by slashes.
860                 $endians = array('%year%/%monthnum%/%day%', '%day%/%monthnum%/%year%', '%monthnum%/%day%/%year%');
861
862                 $this->date_structure = '';
863                 $date_endian = '';
864
865                 foreach ( $endians as $endian ) {
866                         if ( false !== strpos($this->permalink_structure, $endian) ) {
867                                 $date_endian= $endian;
868                                 break;
869                         }
870                 }
871
872                 if ( empty($date_endian) )
873                         $date_endian = '%year%/%monthnum%/%day%';
874
875                 // Do not allow the date tags and %post_id% to overlap in the permalink
876                 // structure. If they do, move the date tags to $front/date/.
877                 $front = $this->front;
878                 preg_match_all('/%.+?%/', $this->permalink_structure, $tokens);
879                 $tok_index = 1;
880                 foreach ( (array) $tokens[0] as $token) {
881                         if ( '%post_id%' == $token && ($tok_index <= 3) ) {
882                                 $front = $front . 'date/';
883                                 break;
884                         }
885                         $tok_index++;
886                 }
887
888                 $this->date_structure = $front . $date_endian;
889
890                 return $this->date_structure;
891         }
892
893         /**
894          * Retrieve the year permalink structure without month and day.
895          *
896          * Gets the date permalink structure and strips out the month and day
897          * permalink structures.
898          *
899          * @since 1.5.0
900          * @access public
901          *
902          * @return bool|string False on failure. Year structure on success.
903          */
904         function get_year_permastruct() {
905                 $structure = $this->get_date_permastruct($this->permalink_structure);
906
907                 if ( empty($structure) )
908                         return false;
909
910                 $structure = str_replace('%monthnum%', '', $structure);
911                 $structure = str_replace('%day%', '', $structure);
912
913                 $structure = preg_replace('#/+#', '/', $structure);
914
915                 return $structure;
916         }
917
918         /**
919          * Retrieve the month permalink structure without day and with year.
920          *
921          * Gets the date permalink structure and strips out the day permalink
922          * structures. Keeps the year permalink structure.
923          *
924          * @since 1.5.0
925          * @access public
926          *
927          * @return bool|string False on failure. Year/Month structure on success.
928          */
929         function get_month_permastruct() {
930                 $structure = $this->get_date_permastruct($this->permalink_structure);
931
932                 if ( empty($structure) )
933                         return false;
934
935                 $structure = str_replace('%day%', '', $structure);
936
937                 $structure = preg_replace('#/+#', '/', $structure);
938
939                 return $structure;
940         }
941
942         /**
943          * Retrieve the day permalink structure with month and year.
944          *
945          * Keeps date permalink structure with all year, month, and day.
946          *
947          * @since 1.5.0
948          * @access public
949          *
950          * @return bool|string False on failure. Year/Month/Day structure on success.
951          */
952         function get_day_permastruct() {
953                 return $this->get_date_permastruct($this->permalink_structure);
954         }
955
956         /**
957          * Retrieve the permalink structure for categories.
958          *
959          * If the category_base property has no value, then the category structure
960          * will have the front property value, followed by 'category', and finally
961          * '%category%'. If it does, then the root property will be used, along with
962          * the category_base property value.
963          *
964          * @since 1.5.0
965          * @access public
966          *
967          * @return bool|string False on failure. Category permalink structure.
968          */
969         function get_category_permastruct() {
970                 return $this->get_extra_permastruct('category');
971         }
972
973         /**
974          * Retrieve the permalink structure for tags.
975          *
976          * If the tag_base property has no value, then the tag structure will have
977          * the front property value, followed by 'tag', and finally '%tag%'. If it
978          * does, then the root property will be used, along with the tag_base
979          * property value.
980          *
981          * @since 2.3.0
982          * @access public
983          *
984          * @return bool|string False on failure. Tag permalink structure.
985          */
986         function get_tag_permastruct() {
987                 return $this->get_extra_permastruct('post_tag');
988         }
989
990         /**
991          * Retrieve extra permalink structure by name.
992          *
993          * @since 2.5.0
994          * @access public
995          *
996          * @param string $name Permalink structure name.
997          * @return string|bool False if not found. Permalink structure string.
998          */
999         function get_extra_permastruct($name) {
1000                 if ( empty($this->permalink_structure) )
1001                         return false;
1002
1003                 if ( isset($this->extra_permastructs[$name]) )
1004                         return $this->extra_permastructs[$name][0];
1005
1006                 return false;
1007         }
1008
1009         /**
1010          * Retrieve the author permalink structure.
1011          *
1012          * The permalink structure is front property, author base, and finally
1013          * '/%author%'. Will set the author_structure property and then return it
1014          * without attempting to set the value again.
1015          *
1016          * @since 1.5.0
1017          * @access public
1018          *
1019          * @return string|bool False if not found. Permalink structure string.
1020          */
1021         function get_author_permastruct() {
1022                 if ( isset($this->author_structure) )
1023                         return $this->author_structure;
1024
1025                 if ( empty($this->permalink_structure) ) {
1026                         $this->author_structure = '';
1027                         return false;
1028                 }
1029
1030                 $this->author_structure = $this->front . $this->author_base . '/%author%';
1031
1032                 return $this->author_structure;
1033         }
1034
1035         /**
1036          * Retrieve the search permalink structure.
1037          *
1038          * The permalink structure is root property, search base, and finally
1039          * '/%search%'. Will set the search_structure property and then return it
1040          * without attempting to set the value again.
1041          *
1042          * @since 1.5.0
1043          * @access public
1044          *
1045          * @return string|bool False if not found. Permalink structure string.
1046          */
1047         function get_search_permastruct() {
1048                 if ( isset($this->search_structure) )
1049                         return $this->search_structure;
1050
1051                 if ( empty($this->permalink_structure) ) {
1052                         $this->search_structure = '';
1053                         return false;
1054                 }
1055
1056                 $this->search_structure = $this->root . $this->search_base . '/%search%';
1057
1058                 return $this->search_structure;
1059         }
1060
1061         /**
1062          * Retrieve the page permalink structure.
1063          *
1064          * The permalink structure is root property, and '%pagename%'. Will set the
1065          * page_structure property and then return it without attempting to set the
1066          * value again.
1067          *
1068          * @since 1.5.0
1069          * @access public
1070          *
1071          * @return string|bool False if not found. Permalink structure string.
1072          */
1073         function get_page_permastruct() {
1074                 if ( isset($this->page_structure) )
1075                         return $this->page_structure;
1076
1077                 if (empty($this->permalink_structure)) {
1078                         $this->page_structure = '';
1079                         return false;
1080                 }
1081
1082                 $this->page_structure = $this->root . '%pagename%';
1083
1084                 return $this->page_structure;
1085         }
1086
1087         /**
1088          * Retrieve the feed permalink structure.
1089          *
1090          * The permalink structure is root property, feed base, and finally
1091          * '/%feed%'. Will set the feed_structure property and then return it
1092          * without attempting to set the value again.
1093          *
1094          * @since 1.5.0
1095          * @access public
1096          *
1097          * @return string|bool False if not found. Permalink structure string.
1098          */
1099         function get_feed_permastruct() {
1100                 if ( isset($this->feed_structure) )
1101                         return $this->feed_structure;
1102
1103                 if ( empty($this->permalink_structure) ) {
1104                         $this->feed_structure = '';
1105                         return false;
1106                 }
1107
1108                 $this->feed_structure = $this->root . $this->feed_base . '/%feed%';
1109
1110                 return $this->feed_structure;
1111         }
1112
1113         /**
1114          * Retrieve the comment feed permalink structure.
1115          *
1116          * The permalink structure is root property, comment base property, feed
1117          * base and finally '/%feed%'. Will set the comment_feed_structure property
1118          * and then return it without attempting to set the value again.
1119          *
1120          * @since 1.5.0
1121          * @access public
1122          *
1123          * @return string|bool False if not found. Permalink structure string.
1124          */
1125         function get_comment_feed_permastruct() {
1126                 if ( isset($this->comment_feed_structure) )
1127                         return $this->comment_feed_structure;
1128
1129                 if (empty($this->permalink_structure)) {
1130                         $this->comment_feed_structure = '';
1131                         return false;
1132                 }
1133
1134                 $this->comment_feed_structure = $this->root . $this->comments_base . '/' . $this->feed_base . '/%feed%';
1135
1136                 return $this->comment_feed_structure;
1137         }
1138
1139         /**
1140          * Append or update tag, pattern, and query for replacement.
1141          *
1142          * If the tag already exists, replace the existing pattern and query for
1143          * that tag, otherwise add the new tag, pattern, and query to the end of the
1144          * arrays.
1145          *
1146          * @internal What is the purpose of this function again? Need to finish long
1147          *           description.
1148          *
1149          * @since 1.5.0
1150          * @access public
1151          *
1152          * @param string $tag Append tag to rewritecode property array.
1153          * @param string $pattern Append pattern to rewritereplace property array.
1154          * @param string $query Append query to queryreplace property array.
1155          */
1156         function add_rewrite_tag($tag, $pattern, $query) {
1157                 $position = array_search($tag, $this->rewritecode);
1158                 if ( false !== $position && null !== $position ) {
1159                         $this->rewritereplace[$position] = $pattern;
1160                         $this->queryreplace[$position] = $query;
1161                 } else {
1162                         $this->rewritecode[] = $tag;
1163                         $this->rewritereplace[] = $pattern;
1164                         $this->queryreplace[] = $query;
1165                 }
1166         }
1167
1168         /**
1169          * Generate the rules from permalink structure.
1170          *
1171          * The main WP_Rewrite function for building the rewrite rule list. The
1172          * contents of the function is a mix of black magic and regular expressions,
1173          * so best just ignore the contents and move to the parameters.
1174          *
1175          * @since 1.5.0
1176          * @access public
1177          *
1178          * @param string $permalink_structure The permalink structure.
1179          * @param int $ep_mask Optional, default is EP_NONE. Endpoint constant, see EP_* constants.
1180          * @param bool $paged Optional, default is true. Whether permalink request is paged.
1181          * @param bool $feed Optional, default is true. Whether for feed.
1182          * @param bool $forcomments Optional, default is false. Whether for comments.
1183          * @param bool $walk_dirs Optional, default is true. Whether to create list of directories to walk over.
1184          * @param bool $endpoints Optional, default is true. Whether endpoints are enabled.
1185          * @return array Rewrite rule list.
1186          */
1187         function generate_rewrite_rules($permalink_structure, $ep_mask = EP_NONE, $paged = true, $feed = true, $forcomments = false, $walk_dirs = true, $endpoints = true) {
1188                 //build a regex to match the feed section of URLs, something like (feed|atom|rss|rss2)/?
1189                 $feedregex2 = '';
1190                 foreach ( (array) $this->feeds as $feed_name)
1191                         $feedregex2 .= $feed_name . '|';
1192                 $feedregex2 = '(' . trim($feedregex2, '|') .  ')/?$';
1193
1194                 //$feedregex is identical but with /feed/ added on as well, so URLs like <permalink>/feed/atom
1195                 //and <permalink>/atom are both possible
1196                 $feedregex = $this->feed_base  . '/' . $feedregex2;
1197
1198                 //build a regex to match the trackback and page/xx parts of URLs
1199                 $trackbackregex = 'trackback/?$';
1200                 $pageregex = $this->pagination_base . '/?([0-9]{1,})/?$';
1201                 $commentregex = 'comment-page-([0-9]{1,})/?$';
1202
1203                 //build up an array of endpoint regexes to append => queries to append
1204                 if ( $endpoints ) {
1205                         $ep_query_append = array ();
1206                         foreach ( (array) $this->endpoints as $endpoint) {
1207                                 //match everything after the endpoint name, but allow for nothing to appear there
1208                                 $epmatch = $endpoint[1] . '(/(.*))?/?$';
1209                                 //this will be appended on to the rest of the query for each dir
1210                                 $epquery = '&' . $endpoint[1] . '=';
1211                                 $ep_query_append[$epmatch] = array ( $endpoint[0], $epquery );
1212                         }
1213                 }
1214
1215                 //get everything up to the first rewrite tag
1216                 $front = substr($permalink_structure, 0, strpos($permalink_structure, '%'));
1217                 //build an array of the tags (note that said array ends up being in $tokens[0])
1218                 preg_match_all('/%.+?%/', $permalink_structure, $tokens);
1219
1220                 $num_tokens = count($tokens[0]);
1221
1222                 $index = $this->index; //probably 'index.php'
1223                 $feedindex = $index;
1224                 $trackbackindex = $index;
1225                 //build a list from the rewritecode and queryreplace arrays, that will look something like
1226                 //tagname=$matches[i] where i is the current $i
1227                 for ( $i = 0; $i < $num_tokens; ++$i ) {
1228                         if ( 0 < $i )
1229                                 $queries[$i] = $queries[$i - 1] . '&';
1230                         else
1231                                 $queries[$i] = '';
1232
1233                         $query_token = str_replace($this->rewritecode, $this->queryreplace, $tokens[0][$i]) . $this->preg_index($i+1);
1234                         $queries[$i] .= $query_token;
1235                 }
1236
1237                 //get the structure, minus any cruft (stuff that isn't tags) at the front
1238                 $structure = $permalink_structure;
1239                 if ( $front != '/' )
1240                         $structure = str_replace($front, '', $structure);
1241
1242                 //create a list of dirs to walk over, making rewrite rules for each level
1243                 //so for example, a $structure of /%year%/%month%/%postname% would create
1244                 //rewrite rules for /%year%/, /%year%/%month%/ and /%year%/%month%/%postname%
1245                 $structure = trim($structure, '/');
1246                 $dirs = $walk_dirs ? explode('/', $structure) : array( $structure );
1247                 $num_dirs = count($dirs);
1248
1249                 //strip slashes from the front of $front
1250                 $front = preg_replace('|^/+|', '', $front);
1251
1252                 //the main workhorse loop
1253                 $post_rewrite = array();
1254                 $struct = $front;
1255                 for ( $j = 0; $j < $num_dirs; ++$j ) {
1256                         //get the struct for this dir, and trim slashes off the front
1257                         $struct .= $dirs[$j] . '/'; //accumulate. see comment near explode('/', $structure) above
1258                         $struct = ltrim($struct, '/');
1259
1260                         //replace tags with regexes
1261                         $match = str_replace($this->rewritecode, $this->rewritereplace, $struct);
1262
1263                         //make a list of tags, and store how many there are in $num_toks
1264                         $num_toks = preg_match_all('/%.+?%/', $struct, $toks);
1265
1266                         //get the 'tagname=$matches[i]'
1267                         $query = ( isset($queries) && is_array($queries) && !empty($num_toks) ) ? $queries[$num_toks - 1] : '';
1268
1269                         //set up $ep_mask_specific which is used to match more specific URL types
1270                         switch ( $dirs[$j] ) {
1271                                 case '%year%':
1272                                         $ep_mask_specific = EP_YEAR;
1273                                         break;
1274                                 case '%monthnum%':
1275                                         $ep_mask_specific = EP_MONTH;
1276                                         break;
1277                                 case '%day%':
1278                                         $ep_mask_specific = EP_DAY;
1279                                         break;
1280                                 default:
1281                                         $ep_mask_specific = EP_NONE;
1282                         }
1283
1284                         //create query for /page/xx
1285                         $pagematch = $match . $pageregex;
1286                         $pagequery = $index . '?' . $query . '&paged=' . $this->preg_index($num_toks + 1);
1287
1288                         //create query for /comment-page-xx
1289                         $commentmatch = $match . $commentregex;
1290                         $commentquery = $index . '?' . $query . '&cpage=' . $this->preg_index($num_toks + 1);
1291
1292                         if ( get_option('page_on_front') ) {
1293                                 //create query for Root /comment-page-xx
1294                                 $rootcommentmatch = $match . $commentregex;
1295                                 $rootcommentquery = $index . '?' . $query . '&page_id=' . get_option('page_on_front') . '&cpage=' . $this->preg_index($num_toks + 1);
1296                         }
1297
1298                         //create query for /feed/(feed|atom|rss|rss2|rdf)
1299                         $feedmatch = $match . $feedregex;
1300                         $feedquery = $feedindex . '?' . $query . '&feed=' . $this->preg_index($num_toks + 1);
1301
1302                         //create query for /(feed|atom|rss|rss2|rdf) (see comment near creation of $feedregex)
1303                         $feedmatch2 = $match . $feedregex2;
1304                         $feedquery2 = $feedindex . '?' . $query . '&feed=' . $this->preg_index($num_toks + 1);
1305
1306                         //if asked to, turn the feed queries into comment feed ones
1307                         if ( $forcomments ) {
1308                                 $feedquery .= '&withcomments=1';
1309                                 $feedquery2 .= '&withcomments=1';
1310                         }
1311
1312                         //start creating the array of rewrites for this dir
1313                         $rewrite = array();
1314                         if ( $feed ) //...adding on /feed/ regexes => queries
1315                                 $rewrite = array($feedmatch => $feedquery, $feedmatch2 => $feedquery2);
1316                         if ( $paged ) //...and /page/xx ones
1317                                 $rewrite = array_merge($rewrite, array($pagematch => $pagequery));
1318
1319                         //only on pages with comments add ../comment-page-xx/
1320                         if ( EP_PAGES & $ep_mask || EP_PERMALINK & $ep_mask )
1321                                 $rewrite = array_merge($rewrite, array($commentmatch => $commentquery));
1322                         else if ( EP_ROOT & $ep_mask && get_option('page_on_front') )
1323                                 $rewrite = array_merge($rewrite, array($rootcommentmatch => $rootcommentquery));
1324
1325                         //do endpoints
1326                         if ( $endpoints ) {
1327                                 foreach ( (array) $ep_query_append as $regex => $ep) {
1328                                         //add the endpoints on if the mask fits
1329                                         if ( $ep[0] & $ep_mask || $ep[0] & $ep_mask_specific )
1330                                                 $rewrite[$match . $regex] = $index . '?' . $query . $ep[1] . $this->preg_index($num_toks + 2);
1331                                 }
1332                         }
1333
1334                         //if we've got some tags in this dir
1335                         if ( $num_toks ) {
1336                                 $post = false;
1337                                 $page = false;
1338
1339                                 //check to see if this dir is permalink-level: i.e. the structure specifies an
1340                                 //individual post. Do this by checking it contains at least one of 1) post name,
1341                                 //2) post ID, 3) page name, 4) timestamp (year, month, day, hour, second and
1342                                 //minute all present). Set these flags now as we need them for the endpoints.
1343                                 if ( strpos($struct, '%postname%') !== false
1344                                                 || strpos($struct, '%post_id%') !== false
1345                                                 || strpos($struct, '%pagename%') !== false
1346                                                 || (strpos($struct, '%year%') !== false && strpos($struct, '%monthnum%') !== false && strpos($struct, '%day%') !== false && strpos($struct, '%hour%') !== false && strpos($struct, '%minute%') !== false && strpos($struct, '%second%') !== false)
1347                                                 ) {
1348                                         $post = true;
1349                                         if ( strpos($struct, '%pagename%') !== false )
1350                                                 $page = true;
1351                                 }
1352
1353                                 if ( ! $post ) {
1354                                         // For custom post types, we need to add on endpoints as well.
1355                                         foreach ( get_post_types( array('_builtin' => false ) ) as $ptype ) {
1356                                                 if ( strpos($struct, "%$ptype%") !== false ) {
1357                                                         $post = true;
1358                                                         $page = is_post_type_hierarchical( $ptype ); // This is for page style attachment url's
1359                                                         break;
1360                                                 }
1361                                         }
1362                                 }
1363
1364                                 //if we're creating rules for a permalink, do all the endpoints like attachments etc
1365                                 if ( $post ) {
1366                                         //create query and regex for trackback
1367                                         $trackbackmatch = $match . $trackbackregex;
1368                                         $trackbackquery = $trackbackindex . '?' . $query . '&tb=1';
1369                                         //trim slashes from the end of the regex for this dir
1370                                         $match = rtrim($match, '/');
1371                                         //get rid of brackets
1372                                         $submatchbase = str_replace( array('(', ')'), '', $match);
1373
1374                                         //add a rule for at attachments, which take the form of <permalink>/some-text
1375                                         $sub1 = $submatchbase . '/([^/]+)/';
1376                                         $sub1tb = $sub1 . $trackbackregex; //add trackback regex <permalink>/trackback/...
1377                                         $sub1feed = $sub1 . $feedregex; //and <permalink>/feed/(atom|...)
1378                                         $sub1feed2 = $sub1 . $feedregex2; //and <permalink>/(feed|atom...)
1379                                         $sub1comment = $sub1 . $commentregex; //and <permalink>/comment-page-xx
1380                                         //add an ? as we don't have to match that last slash, and finally a $ so we
1381                                         //match to the end of the URL
1382
1383                                         //add another rule to match attachments in the explicit form:
1384                                         //<permalink>/attachment/some-text
1385                                         $sub2 = $submatchbase . '/attachment/([^/]+)/';
1386                                         $sub2tb = $sub2 . $trackbackregex; //and add trackbacks <permalink>/attachment/trackback
1387                                         $sub2feed = $sub2 . $feedregex;    //feeds, <permalink>/attachment/feed/(atom|...)
1388                                         $sub2feed2 = $sub2 . $feedregex2;  //and feeds again on to this <permalink>/attachment/(feed|atom...)
1389                                         $sub2comment = $sub2 . $commentregex; //and <permalink>/comment-page-xx
1390
1391                                         //create queries for these extra tag-ons we've just dealt with
1392                                         $subquery = $index . '?attachment=' . $this->preg_index(1);
1393                                         $subtbquery = $subquery . '&tb=1';
1394                                         $subfeedquery = $subquery . '&feed=' . $this->preg_index(2);
1395                                         $subcommentquery = $subquery . '&cpage=' . $this->preg_index(2);
1396
1397                                         //do endpoints for attachments
1398                                         if ( !empty($endpoints) ) {
1399                                                 foreach ( (array) $ep_query_append as $regex => $ep ) {
1400                                                         if ( $ep[0] & EP_ATTACHMENT ) {
1401                                                                 $rewrite[$sub1 . $regex] = $subquery . $ep[1] . $this->preg_index(2);
1402                                                                 $rewrite[$sub2 . $regex] = $subquery . $ep[1] . $this->preg_index(2);
1403                                                         }
1404                                                 }
1405                                         }
1406
1407                                         //now we've finished with endpoints, finish off the $sub1 and $sub2 matches
1408                                         $sub1 .= '?$';
1409                                         $sub2 .= '?$';
1410
1411                                         //allow URLs like <permalink>/2 for <permalink>/page/2
1412                                         $match = $match . '(/[0-9]+)?/?$';
1413                                         $query = $index . '?' . $query . '&page=' . $this->preg_index($num_toks + 1);
1414                                 } else { //not matching a permalink so this is a lot simpler
1415                                         //close the match and finalise the query
1416                                         $match .= '?$';
1417                                         $query = $index . '?' . $query;
1418                                 }
1419
1420                                 //create the final array for this dir by joining the $rewrite array (which currently
1421                                 //only contains rules/queries for trackback, pages etc) to the main regex/query for
1422                                 //this dir
1423                                 $rewrite = array_merge($rewrite, array($match => $query));
1424
1425                                 //if we're matching a permalink, add those extras (attachments etc) on
1426                                 if ( $post ) {
1427                                         //add trackback
1428                                         $rewrite = array_merge(array($trackbackmatch => $trackbackquery), $rewrite);
1429
1430                                         //add regexes/queries for attachments, attachment trackbacks and so on
1431                                         if ( ! $page ) //require <permalink>/attachment/stuff form for pages because of confusion with subpages
1432                                                 $rewrite = array_merge($rewrite, array($sub1 => $subquery, $sub1tb => $subtbquery, $sub1feed => $subfeedquery, $sub1feed2 => $subfeedquery, $sub1comment => $subcommentquery));
1433                                         $rewrite = array_merge(array($sub2 => $subquery, $sub2tb => $subtbquery, $sub2feed => $subfeedquery, $sub2feed2 => $subfeedquery, $sub2comment => $subcommentquery), $rewrite);
1434                                 }
1435                         } //if($num_toks)
1436                         //add the rules for this dir to the accumulating $post_rewrite
1437                         $post_rewrite = array_merge($rewrite, $post_rewrite);
1438                 } //foreach ($dir)
1439                 return $post_rewrite; //the finished rules. phew!
1440         }
1441
1442         /**
1443          * Generate Rewrite rules with permalink structure and walking directory only.
1444          *
1445          * Shorten version of {@link WP_Rewrite::generate_rewrite_rules()} that
1446          * allows for shorter list of parameters. See the method for longer
1447          * description of what generating rewrite rules does.
1448          *
1449          * @uses WP_Rewrite::generate_rewrite_rules() See for long description and rest of parameters.
1450          * @since 1.5.0
1451          * @access public
1452          *
1453          * @param string $permalink_structure The permalink structure to generate rules.
1454          * @param bool $walk_dirs Optional, default is false. Whether to create list of directories to walk over.
1455          * @return array
1456          */
1457         function generate_rewrite_rule($permalink_structure, $walk_dirs = false) {
1458                 return $this->generate_rewrite_rules($permalink_structure, EP_NONE, false, false, false, $walk_dirs);
1459         }
1460
1461         /**
1462          * Construct rewrite matches and queries from permalink structure.
1463          *
1464          * Runs the action 'generate_rewrite_rules' with the parameter that is an
1465          * reference to the current WP_Rewrite instance to further manipulate the
1466          * permalink structures and rewrite rules. Runs the 'rewrite_rules_array'
1467          * filter on the full rewrite rule array.
1468          *
1469          * There are two ways to manipulate the rewrite rules, one by hooking into
1470          * the 'generate_rewrite_rules' action and gaining full control of the
1471          * object or just manipulating the rewrite rule array before it is passed
1472          * from the function.
1473          *
1474          * @since 1.5.0
1475          * @access public
1476          *
1477          * @return array An associate array of matches and queries.
1478          */
1479         function rewrite_rules() {
1480                 $rewrite = array();
1481
1482                 if ( empty($this->permalink_structure) )
1483                         return $rewrite;
1484
1485                 // robots.txt -only if installed at the root
1486                 $home_path = parse_url( home_url() );
1487                 $robots_rewrite = ( empty( $home_path['path'] ) || '/' == $home_path['path'] ) ? array( 'robots\.txt$' => $this->index . '?robots=1' ) : array();
1488
1489                 // Old feed files
1490                 $old_feed_files = array( '.*wp-(atom|rdf|rss|rss2|feed|commentsrss2)\.php$' => $this->index . '?feed=old' );
1491
1492                 // Registration rules
1493                 $registration_pages = array();
1494                 if ( is_multisite() && is_main_site() ) {
1495                         $registration_pages['.*wp-signup.php$'] = $this->index . '?signup=true';
1496                         $registration_pages['.*wp-activate.php$'] = $this->index . '?activate=true';
1497                 }
1498
1499                 // Post
1500                 $post_rewrite = $this->generate_rewrite_rules($this->permalink_structure, EP_PERMALINK);
1501                 $post_rewrite = apply_filters('post_rewrite_rules', $post_rewrite);
1502
1503                 // Date
1504                 $date_rewrite = $this->generate_rewrite_rules($this->get_date_permastruct(), EP_DATE);
1505                 $date_rewrite = apply_filters('date_rewrite_rules', $date_rewrite);
1506
1507                 // Root
1508                 $root_rewrite = $this->generate_rewrite_rules($this->root . '/', EP_ROOT);
1509                 $root_rewrite = apply_filters('root_rewrite_rules', $root_rewrite);
1510
1511                 // Comments
1512                 $comments_rewrite = $this->generate_rewrite_rules($this->root . $this->comments_base, EP_COMMENTS, true, true, true, false);
1513                 $comments_rewrite = apply_filters('comments_rewrite_rules', $comments_rewrite);
1514
1515                 // Search
1516                 $search_structure = $this->get_search_permastruct();
1517                 $search_rewrite = $this->generate_rewrite_rules($search_structure, EP_SEARCH);
1518                 $search_rewrite = apply_filters('search_rewrite_rules', $search_rewrite);
1519
1520                 // Authors
1521                 $author_rewrite = $this->generate_rewrite_rules($this->get_author_permastruct(), EP_AUTHORS);
1522                 $author_rewrite = apply_filters('author_rewrite_rules', $author_rewrite);
1523
1524                 // Pages
1525                 $page_rewrite = $this->page_rewrite_rules();
1526                 $page_rewrite = apply_filters('page_rewrite_rules', $page_rewrite);
1527
1528                 // Extra permastructs
1529                 foreach ( $this->extra_permastructs as $permastructname => $permastruct ) {
1530                         if ( is_array($permastruct) )
1531                                 $rules = $this->generate_rewrite_rules($permastruct[0], $permastruct[1]);
1532                         else
1533                                 $rules = $this->generate_rewrite_rules($permastruct, EP_NONE);
1534
1535                         $rules = apply_filters($permastructname . '_rewrite_rules', $rules);
1536                         if ( 'post_tag' == $permastructname )
1537                                 $rules = apply_filters('tag_rewrite_rules', $rules);
1538
1539                         $this->extra_rules_top = array_merge($this->extra_rules_top, $rules);
1540                 }
1541
1542                 // Put them together.
1543                 if ( $this->use_verbose_page_rules )
1544                         $this->rules = array_merge($this->extra_rules_top, $robots_rewrite, $old_feed_files, $registration_pages, $root_rewrite, $comments_rewrite, $search_rewrite,  $author_rewrite, $date_rewrite, $page_rewrite, $post_rewrite, $this->extra_rules);
1545                 else
1546                         $this->rules = array_merge($this->extra_rules_top, $robots_rewrite, $old_feed_files, $registration_pages, $root_rewrite, $comments_rewrite, $search_rewrite,  $author_rewrite, $date_rewrite, $post_rewrite, $page_rewrite, $this->extra_rules);
1547
1548                 do_action_ref_array('generate_rewrite_rules', array(&$this));
1549                 $this->rules = apply_filters('rewrite_rules_array', $this->rules);
1550
1551                 return $this->rules;
1552         }
1553
1554         /**
1555          * Retrieve the rewrite rules.
1556          *
1557          * The difference between this method and {@link
1558          * WP_Rewrite::rewrite_rules()} is that this method stores the rewrite rules
1559          * in the 'rewrite_rules' option and retrieves it. This prevents having to
1560          * process all of the permalinks to get the rewrite rules in the form of
1561          * caching.
1562          *
1563          * @since 1.5.0
1564          * @access public
1565          *
1566          * @return array Rewrite rules.
1567          */
1568         function wp_rewrite_rules() {
1569                 $this->rules = get_option('rewrite_rules');
1570                 if ( empty($this->rules) ) {
1571                         $this->matches = 'matches';
1572                         $this->rewrite_rules();
1573                         update_option('rewrite_rules', $this->rules);
1574                 }
1575
1576                 return $this->rules;
1577         }
1578
1579         /**
1580          * Retrieve mod_rewrite formatted rewrite rules to write to .htaccess.
1581          *
1582          * Does not actually write to the .htaccess file, but creates the rules for
1583          * the process that will.
1584          *
1585          * Will add  the non_wp_rules property rules to the .htaccess file before
1586          * the WordPress rewrite rules one.
1587          *
1588          * @since 1.5.0
1589          * @access public
1590          *
1591          * @return string
1592          */
1593         function mod_rewrite_rules() {
1594                 if ( ! $this->using_permalinks() )
1595                         return '';
1596
1597                 $site_root = parse_url(get_option('siteurl'));
1598                 if ( isset( $site_root['path'] ) )
1599                         $site_root = trailingslashit($site_root['path']);
1600
1601                 $home_root = parse_url(home_url());
1602                 if ( isset( $home_root['path'] ) )
1603                         $home_root = trailingslashit($home_root['path']);
1604                 else
1605                         $home_root = '/';
1606
1607                 $rules = "<IfModule mod_rewrite.c>\n";
1608                 $rules .= "RewriteEngine On\n";
1609                 $rules .= "RewriteBase $home_root\n";
1610                 $rules .= "RewriteRule ^index\.php$ - [L]\n"; // Prevent -f checks on index.php.
1611
1612                 //add in the rules that don't redirect to WP's index.php (and thus shouldn't be handled by WP at all)
1613                 foreach ( (array) $this->non_wp_rules as $match => $query) {
1614                         // Apache 1.3 does not support the reluctant (non-greedy) modifier.
1615                         $match = str_replace('.+?', '.+', $match);
1616
1617                         // If the match is unanchored and greedy, prepend rewrite conditions
1618                         // to avoid infinite redirects and eclipsing of real files.
1619                         //if ($match == '(.+)/?$' || $match == '([^/]+)/?$' ) {
1620                                 //nada.
1621                         //}
1622
1623                         $rules .= 'RewriteRule ^' . $match . ' ' . $home_root . $query . " [QSA,L]\n";
1624                 }
1625
1626                 if ( $this->use_verbose_rules ) {
1627                         $this->matches = '';
1628                         $rewrite = $this->rewrite_rules();
1629                         $num_rules = count($rewrite);
1630                         $rules .= "RewriteCond %{REQUEST_FILENAME} -f [OR]\n" .
1631                                 "RewriteCond %{REQUEST_FILENAME} -d\n" .
1632                                 "RewriteRule ^.*$ - [S=$num_rules]\n";
1633
1634                         foreach ( (array) $rewrite as $match => $query) {
1635                                 // Apache 1.3 does not support the reluctant (non-greedy) modifier.
1636                                 $match = str_replace('.+?', '.+', $match);
1637
1638                                 // If the match is unanchored and greedy, prepend rewrite conditions
1639                                 // to avoid infinite redirects and eclipsing of real files.
1640                                 //if ($match == '(.+)/?$' || $match == '([^/]+)/?$' ) {
1641                                         //nada.
1642                                 //}
1643
1644                                 if ( strpos($query, $this->index) !== false )
1645                                         $rules .= 'RewriteRule ^' . $match . ' ' . $home_root . $query . " [QSA,L]\n";
1646                                 else
1647                                         $rules .= 'RewriteRule ^' . $match . ' ' . $site_root . $query . " [QSA,L]\n";
1648                         }
1649                 } else {
1650                         $rules .= "RewriteCond %{REQUEST_FILENAME} !-f\n" .
1651                                 "RewriteCond %{REQUEST_FILENAME} !-d\n" .
1652                                 "RewriteRule . {$home_root}{$this->index} [L]\n";
1653                 }
1654
1655                 $rules .= "</IfModule>\n";
1656
1657                 $rules = apply_filters('mod_rewrite_rules', $rules);
1658                 $rules = apply_filters('rewrite_rules', $rules);  // Deprecated
1659
1660                 return $rules;
1661         }
1662
1663         /**
1664          * Retrieve IIS7 URL Rewrite formatted rewrite rules to write to web.config file.
1665          *
1666          * Does not actually write to the web.config file, but creates the rules for
1667          * the process that will.
1668          *
1669          * @since 2.8.0
1670          * @access public
1671          *
1672          * @return string
1673          */
1674         function iis7_url_rewrite_rules( $add_parent_tags = false ) {
1675
1676                 if ( ! $this->using_permalinks() )
1677                         return '';
1678                 $rules = '';
1679                 if ( $add_parent_tags ) {
1680                         $rules .= '<configuration>
1681         <system.webServer>
1682                 <rewrite>
1683                         <rules>';
1684                 }
1685                 if ( !is_multisite() ) {
1686                         $rules .= '
1687                                 <rule name="wordpress" patternSyntax="Wildcard">
1688                                         <match url="*" />
1689                                                 <conditions>
1690                                                         <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
1691                                                         <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
1692                                                 </conditions>
1693                                         <action type="Rewrite" url="index.php" />
1694                                 </rule>';
1695                 } else {
1696                         if (is_subdomain_install()) {
1697                                 $rules .= '
1698                                 <rule name="wordpress - Rule 1" stopProcessing="true">
1699                                         <match url="^index\.php$" ignoreCase="false" />
1700                                         <action type="None" />
1701                                 </rule>
1702                                 <rule name="wordpress - Rule 2" stopProcessing="true">
1703                                         <match url="^files/(.+)" ignoreCase="false" />
1704                                         <action type="Rewrite" url="wp-includes/ms-files.php?file={R:1}" appendQueryString="false" />
1705                                 </rule>
1706                                 <rule name="wordpress - Rule 3" stopProcessing="true">
1707                                         <match url="^" ignoreCase="false" />
1708                                         <conditions logicalGrouping="MatchAny">
1709                                                 <add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" />
1710                                                 <add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" />
1711                                         </conditions>
1712                                         <action type="None" />
1713                                 </rule>
1714                                 <rule name="wordpress - Rule 4" stopProcessing="true">
1715                                         <match url="." ignoreCase="false" />
1716                                         <action type="Rewrite" url="index.php" />
1717                                 </rule>';
1718                         } else {
1719                                 $rules .= '
1720                                 <rule name="wordpress - Rule 1" stopProcessing="true">
1721                                         <match url="^index\.php$" ignoreCase="false" />
1722                                         <action type="None" />
1723                                 </rule>
1724                                 <rule name="wordpress - Rule 2" stopProcessing="true">
1725                                         <match url="^([_0-9a-zA-Z-]+/)?files/(.+)" ignoreCase="false" />
1726                                         <action type="Rewrite" url="wp-includes/ms-files.php?file={R:2}" appendQueryString="false" />
1727                                 </rule>
1728                                 <rule name="wordpress - Rule 3" stopProcessing="true">
1729                                         <match url="^([_0-9a-zA-Z-]+/)?wp-admin$" ignoreCase="false" />
1730                                         <action type="Redirect" url="{R:1}wp-admin/" redirectType="Permanent" />
1731                                 </rule>
1732                                 <rule name="wordpress - Rule 4" stopProcessing="true">
1733                                         <match url="^" ignoreCase="false" />
1734                                         <conditions logicalGrouping="MatchAny">
1735                                                 <add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" />
1736                                                 <add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" />
1737                                         </conditions>
1738                                         <action type="None" />
1739                                 </rule>
1740                                 <rule name="wordpress - Rule 5" stopProcessing="true">
1741                                         <match url="^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*)" ignoreCase="false" />
1742                                         <action type="Rewrite" url="{R:1}" />
1743                                 </rule>
1744                                 <rule name="wordpress - Rule 6" stopProcessing="true">
1745                                         <match url="^([_0-9a-zA-Z-]+/)?(.*\.php)$" ignoreCase="false" />
1746                                         <action type="Rewrite" url="{R:2}" />
1747                                 </rule>
1748                                 <rule name="wordpress - Rule 7" stopProcessing="true">
1749                                         <match url="." ignoreCase="false" />
1750                                         <action type="Rewrite" url="index.php" />
1751                                 </rule>';
1752                         }
1753                 }
1754                 if ( $add_parent_tags ) {
1755                         $rules .= '
1756                         </rules>
1757                 </rewrite>
1758         </system.webServer>
1759 </configuration>';
1760                 }
1761
1762                 $rules = apply_filters('iis7_url_rewrite_rules', $rules);
1763
1764                 return $rules;
1765         }
1766
1767         /**
1768          * Add a straight rewrite rule.
1769          *
1770          * Any value in the $after parameter that isn't 'bottom' will be placed at
1771          * the top of the rules.
1772          *
1773          * @since 2.1.0
1774          * @access public
1775          *
1776          * @param string $regex Regular expression to match against request.
1777          * @param string $redirect URL regex redirects to when regex matches request.
1778          * @param string $after Optional, default is bottom. Location to place rule.
1779          */
1780         function add_rule($regex, $redirect, $after = 'bottom') {
1781                 //get everything up to the first ?
1782                 $index = (strpos($redirect, '?') == false ? strlen($redirect) : strpos($redirect, '?'));
1783                 $front = substr($redirect, 0, $index);
1784                 if ( $front != $this->index ) { //it doesn't redirect to WP's index.php
1785                         $this->add_external_rule($regex, $redirect);
1786                 } else {
1787                         if ( 'bottom' == $after)
1788                                 $this->extra_rules = array_merge($this->extra_rules, array($regex => $redirect));
1789                         else
1790                                 $this->extra_rules_top = array_merge($this->extra_rules_top, array($regex => $redirect));
1791                         //$this->extra_rules[$regex] = $redirect;
1792                 }
1793         }
1794
1795         /**
1796          * Add a rule that doesn't redirect to index.php.
1797          *
1798          * Can redirect to any place.
1799          *
1800          * @since 2.1.0
1801          * @access public
1802          *
1803          * @param string $regex Regular expression to match against request.
1804          * @param string $redirect URL regex redirects to when regex matches request.
1805          */
1806         function add_external_rule($regex, $redirect) {
1807                 $this->non_wp_rules[$regex] = $redirect;
1808         }
1809
1810         /**
1811          * Add an endpoint, like /trackback/.
1812          *
1813          * To be inserted after certain URL types (specified in $places).
1814          *
1815          * @since 2.1.0
1816          * @access public
1817          *
1818          * @param string $name Name of endpoint.
1819          * @param array $places URL types that endpoint can be used.
1820          */
1821         function add_endpoint($name, $places) {
1822                 global $wp;
1823                 $this->endpoints[] = array ( $places, $name );
1824                 $wp->add_query_var($name);
1825         }
1826
1827         /**
1828          * Add permalink structure.
1829          *
1830          * These are added along with the extra rewrite rules that are merged to the
1831          * top.
1832          *
1833          * @since 2.5.0
1834          * @access public
1835          *
1836          * @param string $name Name for permalink structure.
1837          * @param string $struct Permalink structure.
1838          * @param bool $with_front Prepend front base to permalink structure.
1839          */
1840         function add_permastruct($name, $struct, $with_front = true, $ep_mask = EP_NONE) {
1841                 if ( $with_front )
1842                         $struct = $this->front . $struct;
1843                 else
1844                         $struct = $this->root . $struct;
1845                 $this->extra_permastructs[$name] = array($struct, $ep_mask);
1846         }
1847
1848         /**
1849          * Remove rewrite rules and then recreate rewrite rules.
1850          *
1851          * Calls {@link WP_Rewrite::wp_rewrite_rules()} after removing the
1852          * 'rewrite_rules' option. If the function named 'save_mod_rewrite_rules'
1853          * exists, it will be called.
1854          *
1855          * @since 2.0.1
1856          * @access public
1857          * @param bool $hard Whether to update .htaccess (hard flush) or just update rewrite_rules option (soft flush). Default is true (hard).
1858          */
1859         function flush_rules($hard = true) {
1860                 delete_option('rewrite_rules');
1861                 $this->wp_rewrite_rules();
1862                 if ( $hard && function_exists('save_mod_rewrite_rules') )
1863                         save_mod_rewrite_rules();
1864                 if ( $hard && function_exists('iis7_save_url_rewrite_rules') )
1865                         iis7_save_url_rewrite_rules();
1866         }
1867
1868         /**
1869          * Sets up the object's properties.
1870          *
1871          * The 'use_verbose_page_rules' object property will be set to true if the
1872          * permalink structure begins with one of the following: '%postname%', '%category%',
1873          * '%tag%', or '%author%'.
1874          *
1875          * @since 1.5.0
1876          * @access public
1877          */
1878         function init() {
1879                 $this->extra_rules = $this->non_wp_rules = $this->endpoints = array();
1880                 $this->permalink_structure = get_option('permalink_structure');
1881                 $this->front = substr($this->permalink_structure, 0, strpos($this->permalink_structure, '%'));
1882                 $this->root = '';
1883                 if ( $this->using_index_permalinks() )
1884                         $this->root = $this->index . '/';
1885                 unset($this->author_structure);
1886                 unset($this->date_structure);
1887                 unset($this->page_structure);
1888                 unset($this->search_structure);
1889                 unset($this->feed_structure);
1890                 unset($this->comment_feed_structure);
1891                 $this->use_trailing_slashes = ( '/' == substr($this->permalink_structure, -1, 1) );
1892
1893                 // Enable generic rules for pages if permalink structure doesn't begin with a wildcard.
1894                 if ( preg_match("/^[^%]*%(?:postname|category|tag|author)%/", $this->permalink_structure) )
1895                          $this->use_verbose_page_rules = true;
1896                 else
1897                         $this->use_verbose_page_rules = false;
1898         }
1899
1900         /**
1901          * Set the main permalink structure for the blog.
1902          *
1903          * Will update the 'permalink_structure' option, if there is a difference
1904          * between the current permalink structure and the parameter value. Calls
1905          * {@link WP_Rewrite::init()} after the option is updated.
1906          *
1907          * Fires the 'permalink_structure_changed' action once the init call has
1908          * processed passing the old and new values
1909          *
1910          * @since 1.5.0
1911          * @access public
1912          *
1913          * @param string $permalink_structure Permalink structure.
1914          */
1915         function set_permalink_structure($permalink_structure) {
1916                 if ( $permalink_structure != $this->permalink_structure ) {
1917                         update_option('permalink_structure', $permalink_structure);
1918                         $this->init();
1919                         do_action('permalink_structure_changed', $this->permalink_structure, $permalink_structure);
1920                 }
1921         }
1922
1923         /**
1924          * Set the category base for the category permalink.
1925          *
1926          * Will update the 'category_base' option, if there is a difference between
1927          * the current category base and the parameter value. Calls
1928          * {@link WP_Rewrite::init()} after the option is updated.
1929          *
1930          * @since 1.5.0
1931          * @access public
1932          *
1933          * @param string $category_base Category permalink structure base.
1934          */
1935         function set_category_base($category_base) {
1936                 if ( $category_base != get_option('category_base') ) {
1937                         update_option('category_base', $category_base);
1938                         $this->init();
1939                 }
1940         }
1941
1942         /**
1943          * Set the tag base for the tag permalink.
1944          *
1945          * Will update the 'tag_base' option, if there is a difference between the
1946          * current tag base and the parameter value. Calls
1947          * {@link WP_Rewrite::init()} after the option is updated.
1948          *
1949          * @since 2.3.0
1950          * @access public
1951          *
1952          * @param string $tag_base Tag permalink structure base.
1953          */
1954         function set_tag_base( $tag_base ) {
1955                 if ( $tag_base != get_option( 'tag_base') ) {
1956                         update_option( 'tag_base', $tag_base );
1957                         $this->init();
1958                 }
1959         }
1960
1961         /**
1962          * Constructor - Calls init(), which runs setup.
1963          *
1964          * @since 1.5.0
1965          * @access public
1966          *
1967          * @return WP_Rewrite
1968          */
1969         function __construct() {
1970                 $this->init();
1971         }
1972 }
1973
1974 ?>