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