~canonical-sysadmins/wordpress/4.7.2

« back to all changes in this revision

Viewing changes to wp-includes/rewrite.php

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

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