Make WordPress Core

source: trunk/src/wp-includes/link-template.php @ 41584

Last change on this file since 41584 was 41584, checked in by azaozz, 5 years ago

Retire Press This and extract it to a plugin. First run.

Props kraftbj, azaozz.
See #41689.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 130.6 KB
Line 
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 */
17function 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 */
47function 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 */
77function 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 */
105function 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 */
118function 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|WP_Post $id        Optional. Post ID or post object. Default is 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 */
255function 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( $post ) && in_array( get_post_status( $post ), 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( $post );
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 */
314function 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 */
350function _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 */
394function 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 */
449function 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 */
483function 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 */
522function 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 */
562function 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 */
587function 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 */
633function 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 */
696function 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 */
727function 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 */
771function 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 */
788function 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 $taxonomy 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 */
866function 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 */
879function 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 */
901function 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 */
928function 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 */
981function 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 */
1024function 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 */
1065function 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 */
1104function 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 */
1134function 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 */
1186function 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 */
1229function 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|WP_Post $id      Optional. Post ID or post object. Default is 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 */
1270function 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|WP_Post $id     Optional. Post ID or post object. Default is the global `$post`.
1317 * @param string      $class  Optional. Add custom class to link. Default 'post-edit-link'.
1318 */
1319function 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|WP_Post $id           Optional. Post ID or post object. Default is 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 */
1358function 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 */
1396function 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 */
1423function 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 */
1456function 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 */
1485function 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 */
1515function 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 */
1556function 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 */
1571function 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 */
1591function 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 */
1767function 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 */
1815function 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 */
1830function 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 */
1849function 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 */
1865function 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 */
1885function 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 */
1941function 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 */
1958function 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 */
1974function 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 */
1990function 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 */
2009function 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 */
2071function 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 */
2087function 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 */
2163function 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 */
2184function 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 */
2205function 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 */
2241function 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 */
2258function 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 */
2277function 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 */
2296function 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 */
2322function 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 */
2344function 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 */
2384function 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 */
2407function 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 */
2451function 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 */
2474function 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 */
2510function 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 */
2527function 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 */
2563function 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 */
2578function _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 */
2623function 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 */
2667function 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 */
2711function 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 */
2723function 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 */
2754function 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 */
2769function 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 */
2815function 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 */
2850function 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 */
2868function 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 a string back. Plain is the next best thing.
2876        if ( isset( $args['type'] ) && 'array' == $args['type'] ) {
2877                $args['type'] = 'plain';
2878        }
2879
2880        $links = paginate_comments_links( $args );
2881
2882        if ( $links ) {
2883                $navigation = _navigation_markup( $links, 'comments-pagination', $args['screen_reader_text'] );
2884        }
2885
2886        return $navigation;
2887}
2888
2889/**
2890 * Displays a paginated navigation to next/previous set of comments, when applicable.
2891 *
2892 * @since 4.4.0
2893 *
2894 * @param array $args See get_the_comments_pagination() for available arguments. Default empty array.
2895 */
2896function the_comments_pagination( $args = array() ) {
2897        echo get_the_comments_pagination( $args );
2898}
2899
2900/**
2901 * Retrieves the URL for the current site where the front end is accessible.
2902 *
2903 * Returns the 'home' option with the appropriate protocol. The protocol will be 'https'
2904 * if is_ssl() evaluates to true; otherwise, it will be the same as the 'home' option.
2905 * If `$scheme` is 'http' or 'https', is_ssl() is overridden.
2906 *
2907 * @since 3.0.0
2908 *
2909 * @param  string      $path   Optional. Path relative to the home URL. Default empty.
2910 * @param  string|null $scheme Optional. Scheme to give the home URL context. Accepts
2911 *                             'http', 'https', 'relative', 'rest', or null. Default null.
2912 * @return string Home URL link with optional path appended.
2913 */
2914function home_url( $path = '', $scheme = null ) {
2915        return get_home_url( null, $path, $scheme );
2916}
2917
2918/**
2919 * Retrieves the URL for a given site where the front end is accessible.
2920 *
2921 * Returns the 'home' option with the appropriate protocol. The protocol will be 'https'
2922 * if is_ssl() evaluates to true; otherwise, it will be the same as the 'home' option.
2923 * If `$scheme` is 'http' or 'https', is_ssl() is overridden.
2924 *
2925 * @since 3.0.0
2926 *
2927 * @global string $pagenow
2928 *
2929 * @param  int         $blog_id Optional. Site ID. Default null (current site).
2930 * @param  string      $path    Optional. Path relative to the home URL. Default empty.
2931 * @param  string|null $scheme  Optional. Scheme to give the home URL context. Accepts
2932 *                              'http', 'https', 'relative', 'rest', or null. Default null.
2933 * @return string Home URL link with optional path appended.
2934 */
2935function get_home_url( $blog_id = null, $path = '', $scheme = null ) {
2936        global $pagenow;
2937
2938        $orig_scheme = $scheme;
2939
2940        if ( empty( $blog_id ) || !is_multisite() ) {
2941                $url = get_option( 'home' );
2942        } else {
2943                switch_to_blog( $blog_id );
2944                $url = get_option( 'home' );
2945                restore_current_blog();
2946        }
2947
2948        if ( ! in_array( $scheme, array( 'http', 'https', 'relative' ) ) ) {
2949                if ( is_ssl() && ! is_admin() && 'wp-login.php' !== $pagenow )
2950                        $scheme = 'https';
2951                else
2952                        $scheme = parse_url( $url, PHP_URL_SCHEME );
2953        }
2954
2955        $url = set_url_scheme( $url, $scheme );
2956
2957        if ( $path && is_string( $path ) )
2958                $url .= '/' . ltrim( $path, '/' );
2959
2960        /**
2961         * Filters the home URL.
2962         *
2963         * @since 3.0.0
2964         *
2965         * @param string      $url         The complete home URL including scheme and path.
2966         * @param string      $path        Path relative to the home URL. Blank string if no path is specified.
2967         * @param string|null $orig_scheme Scheme to give the home URL context. Accepts 'http', 'https',
2968         *                                 'relative', 'rest', or null.
2969         * @param int|null    $blog_id     Site ID, or null for the current site.
2970         */
2971        return apply_filters( 'home_url', $url, $path, $orig_scheme, $blog_id );
2972}
2973
2974/**
2975 * Retrieves the URL for the current site where WordPress application files
2976 * (e.g. wp-blog-header.php or the wp-admin/ folder) are accessible.
2977 *
2978 * Returns the 'site_url' option with the appropriate protocol, 'https' if
2979 * is_ssl() and 'http' otherwise. If $scheme is 'http' or 'https', is_ssl() is
2980 * overridden.
2981 *
2982 * @since 3.0.0
2983 *
2984 * @param string $path   Optional. Path relative to the site URL. Default empty.
2985 * @param string $scheme Optional. Scheme to give the site URL context. See set_url_scheme().
2986 * @return string Site URL link with optional path appended.
2987 */
2988function site_url( $path = '', $scheme = null ) {
2989        return get_site_url( null, $path, $scheme );
2990}
2991
2992/**
2993 * Retrieves the URL for a given site where WordPress application files
2994 * (e.g. wp-blog-header.php or the wp-admin/ folder) are accessible.
2995 *
2996 * Returns the 'site_url' option with the appropriate protocol, 'https' if
2997 * is_ssl() and 'http' otherwise. If `$scheme` is 'http' or 'https',
2998 * `is_ssl()` is overridden.
2999 *
3000 * @since 3.0.0
3001 *
3002 * @param int    $blog_id Optional. Site ID. Default null (current site).
3003 * @param string $path    Optional. Path relative to the site URL. Default empty.
3004 * @param string $scheme  Optional. Scheme to give the site URL context. Accepts
3005 *                        'http', 'https', 'login', 'login_post', 'admin', or
3006 *                        'relative'. Default null.
3007 * @return string Site URL link with optional path appended.
3008 */
3009function get_site_url( $blog_id = null, $path = '', $scheme = null ) {
3010        if ( empty( $blog_id ) || !is_multisite() ) {
3011                $url = get_option( 'siteurl' );
3012        } else {
3013                switch_to_blog( $blog_id );
3014                $url = get_option( 'siteurl' );
3015                restore_current_blog();
3016        }
3017
3018        $url = set_url_scheme( $url, $scheme );
3019
3020        if ( $path && is_string( $path ) )
3021                $url .= '/' . ltrim( $path, '/' );
3022
3023        /**
3024         * Filters the site URL.
3025         *
3026         * @since 2.7.0
3027         *
3028         * @param string      $url     The complete site URL including scheme and path.
3029         * @param string      $path    Path relative to the site URL. Blank string if no path is specified.
3030         * @param string|null $scheme  Scheme to give the site URL context. Accepts 'http', 'https', 'login',
3031         *                             'login_post', 'admin', 'relative' or null.
3032         * @param int|null    $blog_id Site ID, or null for the current site.
3033         */
3034        return apply_filters( 'site_url', $url, $path, $scheme, $blog_id );
3035}
3036
3037/**
3038 * Retrieves the URL to the admin area for the current site.
3039 *
3040 * @since 2.6.0
3041 *
3042 * @param string $path   Optional path relative to the admin URL.
3043 * @param string $scheme The scheme to use. Default is 'admin', which obeys force_ssl_admin() and is_ssl().
3044 *                       'http' or 'https' can be passed to force those schemes.
3045 * @return string Admin URL link with optional path appended.
3046 */
3047function admin_url( $path = '', $scheme = 'admin' ) {
3048        return get_admin_url( null, $path, $scheme );
3049}
3050
3051/**
3052 * Retrieves the URL to the admin area for a given site.
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 admin URL. Default empty.
3058 * @param string $scheme  Optional. The scheme to use. Accepts 'http' or 'https',
3059 *                        to force those schemes. Default 'admin', which obeys
3060 *                        force_ssl_admin() and is_ssl().
3061 * @return string Admin URL link with optional path appended.
3062 */
3063function get_admin_url( $blog_id = null, $path = '', $scheme = 'admin' ) {
3064        $url = get_site_url($blog_id, 'wp-admin/', $scheme);
3065
3066        if ( $path && is_string( $path ) )
3067                $url .= ltrim( $path, '/' );
3068
3069        /**
3070         * Filters the admin area URL.
3071         *
3072         * @since 2.8.0
3073         *
3074         * @param string   $url     The complete admin area URL including scheme and path.
3075         * @param string   $path    Path relative to the admin area URL. Blank string if no path is specified.
3076         * @param int|null $blog_id Site ID, or null for the current site.
3077         */
3078        return apply_filters( 'admin_url', $url, $path, $blog_id );
3079}
3080
3081/**
3082 * Retrieves the URL to the includes directory.
3083 *
3084 * @since 2.6.0
3085 *
3086 * @param string $path   Optional. Path relative to the includes URL. Default empty.
3087 * @param string $scheme Optional. Scheme to give the includes URL context. Accepts
3088 *                       'http', 'https', or 'relative'. Default null.
3089 * @return string Includes URL link with optional path appended.
3090 */
3091function includes_url( $path = '', $scheme = null ) {
3092        $url = site_url( '/' . WPINC . '/', $scheme );
3093
3094        if ( $path && is_string( $path ) )
3095                $url .= ltrim($path, '/');
3096
3097        /**
3098         * Filters the URL to the includes directory.
3099         *
3100         * @since 2.8.0
3101         *
3102         * @param string $url  The complete URL to the includes directory including scheme and path.
3103         * @param string $path Path relative to the URL to the wp-includes directory. Blank string
3104         *                     if no path is specified.
3105         */
3106        return apply_filters( 'includes_url', $url, $path );
3107}
3108
3109/**
3110 * Retrieves the URL to the content directory.
3111 *
3112 * @since 2.6.0
3113 *
3114 * @param string $path Optional. Path relative to the content URL. Default empty.
3115 * @return string Content URL link with optional path appended.
3116 */
3117function content_url( $path = '' ) {
3118        $url = set_url_scheme( WP_CONTENT_URL );
3119
3120        if ( $path && is_string( $path ) )
3121                $url .= '/' . ltrim($path, '/');
3122
3123        /**
3124         * Filters the URL to the content directory.
3125         *
3126         * @since 2.8.0
3127         *
3128         * @param string $url  The complete URL to the content directory including scheme and path.
3129         * @param string $path Path relative to the URL to the content directory. Blank string
3130         *                     if no path is specified.
3131         */
3132        return apply_filters( 'content_url', $url, $path);
3133}
3134
3135/**
3136 * Retrieves a URL within the plugins or mu-plugins directory.
3137 *
3138 * Defaults to the plugins directory URL if no arguments are supplied.
3139 *
3140 * @since 2.6.0
3141 *
3142 * @param  string $path   Optional. Extra path appended to the end of the URL, including
3143 *                        the relative directory if $plugin is supplied. Default empty.
3144 * @param  string $plugin Optional. A full path to a file inside a plugin or mu-plugin.
3145 *                        The URL will be relative to its directory. Default empty.
3146 *                        Typically this is done by passing `__FILE__` as the argument.
3147 * @return string Plugins URL link with optional paths appended.
3148 */
3149function plugins_url( $path = '', $plugin = '' ) {
3150
3151        $path = wp_normalize_path( $path );
3152        $plugin = wp_normalize_path( $plugin );
3153        $mu_plugin_dir = wp_normalize_path( WPMU_PLUGIN_DIR );
3154
3155        if ( !empty($plugin) && 0 === strpos($plugin, $mu_plugin_dir) )
3156                $url = WPMU_PLUGIN_URL;
3157        else
3158                $url = WP_PLUGIN_URL;
3159
3160
3161        $url = set_url_scheme( $url );
3162
3163        if ( !empty($plugin) && is_string($plugin) ) {
3164                $folder = dirname(plugin_basename($plugin));
3165                if ( '.' != $folder )
3166                        $url .= '/' . ltrim($folder, '/');
3167        }
3168
3169        if ( $path && is_string( $path ) )
3170                $url .= '/' . ltrim($path, '/');
3171
3172        /**
3173         * Filters the URL to the plugins directory.
3174         *
3175         * @since 2.8.0
3176         *
3177         * @param string $url    The complete URL to the plugins directory including scheme and path.
3178         * @param string $path   Path relative to the URL to the plugins directory. Blank string
3179         *                       if no path is specified.
3180         * @param string $plugin The plugin file path to be relative to. Blank string if no plugin
3181         *                       is specified.
3182         */
3183        return apply_filters( 'plugins_url', $url, $path, $plugin );
3184}
3185
3186/**
3187 * Retrieves the site URL for the current network.
3188 *
3189 * Returns the site URL with the appropriate protocol, 'https' if
3190 * is_ssl() and 'http' otherwise. If $scheme is 'http' or 'https', is_ssl() is
3191 * overridden.
3192 *
3193 * @since 3.0.0
3194 *
3195 * @see set_url_scheme()
3196 *
3197 * @param string $path   Optional. Path relative to the site URL. Default empty.
3198 * @param string $scheme Optional. Scheme to give the site URL context. Accepts
3199 *                       'http', 'https', or 'relative'. Default null.
3200 * @return string Site URL link with optional path appended.
3201 */
3202function network_site_url( $path = '', $scheme = null ) {
3203        if ( ! is_multisite() )
3204                return site_url($path, $scheme);
3205
3206        $current_network = get_network();
3207
3208        if ( 'relative' == $scheme )
3209                $url = $current_network->path;
3210        else
3211                $url = set_url_scheme( 'http://' . $current_network->domain . $current_network->path, $scheme );
3212
3213        if ( $path && is_string( $path ) )
3214                $url .= ltrim( $path, '/' );
3215
3216        /**
3217         * Filters the network site URL.
3218         *
3219         * @since 3.0.0
3220         *
3221         * @param string      $url    The complete network site URL including scheme and path.
3222         * @param string      $path   Path relative to the network site URL. Blank string if
3223         *                            no path is specified.
3224         * @param string|null $scheme Scheme to give the URL context. Accepts 'http', 'https',
3225         *                            'relative' or null.
3226         */
3227        return apply_filters( 'network_site_url', $url, $path, $scheme );
3228}
3229
3230/**
3231 * Retrieves the home URL for the current network.
3232 *
3233 * Returns the home URL with the appropriate protocol, 'https' is_ssl()
3234 * and 'http' otherwise. If `$scheme` is 'http' or 'https', `is_ssl()` is
3235 * overridden.
3236 *
3237 * @since 3.0.0
3238 *
3239 * @param  string $path   Optional. Path relative to the home URL. Default empty.
3240 * @param  string $scheme Optional. Scheme to give the home URL context. Accepts
3241 *                        'http', 'https', or 'relative'. Default null.
3242 * @return string Home URL link with optional path appended.
3243 */
3244function network_home_url( $path = '', $scheme = null ) {
3245        if ( ! is_multisite() )
3246                return home_url($path, $scheme);
3247
3248        $current_network = get_network();
3249        $orig_scheme = $scheme;
3250
3251        if ( ! in_array( $scheme, array( 'http', 'https', 'relative' ) ) )
3252                $scheme = is_ssl() && ! is_admin() ? 'https' : 'http';
3253
3254        if ( 'relative' == $scheme )
3255                $url = $current_network->path;
3256        else
3257                $url = set_url_scheme( 'http://' . $current_network->domain . $current_network->path, $scheme );
3258
3259        if ( $path && is_string( $path ) )
3260                $url .= ltrim( $path, '/' );
3261
3262        /**
3263         * Filters the network home URL.
3264         *
3265         * @since 3.0.0
3266         *
3267         * @param string      $url         The complete network home URL including scheme and path.
3268         * @param string      $path        Path relative to the network home URL. Blank string
3269         *                                 if no path is specified.
3270         * @param string|null $orig_scheme Scheme to give the URL context. Accepts 'http', 'https',
3271         *                                 'relative' or null.
3272         */
3273        return apply_filters( 'network_home_url', $url, $path, $orig_scheme);
3274}
3275
3276/**
3277 * Retrieves the URL to the admin area for the network.
3278 *
3279 * @since 3.0.0
3280 *
3281 * @param string $path   Optional path relative to the admin URL. Default empty.
3282 * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
3283 *                       and is_ssl(). 'http' or 'https' can be passed to force those schemes.
3284 * @return string Admin URL link with optional path appended.
3285 */
3286function network_admin_url( $path = '', $scheme = 'admin' ) {
3287        if ( ! is_multisite() )
3288                return admin_url( $path, $scheme );
3289
3290        $url = network_site_url('wp-admin/network/', $scheme);
3291
3292        if ( $path && is_string( $path ) )
3293                $url .= ltrim($path, '/');
3294
3295        /**
3296         * Filters the network admin URL.
3297         *
3298         * @since 3.0.0
3299         *
3300         * @param string $url  The complete network admin URL including scheme and path.
3301         * @param string $path Path relative to the network admin URL. Blank string if
3302         *                     no path is specified.
3303         */
3304        return apply_filters( 'network_admin_url', $url, $path );
3305}
3306
3307/**
3308 * Retrieves the URL to the admin area for the current user.
3309 *
3310 * @since 3.0.0
3311 *
3312 * @param string $path   Optional. Path relative to the admin URL. Default empty.
3313 * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
3314 *                       and is_ssl(). 'http' or 'https' can be passed to force those schemes.
3315 * @return string Admin URL link with optional path appended.
3316 */
3317function user_admin_url( $path = '', $scheme = 'admin' ) {
3318        $url = network_site_url('wp-admin/user/', $scheme);
3319
3320        if ( $path && is_string( $path ) )
3321                $url .= ltrim($path, '/');
3322
3323        /**
3324         * Filters the user admin URL for the current user.
3325         *
3326         * @since 3.1.0
3327         *
3328         * @param string $url  The complete URL including scheme and path.
3329         * @param string $path Path relative to the URL. Blank string if
3330         *                     no path is specified.
3331         */
3332        return apply_filters( 'user_admin_url', $url, $path );
3333}
3334
3335/**
3336 * Retrieves the URL to the admin area for either the current site or the network depending on context.
3337 *
3338 * @since 3.1.0
3339 *
3340 * @param string $path   Optional. Path relative to the admin URL. Default empty.
3341 * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
3342 *                       and is_ssl(). 'http' or 'https' can be passed to force those schemes.
3343 * @return string Admin URL link with optional path appended.
3344 */
3345function self_admin_url( $path = '', $scheme = 'admin' ) {
3346        if ( is_network_admin() ) {
3347                $url = network_admin_url( $path, $scheme );
3348        } elseif ( is_user_admin() ) {
3349                $url = user_admin_url( $path, $scheme );
3350        } else {
3351                $url = admin_url( $path, $scheme );
3352        }
3353
3354        /**
3355         * Filters the admin URL for the current site or network depending on context.
3356         *
3357         * @since 4.9.0
3358         *
3359         * @param string $url    The complete URL including scheme and path.
3360         * @param string $path   Path relative to the URL. Blank string if no path is specified.
3361         * @param string $scheme The scheme to use.
3362         */
3363        return apply_filters( 'self_admin_url', $url, $path, $scheme );
3364}
3365
3366/**
3367 * Sets the scheme for a URL.
3368 *
3369 * @since 3.4.0
3370 * @since 4.4.0 The 'rest' scheme was added.
3371 *
3372 * @param string      $url    Absolute URL that includes a scheme
3373 * @param string|null $scheme Optional. Scheme to give $url. Currently 'http', 'https', 'login',
3374 *                            'login_post', 'admin', 'relative', 'rest', 'rpc', or null. Default null.
3375 * @return string $url URL with chosen scheme.
3376 */
3377function set_url_scheme( $url, $scheme = null ) {
3378        $orig_scheme = $scheme;
3379
3380        if ( ! $scheme ) {
3381                $scheme = is_ssl() ? 'https' : 'http';
3382        } elseif ( $scheme === 'admin' || $scheme === 'login' || $scheme === 'login_post' || $scheme === 'rpc' ) {
3383                $scheme = is_ssl() || force_ssl_admin() ? 'https' : 'http';
3384        } elseif ( $scheme !== 'http' && $scheme !== 'https' && $scheme !== 'relative' ) {
3385                $scheme = is_ssl() ? 'https' : 'http';
3386        }
3387
3388        $url = trim( $url );
3389        if ( substr( $url, 0, 2 ) === '//' )
3390                $url = 'http:' . $url;
3391
3392        if ( 'relative' == $scheme ) {
3393                $url = ltrim( preg_replace( '#^\w+://[^/]*#', '', $url ) );
3394                if ( $url !== '' && $url[0] === '/' )
3395                        $url = '/' . ltrim($url , "/ \t\n\r\0\x0B" );
3396        } else {
3397                $url = preg_replace( '#^\w+://#', $scheme . '://', $url );
3398        }
3399
3400        /**
3401         * Filters the resulting URL after setting the scheme.
3402         *
3403         * @since 3.4.0
3404         *
3405         * @param string      $url         The complete URL including scheme and path.
3406         * @param string      $scheme      Scheme applied to the URL. One of 'http', 'https', or 'relative'.
3407         * @param string|null $orig_scheme Scheme requested for the URL. One of 'http', 'https', 'login',
3408         *                                 'login_post', 'admin', 'relative', 'rest', 'rpc', or null.
3409         */
3410        return apply_filters( 'set_url_scheme', $url, $scheme, $orig_scheme );
3411}
3412
3413/**
3414 * Retrieves the URL to the user's dashboard.
3415 *
3416 * If a user does not belong to any site, the global user dashboard is used. If the user
3417 * belongs to the current site, the dashboard for the current site is returned. If the user
3418 * cannot edit the current site, the dashboard to the user's primary site is returned.
3419 *
3420 * @since 3.1.0
3421 *
3422 * @param int    $user_id Optional. User ID. Defaults to current user.
3423 * @param string $path    Optional path relative to the dashboard. Use only paths known to
3424 *                        both site and user admins. Default empty.
3425 * @param string $scheme  The scheme to use. Default is 'admin', which obeys force_ssl_admin()
3426 *                        and is_ssl(). 'http' or 'https' can be passed to force those schemes.
3427 * @return string Dashboard URL link with optional path appended.
3428 */
3429function get_dashboard_url( $user_id = 0, $path = '', $scheme = 'admin' ) {
3430        $user_id = $user_id ? (int) $user_id : get_current_user_id();
3431
3432        $blogs = get_blogs_of_user( $user_id );
3433        if ( is_multisite() && ! user_can( $user_id, 'manage_network' ) && empty($blogs) ) {
3434                $url = user_admin_url( $path, $scheme );
3435        } elseif ( ! is_multisite() ) {
3436                $url = admin_url( $path, $scheme );
3437        } else {
3438                $current_blog = get_current_blog_id();
3439                if ( $current_blog  && ( user_can( $user_id, 'manage_network' ) || in_array( $current_blog, array_keys( $blogs ) ) ) ) {
3440                        $url = admin_url( $path, $scheme );
3441                } else {
3442                        $active = get_active_blog_for_user( $user_id );
3443                        if ( $active )
3444                                $url = get_admin_url( $active->blog_id, $path, $scheme );
3445                        else
3446                                $url = user_admin_url( $path, $scheme );
3447                }
3448        }
3449
3450        /**
3451         * Filters the dashboard URL for a user.
3452         *
3453         * @since 3.1.0
3454         *
3455         * @param string $url     The complete URL including scheme and path.
3456         * @param int    $user_id The user ID.
3457         * @param string $path    Path relative to the URL. Blank string if no path is specified.
3458         * @param string $scheme  Scheme to give the URL context. Accepts 'http', 'https', 'login',
3459         *                        'login_post', 'admin', 'relative' or null.
3460         */
3461        return apply_filters( 'user_dashboard_url', $url, $user_id, $path, $scheme);
3462}
3463
3464/**
3465 * Retrieves the URL to the user's profile editor.
3466 *
3467 * @since 3.1.0
3468 *
3469 * @param int    $user_id Optional. User ID. Defaults to current user.
3470 * @param string $scheme  Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
3471 *                        and is_ssl(). 'http' or 'https' can be passed to force those schemes.
3472 * @return string Dashboard URL link with optional path appended.
3473 */
3474function get_edit_profile_url( $user_id = 0, $scheme = 'admin' ) {
3475        $user_id = $user_id ? (int) $user_id : get_current_user_id();
3476
3477        if ( is_user_admin() )
3478                $url = user_admin_url( 'profile.php', $scheme );
3479        elseif ( is_network_admin() )
3480                $url = network_admin_url( 'profile.php', $scheme );
3481        else
3482                $url = get_dashboard_url( $user_id, 'profile.php', $scheme );
3483
3484        /**
3485         * Filters the URL for a user's profile editor.
3486         *
3487         * @since 3.1.0
3488         *
3489         * @param string $url     The complete URL including scheme and path.
3490         * @param int    $user_id The user ID.
3491         * @param string $scheme  Scheme to give the URL context. Accepts 'http', 'https', 'login',
3492         *                        'login_post', 'admin', 'relative' or null.
3493         */
3494        return apply_filters( 'edit_profile_url', $url, $user_id, $scheme);
3495}
3496
3497/**
3498 * Returns the canonical URL for a post.
3499 *
3500 * When the post is the same as the current requested page the function will handle the
3501 * pagination arguments too.
3502 *
3503 * @since 4.6.0
3504 *
3505 * @param int|WP_Post $post Optional. Post ID or object. Default is global `$post`.
3506 * @return string|false The canonical URL, or false if the post does not exist or has not
3507 *                      been published yet.
3508 */
3509function wp_get_canonical_url( $post = null ) {
3510        $post = get_post( $post );
3511
3512        if ( ! $post ) {
3513                return false;
3514        }
3515
3516        if ( 'publish' !== $post->post_status ) {
3517                return false;
3518        }
3519
3520        $canonical_url = get_permalink( $post );
3521
3522        // If a canonical is being generated for the current page, make sure it has pagination if needed.
3523        if ( $post->ID === get_queried_object_id() ) {
3524                $page = get_query_var( 'page', 0 );
3525                if ( $page >= 2 ) {
3526                        if ( '' == get_option( 'permalink_structure' ) ) {
3527                                $canonical_url = add_query_arg( 'page', $page, $canonical_url );
3528                        } else {
3529                                $canonical_url = trailingslashit( $canonical_url ) . user_trailingslashit( $page, 'single_paged' );
3530                        }
3531                }
3532
3533                $cpage = get_query_var( 'cpage', 0 );
3534                if ( $cpage ) {
3535                        $canonical_url = get_comments_pagenum_link( $cpage );
3536                }
3537        }
3538
3539        /**
3540         * Filters the canonical URL for a post.
3541         *
3542         * @since 4.6.0
3543         *
3544         * @param string  $canonical_url The post's canonical URL.
3545         * @param WP_Post $post          Post object.
3546         */
3547        return apply_filters( 'get_canonical_url', $canonical_url, $post );
3548}
3549
3550/**
3551 * Outputs rel=canonical for singular queries.
3552 *
3553 * @since 2.9.0
3554 * @since 4.6.0 Adjusted to use wp_get_canonical_url().
3555 */
3556function rel_canonical() {
3557        if ( ! is_singular() ) {
3558                return;
3559        }
3560
3561        $id = get_queried_object_id();
3562
3563        if ( 0 === $id ) {
3564                return;
3565        }
3566
3567        $url = wp_get_canonical_url( $id );
3568
3569        if ( ! empty( $url ) ) {
3570                echo '<link rel="canonical" href="' . esc_url( $url ) . '" />' . "\n";
3571        }
3572}
3573
3574/**
3575 * Returns a shortlink for a post, page, attachment, or site.
3576 *
3577 * This function exists to provide a shortlink tag that all themes and plugins can target.
3578 * A plugin must hook in to provide the actual shortlinks. Default shortlink support is
3579 * limited to providing ?p= style links for posts. Plugins can short-circuit this function
3580 * via the {@see 'pre_get_shortlink'} filter or filter the output via the {@see 'get_shortlink'}
3581 * filter.
3582 *
3583 * @since 3.0.0.
3584 *
3585 * @param int    $id          Optional. A post or site id. Default is 0, which means the current post or site.
3586 * @param string $context     Optional. Whether the id is a 'site' id, 'post' id, or 'media' id. If 'post',
3587 *                            the post_type of the post is consulted. If 'query', the current query is consulted
3588 *                            to determine the id and context. Default 'post'.
3589 * @param bool   $allow_slugs Optional. Whether to allow post slugs in the shortlink. It is up to the plugin how
3590 *                            and whether to honor this. Default true.
3591 * @return string A shortlink or an empty string if no shortlink exists for the requested resource or if shortlinks
3592 *                are not enabled.
3593 */
3594function wp_get_shortlink( $id = 0, $context = 'post', $allow_slugs = true ) {
3595        /**
3596         * Filters whether to preempt generating a shortlink for the given post.
3597         *
3598         * Passing a truthy value to the filter will effectively short-circuit the
3599         * shortlink-generation process, returning that value instead.
3600         *
3601         * @since 3.0.0
3602         *
3603         * @param bool|string $return      Short-circuit return value. Either false or a URL string.
3604         * @param int         $id          Post ID, or 0 for the current post.
3605         * @param string      $context     The context for the link. One of 'post' or 'query',
3606         * @param bool        $allow_slugs Whether to allow post slugs in the shortlink.
3607         */
3608        $shortlink = apply_filters( 'pre_get_shortlink', false, $id, $context, $allow_slugs );
3609
3610        if ( false !== $shortlink ) {
3611                return $shortlink;
3612        }
3613
3614        $post_id = 0;
3615        if ( 'query' == $context && is_singular() ) {
3616                $post_id = get_queried_object_id();
3617                $post = get_post( $post_id );
3618        } elseif ( 'post' == $context ) {
3619                $post = get_post( $id );
3620                if ( ! empty( $post->ID ) )
3621                        $post_id = $post->ID;
3622        }
3623
3624        $shortlink = '';
3625
3626        // Return p= link for all public post types.
3627        if ( ! empty( $post_id ) ) {
3628                $post_type = get_post_type_object( $post->post_type );
3629
3630                if ( 'page' === $post->post_type && $post->ID == get_option( 'page_on_front' ) && 'page' == get_option( 'show_on_front' ) ) {
3631                        $shortlink = home_url( '/' );
3632                } elseif ( $post_type->public ) {
3633                        $shortlink = home_url( '?p=' . $post_id );
3634                }
3635        }
3636
3637        /**
3638         * Filters the shortlink for a post.
3639         *
3640         * @since 3.0.0
3641         *
3642         * @param string $shortlink   Shortlink URL.
3643         * @param int    $id          Post ID, or 0 for the current post.
3644         * @param string $context     The context for the link. One of 'post' or 'query',
3645         * @param bool   $allow_slugs Whether to allow post slugs in the shortlink. Not used by default.
3646         */
3647        return apply_filters( 'get_shortlink', $shortlink, $id, $context, $allow_slugs );
3648}
3649
3650/**
3651 * Injects rel=shortlink into the head if a shortlink is defined for the current page.
3652 *
3653 * Attached to the {@see 'wp_head'} action.
3654 *
3655 * @since 3.0.0
3656 */
3657function wp_shortlink_wp_head() {
3658        $shortlink = wp_get_shortlink( 0, 'query' );
3659
3660        if ( empty( $shortlink ) )
3661                return;
3662
3663        echo "<link rel='shortlink' href='" . esc_url( $shortlink ) . "' />\n";
3664}
3665
3666/**
3667 * Sends a Link: rel=shortlink header if a shortlink is defined for the current page.
3668 *
3669 * Attached to the {@see 'wp'} action.
3670 *
3671 * @since 3.0.0
3672 */
3673function wp_shortlink_header() {
3674        if ( headers_sent() )
3675                return;
3676
3677        $shortlink = wp_get_shortlink(0, 'query');
3678
3679        if ( empty($shortlink) )
3680                return;
3681
3682        header('Link: <' . $shortlink . '>; rel=shortlink', false);
3683}
3684
3685/**
3686 * Displays the shortlink for a post.
3687 *
3688 * Must be called from inside "The Loop"
3689 *
3690 * Call like the_shortlink( __( 'Shortlinkage FTW' ) )
3691 *
3692 * @since 3.0.0
3693 *
3694 * @param string $text   Optional The link text or HTML to be displayed. Defaults to 'This is the short link.'
3695 * @param string $title  Optional The tooltip for the link. Must be sanitized. Defaults to the sanitized post title.
3696 * @param string $before Optional HTML to display before the link. Default empty.
3697 * @param string $after  Optional HTML to display after the link. Default empty.
3698 */
3699function the_shortlink( $text = '', $title = '', $before = '', $after = '' ) {
3700        $post = get_post();
3701
3702        if ( empty( $text ) )
3703                $text = __('This is the short link.');
3704
3705        if ( empty( $title ) )
3706                $title = the_title_attribute( array( 'echo' => false ) );
3707
3708        $shortlink = wp_get_shortlink( $post->ID );
3709
3710        if ( !empty( $shortlink ) ) {
3711                $link = '<a rel="shortlink" href="' . esc_url( $shortlink ) . '" title="' . $title . '">' . $text . '</a>';
3712
3713                /**
3714                 * Filters the short link anchor tag for a post.
3715                 *
3716                 * @since 3.0.0
3717                 *
3718                 * @param string $link      Shortlink anchor tag.
3719                 * @param string $shortlink Shortlink URL.
3720                 * @param string $text      Shortlink's text.
3721                 * @param string $title     Shortlink's title attribute.
3722                 */
3723                $link = apply_filters( 'the_shortlink', $link, $shortlink, $text, $title );
3724                echo $before, $link, $after;
3725        }
3726}
3727
3728
3729/**
3730 * Retrieves the avatar URL.
3731 *
3732 * @since 4.2.0
3733 *
3734 * @param mixed $id_or_email The Gravatar to retrieve a URL for. Accepts a user_id, gravatar md5 hash,
3735 *                           user email, WP_User object, WP_Post object, or WP_Comment object.
3736 * @param array $args {
3737 *     Optional. Arguments to return instead of the default arguments.
3738 *
3739 *     @type int    $size           Height and width of the avatar in pixels. Default 96.
3740 *     @type string $default        URL for the default image or a default type. Accepts '404' (return
3741 *                                  a 404 instead of a default image), 'retro' (8bit), 'monsterid' (monster),
3742 *                                  'wavatar' (cartoon face), 'indenticon' (the "quilt"), 'mystery', 'mm',
3743 *                                  or 'mysteryman' (The Oyster Man), 'blank' (transparent GIF), or
3744 *                                  'gravatar_default' (the Gravatar logo). Default is the value of the
3745 *                                  'avatar_default' option, with a fallback of 'mystery'.
3746 *     @type bool   $force_default  Whether to always show the default image, never the Gravatar. Default false.
3747 *     @type string $rating         What rating to display avatars up to. Accepts 'G', 'PG', 'R', 'X', and are
3748 *                                  judged in that order. Default is the value of the 'avatar_rating' option.
3749 *     @type string $scheme         URL scheme to use. See set_url_scheme() for accepted values.
3750 *                                  Default null.
3751 *     @type array  $processed_args When the function returns, the value will be the processed/sanitized $args
3752 *                                  plus a "found_avatar" guess. Pass as a reference. Default null.
3753 * }
3754 * @return false|string The URL of the avatar we found, or false if we couldn't find an avatar.
3755 */
3756function get_avatar_url( $id_or_email, $args = null ) {
3757        $args = get_avatar_data( $id_or_email, $args );
3758        return $args['url'];
3759}
3760
3761/**
3762 * Retrieves default data about the avatar.
3763 *
3764 * @since 4.2.0
3765 *
3766 * @param mixed $id_or_email The Gravatar to retrieve. Accepts a user_id, gravatar md5 hash,
3767 *                            user email, WP_User object, WP_Post object, or WP_Comment object.
3768 * @param array $args {
3769 *     Optional. Arguments to return instead of the default arguments.
3770 *
3771 *     @type int    $size           Height and width of the avatar image file in pixels. Default 96.
3772 *     @type int    $height         Display height of the avatar in pixels. Defaults to $size.
3773 *     @type int    $width          Display width of the avatar in pixels. Defaults to $size.
3774 *     @type string $default        URL for the default image or a default type. Accepts '404' (return
3775 *                                  a 404 instead of a default image), 'retro' (8bit), 'monsterid' (monster),
3776 *                                  'wavatar' (cartoon face), 'indenticon' (the "quilt"), 'mystery', 'mm',
3777 *                                  or 'mysteryman' (The Oyster Man), 'blank' (transparent GIF), or
3778 *                                  'gravatar_default' (the Gravatar logo). Default is the value of the
3779 *                                  'avatar_default' option, with a fallback of 'mystery'.
3780 *     @type bool   $force_default  Whether to always show the default image, never the Gravatar. Default false.
3781 *     @type string $rating         What rating to display avatars up to. Accepts 'G', 'PG', 'R', 'X', and are
3782 *                                  judged in that order. Default is the value of the 'avatar_rating' option.
3783 *     @type string $scheme         URL scheme to use. See set_url_scheme() for accepted values.
3784 *                                  Default null.
3785 *     @type array  $processed_args When the function returns, the value will be the processed/sanitized $args
3786 *                                  plus a "found_avatar" guess. Pass as a reference. Default null.
3787 *     @type string $extra_attr     HTML attributes to insert in the IMG element. Is not sanitized. Default empty.
3788 * }
3789 * @return array $processed_args {
3790 *     Along with the arguments passed in `$args`, this will contain a couple of extra arguments.
3791 *
3792 *     @type bool   $found_avatar True if we were able to find an avatar for this user,
3793 *                                false or not set if we couldn't.
3794 *     @type string $url          The URL of the avatar we found.
3795 * }
3796 */
3797function get_avatar_data( $id_or_email, $args = null ) {
3798        $args = wp_parse_args( $args, array(
3799                'size'           => 96,
3800                'height'         => null,
3801                'width'          => null,
3802                'default'        => get_option( 'avatar_default', 'mystery' ),
3803                'force_default'  => false,
3804                'rating'         => get_option( 'avatar_rating' ),
3805                'scheme'         => null,
3806                'processed_args' => null, // if used, should be a reference
3807                'extra_attr'     => '',
3808        ) );
3809
3810        if ( is_numeric( $args['size'] ) ) {
3811                $args['size'] = absint( $args['size'] );
3812                if ( ! $args['size'] ) {
3813                        $args['size'] = 96;
3814                }
3815        } else {
3816                $args['size'] = 96;
3817        }
3818
3819        if ( is_numeric( $args['height'] ) ) {
3820                $args['height'] = absint( $args['height'] );
3821                if ( ! $args['height'] ) {
3822                        $args['height'] = $args['size'];
3823                }
3824        } else {
3825                $args['height'] = $args['size'];
3826        }
3827
3828        if ( is_numeric( $args['width'] ) ) {
3829                $args['width'] = absint( $args['width'] );
3830                if ( ! $args['width'] ) {
3831                        $args['width'] = $args['size'];
3832                }
3833        } else {
3834                $args['width'] = $args['size'];
3835        }
3836
3837        if ( empty( $args['default'] ) ) {
3838                $args['default'] = get_option( 'avatar_default', 'mystery' );
3839        }
3840
3841        switch ( $args['default'] ) {
3842                case 'mm' :
3843                case 'mystery' :
3844                case 'mysteryman' :
3845                        $args['default'] = 'mm';
3846                        break;
3847                case 'gravatar_default' :
3848                        $args['default'] = false;
3849                        break;
3850        }
3851
3852        $args['force_default'] = (bool) $args['force_default'];
3853
3854        $args['rating'] = strtolower( $args['rating'] );
3855
3856        $args['found_avatar'] = false;
3857
3858        /**
3859         * Filters whether to retrieve the avatar URL early.
3860         *
3861         * Passing a non-null value in the 'url' member of the return array will
3862         * effectively short circuit get_avatar_data(), passing the value through
3863         * the {@see 'get_avatar_data'} filter and returning early.
3864         *
3865         * @since 4.2.0
3866         *
3867         * @param array  $args        Arguments passed to get_avatar_data(), after processing.
3868         * @param mixed  $id_or_email The Gravatar to retrieve. Accepts a user_id, gravatar md5 hash,
3869         *                            user email, WP_User object, WP_Post object, or WP_Comment object.
3870         */
3871        $args = apply_filters( 'pre_get_avatar_data', $args, $id_or_email );
3872
3873        if ( isset( $args['url'] ) && ! is_null( $args['url'] ) ) {
3874                /** This filter is documented in wp-includes/link-template.php */
3875                return apply_filters( 'get_avatar_data', $args, $id_or_email );
3876        }
3877
3878        $email_hash = '';
3879        $user = $email = false;
3880
3881        if ( is_object( $id_or_email ) && isset( $id_or_email->comment_ID ) ) {
3882                $id_or_email = get_comment( $id_or_email );
3883        }
3884
3885        // Process the user identifier.
3886        if ( is_numeric( $id_or_email ) ) {
3887                $user = get_user_by( 'id', absint( $id_or_email ) );
3888        } elseif ( is_string( $id_or_email ) ) {
3889                if ( strpos( $id_or_email, '@md5.gravatar.com' ) ) {
3890                        // md5 hash
3891                        list( $email_hash ) = explode( '@', $id_or_email );
3892                } else {
3893                        // email address
3894                        $email = $id_or_email;
3895                }
3896        } elseif ( $id_or_email instanceof WP_User ) {
3897                // User Object
3898                $user = $id_or_email;
3899        } elseif ( $id_or_email instanceof WP_Post ) {
3900                // Post Object
3901                $user = get_user_by( 'id', (int) $id_or_email->post_author );
3902        } elseif ( $id_or_email instanceof WP_Comment ) {
3903                /**
3904                 * Filters the list of allowed comment types for retrieving avatars.
3905                 *
3906                 * @since 3.0.0
3907                 *
3908                 * @param array $types An array of content types. Default only contains 'comment'.
3909                 */
3910                $allowed_comment_types = apply_filters( 'get_avatar_comment_types', array( 'comment' ) );
3911                if ( ! empty( $id_or_email->comment_type ) && ! in_array( $id_or_email->comment_type, (array) $allowed_comment_types ) ) {
3912                        $args['url'] = false;
3913                        /** This filter is documented in wp-includes/link-template.php */
3914                        return apply_filters( 'get_avatar_data', $args, $id_or_email );
3915                }
3916
3917                if ( ! empty( $id_or_email->user_id ) ) {
3918                        $user = get_user_by( 'id', (int) $id_or_email->user_id );
3919                }
3920                if ( ( ! $user || is_wp_error( $user ) ) && ! empty( $id_or_email->comment_author_email ) ) {
3921                        $email = $id_or_email->comment_author_email;
3922                }
3923        }
3924
3925        if ( ! $email_hash ) {
3926                if ( $user ) {
3927                        $email = $user->user_email;
3928                }
3929
3930                if ( $email ) {
3931                        $email_hash = md5( strtolower( trim( $email ) ) );
3932                }
3933        }
3934
3935        if ( $email_hash ) {
3936                $args['found_avatar'] = true;
3937                $gravatar_server = hexdec( $email_hash[0] ) % 3;
3938        } else {
3939                $gravatar_server = rand( 0, 2 );
3940        }
3941
3942        $url_args = array(
3943                's' => $args['size'],
3944                'd' => $args['default'],
3945                'f' => $args['force_default'] ? 'y' : false,
3946                'r' => $args['rating'],
3947        );
3948
3949        if ( is_ssl() ) {
3950                $url = 'https://secure.gravatar.com/avatar/' . $email_hash;
3951        } else {
3952                $url = sprintf( 'http://%d.gravatar.com/avatar/%s', $gravatar_server, $email_hash );
3953        }
3954
3955        $url = add_query_arg(
3956                rawurlencode_deep( array_filter( $url_args ) ),
3957                set_url_scheme( $url, $args['scheme'] )
3958        );
3959
3960        /**
3961         * Filters the avatar URL.
3962         *
3963         * @since 4.2.0
3964         *
3965         * @param string $url         The URL of the avatar.
3966         * @param mixed  $id_or_email The Gravatar to retrieve. Accepts a user_id, gravatar md5 hash,
3967         *                            user email, WP_User object, WP_Post object, or WP_Comment object.
3968         * @param array  $args        Arguments passed to get_avatar_data(), after processing.
3969         */
3970        $args['url'] = apply_filters( 'get_avatar_url', $url, $id_or_email, $args );
3971
3972        /**
3973         * Filters the avatar data.
3974         *
3975         * @since 4.2.0
3976         *
3977         * @param array  $args        Arguments passed to get_avatar_data(), after processing.
3978         * @param mixed  $id_or_email The Gravatar to retrieve. Accepts a user_id, gravatar md5 hash,
3979         *                            user email, WP_User object, WP_Post object, or WP_Comment object.
3980         */
3981        return apply_filters( 'get_avatar_data', $args, $id_or_email );
3982}
3983
3984/**
3985 * Retrieves the URL of a file in the theme.
3986 *
3987 * Searches in the stylesheet directory before the template directory so themes
3988 * which inherit from a parent theme can just override one file.
3989 *
3990 * @since 4.7.0
3991 *
3992 * @param string $file Optional. File to search for in the stylesheet directory.
3993 * @return string The URL of the file.
3994 */
3995function get_theme_file_uri( $file = '' ) {
3996        $file = ltrim( $file, '/' );
3997
3998        if ( empty( $file ) ) {
3999                $url = get_stylesheet_directory_uri();
4000        } elseif ( file_exists( get_stylesheet_directory() . '/' . $file ) ) {
4001                $url = get_stylesheet_directory_uri() . '/' . $file;
4002        } else {
4003                $url = get_template_directory_uri() . '/' . $file;
4004        }
4005
4006        /**
4007         * Filters the URL to a file in the theme.
4008         *
4009         * @since 4.7.0
4010         *
4011         * @param string $url  The file URL.
4012         * @param string $file The requested file to search for.
4013         */
4014        return apply_filters( 'theme_file_uri', $url, $file );
4015}
4016
4017/**
4018 * Retrieves the URL of a file in the parent theme.
4019 *
4020 * @since 4.7.0
4021 *
4022 * @param string $file Optional. File to return the URL for in the template directory.
4023 * @return string The URL of the file.
4024 */
4025function get_parent_theme_file_uri( $file = '' ) {
4026        $file = ltrim( $file, '/' );
4027
4028        if ( empty( $file ) ) {
4029                $url = get_template_directory_uri();
4030        } else {
4031                $url = get_template_directory_uri() . '/' . $file;
4032        }
4033
4034        /**
4035         * Filters the URL to a file in the parent theme.
4036         *
4037         * @since 4.7.0
4038         *
4039         * @param string $url  The file URL.
4040         * @param string $file The requested file to search for.
4041         */
4042        return apply_filters( 'parent_theme_file_uri', $url, $file );
4043}
4044
4045/**
4046 * Retrieves the path of a file in the theme.
4047 *
4048 * Searches in the stylesheet directory before the template directory so themes
4049 * which inherit from a parent theme can just override one file.
4050 *
4051 * @since 4.7.0
4052 *
4053 * @param string $file Optional. File to search for in the stylesheet directory.
4054 * @return string The path of the file.
4055 */
4056function get_theme_file_path( $file = '' ) {
4057        $file = ltrim( $file, '/' );
4058
4059        if ( empty( $file ) ) {
4060                $path = get_stylesheet_directory();
4061        } elseif ( file_exists( get_stylesheet_directory() . '/' . $file ) ) {
4062                $path = get_stylesheet_directory() . '/' . $file;
4063        } else {
4064                $path = get_template_directory() . '/' . $file;
4065        }
4066
4067        /**
4068         * Filters the path to a file in the theme.
4069         *
4070         * @since 4.7.0
4071         *
4072         * @param string $path The file path.
4073         * @param string $file The requested file to search for.
4074         */
4075        return apply_filters( 'theme_file_path', $path, $file );
4076}
4077
4078/**
4079 * Retrieves the path of a file in the parent theme.
4080 *
4081 * @since 4.7.0
4082 *
4083 * @param string $file Optional. File to return the path for in the template directory.
4084 * @return string The path of the file.
4085 */
4086function get_parent_theme_file_path( $file = '' ) {
4087        $file = ltrim( $file, '/' );
4088
4089        if ( empty( $file ) ) {
4090                $path = get_template_directory();
4091        } else {
4092                $path = get_template_directory() . '/' . $file;
4093        }
4094
4095        /**
4096         * Filters the path to a file in the parent theme.
4097         *
4098         * @since 4.7.0
4099         *
4100         * @param string $path The file path.
4101         * @param string $file The requested file to search for.
4102         */
4103        return apply_filters( 'parent_theme_file_path', $path, $file );
4104}
Note: See TracBrowser for help on using the repository browser.