]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/link-template.php
WordPress 4.7.2
[autoinstalls/wordpress.git] / wp-includes / link-template.php
1 <?php
2 /**
3  * WordPress Link Template Functions
4  *
5  * @package WordPress
6  * @subpackage Template
7  */
8
9 /**
10  * Displays the permalink for the current post.
11  *
12  * @since 1.2.0
13  * @since 4.4.0 Added the `$post` parameter.
14  *
15  * @param int|WP_Post $post Optional. Post ID or post object. Default is the global `$post`.
16  */
17 function the_permalink( $post = 0 ) {
18         /**
19          * Filters the display of the permalink for the current post.
20          *
21          * @since 1.5.0
22          * @since 4.4.0 Added the `$post` parameter.
23          *
24          * @param string      $permalink The permalink for the current post.
25          * @param int|WP_Post $post      Post ID, WP_Post object, or 0. Default 0.
26          */
27         echo esc_url( apply_filters( 'the_permalink', get_permalink( $post ), $post ) );
28 }
29
30 /**
31  * Retrieves a trailing-slashed string if the site is set for adding trailing slashes.
32  *
33  * Conditionally adds a trailing slash if the permalink structure has a trailing
34  * slash, strips the trailing slash if not. The string is passed through the
35  * {@see 'user_trailingslashit'} filter. Will remove trailing slash from string, if
36  * site is not set to have them.
37  *
38  * @since 2.2.0
39  *
40  * @global WP_Rewrite $wp_rewrite
41  *
42  * @param string $string      URL with or without a trailing slash.
43  * @param string $type_of_url Optional. The type of URL being considered (e.g. single, category, etc)
44  *                            for use in the filter. Default empty string.
45  * @return string The URL with the trailing slash appended or stripped.
46  */
47 function user_trailingslashit($string, $type_of_url = '') {
48         global $wp_rewrite;
49         if ( $wp_rewrite->use_trailing_slashes )
50                 $string = trailingslashit($string);
51         else
52                 $string = untrailingslashit($string);
53
54         /**
55          * Filters the trailing-slashed string, depending on whether the site is set to use trailing slashes.
56          *
57          * @since 2.2.0
58          *
59          * @param string $string      URL with or without a trailing slash.
60          * @param string $type_of_url The type of URL being considered. Accepts 'single', 'single_trackback',
61          *                            'single_feed', 'single_paged', 'commentpaged', 'paged', 'home', 'feed',
62          *                            'category', 'page', 'year', 'month', 'day', 'post_type_archive'.
63          */
64         return apply_filters( 'user_trailingslashit', $string, $type_of_url );
65 }
66
67 /**
68  * Displays the permalink anchor for the current post.
69  *
70  * The permalink mode title will use the post title for the 'a' element 'id'
71  * attribute. The id mode uses 'post-' with the post ID for the 'id' attribute.
72  *
73  * @since 0.71
74  *
75  * @param string $mode Optional. Permalink mode. Accepts 'title' or 'id'. Default 'id'.
76  */
77 function permalink_anchor( $mode = 'id' ) {
78         $post = get_post();
79         switch ( strtolower( $mode ) ) {
80                 case 'title':
81                         $title = sanitize_title( $post->post_title ) . '-' . $post->ID;
82                         echo '<a id="'.$title.'"></a>';
83                         break;
84                 case 'id':
85                 default:
86                         echo '<a id="post-' . $post->ID . '"></a>';
87                         break;
88         }
89 }
90
91 /**
92  * Retrieves the full permalink for the current post or post ID.
93  *
94  * This function is an alias for get_permalink().
95  *
96  * @since 3.9.0
97  *
98  * @see get_permalink()
99  *
100  * @param int|WP_Post $post      Optional. Post ID or post object. Default is the global `$post`.
101  * @param bool        $leavename Optional. Whether to keep post name or page name. Default false.
102  *
103  * @return string|false The permalink URL or false if post does not exist.
104  */
105 function get_the_permalink( $post = 0, $leavename = false ) {
106         return get_permalink( $post, $leavename );
107 }
108
109 /**
110  * Retrieves the full permalink for the current post or post ID.
111  *
112  * @since 1.0.0
113  *
114  * @param int|WP_Post $post      Optional. Post ID or post object. Default is the global `$post`.
115  * @param bool        $leavename Optional. Whether to keep post name or page name. Default false.
116  * @return string|false The permalink URL or false if post does not exist.
117  */
118 function get_permalink( $post = 0, $leavename = false ) {
119         $rewritecode = array(
120                 '%year%',
121                 '%monthnum%',
122                 '%day%',
123                 '%hour%',
124                 '%minute%',
125                 '%second%',
126                 $leavename? '' : '%postname%',
127                 '%post_id%',
128                 '%category%',
129                 '%author%',
130                 $leavename? '' : '%pagename%',
131         );
132
133         if ( is_object( $post ) && isset( $post->filter ) && 'sample' == $post->filter ) {
134                 $sample = true;
135         } else {
136                 $post = get_post( $post );
137                 $sample = false;
138         }
139
140         if ( empty($post->ID) )
141                 return false;
142
143         if ( $post->post_type == 'page' )
144                 return get_page_link($post, $leavename, $sample);
145         elseif ( $post->post_type == 'attachment' )
146                 return get_attachment_link( $post, $leavename );
147         elseif ( in_array($post->post_type, get_post_types( array('_builtin' => false) ) ) )
148                 return get_post_permalink($post, $leavename, $sample);
149
150         $permalink = get_option('permalink_structure');
151
152         /**
153          * Filters the permalink structure for a post before token replacement occurs.
154          *
155          * Only applies to posts with post_type of 'post'.
156          *
157          * @since 3.0.0
158          *
159          * @param string  $permalink The site's permalink structure.
160          * @param WP_Post $post      The post in question.
161          * @param bool    $leavename Whether to keep the post name.
162          */
163         $permalink = apply_filters( 'pre_post_link', $permalink, $post, $leavename );
164
165         if ( '' != $permalink && !in_array( $post->post_status, array( 'draft', 'pending', 'auto-draft', 'future' ) ) ) {
166                 $unixtime = strtotime($post->post_date);
167
168                 $category = '';
169                 if ( strpos($permalink, '%category%') !== false ) {
170                         $cats = get_the_category($post->ID);
171                         if ( $cats ) {
172                                 $cats = wp_list_sort( $cats, array(
173                                         'term_id' => 'ASC',
174                                 ) );
175
176                                 /**
177                                  * Filters the category that gets used in the %category% permalink token.
178                                  *
179                                  * @since 3.5.0
180                                  *
181                                  * @param WP_Term  $cat  The category to use in the permalink.
182                                  * @param array    $cats Array of all categories (WP_Term objects) associated with the post.
183                                  * @param WP_Post  $post The post in question.
184                                  */
185                                 $category_object = apply_filters( 'post_link_category', $cats[0], $cats, $post );
186
187                                 $category_object = get_term( $category_object, 'category' );
188                                 $category = $category_object->slug;
189                                 if ( $parent = $category_object->parent )
190                                         $category = get_category_parents($parent, false, '/', true) . $category;
191                         }
192                         // show default category in permalinks, without
193                         // having to assign it explicitly
194                         if ( empty($category) ) {
195                                 $default_category = get_term( get_option( 'default_category' ), 'category' );
196                                 if ( $default_category && ! is_wp_error( $default_category ) ) {
197                                         $category = $default_category->slug;
198                                 }
199                         }
200                 }
201
202                 $author = '';
203                 if ( strpos($permalink, '%author%') !== false ) {
204                         $authordata = get_userdata($post->post_author);
205                         $author = $authordata->user_nicename;
206                 }
207
208                 $date = explode(" ",date('Y m d H i s', $unixtime));
209                 $rewritereplace =
210                 array(
211                         $date[0],
212                         $date[1],
213                         $date[2],
214                         $date[3],
215                         $date[4],
216                         $date[5],
217                         $post->post_name,
218                         $post->ID,
219                         $category,
220                         $author,
221                         $post->post_name,
222                 );
223                 $permalink = home_url( str_replace($rewritecode, $rewritereplace, $permalink) );
224                 $permalink = user_trailingslashit($permalink, 'single');
225         } else { // if they're not using the fancy permalink option
226                 $permalink = home_url('?p=' . $post->ID);
227         }
228
229         /**
230          * Filters the permalink for a post.
231          *
232          * Only applies to posts with post_type of 'post'.
233          *
234          * @since 1.5.0
235          *
236          * @param string  $permalink The post's permalink.
237          * @param WP_Post $post      The post in question.
238          * @param bool    $leavename Whether to keep the post name.
239          */
240         return apply_filters( 'post_link', $permalink, $post, $leavename );
241 }
242
243 /**
244  * Retrieves the permalink for a post of a custom post type.
245  *
246  * @since 3.0.0
247  *
248  * @global WP_Rewrite $wp_rewrite
249  *
250  * @param int $id         Optional. Post ID. Default uses the global `$post`.
251  * @param bool $leavename Optional, defaults to false. Whether to keep post name. Default false.
252  * @param bool $sample    Optional, defaults to false. Is it a sample permalink. Default false.
253  * @return string|WP_Error The post permalink.
254  */
255 function get_post_permalink( $id = 0, $leavename = false, $sample = false ) {
256         global $wp_rewrite;
257
258         $post = get_post($id);
259
260         if ( is_wp_error( $post ) )
261                 return $post;
262
263         $post_link = $wp_rewrite->get_extra_permastruct($post->post_type);
264
265         $slug = $post->post_name;
266
267         $draft_or_pending = get_post_status( $id ) && in_array( get_post_status( $id ), array( 'draft', 'pending', 'auto-draft', 'future' ) );
268
269         $post_type = get_post_type_object($post->post_type);
270
271         if ( $post_type->hierarchical ) {
272                 $slug = get_page_uri( $id );
273         }
274
275         if ( !empty($post_link) && ( !$draft_or_pending || $sample ) ) {
276                 if ( ! $leavename ) {
277                         $post_link = str_replace("%$post->post_type%", $slug, $post_link);
278                 }
279                 $post_link = home_url( user_trailingslashit($post_link) );
280         } else {
281                 if ( $post_type->query_var && ( isset($post->post_status) && !$draft_or_pending ) )
282                         $post_link = add_query_arg($post_type->query_var, $slug, '');
283                 else
284                         $post_link = add_query_arg(array('post_type' => $post->post_type, 'p' => $post->ID), '');
285                 $post_link = home_url($post_link);
286         }
287
288         /**
289          * Filters the permalink for a post of a custom post type.
290          *
291          * @since 3.0.0
292          *
293          * @param string  $post_link The post's permalink.
294          * @param WP_Post $post      The post in question.
295          * @param bool    $leavename Whether to keep the post name.
296          * @param bool    $sample    Is it a sample permalink.
297          */
298         return apply_filters( 'post_type_link', $post_link, $post, $leavename, $sample );
299 }
300
301 /**
302  * Retrieves the permalink for the current page or page ID.
303  *
304  * Respects page_on_front. Use this one.
305  *
306  * @since 1.5.0
307  *
308  * @param int|WP_Post $post      Optional. Post ID or object. Default uses the global `$post`.
309  * @param bool        $leavename Optional. Whether to keep the page name. Default false.
310  * @param bool        $sample    Optional. Whether it should be treated as a sample permalink.
311  *                               Default false.
312  * @return string The page permalink.
313  */
314 function get_page_link( $post = false, $leavename = false, $sample = false ) {
315         $post = get_post( $post );
316
317         if ( 'page' == get_option( 'show_on_front' ) && $post->ID == get_option( 'page_on_front' ) )
318                 $link = home_url('/');
319         else
320                 $link = _get_page_link( $post, $leavename, $sample );
321
322         /**
323          * Filters the permalink for a page.
324          *
325          * @since 1.5.0
326          *
327          * @param string $link    The page's permalink.
328          * @param int    $post_id The ID of the page.
329          * @param bool   $sample  Is it a sample permalink.
330          */
331         return apply_filters( 'page_link', $link, $post->ID, $sample );
332 }
333
334 /**
335  * Retrieves the page permalink.
336  *
337  * Ignores page_on_front. Internal use only.
338  *
339  * @since 2.1.0
340  * @access private
341  *
342  * @global WP_Rewrite $wp_rewrite
343  *
344  * @param int|WP_Post $post      Optional. Post ID or object. Default uses the global `$post`.
345  * @param bool        $leavename Optional. Whether to keep the page name. Default false.
346  * @param bool        $sample    Optional. Whether it should be treated as a sample permalink.
347  *                               Default false.
348  * @return string The page permalink.
349  */
350 function _get_page_link( $post = false, $leavename = false, $sample = false ) {
351         global $wp_rewrite;
352
353         $post = get_post( $post );
354
355         $draft_or_pending = in_array( $post->post_status, array( 'draft', 'pending', 'auto-draft' ) );
356
357         $link = $wp_rewrite->get_page_permastruct();
358
359         if ( !empty($link) && ( ( isset($post->post_status) && !$draft_or_pending ) || $sample ) ) {
360                 if ( ! $leavename ) {
361                         $link = str_replace('%pagename%', get_page_uri( $post ), $link);
362                 }
363
364                 $link = home_url($link);
365                 $link = user_trailingslashit($link, 'page');
366         } else {
367                 $link = home_url( '?page_id=' . $post->ID );
368         }
369
370         /**
371          * Filters the permalink for a non-page_on_front page.
372          *
373          * @since 2.1.0
374          *
375          * @param string $link    The page's permalink.
376          * @param int    $post_id The ID of the page.
377          */
378         return apply_filters( '_get_page_link', $link, $post->ID );
379 }
380
381 /**
382  * Retrieves the permalink for an attachment.
383  *
384  * This can be used in the WordPress Loop or outside of it.
385  *
386  * @since 2.0.0
387  *
388  * @global WP_Rewrite $wp_rewrite
389  *
390  * @param int|object $post      Optional. Post ID or object. Default uses the global `$post`.
391  * @param bool       $leavename Optional. Whether to keep the page name. Default false.
392  * @return string The attachment permalink.
393  */
394 function get_attachment_link( $post = null, $leavename = false ) {
395         global $wp_rewrite;
396
397         $link = false;
398
399         $post = get_post( $post );
400         $parent = ( $post->post_parent > 0 && $post->post_parent != $post->ID ) ? get_post( $post->post_parent ) : false;
401         if ( $parent && ! in_array( $parent->post_type, get_post_types() ) ) {
402                 $parent = false;
403         }
404
405         if ( $wp_rewrite->using_permalinks() && $parent ) {
406                 if ( 'page' == $parent->post_type )
407                         $parentlink = _get_page_link( $post->post_parent ); // Ignores page_on_front
408                 else
409                         $parentlink = get_permalink( $post->post_parent );
410
411                 if ( is_numeric($post->post_name) || false !== strpos(get_option('permalink_structure'), '%category%') )
412                         $name = 'attachment/' . $post->post_name; // <permalink>/<int>/ is paged so we use the explicit attachment marker
413                 else
414                         $name = $post->post_name;
415
416                 if ( strpos($parentlink, '?') === false )
417                         $link = user_trailingslashit( trailingslashit($parentlink) . '%postname%' );
418
419                 if ( ! $leavename )
420                         $link = str_replace( '%postname%', $name, $link );
421         } elseif ( $wp_rewrite->using_permalinks() && ! $leavename ) {
422                 $link = home_url( user_trailingslashit( $post->post_name ) );
423         }
424
425         if ( ! $link )
426                 $link = home_url( '/?attachment_id=' . $post->ID );
427
428         /**
429          * Filters the permalink for an attachment.
430          *
431          * @since 2.0.0
432          *
433          * @param string $link    The attachment's permalink.
434          * @param int    $post_id Attachment ID.
435          */
436         return apply_filters( 'attachment_link', $link, $post->ID );
437 }
438
439 /**
440  * Retrieves the permalink for the year archives.
441  *
442  * @since 1.5.0
443  *
444  * @global WP_Rewrite $wp_rewrite
445  *
446  * @param int|bool $year False for current year or year for permalink.
447  * @return string The permalink for the specified year archive.
448  */
449 function get_year_link( $year ) {
450         global $wp_rewrite;
451         if ( !$year )
452                 $year = gmdate('Y', current_time('timestamp'));
453         $yearlink = $wp_rewrite->get_year_permastruct();
454         if ( !empty($yearlink) ) {
455                 $yearlink = str_replace('%year%', $year, $yearlink);
456                 $yearlink = home_url( user_trailingslashit( $yearlink, 'year' ) );
457         } else {
458                 $yearlink = home_url( '?m=' . $year );
459         }
460
461         /**
462          * Filters the year archive permalink.
463          *
464          * @since 1.5.0
465          *
466          * @param string $yearlink Permalink for the year archive.
467          * @param int    $year     Year for the archive.
468          */
469         return apply_filters( 'year_link', $yearlink, $year );
470 }
471
472 /**
473  * Retrieves the permalink for the month archives with year.
474  *
475  * @since 1.0.0
476  *
477  * @global WP_Rewrite $wp_rewrite
478  *
479  * @param bool|int $year  False for current year. Integer of year.
480  * @param bool|int $month False for current month. Integer of month.
481  * @return string The permalink for the specified month and year archive.
482  */
483 function get_month_link($year, $month) {
484         global $wp_rewrite;
485         if ( !$year )
486                 $year = gmdate('Y', current_time('timestamp'));
487         if ( !$month )
488                 $month = gmdate('m', current_time('timestamp'));
489         $monthlink = $wp_rewrite->get_month_permastruct();
490         if ( !empty($monthlink) ) {
491                 $monthlink = str_replace('%year%', $year, $monthlink);
492                 $monthlink = str_replace('%monthnum%', zeroise(intval($month), 2), $monthlink);
493                 $monthlink = home_url( user_trailingslashit( $monthlink, 'month' ) );
494         } else {
495                 $monthlink = home_url( '?m=' . $year . zeroise( $month, 2 ) );
496         }
497
498         /**
499          * Filters the month archive permalink.
500          *
501          * @since 1.5.0
502          *
503          * @param string $monthlink Permalink for the month archive.
504          * @param int    $year      Year for the archive.
505          * @param int    $month     The month for the archive.
506          */
507         return apply_filters( 'month_link', $monthlink, $year, $month );
508 }
509
510 /**
511  * Retrieves the permalink for the day archives with year and month.
512  *
513  * @since 1.0.0
514  *
515  * @global WP_Rewrite $wp_rewrite
516  *
517  * @param bool|int $year  False for current year. Integer of year.
518  * @param bool|int $month False for current month. Integer of month.
519  * @param bool|int $day   False for current day. Integer of day.
520  * @return string The permalink for the specified day, month, and year archive.
521  */
522 function get_day_link($year, $month, $day) {
523         global $wp_rewrite;
524         if ( !$year )
525                 $year = gmdate('Y', current_time('timestamp'));
526         if ( !$month )
527                 $month = gmdate('m', current_time('timestamp'));
528         if ( !$day )
529                 $day = gmdate('j', current_time('timestamp'));
530
531         $daylink = $wp_rewrite->get_day_permastruct();
532         if ( !empty($daylink) ) {
533                 $daylink = str_replace('%year%', $year, $daylink);
534                 $daylink = str_replace('%monthnum%', zeroise(intval($month), 2), $daylink);
535                 $daylink = str_replace('%day%', zeroise(intval($day), 2), $daylink);
536                 $daylink = home_url( user_trailingslashit( $daylink, 'day' ) );
537         } else {
538                 $daylink = home_url( '?m=' . $year . zeroise( $month, 2 ) . zeroise( $day, 2 ) );
539         }
540
541         /**
542          * Filters the day archive permalink.
543          *
544          * @since 1.5.0
545          *
546          * @param string $daylink Permalink for the day archive.
547          * @param int    $year    Year for the archive.
548          * @param int    $month   Month for the archive.
549          * @param int    $day     The day for the archive.
550          */
551         return apply_filters( 'day_link', $daylink, $year, $month, $day );
552 }
553
554 /**
555  * Displays the permalink for the feed type.
556  *
557  * @since 3.0.0
558  *
559  * @param string $anchor The link's anchor text.
560  * @param string $feed   Optional. Feed type. Default empty.
561  */
562 function the_feed_link( $anchor, $feed = '' ) {
563         $link = '<a href="' . esc_url( get_feed_link( $feed ) ) . '">' . $anchor . '</a>';
564
565         /**
566          * Filters the feed link anchor tag.
567          *
568          * @since 3.0.0
569          *
570          * @param string $link The complete anchor tag for a feed link.
571          * @param string $feed The feed type, or an empty string for the
572          *                     default feed type.
573          */
574         echo apply_filters( 'the_feed_link', $link, $feed );
575 }
576
577 /**
578  * Retrieves the permalink for the feed type.
579  *
580  * @since 1.5.0
581  *
582  * @global WP_Rewrite $wp_rewrite
583  *
584  * @param string $feed Optional. Feed type. Default empty.
585  * @return string The feed permalink.
586  */
587 function get_feed_link( $feed = '' ) {
588         global $wp_rewrite;
589
590         $permalink = $wp_rewrite->get_feed_permastruct();
591         if ( '' != $permalink ) {
592                 if ( false !== strpos($feed, 'comments_') ) {
593                         $feed = str_replace('comments_', '', $feed);
594                         $permalink = $wp_rewrite->get_comment_feed_permastruct();
595                 }
596
597                 if ( get_default_feed() == $feed )
598                         $feed = '';
599
600                 $permalink = str_replace('%feed%', $feed, $permalink);
601                 $permalink = preg_replace('#/+#', '/', "/$permalink");
602                 $output =  home_url( user_trailingslashit($permalink, 'feed') );
603         } else {
604                 if ( empty($feed) )
605                         $feed = get_default_feed();
606
607                 if ( false !== strpos($feed, 'comments_') )
608                         $feed = str_replace('comments_', 'comments-', $feed);
609
610                 $output = home_url("?feed={$feed}");
611         }
612
613         /**
614          * Filters the feed type permalink.
615          *
616          * @since 1.5.0
617          *
618          * @param string $output The feed permalink.
619          * @param string $feed   Feed type.
620          */
621         return apply_filters( 'feed_link', $output, $feed );
622 }
623
624 /**
625  * Retrieves the permalink for the post comments feed.
626  *
627  * @since 2.2.0
628  *
629  * @param int    $post_id Optional. Post ID. Default is the ID of the global `$post`.
630  * @param string $feed    Optional. Feed type. Default empty.
631  * @return string The permalink for the comments feed for the given post.
632  */
633 function get_post_comments_feed_link( $post_id = 0, $feed = '' ) {
634         $post_id = absint( $post_id );
635
636         if ( ! $post_id )
637                 $post_id = get_the_ID();
638
639         if ( empty( $feed ) )
640                 $feed = get_default_feed();
641
642         $post = get_post( $post_id );
643         $unattached = 'attachment' === $post->post_type && 0 === (int) $post->post_parent;
644
645         if ( '' != get_option('permalink_structure') ) {
646                 if ( 'page' == get_option('show_on_front') && $post_id == get_option('page_on_front') )
647                         $url = _get_page_link( $post_id );
648                 else
649                         $url = get_permalink($post_id);
650
651                 if ( $unattached ) {
652                         $url =  home_url( '/feed/' );
653                         if ( $feed !== get_default_feed() ) {
654                                 $url .= "$feed/";
655                         }
656                         $url = add_query_arg( 'attachment_id', $post_id, $url );
657                 } else {
658                         $url = trailingslashit($url) . 'feed';
659                         if ( $feed != get_default_feed() )
660                                 $url .= "/$feed";
661                         $url = user_trailingslashit($url, 'single_feed');
662                 }
663         } else {
664                 if ( $unattached ) {
665                         $url = add_query_arg( array( 'feed' => $feed, 'attachment_id' => $post_id ), home_url( '/' ) );
666                 } elseif ( 'page' == $post->post_type ) {
667                         $url = add_query_arg( array( 'feed' => $feed, 'page_id' => $post_id ), home_url( '/' ) );
668                 } else {
669                         $url = add_query_arg( array( 'feed' => $feed, 'p' => $post_id ), home_url( '/' ) );
670                 }
671         }
672
673         /**
674          * Filters the post comments feed permalink.
675          *
676          * @since 1.5.1
677          *
678          * @param string $url Post comments feed permalink.
679          */
680         return apply_filters( 'post_comments_feed_link', $url );
681 }
682
683 /**
684  * Displays the comment feed link for a post.
685  *
686  * Prints out the comment feed link for a post. Link text is placed in the
687  * anchor. If no link text is specified, default text is used. If no post ID is
688  * specified, the current post is used.
689  *
690  * @since 2.5.0
691  *
692  * @param string $link_text Optional. Descriptive link text. Default 'Comments Feed'.
693  * @param int    $post_id   Optional. Post ID. Default is the ID of the global `$post`.
694  * @param string $feed      Optional. Feed format. Default empty.
695  */
696 function post_comments_feed_link( $link_text = '', $post_id = '', $feed = '' ) {
697         $url = get_post_comments_feed_link( $post_id, $feed );
698         if ( empty( $link_text ) ) {
699                 $link_text = __('Comments Feed');
700         }
701
702         $link = '<a href="' . esc_url( $url ) . '">' . $link_text . '</a>';
703         /**
704          * Filters the post comment feed link anchor tag.
705          *
706          * @since 2.8.0
707          *
708          * @param string $link    The complete anchor tag for the comment feed link.
709          * @param int    $post_id Post ID.
710          * @param string $feed    The feed type, or an empty string for the default feed type.
711          */
712         echo apply_filters( 'post_comments_feed_link_html', $link, $post_id, $feed );
713 }
714
715 /**
716  * Retrieves the feed link for a given author.
717  *
718  * Returns a link to the feed for all posts by a given author. A specific feed
719  * can be requested or left blank to get the default feed.
720  *
721  * @since 2.5.0
722  *
723  * @param int    $author_id Author ID.
724  * @param string $feed      Optional. Feed type. Default empty.
725  * @return string Link to the feed for the author specified by $author_id.
726  */
727 function get_author_feed_link( $author_id, $feed = '' ) {
728         $author_id = (int) $author_id;
729         $permalink_structure = get_option('permalink_structure');
730
731         if ( empty($feed) )
732                 $feed = get_default_feed();
733
734         if ( '' == $permalink_structure ) {
735                 $link = home_url("?feed=$feed&amp;author=" . $author_id);
736         } else {
737                 $link = get_author_posts_url($author_id);
738                 if ( $feed == get_default_feed() )
739                         $feed_link = 'feed';
740                 else
741                         $feed_link = "feed/$feed";
742
743                 $link = trailingslashit($link) . user_trailingslashit($feed_link, 'feed');
744         }
745
746         /**
747          * Filters the feed link for a given author.
748          *
749          * @since 1.5.1
750          *
751          * @param string $link The author feed link.
752          * @param string $feed Feed type.
753          */
754         $link = apply_filters( 'author_feed_link', $link, $feed );
755
756         return $link;
757 }
758
759 /**
760  * Retrieves the feed link for a category.
761  *
762  * Returns a link to the feed for all posts in a given category. A specific feed
763  * can be requested or left blank to get the default feed.
764  *
765  * @since 2.5.0
766  *
767  * @param int    $cat_id Category ID.
768  * @param string $feed   Optional. Feed type. Default empty.
769  * @return string Link to the feed for the category specified by $cat_id.
770  */
771 function get_category_feed_link( $cat_id, $feed = '' ) {
772         return get_term_feed_link( $cat_id, 'category', $feed );
773 }
774
775 /**
776  * Retrieves the feed link for a term.
777  *
778  * Returns a link to the feed for all posts in a given term. A specific feed
779  * can be requested or left blank to get the default feed.
780  *
781  * @since 3.0.0
782  *
783  * @param int    $term_id  Term ID.
784  * @param string $taxonomy Optional. Taxonomy of `$term_id`. Default 'category'.
785  * @param string $feed     Optional. Feed type. Default empty.
786  * @return string|false Link to the feed for the term specified by $term_id and $taxonomy.
787  */
788 function get_term_feed_link( $term_id, $taxonomy = 'category', $feed = '' ) {
789         $term_id = ( int ) $term_id;
790
791         $term = get_term( $term_id, $taxonomy  );
792
793         if ( empty( $term ) || is_wp_error( $term ) )
794                 return false;
795
796         if ( empty( $feed ) )
797                 $feed = get_default_feed();
798
799         $permalink_structure = get_option( 'permalink_structure' );
800
801         if ( '' == $permalink_structure ) {
802                 if ( 'category' == $taxonomy ) {
803                         $link = home_url("?feed=$feed&amp;cat=$term_id");
804                 }
805                 elseif ( 'post_tag' == $taxonomy ) {
806                         $link = home_url("?feed=$feed&amp;tag=$term->slug");
807                 } else {
808                         $t = get_taxonomy( $taxonomy );
809                         $link = home_url("?feed=$feed&amp;$t->query_var=$term->slug");
810                 }
811         } else {
812                 $link = get_term_link( $term_id, $term->taxonomy );
813                 if ( $feed == get_default_feed() )
814                         $feed_link = 'feed';
815                 else
816                         $feed_link = "feed/$feed";
817
818                 $link = trailingslashit( $link ) . user_trailingslashit( $feed_link, 'feed' );
819         }
820
821         if ( 'category' == $taxonomy ) {
822                 /**
823                  * Filters the category feed link.
824                  *
825                  * @since 1.5.1
826                  *
827                  * @param string $link The category feed link.
828                  * @param string $feed Feed type.
829                  */
830                 $link = apply_filters( 'category_feed_link', $link, $feed );
831         } elseif ( 'post_tag' == $taxonomy ) {
832                 /**
833                  * Filters the post tag feed link.
834                  *
835                  * @since 2.3.0
836                  *
837                  * @param string $link The tag feed link.
838                  * @param string $feed Feed type.
839                  */
840                 $link = apply_filters( 'tag_feed_link', $link, $feed );
841         } else {
842                 /**
843                  * Filters the feed link for a taxonomy other than 'category' or 'post_tag'.
844                  *
845                  * @since 3.0.0
846                  *
847                  * @param string $link The taxonomy feed link.
848                  * @param string $feed Feed type.
849                  * @param string $feed The taxonomy name.
850                  */
851                 $link = apply_filters( 'taxonomy_feed_link', $link, $feed, $taxonomy );
852         }
853
854         return $link;
855 }
856
857 /**
858  * Retrieves the permalink for a tag feed.
859  *
860  * @since 2.3.0
861  *
862  * @param int    $tag_id Tag ID.
863  * @param string $feed   Optional. Feed type. Default empty.
864  * @return string The feed permalink for the given tag.
865  */
866 function get_tag_feed_link( $tag_id, $feed = '' ) {
867         return get_term_feed_link( $tag_id, 'post_tag', $feed );
868 }
869
870 /**
871  * Retrieves the edit link for a tag.
872  *
873  * @since 2.7.0
874  *
875  * @param int    $tag_id   Tag ID.
876  * @param string $taxonomy Optional. Taxonomy slug. Default 'post_tag'.
877  * @return string The edit tag link URL for the given tag.
878  */
879 function get_edit_tag_link( $tag_id, $taxonomy = 'post_tag' ) {
880         /**
881          * Filters the edit link for a tag (or term in another taxonomy).
882          *
883          * @since 2.7.0
884          *
885          * @param string $link The term edit link.
886          */
887         return apply_filters( 'get_edit_tag_link', get_edit_term_link( $tag_id, $taxonomy ) );
888 }
889
890 /**
891  * Displays or retrieves the edit link for a tag with formatting.
892  *
893  * @since 2.7.0
894  *
895  * @param string  $link   Optional. Anchor text. Default empty.
896  * @param string  $before Optional. Display before edit link. Default empty.
897  * @param string  $after  Optional. Display after edit link. Default empty.
898  * @param WP_Term $tag    Optional. Term object. If null, the queried object will be inspected.
899  *                        Default null.
900  */
901 function edit_tag_link( $link = '', $before = '', $after = '', $tag = null ) {
902         $link = edit_term_link( $link, '', '', $tag, false );
903
904         /**
905          * Filters the anchor tag for the edit link for a tag (or term in another taxonomy).
906          *
907          * @since 2.7.0
908          *
909          * @param string $link The anchor tag for the edit link.
910          */
911         echo $before . apply_filters( 'edit_tag_link', $link ) . $after;
912 }
913
914 /**
915  * Retrieves the URL for editing a given term.
916  *
917  * @since 3.1.0
918  * @since 4.5.0 The `$taxonomy` argument was made optional.
919  *
920  * @param int    $term_id     Term ID.
921  * @param string $taxonomy    Optional. Taxonomy. Defaults to the taxonomy of the term identified
922  *                            by `$term_id`.
923  * @param string $object_type Optional. The object type. Used to highlight the proper post type
924  *                            menu on the linked page. Defaults to the first object_type associated
925  *                            with the taxonomy.
926  * @return string|null The edit term link URL for the given term, or null on failure.
927  */
928 function get_edit_term_link( $term_id, $taxonomy = '', $object_type = '' ) {
929         $term = get_term( $term_id, $taxonomy );
930         if ( ! $term || is_wp_error( $term ) ) {
931                 return;
932         }
933
934         $tax = get_taxonomy( $term->taxonomy );
935         if ( ! $tax || ! current_user_can( 'edit_term', $term->term_id ) ) {
936                 return;
937         }
938
939         $args = array(
940                 'taxonomy' => $taxonomy,
941                 'tag_ID'   => $term->term_id,
942         );
943
944         if ( $object_type ) {
945                 $args['post_type'] = $object_type;
946         } elseif ( ! empty( $tax->object_type ) ) {
947                 $args['post_type'] = reset( $tax->object_type );
948         }
949
950         if ( $tax->show_ui ) {
951                 $location = add_query_arg( $args, admin_url( 'term.php' ) );
952         } else {
953                 $location = '';
954         }
955
956         /**
957          * Filters the edit link for a term.
958          *
959          * @since 3.1.0
960          *
961          * @param string $location    The edit link.
962          * @param int    $term_id     Term ID.
963          * @param string $taxonomy    Taxonomy name.
964          * @param string $object_type The object type (eg. the post type).
965          */
966         return apply_filters( 'get_edit_term_link', $location, $term_id, $taxonomy, $object_type );
967 }
968
969 /**
970  * Displays or retrieves the edit term link with formatting.
971  *
972  * @since 3.1.0
973  *
974  * @param string $link   Optional. Anchor text. Default empty.
975  * @param string $before Optional. Display before edit link. Default empty.
976  * @param string $after  Optional. Display after edit link. Default empty.
977  * @param object $term   Optional. Term object. If null, the queried object will be inspected. Default null.
978  * @param bool   $echo   Optional. Whether or not to echo the return. Default true.
979  * @return string|void HTML content.
980  */
981 function edit_term_link( $link = '', $before = '', $after = '', $term = null, $echo = true ) {
982         if ( is_null( $term ) )
983                 $term = get_queried_object();
984
985         if ( ! $term )
986                 return;
987
988         $tax = get_taxonomy( $term->taxonomy );
989         if ( ! current_user_can( 'edit_term', $term->term_id ) ) {
990                 return;
991         }
992
993         if ( empty( $link ) )
994                 $link = __('Edit This');
995
996         $link = '<a href="' . get_edit_term_link( $term->term_id, $term->taxonomy ) . '">' . $link . '</a>';
997
998         /**
999          * Filters the anchor tag for the edit link of a term.
1000          *
1001          * @since 3.1.0
1002          *
1003          * @param string $link    The anchor tag for the edit link.
1004          * @param int    $term_id Term ID.
1005          */
1006         $link = $before . apply_filters( 'edit_term_link', $link, $term->term_id ) . $after;
1007
1008         if ( $echo )
1009                 echo $link;
1010         else
1011                 return $link;
1012 }
1013
1014 /**
1015  * Retrieves the permalink for a search.
1016  *
1017  * @since  3.0.0
1018  *
1019  * @global WP_Rewrite $wp_rewrite
1020  *
1021  * @param string $query Optional. The query string to use. If empty the current query is used. Default empty.
1022  * @return string The search permalink.
1023  */
1024 function get_search_link( $query = '' ) {
1025         global $wp_rewrite;
1026
1027         if ( empty($query) )
1028                 $search = get_search_query( false );
1029         else
1030                 $search = stripslashes($query);
1031
1032         $permastruct = $wp_rewrite->get_search_permastruct();
1033
1034         if ( empty( $permastruct ) ) {
1035                 $link = home_url('?s=' . urlencode($search) );
1036         } else {
1037                 $search = urlencode($search);
1038                 $search = str_replace('%2F', '/', $search); // %2F(/) is not valid within a URL, send it un-encoded.
1039                 $link = str_replace( '%search%', $search, $permastruct );
1040                 $link = home_url( user_trailingslashit( $link, 'search' ) );
1041         }
1042
1043         /**
1044          * Filters the search permalink.
1045          *
1046          * @since 3.0.0
1047          *
1048          * @param string $link   Search permalink.
1049          * @param string $search The URL-encoded search term.
1050          */
1051         return apply_filters( 'search_link', $link, $search );
1052 }
1053
1054 /**
1055  * Retrieves the permalink for the search results feed.
1056  *
1057  * @since 2.5.0
1058  *
1059  * @global WP_Rewrite $wp_rewrite
1060  *
1061  * @param string $search_query Optional. Search query. Default empty.
1062  * @param string $feed         Optional. Feed type. Default empty.
1063  * @return string The search results feed permalink.
1064  */
1065 function get_search_feed_link($search_query = '', $feed = '') {
1066         global $wp_rewrite;
1067         $link = get_search_link($search_query);
1068
1069         if ( empty($feed) )
1070                 $feed = get_default_feed();
1071
1072         $permastruct = $wp_rewrite->get_search_permastruct();
1073
1074         if ( empty($permastruct) ) {
1075                 $link = add_query_arg('feed', $feed, $link);
1076         } else {
1077                 $link = trailingslashit($link);
1078                 $link .= "feed/$feed/";
1079         }
1080
1081         /**
1082          * Filters the search feed link.
1083          *
1084          * @since 2.5.0
1085          *
1086          * @param string $link Search feed link.
1087          * @param string $feed Feed type.
1088          * @param string $type The search type. One of 'posts' or 'comments'.
1089          */
1090         return apply_filters( 'search_feed_link', $link, $feed, 'posts' );
1091 }
1092
1093 /**
1094  * Retrieves the permalink for the search results comments feed.
1095  *
1096  * @since 2.5.0
1097  *
1098  * @global WP_Rewrite $wp_rewrite
1099  *
1100  * @param string $search_query Optional. Search query. Default empty.
1101  * @param string $feed         Optional. Feed type. Default empty.
1102  * @return string The comments feed search results permalink.
1103  */
1104 function get_search_comments_feed_link($search_query = '', $feed = '') {
1105         global $wp_rewrite;
1106
1107         if ( empty($feed) )
1108                 $feed = get_default_feed();
1109
1110         $link = get_search_feed_link($search_query, $feed);
1111
1112         $permastruct = $wp_rewrite->get_search_permastruct();
1113
1114         if ( empty($permastruct) )
1115                 $link = add_query_arg('feed', 'comments-' . $feed, $link);
1116         else
1117                 $link = add_query_arg('withcomments', 1, $link);
1118
1119         /** This filter is documented in wp-includes/link-template.php */
1120         return apply_filters( 'search_feed_link', $link, $feed, 'comments' );
1121 }
1122
1123 /**
1124  * Retrieves the permalink for a post type archive.
1125  *
1126  * @since 3.1.0
1127  * @since 4.5.0 Support for posts was added.
1128  *
1129  * @global WP_Rewrite $wp_rewrite
1130  *
1131  * @param string $post_type Post type.
1132  * @return string|false The post type archive permalink.
1133  */
1134 function get_post_type_archive_link( $post_type ) {
1135         global $wp_rewrite;
1136         if ( ! $post_type_obj = get_post_type_object( $post_type ) )
1137                 return false;
1138
1139         if ( 'post' === $post_type ) {
1140                 $show_on_front = get_option( 'show_on_front' );
1141                 $page_for_posts  = get_option( 'page_for_posts' );
1142
1143                 if ( 'page' == $show_on_front && $page_for_posts ) {
1144                         $link = get_permalink( $page_for_posts );
1145                 } else {
1146                         $link = get_home_url();
1147                 }
1148                 /** This filter is documented in wp-includes/link-template.php */
1149                 return apply_filters( 'post_type_archive_link', $link, $post_type );
1150         }
1151
1152         if ( ! $post_type_obj->has_archive )
1153                 return false;
1154
1155         if ( get_option( 'permalink_structure' ) && is_array( $post_type_obj->rewrite ) ) {
1156                 $struct = ( true === $post_type_obj->has_archive ) ? $post_type_obj->rewrite['slug'] : $post_type_obj->has_archive;
1157                 if ( $post_type_obj->rewrite['with_front'] )
1158                         $struct = $wp_rewrite->front . $struct;
1159                 else
1160                         $struct = $wp_rewrite->root . $struct;
1161                 $link = home_url( user_trailingslashit( $struct, 'post_type_archive' ) );
1162         } else {
1163                 $link = home_url( '?post_type=' . $post_type );
1164         }
1165
1166         /**
1167          * Filters the post type archive permalink.
1168          *
1169          * @since 3.1.0
1170          *
1171          * @param string $link      The post type archive permalink.
1172          * @param string $post_type Post type name.
1173          */
1174         return apply_filters( 'post_type_archive_link', $link, $post_type );
1175 }
1176
1177 /**
1178  * Retrieves the permalink for a post type archive feed.
1179  *
1180  * @since 3.1.0
1181  *
1182  * @param string $post_type Post type
1183  * @param string $feed      Optional. Feed type. Default empty.
1184  * @return string|false The post type feed permalink.
1185  */
1186 function get_post_type_archive_feed_link( $post_type, $feed = '' ) {
1187         $default_feed = get_default_feed();
1188         if ( empty( $feed ) )
1189                 $feed = $default_feed;
1190
1191         if ( ! $link = get_post_type_archive_link( $post_type ) )
1192                 return false;
1193
1194         $post_type_obj = get_post_type_object( $post_type );
1195         if ( get_option( 'permalink_structure' ) && is_array( $post_type_obj->rewrite ) && $post_type_obj->rewrite['feeds'] ) {
1196                 $link = trailingslashit( $link );
1197                 $link .= 'feed/';
1198                 if ( $feed != $default_feed )
1199                         $link .= "$feed/";
1200         } else {
1201                 $link = add_query_arg( 'feed', $feed, $link );
1202         }
1203
1204         /**
1205          * Filters the post type archive feed link.
1206          *
1207          * @since 3.1.0
1208          *
1209          * @param string $link The post type archive feed link.
1210          * @param string $feed Feed type.
1211          */
1212         return apply_filters( 'post_type_archive_feed_link', $link, $feed );
1213 }
1214
1215 /**
1216  * Retrieves the URL used for the post preview.
1217  *
1218  * Allows additional query args to be appended.
1219  *
1220  * @since 4.4.0
1221  *
1222  * @param int|WP_Post $post         Optional. Post ID or `WP_Post` object. Defaults to global `$post`.
1223  * @param array       $query_args   Optional. Array of additional query args to be appended to the link.
1224  *                                  Default empty array.
1225  * @param string      $preview_link Optional. Base preview link to be used if it should differ from the
1226  *                                  post permalink. Default empty.
1227  * @return string|null URL used for the post preview, or null if the post does not exist.
1228  */
1229 function get_preview_post_link( $post = null, $query_args = array(), $preview_link = '' ) {
1230         $post = get_post( $post );
1231         if ( ! $post ) {
1232                 return;
1233         }
1234
1235         $post_type_object = get_post_type_object( $post->post_type );
1236         if ( is_post_type_viewable( $post_type_object ) ) {
1237                 if ( ! $preview_link ) {
1238                         $preview_link = set_url_scheme( get_permalink( $post ) );
1239                 }
1240
1241                 $query_args['preview'] = 'true';
1242                 $preview_link = add_query_arg( $query_args, $preview_link );
1243         }
1244
1245         /**
1246          * Filters the URL used for a post preview.
1247          *
1248          * @since 2.0.5
1249          * @since 4.0.0 Added the `$post` parameter.
1250          *
1251          * @param string  $preview_link URL used for the post preview.
1252          * @param WP_Post $post         Post object.
1253          */
1254         return apply_filters( 'preview_post_link', $preview_link, $post );
1255 }
1256
1257 /**
1258  * Retrieves the edit post link for post.
1259  *
1260  * Can be used within the WordPress loop or outside of it. Can be used with
1261  * pages, posts, attachments, and revisions.
1262  *
1263  * @since 2.3.0
1264  *
1265  * @param int    $id      Optional. Post ID. Default is the ID of the global `$post`.
1266  * @param string $context Optional. How to output the '&' character. Default '&amp;'.
1267  * @return string|null The edit post link for the given post. null if the post type is invalid or does
1268  *                     not allow an editing UI.
1269  */
1270 function get_edit_post_link( $id = 0, $context = 'display' ) {
1271         if ( ! $post = get_post( $id ) )
1272                 return;
1273
1274         if ( 'revision' === $post->post_type )
1275                 $action = '';
1276         elseif ( 'display' == $context )
1277                 $action = '&amp;action=edit';
1278         else
1279                 $action = '&action=edit';
1280
1281         $post_type_object = get_post_type_object( $post->post_type );
1282         if ( !$post_type_object )
1283                 return;
1284
1285         if ( !current_user_can( 'edit_post', $post->ID ) )
1286                 return;
1287
1288         if ( $post_type_object->_edit_link ) {
1289                 $link = admin_url( sprintf( $post_type_object->_edit_link . $action, $post->ID ) );
1290         } else {
1291                 $link = '';
1292         }
1293
1294         /**
1295          * Filters the post edit link.
1296          *
1297          * @since 2.3.0
1298          *
1299          * @param string $link    The edit link.
1300          * @param int    $post_id Post ID.
1301          * @param string $context The link context. If set to 'display' then ampersands
1302          *                        are encoded.
1303          */
1304         return apply_filters( 'get_edit_post_link', $link, $post->ID, $context );
1305 }
1306
1307 /**
1308  * Displays the edit post link for post.
1309  *
1310  * @since 1.0.0
1311  * @since 4.4.0 The `$class` argument was added.
1312  *
1313  * @param string $text   Optional. Anchor text. If null, default is 'Edit This'. Default null.
1314  * @param string $before Optional. Display before edit link. Default empty.
1315  * @param string $after  Optional. Display after edit link. Default empty.
1316  * @param int    $id     Optional. Post ID. Default is the ID of the global `$post`.
1317  * @param string $class  Optional. Add custom class to link. Default 'post-edit-link'.
1318  */
1319 function edit_post_link( $text = null, $before = '', $after = '', $id = 0, $class = 'post-edit-link' ) {
1320         if ( ! $post = get_post( $id ) ) {
1321                 return;
1322         }
1323
1324         if ( ! $url = get_edit_post_link( $post->ID ) ) {
1325                 return;
1326         }
1327
1328         if ( null === $text ) {
1329                 $text = __( 'Edit This' );
1330         }
1331
1332         $link = '<a class="' . esc_attr( $class ) . '" href="' . esc_url( $url ) . '">' . $text . '</a>';
1333
1334         /**
1335          * Filters the post edit link anchor tag.
1336          *
1337          * @since 2.3.0
1338          *
1339          * @param string $link    Anchor tag for the edit link.
1340          * @param int    $post_id Post ID.
1341          * @param string $text    Anchor text.
1342          */
1343         echo $before . apply_filters( 'edit_post_link', $link, $post->ID, $text ) . $after;
1344 }
1345
1346 /**
1347  * Retrieves the delete posts link for post.
1348  *
1349  * Can be used within the WordPress loop or outside of it, with any post type.
1350  *
1351  * @since 2.9.0
1352  *
1353  * @param int    $id           Optional. Post ID. Default is the ID of the global `$post`.
1354  * @param string $deprecated   Not used.
1355  * @param bool   $force_delete Optional. Whether to bypass trash and force deletion. Default false.
1356  * @return string|void The delete post link URL for the given post.
1357  */
1358 function get_delete_post_link( $id = 0, $deprecated = '', $force_delete = false ) {
1359         if ( ! empty( $deprecated ) )
1360                 _deprecated_argument( __FUNCTION__, '3.0.0' );
1361
1362         if ( !$post = get_post( $id ) )
1363                 return;
1364
1365         $post_type_object = get_post_type_object( $post->post_type );
1366         if ( !$post_type_object )
1367                 return;
1368
1369         if ( !current_user_can( 'delete_post', $post->ID ) )
1370                 return;
1371
1372         $action = ( $force_delete || !EMPTY_TRASH_DAYS ) ? 'delete' : 'trash';
1373
1374         $delete_link = add_query_arg( 'action', $action, admin_url( sprintf( $post_type_object->_edit_link, $post->ID ) ) );
1375
1376         /**
1377          * Filters the post delete link.
1378          *
1379          * @since 2.9.0
1380          *
1381          * @param string $link         The delete link.
1382          * @param int    $post_id      Post ID.
1383          * @param bool   $force_delete Whether to bypass the trash and force deletion. Default false.
1384          */
1385         return apply_filters( 'get_delete_post_link', wp_nonce_url( $delete_link, "$action-post_{$post->ID}" ), $post->ID, $force_delete );
1386 }
1387
1388 /**
1389  * Retrieves the edit comment link.
1390  *
1391  * @since 2.3.0
1392  *
1393  * @param int|WP_Comment $comment_id Optional. Comment ID or WP_Comment object.
1394  * @return string|void The edit comment link URL for the given comment.
1395  */
1396 function get_edit_comment_link( $comment_id = 0 ) {
1397         $comment = get_comment( $comment_id );
1398
1399         if ( !current_user_can( 'edit_comment', $comment->comment_ID ) )
1400                 return;
1401
1402         $location = admin_url('comment.php?action=editcomment&amp;c=') . $comment->comment_ID;
1403
1404         /**
1405          * Filters the comment edit link.
1406          *
1407          * @since 2.3.0
1408          *
1409          * @param string $location The edit link.
1410          */
1411         return apply_filters( 'get_edit_comment_link', $location );
1412 }
1413
1414 /**
1415  * Displays the edit comment link with formatting.
1416  *
1417  * @since 1.0.0
1418  *
1419  * @param string $text   Optional. Anchor text. If null, default is 'Edit This'. Default null.
1420  * @param string $before Optional. Display before edit link. Default empty.
1421  * @param string $after  Optional. Display after edit link. Default empty.
1422  */
1423 function edit_comment_link( $text = null, $before = '', $after = '' ) {
1424         $comment = get_comment();
1425
1426         if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) ) {
1427                 return;
1428         }
1429
1430         if ( null === $text ) {
1431                 $text = __( 'Edit This' );
1432         }
1433
1434         $link = '<a class="comment-edit-link" href="' . esc_url( get_edit_comment_link( $comment ) ) . '">' . $text . '</a>';
1435
1436         /**
1437          * Filters the comment edit link anchor tag.
1438          *
1439          * @since 2.3.0
1440          *
1441          * @param string $link       Anchor tag for the edit link.
1442          * @param int    $comment_id Comment ID.
1443          * @param string $text       Anchor text.
1444          */
1445         echo $before . apply_filters( 'edit_comment_link', $link, $comment->comment_ID, $text ) . $after;
1446 }
1447
1448 /**
1449  * Displays the edit bookmark link.
1450  *
1451  * @since 2.7.0
1452  *
1453  * @param int|stdClass $link Optional. Bookmark ID. Default is the id of the current bookmark.
1454  * @return string|void The edit bookmark link URL.
1455  */
1456 function get_edit_bookmark_link( $link = 0 ) {
1457         $link = get_bookmark( $link );
1458
1459         if ( !current_user_can('manage_links') )
1460                 return;
1461
1462         $location = admin_url('link.php?action=edit&amp;link_id=') . $link->link_id;
1463
1464         /**
1465          * Filters the bookmark edit link.
1466          *
1467          * @since 2.7.0
1468          *
1469          * @param string $location The edit link.
1470          * @param int    $link_id  Bookmark ID.
1471          */
1472         return apply_filters( 'get_edit_bookmark_link', $location, $link->link_id );
1473 }
1474
1475 /**
1476  * Displays the edit bookmark link anchor content.
1477  *
1478  * @since 2.7.0
1479  *
1480  * @param string $link     Optional. Anchor text. Default empty.
1481  * @param string $before   Optional. Display before edit link. Default empty.
1482  * @param string $after    Optional. Display after edit link. Default empty.
1483  * @param int    $bookmark Optional. Bookmark ID. Default is the current bookmark.
1484  */
1485 function edit_bookmark_link( $link = '', $before = '', $after = '', $bookmark = null ) {
1486         $bookmark = get_bookmark($bookmark);
1487
1488         if ( !current_user_can('manage_links') )
1489                 return;
1490
1491         if ( empty($link) )
1492                 $link = __('Edit This');
1493
1494         $link = '<a href="' . esc_url( get_edit_bookmark_link( $bookmark ) ) . '">' . $link . '</a>';
1495
1496         /**
1497          * Filters the bookmark edit link anchor tag.
1498          *
1499          * @since 2.7.0
1500          *
1501          * @param string $link    Anchor tag for the edit link.
1502          * @param int    $link_id Bookmark ID.
1503          */
1504         echo $before . apply_filters( 'edit_bookmark_link', $link, $bookmark->link_id ) . $after;
1505 }
1506
1507 /**
1508  * Retrieves the edit user link.
1509  *
1510  * @since 3.5.0
1511  *
1512  * @param int $user_id Optional. User ID. Defaults to the current user.
1513  * @return string URL to edit user page or empty string.
1514  */
1515 function get_edit_user_link( $user_id = null ) {
1516         if ( ! $user_id )
1517                 $user_id = get_current_user_id();
1518
1519         if ( empty( $user_id ) || ! current_user_can( 'edit_user', $user_id ) )
1520                 return '';
1521
1522         $user = get_userdata( $user_id );
1523
1524         if ( ! $user )
1525                 return '';
1526
1527         if ( get_current_user_id() == $user->ID )
1528                 $link = get_edit_profile_url( $user->ID );
1529         else
1530                 $link = add_query_arg( 'user_id', $user->ID, self_admin_url( 'user-edit.php' ) );
1531
1532         /**
1533          * Filters the user edit link.
1534          *
1535          * @since 3.5.0
1536          *
1537          * @param string $link    The edit link.
1538          * @param int    $user_id User ID.
1539          */
1540         return apply_filters( 'get_edit_user_link', $link, $user->ID );
1541 }
1542
1543 // Navigation links
1544
1545 /**
1546  * Retrieves the previous post that is adjacent to the current post.
1547  *
1548  * @since 1.5.0
1549  *
1550  * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term. Default false.
1551  * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty.
1552  * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
1553  * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no
1554  *                             corresponding post exists.
1555  */
1556 function get_previous_post( $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
1557         return get_adjacent_post( $in_same_term, $excluded_terms, true, $taxonomy );
1558 }
1559
1560 /**
1561  * Retrieves the next post that is adjacent to the current post.
1562  *
1563  * @since 1.5.0
1564  *
1565  * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term. Default false.
1566  * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty.
1567  * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
1568  * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no
1569  *                             corresponding post exists.
1570  */
1571 function get_next_post( $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
1572         return get_adjacent_post( $in_same_term, $excluded_terms, false, $taxonomy );
1573 }
1574
1575 /**
1576  * Retrieves the adjacent post.
1577  *
1578  * Can either be next or previous post.
1579  *
1580  * @since 2.5.0
1581  *
1582  * @global wpdb $wpdb WordPress database abstraction object.
1583  *
1584  * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term. Default false.
1585  * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty.
1586  * @param bool         $previous       Optional. Whether to retrieve previous post. Default true
1587  * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
1588  * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no
1589  *                             corresponding post exists.
1590  */
1591 function get_adjacent_post( $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) {
1592         global $wpdb;
1593
1594         if ( ( ! $post = get_post() ) || ! taxonomy_exists( $taxonomy ) )
1595                 return null;
1596
1597         $current_post_date = $post->post_date;
1598
1599         $join = '';
1600         $where = '';
1601         $adjacent = $previous ? 'previous' : 'next';
1602
1603         if ( $in_same_term || ! empty( $excluded_terms ) ) {
1604                 if ( ! empty( $excluded_terms ) && ! is_array( $excluded_terms ) ) {
1605                         // back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and "
1606                         if ( false !== strpos( $excluded_terms, ' and ' ) ) {
1607                                 _deprecated_argument( __FUNCTION__, '3.3.0', sprintf( __( 'Use commas instead of %s to separate excluded terms.' ), "'and'" ) );
1608                                 $excluded_terms = explode( ' and ', $excluded_terms );
1609                         } else {
1610                                 $excluded_terms = explode( ',', $excluded_terms );
1611                         }
1612
1613                         $excluded_terms = array_map( 'intval', $excluded_terms );
1614                 }
1615
1616                 if ( $in_same_term ) {
1617                         $join .= " INNER JOIN $wpdb->term_relationships AS tr ON p.ID = tr.object_id INNER JOIN $wpdb->term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
1618                         $where .= $wpdb->prepare( "AND tt.taxonomy = %s", $taxonomy );
1619
1620                         if ( ! is_object_in_taxonomy( $post->post_type, $taxonomy ) )
1621                                 return '';
1622                         $term_array = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'ids' ) );
1623
1624                         // Remove any exclusions from the term array to include.
1625                         $term_array = array_diff( $term_array, (array) $excluded_terms );
1626                         $term_array = array_map( 'intval', $term_array );
1627
1628                         if ( ! $term_array || is_wp_error( $term_array ) )
1629                                 return '';
1630
1631                         $where .= " AND tt.term_id IN (" . implode( ',', $term_array ) . ")";
1632                 }
1633
1634                 /**
1635                  * Filters the IDs of terms excluded from adjacent post queries.
1636                  *
1637                  * The dynamic portion of the hook name, `$adjacent`, refers to the type
1638                  * of adjacency, 'next' or 'previous'.
1639                  *
1640                  * @since 4.4.0
1641                  *
1642                  * @param string $excluded_terms Array of excluded term IDs.
1643                  */
1644                 $excluded_terms = apply_filters( "get_{$adjacent}_post_excluded_terms", $excluded_terms );
1645
1646                 if ( ! empty( $excluded_terms ) ) {
1647                         $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM $wpdb->term_relationships tr LEFT JOIN $wpdb->term_taxonomy tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode( ',', array_map( 'intval', $excluded_terms ) ) . ') )';
1648                 }
1649         }
1650
1651         // 'post_status' clause depends on the current user.
1652         if ( is_user_logged_in() ) {
1653                 $user_id = get_current_user_id();
1654
1655                 $post_type_object = get_post_type_object( $post->post_type );
1656                 if ( empty( $post_type_object ) ) {
1657                         $post_type_cap    = $post->post_type;
1658                         $read_private_cap = 'read_private_' . $post_type_cap . 's';
1659                 } else {
1660                         $read_private_cap = $post_type_object->cap->read_private_posts;
1661                 }
1662
1663                 /*
1664                  * Results should include private posts belonging to the current user, or private posts where the
1665                  * current user has the 'read_private_posts' cap.
1666                  */
1667                 $private_states = get_post_stati( array( 'private' => true ) );
1668                 $where .= " AND ( p.post_status = 'publish'";
1669                 foreach ( (array) $private_states as $state ) {
1670                         if ( current_user_can( $read_private_cap ) ) {
1671                                 $where .= $wpdb->prepare( " OR p.post_status = %s", $state );
1672                         } else {
1673                                 $where .= $wpdb->prepare( " OR (p.post_author = %d AND p.post_status = %s)", $user_id, $state );
1674                         }
1675                 }
1676                 $where .= " )";
1677         } else {
1678                 $where .= " AND p.post_status = 'publish'";
1679         }
1680
1681         $op = $previous ? '<' : '>';
1682         $order = $previous ? 'DESC' : 'ASC';
1683
1684         /**
1685          * Filters the JOIN clause in the SQL for an adjacent post query.
1686          *
1687          * The dynamic portion of the hook name, `$adjacent`, refers to the type
1688          * of adjacency, 'next' or 'previous'.
1689          *
1690          * @since 2.5.0
1691          * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
1692          *
1693          * @param string  $join           The JOIN clause in the SQL.
1694          * @param bool    $in_same_term   Whether post should be in a same taxonomy term.
1695          * @param array   $excluded_terms Array of excluded term IDs.
1696          * @param string  $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
1697          * @param WP_Post $post           WP_Post object.
1698          */
1699         $join = apply_filters( "get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post );
1700
1701         /**
1702          * Filters the WHERE clause in the SQL for an adjacent post query.
1703          *
1704          * The dynamic portion of the hook name, `$adjacent`, refers to the type
1705          * of adjacency, 'next' or 'previous'.
1706          *
1707          * @since 2.5.0
1708          * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
1709          *
1710          * @param string $where          The `WHERE` clause in the SQL.
1711          * @param bool   $in_same_term   Whether post should be in a same taxonomy term.
1712          * @param array  $excluded_terms Array of excluded term IDs.
1713          * @param string $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
1714          * @param WP_Post $post           WP_Post object.
1715          */
1716         $where = apply_filters( "get_{$adjacent}_post_where", $wpdb->prepare( "WHERE p.post_date $op %s AND p.post_type = %s $where", $current_post_date, $post->post_type ), $in_same_term, $excluded_terms, $taxonomy, $post );
1717
1718         /**
1719          * Filters the ORDER BY clause in the SQL for an adjacent post query.
1720          *
1721          * The dynamic portion of the hook name, `$adjacent`, refers to the type
1722          * of adjacency, 'next' or 'previous'.
1723          *
1724          * @since 2.5.0
1725          * @since 4.4.0 Added the `$post` parameter.
1726          *
1727          * @param string $order_by The `ORDER BY` clause in the SQL.
1728          * @param WP_Post $post    WP_Post object.
1729          */
1730         $sort  = apply_filters( "get_{$adjacent}_post_sort", "ORDER BY p.post_date $order LIMIT 1", $post );
1731
1732         $query = "SELECT p.ID FROM $wpdb->posts AS p $join $where $sort";
1733         $query_key = 'adjacent_post_' . md5( $query );
1734         $result = wp_cache_get( $query_key, 'counts' );
1735         if ( false !== $result ) {
1736                 if ( $result )
1737                         $result = get_post( $result );
1738                 return $result;
1739         }
1740
1741         $result = $wpdb->get_var( $query );
1742         if ( null === $result )
1743                 $result = '';
1744
1745         wp_cache_set( $query_key, $result, 'counts' );
1746
1747         if ( $result )
1748                 $result = get_post( $result );
1749
1750         return $result;
1751 }
1752
1753 /**
1754  * Retrieves the adjacent post relational link.
1755  *
1756  * Can either be next or previous post relational link.
1757  *
1758  * @since 2.8.0
1759  *
1760  * @param string       $title          Optional. Link title format. Default '%title'.
1761  * @param bool         $in_same_term   Optional. Whether link should be in a same taxonomy term. Default false.
1762  * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty.
1763  * @param bool         $previous       Optional. Whether to display link to previous or next post. Default true.
1764  * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
1765  * @return string|void The adjacent post relational link URL.
1766  */
1767 function get_adjacent_post_rel_link( $title = '%title', $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) {
1768         if ( $previous && is_attachment() && $post = get_post() )
1769                 $post = get_post( $post->post_parent );
1770         else
1771                 $post = get_adjacent_post( $in_same_term, $excluded_terms, $previous, $taxonomy );
1772
1773         if ( empty( $post ) )
1774                 return;
1775
1776         $post_title = the_title_attribute( array( 'echo' => false, 'post' => $post ) );
1777
1778         if ( empty( $post_title ) )
1779                 $post_title = $previous ? __( 'Previous Post' ) : __( 'Next Post' );
1780
1781         $date = mysql2date( get_option( 'date_format' ), $post->post_date );
1782
1783         $title = str_replace( '%title', $post_title, $title );
1784         $title = str_replace( '%date', $date, $title );
1785
1786         $link = $previous ? "<link rel='prev' title='" : "<link rel='next' title='";
1787         $link .= esc_attr( $title );
1788         $link .= "' href='" . get_permalink( $post ) . "' />\n";
1789
1790         $adjacent = $previous ? 'previous' : 'next';
1791
1792         /**
1793          * Filters the adjacent post relational link.
1794          *
1795          * The dynamic portion of the hook name, `$adjacent`, refers to the type
1796          * of adjacency, 'next' or 'previous'.
1797          *
1798          * @since 2.8.0
1799          *
1800          * @param string $link The relational link.
1801          */
1802         return apply_filters( "{$adjacent}_post_rel_link", $link );
1803 }
1804
1805 /**
1806  * Displays the relational links for the posts adjacent to the current post.
1807  *
1808  * @since 2.8.0
1809  *
1810  * @param string       $title          Optional. Link title format. Default '%title'.
1811  * @param bool         $in_same_term   Optional. Whether link should be in a same taxonomy term. Default false.
1812  * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty.
1813  * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
1814  */
1815 function adjacent_posts_rel_link( $title = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
1816         echo get_adjacent_post_rel_link( $title, $in_same_term, $excluded_terms, true, $taxonomy );
1817         echo get_adjacent_post_rel_link( $title, $in_same_term, $excluded_terms, false, $taxonomy );
1818 }
1819
1820 /**
1821  * Displays relational links for the posts adjacent to the current post for single post pages.
1822  *
1823  * This is meant to be attached to actions like 'wp_head'. Do not call this directly in plugins
1824  * or theme templates.
1825  *
1826  * @since 3.0.0
1827  *
1828  * @see adjacent_posts_rel_link()
1829  */
1830 function adjacent_posts_rel_link_wp_head() {
1831         if ( ! is_single() || is_attachment() ) {
1832                 return;
1833         }
1834         adjacent_posts_rel_link();
1835 }
1836
1837 /**
1838  * Displays the relational link for the next post adjacent to the current post.
1839  *
1840  * @since 2.8.0
1841  *
1842  * @see get_adjacent_post_rel_link()
1843  *
1844  * @param string       $title          Optional. Link title format. Default '%title'.
1845  * @param bool         $in_same_term   Optional. Whether link should be in a same taxonomy term. Default false.
1846  * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty.
1847  * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
1848  */
1849 function next_post_rel_link( $title = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
1850         echo get_adjacent_post_rel_link( $title, $in_same_term, $excluded_terms, false, $taxonomy );
1851 }
1852
1853 /**
1854  * Displays the relational link for the previous post adjacent to the current post.
1855  *
1856  * @since 2.8.0
1857  *
1858  * @see get_adjacent_post_rel_link()
1859  *
1860  * @param string       $title          Optional. Link title format. Default '%title'.
1861  * @param bool         $in_same_term   Optional. Whether link should be in a same taxonomy term. Default false.
1862  * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default true.
1863  * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
1864  */
1865 function prev_post_rel_link( $title = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
1866         echo get_adjacent_post_rel_link( $title, $in_same_term, $excluded_terms, true, $taxonomy );
1867 }
1868
1869 /**
1870  * Retrieves the boundary post.
1871  *
1872  * Boundary being either the first or last post by publish date within the constraints specified
1873  * by $in_same_term or $excluded_terms.
1874  *
1875  * @since 2.8.0
1876  *
1877  * @param bool         $in_same_term   Optional. Whether returned post should be in a same taxonomy term.
1878  *                                     Default false.
1879  * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
1880  *                                     Default empty.
1881  * @param bool         $start          Optional. Whether to retrieve first or last post. Default true
1882  * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
1883  * @return null|array Array containing the boundary post object if successful, null otherwise.
1884  */
1885 function get_boundary_post( $in_same_term = false, $excluded_terms = '', $start = true, $taxonomy = 'category' ) {
1886         $post = get_post();
1887         if ( ! $post || ! is_single() || is_attachment() || ! taxonomy_exists( $taxonomy ) )
1888                 return null;
1889
1890         $query_args = array(
1891                 'posts_per_page' => 1,
1892                 'order' => $start ? 'ASC' : 'DESC',
1893                 'update_post_term_cache' => false,
1894                 'update_post_meta_cache' => false
1895         );
1896
1897         $term_array = array();
1898
1899         if ( ! is_array( $excluded_terms ) ) {
1900                 if ( ! empty( $excluded_terms ) )
1901                         $excluded_terms = explode( ',', $excluded_terms );
1902                 else
1903                         $excluded_terms = array();
1904         }
1905
1906         if ( $in_same_term || ! empty( $excluded_terms ) ) {
1907                 if ( $in_same_term )
1908                         $term_array = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'ids' ) );
1909
1910                 if ( ! empty( $excluded_terms ) ) {
1911                         $excluded_terms = array_map( 'intval', $excluded_terms );
1912                         $excluded_terms = array_diff( $excluded_terms, $term_array );
1913
1914                         $inverse_terms = array();
1915                         foreach ( $excluded_terms as $excluded_term )
1916                                 $inverse_terms[] = $excluded_term * -1;
1917                         $excluded_terms = $inverse_terms;
1918                 }
1919
1920                 $query_args[ 'tax_query' ] = array( array(
1921                         'taxonomy' => $taxonomy,
1922                         'terms' => array_merge( $term_array, $excluded_terms )
1923                 ) );
1924         }
1925
1926         return get_posts( $query_args );
1927 }
1928
1929 /**
1930  * Retrieves the previous post link that is adjacent to the current post.
1931  *
1932  * @since 3.7.0
1933  *
1934  * @param string       $format         Optional. Link anchor format. Default '&laquo; %link'.
1935  * @param string       $link           Optional. Link permalink format. Default '%title%'.
1936  * @param bool         $in_same_term   Optional. Whether link should be in a same taxonomy term. Default false.
1937  * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty.
1938  * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
1939  * @return string The link URL of the previous post in relation to the current post.
1940  */
1941 function get_previous_post_link( $format = '&laquo; %link', $link = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
1942         return get_adjacent_post_link( $format, $link, $in_same_term, $excluded_terms, true, $taxonomy );
1943 }
1944
1945 /**
1946  * Displays the previous post link that is adjacent to the current post.
1947  *
1948  * @since 1.5.0
1949  *
1950  * @see get_previous_post_link()
1951  *
1952  * @param string       $format         Optional. Link anchor format. Default '&laquo; %link'.
1953  * @param string       $link           Optional. Link permalink format. Default '%title'.
1954  * @param bool         $in_same_term   Optional. Whether link should be in a same taxonomy term. Default false.
1955  * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty.
1956  * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
1957  */
1958 function previous_post_link( $format = '&laquo; %link', $link = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
1959         echo get_previous_post_link( $format, $link, $in_same_term, $excluded_terms, $taxonomy );
1960 }
1961
1962 /**
1963  * Retrieves the next post link that is adjacent to the current post.
1964  *
1965  * @since 3.7.0
1966  *
1967  * @param string       $format         Optional. Link anchor format. Default '&laquo; %link'.
1968  * @param string       $link           Optional. Link permalink format. Default '%title'.
1969  * @param bool         $in_same_term   Optional. Whether link should be in a same taxonomy term. Default false.
1970  * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty.
1971  * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
1972  * @return string The link URL of the next post in relation to the current post.
1973  */
1974 function get_next_post_link( $format = '%link &raquo;', $link = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
1975         return get_adjacent_post_link( $format, $link, $in_same_term, $excluded_terms, false, $taxonomy );
1976 }
1977
1978 /**
1979  * Displays the next post link that is adjacent to the current post.
1980  *
1981  * @since 1.5.0
1982  * @see get_next_post_link()
1983  *
1984  * @param string       $format         Optional. Link anchor format. Default '&laquo; %link'.
1985  * @param string       $link           Optional. Link permalink format. Default '%title'
1986  * @param bool         $in_same_term   Optional. Whether link should be in a same taxonomy term. Default false.
1987  * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty.
1988  * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
1989  */
1990 function next_post_link( $format = '%link &raquo;', $link = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
1991          echo get_next_post_link( $format, $link, $in_same_term, $excluded_terms, $taxonomy );
1992 }
1993
1994 /**
1995  * Retrieves the adjacent post link.
1996  *
1997  * Can be either next post link or previous.
1998  *
1999  * @since 3.7.0
2000  *
2001  * @param string       $format         Link anchor format.
2002  * @param string       $link           Link permalink format.
2003  * @param bool         $in_same_term   Optional. Whether link should be in a same taxonomy term. Default false.
2004  * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded terms IDs. Default empty.
2005  * @param bool         $previous       Optional. Whether to display link to previous or next post. Default true.
2006  * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
2007  * @return string The link URL of the previous or next post in relation to the current post.
2008  */
2009 function get_adjacent_post_link( $format, $link, $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) {
2010         if ( $previous && is_attachment() )
2011                 $post = get_post( get_post()->post_parent );
2012         else
2013                 $post = get_adjacent_post( $in_same_term, $excluded_terms, $previous, $taxonomy );
2014
2015         if ( ! $post ) {
2016                 $output = '';
2017         } else {
2018                 $title = $post->post_title;
2019
2020                 if ( empty( $post->post_title ) )
2021                         $title = $previous ? __( 'Previous Post' ) : __( 'Next Post' );
2022
2023                 /** This filter is documented in wp-includes/post-template.php */
2024                 $title = apply_filters( 'the_title', $title, $post->ID );
2025
2026                 $date = mysql2date( get_option( 'date_format' ), $post->post_date );
2027                 $rel = $previous ? 'prev' : 'next';
2028
2029                 $string = '<a href="' . get_permalink( $post ) . '" rel="'.$rel.'">';
2030                 $inlink = str_replace( '%title', $title, $link );
2031                 $inlink = str_replace( '%date', $date, $inlink );
2032                 $inlink = $string . $inlink . '</a>';
2033
2034                 $output = str_replace( '%link', $inlink, $format );
2035         }
2036
2037         $adjacent = $previous ? 'previous' : 'next';
2038
2039         /**
2040          * Filters the adjacent post link.
2041          *
2042          * The dynamic portion of the hook name, `$adjacent`, refers to the type
2043          * of adjacency, 'next' or 'previous'.
2044          *
2045          * @since 2.6.0
2046          * @since 4.2.0 Added the `$adjacent` parameter.
2047          *
2048          * @param string  $output   The adjacent post link.
2049          * @param string  $format   Link anchor format.
2050          * @param string  $link     Link permalink format.
2051          * @param WP_Post $post     The adjacent post.
2052          * @param string  $adjacent Whether the post is previous or next.
2053          */
2054         return apply_filters( "{$adjacent}_post_link", $output, $format, $link, $post, $adjacent );
2055 }
2056
2057 /**
2058  * Displays the adjacent post link.
2059  *
2060  * Can be either next post link or previous.
2061  *
2062  * @since 2.5.0
2063  *
2064  * @param string       $format         Link anchor format.
2065  * @param string       $link           Link permalink format.
2066  * @param bool         $in_same_term   Optional. Whether link should be in a same taxonomy term. Default false.
2067  * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded category IDs. Default empty.
2068  * @param bool         $previous       Optional. Whether to display link to previous or next post. Default true.
2069  * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
2070  */
2071 function adjacent_post_link( $format, $link, $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) {
2072         echo get_adjacent_post_link( $format, $link, $in_same_term, $excluded_terms, $previous, $taxonomy );
2073 }
2074
2075 /**
2076  * Retrieves the link for a page number.
2077  *
2078  * @since 1.5.0
2079  *
2080  * @global WP_Rewrite $wp_rewrite
2081  *
2082  * @param int  $pagenum Optional. Page ID. Default 1.
2083  * @param bool $escape  Optional. Whether to escape the URL for display, with esc_url(). Defaults to true.
2084  *                          Otherwise, prepares the URL with esc_url_raw().
2085  * @return string The link URL for the given page number.
2086  */
2087 function get_pagenum_link($pagenum = 1, $escape = true ) {
2088         global $wp_rewrite;
2089
2090         $pagenum = (int) $pagenum;
2091
2092         $request = remove_query_arg( 'paged' );
2093
2094         $home_root = parse_url(home_url());
2095         $home_root = ( isset($home_root['path']) ) ? $home_root['path'] : '';
2096         $home_root = preg_quote( $home_root, '|' );
2097
2098         $request = preg_replace('|^'. $home_root . '|i', '', $request);
2099         $request = preg_replace('|^/+|', '', $request);
2100
2101         if ( !$wp_rewrite->using_permalinks() || is_admin() ) {
2102                 $base = trailingslashit( get_bloginfo( 'url' ) );
2103
2104                 if ( $pagenum > 1 ) {
2105                         $result = add_query_arg( 'paged', $pagenum, $base . $request );
2106                 } else {
2107                         $result = $base . $request;
2108                 }
2109         } else {
2110                 $qs_regex = '|\?.*?$|';
2111                 preg_match( $qs_regex, $request, $qs_match );
2112
2113                 if ( !empty( $qs_match[0] ) ) {
2114                         $query_string = $qs_match[0];
2115                         $request = preg_replace( $qs_regex, '', $request );
2116                 } else {
2117                         $query_string = '';
2118                 }
2119
2120                 $request = preg_replace( "|$wp_rewrite->pagination_base/\d+/?$|", '', $request);
2121                 $request = preg_replace( '|^' . preg_quote( $wp_rewrite->index, '|' ) . '|i', '', $request);
2122                 $request = ltrim($request, '/');
2123
2124                 $base = trailingslashit( get_bloginfo( 'url' ) );
2125
2126                 if ( $wp_rewrite->using_index_permalinks() && ( $pagenum > 1 || '' != $request ) )
2127                         $base .= $wp_rewrite->index . '/';
2128
2129                 if ( $pagenum > 1 ) {
2130                         $request = ( ( !empty( $request ) ) ? trailingslashit( $request ) : $request ) . user_trailingslashit( $wp_rewrite->pagination_base . "/" . $pagenum, 'paged' );
2131                 }
2132
2133                 $result = $base . $request . $query_string;
2134         }
2135
2136         /**
2137          * Filters the page number link for the current request.
2138          *
2139          * @since 2.5.0
2140          *
2141          * @param string $result The page number link.
2142          */
2143         $result = apply_filters( 'get_pagenum_link', $result );
2144
2145         if ( $escape )
2146                 return esc_url( $result );
2147         else
2148                 return esc_url_raw( $result );
2149 }
2150
2151 /**
2152  * Retrieves the next posts page link.
2153  *
2154  * Backported from 2.1.3 to 2.0.10.
2155  *
2156  * @since 2.0.10
2157  *
2158  * @global int $paged
2159  *
2160  * @param int $max_page Optional. Max pages. Default 0.
2161  * @return string|void The link URL for next posts page.
2162  */
2163 function get_next_posts_page_link($max_page = 0) {
2164         global $paged;
2165
2166         if ( !is_single() ) {
2167                 if ( !$paged )
2168                         $paged = 1;
2169                 $nextpage = intval($paged) + 1;
2170                 if ( !$max_page || $max_page >= $nextpage )
2171                         return get_pagenum_link($nextpage);
2172         }
2173 }
2174
2175 /**
2176  * Displays or retrieves the next posts page link.
2177  *
2178  * @since 0.71
2179  *
2180  * @param int   $max_page Optional. Max pages. Default 0.
2181  * @param bool  $echo     Optional. Whether to echo the link. Default true.
2182  * @return string|void The link URL for next posts page if `$echo = false`.
2183  */
2184 function next_posts( $max_page = 0, $echo = true ) {
2185         $output = esc_url( get_next_posts_page_link( $max_page ) );
2186
2187         if ( $echo )
2188                 echo $output;
2189         else
2190                 return $output;
2191 }
2192
2193 /**
2194  * Retrieves the next posts page link.
2195  *
2196  * @since 2.7.0
2197  *
2198  * @global int      $paged
2199  * @global WP_Query $wp_query
2200  *
2201  * @param string $label    Content for link text.
2202  * @param int    $max_page Optional. Max pages. Default 0.
2203  * @return string|void HTML-formatted next posts page link.
2204  */
2205 function get_next_posts_link( $label = null, $max_page = 0 ) {
2206         global $paged, $wp_query;
2207
2208         if ( !$max_page )
2209                 $max_page = $wp_query->max_num_pages;
2210
2211         if ( !$paged )
2212                 $paged = 1;
2213
2214         $nextpage = intval($paged) + 1;
2215
2216         if ( null === $label )
2217                 $label = __( 'Next Page &raquo;' );
2218
2219         if ( !is_single() && ( $nextpage <= $max_page ) ) {
2220                 /**
2221                  * Filters the anchor tag attributes for the next posts page link.
2222                  *
2223                  * @since 2.7.0
2224                  *
2225                  * @param string $attributes Attributes for the anchor tag.
2226                  */
2227                 $attr = apply_filters( 'next_posts_link_attributes', '' );
2228
2229                 return '<a href="' . next_posts( $max_page, false ) . "\" $attr>" . preg_replace('/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $label) . '</a>';
2230         }
2231 }
2232
2233 /**
2234  * Displays the next posts page link.
2235  *
2236  * @since 0.71
2237  *
2238  * @param string $label    Content for link text.
2239  * @param int    $max_page Optional. Max pages. Default 0.
2240  */
2241 function next_posts_link( $label = null, $max_page = 0 ) {
2242         echo get_next_posts_link( $label, $max_page );
2243 }
2244
2245 /**
2246  * Retrieves the previous posts page link.
2247  *
2248  * Will only return string, if not on a single page or post.
2249  *
2250  * Backported to 2.0.10 from 2.1.3.
2251  *
2252  * @since 2.0.10
2253  *
2254  * @global int $paged
2255  *
2256  * @return string|void The link for the previous posts page.
2257  */
2258 function get_previous_posts_page_link() {
2259         global $paged;
2260
2261         if ( !is_single() ) {
2262                 $nextpage = intval($paged) - 1;
2263                 if ( $nextpage < 1 )
2264                         $nextpage = 1;
2265                 return get_pagenum_link($nextpage);
2266         }
2267 }
2268
2269 /**
2270  * Displays or retrieves the previous posts page link.
2271  *
2272  * @since 0.71
2273  *
2274  * @param bool $echo Optional. Whether to echo the link. Default true.
2275  * @return string|void The previous posts page link if `$echo = false`.
2276  */
2277 function previous_posts( $echo = true ) {
2278         $output = esc_url( get_previous_posts_page_link() );
2279
2280         if ( $echo )
2281                 echo $output;
2282         else
2283                 return $output;
2284 }
2285
2286 /**
2287  * Retrieves the previous posts page link.
2288  *
2289  * @since 2.7.0
2290  *
2291  * @global int $paged
2292  *
2293  * @param string $label Optional. Previous page link text.
2294  * @return string|void HTML-formatted previous page link.
2295  */
2296 function get_previous_posts_link( $label = null ) {
2297         global $paged;
2298
2299         if ( null === $label )
2300                 $label = __( '&laquo; Previous Page' );
2301
2302         if ( !is_single() && $paged > 1 ) {
2303                 /**
2304                  * Filters the anchor tag attributes for the previous posts page link.
2305                  *
2306                  * @since 2.7.0
2307                  *
2308                  * @param string $attributes Attributes for the anchor tag.
2309                  */
2310                 $attr = apply_filters( 'previous_posts_link_attributes', '' );
2311                 return '<a href="' . previous_posts( false ) . "\" $attr>". preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $label ) .'</a>';
2312         }
2313 }
2314
2315 /**
2316  * Displays the previous posts page link.
2317  *
2318  * @since 0.71
2319  *
2320  * @param string $label Optional. Previous page link text.
2321  */
2322 function previous_posts_link( $label = null ) {
2323         echo get_previous_posts_link( $label );
2324 }
2325
2326 /**
2327  * Retrieves the post pages link navigation for previous and next pages.
2328  *
2329  * @since 2.8.0
2330  *
2331  * @global WP_Query $wp_query
2332  *
2333  * @param string|array $args {
2334  *     Optional. Arguments to build the post pages link navigation.
2335  *
2336  *     @type string $sep      Separator character. Default '&#8212;'.
2337  *     @type string $prelabel Link text to display for the previous page link.
2338  *                            Default '&laquo; Previous Page'.
2339  *     @type string $nxtlabel Link text to display for the next page link.
2340  *                            Default 'Next Page &raquo;'.
2341  * }
2342  * @return string The posts link navigation.
2343  */
2344 function get_posts_nav_link( $args = array() ) {
2345         global $wp_query;
2346
2347         $return = '';
2348
2349         if ( !is_singular() ) {
2350                 $defaults = array(
2351                         'sep' => ' &#8212; ',
2352                         'prelabel' => __('&laquo; Previous Page'),
2353                         'nxtlabel' => __('Next Page &raquo;'),
2354                 );
2355                 $args = wp_parse_args( $args, $defaults );
2356
2357                 $max_num_pages = $wp_query->max_num_pages;
2358                 $paged = get_query_var('paged');
2359
2360                 //only have sep if there's both prev and next results
2361                 if ($paged < 2 || $paged >= $max_num_pages) {
2362                         $args['sep'] = '';
2363                 }
2364
2365                 if ( $max_num_pages > 1 ) {
2366                         $return = get_previous_posts_link($args['prelabel']);
2367                         $return .= preg_replace('/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $args['sep']);
2368                         $return .= get_next_posts_link($args['nxtlabel']);
2369                 }
2370         }
2371         return $return;
2372
2373 }
2374
2375 /**
2376  * Displays the post pages link navigation for previous and next pages.
2377  *
2378  * @since 0.71
2379  *
2380  * @param string $sep      Optional. Separator for posts navigation links. Default empty.
2381  * @param string $prelabel Optional. Label for previous pages. Default empty.
2382  * @param string $nxtlabel Optional Label for next pages. Default empty.
2383  */
2384 function posts_nav_link( $sep = '', $prelabel = '', $nxtlabel = '' ) {
2385         $args = array_filter( compact('sep', 'prelabel', 'nxtlabel') );
2386         echo get_posts_nav_link($args);
2387 }
2388
2389 /**
2390  * Retrieves the navigation to next/previous post, when applicable.
2391  *
2392  * @since 4.1.0
2393  * @since 4.4.0 Introduced the `in_same_term`, `excluded_terms`, and `taxonomy` arguments.
2394  *
2395  * @param array $args {
2396  *     Optional. Default post navigation arguments. Default empty array.
2397  *
2398  *     @type string       $prev_text          Anchor text to display in the previous post link. Default '%title'.
2399  *     @type string       $next_text          Anchor text to display in the next post link. Default '%title'.
2400  *     @type bool         $in_same_term       Whether link should be in a same taxonomy term. Default false.
2401  *     @type array|string $excluded_terms     Array or comma-separated list of excluded term IDs. Default empty.
2402  *     @type string       $taxonomy           Taxonomy, if `$in_same_term` is true. Default 'category'.
2403  *     @type string       $screen_reader_text Screen reader text for nav element. Default 'Post navigation'.
2404  * }
2405  * @return string Markup for post links.
2406  */
2407 function get_the_post_navigation( $args = array() ) {
2408         $args = wp_parse_args( $args, array(
2409                 'prev_text'          => '%title',
2410                 'next_text'          => '%title',
2411                 'in_same_term'       => false,
2412                 'excluded_terms'     => '',
2413                 'taxonomy'           => 'category',
2414                 'screen_reader_text' => __( 'Post navigation' ),
2415         ) );
2416
2417         $navigation = '';
2418
2419         $previous = get_previous_post_link(
2420                 '<div class="nav-previous">%link</div>',
2421                 $args['prev_text'],
2422                 $args['in_same_term'],
2423                 $args['excluded_terms'],
2424                 $args['taxonomy']
2425         );
2426
2427         $next = get_next_post_link(
2428                 '<div class="nav-next">%link</div>',
2429                 $args['next_text'],
2430                 $args['in_same_term'],
2431                 $args['excluded_terms'],
2432                 $args['taxonomy']
2433         );
2434
2435         // Only add markup if there's somewhere to navigate to.
2436         if ( $previous || $next ) {
2437                 $navigation = _navigation_markup( $previous . $next, 'post-navigation', $args['screen_reader_text'] );
2438         }
2439
2440         return $navigation;
2441 }
2442
2443 /**
2444  * Displays the navigation to next/previous post, when applicable.
2445  *
2446  * @since 4.1.0
2447  *
2448  * @param array $args Optional. See get_the_post_navigation() for available arguments.
2449  *                    Default empty array.
2450  */
2451 function the_post_navigation( $args = array() ) {
2452         echo get_the_post_navigation( $args );
2453 }
2454
2455 /**
2456  * Returns the navigation to next/previous set of posts, when applicable.
2457  *
2458  * @since 4.1.0
2459  *
2460  * @global WP_Query $wp_query WordPress Query object.
2461  *
2462  * @param array $args {
2463  *     Optional. Default posts navigation arguments. Default empty array.
2464  *
2465  *     @type string $prev_text          Anchor text to display in the previous posts link.
2466  *                                      Default 'Older posts'.
2467  *     @type string $next_text          Anchor text to display in the next posts link.
2468  *                                      Default 'Newer posts'.
2469  *     @type string $screen_reader_text Screen reader text for nav element.
2470  *                                      Default 'Posts navigation'.
2471  * }
2472  * @return string Markup for posts links.
2473  */
2474 function get_the_posts_navigation( $args = array() ) {
2475         $navigation = '';
2476
2477         // Don't print empty markup if there's only one page.
2478         if ( $GLOBALS['wp_query']->max_num_pages > 1 ) {
2479                 $args = wp_parse_args( $args, array(
2480                         'prev_text'          => __( 'Older posts' ),
2481                         'next_text'          => __( 'Newer posts' ),
2482                         'screen_reader_text' => __( 'Posts navigation' ),
2483                 ) );
2484
2485                 $next_link = get_previous_posts_link( $args['next_text'] );
2486                 $prev_link = get_next_posts_link( $args['prev_text'] );
2487
2488                 if ( $prev_link ) {
2489                         $navigation .= '<div class="nav-previous">' . $prev_link . '</div>';
2490                 }
2491
2492                 if ( $next_link ) {
2493                         $navigation .= '<div class="nav-next">' . $next_link . '</div>';
2494                 }
2495
2496                 $navigation = _navigation_markup( $navigation, 'posts-navigation', $args['screen_reader_text'] );
2497         }
2498
2499         return $navigation;
2500 }
2501
2502 /**
2503  * Displays the navigation to next/previous set of posts, when applicable.
2504  *
2505  * @since 4.1.0
2506  *
2507  * @param array $args Optional. See get_the_posts_navigation() for available arguments.
2508  *                    Default empty array.
2509  */
2510 function the_posts_navigation( $args = array() ) {
2511         echo get_the_posts_navigation( $args );
2512 }
2513
2514 /**
2515  * Retrieves a paginated navigation to next/previous set of posts, when applicable.
2516  *
2517  * @since 4.1.0
2518  *
2519  * @param array $args {
2520  *     Optional. Default pagination arguments, see paginate_links().
2521  *
2522  *     @type string $screen_reader_text Screen reader text for navigation element.
2523  *                                      Default 'Posts navigation'.
2524  * }
2525  * @return string Markup for pagination links.
2526  */
2527 function get_the_posts_pagination( $args = array() ) {
2528         $navigation = '';
2529
2530         // Don't print empty markup if there's only one page.
2531         if ( $GLOBALS['wp_query']->max_num_pages > 1 ) {
2532                 $args = wp_parse_args( $args, array(
2533                         'mid_size'           => 1,
2534                         'prev_text'          => _x( 'Previous', 'previous set of posts' ),
2535                         'next_text'          => _x( 'Next', 'next set of posts' ),
2536                         'screen_reader_text' => __( 'Posts navigation' ),
2537                 ) );
2538
2539                 // Make sure we get a string back. Plain is the next best thing.
2540                 if ( isset( $args['type'] ) && 'array' == $args['type'] ) {
2541                         $args['type'] = 'plain';
2542                 }
2543
2544                 // Set up paginated links.
2545                 $links = paginate_links( $args );
2546
2547                 if ( $links ) {
2548                         $navigation = _navigation_markup( $links, 'pagination', $args['screen_reader_text'] );
2549                 }
2550         }
2551
2552         return $navigation;
2553 }
2554
2555 /**
2556  * Displays a paginated navigation to next/previous set of posts, when applicable.
2557  *
2558  * @since 4.1.0
2559  *
2560  * @param array $args Optional. See get_the_posts_pagination() for available arguments.
2561  *                    Default empty array.
2562  */
2563 function the_posts_pagination( $args = array() ) {
2564         echo get_the_posts_pagination( $args );
2565 }
2566
2567 /**
2568  * Wraps passed links in navigational markup.
2569  *
2570  * @since 4.1.0
2571  * @access private
2572  *
2573  * @param string $links              Navigational links.
2574  * @param string $class              Optional. Custom class for nav element. Default: 'posts-navigation'.
2575  * @param string $screen_reader_text Optional. Screen reader text for nav element. Default: 'Posts navigation'.
2576  * @return string Navigation template tag.
2577  */
2578 function _navigation_markup( $links, $class = 'posts-navigation', $screen_reader_text = '' ) {
2579         if ( empty( $screen_reader_text ) ) {
2580                 $screen_reader_text = __( 'Posts navigation' );
2581         }
2582
2583         $template = '
2584         <nav class="navigation %1$s" role="navigation">
2585                 <h2 class="screen-reader-text">%2$s</h2>
2586                 <div class="nav-links">%3$s</div>
2587         </nav>';
2588
2589         /**
2590          * Filters the navigation markup template.
2591          *
2592          * Note: The filtered template HTML must contain specifiers for the navigation
2593          * class (%1$s), the screen-reader-text value (%2$s), and placement of the
2594          * navigation links (%3$s):
2595          *
2596          *     <nav class="navigation %1$s" role="navigation">
2597          *         <h2 class="screen-reader-text">%2$s</h2>
2598          *         <div class="nav-links">%3$s</div>
2599          *     </nav>
2600          *
2601          * @since 4.4.0
2602          *
2603          * @param string $template The default template.
2604          * @param string $class    The class passed by the calling function.
2605          * @return string Navigation template.
2606          */
2607         $template = apply_filters( 'navigation_markup_template', $template, $class );
2608
2609         return sprintf( $template, sanitize_html_class( $class ), esc_html( $screen_reader_text ), $links );
2610 }
2611
2612 /**
2613  * Retrieves the comments page number link.
2614  *
2615  * @since 2.7.0
2616  *
2617  * @global WP_Rewrite $wp_rewrite
2618  *
2619  * @param int $pagenum  Optional. Page number. Default 1.
2620  * @param int $max_page Optional. The maximum number of comment pages. Default 0.
2621  * @return string The comments page number link URL.
2622  */
2623 function get_comments_pagenum_link( $pagenum = 1, $max_page = 0 ) {
2624         global $wp_rewrite;
2625
2626         $pagenum = (int) $pagenum;
2627
2628         $result = get_permalink();
2629
2630         if ( 'newest' == get_option('default_comments_page') ) {
2631                 if ( $pagenum != $max_page ) {
2632                         if ( $wp_rewrite->using_permalinks() )
2633                                 $result = user_trailingslashit( trailingslashit($result) . $wp_rewrite->comments_pagination_base . '-' . $pagenum, 'commentpaged');
2634                         else
2635                                 $result = add_query_arg( 'cpage', $pagenum, $result );
2636                 }
2637         } elseif ( $pagenum > 1 ) {
2638                 if ( $wp_rewrite->using_permalinks() )
2639                         $result = user_trailingslashit( trailingslashit($result) . $wp_rewrite->comments_pagination_base . '-' . $pagenum, 'commentpaged');
2640                 else
2641                         $result = add_query_arg( 'cpage', $pagenum, $result );
2642         }
2643
2644         $result .= '#comments';
2645
2646         /**
2647          * Filters the comments page number link for the current request.
2648          *
2649          * @since 2.7.0
2650          *
2651          * @param string $result The comments page number link.
2652          */
2653         return apply_filters( 'get_comments_pagenum_link', $result );
2654 }
2655
2656 /**
2657  * Retrieves the link to the next comments page.
2658  *
2659  * @since 2.7.1
2660  *
2661  * @global WP_Query $wp_query
2662  *
2663  * @param string $label    Optional. Label for link text. Default empty.
2664  * @param int    $max_page Optional. Max page. Default 0.
2665  * @return string|void HTML-formatted link for the next page of comments.
2666  */
2667 function get_next_comments_link( $label = '', $max_page = 0 ) {
2668         global $wp_query;
2669
2670         if ( ! is_singular() )
2671                 return;
2672
2673         $page = get_query_var('cpage');
2674
2675         if ( ! $page ) {
2676                 $page = 1;
2677         }
2678
2679         $nextpage = intval($page) + 1;
2680
2681         if ( empty($max_page) )
2682                 $max_page = $wp_query->max_num_comment_pages;
2683
2684         if ( empty($max_page) )
2685                 $max_page = get_comment_pages_count();
2686
2687         if ( $nextpage > $max_page )
2688                 return;
2689
2690         if ( empty($label) )
2691                 $label = __('Newer Comments &raquo;');
2692
2693         /**
2694          * Filters the anchor tag attributes for the next comments page link.
2695          *
2696          * @since 2.7.0
2697          *
2698          * @param string $attributes Attributes for the anchor tag.
2699          */
2700         return '<a href="' . esc_url( get_comments_pagenum_link( $nextpage, $max_page ) ) . '" ' . apply_filters( 'next_comments_link_attributes', '' ) . '>'. preg_replace('/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $label) .'</a>';
2701 }
2702
2703 /**
2704  * Displays the link to the next comments page.
2705  *
2706  * @since 2.7.0
2707  *
2708  * @param string $label    Optional. Label for link text. Default empty.
2709  * @param int    $max_page Optional. Max page. Default 0.
2710  */
2711 function next_comments_link( $label = '', $max_page = 0 ) {
2712         echo get_next_comments_link( $label, $max_page );
2713 }
2714
2715 /**
2716  * Retrieves the link to the previous comments page.
2717  *
2718  * @since 2.7.1
2719  *
2720  * @param string $label Optional. Label for comments link text. Default empty.
2721  * @return string|void HTML-formatted link for the previous page of comments.
2722  */
2723 function get_previous_comments_link( $label = '' ) {
2724         if ( ! is_singular() )
2725                 return;
2726
2727         $page = get_query_var('cpage');
2728
2729         if ( intval($page) <= 1 )
2730                 return;
2731
2732         $prevpage = intval($page) - 1;
2733
2734         if ( empty($label) )
2735                 $label = __('&laquo; Older Comments');
2736
2737         /**
2738          * Filters the anchor tag attributes for the previous comments page link.
2739          *
2740          * @since 2.7.0
2741          *
2742          * @param string $attributes Attributes for the anchor tag.
2743          */
2744         return '<a href="' . esc_url( get_comments_pagenum_link( $prevpage ) ) . '" ' . apply_filters( 'previous_comments_link_attributes', '' ) . '>' . preg_replace('/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $label) .'</a>';
2745 }
2746
2747 /**
2748  * Displays the link to the previous comments page.
2749  *
2750  * @since 2.7.0
2751  *
2752  * @param string $label Optional. Label for comments link text. Default empty.
2753  */
2754 function previous_comments_link( $label = '' ) {
2755         echo get_previous_comments_link( $label );
2756 }
2757
2758 /**
2759  * Displays or retrieves pagination links for the comments on the current post.
2760  *
2761  * @see paginate_links()
2762  * @since 2.7.0
2763  *
2764  * @global WP_Rewrite $wp_rewrite
2765  *
2766  * @param string|array $args Optional args. See paginate_links(). Default empty array.
2767  * @return string|void Markup for pagination links.
2768  */
2769 function paginate_comments_links( $args = array() ) {
2770         global $wp_rewrite;
2771
2772         if ( ! is_singular() )
2773                 return;
2774
2775         $page = get_query_var('cpage');
2776         if ( !$page )
2777                 $page = 1;
2778         $max_page = get_comment_pages_count();
2779         $defaults = array(
2780                 'base' => add_query_arg( 'cpage', '%#%' ),
2781                 'format' => '',
2782                 'total' => $max_page,
2783                 'current' => $page,
2784                 'echo' => true,
2785                 'add_fragment' => '#comments'
2786         );
2787         if ( $wp_rewrite->using_permalinks() )
2788                 $defaults['base'] = user_trailingslashit(trailingslashit(get_permalink()) . $wp_rewrite->comments_pagination_base . '-%#%', 'commentpaged');
2789
2790         $args = wp_parse_args( $args, $defaults );
2791         $page_links = paginate_links( $args );
2792
2793         if ( $args['echo'] )
2794                 echo $page_links;
2795         else
2796                 return $page_links;
2797 }
2798
2799 /**
2800  * Retrieves navigation to next/previous set of comments, when applicable.
2801  *
2802  * @since 4.4.0
2803  *
2804  * @param array $args {
2805  *     Optional. Default comments navigation arguments.
2806  *
2807  *     @type string $prev_text          Anchor text to display in the previous comments link.
2808  *                                      Default 'Older comments'.
2809  *     @type string $next_text          Anchor text to display in the next comments link.
2810  *                                      Default 'Newer comments'.
2811  *     @type string $screen_reader_text Screen reader text for nav element. Default 'Comments navigation'.
2812  * }
2813  * @return string Markup for comments links.
2814  */
2815 function get_the_comments_navigation( $args = array() ) {
2816         $navigation = '';
2817
2818         // Are there comments to navigate through?
2819         if ( get_comment_pages_count() > 1 ) {
2820                 $args = wp_parse_args( $args, array(
2821                         'prev_text'          => __( 'Older comments' ),
2822                         'next_text'          => __( 'Newer comments' ),
2823                         'screen_reader_text' => __( 'Comments navigation' ),
2824                 ) );
2825
2826                 $prev_link = get_previous_comments_link( $args['prev_text'] );
2827                 $next_link = get_next_comments_link( $args['next_text'] );
2828
2829                 if ( $prev_link ) {
2830                         $navigation .= '<div class="nav-previous">' . $prev_link . '</div>';
2831                 }
2832
2833                 if ( $next_link ) {
2834                         $navigation .= '<div class="nav-next">' . $next_link . '</div>';
2835                 }
2836
2837                 $navigation = _navigation_markup( $navigation, 'comment-navigation', $args['screen_reader_text'] );
2838         }
2839
2840         return $navigation;
2841 }
2842
2843 /**
2844  * Displays navigation to next/previous set of comments, when applicable.
2845  *
2846  * @since 4.4.0
2847  *
2848  * @param array $args See get_the_comments_navigation() for available arguments. Default empty array.
2849  */
2850 function the_comments_navigation( $args = array() ) {
2851         echo get_the_comments_navigation( $args );
2852 }
2853
2854 /**
2855  * Retrieves a paginated navigation to next/previous set of comments, when applicable.
2856  *
2857  * @since 4.4.0
2858  *
2859  * @see paginate_comments_links()
2860  *
2861  * @param array $args {
2862  *     Optional. Default pagination arguments.
2863  *
2864  *     @type string $screen_reader_text Screen reader text for nav element. Default 'Comments navigation'.
2865  * }
2866  * @return string Markup for pagination links.
2867  */
2868 function get_the_comments_pagination( $args = array() ) {
2869         $navigation = '';
2870         $args       = wp_parse_args( $args, array(
2871                 'screen_reader_text' => __( 'Comments navigation' ),
2872         ) );
2873         $args['echo'] = false;
2874
2875         // Make sure we get plain links, so we get a string we can work with.
2876         $args['type'] = 'plain';
2877
2878         $links = paginate_comments_links( $args );
2879
2880         if ( $links ) {
2881                 $navigation = _navigation_markup( $links, 'comments-pagination', $args['screen_reader_text'] );
2882         }
2883
2884         return $navigation;
2885 }
2886
2887 /**
2888  * Displays a paginated navigation to next/previous set of comments, when applicable.
2889  *
2890  * @since 4.4.0
2891  *
2892  * @param array $args See get_the_comments_pagination() for available arguments. Default empty array.
2893  */
2894 function the_comments_pagination( $args = array() ) {
2895         echo get_the_comments_pagination( $args );
2896 }
2897
2898 /**
2899  * Retrieves the Press This bookmarklet link.
2900  *
2901  * @since 2.6.0
2902  *
2903  * @global bool          $is_IE      Whether the browser matches an Internet Explorer user agent.
2904  */
2905 function get_shortcut_link() {
2906         global $is_IE;
2907
2908         include_once( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' );
2909
2910         $link = '';
2911
2912         if ( $is_IE ) {
2913                 /*
2914                  * Return the old/shorter bookmarklet code for MSIE 8 and lower,
2915                  * since they only support a max length of ~2000 characters for
2916                  * bookmark[let] URLs, which is way to small for our smarter one.
2917                  * Do update the version number so users do not get the "upgrade your
2918                  * bookmarklet" notice when using PT in those browsers.
2919                  */
2920                 $ua = $_SERVER['HTTP_USER_AGENT'];
2921
2922                 if ( ! empty( $ua ) && preg_match( '/\bMSIE (\d)/', $ua, $matches ) && (int) $matches[1] <= 8 ) {
2923                         $url = wp_json_encode( admin_url( 'press-this.php' ) );
2924
2925                         $link = 'javascript:var d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,' .
2926                                 's=(e?e():(k)?k():(x?x.createRange().text:0)),f=' . $url . ',l=d.location,e=encodeURIComponent,' .
2927                                 'u=f+"?u="+e(l.href)+"&t="+e(d.title)+"&s="+e(s)+"&v=' . WP_Press_This::VERSION . '";' .
2928                                 'a=function(){if(!w.open(u,"t","toolbar=0,resizable=1,scrollbars=1,status=1,width=600,height=700"))l.href=u;};' .
2929                                 'if(/Firefox/.test(navigator.userAgent))setTimeout(a,0);else a();void(0)';
2930                 }
2931         }
2932
2933         if ( empty( $link ) ) {
2934                 $src = @file_get_contents( ABSPATH . 'wp-admin/js/bookmarklet.min.js' );
2935
2936                 if ( $src ) {
2937                         $url = wp_json_encode( admin_url( 'press-this.php' ) . '?v=' . WP_Press_This::VERSION );
2938                         $link = 'javascript:' . str_replace( 'window.pt_url', $url, $src );
2939                 }
2940         }
2941
2942         $link = str_replace( array( "\r", "\n", "\t" ),  '', $link );
2943
2944         /**
2945          * Filters the Press This bookmarklet link.
2946          *
2947          * @since 2.6.0
2948          *
2949          * @param string $link The Press This bookmarklet link.
2950          */
2951         return apply_filters( 'shortcut_link', $link );
2952 }
2953
2954 /**
2955  * Retrieves the URL for the current site where the front end is accessible.
2956  *
2957  * Returns the 'home' option with the appropriate protocol. The protocol will be 'https'
2958  * if is_ssl() evaluates to true; otherwise, it will be the same as the 'home' option.
2959  * If `$scheme` is 'http' or 'https', is_ssl() is overridden.
2960  *
2961  * @since 3.0.0
2962  *
2963  * @param  string      $path   Optional. Path relative to the home URL. Default empty.
2964  * @param  string|null $scheme Optional. Scheme to give the home URL context. Accepts
2965  *                             'http', 'https', 'relative', 'rest', or null. Default null.
2966  * @return string Home URL link with optional path appended.
2967  */
2968 function home_url( $path = '', $scheme = null ) {
2969         return get_home_url( null, $path, $scheme );
2970 }
2971
2972 /**
2973  * Retrieves the URL for a given site where the front end is accessible.
2974  *
2975  * Returns the 'home' option with the appropriate protocol. The protocol will be 'https'
2976  * if is_ssl() evaluates to true; otherwise, it will be the same as the 'home' option.
2977  * If `$scheme` is 'http' or 'https', is_ssl() is overridden.
2978  *
2979  * @since 3.0.0
2980  *
2981  * @global string $pagenow
2982  *
2983  * @param  int         $blog_id Optional. Site ID. Default null (current site).
2984  * @param  string      $path    Optional. Path relative to the home URL. Default empty.
2985  * @param  string|null $scheme  Optional. Scheme to give the home URL context. Accepts
2986  *                              'http', 'https', 'relative', 'rest', or null. Default null.
2987  * @return string Home URL link with optional path appended.
2988  */
2989 function get_home_url( $blog_id = null, $path = '', $scheme = null ) {
2990         global $pagenow;
2991
2992         $orig_scheme = $scheme;
2993
2994         if ( empty( $blog_id ) || !is_multisite() ) {
2995                 $url = get_option( 'home' );
2996         } else {
2997                 switch_to_blog( $blog_id );
2998                 $url = get_option( 'home' );
2999                 restore_current_blog();
3000         }
3001
3002         if ( ! in_array( $scheme, array( 'http', 'https', 'relative' ) ) ) {
3003                 if ( is_ssl() && ! is_admin() && 'wp-login.php' !== $pagenow )
3004                         $scheme = 'https';
3005                 else
3006                         $scheme = parse_url( $url, PHP_URL_SCHEME );
3007         }
3008
3009         $url = set_url_scheme( $url, $scheme );
3010
3011         if ( $path && is_string( $path ) )
3012                 $url .= '/' . ltrim( $path, '/' );
3013
3014         /**
3015          * Filters the home URL.
3016          *
3017          * @since 3.0.0
3018          *
3019          * @param string      $url         The complete home URL including scheme and path.
3020          * @param string      $path        Path relative to the home URL. Blank string if no path is specified.
3021          * @param string|null $orig_scheme Scheme to give the home URL context. Accepts 'http', 'https',
3022          *                                 'relative', 'rest', or null.
3023          * @param int|null    $blog_id     Site ID, or null for the current site.
3024          */
3025         return apply_filters( 'home_url', $url, $path, $orig_scheme, $blog_id );
3026 }
3027
3028 /**
3029  * Retrieves the URL for the current site where WordPress application files
3030  * (e.g. wp-blog-header.php or the wp-admin/ folder) are accessible.
3031  *
3032  * Returns the 'site_url' option with the appropriate protocol, 'https' if
3033  * is_ssl() and 'http' otherwise. If $scheme is 'http' or 'https', is_ssl() is
3034  * overridden.
3035  *
3036  * @since 3.0.0
3037  *
3038  * @param string $path   Optional. Path relative to the site URL. Default empty.
3039  * @param string $scheme Optional. Scheme to give the site URL context. See set_url_scheme().
3040  * @return string Site URL link with optional path appended.
3041  */
3042 function site_url( $path = '', $scheme = null ) {
3043         return get_site_url( null, $path, $scheme );
3044 }
3045
3046 /**
3047  * Retrieves the URL for a given site where WordPress application files
3048  * (e.g. wp-blog-header.php or the wp-admin/ folder) are accessible.
3049  *
3050  * Returns the 'site_url' option with the appropriate protocol, 'https' if
3051  * is_ssl() and 'http' otherwise. If `$scheme` is 'http' or 'https',
3052  * `is_ssl()` is overridden.
3053  *
3054  * @since 3.0.0
3055  *
3056  * @param int    $blog_id Optional. Site ID. Default null (current site).
3057  * @param string $path    Optional. Path relative to the site URL. Default empty.
3058  * @param string $scheme  Optional. Scheme to give the site URL context. Accepts
3059  *                        'http', 'https', 'login', 'login_post', 'admin', or
3060  *                        'relative'. Default null.
3061  * @return string Site URL link with optional path appended.
3062  */
3063 function get_site_url( $blog_id = null, $path = '', $scheme = null ) {
3064         if ( empty( $blog_id ) || !is_multisite() ) {
3065                 $url = get_option( 'siteurl' );
3066         } else {
3067                 switch_to_blog( $blog_id );
3068                 $url = get_option( 'siteurl' );
3069                 restore_current_blog();
3070         }
3071
3072         $url = set_url_scheme( $url, $scheme );
3073
3074         if ( $path && is_string( $path ) )
3075                 $url .= '/' . ltrim( $path, '/' );
3076
3077         /**
3078          * Filters the site URL.
3079          *
3080          * @since 2.7.0
3081          *
3082          * @param string      $url     The complete site URL including scheme and path.
3083          * @param string      $path    Path relative to the site URL. Blank string if no path is specified.
3084          * @param string|null $scheme  Scheme to give the site URL context. Accepts 'http', 'https', 'login',
3085          *                             'login_post', 'admin', 'relative' or null.
3086          * @param int|null    $blog_id Site ID, or null for the current site.
3087          */
3088         return apply_filters( 'site_url', $url, $path, $scheme, $blog_id );
3089 }
3090
3091 /**
3092  * Retrieves the URL to the admin area for the current site.
3093  *
3094  * @since 2.6.0
3095  *
3096  * @param string $path   Optional path relative to the admin URL.
3097  * @param string $scheme The scheme to use. Default is 'admin', which obeys force_ssl_admin() and is_ssl().
3098  *                       'http' or 'https' can be passed to force those schemes.
3099  * @return string Admin URL link with optional path appended.
3100  */
3101 function admin_url( $path = '', $scheme = 'admin' ) {
3102         return get_admin_url( null, $path, $scheme );
3103 }
3104
3105 /**
3106  * Retrieves the URL to the admin area for a given site.
3107  *
3108  * @since 3.0.0
3109  *
3110  * @param int    $blog_id Optional. Site ID. Default null (current site).
3111  * @param string $path    Optional. Path relative to the admin URL. Default empty.
3112  * @param string $scheme  Optional. The scheme to use. Accepts 'http' or 'https',
3113  *                        to force those schemes. Default 'admin', which obeys
3114  *                        force_ssl_admin() and is_ssl().
3115  * @return string Admin URL link with optional path appended.
3116  */
3117 function get_admin_url( $blog_id = null, $path = '', $scheme = 'admin' ) {
3118         $url = get_site_url($blog_id, 'wp-admin/', $scheme);
3119
3120         if ( $path && is_string( $path ) )
3121                 $url .= ltrim( $path, '/' );
3122
3123         /**
3124          * Filters the admin area URL.
3125          *
3126          * @since 2.8.0
3127          *
3128          * @param string   $url     The complete admin area URL including scheme and path.
3129          * @param string   $path    Path relative to the admin area URL. Blank string if no path is specified.
3130          * @param int|null $blog_id Site ID, or null for the current site.
3131          */
3132         return apply_filters( 'admin_url', $url, $path, $blog_id );
3133 }
3134
3135 /**
3136  * Retrieves the URL to the includes directory.
3137  *
3138  * @since 2.6.0
3139  *
3140  * @param string $path   Optional. Path relative to the includes URL. Default empty.
3141  * @param string $scheme Optional. Scheme to give the includes URL context. Accepts
3142  *                       'http', 'https', or 'relative'. Default null.
3143  * @return string Includes URL link with optional path appended.
3144  */
3145 function includes_url( $path = '', $scheme = null ) {
3146         $url = site_url( '/' . WPINC . '/', $scheme );
3147
3148         if ( $path && is_string( $path ) )
3149                 $url .= ltrim($path, '/');
3150
3151         /**
3152          * Filters the URL to the includes directory.
3153          *
3154          * @since 2.8.0
3155          *
3156          * @param string $url  The complete URL to the includes directory including scheme and path.
3157          * @param string $path Path relative to the URL to the wp-includes directory. Blank string
3158          *                     if no path is specified.
3159          */
3160         return apply_filters( 'includes_url', $url, $path );
3161 }
3162
3163 /**
3164  * Retrieves the URL to the content directory.
3165  *
3166  * @since 2.6.0
3167  *
3168  * @param string $path Optional. Path relative to the content URL. Default empty.
3169  * @return string Content URL link with optional path appended.
3170  */
3171 function content_url( $path = '' ) {
3172         $url = set_url_scheme( WP_CONTENT_URL );
3173
3174         if ( $path && is_string( $path ) )
3175                 $url .= '/' . ltrim($path, '/');
3176
3177         /**
3178          * Filters the URL to the content directory.
3179          *
3180          * @since 2.8.0
3181          *
3182          * @param string $url  The complete URL to the content directory including scheme and path.
3183          * @param string $path Path relative to the URL to the content directory. Blank string
3184          *                     if no path is specified.
3185          */
3186         return apply_filters( 'content_url', $url, $path);
3187 }
3188
3189 /**
3190  * Retrieves a URL within the plugins or mu-plugins directory.
3191  *
3192  * Defaults to the plugins directory URL if no arguments are supplied.
3193  *
3194  * @since 2.6.0
3195  *
3196  * @param  string $path   Optional. Extra path appended to the end of the URL, including
3197  *                        the relative directory if $plugin is supplied. Default empty.
3198  * @param  string $plugin Optional. A full path to a file inside a plugin or mu-plugin.
3199  *                        The URL will be relative to its directory. Default empty.
3200  *                        Typically this is done by passing `__FILE__` as the argument.
3201  * @return string Plugins URL link with optional paths appended.
3202  */
3203 function plugins_url( $path = '', $plugin = '' ) {
3204
3205         $path = wp_normalize_path( $path );
3206         $plugin = wp_normalize_path( $plugin );
3207         $mu_plugin_dir = wp_normalize_path( WPMU_PLUGIN_DIR );
3208
3209         if ( !empty($plugin) && 0 === strpos($plugin, $mu_plugin_dir) )
3210                 $url = WPMU_PLUGIN_URL;
3211         else
3212                 $url = WP_PLUGIN_URL;
3213
3214
3215         $url = set_url_scheme( $url );
3216
3217         if ( !empty($plugin) && is_string($plugin) ) {
3218                 $folder = dirname(plugin_basename($plugin));
3219                 if ( '.' != $folder )
3220                         $url .= '/' . ltrim($folder, '/');
3221         }
3222
3223         if ( $path && is_string( $path ) )
3224                 $url .= '/' . ltrim($path, '/');
3225
3226         /**
3227          * Filters the URL to the plugins directory.
3228          *
3229          * @since 2.8.0
3230          *
3231          * @param string $url    The complete URL to the plugins directory including scheme and path.
3232          * @param string $path   Path relative to the URL to the plugins directory. Blank string
3233          *                       if no path is specified.
3234          * @param string $plugin The plugin file path to be relative to. Blank string if no plugin
3235          *                       is specified.
3236          */
3237         return apply_filters( 'plugins_url', $url, $path, $plugin );
3238 }
3239
3240 /**
3241  * Retrieves the site URL for the current network.
3242  *
3243  * Returns the site URL with the appropriate protocol, 'https' if
3244  * is_ssl() and 'http' otherwise. If $scheme is 'http' or 'https', is_ssl() is
3245  * overridden.
3246  *
3247  * @since 3.0.0
3248  *
3249  * @see set_url_scheme()
3250  *
3251  * @param string $path   Optional. Path relative to the site URL. Default empty.
3252  * @param string $scheme Optional. Scheme to give the site URL context. Accepts
3253  *                       'http', 'https', or 'relative'. Default null.
3254  * @return string Site URL link with optional path appended.
3255  */
3256 function network_site_url( $path = '', $scheme = null ) {
3257         if ( ! is_multisite() )
3258                 return site_url($path, $scheme);
3259
3260         $current_network = get_network();
3261
3262         if ( 'relative' == $scheme )
3263                 $url = $current_network->path;
3264         else
3265                 $url = set_url_scheme( 'http://' . $current_network->domain . $current_network->path, $scheme );
3266
3267         if ( $path && is_string( $path ) )
3268                 $url .= ltrim( $path, '/' );
3269
3270         /**
3271          * Filters the network site URL.
3272          *
3273          * @since 3.0.0
3274          *
3275          * @param string      $url    The complete network site URL including scheme and path.
3276          * @param string      $path   Path relative to the network site URL. Blank string if
3277          *                            no path is specified.
3278          * @param string|null $scheme Scheme to give the URL context. Accepts 'http', 'https',
3279          *                            'relative' or null.
3280          */
3281         return apply_filters( 'network_site_url', $url, $path, $scheme );
3282 }
3283
3284 /**
3285  * Retrieves the home URL for the current network.
3286  *
3287  * Returns the home URL with the appropriate protocol, 'https' is_ssl()
3288  * and 'http' otherwise. If `$scheme` is 'http' or 'https', `is_ssl()` is
3289  * overridden.
3290  *
3291  * @since 3.0.0
3292  *
3293  * @param  string $path   Optional. Path relative to the home URL. Default empty.
3294  * @param  string $scheme Optional. Scheme to give the home URL context. Accepts
3295  *                        'http', 'https', or 'relative'. Default null.
3296  * @return string Home URL link with optional path appended.
3297  */
3298 function network_home_url( $path = '', $scheme = null ) {
3299         if ( ! is_multisite() )
3300                 return home_url($path, $scheme);
3301
3302         $current_network = get_network();
3303         $orig_scheme = $scheme;
3304
3305         if ( ! in_array( $scheme, array( 'http', 'https', 'relative' ) ) )
3306                 $scheme = is_ssl() && ! is_admin() ? 'https' : 'http';
3307
3308         if ( 'relative' == $scheme )
3309                 $url = $current_network->path;
3310         else
3311                 $url = set_url_scheme( 'http://' . $current_network->domain . $current_network->path, $scheme );
3312
3313         if ( $path && is_string( $path ) )
3314                 $url .= ltrim( $path, '/' );
3315
3316         /**
3317          * Filters the network home URL.
3318          *
3319          * @since 3.0.0
3320          *
3321          * @param string      $url         The complete network home URL including scheme and path.
3322          * @param string      $path        Path relative to the network home URL. Blank string
3323          *                                 if no path is specified.
3324          * @param string|null $orig_scheme Scheme to give the URL context. Accepts 'http', 'https',
3325          *                                 'relative' or null.
3326          */
3327         return apply_filters( 'network_home_url', $url, $path, $orig_scheme);
3328 }
3329
3330 /**
3331  * Retrieves the URL to the admin area for the network.
3332  *
3333  * @since 3.0.0
3334  *
3335  * @param string $path   Optional path relative to the admin URL. Default empty.
3336  * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
3337  *                       and is_ssl(). 'http' or 'https' can be passed to force those schemes.
3338  * @return string Admin URL link with optional path appended.
3339  */
3340 function network_admin_url( $path = '', $scheme = 'admin' ) {
3341         if ( ! is_multisite() )
3342                 return admin_url( $path, $scheme );
3343
3344         $url = network_site_url('wp-admin/network/', $scheme);
3345
3346         if ( $path && is_string( $path ) )
3347                 $url .= ltrim($path, '/');
3348
3349         /**
3350          * Filters the network admin URL.
3351          *
3352          * @since 3.0.0
3353          *
3354          * @param string $url  The complete network admin URL including scheme and path.
3355          * @param string $path Path relative to the network admin URL. Blank string if
3356          *                     no path is specified.
3357          */
3358         return apply_filters( 'network_admin_url', $url, $path );
3359 }
3360
3361 /**
3362  * Retrieves the URL to the admin area for the current user.
3363  *
3364  * @since 3.0.0
3365  *
3366  * @param string $path   Optional. Path relative to the admin URL. Default empty.
3367  * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
3368  *                       and is_ssl(). 'http' or 'https' can be passed to force those schemes.
3369  * @return string Admin URL link with optional path appended.
3370  */
3371 function user_admin_url( $path = '', $scheme = 'admin' ) {
3372         $url = network_site_url('wp-admin/user/', $scheme);
3373
3374         if ( $path && is_string( $path ) )
3375                 $url .= ltrim($path, '/');
3376
3377         /**
3378          * Filters the user admin URL for the current user.
3379          *
3380          * @since 3.1.0
3381          *
3382          * @param string $url  The complete URL including scheme and path.
3383          * @param string $path Path relative to the URL. Blank string if
3384          *                     no path is specified.
3385          */
3386         return apply_filters( 'user_admin_url', $url, $path );
3387 }
3388
3389 /**
3390  * Retrieves the URL to the admin area for either the current site or the network depending on context.
3391  *
3392  * @since 3.1.0
3393  *
3394  * @param string $path   Optional. Path relative to the admin URL. Default empty.
3395  * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
3396  *                       and is_ssl(). 'http' or 'https' can be passed to force those schemes.
3397  * @return string Admin URL link with optional path appended.
3398  */
3399 function self_admin_url( $path = '', $scheme = 'admin' ) {
3400         if ( is_network_admin() )
3401                 return network_admin_url($path, $scheme);
3402         elseif ( is_user_admin() )
3403                 return user_admin_url($path, $scheme);
3404         else
3405                 return admin_url($path, $scheme);
3406 }
3407
3408 /**
3409  * Sets the scheme for a URL.
3410  *
3411  * @since 3.4.0
3412  * @since 4.4.0 The 'rest' scheme was added.
3413  *
3414  * @param string      $url    Absolute URL that includes a scheme
3415  * @param string|null $scheme Optional. Scheme to give $url. Currently 'http', 'https', 'login',
3416  *                            'login_post', 'admin', 'relative', 'rest', 'rpc', or null. Default null.
3417  * @return string $url URL with chosen scheme.
3418  */
3419 function set_url_scheme( $url, $scheme = null ) {
3420         $orig_scheme = $scheme;
3421
3422         if ( ! $scheme ) {
3423                 $scheme = is_ssl() ? 'https' : 'http';
3424         } elseif ( $scheme === 'admin' || $scheme === 'login' || $scheme === 'login_post' || $scheme === 'rpc' ) {
3425                 $scheme = is_ssl() || force_ssl_admin() ? 'https' : 'http';
3426         } elseif ( $scheme !== 'http' && $scheme !== 'https' && $scheme !== 'relative' ) {
3427                 $scheme = is_ssl() ? 'https' : 'http';
3428         }
3429
3430         $url = trim( $url );
3431         if ( substr( $url, 0, 2 ) === '//' )
3432                 $url = 'http:' . $url;
3433
3434         if ( 'relative' == $scheme ) {
3435                 $url = ltrim( preg_replace( '#^\w+://[^/]*#', '', $url ) );
3436                 if ( $url !== '' && $url[0] === '/' )
3437                         $url = '/' . ltrim($url , "/ \t\n\r\0\x0B" );
3438         } else {
3439                 $url = preg_replace( '#^\w+://#', $scheme . '://', $url );
3440         }
3441
3442         /**
3443          * Filters the resulting URL after setting the scheme.
3444          *
3445          * @since 3.4.0
3446          *
3447          * @param string      $url         The complete URL including scheme and path.
3448          * @param string      $scheme      Scheme applied to the URL. One of 'http', 'https', or 'relative'.
3449          * @param string|null $orig_scheme Scheme requested for the URL. One of 'http', 'https', 'login',
3450          *                                 'login_post', 'admin', 'relative', 'rest', 'rpc', or null.
3451          */
3452         return apply_filters( 'set_url_scheme', $url, $scheme, $orig_scheme );
3453 }
3454
3455 /**
3456  * Retrieves the URL to the user's dashboard.
3457  *
3458  * If a user does not belong to any site, the global user dashboard is used. If the user
3459  * belongs to the current site, the dashboard for the current site is returned. If the user
3460  * cannot edit the current site, the dashboard to the user's primary site is returned.
3461  *
3462  * @since 3.1.0
3463  *
3464  * @param int    $user_id Optional. User ID. Defaults to current user.
3465  * @param string $path    Optional path relative to the dashboard. Use only paths known to
3466  *                        both site and user admins. Default empty.
3467  * @param string $scheme  The scheme to use. Default is 'admin', which obeys force_ssl_admin()
3468  *                        and is_ssl(). 'http' or 'https' can be passed to force those schemes.
3469  * @return string Dashboard URL link with optional path appended.
3470  */
3471 function get_dashboard_url( $user_id = 0, $path = '', $scheme = 'admin' ) {
3472         $user_id = $user_id ? (int) $user_id : get_current_user_id();
3473
3474         $blogs = get_blogs_of_user( $user_id );
3475         if ( ! is_super_admin() && empty($blogs) ) {
3476                 $url = user_admin_url( $path, $scheme );
3477         } elseif ( ! is_multisite() ) {
3478                 $url = admin_url( $path, $scheme );
3479         } else {
3480                 $current_blog = get_current_blog_id();
3481                 if ( $current_blog  && ( is_super_admin( $user_id ) || in_array( $current_blog, array_keys( $blogs ) ) ) ) {
3482                         $url = admin_url( $path, $scheme );
3483                 } else {
3484                         $active = get_active_blog_for_user( $user_id );
3485                         if ( $active )
3486                                 $url = get_admin_url( $active->blog_id, $path, $scheme );
3487                         else
3488                                 $url = user_admin_url( $path, $scheme );
3489                 }
3490         }
3491
3492         /**
3493          * Filters the dashboard URL for a user.
3494          *
3495          * @since 3.1.0
3496          *
3497          * @param string $url     The complete URL including scheme and path.
3498          * @param int    $user_id The user ID.
3499          * @param string $path    Path relative to the URL. Blank string if no path is specified.
3500          * @param string $scheme  Scheme to give the URL context. Accepts 'http', 'https', 'login',
3501          *                        'login_post', 'admin', 'relative' or null.
3502          */
3503         return apply_filters( 'user_dashboard_url', $url, $user_id, $path, $scheme);
3504 }
3505
3506 /**
3507  * Retrieves the URL to the user's profile editor.
3508  *
3509  * @since 3.1.0
3510  *
3511  * @param int    $user_id Optional. User ID. Defaults to current user.
3512  * @param string $scheme  Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
3513  *                        and is_ssl(). 'http' or 'https' can be passed to force those schemes.
3514  * @return string Dashboard URL link with optional path appended.
3515  */
3516 function get_edit_profile_url( $user_id = 0, $scheme = 'admin' ) {
3517         $user_id = $user_id ? (int) $user_id : get_current_user_id();
3518
3519         if ( is_user_admin() )
3520                 $url = user_admin_url( 'profile.php', $scheme );
3521         elseif ( is_network_admin() )
3522                 $url = network_admin_url( 'profile.php', $scheme );
3523         else
3524                 $url = get_dashboard_url( $user_id, 'profile.php', $scheme );
3525
3526         /**
3527          * Filters the URL for a user's profile editor.
3528          *
3529          * @since 3.1.0
3530          *
3531          * @param string $url     The complete URL including scheme and path.
3532          * @param int    $user_id The user ID.
3533          * @param string $scheme  Scheme to give the URL context. Accepts 'http', 'https', 'login',
3534          *                        'login_post', 'admin', 'relative' or null.
3535          */
3536         return apply_filters( 'edit_profile_url', $url, $user_id, $scheme);
3537 }
3538
3539 /**
3540  * Returns the canonical URL for a post.
3541  *
3542  * When the post is the same as the current requested page the function will handle the
3543  * pagination arguments too.
3544  *
3545  * @since 4.6.0
3546  *
3547  * @param int|WP_Post $post Optional. Post ID or object. Default is global `$post`.
3548  * @return string|false The canonical URL, or false if the post does not exist or has not
3549  *                      been published yet.
3550  */
3551 function wp_get_canonical_url( $post = null ) {
3552         $post = get_post( $post );
3553
3554         if ( ! $post ) {
3555                 return false;
3556         }
3557
3558         if ( 'publish' !== $post->post_status ) {
3559                 return false;
3560         }
3561
3562         $canonical_url = get_permalink( $post );
3563
3564         // If a canonical is being generated for the current page, make sure it has pagination if needed.
3565         if ( $post->ID === get_queried_object_id() ) {
3566                 $page = get_query_var( 'page', 0 );
3567                 if ( $page >= 2 ) {
3568                         if ( '' == get_option( 'permalink_structure' ) ) {
3569                                 $canonical_url = add_query_arg( 'page', $page, $canonical_url );
3570                         } else {
3571                                 $canonical_url = trailingslashit( $canonical_url ) . user_trailingslashit( $page, 'single_paged' );
3572                         }
3573                 }
3574
3575                 $cpage = get_query_var( 'cpage', 0 );
3576                 if ( $cpage ) {
3577                         $canonical_url = get_comments_pagenum_link( $cpage );
3578                 }
3579         }
3580
3581         /**
3582          * Filters the canonical URL for a post.
3583          *
3584          * @since 4.6.0
3585          *
3586          * @param string  $string The post's canonical URL.
3587          * @param WP_Post $post   Post object.
3588          */
3589         return apply_filters( 'get_canonical_url', $canonical_url, $post );
3590 }
3591
3592 /**
3593  * Outputs rel=canonical for singular queries.
3594  *
3595  * @since 2.9.0
3596  * @since 4.6.0 Adjusted to use wp_get_canonical_url().
3597  */
3598 function rel_canonical() {
3599         if ( ! is_singular() ) {
3600                 return;
3601         }
3602
3603         $id = get_queried_object_id();
3604
3605         if ( 0 === $id ) {
3606                 return;
3607         }
3608
3609         $url = wp_get_canonical_url( $id );
3610
3611         if ( ! empty( $url ) ) {
3612                 echo '<link rel="canonical" href="' . esc_url( $url ) . '" />' . "\n";
3613         }
3614 }
3615
3616 /**
3617  * Returns a shortlink for a post, page, attachment, or site.
3618  *
3619  * This function exists to provide a shortlink tag that all themes and plugins can target.
3620  * A plugin must hook in to provide the actual shortlinks. Default shortlink support is
3621  * limited to providing ?p= style links for posts. Plugins can short-circuit this function
3622  * via the {@see 'pre_get_shortlink'} filter or filter the output via the {@see 'get_shortlink'}
3623  * filter.
3624  *
3625  * @since 3.0.0.
3626  *
3627  * @param int    $id          Optional. A post or site id. Default is 0, which means the current post or site.
3628  * @param string $context     Optional. Whether the id is a 'site' id, 'post' id, or 'media' id. If 'post',
3629  *                            the post_type of the post is consulted. If 'query', the current query is consulted
3630  *                            to determine the id and context. Default 'post'.
3631  * @param bool   $allow_slugs Optional. Whether to allow post slugs in the shortlink. It is up to the plugin how
3632  *                            and whether to honor this. Default true.
3633  * @return string A shortlink or an empty string if no shortlink exists for the requested resource or if shortlinks
3634  *                are not enabled.
3635  */
3636 function wp_get_shortlink( $id = 0, $context = 'post', $allow_slugs = true ) {
3637         /**
3638          * Filters whether to preempt generating a shortlink for the given post.
3639          *
3640          * Passing a truthy value to the filter will effectively short-circuit the
3641          * shortlink-generation process, returning that value instead.
3642          *
3643          * @since 3.0.0
3644          *
3645          * @param bool|string $return      Short-circuit return value. Either false or a URL string.
3646          * @param int         $id          Post ID, or 0 for the current post.
3647          * @param string      $context     The context for the link. One of 'post' or 'query',
3648          * @param bool        $allow_slugs Whether to allow post slugs in the shortlink.
3649          */
3650         $shortlink = apply_filters( 'pre_get_shortlink', false, $id, $context, $allow_slugs );
3651
3652         if ( false !== $shortlink ) {
3653                 return $shortlink;
3654         }
3655
3656         $post_id = 0;
3657         if ( 'query' == $context && is_singular() ) {
3658                 $post_id = get_queried_object_id();
3659                 $post = get_post( $post_id );
3660         } elseif ( 'post' == $context ) {
3661                 $post = get_post( $id );
3662                 if ( ! empty( $post->ID ) )
3663                         $post_id = $post->ID;
3664         }
3665
3666         $shortlink = '';
3667
3668         // Return p= link for all public post types.
3669         if ( ! empty( $post_id ) ) {
3670                 $post_type = get_post_type_object( $post->post_type );
3671
3672                 if ( 'page' === $post->post_type && $post->ID == get_option( 'page_on_front' ) && 'page' == get_option( 'show_on_front' ) ) {
3673                         $shortlink = home_url( '/' );
3674                 } elseif ( $post_type->public ) {
3675                         $shortlink = home_url( '?p=' . $post_id );
3676                 }
3677         }
3678
3679         /**
3680          * Filters the shortlink for a post.
3681          *
3682          * @since 3.0.0
3683          *
3684          * @param string $shortlink   Shortlink URL.
3685          * @param int    $id          Post ID, or 0 for the current post.
3686          * @param string $context     The context for the link. One of 'post' or 'query',
3687          * @param bool   $allow_slugs Whether to allow post slugs in the shortlink. Not used by default.
3688          */
3689         return apply_filters( 'get_shortlink', $shortlink, $id, $context, $allow_slugs );
3690 }
3691
3692 /**
3693  * Injects rel=shortlink into the head if a shortlink is defined for the current page.
3694  *
3695  * Attached to the {@see 'wp_head'} action.
3696  *
3697  * @since 3.0.0
3698  */
3699 function wp_shortlink_wp_head() {
3700         $shortlink = wp_get_shortlink( 0, 'query' );
3701
3702         if ( empty( $shortlink ) )
3703                 return;
3704
3705         echo "<link rel='shortlink' href='" . esc_url( $shortlink ) . "' />\n";
3706 }
3707
3708 /**
3709  * Sends a Link: rel=shortlink header if a shortlink is defined for the current page.
3710  *
3711  * Attached to the {@see 'wp'} action.
3712  *
3713  * @since 3.0.0
3714  */
3715 function wp_shortlink_header() {
3716         if ( headers_sent() )
3717                 return;
3718
3719         $shortlink = wp_get_shortlink(0, 'query');
3720
3721         if ( empty($shortlink) )
3722                 return;
3723
3724         header('Link: <' . $shortlink . '>; rel=shortlink', false);
3725 }
3726
3727 /**
3728  * Displays the shortlink for a post.
3729  *
3730  * Must be called from inside "The Loop"
3731  *
3732  * Call like the_shortlink( __( 'Shortlinkage FTW' ) )
3733  *
3734  * @since 3.0.0
3735  *
3736  * @param string $text   Optional The link text or HTML to be displayed. Defaults to 'This is the short link.'
3737  * @param string $title  Optional The tooltip for the link. Must be sanitized. Defaults to the sanitized post title.
3738  * @param string $before Optional HTML to display before the link. Default empty.
3739  * @param string $after  Optional HTML to display after the link. Default empty.
3740  */
3741 function the_shortlink( $text = '', $title = '', $before = '', $after = '' ) {
3742         $post = get_post();
3743
3744         if ( empty( $text ) )
3745                 $text = __('This is the short link.');
3746
3747         if ( empty( $title ) )
3748                 $title = the_title_attribute( array( 'echo' => false ) );
3749
3750         $shortlink = wp_get_shortlink( $post->ID );
3751
3752         if ( !empty( $shortlink ) ) {
3753                 $link = '<a rel="shortlink" href="' . esc_url( $shortlink ) . '" title="' . $title . '">' . $text . '</a>';
3754
3755                 /**
3756                  * Filters the short link anchor tag for a post.
3757                  *
3758                  * @since 3.0.0
3759                  *
3760                  * @param string $link      Shortlink anchor tag.
3761                  * @param string $shortlink Shortlink URL.
3762                  * @param string $text      Shortlink's text.
3763                  * @param string $title     Shortlink's title attribute.
3764                  */
3765                 $link = apply_filters( 'the_shortlink', $link, $shortlink, $text, $title );
3766                 echo $before, $link, $after;
3767         }
3768 }
3769
3770
3771 /**
3772  * Retrieves the avatar URL.
3773  *
3774  * @since 4.2.0
3775  *
3776  * @param mixed $id_or_email The Gravatar to retrieve a URL for. Accepts a user_id, gravatar md5 hash,
3777  *                           user email, WP_User object, WP_Post object, or WP_Comment object.
3778  * @param array $args {
3779  *     Optional. Arguments to return instead of the default arguments.
3780  *
3781  *     @type int    $size           Height and width of the avatar in pixels. Default 96.
3782  *     @type string $default        URL for the default image or a default type. Accepts '404' (return
3783  *                                  a 404 instead of a default image), 'retro' (8bit), 'monsterid' (monster),
3784  *                                  'wavatar' (cartoon face), 'indenticon' (the "quilt"), 'mystery', 'mm',
3785  *                                  or 'mysteryman' (The Oyster Man), 'blank' (transparent GIF), or
3786  *                                  'gravatar_default' (the Gravatar logo). Default is the value of the
3787  *                                  'avatar_default' option, with a fallback of 'mystery'.
3788  *     @type bool   $force_default  Whether to always show the default image, never the Gravatar. Default false.
3789  *     @type string $rating         What rating to display avatars up to. Accepts 'G', 'PG', 'R', 'X', and are
3790  *                                  judged in that order. Default is the value of the 'avatar_rating' option.
3791  *     @type string $scheme         URL scheme to use. See set_url_scheme() for accepted values.
3792  *                                  Default null.
3793  *     @type array  $processed_args When the function returns, the value will be the processed/sanitized $args
3794  *                                  plus a "found_avatar" guess. Pass as a reference. Default null.
3795  * }
3796  * @return false|string The URL of the avatar we found, or false if we couldn't find an avatar.
3797  */
3798 function get_avatar_url( $id_or_email, $args = null ) {
3799         $args = get_avatar_data( $id_or_email, $args );
3800         return $args['url'];
3801 }
3802
3803 /**
3804  * Retrieves default data about the avatar.
3805  *
3806  * @since 4.2.0
3807  *
3808  * @param mixed $id_or_email The Gravatar to retrieve. Accepts a user_id, gravatar md5 hash,
3809  *                            user email, WP_User object, WP_Post object, or WP_Comment object.
3810  * @param array $args {
3811  *     Optional. Arguments to return instead of the default arguments.
3812  *
3813  *     @type int    $size           Height and width of the avatar image file in pixels. Default 96.
3814  *     @type int    $height         Display height of the avatar in pixels. Defaults to $size.
3815  *     @type int    $width          Display width of the avatar in pixels. Defaults to $size.
3816  *     @type string $default        URL for the default image or a default type. Accepts '404' (return
3817  *                                  a 404 instead of a default image), 'retro' (8bit), 'monsterid' (monster),
3818  *                                  'wavatar' (cartoon face), 'indenticon' (the "quilt"), 'mystery', 'mm',
3819  *                                  or 'mysteryman' (The Oyster Man), 'blank' (transparent GIF), or
3820  *                                  'gravatar_default' (the Gravatar logo). Default is the value of the
3821  *                                  'avatar_default' option, with a fallback of 'mystery'.
3822  *     @type bool   $force_default  Whether to always show the default image, never the Gravatar. Default false.
3823  *     @type string $rating         What rating to display avatars up to. Accepts 'G', 'PG', 'R', 'X', and are
3824  *                                  judged in that order. Default is the value of the 'avatar_rating' option.
3825  *     @type string $scheme         URL scheme to use. See set_url_scheme() for accepted values.
3826  *                                  Default null.
3827  *     @type array  $processed_args When the function returns, the value will be the processed/sanitized $args
3828  *                                  plus a "found_avatar" guess. Pass as a reference. Default null.
3829  *     @type string $extra_attr     HTML attributes to insert in the IMG element. Is not sanitized. Default empty.
3830  * }
3831  * @return array $processed_args {
3832  *     Along with the arguments passed in `$args`, this will contain a couple of extra arguments.
3833  *
3834  *     @type bool   $found_avatar True if we were able to find an avatar for this user,
3835  *                                false or not set if we couldn't.
3836  *     @type string $url          The URL of the avatar we found.
3837  * }
3838  */
3839 function get_avatar_data( $id_or_email, $args = null ) {
3840         $args = wp_parse_args( $args, array(
3841                 'size'           => 96,
3842                 'height'         => null,
3843                 'width'          => null,
3844                 'default'        => get_option( 'avatar_default', 'mystery' ),
3845                 'force_default'  => false,
3846                 'rating'         => get_option( 'avatar_rating' ),
3847                 'scheme'         => null,
3848                 'processed_args' => null, // if used, should be a reference
3849                 'extra_attr'     => '',
3850         ) );
3851
3852         if ( is_numeric( $args['size'] ) ) {
3853                 $args['size'] = absint( $args['size'] );
3854                 if ( ! $args['size'] ) {
3855                         $args['size'] = 96;
3856                 }
3857         } else {
3858                 $args['size'] = 96;
3859         }
3860
3861         if ( is_numeric( $args['height'] ) ) {
3862                 $args['height'] = absint( $args['height'] );
3863                 if ( ! $args['height'] ) {
3864                         $args['height'] = $args['size'];
3865                 }
3866         } else {
3867                 $args['height'] = $args['size'];
3868         }
3869
3870         if ( is_numeric( $args['width'] ) ) {
3871                 $args['width'] = absint( $args['width'] );
3872                 if ( ! $args['width'] ) {
3873                         $args['width'] = $args['size'];
3874                 }
3875         } else {
3876                 $args['width'] = $args['size'];
3877         }
3878
3879         if ( empty( $args['default'] ) ) {
3880                 $args['default'] = get_option( 'avatar_default', 'mystery' );
3881         }
3882
3883         switch ( $args['default'] ) {
3884                 case 'mm' :
3885                 case 'mystery' :
3886                 case 'mysteryman' :
3887                         $args['default'] = 'mm';
3888                         break;
3889                 case 'gravatar_default' :
3890                         $args['default'] = false;
3891                         break;
3892         }
3893
3894         $args['force_default'] = (bool) $args['force_default'];
3895
3896         $args['rating'] = strtolower( $args['rating'] );
3897
3898         $args['found_avatar'] = false;
3899
3900         /**
3901          * Filters whether to retrieve the avatar URL early.
3902          *
3903          * Passing a non-null value in the 'url' member of the return array will
3904          * effectively short circuit get_avatar_data(), passing the value through
3905          * the {@see 'get_avatar_data'} filter and returning early.
3906          *
3907          * @since 4.2.0
3908          *
3909          * @param array  $args        Arguments passed to get_avatar_data(), after processing.
3910          * @param mixed  $id_or_email The Gravatar to retrieve. Accepts a user_id, gravatar md5 hash,
3911          *                            user email, WP_User object, WP_Post object, or WP_Comment object.
3912          */
3913         $args = apply_filters( 'pre_get_avatar_data', $args, $id_or_email );
3914
3915         if ( isset( $args['url'] ) && ! is_null( $args['url'] ) ) {
3916                 /** This filter is documented in wp-includes/link-template.php */
3917                 return apply_filters( 'get_avatar_data', $args, $id_or_email );
3918         }
3919
3920         $email_hash = '';
3921         $user = $email = false;
3922
3923         if ( is_object( $id_or_email ) && isset( $id_or_email->comment_ID ) ) {
3924                 $id_or_email = get_comment( $id_or_email );
3925         }
3926
3927         // Process the user identifier.
3928         if ( is_numeric( $id_or_email ) ) {
3929                 $user = get_user_by( 'id', absint( $id_or_email ) );
3930         } elseif ( is_string( $id_or_email ) ) {
3931                 if ( strpos( $id_or_email, '@md5.gravatar.com' ) ) {
3932                         // md5 hash
3933                         list( $email_hash ) = explode( '@', $id_or_email );
3934                 } else {
3935                         // email address
3936                         $email = $id_or_email;
3937                 }
3938         } elseif ( $id_or_email instanceof WP_User ) {
3939                 // User Object
3940                 $user = $id_or_email;
3941         } elseif ( $id_or_email instanceof WP_Post ) {
3942                 // Post Object
3943                 $user = get_user_by( 'id', (int) $id_or_email->post_author );
3944         } elseif ( $id_or_email instanceof WP_Comment ) {
3945                 /**
3946                  * Filters the list of allowed comment types for retrieving avatars.
3947                  *
3948                  * @since 3.0.0
3949                  *
3950                  * @param array $types An array of content types. Default only contains 'comment'.
3951                  */
3952                 $allowed_comment_types = apply_filters( 'get_avatar_comment_types', array( 'comment' ) );
3953                 if ( ! empty( $id_or_email->comment_type ) && ! in_array( $id_or_email->comment_type, (array) $allowed_comment_types ) ) {
3954                         $args['url'] = false;
3955                         /** This filter is documented in wp-includes/link-template.php */
3956                         return apply_filters( 'get_avatar_data', $args, $id_or_email );
3957                 }
3958
3959                 if ( ! empty( $id_or_email->user_id ) ) {
3960                         $user = get_user_by( 'id', (int) $id_or_email->user_id );
3961                 }
3962                 if ( ( ! $user || is_wp_error( $user ) ) && ! empty( $id_or_email->comment_author_email ) ) {
3963                         $email = $id_or_email->comment_author_email;
3964                 }
3965         }
3966
3967         if ( ! $email_hash ) {
3968                 if ( $user ) {
3969                         $email = $user->user_email;
3970                 }
3971
3972                 if ( $email ) {
3973                         $email_hash = md5( strtolower( trim( $email ) ) );
3974                 }
3975         }
3976
3977         if ( $email_hash ) {
3978                 $args['found_avatar'] = true;
3979                 $gravatar_server = hexdec( $email_hash[0] ) % 3;
3980         } else {
3981                 $gravatar_server = rand( 0, 2 );
3982         }
3983
3984         $url_args = array(
3985                 's' => $args['size'],
3986                 'd' => $args['default'],
3987                 'f' => $args['force_default'] ? 'y' : false,
3988                 'r' => $args['rating'],
3989         );
3990
3991         if ( is_ssl() ) {
3992                 $url = 'https://secure.gravatar.com/avatar/' . $email_hash;
3993         } else {
3994                 $url = sprintf( 'http://%d.gravatar.com/avatar/%s', $gravatar_server, $email_hash );
3995         }
3996
3997         $url = add_query_arg(
3998                 rawurlencode_deep( array_filter( $url_args ) ),
3999                 set_url_scheme( $url, $args['scheme'] )
4000         );
4001
4002         /**
4003          * Filters the avatar URL.
4004          *
4005          * @since 4.2.0
4006          *
4007          * @param string $url         The URL of the avatar.
4008          * @param mixed  $id_or_email The Gravatar to retrieve. Accepts a user_id, gravatar md5 hash,
4009          *                            user email, WP_User object, WP_Post object, or WP_Comment object.
4010          * @param array  $args        Arguments passed to get_avatar_data(), after processing.
4011          */
4012         $args['url'] = apply_filters( 'get_avatar_url', $url, $id_or_email, $args );
4013
4014         /**
4015          * Filters the avatar data.
4016          *
4017          * @since 4.2.0
4018          *
4019          * @param array  $args        Arguments passed to get_avatar_data(), after processing.
4020          * @param mixed  $id_or_email The Gravatar to retrieve. Accepts a user_id, gravatar md5 hash,
4021          *                            user email, WP_User object, WP_Post object, or WP_Comment object.
4022          */
4023         return apply_filters( 'get_avatar_data', $args, $id_or_email );
4024 }
4025
4026 /**
4027  * Retrieves the URL of a file in the theme.
4028  *
4029  * Searches in the stylesheet directory before the template directory so themes
4030  * which inherit from a parent theme can just override one file.
4031  *
4032  * @since 4.7.0
4033  *
4034  * @param string $file Optional. File to search for in the stylesheet directory.
4035  * @return string The URL of the file.
4036  */
4037 function get_theme_file_uri( $file = '' ) {
4038         $file = ltrim( $file, '/' );
4039
4040         if ( empty( $file ) ) {
4041                 $url = get_stylesheet_directory_uri();
4042         } elseif ( file_exists( get_stylesheet_directory() . '/' . $file ) ) {
4043                 $url = get_stylesheet_directory_uri() . '/' . $file;
4044         } else {
4045                 $url = get_template_directory_uri() . '/' . $file;
4046         }
4047
4048         /**
4049          * Filters the URL to a file in the theme.
4050          *
4051          * @since 4.7.0
4052          *
4053          * @param string $url  The file URL.
4054          * @param string $file The requested file to search for.
4055          */
4056         return apply_filters( 'theme_file_uri', $url, $file );
4057 }
4058
4059 /**
4060  * Retrieves the URL of a file in the parent theme.
4061  *
4062  * @since 4.7.0
4063  *
4064  * @param string $file Optional. File to return the URL for in the template directory.
4065  * @return string The URL of the file.
4066  */
4067 function get_parent_theme_file_uri( $file = '' ) {
4068         $file = ltrim( $file, '/' );
4069
4070         if ( empty( $file ) ) {
4071                 $url = get_template_directory_uri();
4072         } else {
4073                 $url = get_template_directory_uri() . '/' . $file;
4074         }
4075
4076         /**
4077          * Filters the URL to a file in the parent theme.
4078          *
4079          * @since 4.7.0
4080          *
4081          * @param string $url  The file URL.
4082          * @param string $file The requested file to search for.
4083          */
4084         return apply_filters( 'parent_theme_file_uri', $url, $file );
4085 }
4086
4087 /**
4088  * Retrieves the path of a file in the theme.
4089  *
4090  * Searches in the stylesheet directory before the template directory so themes
4091  * which inherit from a parent theme can just override one file.
4092  *
4093  * @since 4.7.0
4094  *
4095  * @param string $file Optional. File to search for in the stylesheet directory.
4096  * @return string The path of the file.
4097  */
4098 function get_theme_file_path( $file = '' ) {
4099         $file = ltrim( $file, '/' );
4100
4101         if ( empty( $file ) ) {
4102                 $path = get_stylesheet_directory();
4103         } elseif ( file_exists( get_stylesheet_directory() . '/' . $file ) ) {
4104                 $path = get_stylesheet_directory() . '/' . $file;
4105         } else {
4106                 $path = get_template_directory() . '/' . $file;
4107         }
4108
4109         /**
4110          * Filters the path to a file in the theme.
4111          *
4112          * @since 4.7.0
4113          *
4114          * @param string $path The file path.
4115          * @param string $file The requested file to search for.
4116          */
4117         return apply_filters( 'theme_file_path', $path, $file );
4118 }
4119
4120 /**
4121  * Retrieves the path of a file in the parent theme.
4122  *
4123  * @since 4.7.0
4124  *
4125  * @param string $file Optional. File to return the path for in the template directory.
4126  * @return string The path of the file.
4127  */
4128 function get_parent_theme_file_path( $file = '' ) {
4129         $file = ltrim( $file, '/' );
4130
4131         if ( empty( $file ) ) {
4132                 $path = get_template_directory();
4133         } else {
4134                 $path = get_template_directory() . '/' . $file;
4135         }
4136
4137         /**
4138          * Filters the path to a file in the parent theme.
4139          *
4140          * @since 4.7.0
4141          *
4142          * @param string $path The file path.
4143          * @param string $file The requested file to search for.
4144          */
4145         return apply_filters( 'parent_theme_file_path', $path, $file );
4146 }