run:R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
23.8 KB
2026-03-11 16:18:51
R W Run
7.8 KB
2026-03-11 16:18:52
R W Run
36.1 KB
2026-03-11 16:18:51
R W Run
11.9 KB
2026-03-11 16:18:52
R W Run
18.94 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:52
R W Run
28.6 KB
2026-03-11 16:18:51
R W Run
316 By
2026-03-11 16:18:51
R W Run
12.9 KB
2026-03-11 16:18:51
R W Run
61.02 KB
2026-03-11 16:18:52
R W Run
15 KB
2026-03-11 16:18:51
R W Run
112.05 KB
2026-03-11 16:18:51
R W Run
12.47 KB
2026-03-11 16:18:51
R W Run
15.07 KB
2026-03-11 16:18:52
R W Run
9.84 KB
2026-03-11 16:18:52
R W Run
13.17 KB
2026-03-11 16:18:52
R W Run
33.83 KB
2026-03-11 16:18:51
R W Run
42.63 KB
2026-03-11 16:18:51
R W Run
55.71 KB
2026-03-11 16:18:52
R W Run
12.53 KB
2026-03-11 16:18:51
R W Run
2.55 KB
2026-03-11 16:18:52
R W Run
28.92 KB
2026-03-11 16:18:52
R W Run
539 By
2026-03-11 16:18:51
R W Run
367 By
2026-03-11 16:18:52
R W Run
42.65 KB
2026-03-11 16:18:51
R W Run
401 By
2026-03-11 16:18:51
R W Run
6.61 KB
2026-03-11 16:18:51
R W Run
664 By
2026-03-11 16:18:52
R W Run
20.63 KB
2026-03-11 16:18:51
R W Run
2.18 KB
2026-03-11 16:18:52
R W Run
453 By
2026-03-11 16:18:52
R W Run
457 By
2026-03-11 16:18:51
R W Run
36.83 KB
2026-03-11 16:18:52
R W Run
2.41 KB
2026-03-11 16:18:52
R W Run
8.28 KB
2026-03-11 16:18:51
R W Run
13.89 KB
2026-03-11 16:18:51
R W Run
11.76 KB
2026-03-11 16:18:51
R W Run
2.65 KB
2026-03-11 16:18:51
R W Run
7.43 KB
2026-03-11 16:18:51
R W Run
17.46 KB
2026-03-11 16:18:51
R W Run
5.14 KB
2026-03-11 16:18:52
R W Run
16.7 KB
2026-03-11 16:18:51
R W Run
8.28 KB
2026-03-11 16:18:52
R W Run
2.92 KB
2026-03-11 16:18:52
R W Run
1.32 KB
2026-03-11 16:18:51
R W Run
4.6 KB
2026-03-11 16:18:52
R W Run
11.62 KB
2026-03-11 16:18:52
R W Run
2.5 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
11.25 KB
2026-03-11 16:18:52
R W Run
5.32 KB
2026-03-11 16:18:51
R W Run
10.99 KB
2026-03-11 16:18:52
R W Run
68.32 KB
2026-03-11 16:18:51
R W Run
6.34 KB
2026-03-11 16:18:51
R W Run
5.49 KB
2026-03-11 16:18:51
R W Run
1.99 KB
2026-03-11 16:18:52
R W Run
7.02 KB
2026-03-11 16:18:51
R W Run
4.91 KB
2026-03-11 16:18:52
R W Run
16.86 KB
2026-03-11 16:18:51
R W Run
24.23 KB
2026-03-11 16:18:51
R W Run
3.97 KB
2026-03-11 16:18:51
R W Run
47.66 KB
2026-03-11 16:18:51
R W Run
9.22 KB
2026-03-11 16:18:51
R W Run
25.51 KB
2026-03-11 16:18:51
R W Run
198.38 KB
2026-03-11 16:18:52
R W Run
56.65 KB
2026-03-11 16:18:51
R W Run
10.46 KB
2026-03-11 16:18:51
R W Run
10.95 KB
2026-03-11 16:18:52
R W Run
29.26 KB
2026-03-11 16:18:51
R W Run
70.91 KB
2026-03-11 16:18:52
R W Run
35.3 KB
2026-03-11 16:18:52
R W Run
16.61 KB
2026-03-11 16:18:52
R W Run
2.57 KB
2026-03-11 16:18:52
R W Run
39.83 KB
2026-03-11 16:18:51
R W Run
70.64 KB
2026-03-11 16:18:51
R W Run
15.56 KB
2026-03-11 16:18:52
R W Run
7.33 KB
2026-03-11 16:18:52
R W Run
253 By
2026-03-11 16:18:51
R W Run
7.96 KB
2026-03-11 16:18:52
R W Run
3.23 KB
2026-03-11 16:18:52
R W Run
969 By
2026-03-11 16:18:52
R W Run
16.28 KB
2026-03-11 16:18:51
R W Run
7.22 KB
2026-03-11 16:18:51
R W Run
12.95 KB
2026-03-11 16:18:51
R W Run
6.53 KB
2026-03-11 16:18:51
R W Run
3.42 KB
2026-03-11 16:18:52
R W Run
5.84 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
4.3 KB
2026-03-11 16:18:52
R W Run
2.91 KB
2026-03-11 16:18:51
R W Run
16.46 KB
2026-03-11 16:18:52
R W Run
40.6 KB
2026-03-11 16:18:51
R W Run
20.22 KB
2026-03-11 16:18:51
R W Run
36.11 KB
2026-03-11 16:18:52
R W Run
17.01 KB
2026-03-11 16:18:51
R W Run
7.27 KB
2026-03-11 16:18:52
R W Run
6.62 KB
2026-03-11 16:18:52
R W Run
16.49 KB
2026-03-11 16:18:52
R W Run
1.79 KB
2026-03-11 16:18:52
R W Run
29.82 KB
2026-03-11 16:18:51
R W Run
6.67 KB
2026-03-11 16:18:52
R W Run
8.98 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:51
R W Run
12.01 KB
2026-03-11 16:18:51
R W Run
17.11 KB
2026-03-11 16:18:51
R W Run
6.74 KB
2026-03-11 16:18:52
R W Run
30.93 KB
2026-03-11 16:18:51
R W Run
4.99 KB
2026-03-11 16:18:51
R W Run
4.25 KB
2026-03-11 16:18:51
R W Run
24.72 KB
2026-03-11 16:18:51
R W Run
29.96 KB
2026-03-11 16:18:52
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
160 KB
2026-03-11 16:18:51
R W Run
6.72 KB
2026-03-11 16:18:52
R W Run
10.92 KB
2026-03-11 16:18:51
R W Run
4.77 KB
2026-03-11 16:18:51
R W Run
3.38 KB
2026-03-11 16:18:51
R W Run
11.18 KB
2026-03-11 16:18:51
R W Run
62.19 KB
2026-03-11 16:18:51
R W Run
2.46 KB
2026-03-11 16:18:51
R W Run
9.17 KB
2026-03-11 16:18:51
R W Run
32.15 KB
2026-03-11 16:18:51
R W Run
34.05 KB
2026-03-11 16:18:52
R W Run
7.15 KB
2026-03-11 16:18:51
R W Run
3.47 KB
2026-03-11 16:18:52
R W Run
1.87 KB
2026-03-11 16:18:52
R W Run
30.91 KB
2026-03-11 16:18:51
R W Run
7.29 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:51
R W Run
12.54 KB
2026-03-11 16:18:51
R W Run
19.12 KB
2026-03-11 16:18:51
R W Run
18.12 KB
2026-03-11 16:18:52
R W Run
39.99 KB
2026-03-11 16:18:52
R W Run
5.17 KB
2026-03-11 16:18:52
R W Run
979 By
2026-03-11 16:18:51
R W Run
18.44 KB
2026-03-11 16:18:52
R W Run
10.24 KB
2026-03-11 16:18:51
R W Run
1.77 KB
2026-03-11 16:18:52
R W Run
34.9 KB
2026-03-11 16:18:51
R W Run
7.19 KB
2026-03-11 16:18:52
R W Run
160.5 KB
2026-03-11 16:18:51
R W Run
64.27 KB
2026-03-11 16:18:51
R W Run
27.95 KB
2026-03-11 16:18:51
R W Run
4.69 KB
2026-03-11 16:18:51
R W Run
2.94 KB
2026-03-11 16:18:51
R W Run
43.13 KB
2026-03-11 16:18:52
R W Run
2.25 KB
2026-03-11 16:18:52
R W Run
22.5 KB
2026-03-11 16:18:51
R W Run
13.01 KB
2026-03-11 16:18:52
R W Run
3.27 KB
2026-03-11 16:18:51
R W Run
18 KB
2026-03-11 16:18:51
R W Run
210.4 KB
2026-03-11 16:18:52
R W Run
25.86 KB
2026-03-11 16:18:52
R W Run
115.85 KB
2026-03-11 16:18:51
R W Run
373 By
2026-03-11 16:18:52
R W Run
343 By
2026-03-11 16:18:52
R W Run
338 By
2026-03-11 16:18:51
R W Run
100.73 KB
2026-03-11 16:18:52
R W Run
130.93 KB
2026-03-11 16:18:51
R W Run
19.1 KB
2026-03-11 16:18:51
R W Run
17.41 KB
2026-03-11 16:18:52
R W Run
41.98 KB
2026-03-11 16:18:52
R W Run
400 By
2026-03-11 16:18:52
R W Run
11.1 KB
2026-03-11 16:18:52
R W Run
37.02 KB
2026-03-11 16:18:51
R W Run
2.24 KB
2026-03-11 16:18:51
R W Run
188.13 KB
2026-03-11 16:18:51
R W Run
338 By
2026-03-11 16:18:51
R W Run
38 KB
2026-03-11 16:18:51
R W Run
4.02 KB
2026-03-11 16:18:52
R W Run
5.38 KB
2026-03-11 16:18:51
R W Run
3.05 KB
2026-03-11 16:18:52
R W Run
2.61 KB
2026-03-11 16:18:51
R W Run
1.16 KB
2026-03-11 16:18:52
R W Run
4.04 KB
2026-03-11 16:18:51
R W Run
3.71 KB
2026-03-11 16:18:51
R W Run
24.6 KB
2026-03-11 16:18:51
R W Run
9.56 KB
2026-03-11 16:18:51
R W Run
346.43 KB
2026-03-11 16:18:52
R W Run
281.84 KB
2026-03-11 16:18:52
R W Run
14.95 KB
2026-03-11 16:18:51
R W Run
8.44 KB
2026-03-11 16:18:52
R W Run
168.95 KB
2026-03-11 16:18:52
R W Run
20.71 KB
2026-03-11 16:18:52
R W Run
25.27 KB
2026-03-11 16:18:51
R W Run
5.72 KB
2026-03-11 16:18:51
R W Run
4.63 KB
2026-03-11 16:18:52
R W Run
81.73 KB
2026-03-11 16:18:51
R W Run
67.18 KB
2026-03-11 16:18:51
R W Run
156.36 KB
2026-03-11 16:18:52
R W Run
55.19 KB
2026-03-11 16:18:51
R W Run
162 By
2026-03-11 16:18:51
R W Run
61.72 KB
2026-03-11 16:18:51
R W Run
216.06 KB
2026-03-11 16:18:52
R W Run
65.09 KB
2026-03-11 16:18:51
R W Run
25.24 KB
2026-03-11 16:18:52
R W Run
4.81 KB
2026-03-11 16:18:51
R W Run
6.48 KB
2026-03-11 16:18:52
R W Run
21.25 KB
2026-03-11 16:18:51
R W Run
2.79 KB
2026-03-11 16:18:52
R W Run
89.69 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:52
R W Run
3.69 KB
2026-03-11 16:18:52
R W Run
4.11 KB
2026-03-11 16:18:51
R W Run
40.74 KB
2026-03-11 16:18:51
R W Run
25.38 KB
2026-03-11 16:18:51
R W Run
43.31 KB
2026-03-11 16:18:52
R W Run
102.57 KB
2026-03-11 16:18:52
R W Run
6.18 KB
2026-03-11 16:18:51
R W Run
124.47 KB
2026-03-11 16:18:52
R W Run
35.65 KB
2026-03-11 16:18:52
R W Run
6.94 KB
2026-03-11 16:18:52
R W Run
67.04 KB
2026-03-11 16:18:52
R W Run
10.62 KB
2026-03-11 16:18:51
R W Run
289.35 KB
2026-03-11 16:18:52
R W Run
36.23 KB
2026-03-11 16:18:51
R W Run
200 By
2026-03-11 16:18:52
R W Run
200 By
2026-03-11 16:18:52
R W Run
98.29 KB
2026-03-11 16:18:52
R W Run
30.02 KB
2026-03-11 16:18:52
R W Run
19.03 KB
2026-03-11 16:18:52
R W Run
5.06 KB
2026-03-11 16:18:52
R W Run
255 By
2026-03-11 16:18:51
R W Run
22.66 KB
2026-03-11 16:18:52
R W Run
154.63 KB
2026-03-11 16:18:51
R W Run
9.68 KB
2026-03-11 16:18:51
R W Run
258 By
2026-03-11 16:18:51
R W Run
23.49 KB
2026-03-11 16:18:51
R W Run
3.16 KB
2026-03-11 16:18:51
R W Run
8.4 KB
2026-03-11 16:18:52
R W Run
441 By
2026-03-11 16:18:51
R W Run
7.39 KB
2026-03-11 16:18:51
R W Run
173 KB
2026-03-11 16:18:52
R W Run
544 By
2026-03-11 16:18:52
R W Run
4.17 KB
2026-03-11 16:18:51
R W Run
35.97 KB
2026-03-11 16:18:52
R W Run
1.69 KB
2026-03-11 16:18:51
R W Run
2.84 KB
2026-03-11 16:18:52
R W Run
6.09 KB
2026-03-11 16:18:51
R W Run
8.71 KB
2026-03-11 16:18:51
R W Run
131.84 KB
2026-03-11 16:18:51
R W Run
37.45 KB
2026-03-11 16:18:51
R W Run
173.89 KB
2026-03-11 16:18:51
R W Run
7.09 KB
2026-03-11 16:18:51
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
1.08 KB
2026-03-11 16:18:51
R W Run
69.46 KB
2026-03-11 16:18:52
R W Run
445 By
2026-03-11 16:18:51
R W Run
799 By
2026-03-11 16:18:52
R W Run
error_log
📄link-template.php
1<?php
2/**
3 * WordPress Link Template Functions
4 *
5 * @package WordPress
6 * @subpackage Template
7 */
8
9/**
10 * Displays the permalink for the current post.
11 *
12 * @since 1.2.0
13 * @since 4.4.0 Added the `$post` parameter.
14 *
15 * @param int|WP_Post $post Optional. Post ID or post object. Default is the global `$post`.
16 */
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 WordPress rewrite component.
41 *
42 * @param string $url 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( $url, $type_of_url = '' ) {
48 global $wp_rewrite;
49 if ( $wp_rewrite->use_trailing_slashes ) {
50 $url = trailingslashit( $url );
51 } else {
52 $url = untrailingslashit( $url );
53 }
54
55 /**
56 * Filters the trailing-slashed string, depending on whether the site is set to use trailing slashes.
57 *
58 * @since 2.2.0
59 *
60 * @param string $url URL with or without a trailing slash.
61 * @param string $type_of_url The type of URL being considered. Accepts 'single', 'single_trackback',
62 * 'single_feed', 'single_paged', 'commentpaged', 'paged', 'home', 'feed',
63 * 'category', 'page', 'year', 'month', 'day', 'post_type_archive'.
64 */
65 return apply_filters( 'user_trailingslashit', $url, $type_of_url );
66}
67
68/**
69 * Displays the permalink anchor for the current post.
70 *
71 * The permalink mode title will use the post title for the 'a' element 'id'
72 * attribute. The id mode uses 'post-' with the post ID for the 'id' attribute.
73 *
74 * @since 0.71
75 *
76 * @param string $mode Optional. Permalink mode. Accepts 'title' or 'id'. Default 'id'.
77 */
78function permalink_anchor( $mode = 'id' ) {
79 $post = get_post();
80 switch ( strtolower( $mode ) ) {
81 case 'title':
82 $title = sanitize_title( $post->post_title ) . '-' . $post->ID;
83 echo '<a id="' . $title . '"></a>';
84 break;
85 case 'id':
86 default:
87 echo '<a id="post-' . $post->ID . '"></a>';
88 break;
89 }
90}
91
92/**
93 * Determine whether post should always use a plain permalink structure.
94 *
95 * @since 5.7.0
96 *
97 * @param WP_Post|int|null $post Optional. Post ID or post object. Defaults to global $post.
98 * @param bool|null $sample Optional. Whether to force consideration based on sample links.
99 * If omitted, a sample link is generated if a post object is passed
100 * with the filter property set to 'sample'.
101 * @return bool Whether to use a plain permalink structure.
102 */
103function wp_force_plain_post_permalink( $post = null, $sample = null ) {
104 if (
105 null === $sample &&
106 is_object( $post ) &&
107 isset( $post->filter ) &&
108 'sample' === $post->filter
109 ) {
110 $sample = true;
111 } else {
112 $post = get_post( $post );
113 $sample = null !== $sample ? $sample : false;
114 }
115
116 if ( ! $post ) {
117 return true;
118 }
119
120 $post_status_obj = get_post_status_object( get_post_status( $post ) );
121 $post_type_obj = get_post_type_object( get_post_type( $post ) );
122
123 if ( ! $post_status_obj || ! $post_type_obj ) {
124 return true;
125 }
126
127 if (
128 // Publicly viewable links never have plain permalinks.
129 is_post_status_viewable( $post_status_obj ) ||
130 (
131 // Private posts don't have plain permalinks if the user can read them.
132 $post_status_obj->private &&
133 current_user_can( 'read_post', $post->ID )
134 ) ||
135 // Protected posts don't have plain links if getting a sample URL.
136 ( $post_status_obj->protected && $sample )
137 ) {
138 return false;
139 }
140
141 return true;
142}
143
144/**
145 * Retrieves the full permalink for the current post or post ID.
146 *
147 * This function is an alias for get_permalink().
148 *
149 * @since 3.9.0
150 *
151 * @see get_permalink()
152 *
153 * @param int|WP_Post $post Optional. Post ID or post object. Default is the global `$post`.
154 * @param bool $leavename Optional. Whether to keep post name or page name. Default false.
155 * @return string|false The permalink URL. False if the post does not exist.
156 */
157function get_the_permalink( $post = 0, $leavename = false ) {
158 return get_permalink( $post, $leavename );
159}
160
161/**
162 * Retrieves the full permalink for the current post or post ID.
163 *
164 * @since 1.0.0
165 *
166 * @param int|WP_Post $post Optional. Post ID or post object. Default is the global `$post`.
167 * @param bool $leavename Optional. Whether to keep post name or page name. Default false.
168 * @return string|false The permalink URL. False if the post does not exist.
169 */
170function get_permalink( $post = 0, $leavename = false ) {
171 $rewritecode = array(
172 '%year%',
173 '%monthnum%',
174 '%day%',
175 '%hour%',
176 '%minute%',
177 '%second%',
178 $leavename ? '' : '%postname%',
179 '%post_id%',
180 '%category%',
181 '%author%',
182 $leavename ? '' : '%pagename%',
183 );
184
185 if ( is_object( $post ) && isset( $post->filter ) && 'sample' === $post->filter ) {
186 $sample = true;
187 } else {
188 $post = get_post( $post );
189 $sample = false;
190 }
191
192 if ( empty( $post->ID ) ) {
193 return false;
194 }
195
196 if ( 'page' === $post->post_type ) {
197 return get_page_link( $post, $leavename, $sample );
198 } elseif ( 'attachment' === $post->post_type ) {
199 return get_attachment_link( $post, $leavename );
200 } elseif ( in_array( $post->post_type, get_post_types( array( '_builtin' => false ) ), true ) ) {
201 return get_post_permalink( $post, $leavename, $sample );
202 }
203
204 $permalink = get_option( 'permalink_structure' );
205
206 /**
207 * Filters the permalink structure for a post before token replacement occurs.
208 *
209 * Only applies to posts with post_type of 'post'.
210 *
211 * @since 3.0.0
212 *
213 * @param string $permalink The site's permalink structure.
214 * @param WP_Post $post The post in question.
215 * @param bool $leavename Whether to keep the post name.
216 */
217 $permalink = apply_filters( 'pre_post_link', $permalink, $post, $leavename );
218
219 if (
220 $permalink &&
221 ! wp_force_plain_post_permalink( $post )
222 ) {
223
224 $category = '';
225 if ( str_contains( $permalink, '%category%' ) ) {
226 $cats = get_the_category( $post->ID );
227 if ( $cats ) {
228 $cats = wp_list_sort(
229 $cats,
230 array(
231 'term_id' => 'ASC',
232 )
233 );
234
235 /**
236 * Filters the category that gets used in the %category% permalink token.
237 *
238 * @since 3.5.0
239 *
240 * @param WP_Term $cat The category to use in the permalink.
241 * @param array $cats Array of all categories (WP_Term objects) associated with the post.
242 * @param WP_Post $post The post in question.
243 */
244 $category_object = apply_filters( 'post_link_category', $cats[0], $cats, $post );
245
246 $category_object = get_term( $category_object, 'category' );
247 $category = $category_object->slug;
248 if ( $category_object->parent ) {
249 $category = get_category_parents( $category_object->parent, false, '/', true ) . $category;
250 }
251 }
252 /*
253 * Show default category in permalinks,
254 * without having to assign it explicitly.
255 */
256 if ( empty( $category ) ) {
257 $default_category = get_term( get_option( 'default_category' ), 'category' );
258 if ( $default_category && ! is_wp_error( $default_category ) ) {
259 $category = $default_category->slug;
260 }
261 }
262 }
263
264 $author = '';
265 if ( str_contains( $permalink, '%author%' ) ) {
266 $authordata = get_userdata( $post->post_author );
267 $author = $authordata->user_nicename;
268 }
269
270 /*
271 * This is not an API call because the permalink is based on the stored post_date value,
272 * which should be parsed as local time regardless of the default PHP timezone.
273 */
274 $date = explode( ' ', str_replace( array( '-', ':' ), ' ', $post->post_date ) );
275
276 $rewritereplace = array(
277 $date[0],
278 $date[1],
279 $date[2],
280 $date[3],
281 $date[4],
282 $date[5],
283 $post->post_name,
284 $post->ID,
285 $category,
286 $author,
287 $post->post_name,
288 );
289
290 $permalink = home_url( str_replace( $rewritecode, $rewritereplace, $permalink ) );
291 $permalink = user_trailingslashit( $permalink, 'single' );
292
293 } else { // If they're not using the fancy permalink option.
294 $permalink = home_url( '?p=' . $post->ID );
295 }
296
297 /**
298 * Filters the permalink for a post.
299 *
300 * Only applies to posts with post_type of 'post'.
301 *
302 * @since 1.5.0
303 *
304 * @param string $permalink The post's permalink.
305 * @param WP_Post $post The post in question.
306 * @param bool $leavename Whether to keep the post name.
307 */
308 return apply_filters( 'post_link', $permalink, $post, $leavename );
309}
310
311/**
312 * Retrieves the permalink for a post of a custom post type.
313 *
314 * @since 3.0.0
315 * @since 6.1.0 Returns false if the post does not exist.
316 *
317 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
318 *
319 * @param int|WP_Post $post Optional. Post ID or post object. Default is the global `$post`.
320 * @param bool $leavename Optional. Whether to keep post name. Default false.
321 * @param bool $sample Optional. Is it a sample permalink. Default false.
322 * @return string|false The post permalink URL. False if the post does not exist.
323 */
324function get_post_permalink( $post = 0, $leavename = false, $sample = false ) {
325 global $wp_rewrite;
326
327 $post = get_post( $post );
328
329 if ( ! $post ) {
330 return false;
331 }
332
333 $post_link = $wp_rewrite->get_extra_permastruct( $post->post_type );
334
335 $slug = $post->post_name;
336
337 $force_plain_link = wp_force_plain_post_permalink( $post );
338
339 $post_type = get_post_type_object( $post->post_type );
340
341 if ( $post_type->hierarchical ) {
342 $slug = get_page_uri( $post );
343 }
344
345 if ( ! empty( $post_link ) && ( ! $force_plain_link || $sample ) ) {
346 if ( ! $leavename ) {
347 $post_link = str_replace( "%$post->post_type%", $slug, $post_link );
348 }
349 $post_link = home_url( user_trailingslashit( $post_link ) );
350 } else {
351 if ( $post_type->query_var && ( isset( $post->post_status ) && ! $force_plain_link ) ) {
352 $post_link = add_query_arg( $post_type->query_var, $slug, '' );
353 } else {
354 $post_link = add_query_arg(
355 array(
356 'post_type' => $post->post_type,
357 'p' => $post->ID,
358 ),
359 ''
360 );
361 }
362 $post_link = home_url( $post_link );
363 }
364
365 /**
366 * Filters the permalink for a post of a custom post type.
367 *
368 * @since 3.0.0
369 *
370 * @param string $post_link The post's permalink.
371 * @param WP_Post $post The post in question.
372 * @param bool $leavename Whether to keep the post name.
373 * @param bool $sample Is it a sample permalink.
374 */
375 return apply_filters( 'post_type_link', $post_link, $post, $leavename, $sample );
376}
377
378/**
379 * Retrieves the permalink for the current page or page ID.
380 *
381 * Respects page_on_front. Use this one.
382 *
383 * @since 1.5.0
384 *
385 * @param int|WP_Post $post Optional. Post ID or object. Default uses the global `$post`.
386 * @param bool $leavename Optional. Whether to keep the page name. Default false.
387 * @param bool $sample Optional. Whether it should be treated as a sample permalink.
388 * Default false.
389 * @return string The page permalink.
390 */
391function get_page_link( $post = 0, $leavename = false, $sample = false ) {
392 $post = get_post( $post );
393
394 if ( 'page' === get_option( 'show_on_front' ) && (int) get_option( 'page_on_front' ) === $post->ID ) {
395 $link = home_url( '/' );
396 } else {
397 $link = _get_page_link( $post, $leavename, $sample );
398 }
399
400 /**
401 * Filters the permalink for a page.
402 *
403 * @since 1.5.0
404 *
405 * @param string $link The page's permalink.
406 * @param int $post_id The ID of the page.
407 * @param bool $sample Is it a sample permalink.
408 */
409 return apply_filters( 'page_link', $link, $post->ID, $sample );
410}
411
412/**
413 * Retrieves the page permalink.
414 *
415 * Ignores page_on_front. Internal use only.
416 *
417 * @since 2.1.0
418 * @access private
419 *
420 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
421 *
422 * @param int|WP_Post $post Optional. Post ID or object. Default uses the global `$post`.
423 * @param bool $leavename Optional. Whether to keep the page name. Default false.
424 * @param bool $sample Optional. Whether it should be treated as a sample permalink.
425 * Default false.
426 * @return string The page permalink.
427 */
428function _get_page_link( $post = 0, $leavename = false, $sample = false ) {
429 global $wp_rewrite;
430
431 $post = get_post( $post );
432
433 $force_plain_link = wp_force_plain_post_permalink( $post );
434
435 $link = $wp_rewrite->get_page_permastruct();
436
437 if ( ! empty( $link ) && ( ( isset( $post->post_status ) && ! $force_plain_link ) || $sample ) ) {
438 if ( ! $leavename ) {
439 $link = str_replace( '%pagename%', get_page_uri( $post ), $link );
440 }
441
442 $link = home_url( $link );
443 $link = user_trailingslashit( $link, 'page' );
444 } else {
445 $link = home_url( '?page_id=' . $post->ID );
446 }
447
448 /**
449 * Filters the permalink for a non-page_on_front page.
450 *
451 * @since 2.1.0
452 *
453 * @param string $link The page's permalink.
454 * @param int $post_id The ID of the page.
455 */
456 return apply_filters( '_get_page_link', $link, $post->ID );
457}
458
459/**
460 * Retrieves the permalink for an attachment.
461 *
462 * This can be used in the WordPress Loop or outside of it.
463 *
464 * @since 2.0.0
465 *
466 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
467 *
468 * @param int|WP_Post $post Optional. Post ID or object. Default uses the global `$post`.
469 * @param bool $leavename Optional. Whether to keep the page name. Default false.
470 * @return string The attachment permalink.
471 */
472function get_attachment_link( $post = null, $leavename = false ) {
473 global $wp_rewrite;
474
475 $link = false;
476
477 $post = get_post( $post );
478 $force_plain_link = wp_force_plain_post_permalink( $post );
479 $parent_id = $post->post_parent;
480 $parent = $parent_id ? get_post( $parent_id ) : false;
481 $parent_valid = true; // Default for no parent.
482 if (
483 $parent_id &&
484 (
485 $post->post_parent === $post->ID ||
486 ! $parent ||
487 ! is_post_type_viewable( get_post_type( $parent ) )
488 )
489 ) {
490 // Post is either its own parent or parent post unavailable.
491 $parent_valid = false;
492 }
493
494 if ( $force_plain_link || ! $parent_valid ) {
495 $link = false;
496 } elseif ( $wp_rewrite->using_permalinks() && $parent ) {
497 if ( 'page' === $parent->post_type ) {
498 $parentlink = _get_page_link( $post->post_parent ); // Ignores page_on_front.
499 } else {
500 $parentlink = get_permalink( $post->post_parent );
501 }
502
503 if ( is_numeric( $post->post_name ) || str_contains( get_option( 'permalink_structure' ), '%category%' ) ) {
504 $name = 'attachment/' . $post->post_name; // <permalink>/<int>/ is paged so we use the explicit attachment marker.
505 } else {
506 $name = $post->post_name;
507 }
508
509 if ( ! str_contains( $parentlink, '?' ) ) {
510 $link = user_trailingslashit( trailingslashit( $parentlink ) . '%postname%' );
511 }
512
513 if ( ! $leavename ) {
514 $link = str_replace( '%postname%', $name, $link );
515 }
516 } elseif ( $wp_rewrite->using_permalinks() && ! $leavename ) {
517 $link = home_url( user_trailingslashit( $post->post_name ) );
518 }
519
520 if ( ! $link ) {
521 $link = home_url( '/?attachment_id=' . $post->ID );
522 }
523
524 /**
525 * Filters the permalink for an attachment.
526 *
527 * @since 2.0.0
528 * @since 5.6.0 Providing an empty string will now disable
529 * the view attachment page link on the media modal.
530 *
531 * @param string $link The attachment's permalink.
532 * @param int $post_id Attachment ID.
533 */
534 return apply_filters( 'attachment_link', $link, $post->ID );
535}
536
537/**
538 * Retrieves the permalink for the year archives.
539 *
540 * @since 1.5.0
541 *
542 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
543 *
544 * @param int|false $year Integer of year. False for current year.
545 * @return string The permalink for the specified year archive.
546 */
547function get_year_link( $year ) {
548 global $wp_rewrite;
549 if ( ! $year ) {
550 $year = current_time( 'Y' );
551 }
552 $yearlink = $wp_rewrite->get_year_permastruct();
553 if ( ! empty( $yearlink ) ) {
554 $yearlink = str_replace( '%year%', $year, $yearlink );
555 $yearlink = home_url( user_trailingslashit( $yearlink, 'year' ) );
556 } else {
557 $yearlink = home_url( '?m=' . $year );
558 }
559
560 /**
561 * Filters the year archive permalink.
562 *
563 * @since 1.5.0
564 *
565 * @param string $yearlink Permalink for the year archive.
566 * @param int $year Year for the archive.
567 */
568 return apply_filters( 'year_link', $yearlink, $year );
569}
570
571/**
572 * Retrieves the permalink for the month archives with year.
573 *
574 * @since 1.0.0
575 *
576 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
577 *
578 * @param int|false $year Integer of year. False for current year.
579 * @param int|false $month Integer of month. False for current month.
580 * @return string The permalink for the specified month and year archive.
581 */
582function get_month_link( $year, $month ) {
583 global $wp_rewrite;
584 if ( ! $year ) {
585 $year = current_time( 'Y' );
586 }
587 if ( ! $month ) {
588 $month = current_time( 'm' );
589 }
590 $monthlink = $wp_rewrite->get_month_permastruct();
591 if ( ! empty( $monthlink ) ) {
592 $monthlink = str_replace( '%year%', $year, $monthlink );
593 $monthlink = str_replace( '%monthnum%', zeroise( (int) $month, 2 ), $monthlink );
594 $monthlink = home_url( user_trailingslashit( $monthlink, 'month' ) );
595 } else {
596 $monthlink = home_url( '?m=' . $year . zeroise( $month, 2 ) );
597 }
598
599 /**
600 * Filters the month archive permalink.
601 *
602 * @since 1.5.0
603 *
604 * @param string $monthlink Permalink for the month archive.
605 * @param int $year Year for the archive.
606 * @param int $month The month for the archive.
607 */
608 return apply_filters( 'month_link', $monthlink, $year, $month );
609}
610
611/**
612 * Retrieves the permalink for the day archives with year and month.
613 *
614 * @since 1.0.0
615 *
616 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
617 *
618 * @param int|false $year Integer of year. False for current year.
619 * @param int|false $month Integer of month. False for current month.
620 * @param int|false $day Integer of day. False for current day.
621 * @return string The permalink for the specified day, month, and year archive.
622 */
623function get_day_link( $year, $month, $day ) {
624 global $wp_rewrite;
625 if ( ! $year ) {
626 $year = current_time( 'Y' );
627 }
628 if ( ! $month ) {
629 $month = current_time( 'm' );
630 }
631 if ( ! $day ) {
632 $day = current_time( 'j' );
633 }
634
635 $daylink = $wp_rewrite->get_day_permastruct();
636 if ( ! empty( $daylink ) ) {
637 $daylink = str_replace( '%year%', $year, $daylink );
638 $daylink = str_replace( '%monthnum%', zeroise( (int) $month, 2 ), $daylink );
639 $daylink = str_replace( '%day%', zeroise( (int) $day, 2 ), $daylink );
640 $daylink = home_url( user_trailingslashit( $daylink, 'day' ) );
641 } else {
642 $daylink = home_url( '?m=' . $year . zeroise( $month, 2 ) . zeroise( $day, 2 ) );
643 }
644
645 /**
646 * Filters the day archive permalink.
647 *
648 * @since 1.5.0
649 *
650 * @param string $daylink Permalink for the day archive.
651 * @param int $year Year for the archive.
652 * @param int $month Month for the archive.
653 * @param int $day The day for the archive.
654 */
655 return apply_filters( 'day_link', $daylink, $year, $month, $day );
656}
657
658/**
659 * Displays the permalink for the feed type.
660 *
661 * @since 3.0.0
662 *
663 * @param string $anchor The link's anchor text.
664 * @param string $feed Optional. Feed type. Possible values include 'rss2', 'atom'.
665 * Default is the value of get_default_feed().
666 */
667function the_feed_link( $anchor, $feed = '' ) {
668 $link = '<a href="' . esc_url( get_feed_link( $feed ) ) . '">' . $anchor . '</a>';
669
670 /**
671 * Filters the feed link anchor tag.
672 *
673 * @since 3.0.0
674 *
675 * @param string $link The complete anchor tag for a feed link.
676 * @param string $feed The feed type. Possible values include 'rss2', 'atom',
677 * or an empty string for the default feed type.
678 */
679 echo apply_filters( 'the_feed_link', $link, $feed );
680}
681
682/**
683 * Retrieves the permalink for the feed type.
684 *
685 * @since 1.5.0
686 *
687 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
688 *
689 * @param string $feed Optional. Feed type. Possible values include 'rss2', 'atom'.
690 * Default is the value of get_default_feed().
691 * @return string The feed permalink.
692 */
693function get_feed_link( $feed = '' ) {
694 global $wp_rewrite;
695
696 $permalink = $wp_rewrite->get_feed_permastruct();
697
698 if ( $permalink ) {
699 if ( str_contains( $feed, 'comments_' ) ) {
700 $feed = str_replace( 'comments_', '', $feed );
701 $permalink = $wp_rewrite->get_comment_feed_permastruct();
702 }
703
704 if ( get_default_feed() === $feed ) {
705 $feed = '';
706 }
707
708 $permalink = str_replace( '%feed%', $feed, $permalink );
709 $permalink = preg_replace( '#/+#', '/', "/$permalink" );
710 $output = home_url( user_trailingslashit( $permalink, 'feed' ) );
711 } else {
712 if ( empty( $feed ) ) {
713 $feed = get_default_feed();
714 }
715
716 if ( str_contains( $feed, 'comments_' ) ) {
717 $feed = str_replace( 'comments_', 'comments-', $feed );
718 }
719
720 $output = home_url( "?feed={$feed}" );
721 }
722
723 /**
724 * Filters the feed type permalink.
725 *
726 * @since 1.5.0
727 *
728 * @param string $output The feed permalink.
729 * @param string $feed The feed type. Possible values include 'rss2', 'atom',
730 * or an empty string for the default feed type.
731 */
732 return apply_filters( 'feed_link', $output, $feed );
733}
734
735/**
736 * Retrieves the permalink for the post comments feed.
737 *
738 * @since 2.2.0
739 *
740 * @param int $post_id Optional. Post ID. Default is the ID of the global `$post`.
741 * @param string $feed Optional. Feed type. Possible values include 'rss2', 'atom'.
742 * Default is the value of get_default_feed().
743 * @return string The permalink for the comments feed for the given post on success, empty string on failure.
744 */
745function get_post_comments_feed_link( $post_id = 0, $feed = '' ) {
746 $post_id = absint( $post_id );
747
748 if ( ! $post_id ) {
749 $post_id = get_the_ID();
750 }
751
752 if ( empty( $feed ) ) {
753 $feed = get_default_feed();
754 }
755
756 $post = get_post( $post_id );
757
758 // Bail out if the post does not exist.
759 if ( ! $post instanceof WP_Post ) {
760 return '';
761 }
762
763 $unattached = 'attachment' === $post->post_type && 0 === (int) $post->post_parent;
764
765 if ( get_option( 'permalink_structure' ) ) {
766 if ( 'page' === get_option( 'show_on_front' ) && (int) get_option( 'page_on_front' ) === $post_id ) {
767 $url = _get_page_link( $post_id );
768 } else {
769 $url = get_permalink( $post_id );
770 }
771
772 if ( $unattached ) {
773 $url = home_url( '/feed/' );
774 if ( get_default_feed() !== $feed ) {
775 $url .= "$feed/";
776 }
777 $url = add_query_arg( 'attachment_id', $post_id, $url );
778 } else {
779 $url = trailingslashit( $url ) . 'feed';
780 if ( get_default_feed() !== $feed ) {
781 $url .= "/$feed";
782 }
783 $url = user_trailingslashit( $url, 'single_feed' );
784 }
785 } else {
786 if ( $unattached ) {
787 $url = add_query_arg(
788 array(
789 'feed' => $feed,
790 'attachment_id' => $post_id,
791 ),
792 home_url( '/' )
793 );
794 } elseif ( 'page' === $post->post_type ) {
795 $url = add_query_arg(
796 array(
797 'feed' => $feed,
798 'page_id' => $post_id,
799 ),
800 home_url( '/' )
801 );
802 } else {
803 $url = add_query_arg(
804 array(
805 'feed' => $feed,
806 'p' => $post_id,
807 ),
808 home_url( '/' )
809 );
810 }
811 }
812
813 /**
814 * Filters the post comments feed permalink.
815 *
816 * @since 1.5.1
817 *
818 * @param string $url Post comments feed permalink.
819 */
820 return apply_filters( 'post_comments_feed_link', $url );
821}
822
823/**
824 * Displays the comment feed link for a post.
825 *
826 * Prints out the comment feed link for a post. Link text is placed in the
827 * anchor. If no link text is specified, default text is used. If no post ID is
828 * specified, the current post is used.
829 *
830 * @since 2.5.0
831 *
832 * @param string $link_text Optional. Descriptive link text. Default 'Comments Feed'.
833 * @param int $post_id Optional. Post ID. Default is the ID of the global `$post`.
834 * @param string $feed Optional. Feed type. Possible values include 'rss2', 'atom'.
835 * Default is the value of get_default_feed().
836 */
837function post_comments_feed_link( $link_text = '', $post_id = 0, $feed = '' ) {
838 $url = get_post_comments_feed_link( $post_id, $feed );
839 if ( empty( $link_text ) ) {
840 $link_text = __( 'Comments Feed' );
841 }
842
843 $link = '<a href="' . esc_url( $url ) . '">' . $link_text . '</a>';
844 /**
845 * Filters the post comment feed link anchor tag.
846 *
847 * @since 2.8.0
848 *
849 * @param string $link The complete anchor tag for the comment feed link.
850 * @param int $post_id Post ID.
851 * @param string $feed The feed type. Possible values include 'rss2', 'atom',
852 * or an empty string for the default feed type.
853 */
854 echo apply_filters( 'post_comments_feed_link_html', $link, $post_id, $feed );
855}
856
857/**
858 * Retrieves the feed link for a given author.
859 *
860 * Returns a link to the feed for all posts by a given author. A specific feed
861 * can be requested or left blank to get the default feed.
862 *
863 * @since 2.5.0
864 *
865 * @param int $author_id Author ID.
866 * @param string $feed Optional. Feed type. Possible values include 'rss2', 'atom'.
867 * Default is the value of get_default_feed().
868 * @return string Link to the feed for the author specified by $author_id.
869 */
870function get_author_feed_link( $author_id, $feed = '' ) {
871 $author_id = (int) $author_id;
872 $permalink_structure = get_option( 'permalink_structure' );
873
874 if ( empty( $feed ) ) {
875 $feed = get_default_feed();
876 }
877
878 if ( ! $permalink_structure ) {
879 $link = home_url( "?feed=$feed&amp;author=" . $author_id );
880 } else {
881 $link = get_author_posts_url( $author_id );
882 if ( get_default_feed() === $feed ) {
883 $feed_link = 'feed';
884 } else {
885 $feed_link = "feed/$feed";
886 }
887
888 $link = trailingslashit( $link ) . user_trailingslashit( $feed_link, 'feed' );
889 }
890
891 /**
892 * Filters the feed link for a given author.
893 *
894 * @since 1.5.1
895 *
896 * @param string $link The author feed link.
897 * @param string $feed Feed type. Possible values include 'rss2', 'atom'.
898 */
899 $link = apply_filters( 'author_feed_link', $link, $feed );
900
901 return $link;
902}
903
904/**
905 * Retrieves the feed link for a category.
906 *
907 * Returns a link to the feed for all posts in a given category. A specific feed
908 * can be requested or left blank to get the default feed.
909 *
910 * @since 2.5.0
911 *
912 * @param int|WP_Term|object $cat The ID or category object whose feed link will be retrieved.
913 * @param string $feed Optional. Feed type. Possible values include 'rss2', 'atom'.
914 * Default is the value of get_default_feed().
915 * @return string Link to the feed for the category specified by `$cat`.
916 */
917function get_category_feed_link( $cat, $feed = '' ) {
918 return get_term_feed_link( $cat, 'category', $feed );
919}
920
921/**
922 * Retrieves the feed link for a term.
923 *
924 * Returns a link to the feed for all posts in a given term. A specific feed
925 * can be requested or left blank to get the default feed.
926 *
927 * @since 3.0.0
928 *
929 * @param int|WP_Term|object $term The ID or term object whose feed link will be retrieved.
930 * @param string $taxonomy Optional. Taxonomy of `$term_id`.
931 * @param string $feed Optional. Feed type. Possible values include 'rss2', 'atom'.
932 * Default is the value of get_default_feed().
933 * @return string|false Link to the feed for the term specified by `$term` and `$taxonomy`.
934 */
935function get_term_feed_link( $term, $taxonomy = '', $feed = '' ) {
936 if ( ! is_object( $term ) ) {
937 $term = (int) $term;
938 }
939
940 $term = get_term( $term, $taxonomy );
941
942 if ( empty( $term ) || is_wp_error( $term ) ) {
943 return false;
944 }
945
946 $taxonomy = $term->taxonomy;
947
948 if ( empty( $feed ) ) {
949 $feed = get_default_feed();
950 }
951
952 $permalink_structure = get_option( 'permalink_structure' );
953
954 if ( ! $permalink_structure ) {
955 if ( 'category' === $taxonomy ) {
956 $link = home_url( "?feed=$feed&amp;cat=$term->term_id" );
957 } elseif ( 'post_tag' === $taxonomy ) {
958 $link = home_url( "?feed=$feed&amp;tag=$term->slug" );
959 } else {
960 $t = get_taxonomy( $taxonomy );
961 $link = home_url( "?feed=$feed&amp;$t->query_var=$term->slug" );
962 }
963 } else {
964 $link = get_term_link( $term, $term->taxonomy );
965 if ( get_default_feed() === $feed ) {
966 $feed_link = 'feed';
967 } else {
968 $feed_link = "feed/$feed";
969 }
970
971 $link = trailingslashit( $link ) . user_trailingslashit( $feed_link, 'feed' );
972 }
973
974 if ( 'category' === $taxonomy ) {
975 /**
976 * Filters the category feed link.
977 *
978 * @since 1.5.1
979 *
980 * @param string $link The category feed link.
981 * @param string $feed Feed type. Possible values include 'rss2', 'atom'.
982 */
983 $link = apply_filters( 'category_feed_link', $link, $feed );
984 } elseif ( 'post_tag' === $taxonomy ) {
985 /**
986 * Filters the post tag feed link.
987 *
988 * @since 2.3.0
989 *
990 * @param string $link The tag feed link.
991 * @param string $feed Feed type. Possible values include 'rss2', 'atom'.
992 */
993 $link = apply_filters( 'tag_feed_link', $link, $feed );
994 } else {
995 /**
996 * Filters the feed link for a taxonomy other than 'category' or 'post_tag'.
997 *
998 * @since 3.0.0
999 *
1000 * @param string $link The taxonomy feed link.
1001 * @param string $feed Feed type. Possible values include 'rss2', 'atom'.
1002 * @param string $taxonomy The taxonomy name.
1003 */
1004 $link = apply_filters( 'taxonomy_feed_link', $link, $feed, $taxonomy );
1005 }
1006
1007 return $link;
1008}
1009
1010/**
1011 * Retrieves the permalink for a tag feed.
1012 *
1013 * @since 2.3.0
1014 *
1015 * @param int|WP_Term|object $tag The ID or term object whose feed link will be retrieved.
1016 * @param string $feed Optional. Feed type. Possible values include 'rss2', 'atom'.
1017 * Default is the value of get_default_feed().
1018 * @return string The feed permalink for the given tag.
1019 */
1020function get_tag_feed_link( $tag, $feed = '' ) {
1021 return get_term_feed_link( $tag, 'post_tag', $feed );
1022}
1023
1024/**
1025 * Retrieves the edit link for a tag.
1026 *
1027 * @since 2.7.0
1028 *
1029 * @param int|WP_Term|object $tag The ID or term object whose edit link will be retrieved.
1030 * @param string $taxonomy Optional. Taxonomy slug. Default 'post_tag'.
1031 * @return string The edit tag link URL for the given tag.
1032 */
1033function get_edit_tag_link( $tag, $taxonomy = 'post_tag' ) {
1034 /**
1035 * Filters the edit link for a tag (or term in another taxonomy).
1036 *
1037 * @since 2.7.0
1038 *
1039 * @param string $link The term edit link.
1040 */
1041 return apply_filters( 'get_edit_tag_link', get_edit_term_link( $tag, $taxonomy ) );
1042}
1043
1044/**
1045 * Displays or retrieves the edit link for a tag with formatting.
1046 *
1047 * @since 2.7.0
1048 *
1049 * @param string $link Optional. Anchor text. If empty, default is 'Edit This'. Default empty.
1050 * @param string $before Optional. Display before edit link. Default empty.
1051 * @param string $after Optional. Display after edit link. Default empty.
1052 * @param WP_Term $tag Optional. Term object. If null, the queried object will be inspected.
1053 * Default null.
1054 */
1055function edit_tag_link( $link = '', $before = '', $after = '', $tag = null ) {
1056 $link = edit_term_link( $link, '', '', $tag, false );
1057
1058 /**
1059 * Filters the anchor tag for the edit link for a tag (or term in another taxonomy).
1060 *
1061 * @since 2.7.0
1062 *
1063 * @param string $link The anchor tag for the edit link.
1064 */
1065 echo $before . apply_filters( 'edit_tag_link', $link ) . $after;
1066}
1067
1068/**
1069 * Retrieves the URL for editing a given term.
1070 *
1071 * @since 3.1.0
1072 * @since 4.5.0 The `$taxonomy` parameter was made optional.
1073 *
1074 * @param int|WP_Term|object $term The ID or term object whose edit link will be retrieved.
1075 * @param string $taxonomy Optional. Taxonomy. Defaults to the taxonomy of the term identified
1076 * by `$term`.
1077 * @param string $object_type Optional. The object type. Used to highlight the proper post type
1078 * menu on the linked page. Defaults to the first object_type associated
1079 * with the taxonomy.
1080 * @return string|null The edit term link URL for the given term, or null on failure.
1081 */
1082function get_edit_term_link( $term, $taxonomy = '', $object_type = '' ) {
1083 $term = get_term( $term, $taxonomy );
1084 if ( ! $term || is_wp_error( $term ) ) {
1085 return;
1086 }
1087
1088 $tax = get_taxonomy( $term->taxonomy );
1089 $term_id = $term->term_id;
1090 if ( ! $tax || ! current_user_can( 'edit_term', $term_id ) ) {
1091 return;
1092 }
1093
1094 $args = array(
1095 'taxonomy' => $tax->name,
1096 'tag_ID' => $term_id,
1097 );
1098
1099 if ( $object_type ) {
1100 $args['post_type'] = $object_type;
1101 } elseif ( ! empty( $tax->object_type ) ) {
1102 $args['post_type'] = reset( $tax->object_type );
1103 }
1104
1105 if ( $tax->show_ui ) {
1106 $location = add_query_arg( $args, admin_url( 'term.php' ) );
1107 } else {
1108 $location = '';
1109 }
1110
1111 /**
1112 * Filters the edit link for a term.
1113 *
1114 * @since 3.1.0
1115 *
1116 * @param string $location The edit link.
1117 * @param int $term_id Term ID.
1118 * @param string $taxonomy Taxonomy name.
1119 * @param string $object_type The object type.
1120 */
1121 return apply_filters( 'get_edit_term_link', $location, $term_id, $taxonomy, $object_type );
1122}
1123
1124/**
1125 * Displays or retrieves the edit term link with formatting.
1126 *
1127 * @since 3.1.0
1128 *
1129 * @param string $link Optional. Anchor text. If empty, default is 'Edit This'. Default empty.
1130 * @param string $before Optional. Display before edit link. Default empty.
1131 * @param string $after Optional. Display after edit link. Default empty.
1132 * @param int|WP_Term|null $term Optional. Term ID or object. If null, the queried object will be inspected. Default null.
1133 * @param bool $display Optional. Whether or not to echo the return. Default true.
1134 * @return string|void HTML content.
1135 */
1136function edit_term_link( $link = '', $before = '', $after = '', $term = null, $display = true ) {
1137 if ( is_null( $term ) ) {
1138 $term = get_queried_object();
1139 } else {
1140 $term = get_term( $term );
1141 }
1142
1143 if ( ! $term ) {
1144 return;
1145 }
1146
1147 $tax = get_taxonomy( $term->taxonomy );
1148 if ( ! current_user_can( 'edit_term', $term->term_id ) ) {
1149 return;
1150 }
1151
1152 if ( empty( $link ) ) {
1153 $link = __( 'Edit This' );
1154 }
1155
1156 $link = '<a href="' . get_edit_term_link( $term->term_id, $term->taxonomy ) . '">' . $link . '</a>';
1157
1158 /**
1159 * Filters the anchor tag for the edit link of a term.
1160 *
1161 * @since 3.1.0
1162 *
1163 * @param string $link The anchor tag for the edit link.
1164 * @param int $term_id Term ID.
1165 */
1166 $link = $before . apply_filters( 'edit_term_link', $link, $term->term_id ) . $after;
1167
1168 if ( $display ) {
1169 echo $link;
1170 } else {
1171 return $link;
1172 }
1173}
1174
1175/**
1176 * Retrieves the permalink for a search.
1177 *
1178 * @since 3.0.0
1179 *
1180 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
1181 *
1182 * @param string $query Optional. The query string to use. If empty the current query is used. Default empty.
1183 * @return string The search permalink.
1184 */
1185function get_search_link( $query = '' ) {
1186 global $wp_rewrite;
1187
1188 if ( empty( $query ) ) {
1189 $search = get_search_query( false );
1190 } else {
1191 $search = stripslashes( $query );
1192 }
1193
1194 $permastruct = $wp_rewrite->get_search_permastruct();
1195
1196 if ( empty( $permastruct ) ) {
1197 $link = home_url( '?s=' . urlencode( $search ) );
1198 } else {
1199 $search = urlencode( $search );
1200 $search = str_replace( '%2F', '/', $search ); // %2F(/) is not valid within a URL, send it un-encoded.
1201 $link = str_replace( '%search%', $search, $permastruct );
1202 $link = home_url( user_trailingslashit( $link, 'search' ) );
1203 }
1204
1205 /**
1206 * Filters the search permalink.
1207 *
1208 * @since 3.0.0
1209 *
1210 * @param string $link Search permalink.
1211 * @param string $search The URL-encoded search term.
1212 */
1213 return apply_filters( 'search_link', $link, $search );
1214}
1215
1216/**
1217 * Retrieves the permalink for the search results feed.
1218 *
1219 * @since 2.5.0
1220 *
1221 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
1222 *
1223 * @param string $search_query Optional. Search query. Default empty.
1224 * @param string $feed Optional. Feed type. Possible values include 'rss2', 'atom'.
1225 * Default is the value of get_default_feed().
1226 * @return string The search results feed permalink.
1227 */
1228function get_search_feed_link( $search_query = '', $feed = '' ) {
1229 global $wp_rewrite;
1230 $link = get_search_link( $search_query );
1231
1232 if ( empty( $feed ) ) {
1233 $feed = get_default_feed();
1234 }
1235
1236 $permastruct = $wp_rewrite->get_search_permastruct();
1237
1238 if ( empty( $permastruct ) ) {
1239 $link = add_query_arg( 'feed', $feed, $link );
1240 } else {
1241 $link = trailingslashit( $link );
1242 $link .= "feed/$feed/";
1243 }
1244
1245 /**
1246 * Filters the search feed link.
1247 *
1248 * @since 2.5.0
1249 *
1250 * @param string $link Search feed link.
1251 * @param string $feed Feed type. Possible values include 'rss2', 'atom'.
1252 * @param string $type The search type. One of 'posts' or 'comments'.
1253 */
1254 return apply_filters( 'search_feed_link', $link, $feed, 'posts' );
1255}
1256
1257/**
1258 * Retrieves the permalink for the search results comments feed.
1259 *
1260 * @since 2.5.0
1261 *
1262 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
1263 *
1264 * @param string $search_query Optional. Search query. Default empty.
1265 * @param string $feed Optional. Feed type. Possible values include 'rss2', 'atom'.
1266 * Default is the value of get_default_feed().
1267 * @return string The comments feed search results permalink.
1268 */
1269function get_search_comments_feed_link( $search_query = '', $feed = '' ) {
1270 global $wp_rewrite;
1271
1272 if ( empty( $feed ) ) {
1273 $feed = get_default_feed();
1274 }
1275
1276 $link = get_search_feed_link( $search_query, $feed );
1277
1278 $permastruct = $wp_rewrite->get_search_permastruct();
1279
1280 if ( empty( $permastruct ) ) {
1281 $link = add_query_arg( 'feed', 'comments-' . $feed, $link );
1282 } else {
1283 $link = add_query_arg( 'withcomments', 1, $link );
1284 }
1285
1286 /** This filter is documented in wp-includes/link-template.php */
1287 return apply_filters( 'search_feed_link', $link, $feed, 'comments' );
1288}
1289
1290/**
1291 * Retrieves the permalink for a post type archive.
1292 *
1293 * @since 3.1.0
1294 * @since 4.5.0 Support for posts was added.
1295 *
1296 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
1297 *
1298 * @param string $post_type Post type.
1299 * @return string|false The post type archive permalink. False if the post type
1300 * does not exist or does not have an archive.
1301 */
1302function get_post_type_archive_link( $post_type ) {
1303 global $wp_rewrite;
1304
1305 $post_type_obj = get_post_type_object( $post_type );
1306
1307 if ( ! $post_type_obj ) {
1308 return false;
1309 }
1310
1311 if ( 'post' === $post_type ) {
1312 $show_on_front = get_option( 'show_on_front' );
1313 $page_for_posts = get_option( 'page_for_posts' );
1314
1315 if ( 'page' === $show_on_front && $page_for_posts ) {
1316 $link = get_permalink( $page_for_posts );
1317 } else {
1318 $link = get_home_url();
1319 }
1320 /** This filter is documented in wp-includes/link-template.php */
1321 return apply_filters( 'post_type_archive_link', $link, $post_type );
1322 }
1323
1324 if ( ! $post_type_obj->has_archive ) {
1325 return false;
1326 }
1327
1328 if ( get_option( 'permalink_structure' ) && is_array( $post_type_obj->rewrite ) ) {
1329 $struct = ( true === $post_type_obj->has_archive ) ? $post_type_obj->rewrite['slug'] : $post_type_obj->has_archive;
1330 if ( $post_type_obj->rewrite['with_front'] ) {
1331 $struct = $wp_rewrite->front . $struct;
1332 } else {
1333 $struct = $wp_rewrite->root . $struct;
1334 }
1335 $link = home_url( user_trailingslashit( $struct, 'post_type_archive' ) );
1336 } else {
1337 $link = home_url( '?post_type=' . $post_type );
1338 }
1339
1340 /**
1341 * Filters the post type archive permalink.
1342 *
1343 * @since 3.1.0
1344 *
1345 * @param string $link The post type archive permalink.
1346 * @param string $post_type Post type name.
1347 */
1348 return apply_filters( 'post_type_archive_link', $link, $post_type );
1349}
1350
1351/**
1352 * Retrieves the permalink for a post type archive feed.
1353 *
1354 * @since 3.1.0
1355 *
1356 * @param string $post_type Post type.
1357 * @param string $feed Optional. Feed type. Possible values include 'rss2', 'atom'.
1358 * Default is the value of get_default_feed().
1359 * @return string|false The post type feed permalink. False if the post type
1360 * does not exist or does not have an archive.
1361 */
1362function get_post_type_archive_feed_link( $post_type, $feed = '' ) {
1363 $default_feed = get_default_feed();
1364 if ( empty( $feed ) ) {
1365 $feed = $default_feed;
1366 }
1367
1368 $link = get_post_type_archive_link( $post_type );
1369 if ( ! $link ) {
1370 return false;
1371 }
1372
1373 $post_type_obj = get_post_type_object( $post_type );
1374 if ( get_option( 'permalink_structure' ) && is_array( $post_type_obj->rewrite ) && $post_type_obj->rewrite['feeds'] ) {
1375 $link = trailingslashit( $link );
1376 $link .= 'feed/';
1377 if ( $feed !== $default_feed ) {
1378 $link .= "$feed/";
1379 }
1380 } else {
1381 $link = add_query_arg( 'feed', $feed, $link );
1382 }
1383
1384 /**
1385 * Filters the post type archive feed link.
1386 *
1387 * @since 3.1.0
1388 *
1389 * @param string $link The post type archive feed link.
1390 * @param string $feed Feed type. Possible values include 'rss2', 'atom'.
1391 */
1392 return apply_filters( 'post_type_archive_feed_link', $link, $feed );
1393}
1394
1395/**
1396 * Retrieves the URL used for the post preview.
1397 *
1398 * Allows additional query args to be appended.
1399 *
1400 * @since 4.4.0
1401 *
1402 * @param int|WP_Post $post Optional. Post ID or `WP_Post` object. Defaults to global `$post`.
1403 * @param array $query_args Optional. Array of additional query args to be appended to the link.
1404 * Default empty array.
1405 * @param string $preview_link Optional. Base preview link to be used if it should differ from the
1406 * post permalink. Default empty.
1407 * @return string|null URL used for the post preview, or null if the post does not exist.
1408 */
1409function get_preview_post_link( $post = null, $query_args = array(), $preview_link = '' ) {
1410 $post = get_post( $post );
1411
1412 if ( ! $post ) {
1413 return;
1414 }
1415
1416 $post_type_object = get_post_type_object( $post->post_type );
1417 if ( is_post_type_viewable( $post_type_object ) ) {
1418 if ( ! $preview_link ) {
1419 $preview_link = set_url_scheme( get_permalink( $post ) );
1420 }
1421
1422 $query_args['preview'] = 'true';
1423 $preview_link = add_query_arg( $query_args, $preview_link );
1424 }
1425
1426 /**
1427 * Filters the URL used for a post preview.
1428 *
1429 * @since 2.0.5
1430 * @since 4.0.0 Added the `$post` parameter.
1431 *
1432 * @param string $preview_link URL used for the post preview.
1433 * @param WP_Post $post Post object.
1434 */
1435 return apply_filters( 'preview_post_link', $preview_link, $post );
1436}
1437
1438/**
1439 * Retrieves the edit post link for post.
1440 *
1441 * Can be used within the WordPress loop or outside of it. Can be used with
1442 * pages, posts, attachments, revisions, global styles, templates, and template parts.
1443 *
1444 * @since 2.3.0
1445 * @since 6.3.0 Adds custom link for wp_navigation post types.
1446 * Adds custom links for wp_template_part and wp_template post types.
1447 *
1448 * @param int|WP_Post $post Optional. Post ID or post object. Default is the global `$post`.
1449 * @param string $context Optional. How to output the '&' character. Default '&amp;'.
1450 * @return string|null The edit post link for the given post. Null if the post type does not exist
1451 * or does not allow an editing UI.
1452 */
1453function get_edit_post_link( $post = 0, $context = 'display' ) {
1454 $post = get_post( $post );
1455
1456 if ( ! $post ) {
1457 return;
1458 }
1459
1460 if ( 'revision' === $post->post_type ) {
1461 $action = '';
1462 } elseif ( 'display' === $context ) {
1463 $action = '&amp;action=edit';
1464 } else {
1465 $action = '&action=edit';
1466 }
1467
1468 $post_type_object = get_post_type_object( $post->post_type );
1469
1470 if ( ! $post_type_object ) {
1471 return;
1472 }
1473
1474 if ( ! current_user_can( 'edit_post', $post->ID ) ) {
1475 return;
1476 }
1477
1478 $link = '';
1479
1480 if ( 'wp_template' === $post->post_type || 'wp_template_part' === $post->post_type ) {
1481 $slug = urlencode( get_stylesheet() . '//' . $post->post_name );
1482 $link = admin_url( sprintf( $post_type_object->_edit_link, $post->post_type, $slug ) );
1483 } elseif ( 'wp_navigation' === $post->post_type ) {
1484 $link = admin_url( sprintf( $post_type_object->_edit_link, (string) $post->ID ) );
1485 } elseif ( $post_type_object->_edit_link ) {
1486 $link = admin_url( sprintf( $post_type_object->_edit_link . $action, $post->ID ) );
1487 }
1488
1489 /**
1490 * Filters the post edit link.
1491 *
1492 * @since 2.3.0
1493 *
1494 * @param string $link The edit link.
1495 * @param int $post_id Post ID.
1496 * @param string $context The link context. If set to 'display' then ampersands
1497 * are encoded.
1498 */
1499 return apply_filters( 'get_edit_post_link', $link, $post->ID, $context );
1500}
1501
1502/**
1503 * Displays the edit post link for post.
1504 *
1505 * @since 1.0.0
1506 * @since 4.4.0 The `$css_class` argument was added.
1507 *
1508 * @param string $text Optional. Anchor text. If null, default is 'Edit This'. Default null.
1509 * @param string $before Optional. Display before edit link. Default empty.
1510 * @param string $after Optional. Display after edit link. Default empty.
1511 * @param int|WP_Post $post Optional. Post ID or post object. Default is the global `$post`.
1512 * @param string $css_class Optional. Add custom class to link. Default 'post-edit-link'.
1513 */
1514function edit_post_link( $text = null, $before = '', $after = '', $post = 0, $css_class = 'post-edit-link' ) {
1515 $post = get_post( $post );
1516
1517 if ( ! $post ) {
1518 return;
1519 }
1520
1521 $url = get_edit_post_link( $post->ID );
1522
1523 if ( ! $url ) {
1524 return;
1525 }
1526
1527 if ( null === $text ) {
1528 $text = __( 'Edit This' );
1529 }
1530
1531 $link = '<a class="' . esc_attr( $css_class ) . '" href="' . esc_url( $url ) . '">' . $text . '</a>';
1532
1533 /**
1534 * Filters the post edit link anchor tag.
1535 *
1536 * @since 2.3.0
1537 *
1538 * @param string $link Anchor tag for the edit link.
1539 * @param int $post_id Post ID.
1540 * @param string $text Anchor text.
1541 */
1542 echo $before . apply_filters( 'edit_post_link', $link, $post->ID, $text ) . $after;
1543}
1544
1545/**
1546 * Retrieves the delete posts link for post.
1547 *
1548 * Can be used within the WordPress loop or outside of it, with any post type.
1549 *
1550 * @since 2.9.0
1551 *
1552 * @param int|WP_Post $post Optional. Post ID or post object. Default is the global `$post`.
1553 * @param string $deprecated Not used.
1554 * @param bool $force_delete Optional. Whether to bypass Trash and force deletion. Default false.
1555 * @return string|void The delete post link URL for the given post.
1556 */
1557function get_delete_post_link( $post = 0, $deprecated = '', $force_delete = false ) {
1558 if ( ! empty( $deprecated ) ) {
1559 _deprecated_argument( __FUNCTION__, '3.0.0' );
1560 }
1561
1562 $post = get_post( $post );
1563
1564 if ( ! $post ) {
1565 return;
1566 }
1567
1568 $post_type_object = get_post_type_object( $post->post_type );
1569
1570 if ( ! $post_type_object ) {
1571 return;
1572 }
1573
1574 if ( ! current_user_can( 'delete_post', $post->ID ) ) {
1575 return;
1576 }
1577
1578 $action = ( $force_delete || ! EMPTY_TRASH_DAYS ) ? 'delete' : 'trash';
1579
1580 $delete_link = add_query_arg( 'action', $action, admin_url( sprintf( $post_type_object->_edit_link, $post->ID ) ) );
1581
1582 /**
1583 * Filters the post delete link.
1584 *
1585 * @since 2.9.0
1586 *
1587 * @param string $link The delete link.
1588 * @param int $post_id Post ID.
1589 * @param bool $force_delete Whether to bypass the Trash and force deletion. Default false.
1590 */
1591 return apply_filters( 'get_delete_post_link', wp_nonce_url( $delete_link, "$action-post_{$post->ID}" ), $post->ID, $force_delete );
1592}
1593
1594/**
1595 * Retrieves the edit comment link.
1596 *
1597 * @since 2.3.0
1598 * @since 6.7.0 The $context parameter was added.
1599 *
1600 * @param int|WP_Comment $comment_id Optional. Comment ID or WP_Comment object.
1601 * @param string $context Optional. Context in which the URL should be used. Either 'display',
1602 * to include HTML entities, or 'url'. Default 'display'.
1603 * @return string|void The edit comment link URL for the given comment, or void if the comment id does not exist or
1604 * the current user is not allowed to edit it.
1605 */
1606function get_edit_comment_link( $comment_id = 0, $context = 'display' ) {
1607 $comment = get_comment( $comment_id );
1608
1609 if ( ! is_object( $comment ) || ! current_user_can( 'edit_comment', $comment->comment_ID ) ) {
1610 return;
1611 }
1612
1613 if ( 'display' === $context ) {
1614 $action = 'comment.php?action=editcomment&amp;c=';
1615 } else {
1616 $action = 'comment.php?action=editcomment&c=';
1617 }
1618
1619 $location = admin_url( $action ) . $comment->comment_ID;
1620
1621 // Ensure the $comment_id variable passed to the filter is always an ID.
1622 $comment_id = (int) $comment->comment_ID;
1623
1624 /**
1625 * Filters the comment edit link.
1626 *
1627 * @since 2.3.0
1628 * @since 6.7.0 The $comment_id and $context parameters are now being passed to the filter.
1629 *
1630 * @param string $location The edit link.
1631 * @param int $comment_id Unique ID of the comment to generate an edit link.
1632 * @param string $context Context to include HTML entities in link. Default 'display'.
1633 */
1634 return apply_filters( 'get_edit_comment_link', $location, $comment_id, $context );
1635}
1636
1637/**
1638 * Displays the edit comment link with formatting.
1639 *
1640 * @since 1.0.0
1641 *
1642 * @param string $text Optional. Anchor text. If null, default is 'Edit This'. Default null.
1643 * @param string $before Optional. Display before edit link. Default empty.
1644 * @param string $after Optional. Display after edit link. Default empty.
1645 */
1646function edit_comment_link( $text = null, $before = '', $after = '' ) {
1647 $comment = get_comment();
1648
1649 if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) ) {
1650 return;
1651 }
1652
1653 if ( null === $text ) {
1654 $text = __( 'Edit This' );
1655 }
1656
1657 $link = '<a class="comment-edit-link" href="' . esc_url( get_edit_comment_link( $comment ) ) . '">' . $text . '</a>';
1658
1659 /**
1660 * Filters the comment edit link anchor tag.
1661 *
1662 * @since 2.3.0
1663 *
1664 * @param string $link Anchor tag for the edit link.
1665 * @param string $comment_id Comment ID as a numeric string.
1666 * @param string $text Anchor text.
1667 */
1668 echo $before . apply_filters( 'edit_comment_link', $link, $comment->comment_ID, $text ) . $after;
1669}
1670
1671/**
1672 * Displays the edit bookmark link.
1673 *
1674 * @since 2.7.0
1675 *
1676 * @param int|stdClass $link Optional. Bookmark ID. Default is the ID of the current bookmark.
1677 * @return string|void The edit bookmark link URL.
1678 */
1679function get_edit_bookmark_link( $link = 0 ) {
1680 $link = get_bookmark( $link );
1681
1682 if ( ! current_user_can( 'manage_links' ) ) {
1683 return;
1684 }
1685
1686 $location = admin_url( 'link.php?action=edit&amp;link_id=' ) . $link->link_id;
1687
1688 /**
1689 * Filters the bookmark edit link.
1690 *
1691 * @since 2.7.0
1692 *
1693 * @param string $location The edit link.
1694 * @param int $link_id Bookmark ID.
1695 */
1696 return apply_filters( 'get_edit_bookmark_link', $location, $link->link_id );
1697}
1698
1699/**
1700 * Displays the edit bookmark link anchor content.
1701 *
1702 * @since 2.7.0
1703 *
1704 * @param string $link Optional. Anchor text. If empty, default is 'Edit This'. Default empty.
1705 * @param string $before Optional. Display before edit link. Default empty.
1706 * @param string $after Optional. Display after edit link. Default empty.
1707 * @param int $bookmark Optional. Bookmark ID. Default is the current bookmark.
1708 */
1709function edit_bookmark_link( $link = '', $before = '', $after = '', $bookmark = null ) {
1710 $bookmark = get_bookmark( $bookmark );
1711
1712 if ( ! current_user_can( 'manage_links' ) ) {
1713 return;
1714 }
1715
1716 if ( empty( $link ) ) {
1717 $link = __( 'Edit This' );
1718 }
1719
1720 $link = '<a href="' . esc_url( get_edit_bookmark_link( $bookmark ) ) . '">' . $link . '</a>';
1721
1722 /**
1723 * Filters the bookmark edit link anchor tag.
1724 *
1725 * @since 2.7.0
1726 *
1727 * @param string $link Anchor tag for the edit link.
1728 * @param int $link_id Bookmark ID.
1729 */
1730 echo $before . apply_filters( 'edit_bookmark_link', $link, $bookmark->link_id ) . $after;
1731}
1732
1733/**
1734 * Retrieves the edit user link.
1735 *
1736 * @since 3.5.0
1737 *
1738 * @param int $user_id Optional. User ID. Defaults to the current user.
1739 * @return string URL to edit user page or empty string.
1740 */
1741function get_edit_user_link( $user_id = null ) {
1742 if ( ! $user_id ) {
1743 $user_id = get_current_user_id();
1744 }
1745
1746 if ( empty( $user_id ) || ! current_user_can( 'edit_user', $user_id ) ) {
1747 return '';
1748 }
1749
1750 $user = get_userdata( $user_id );
1751
1752 if ( ! $user ) {
1753 return '';
1754 }
1755
1756 if ( get_current_user_id() === $user->ID ) {
1757 $link = get_edit_profile_url( $user->ID );
1758 } else {
1759 $link = add_query_arg( 'user_id', $user->ID, self_admin_url( 'user-edit.php' ) );
1760 }
1761
1762 /**
1763 * Filters the user edit link.
1764 *
1765 * @since 3.5.0
1766 *
1767 * @param string $link The edit link.
1768 * @param int $user_id User ID.
1769 */
1770 return apply_filters( 'get_edit_user_link', $link, $user->ID );
1771}
1772
1773//
1774// Navigation links.
1775//
1776
1777/**
1778 * Retrieves the previous post that is adjacent to the current post.
1779 *
1780 * @since 1.5.0
1781 *
1782 * @param bool $in_same_term Optional. Whether post should be in the same taxonomy term.
1783 * Default false.
1784 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
1785 * Default empty.
1786 * @param string $taxonomy Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
1787 * @return WP_Post|null|string Post object if successful. Null if global `$post` is not set.
1788 * Empty string if no corresponding post exists.
1789 */
1790function get_previous_post( $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
1791 return get_adjacent_post( $in_same_term, $excluded_terms, true, $taxonomy );
1792}
1793
1794/**
1795 * Retrieves the next post that is adjacent to the current post.
1796 *
1797 * @since 1.5.0
1798 *
1799 * @param bool $in_same_term Optional. Whether post should be in the same taxonomy term.
1800 * Default false.
1801 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
1802 * Default empty.
1803 * @param string $taxonomy Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
1804 * @return WP_Post|null|string Post object if successful. Null if global `$post` is not set.
1805 * Empty string if no corresponding post exists.
1806 */
1807function get_next_post( $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
1808 return get_adjacent_post( $in_same_term, $excluded_terms, false, $taxonomy );
1809}
1810
1811/**
1812 * Retrieves the adjacent post.
1813 *
1814 * Can either be next or previous post.
1815 *
1816 * @since 2.5.0
1817 *
1818 * @global wpdb $wpdb WordPress database abstraction object.
1819 *
1820 * @param bool $in_same_term Optional. Whether post should be in the same taxonomy term.
1821 * Default false.
1822 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
1823 * Default empty string.
1824 * @param bool $previous Optional. Whether to retrieve previous post.
1825 * Default true.
1826 * @param string $taxonomy Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
1827 * @return WP_Post|null|string Post object if successful. Null if global `$post` is not set.
1828 * Empty string if no corresponding post exists.
1829 */
1830function get_adjacent_post( $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) {
1831 global $wpdb;
1832
1833 $post = get_post();
1834
1835 if ( ! $post || ! taxonomy_exists( $taxonomy ) ) {
1836 return null;
1837 }
1838
1839 $current_post_date = $post->post_date;
1840
1841 $join = '';
1842 $where = '';
1843 $adjacent = $previous ? 'previous' : 'next';
1844
1845 if ( ! empty( $excluded_terms ) && ! is_array( $excluded_terms ) ) {
1846 // Back-compat, $excluded_terms used to be $excluded_categories with IDs separated by " and ".
1847 if ( str_contains( $excluded_terms, ' and ' ) ) {
1848 _deprecated_argument(
1849 __FUNCTION__,
1850 '3.3.0',
1851 sprintf(
1852 /* translators: %s: The word 'and'. */
1853 __( 'Use commas instead of %s to separate excluded terms.' ),
1854 "'and'"
1855 )
1856 );
1857 $excluded_terms = explode( ' and ', $excluded_terms );
1858 } else {
1859 $excluded_terms = explode( ',', $excluded_terms );
1860 }
1861
1862 $excluded_terms = array_map( 'intval', $excluded_terms );
1863 }
1864
1865 /**
1866 * Filters the IDs of terms excluded from adjacent post queries.
1867 *
1868 * The dynamic portion of the hook name, `$adjacent`, refers to the type
1869 * of adjacency, 'next' or 'previous'.
1870 *
1871 * Possible hook names include:
1872 *
1873 * - `get_next_post_excluded_terms`
1874 * - `get_previous_post_excluded_terms`
1875 *
1876 * @since 4.4.0
1877 *
1878 * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
1879 */
1880 $excluded_terms = apply_filters( "get_{$adjacent}_post_excluded_terms", $excluded_terms );
1881
1882 if ( $in_same_term || ! empty( $excluded_terms ) ) {
1883 if ( $in_same_term ) {
1884 $join .= " INNER JOIN $wpdb->term_relationships AS tr ON p.ID = tr.object_id INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
1885 $where .= $wpdb->prepare( 'AND tt.taxonomy = %s', $taxonomy );
1886
1887 if ( ! is_object_in_taxonomy( $post->post_type, $taxonomy ) ) {
1888 return '';
1889 }
1890 $term_array = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'ids' ) );
1891 if ( is_wp_error( $term_array ) ) {
1892 return '';
1893 }
1894
1895 // Remove any exclusions from the term array to include.
1896 $term_array = array_diff( $term_array, (array) $excluded_terms );
1897
1898 if ( ! $term_array ) {
1899 return '';
1900 }
1901
1902 $term_array = array_map( 'intval', $term_array );
1903
1904 $where .= ' AND tt.term_id IN (' . implode( ',', $term_array ) . ')';
1905 }
1906
1907 if ( ! empty( $excluded_terms ) ) {
1908 $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 ) ) . ') )';
1909 }
1910 }
1911
1912 // 'post_status' clause depends on the current user.
1913 if ( is_user_logged_in() ) {
1914 $user_id = get_current_user_id();
1915
1916 $post_type_object = get_post_type_object( $post->post_type );
1917 if ( empty( $post_type_object ) ) {
1918 $post_type_cap = $post->post_type;
1919 $read_private_cap = 'read_private_' . $post_type_cap . 's';
1920 } else {
1921 $read_private_cap = $post_type_object->cap->read_private_posts;
1922 }
1923
1924 /*
1925 * Results should include private posts belonging to the current user, or private posts where the
1926 * current user has the 'read_private_posts' cap.
1927 */
1928 $private_states = get_post_stati( array( 'private' => true ) );
1929 $where .= " AND ( p.post_status = 'publish'";
1930 foreach ( $private_states as $state ) {
1931 if ( current_user_can( $read_private_cap ) ) {
1932 $where .= $wpdb->prepare( ' OR p.post_status = %s', $state );
1933 } else {
1934 $where .= $wpdb->prepare( ' OR (p.post_author = %d AND p.post_status = %s)', $user_id, $state );
1935 }
1936 }
1937 $where .= ' )';
1938 } else {
1939 $where .= " AND p.post_status = 'publish'";
1940 }
1941
1942 $comparison_operator = $previous ? '<' : '>';
1943 $order = $previous ? 'DESC' : 'ASC';
1944
1945 /**
1946 * Filters the JOIN clause in the SQL for an adjacent post query.
1947 *
1948 * The dynamic portion of the hook name, `$adjacent`, refers to the type
1949 * of adjacency, 'next' or 'previous'.
1950 *
1951 * Possible hook names include:
1952 *
1953 * - `get_next_post_join`
1954 * - `get_previous_post_join`
1955 *
1956 * @since 2.5.0
1957 * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
1958 *
1959 * @param string $join The JOIN clause in the SQL.
1960 * @param bool $in_same_term Whether post should be in the same taxonomy term.
1961 * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
1962 * @param string $taxonomy Taxonomy. Used to identify the term used when `$in_same_term` is true.
1963 * @param WP_Post $post WP_Post object.
1964 */
1965 $join = apply_filters( "get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post );
1966
1967 // Prepare the where clause for the adjacent post query.
1968 $where_prepared = $wpdb->prepare( "WHERE (p.post_date $comparison_operator %s OR (p.post_date = %s AND p.ID $comparison_operator %d)) AND p.post_type = %s $where", $current_post_date, $current_post_date, $post->ID, $post->post_type ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $comparison_operator is a string literal, either '<' or '>'.
1969
1970 /**
1971 * Filters the WHERE clause in the SQL for an adjacent post query.
1972 *
1973 * The dynamic portion of the hook name, `$adjacent`, refers to the type
1974 * of adjacency, 'next' or 'previous'.
1975 *
1976 * Possible hook names include:
1977 *
1978 * - `get_next_post_where`
1979 * - `get_previous_post_where`
1980 *
1981 * @since 2.5.0
1982 * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
1983 * @since 6.9.0 Adds ID-based fallback for posts with identical dates in adjacent post queries.
1984 *
1985 * @param string $where The `WHERE` clause in the SQL.
1986 * @param bool $in_same_term Whether post should be in the same taxonomy term.
1987 * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
1988 * @param string $taxonomy Taxonomy. Used to identify the term used when `$in_same_term` is true.
1989 * @param WP_Post $post WP_Post object.
1990 */
1991 $where = apply_filters( "get_{$adjacent}_post_where", $where_prepared, $in_same_term, $excluded_terms, $taxonomy, $post );
1992
1993 /**
1994 * Filters the ORDER BY clause in the SQL for an adjacent post query.
1995 *
1996 * The dynamic portion of the hook name, `$adjacent`, refers to the type
1997 * of adjacency, 'next' or 'previous'.
1998 *
1999 * Possible hook names include:
2000 *
2001 * - `get_next_post_sort`
2002 * - `get_previous_post_sort`
2003 *
2004 * @since 2.5.0
2005 * @since 4.4.0 Added the `$post` parameter.
2006 * @since 4.9.0 Added the `$order` parameter.
2007 * @since 6.9.0 Adds ID sort to ensure deterministic ordering for posts with identical dates.
2008 *
2009 * @param string $order_by The `ORDER BY` clause in the SQL.
2010 * @param WP_Post $post WP_Post object.
2011 * @param string $order Sort order. 'DESC' for previous post, 'ASC' for next.
2012 */
2013 $sort = apply_filters( "get_{$adjacent}_post_sort", "ORDER BY p.post_date $order, p.ID $order LIMIT 1", $post, $order );
2014
2015 $query = "SELECT p.ID FROM $wpdb->posts AS p $join $where $sort";
2016 $key = md5( $query );
2017 $last_changed = (array) wp_cache_get_last_changed( 'posts' );
2018 if ( $in_same_term || ! empty( $excluded_terms ) ) {
2019 $last_changed[] = wp_cache_get_last_changed( 'terms' );
2020 }
2021 $cache_key = "adjacent_post:$key";
2022
2023 $result = wp_cache_get_salted( $cache_key, 'post-queries', $last_changed );
2024 if ( false !== $result ) {
2025 if ( $result ) {
2026 $result = get_post( $result );
2027 }
2028 return $result;
2029 }
2030
2031 $result = $wpdb->get_var( $query );
2032 if ( null === $result ) {
2033 $result = '';
2034 }
2035
2036 wp_cache_set_salted( $cache_key, $result, 'post-queries', $last_changed );
2037
2038 if ( $result ) {
2039 $result = get_post( $result );
2040 }
2041
2042 return $result;
2043}
2044
2045/**
2046 * Retrieves the adjacent post relational link.
2047 *
2048 * Can either be next or previous post relational link.
2049 *
2050 * @since 2.8.0
2051 *
2052 * @param string $title Optional. Link title format. Default '%title'.
2053 * @param bool $in_same_term Optional. Whether link should be in the same taxonomy term.
2054 * Default false.
2055 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
2056 * Default empty.
2057 * @param bool $previous Optional. Whether to display link to previous or next post.
2058 * Default true.
2059 * @param string $taxonomy Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
2060 * @return string|void The adjacent post relational link URL.
2061 */
2062function get_adjacent_post_rel_link( $title = '%title', $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) {
2063 $post = get_post();
2064 if ( $previous && is_attachment() && $post ) {
2065 $post = get_post( $post->post_parent );
2066 } else {
2067 $post = get_adjacent_post( $in_same_term, $excluded_terms, $previous, $taxonomy );
2068 }
2069
2070 if ( empty( $post ) ) {
2071 return;
2072 }
2073
2074 $post_title = the_title_attribute(
2075 array(
2076 'echo' => false,
2077 'post' => $post,
2078 )
2079 );
2080
2081 if ( empty( $post_title ) ) {
2082 $post_title = $previous ? __( 'Previous Post' ) : __( 'Next Post' );
2083 }
2084
2085 $date = mysql2date( get_option( 'date_format' ), $post->post_date );
2086
2087 $title = str_replace( '%title', $post_title, $title );
2088 $title = str_replace( '%date', $date, $title );
2089
2090 $link = $previous ? "<link rel='prev' title='" : "<link rel='next' title='";
2091 $link .= esc_attr( $title );
2092 $link .= "' href='" . get_permalink( $post ) . "' />\n";
2093
2094 $adjacent = $previous ? 'previous' : 'next';
2095
2096 /**
2097 * Filters the adjacent post relational link.
2098 *
2099 * The dynamic portion of the hook name, `$adjacent`, refers to the type
2100 * of adjacency, 'next' or 'previous'.
2101 *
2102 * Possible hook names include:
2103 *
2104 * - `next_post_rel_link`
2105 * - `previous_post_rel_link`
2106 *
2107 * @since 2.8.0
2108 *
2109 * @param string $link The relational link.
2110 */
2111 return apply_filters( "{$adjacent}_post_rel_link", $link );
2112}
2113
2114/**
2115 * Displays the relational links for the posts adjacent to the current post.
2116 *
2117 * @since 2.8.0
2118 *
2119 * @param string $title Optional. Link title format. Default '%title'.
2120 * @param bool $in_same_term Optional. Whether link should be in the same taxonomy term.
2121 * Default false.
2122 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
2123 * Default empty.
2124 * @param string $taxonomy Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
2125 */
2126function adjacent_posts_rel_link( $title = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
2127 echo get_adjacent_post_rel_link( $title, $in_same_term, $excluded_terms, true, $taxonomy );
2128 echo get_adjacent_post_rel_link( $title, $in_same_term, $excluded_terms, false, $taxonomy );
2129}
2130
2131/**
2132 * Displays relational links for the posts adjacent to the current post for single post pages.
2133 *
2134 * This is meant to be attached to actions like 'wp_head'. Do not call this directly in plugins
2135 * or theme templates.
2136 *
2137 * @since 3.0.0
2138 * @since 5.6.0 No longer used in core.
2139 *
2140 * @see adjacent_posts_rel_link()
2141 */
2142function adjacent_posts_rel_link_wp_head() {
2143 if ( ! is_single() || is_attachment() ) {
2144 return;
2145 }
2146 adjacent_posts_rel_link();
2147}
2148
2149/**
2150 * Displays the relational link for the next post adjacent to the current post.
2151 *
2152 * @since 2.8.0
2153 *
2154 * @see get_adjacent_post_rel_link()
2155 *
2156 * @param string $title Optional. Link title format. Default '%title'.
2157 * @param bool $in_same_term Optional. Whether link should be in the same taxonomy term.
2158 * Default false.
2159 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
2160 * Default empty.
2161 * @param string $taxonomy Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
2162 */
2163function next_post_rel_link( $title = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
2164 echo get_adjacent_post_rel_link( $title, $in_same_term, $excluded_terms, false, $taxonomy );
2165}
2166
2167/**
2168 * Displays the relational link for the previous post adjacent to the current post.
2169 *
2170 * @since 2.8.0
2171 *
2172 * @see get_adjacent_post_rel_link()
2173 *
2174 * @param string $title Optional. Link title format. Default '%title'.
2175 * @param bool $in_same_term Optional. Whether link should be in the same taxonomy term.
2176 * Default false.
2177 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
2178 * Default true.
2179 * @param string $taxonomy Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
2180 */
2181function prev_post_rel_link( $title = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
2182 echo get_adjacent_post_rel_link( $title, $in_same_term, $excluded_terms, true, $taxonomy );
2183}
2184
2185/**
2186 * Retrieves the boundary post.
2187 *
2188 * Boundary being either the first or last post by publish date within the constraints specified
2189 * by `$in_same_term` or `$excluded_terms`.
2190 *
2191 * @since 2.8.0
2192 *
2193 * @param bool $in_same_term Optional. Whether returned post should be in the same taxonomy term.
2194 * Default false.
2195 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
2196 * Default empty.
2197 * @param bool $start Optional. Whether to retrieve first or last post.
2198 * Default true.
2199 * @param string $taxonomy Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
2200 * @return array|null Array containing the boundary post object if successful, null otherwise.
2201 */
2202function get_boundary_post( $in_same_term = false, $excluded_terms = '', $start = true, $taxonomy = 'category' ) {
2203 $post = get_post();
2204
2205 if ( ! $post || ! is_single() || is_attachment() || ! taxonomy_exists( $taxonomy ) ) {
2206 return null;
2207 }
2208
2209 $query_args = array(
2210 'posts_per_page' => 1,
2211 'order' => $start ? 'ASC' : 'DESC',
2212 'update_post_term_cache' => false,
2213 'update_post_meta_cache' => false,
2214 );
2215
2216 $term_array = array();
2217
2218 if ( ! is_array( $excluded_terms ) ) {
2219 if ( ! empty( $excluded_terms ) ) {
2220 $excluded_terms = explode( ',', $excluded_terms );
2221 } else {
2222 $excluded_terms = array();
2223 }
2224 }
2225
2226 if ( $in_same_term || ! empty( $excluded_terms ) ) {
2227 if ( $in_same_term ) {
2228 $term_array = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'ids' ) );
2229 }
2230
2231 if ( ! empty( $excluded_terms ) ) {
2232 $excluded_terms = array_map( 'intval', $excluded_terms );
2233 $excluded_terms = array_diff( $excluded_terms, $term_array );
2234
2235 $inverse_terms = array();
2236 foreach ( $excluded_terms as $excluded_term ) {
2237 $inverse_terms[] = $excluded_term * -1;
2238 }
2239 $excluded_terms = $inverse_terms;
2240 }
2241
2242 $query_args['tax_query'] = array(
2243 array(
2244 'taxonomy' => $taxonomy,
2245 'terms' => array_merge( $term_array, $excluded_terms ),
2246 ),
2247 );
2248 }
2249
2250 return get_posts( $query_args );
2251}
2252
2253/**
2254 * Retrieves the previous post link that is adjacent to the current post.
2255 *
2256 * @since 3.7.0
2257 *
2258 * @param string $format Optional. Link anchor format. Default '&laquo; %link'.
2259 * @param string $link Optional. Link permalink format. Default '%title'.
2260 * @param bool $in_same_term Optional. Whether link should be in the same taxonomy term.
2261 * Default false.
2262 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
2263 * Default empty.
2264 * @param string $taxonomy Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
2265 * @return string The link URL of the previous post in relation to the current post.
2266 */
2267function get_previous_post_link( $format = '&laquo; %link', $link = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
2268 return get_adjacent_post_link( $format, $link, $in_same_term, $excluded_terms, true, $taxonomy );
2269}
2270
2271/**
2272 * Displays the previous post link that is adjacent to the current post.
2273 *
2274 * @since 1.5.0
2275 *
2276 * @see get_previous_post_link()
2277 *
2278 * @param string $format Optional. Link anchor format. Default '&laquo; %link'.
2279 * @param string $link Optional. Link permalink format. Default '%title'.
2280 * @param bool $in_same_term Optional. Whether link should be in the same taxonomy term.
2281 * Default false.
2282 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
2283 * Default empty.
2284 * @param string $taxonomy Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
2285 */
2286function previous_post_link( $format = '&laquo; %link', $link = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
2287 echo get_previous_post_link( $format, $link, $in_same_term, $excluded_terms, $taxonomy );
2288}
2289
2290/**
2291 * Retrieves the next post link that is adjacent to the current post.
2292 *
2293 * @since 3.7.0
2294 *
2295 * @param string $format Optional. Link anchor format. Default '&laquo; %link'.
2296 * @param string $link Optional. Link permalink format. Default '%title'.
2297 * @param bool $in_same_term Optional. Whether link should be in the same taxonomy term.
2298 * Default false.
2299 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
2300 * Default empty.
2301 * @param string $taxonomy Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
2302 * @return string The link URL of the next post in relation to the current post.
2303 */
2304function get_next_post_link( $format = '%link &raquo;', $link = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
2305 return get_adjacent_post_link( $format, $link, $in_same_term, $excluded_terms, false, $taxonomy );
2306}
2307
2308/**
2309 * Displays the next post link that is adjacent to the current post.
2310 *
2311 * @since 1.5.0
2312 *
2313 * @see get_next_post_link()
2314 *
2315 * @param string $format Optional. Link anchor format. Default '&laquo; %link'.
2316 * @param string $link Optional. Link permalink format. Default '%title'.
2317 * @param bool $in_same_term Optional. Whether link should be in the same taxonomy term.
2318 * Default false.
2319 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
2320 * Default empty.
2321 * @param string $taxonomy Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
2322 */
2323function next_post_link( $format = '%link &raquo;', $link = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
2324 echo get_next_post_link( $format, $link, $in_same_term, $excluded_terms, $taxonomy );
2325}
2326
2327/**
2328 * Retrieves the adjacent post link.
2329 *
2330 * Can be either next post link or previous.
2331 *
2332 * @since 3.7.0
2333 *
2334 * @param string $format Link anchor format.
2335 * @param string $link Link permalink format.
2336 * @param bool $in_same_term Optional. Whether link should be in the same taxonomy term.
2337 * Default false.
2338 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded terms IDs.
2339 * Default empty.
2340 * @param bool $previous Optional. Whether to display link to previous or next post.
2341 * Default true.
2342 * @param string $taxonomy Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
2343 * @return string The link URL of the previous or next post in relation to the current post.
2344 */
2345function get_adjacent_post_link( $format, $link, $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) {
2346 if ( $previous && is_attachment() ) {
2347 $post = get_post( get_post()->post_parent );
2348 } else {
2349 $post = get_adjacent_post( $in_same_term, $excluded_terms, $previous, $taxonomy );
2350 }
2351
2352 if ( ! $post ) {
2353 $output = '';
2354 } else {
2355 $title = $post->post_title;
2356
2357 if ( empty( $post->post_title ) ) {
2358 $title = $previous ? __( 'Previous Post' ) : __( 'Next Post' );
2359 }
2360
2361 /** This filter is documented in wp-includes/post-template.php */
2362 $title = apply_filters( 'the_title', $title, $post->ID );
2363
2364 $date = mysql2date( get_option( 'date_format' ), $post->post_date );
2365 $rel = $previous ? 'prev' : 'next';
2366
2367 $string = '<a href="' . get_permalink( $post ) . '" rel="' . $rel . '">';
2368 $inlink = str_replace( '%title', $title, $link );
2369 $inlink = str_replace( '%date', $date, $inlink );
2370 $inlink = $string . $inlink . '</a>';
2371
2372 $output = str_replace( '%link', $inlink, $format );
2373 }
2374
2375 $adjacent = $previous ? 'previous' : 'next';
2376
2377 /**
2378 * Filters the adjacent post link.
2379 *
2380 * The dynamic portion of the hook name, `$adjacent`, refers to the type
2381 * of adjacency, 'next' or 'previous'.
2382 *
2383 * Possible hook names include:
2384 *
2385 * - `next_post_link`
2386 * - `previous_post_link`
2387 *
2388 * @since 2.6.0
2389 * @since 4.2.0 Added the `$adjacent` parameter.
2390 *
2391 * @param string $output The adjacent post link.
2392 * @param string $format Link anchor format.
2393 * @param string $link Link permalink format.
2394 * @param WP_Post|string $post The adjacent post. Empty string if no corresponding post exists.
2395 * @param string $adjacent Whether the post is previous or next.
2396 */
2397 return apply_filters( "{$adjacent}_post_link", $output, $format, $link, $post, $adjacent );
2398}
2399
2400/**
2401 * Displays the adjacent post link.
2402 *
2403 * Can be either next post link or previous.
2404 *
2405 * @since 2.5.0
2406 *
2407 * @param string $format Link anchor format.
2408 * @param string $link Link permalink format.
2409 * @param bool $in_same_term Optional. Whether link should be in the same taxonomy term.
2410 * Default false.
2411 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded category IDs.
2412 * Default empty.
2413 * @param bool $previous Optional. Whether to display link to previous or next post.
2414 * Default true.
2415 * @param string $taxonomy Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
2416 */
2417function adjacent_post_link( $format, $link, $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) {
2418 echo get_adjacent_post_link( $format, $link, $in_same_term, $excluded_terms, $previous, $taxonomy );
2419}
2420
2421/**
2422 * Retrieves the link for a page number.
2423 *
2424 * @since 1.5.0
2425 *
2426 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
2427 *
2428 * @param int $pagenum Optional. Page number. Default 1.
2429 * @param bool $escape Optional. Whether to escape the URL for display, with esc_url().
2430 * If set to false, prepares the URL with sanitize_url(). Default true.
2431 * @return string The link URL for the given page number.
2432 */
2433function get_pagenum_link( $pagenum = 1, $escape = true ) {
2434 global $wp_rewrite;
2435
2436 $pagenum = (int) $pagenum;
2437
2438 $request = remove_query_arg( 'paged' );
2439
2440 $home_root = parse_url( home_url() );
2441 $home_root = ( isset( $home_root['path'] ) ) ? $home_root['path'] : '';
2442 $home_root = preg_quote( $home_root, '|' );
2443
2444 $request = preg_replace( '|^' . $home_root . '|i', '', $request );
2445 $request = preg_replace( '|^/+|', '', $request );
2446
2447 if ( ! $wp_rewrite->using_permalinks() || is_admin() ) {
2448 $base = trailingslashit( get_bloginfo( 'url' ) );
2449
2450 if ( $pagenum > 1 ) {
2451 $result = add_query_arg( 'paged', $pagenum, $base . $request );
2452 } else {
2453 $result = $base . $request;
2454 }
2455 } else {
2456 $qs_regex = '|\?.*?$|';
2457 preg_match( $qs_regex, $request, $qs_match );
2458
2459 $parts = array();
2460 $parts[] = untrailingslashit( get_bloginfo( 'url' ) );
2461
2462 if ( ! empty( $qs_match[0] ) ) {
2463 $query_string = $qs_match[0];
2464 $request = preg_replace( $qs_regex, '', $request );
2465 } else {
2466 $query_string = '';
2467 }
2468
2469 $request = preg_replace( "|$wp_rewrite->pagination_base/\d+/?$|", '', $request );
2470 $request = preg_replace( '|^' . preg_quote( $wp_rewrite->index, '|' ) . '|i', '', $request );
2471 $request = ltrim( $request, '/' );
2472
2473 if ( $wp_rewrite->using_index_permalinks() && ( $pagenum > 1 || '' !== $request ) ) {
2474 $parts[] = $wp_rewrite->index;
2475 }
2476
2477 $parts[] = untrailingslashit( $request );
2478
2479 if ( $pagenum > 1 ) {
2480 $parts[] = $wp_rewrite->pagination_base;
2481 $parts[] = $pagenum;
2482 }
2483
2484 $result = user_trailingslashit( implode( '/', array_filter( $parts ) ), 'paged' );
2485 if ( ! empty( $query_string ) ) {
2486 $result .= $query_string;
2487 }
2488 }
2489
2490 /**
2491 * Filters the page number link for the current request.
2492 *
2493 * @since 2.5.0
2494 * @since 5.2.0 Added the `$pagenum` argument.
2495 *
2496 * @param string $result The page number link.
2497 * @param int $pagenum The page number.
2498 */
2499 $result = apply_filters( 'get_pagenum_link', $result, $pagenum );
2500
2501 if ( $escape ) {
2502 return esc_url( $result );
2503 } else {
2504 return sanitize_url( $result );
2505 }
2506}
2507
2508/**
2509 * Retrieves the next posts page link.
2510 *
2511 * Backported from 2.1.3 to 2.0.10.
2512 *
2513 * @since 2.0.10
2514 *
2515 * @global int $paged
2516 *
2517 * @param int $max_page Optional. Max pages. Default 0.
2518 * @return string|void The link URL for next posts page.
2519 */
2520function get_next_posts_page_link( $max_page = 0 ) {
2521 global $paged;
2522
2523 if ( ! is_single() ) {
2524 if ( ! $paged ) {
2525 $paged = 1;
2526 }
2527
2528 $next_page = (int) $paged + 1;
2529
2530 if ( ! $max_page || $max_page >= $next_page ) {
2531 return get_pagenum_link( $next_page );
2532 }
2533 }
2534}
2535
2536/**
2537 * Displays or retrieves the next posts page link.
2538 *
2539 * @since 0.71
2540 *
2541 * @param int $max_page Optional. Max pages. Default 0.
2542 * @param bool $display Optional. Whether to echo the link. Default true.
2543 * @return string|void The link URL for next posts page if `$display = false`.
2544 */
2545function next_posts( $max_page = 0, $display = true ) {
2546 $link = get_next_posts_page_link( $max_page );
2547 $output = $link ? esc_url( $link ) : '';
2548
2549 if ( $display ) {
2550 echo $output;
2551 } else {
2552 return $output;
2553 }
2554}
2555
2556/**
2557 * Retrieves the next posts page link.
2558 *
2559 * @since 2.7.0
2560 *
2561 * @global int $paged
2562 * @global WP_Query $wp_query WordPress Query object.
2563 *
2564 * @param string $label Content for link text.
2565 * @param int $max_page Optional. Max pages. Default 0.
2566 * @return string|void HTML-formatted next posts page link.
2567 */
2568function get_next_posts_link( $label = null, $max_page = 0 ) {
2569 global $paged, $wp_query;
2570
2571 if ( ! $max_page ) {
2572 $max_page = $wp_query->max_num_pages;
2573 }
2574
2575 if ( ! $paged ) {
2576 $paged = 1;
2577 }
2578
2579 $next_page = (int) $paged + 1;
2580
2581 if ( null === $label ) {
2582 $label = __( 'Next Page &raquo;' );
2583 }
2584
2585 if ( ! is_single() && ( $next_page <= $max_page ) ) {
2586 /**
2587 * Filters the anchor tag attributes for the next posts page link.
2588 *
2589 * @since 2.7.0
2590 *
2591 * @param string $attributes Attributes for the anchor tag.
2592 */
2593 $attr = apply_filters( 'next_posts_link_attributes', '' );
2594
2595 return sprintf(
2596 '<a href="%1$s" %2$s>%3$s</a>',
2597 next_posts( $max_page, false ),
2598 $attr,
2599 preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $label )
2600 );
2601 }
2602}
2603
2604/**
2605 * Displays the next posts page link.
2606 *
2607 * @since 0.71
2608 *
2609 * @param string $label Content for link text.
2610 * @param int $max_page Optional. Max pages. Default 0.
2611 */
2612function next_posts_link( $label = null, $max_page = 0 ) {
2613 echo get_next_posts_link( $label, $max_page );
2614}
2615
2616/**
2617 * Retrieves the previous posts page link.
2618 *
2619 * Will only return string, if not on a single page or post.
2620 *
2621 * Backported to 2.0.10 from 2.1.3.
2622 *
2623 * @since 2.0.10
2624 *
2625 * @global int $paged
2626 *
2627 * @return string|void The link for the previous posts page.
2628 */
2629function get_previous_posts_page_link() {
2630 global $paged;
2631
2632 if ( ! is_single() ) {
2633 $previous_page = (int) $paged - 1;
2634
2635 if ( $previous_page < 1 ) {
2636 $previous_page = 1;
2637 }
2638
2639 return get_pagenum_link( $previous_page );
2640 }
2641}
2642
2643/**
2644 * Displays or retrieves the previous posts page link.
2645 *
2646 * @since 0.71
2647 *
2648 * @param bool $display Optional. Whether to echo the link. Default true.
2649 * @return string|void The previous posts page link if `$display = false`.
2650 */
2651function previous_posts( $display = true ) {
2652 $output = esc_url( get_previous_posts_page_link() );
2653
2654 if ( $display ) {
2655 echo $output;
2656 } else {
2657 return $output;
2658 }
2659}
2660
2661/**
2662 * Retrieves the previous posts page link.
2663 *
2664 * @since 2.7.0
2665 *
2666 * @global int $paged
2667 *
2668 * @param string $label Optional. Previous page link text.
2669 * @return string|void HTML-formatted previous page link.
2670 */
2671function get_previous_posts_link( $label = null ) {
2672 global $paged;
2673
2674 if ( null === $label ) {
2675 $label = __( '&laquo; Previous Page' );
2676 }
2677
2678 if ( ! is_single() && $paged > 1 ) {
2679 /**
2680 * Filters the anchor tag attributes for the previous posts page link.
2681 *
2682 * @since 2.7.0
2683 *
2684 * @param string $attributes Attributes for the anchor tag.
2685 */
2686 $attr = apply_filters( 'previous_posts_link_attributes', '' );
2687
2688 return sprintf(
2689 '<a href="%1$s" %2$s>%3$s</a>',
2690 previous_posts( false ),
2691 $attr,
2692 preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $label )
2693 );
2694 }
2695}
2696
2697/**
2698 * Displays the previous posts page link.
2699 *
2700 * @since 0.71
2701 *
2702 * @param string $label Optional. Previous page link text.
2703 */
2704function previous_posts_link( $label = null ) {
2705 echo get_previous_posts_link( $label );
2706}
2707
2708/**
2709 * Retrieves the post pages link navigation for previous and next pages.
2710 *
2711 * @since 2.8.0
2712 *
2713 * @global WP_Query $wp_query WordPress Query object.
2714 *
2715 * @param string|array $args {
2716 * Optional. Arguments to build the post pages link navigation.
2717 *
2718 * @type string $sep Separator character. Default '&#8212;'.
2719 * @type string $prelabel Link text to display for the previous page link.
2720 * Default '&laquo; Previous Page'.
2721 * @type string $nxtlabel Link text to display for the next page link.
2722 * Default 'Next Page &raquo;'.
2723 * }
2724 * @return string The posts link navigation.
2725 */
2726function get_posts_nav_link( $args = array() ) {
2727 global $wp_query;
2728
2729 $return = '';
2730
2731 if ( ! is_singular() ) {
2732 $defaults = array(
2733 'sep' => ' &#8212; ',
2734 'prelabel' => __( '&laquo; Previous Page' ),
2735 'nxtlabel' => __( 'Next Page &raquo;' ),
2736 );
2737 $args = wp_parse_args( $args, $defaults );
2738
2739 $max_num_pages = $wp_query->max_num_pages;
2740 $paged = get_query_var( 'paged' );
2741
2742 // Only have sep if there's both prev and next results.
2743 if ( $paged < 2 || $paged >= $max_num_pages ) {
2744 $args['sep'] = '';
2745 }
2746
2747 if ( $max_num_pages > 1 ) {
2748 $return = get_previous_posts_link( $args['prelabel'] );
2749 $return .= preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $args['sep'] );
2750 $return .= get_next_posts_link( $args['nxtlabel'] );
2751 }
2752 }
2753 return $return;
2754}
2755
2756/**
2757 * Displays the post pages link navigation for previous and next pages.
2758 *
2759 * @since 0.71
2760 *
2761 * @param string $sep Optional. Separator for posts navigation links. Default empty.
2762 * @param string $prelabel Optional. Label for previous pages. Default empty.
2763 * @param string $nxtlabel Optional Label for next pages. Default empty.
2764 */
2765function posts_nav_link( $sep = '', $prelabel = '', $nxtlabel = '' ) {
2766 $args = array_filter( compact( 'sep', 'prelabel', 'nxtlabel' ) );
2767 echo get_posts_nav_link( $args );
2768}
2769
2770/**
2771 * Retrieves the navigation to next/previous post, when applicable.
2772 *
2773 * @since 4.1.0
2774 * @since 4.4.0 Introduced the `in_same_term`, `excluded_terms`, and `taxonomy` arguments.
2775 * @since 5.3.0 Added the `aria_label` parameter.
2776 * @since 5.5.0 Added the `class` parameter.
2777 *
2778 * @param array $args {
2779 * Optional. Default post navigation arguments. Default empty array.
2780 *
2781 * @type string $prev_text Anchor text to display in the previous post link.
2782 * Default '%title'.
2783 * @type string $next_text Anchor text to display in the next post link.
2784 * Default '%title'.
2785 * @type bool $in_same_term Whether link should be in the same taxonomy term.
2786 * Default false.
2787 * @type int[]|string $excluded_terms Array or comma-separated list of excluded term IDs.
2788 * Default empty.
2789 * @type string $taxonomy Taxonomy, if `$in_same_term` is true. Default 'category'.
2790 * @type string $screen_reader_text Screen reader text for the nav element.
2791 * Default 'Post navigation'.
2792 * @type string $aria_label ARIA label text for the nav element. Default 'Posts'.
2793 * @type string $class Custom class for the nav element. Default 'post-navigation'.
2794 * }
2795 * @return string Markup for post links.
2796 */
2797function get_the_post_navigation( $args = array() ) {
2798 // Make sure the nav element has an aria-label attribute: fallback to the screen reader text.
2799 if ( ! empty( $args['screen_reader_text'] ) && empty( $args['aria_label'] ) ) {
2800 $args['aria_label'] = $args['screen_reader_text'];
2801 }
2802
2803 $args = wp_parse_args(
2804 $args,
2805 array(
2806 'prev_text' => '%title',
2807 'next_text' => '%title',
2808 'in_same_term' => false,
2809 'excluded_terms' => '',
2810 'taxonomy' => 'category',
2811 'screen_reader_text' => __( 'Post navigation' ),
2812 'aria_label' => __( 'Posts' ),
2813 'class' => 'post-navigation',
2814 )
2815 );
2816
2817 $navigation = '';
2818
2819 $previous = get_previous_post_link(
2820 '<div class="nav-previous">%link</div>',
2821 $args['prev_text'],
2822 $args['in_same_term'],
2823 $args['excluded_terms'],
2824 $args['taxonomy']
2825 );
2826
2827 $next = get_next_post_link(
2828 '<div class="nav-next">%link</div>',
2829 $args['next_text'],
2830 $args['in_same_term'],
2831 $args['excluded_terms'],
2832 $args['taxonomy']
2833 );
2834
2835 // Only add markup if there's somewhere to navigate to.
2836 if ( $previous || $next ) {
2837 $navigation = _navigation_markup( $previous . $next, $args['class'], $args['screen_reader_text'], $args['aria_label'] );
2838 }
2839
2840 return $navigation;
2841}
2842
2843/**
2844 * Displays the navigation to next/previous post, when applicable.
2845 *
2846 * @since 4.1.0
2847 *
2848 * @param array $args Optional. See get_the_post_navigation() for available arguments.
2849 * Default empty array.
2850 */
2851function the_post_navigation( $args = array() ) {
2852 echo get_the_post_navigation( $args );
2853}
2854
2855/**
2856 * Returns the navigation to next/previous set of posts, when applicable.
2857 *
2858 * @since 4.1.0
2859 * @since 5.3.0 Added the `aria_label` parameter.
2860 * @since 5.5.0 Added the `class` parameter.
2861 *
2862 * @global WP_Query $wp_query WordPress Query object.
2863 *
2864 * @param array $args {
2865 * Optional. Default posts navigation arguments. Default empty array.
2866 *
2867 * @type string $prev_text Anchor text to display in the previous posts link.
2868 * Default 'Older posts'.
2869 * @type string $next_text Anchor text to display in the next posts link.
2870 * Default 'Newer posts'.
2871 * @type string $screen_reader_text Screen reader text for the nav element.
2872 * Default 'Posts navigation'.
2873 * @type string $aria_label ARIA label text for the nav element. Default 'Posts'.
2874 * @type string $class Custom class for the nav element. Default 'posts-navigation'.
2875 * }
2876 * @return string Markup for posts links.
2877 */
2878function get_the_posts_navigation( $args = array() ) {
2879 global $wp_query;
2880
2881 $navigation = '';
2882
2883 // Don't print empty markup if there's only one page.
2884 if ( $wp_query->max_num_pages > 1 ) {
2885 // Make sure the nav element has an aria-label attribute: fallback to the screen reader text.
2886 if ( ! empty( $args['screen_reader_text'] ) && empty( $args['aria_label'] ) ) {
2887 $args['aria_label'] = $args['screen_reader_text'];
2888 }
2889
2890 $args = wp_parse_args(
2891 $args,
2892 array(
2893 'prev_text' => __( 'Older posts' ),
2894 'next_text' => __( 'Newer posts' ),
2895 'screen_reader_text' => __( 'Posts navigation' ),
2896 'aria_label' => __( 'Posts' ),
2897 'class' => 'posts-navigation',
2898 )
2899 );
2900
2901 $next_link = get_previous_posts_link( $args['next_text'] );
2902 $prev_link = get_next_posts_link( $args['prev_text'] );
2903
2904 if ( $prev_link ) {
2905 $navigation .= '<div class="nav-previous">' . $prev_link . '</div>';
2906 }
2907
2908 if ( $next_link ) {
2909 $navigation .= '<div class="nav-next">' . $next_link . '</div>';
2910 }
2911
2912 $navigation = _navigation_markup( $navigation, $args['class'], $args['screen_reader_text'], $args['aria_label'] );
2913 }
2914
2915 return $navigation;
2916}
2917
2918/**
2919 * Displays the navigation to next/previous set of posts, when applicable.
2920 *
2921 * @since 4.1.0
2922 *
2923 * @param array $args Optional. See get_the_posts_navigation() for available arguments.
2924 * Default empty array.
2925 */
2926function the_posts_navigation( $args = array() ) {
2927 echo get_the_posts_navigation( $args );
2928}
2929
2930/**
2931 * Retrieves a paginated navigation to next/previous set of posts, when applicable.
2932 *
2933 * @since 4.1.0
2934 * @since 5.3.0 Added the `aria_label` parameter.
2935 * @since 5.5.0 Added the `class` parameter.
2936 *
2937 * @global WP_Query $wp_query WordPress Query object.
2938 *
2939 * @param array $args {
2940 * Optional. Default pagination arguments, see paginate_links().
2941 *
2942 * @type string $screen_reader_text Screen reader text for navigation element.
2943 * Default 'Posts pagination'.
2944 * @type string $aria_label ARIA label text for the nav element. Default 'Posts pagination'.
2945 * @type string $class Custom class for the nav element. Default 'pagination'.
2946 * }
2947 * @return string Markup for pagination links.
2948 */
2949function get_the_posts_pagination( $args = array() ) {
2950 global $wp_query;
2951
2952 $navigation = '';
2953
2954 // Don't print empty markup if there's only one page.
2955 if ( $wp_query->max_num_pages > 1 ) {
2956 // Make sure the nav element has an aria-label attribute: fallback to the screen reader text.
2957 if ( ! empty( $args['screen_reader_text'] ) && empty( $args['aria_label'] ) ) {
2958 $args['aria_label'] = $args['screen_reader_text'];
2959 }
2960
2961 $args = wp_parse_args(
2962 $args,
2963 array(
2964 'mid_size' => 1,
2965 'prev_text' => _x( 'Previous', 'previous set of posts' ),
2966 'next_text' => _x( 'Next', 'next set of posts' ),
2967 'screen_reader_text' => __( 'Posts pagination' ),
2968 'aria_label' => __( 'Posts pagination' ),
2969 'class' => 'pagination',
2970 )
2971 );
2972
2973 /**
2974 * Filters the arguments for posts pagination links.
2975 *
2976 * @since 6.1.0
2977 *
2978 * @param array $args {
2979 * Optional. Default pagination arguments, see paginate_links().
2980 *
2981 * @type string $screen_reader_text Screen reader text for navigation element.
2982 * Default 'Posts navigation'.
2983 * @type string $aria_label ARIA label text for the nav element. Default 'Posts'.
2984 * @type string $class Custom class for the nav element. Default 'pagination'.
2985 * }
2986 */
2987 $args = apply_filters( 'the_posts_pagination_args', $args );
2988
2989 // Make sure we get a string back. Plain is the next best thing.
2990 if ( isset( $args['type'] ) && 'array' === $args['type'] ) {
2991 $args['type'] = 'plain';
2992 }
2993
2994 // Set up paginated links.
2995 $links = paginate_links( $args );
2996
2997 if ( $links ) {
2998 $navigation = _navigation_markup( $links, $args['class'], $args['screen_reader_text'], $args['aria_label'] );
2999 }
3000 }
3001
3002 return $navigation;
3003}
3004
3005/**
3006 * Displays a paginated navigation to next/previous set of posts, when applicable.
3007 *
3008 * @since 4.1.0
3009 *
3010 * @param array $args Optional. See get_the_posts_pagination() for available arguments.
3011 * Default empty array.
3012 */
3013function the_posts_pagination( $args = array() ) {
3014 echo get_the_posts_pagination( $args );
3015}
3016
3017/**
3018 * Wraps passed links in navigational markup.
3019 *
3020 * @since 4.1.0
3021 * @since 5.3.0 Added the `aria_label` parameter.
3022 * @access private
3023 *
3024 * @param string $links Navigational links.
3025 * @param string $css_class Optional. Custom class for the nav element.
3026 * Default 'posts-navigation'.
3027 * @param string $screen_reader_text Optional. Screen reader text for the nav element.
3028 * Default 'Posts navigation'.
3029 * @param string $aria_label Optional. ARIA label for the nav element.
3030 * Defaults to the value of `$screen_reader_text`.
3031 * @return string Navigation template tag.
3032 */
3033function _navigation_markup( $links, $css_class = 'posts-navigation', $screen_reader_text = '', $aria_label = '' ) {
3034 if ( empty( $screen_reader_text ) ) {
3035 $screen_reader_text = /* translators: Hidden accessibility text. */ __( 'Posts navigation' );
3036 }
3037 if ( empty( $aria_label ) ) {
3038 $aria_label = $screen_reader_text;
3039 }
3040
3041 $template = '
3042 <nav class="navigation %1$s" aria-label="%4$s">
3043 <h2 class="screen-reader-text">%2$s</h2>
3044 <div class="nav-links">%3$s</div>
3045 </nav>';
3046
3047 /**
3048 * Filters the navigation markup template.
3049 *
3050 * Note: The filtered template HTML must contain specifiers for the navigation
3051 * class (%1$s), the screen-reader-text value (%2$s), placement of the navigation
3052 * links (%3$s), and ARIA label text if screen-reader-text does not fit that (%4$s):
3053 *
3054 * <nav class="navigation %1$s" aria-label="%4$s">
3055 * <h2 class="screen-reader-text">%2$s</h2>
3056 * <div class="nav-links">%3$s</div>
3057 * </nav>
3058 *
3059 * @since 4.4.0
3060 *
3061 * @param string $template The default template.
3062 * @param string $css_class The class passed by the calling function.
3063 */
3064 $template = apply_filters( 'navigation_markup_template', $template, $css_class );
3065
3066 return sprintf( $template, sanitize_html_class( $css_class ), esc_html( $screen_reader_text ), $links, esc_attr( $aria_label ) );
3067}
3068
3069/**
3070 * Retrieves the comments page number link.
3071 *
3072 * @since 2.7.0
3073 *
3074 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
3075 *
3076 * @param int $pagenum Optional. Page number. Default 1.
3077 * @param int $max_page Optional. The maximum number of comment pages. Default 0.
3078 * @return string The comments page number link URL.
3079 */
3080function get_comments_pagenum_link( $pagenum = 1, $max_page = 0 ) {
3081 global $wp_rewrite;
3082
3083 $pagenum = (int) $pagenum;
3084 $max_page = (int) $max_page;
3085
3086 $result = get_permalink();
3087
3088 if ( 'newest' === get_option( 'default_comments_page' ) ) {
3089 if ( $pagenum !== $max_page ) {
3090 if ( $wp_rewrite->using_permalinks() ) {
3091 $result = user_trailingslashit( trailingslashit( $result ) . $wp_rewrite->comments_pagination_base . '-' . $pagenum, 'commentpaged' );
3092 } else {
3093 $result = add_query_arg( 'cpage', $pagenum, $result );
3094 }
3095 }
3096 } elseif ( $pagenum > 1 ) {
3097 if ( $wp_rewrite->using_permalinks() ) {
3098 $result = user_trailingslashit( trailingslashit( $result ) . $wp_rewrite->comments_pagination_base . '-' . $pagenum, 'commentpaged' );
3099 } else {
3100 $result = add_query_arg( 'cpage', $pagenum, $result );
3101 }
3102 }
3103
3104 $result .= '#comments';
3105
3106 /**
3107 * Filters the comments page number link for the current request.
3108 *
3109 * @since 2.7.0
3110 *
3111 * @param string $result The comments page number link.
3112 */
3113 return apply_filters( 'get_comments_pagenum_link', $result );
3114}
3115
3116/**
3117 * Retrieves the link to the next comments page.
3118 *
3119 * @since 2.7.1
3120 * @since 6.7.0 Added the `page` parameter.
3121 *
3122 * @global WP_Query $wp_query WordPress Query object.
3123 *
3124 * @param string $label Optional. Label for link text. Default empty.
3125 * @param int $max_page Optional. Max page. Default 0.
3126 * @param int|null $page Optional. Page number. Default null.
3127 * @return string|void HTML-formatted link for the next page of comments.
3128 */
3129function get_next_comments_link( $label = '', $max_page = 0, $page = null ) {
3130 global $wp_query;
3131
3132 if ( ! is_singular() ) {
3133 return;
3134 }
3135
3136 if ( is_null( $page ) ) {
3137 $page = get_query_var( 'cpage' );
3138 }
3139
3140 if ( ! $page ) {
3141 $page = 1;
3142 }
3143
3144 $next_page = (int) $page + 1;
3145
3146 if ( empty( $max_page ) ) {
3147 $max_page = $wp_query->max_num_comment_pages;
3148 }
3149
3150 if ( empty( $max_page ) ) {
3151 $max_page = get_comment_pages_count();
3152 }
3153
3154 if ( $next_page > $max_page ) {
3155 return;
3156 }
3157
3158 if ( empty( $label ) ) {
3159 $label = __( 'Newer Comments &raquo;' );
3160 }
3161
3162 /**
3163 * Filters the anchor tag attributes for the next comments page link.
3164 *
3165 * @since 2.7.0
3166 *
3167 * @param string $attributes Attributes for the anchor tag.
3168 */
3169 $attr = apply_filters( 'next_comments_link_attributes', '' );
3170
3171 return sprintf(
3172 '<a href="%1$s" %2$s>%3$s</a>',
3173 esc_url( get_comments_pagenum_link( $next_page, $max_page ) ),
3174 $attr,
3175 preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $label )
3176 );
3177}
3178
3179/**
3180 * Displays the link to the next comments page.
3181 *
3182 * @since 2.7.0
3183 *
3184 * @param string $label Optional. Label for link text. Default empty.
3185 * @param int $max_page Optional. Max page. Default 0.
3186 */
3187function next_comments_link( $label = '', $max_page = 0 ) {
3188 echo get_next_comments_link( $label, $max_page );
3189}
3190
3191/**
3192 * Retrieves the link to the previous comments page.
3193 *
3194 * @since 2.7.1
3195 * @since 6.7.0 Added the `page` parameter.
3196 *
3197 * @param string $label Optional. Label for comments link text. Default empty.
3198 * @param int|null $page Optional. Page number. Default null.
3199 * @return string|void HTML-formatted link for the previous page of comments.
3200 */
3201function get_previous_comments_link( $label = '', $page = null ) {
3202 if ( ! is_singular() ) {
3203 return;
3204 }
3205
3206 if ( is_null( $page ) ) {
3207 $page = get_query_var( 'cpage' );
3208 }
3209
3210 if ( (int) $page <= 1 ) {
3211 return;
3212 }
3213
3214 $previous_page = (int) $page - 1;
3215
3216 if ( empty( $label ) ) {
3217 $label = __( '&laquo; Older Comments' );
3218 }
3219
3220 /**
3221 * Filters the anchor tag attributes for the previous comments page link.
3222 *
3223 * @since 2.7.0
3224 *
3225 * @param string $attributes Attributes for the anchor tag.
3226 */
3227 $attr = apply_filters( 'previous_comments_link_attributes', '' );
3228
3229 return sprintf(
3230 '<a href="%1$s" %2$s>%3$s</a>',
3231 esc_url( get_comments_pagenum_link( $previous_page ) ),
3232 $attr,
3233 preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $label )
3234 );
3235}
3236
3237/**
3238 * Displays the link to the previous comments page.
3239 *
3240 * @since 2.7.0
3241 *
3242 * @param string $label Optional. Label for comments link text. Default empty.
3243 */
3244function previous_comments_link( $label = '' ) {
3245 echo get_previous_comments_link( $label );
3246}
3247
3248/**
3249 * Displays or retrieves pagination links for the comments on the current post.
3250 *
3251 * @see paginate_links()
3252 * @since 2.7.0
3253 *
3254 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
3255 *
3256 * @param string|array $args Optional args. See paginate_links(). Default empty array.
3257 * @return void|string|array Void if 'echo' argument is true and 'type' is not an array,
3258 * or if the query is not for an existing single post of any post type.
3259 * Otherwise, markup for comment page links or array of comment page links,
3260 * depending on 'type' argument.
3261 */
3262function paginate_comments_links( $args = array() ) {
3263 global $wp_rewrite;
3264
3265 if ( ! is_singular() ) {
3266 return;
3267 }
3268
3269 $page = get_query_var( 'cpage' );
3270 if ( ! $page ) {
3271 $page = 1;
3272 }
3273 $max_page = get_comment_pages_count();
3274 $defaults = array(
3275 'base' => add_query_arg( 'cpage', '%#%' ),
3276 'format' => '',
3277 'total' => $max_page,
3278 'current' => $page,
3279 'echo' => true,
3280 'type' => 'plain',
3281 'add_fragment' => '#comments',
3282 );
3283 if ( $wp_rewrite->using_permalinks() ) {
3284 $defaults['base'] = user_trailingslashit( trailingslashit( get_permalink() ) . $wp_rewrite->comments_pagination_base . '-%#%', 'commentpaged' );
3285 }
3286
3287 $args = wp_parse_args( $args, $defaults );
3288 $page_links = paginate_links( $args );
3289
3290 if ( $args['echo'] && 'array' !== $args['type'] ) {
3291 echo $page_links;
3292 } else {
3293 return $page_links;
3294 }
3295}
3296
3297/**
3298 * Retrieves navigation to next/previous set of comments, when applicable.
3299 *
3300 * @since 4.4.0
3301 * @since 5.3.0 Added the `aria_label` parameter.
3302 * @since 5.5.0 Added the `class` parameter.
3303 *
3304 * @param array $args {
3305 * Optional. Default comments navigation arguments.
3306 *
3307 * @type string $prev_text Anchor text to display in the previous comments link.
3308 * Default 'Older comments'.
3309 * @type string $next_text Anchor text to display in the next comments link.
3310 * Default 'Newer comments'.
3311 * @type string $screen_reader_text Screen reader text for the nav element. Default 'Comments navigation'.
3312 * @type string $aria_label ARIA label text for the nav element. Default 'Comments'.
3313 * @type string $class Custom class for the nav element. Default 'comment-navigation'.
3314 * }
3315 * @return string Markup for comments links.
3316 */
3317function get_the_comments_navigation( $args = array() ) {
3318 $navigation = '';
3319
3320 // Are there comments to navigate through?
3321 if ( get_comment_pages_count() > 1 ) {
3322 // Make sure the nav element has an aria-label attribute: fallback to the screen reader text.
3323 if ( ! empty( $args['screen_reader_text'] ) && empty( $args['aria_label'] ) ) {
3324 $args['aria_label'] = $args['screen_reader_text'];
3325 }
3326
3327 $args = wp_parse_args(
3328 $args,
3329 array(
3330 'prev_text' => __( 'Older comments' ),
3331 'next_text' => __( 'Newer comments' ),
3332 'screen_reader_text' => __( 'Comments navigation' ),
3333 'aria_label' => __( 'Comments' ),
3334 'class' => 'comment-navigation',
3335 )
3336 );
3337
3338 $prev_link = get_previous_comments_link( $args['prev_text'] );
3339 $next_link = get_next_comments_link( $args['next_text'] );
3340
3341 if ( $prev_link ) {
3342 $navigation .= '<div class="nav-previous">' . $prev_link . '</div>';
3343 }
3344
3345 if ( $next_link ) {
3346 $navigation .= '<div class="nav-next">' . $next_link . '</div>';
3347 }
3348
3349 $navigation = _navigation_markup( $navigation, $args['class'], $args['screen_reader_text'], $args['aria_label'] );
3350 }
3351
3352 return $navigation;
3353}
3354
3355/**
3356 * Displays navigation to next/previous set of comments, when applicable.
3357 *
3358 * @since 4.4.0
3359 *
3360 * @param array $args See get_the_comments_navigation() for available arguments. Default empty array.
3361 */
3362function the_comments_navigation( $args = array() ) {
3363 echo get_the_comments_navigation( $args );
3364}
3365
3366/**
3367 * Retrieves a paginated navigation to next/previous set of comments, when applicable.
3368 *
3369 * @since 4.4.0
3370 * @since 5.3.0 Added the `aria_label` parameter.
3371 * @since 5.5.0 Added the `class` parameter.
3372 *
3373 * @see paginate_comments_links()
3374 *
3375 * @param array $args {
3376 * Optional. Default pagination arguments.
3377 *
3378 * @type string $screen_reader_text Screen reader text for the nav element. Default 'Comments pagination'.
3379 * @type string $aria_label ARIA label text for the nav element. Default 'Comments pagination'.
3380 * @type string $class Custom class for the nav element. Default 'comments-pagination'.
3381 * }
3382 * @return string Markup for pagination links.
3383 */
3384function get_the_comments_pagination( $args = array() ) {
3385 $navigation = '';
3386
3387 // Make sure the nav element has an aria-label attribute: fallback to the screen reader text.
3388 if ( ! empty( $args['screen_reader_text'] ) && empty( $args['aria_label'] ) ) {
3389 $args['aria_label'] = $args['screen_reader_text'];
3390 }
3391
3392 $args = wp_parse_args(
3393 $args,
3394 array(
3395 'screen_reader_text' => __( 'Comments pagination' ),
3396 'aria_label' => __( 'Comments pagination' ),
3397 'class' => 'comments-pagination',
3398 )
3399 );
3400 $args['echo'] = false;
3401
3402 // Make sure we get a string back. Plain is the next best thing.
3403 if ( isset( $args['type'] ) && 'array' === $args['type'] ) {
3404 $args['type'] = 'plain';
3405 }
3406
3407 $links = paginate_comments_links( $args );
3408
3409 if ( $links ) {
3410 $navigation = _navigation_markup( $links, $args['class'], $args['screen_reader_text'], $args['aria_label'] );
3411 }
3412
3413 return $navigation;
3414}
3415
3416/**
3417 * Displays a paginated navigation to next/previous set of comments, when applicable.
3418 *
3419 * @since 4.4.0
3420 *
3421 * @param array $args See get_the_comments_pagination() for available arguments. Default empty array.
3422 */
3423function the_comments_pagination( $args = array() ) {
3424 echo get_the_comments_pagination( $args );
3425}
3426
3427/**
3428 * Retrieves the URL for the current site where the front end is accessible.
3429 *
3430 * Returns the 'home' option with the appropriate protocol. The protocol will be 'https'
3431 * if is_ssl() evaluates to true; otherwise, it will be the same as the 'home' option.
3432 * If `$scheme` is 'http' or 'https', is_ssl() is overridden.
3433 *
3434 * @since 3.0.0
3435 *
3436 * @param string $path Optional. Path relative to the home URL. Default empty.
3437 * @param string|null $scheme Optional. Scheme to give the home URL context. Accepts
3438 * 'http', 'https', 'relative', 'rest', or null. Default null.
3439 * @return string Home URL link with optional path appended.
3440 */
3441function home_url( $path = '', $scheme = null ) {
3442 return get_home_url( null, $path, $scheme );
3443}
3444
3445/**
3446 * Retrieves the URL for a given site where the front end is accessible.
3447 *
3448 * Returns the 'home' option with the appropriate protocol. The protocol will be 'https'
3449 * if is_ssl() evaluates to true; otherwise, it will be the same as the 'home' option.
3450 * If `$scheme` is 'http' or 'https', is_ssl() is overridden.
3451 *
3452 * @since 3.0.0
3453 *
3454 * @param int|null $blog_id Optional. Site ID. Default null (current site).
3455 * @param string $path Optional. Path relative to the home URL. Default empty.
3456 * @param string|null $scheme Optional. Scheme to give the home URL context. Accepts
3457 * 'http', 'https', 'relative', 'rest', or null. Default null.
3458 * @return string Home URL link with optional path appended.
3459 */
3460function get_home_url( $blog_id = null, $path = '', $scheme = null ) {
3461 $orig_scheme = $scheme;
3462
3463 if ( empty( $blog_id ) || ! is_multisite() ) {
3464 $url = get_option( 'home' );
3465 } else {
3466 switch_to_blog( $blog_id );
3467 $url = get_option( 'home' );
3468 restore_current_blog();
3469 }
3470
3471 if ( ! in_array( $scheme, array( 'http', 'https', 'relative' ), true ) ) {
3472 if ( is_ssl() ) {
3473 $scheme = 'https';
3474 } else {
3475 $scheme = parse_url( $url, PHP_URL_SCHEME );
3476 }
3477 }
3478
3479 $url = set_url_scheme( $url, $scheme );
3480
3481 if ( $path && is_string( $path ) ) {
3482 $url .= '/' . ltrim( $path, '/' );
3483 }
3484
3485 /**
3486 * Filters the home URL.
3487 *
3488 * @since 3.0.0
3489 *
3490 * @param string $url The complete home URL including scheme and path.
3491 * @param string $path Path relative to the home URL. Blank string if no path is specified.
3492 * @param string|null $orig_scheme Scheme to give the home URL context. Accepts 'http', 'https',
3493 * 'relative', 'rest', or null.
3494 * @param int|null $blog_id Site ID, or null for the current site.
3495 */
3496 return apply_filters( 'home_url', $url, $path, $orig_scheme, $blog_id );
3497}
3498
3499/**
3500 * Retrieves the URL for the current site where WordPress application files
3501 * (e.g. wp-blog-header.php or the wp-admin/ folder) are accessible.
3502 *
3503 * Returns the 'site_url' option with the appropriate protocol, 'https' if
3504 * is_ssl() and 'http' otherwise. If $scheme is 'http' or 'https', is_ssl() is
3505 * overridden.
3506 *
3507 * @since 3.0.0
3508 *
3509 * @param string $path Optional. Path relative to the site URL. Default empty.
3510 * @param string|null $scheme Optional. Scheme to give the site URL context. See set_url_scheme().
3511 * @return string Site URL link with optional path appended.
3512 */
3513function site_url( $path = '', $scheme = null ) {
3514 return get_site_url( null, $path, $scheme );
3515}
3516
3517/**
3518 * Retrieves the URL for a given site where WordPress application files
3519 * (e.g. wp-blog-header.php or the wp-admin/ folder) are accessible.
3520 *
3521 * Returns the 'site_url' option with the appropriate protocol, 'https' if
3522 * is_ssl() and 'http' otherwise. If `$scheme` is 'http' or 'https',
3523 * `is_ssl()` is overridden.
3524 *
3525 * @since 3.0.0
3526 *
3527 * @param int|null $blog_id Optional. Site ID. Default null (current site).
3528 * @param string $path Optional. Path relative to the site URL. Default empty.
3529 * @param string|null $scheme Optional. Scheme to give the site URL context. Accepts
3530 * 'http', 'https', 'login', 'login_post', 'admin', or
3531 * 'relative'. Default null.
3532 * @return string Site URL link with optional path appended.
3533 */
3534function get_site_url( $blog_id = null, $path = '', $scheme = null ) {
3535 if ( empty( $blog_id ) || ! is_multisite() ) {
3536 $url = get_option( 'siteurl' );
3537 } else {
3538 switch_to_blog( $blog_id );
3539 $url = get_option( 'siteurl' );
3540 restore_current_blog();
3541 }
3542
3543 $url = set_url_scheme( $url, $scheme );
3544
3545 if ( $path && is_string( $path ) ) {
3546 $url .= '/' . ltrim( $path, '/' );
3547 }
3548
3549 /**
3550 * Filters the site URL.
3551 *
3552 * @since 2.7.0
3553 *
3554 * @param string $url The complete site URL including scheme and path.
3555 * @param string $path Path relative to the site URL. Blank string if no path is specified.
3556 * @param string|null $scheme Scheme to give the site URL context. Accepts 'http', 'https', 'login',
3557 * 'login_post', 'admin', 'relative' or null.
3558 * @param int|null $blog_id Site ID, or null for the current site.
3559 */
3560 return apply_filters( 'site_url', $url, $path, $scheme, $blog_id );
3561}
3562
3563/**
3564 * Retrieves the URL to the admin area for the current site.
3565 *
3566 * @since 2.6.0
3567 *
3568 * @param string $path Optional. Path relative to the admin URL. Default empty.
3569 * @param string $scheme The scheme to use. Default is 'admin', which obeys force_ssl_admin() and is_ssl().
3570 * 'http' or 'https' can be passed to force those schemes.
3571 * @return string Admin URL link with optional path appended.
3572 */
3573function admin_url( $path = '', $scheme = 'admin' ) {
3574 return get_admin_url( null, $path, $scheme );
3575}
3576
3577/**
3578 * Retrieves the URL to the admin area for a given site.
3579 *
3580 * @since 3.0.0
3581 *
3582 * @param int|null $blog_id Optional. Site ID. Default null (current site).
3583 * @param string $path Optional. Path relative to the admin URL. Default empty.
3584 * @param string $scheme Optional. The scheme to use. Accepts 'http' or 'https',
3585 * to force those schemes. Default 'admin', which obeys
3586 * force_ssl_admin() and is_ssl().
3587 * @return string Admin URL link with optional path appended.
3588 */
3589function get_admin_url( $blog_id = null, $path = '', $scheme = 'admin' ) {
3590 $url = get_site_url( $blog_id, 'wp-admin/', $scheme );
3591
3592 if ( $path && is_string( $path ) ) {
3593 $url .= ltrim( $path, '/' );
3594 }
3595
3596 /**
3597 * Filters the admin area URL.
3598 *
3599 * @since 2.8.0
3600 * @since 5.8.0 The `$scheme` parameter was added.
3601 *
3602 * @param string $url The complete admin area URL including scheme and path.
3603 * @param string $path Path relative to the admin area URL. Blank string if no path is specified.
3604 * @param int|null $blog_id Site ID, or null for the current site.
3605 * @param string|null $scheme The scheme to use. Accepts 'http', 'https',
3606 * 'admin', or null. Default 'admin', which obeys force_ssl_admin() and is_ssl().
3607 */
3608 return apply_filters( 'admin_url', $url, $path, $blog_id, $scheme );
3609}
3610
3611/**
3612 * Retrieves the URL to the includes directory.
3613 *
3614 * @since 2.6.0
3615 *
3616 * @param string $path Optional. Path relative to the includes URL. Default empty.
3617 * @param string|null $scheme Optional. Scheme to give the includes URL context. Accepts
3618 * 'http', 'https', or 'relative'. Default null.
3619 * @return string Includes URL link with optional path appended.
3620 */
3621function includes_url( $path = '', $scheme = null ) {
3622 $url = site_url( '/' . WPINC . '/', $scheme );
3623
3624 if ( $path && is_string( $path ) ) {
3625 $url .= ltrim( $path, '/' );
3626 }
3627
3628 /**
3629 * Filters the URL to the includes directory.
3630 *
3631 * @since 2.8.0
3632 * @since 5.8.0 The `$scheme` parameter was added.
3633 *
3634 * @param string $url The complete URL to the includes directory including scheme and path.
3635 * @param string $path Path relative to the URL to the wp-includes directory. Blank string
3636 * if no path is specified.
3637 * @param string|null $scheme Scheme to give the includes URL context. Accepts
3638 * 'http', 'https', 'relative', or null. Default null.
3639 */
3640 return apply_filters( 'includes_url', $url, $path, $scheme );
3641}
3642
3643/**
3644 * Retrieves the URL to the content directory.
3645 *
3646 * @since 2.6.0
3647 *
3648 * @param string $path Optional. Path relative to the content URL. Default empty.
3649 * @return string Content URL link with optional path appended.
3650 */
3651function content_url( $path = '' ) {
3652 $url = set_url_scheme( WP_CONTENT_URL );
3653
3654 if ( $path && is_string( $path ) ) {
3655 $url .= '/' . ltrim( $path, '/' );
3656 }
3657
3658 /**
3659 * Filters the URL to the content directory.
3660 *
3661 * @since 2.8.0
3662 *
3663 * @param string $url The complete URL to the content directory including scheme and path.
3664 * @param string $path Path relative to the URL to the content directory. Blank string
3665 * if no path is specified.
3666 */
3667 return apply_filters( 'content_url', $url, $path );
3668}
3669
3670/**
3671 * Retrieves a URL within the plugins or mu-plugins directory.
3672 *
3673 * Defaults to the plugins directory URL if no arguments are supplied.
3674 *
3675 * @since 2.6.0
3676 *
3677 * @param string $path Optional. Extra path appended to the end of the URL, including
3678 * the relative directory if $plugin is supplied. Default empty.
3679 * @param string $plugin Optional. A full path to a file inside a plugin or mu-plugin.
3680 * The URL will be relative to its directory. Default empty.
3681 * Typically this is done by passing `__FILE__` as the argument.
3682 * @return string Plugins URL link with optional paths appended.
3683 */
3684function plugins_url( $path = '', $plugin = '' ) {
3685
3686 $path = wp_normalize_path( $path );
3687 $plugin = wp_normalize_path( $plugin );
3688 $mu_plugin_dir = wp_normalize_path( WPMU_PLUGIN_DIR );
3689
3690 if ( ! empty( $plugin ) && str_starts_with( $plugin, $mu_plugin_dir ) ) {
3691 $url = WPMU_PLUGIN_URL;
3692 } else {
3693 $url = WP_PLUGIN_URL;
3694 }
3695
3696 $url = set_url_scheme( $url );
3697
3698 if ( ! empty( $plugin ) && is_string( $plugin ) ) {
3699 $folder = dirname( plugin_basename( $plugin ) );
3700 if ( '.' !== $folder ) {
3701 $url .= '/' . ltrim( $folder, '/' );
3702 }
3703 }
3704
3705 if ( $path && is_string( $path ) ) {
3706 $url .= '/' . ltrim( $path, '/' );
3707 }
3708
3709 /**
3710 * Filters the URL to the plugins directory.
3711 *
3712 * @since 2.8.0
3713 *
3714 * @param string $url The complete URL to the plugins directory including scheme and path.
3715 * @param string $path Path relative to the URL to the plugins directory. Blank string
3716 * if no path is specified.
3717 * @param string $plugin The plugin file path to be relative to. Blank string if no plugin
3718 * is specified.
3719 */
3720 return apply_filters( 'plugins_url', $url, $path, $plugin );
3721}
3722
3723/**
3724 * Retrieves the site URL for the current network.
3725 *
3726 * Returns the site URL with the appropriate protocol, 'https' if
3727 * is_ssl() and 'http' otherwise. If $scheme is 'http' or 'https', is_ssl() is
3728 * overridden.
3729 *
3730 * @since 3.0.0
3731 *
3732 * @see set_url_scheme()
3733 *
3734 * @param string $path Optional. Path relative to the site URL. Default empty.
3735 * @param string|null $scheme Optional. Scheme to give the site URL context. Accepts
3736 * 'http', 'https', or 'relative'. Default null.
3737 * @return string Site URL link with optional path appended.
3738 */
3739function network_site_url( $path = '', $scheme = null ) {
3740 if ( ! is_multisite() ) {
3741 return site_url( $path, $scheme );
3742 }
3743
3744 $current_network = get_network();
3745
3746 if ( 'relative' === $scheme ) {
3747 $url = $current_network->path;
3748 } else {
3749 $url = set_url_scheme( 'http://' . $current_network->domain . $current_network->path, $scheme );
3750 }
3751
3752 if ( $path && is_string( $path ) ) {
3753 $url .= ltrim( $path, '/' );
3754 }
3755
3756 /**
3757 * Filters the network site URL.
3758 *
3759 * @since 3.0.0
3760 *
3761 * @param string $url The complete network site URL including scheme and path.
3762 * @param string $path Path relative to the network site URL. Blank string if
3763 * no path is specified.
3764 * @param string|null $scheme Scheme to give the URL context. Accepts 'http', 'https',
3765 * 'relative' or null.
3766 */
3767 return apply_filters( 'network_site_url', $url, $path, $scheme );
3768}
3769
3770/**
3771 * Retrieves the home URL for the current network.
3772 *
3773 * Returns the home URL with the appropriate protocol, 'https' is_ssl()
3774 * and 'http' otherwise. If `$scheme` is 'http' or 'https', `is_ssl()` is
3775 * overridden.
3776 *
3777 * @since 3.0.0
3778 *
3779 * @param string $path Optional. Path relative to the home URL. Default empty.
3780 * @param string|null $scheme Optional. Scheme to give the home URL context. Accepts
3781 * 'http', 'https', or 'relative'. Default null.
3782 * @return string Home URL link with optional path appended.
3783 */
3784function network_home_url( $path = '', $scheme = null ) {
3785 if ( ! is_multisite() ) {
3786 return home_url( $path, $scheme );
3787 }
3788
3789 $current_network = get_network();
3790 $orig_scheme = $scheme;
3791
3792 if ( ! in_array( $scheme, array( 'http', 'https', 'relative' ), true ) ) {
3793 $scheme = is_ssl() ? 'https' : 'http';
3794 }
3795
3796 if ( 'relative' === $scheme ) {
3797 $url = $current_network->path;
3798 } else {
3799 $url = set_url_scheme( 'http://' . $current_network->domain . $current_network->path, $scheme );
3800 }
3801
3802 if ( $path && is_string( $path ) ) {
3803 $url .= ltrim( $path, '/' );
3804 }
3805
3806 /**
3807 * Filters the network home URL.
3808 *
3809 * @since 3.0.0
3810 *
3811 * @param string $url The complete network home URL including scheme and path.
3812 * @param string $path Path relative to the network home URL. Blank string
3813 * if no path is specified.
3814 * @param string|null $orig_scheme Scheme to give the URL context. Accepts 'http', 'https',
3815 * 'relative' or null.
3816 */
3817 return apply_filters( 'network_home_url', $url, $path, $orig_scheme );
3818}
3819
3820/**
3821 * Retrieves the URL to the admin area for the network.
3822 *
3823 * @since 3.0.0
3824 *
3825 * @param string $path Optional path relative to the admin URL. Default empty.
3826 * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
3827 * and is_ssl(). 'http' or 'https' can be passed to force those schemes.
3828 * @return string Admin URL link with optional path appended.
3829 */
3830function network_admin_url( $path = '', $scheme = 'admin' ) {
3831 if ( ! is_multisite() ) {
3832 return admin_url( $path, $scheme );
3833 }
3834
3835 $url = network_site_url( 'wp-admin/network/', $scheme );
3836
3837 if ( $path && is_string( $path ) ) {
3838 $url .= ltrim( $path, '/' );
3839 }
3840
3841 /**
3842 * Filters the network admin URL.
3843 *
3844 * @since 3.0.0
3845 * @since 5.8.0 The `$scheme` parameter was added.
3846 *
3847 * @param string $url The complete network admin URL including scheme and path.
3848 * @param string $path Path relative to the network admin URL. Blank string if
3849 * no path is specified.
3850 * @param string|null $scheme The scheme to use. Accepts 'http', 'https',
3851 * 'admin', or null. Default is 'admin', which obeys force_ssl_admin() and is_ssl().
3852 */
3853 return apply_filters( 'network_admin_url', $url, $path, $scheme );
3854}
3855
3856/**
3857 * Retrieves the URL to the admin area for the current user.
3858 *
3859 * @since 3.0.0
3860 *
3861 * @param string $path Optional. Path relative to the admin URL. Default empty.
3862 * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
3863 * and is_ssl(). 'http' or 'https' can be passed to force those schemes.
3864 * @return string Admin URL link with optional path appended.
3865 */
3866function user_admin_url( $path = '', $scheme = 'admin' ) {
3867 $url = network_site_url( 'wp-admin/user/', $scheme );
3868
3869 if ( $path && is_string( $path ) ) {
3870 $url .= ltrim( $path, '/' );
3871 }
3872
3873 /**
3874 * Filters the user admin URL for the current user.
3875 *
3876 * @since 3.1.0
3877 * @since 5.8.0 The `$scheme` parameter was added.
3878 *
3879 * @param string $url The complete URL including scheme and path.
3880 * @param string $path Path relative to the URL. Blank string if
3881 * no path is specified.
3882 * @param string|null $scheme The scheme to use. Accepts 'http', 'https',
3883 * 'admin', or null. Default is 'admin', which obeys force_ssl_admin() and is_ssl().
3884 */
3885 return apply_filters( 'user_admin_url', $url, $path, $scheme );
3886}
3887
3888/**
3889 * Retrieves the URL to the admin area for either the current site or the network depending on context.
3890 *
3891 * @since 3.1.0
3892 *
3893 * @param string $path Optional. Path relative to the admin URL. Default empty.
3894 * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
3895 * and is_ssl(). 'http' or 'https' can be passed to force those schemes.
3896 * @return string Admin URL link with optional path appended.
3897 */
3898function self_admin_url( $path = '', $scheme = 'admin' ) {
3899 if ( is_network_admin() ) {
3900 $url = network_admin_url( $path, $scheme );
3901 } elseif ( is_user_admin() ) {
3902 $url = user_admin_url( $path, $scheme );
3903 } else {
3904 $url = admin_url( $path, $scheme );
3905 }
3906
3907 /**
3908 * Filters the admin URL for the current site or network depending on context.
3909 *
3910 * @since 4.9.0
3911 *
3912 * @param string $url The complete URL including scheme and path.
3913 * @param string $path Path relative to the URL. Blank string if no path is specified.
3914 * @param string $scheme The scheme to use.
3915 */
3916 return apply_filters( 'self_admin_url', $url, $path, $scheme );
3917}
3918
3919/**
3920 * Sets the scheme for a URL.
3921 *
3922 * @since 3.4.0
3923 * @since 4.4.0 The 'rest' scheme was added.
3924 *
3925 * @param string $url Absolute URL that includes a scheme
3926 * @param string|null $scheme Optional. Scheme to give $url. Currently 'http', 'https', 'login',
3927 * 'login_post', 'admin', 'relative', 'rest', 'rpc', or null. Default null.
3928 * @return string URL with chosen scheme.
3929 */
3930function set_url_scheme( $url, $scheme = null ) {
3931 $orig_scheme = $scheme;
3932
3933 if ( ! $scheme ) {
3934 $scheme = is_ssl() ? 'https' : 'http';
3935 } elseif ( 'admin' === $scheme || 'login' === $scheme || 'login_post' === $scheme || 'rpc' === $scheme ) {
3936 $scheme = is_ssl() || force_ssl_admin() ? 'https' : 'http';
3937 } elseif ( 'http' !== $scheme && 'https' !== $scheme && 'relative' !== $scheme ) {
3938 $scheme = is_ssl() ? 'https' : 'http';
3939 }
3940
3941 $url = trim( $url );
3942 if ( str_starts_with( $url, '//' ) ) {
3943 $url = 'http:' . $url;
3944 }
3945
3946 if ( 'relative' === $scheme ) {
3947 $url = ltrim( preg_replace( '#^\w+://[^/]*#', '', $url ) );
3948 if ( '' !== $url && '/' === $url[0] ) {
3949 $url = '/' . ltrim( $url, "/ \t\n\r\0\x0B" );
3950 }
3951 } else {
3952 $url = preg_replace( '#^\w+://#', $scheme . '://', $url );
3953 }
3954
3955 /**
3956 * Filters the resulting URL after setting the scheme.
3957 *
3958 * @since 3.4.0
3959 *
3960 * @param string $url The complete URL including scheme and path.
3961 * @param string $scheme Scheme applied to the URL. One of 'http', 'https', or 'relative'.
3962 * @param string|null $orig_scheme Scheme requested for the URL. One of 'http', 'https', 'login',
3963 * 'login_post', 'admin', 'relative', 'rest', 'rpc', or null.
3964 */
3965 return apply_filters( 'set_url_scheme', $url, $scheme, $orig_scheme );
3966}
3967
3968/**
3969 * Retrieves the URL to the user's dashboard.
3970 *
3971 * If a user does not belong to any site, the global user dashboard is used. If the user
3972 * belongs to the current site, the dashboard for the current site is returned. If the user
3973 * cannot edit the current site, the dashboard to the user's primary site is returned.
3974 *
3975 * @since 3.1.0
3976 *
3977 * @param int $user_id Optional. User ID. Defaults to current user.
3978 * @param string $path Optional path relative to the dashboard. Use only paths known to
3979 * both site and user admins. Default empty.
3980 * @param string $scheme The scheme to use. Default is 'admin', which obeys force_ssl_admin()
3981 * and is_ssl(). 'http' or 'https' can be passed to force those schemes.
3982 * @return string Dashboard URL link with optional path appended.
3983 */
3984function get_dashboard_url( $user_id = 0, $path = '', $scheme = 'admin' ) {
3985 $user_id = $user_id ? (int) $user_id : get_current_user_id();
3986
3987 $blogs = get_blogs_of_user( $user_id );
3988
3989 if ( is_multisite() && ! user_can( $user_id, 'manage_network' ) && empty( $blogs ) ) {
3990 $url = user_admin_url( $path, $scheme );
3991 } elseif ( ! is_multisite() ) {
3992 $url = admin_url( $path, $scheme );
3993 } else {
3994 $current_blog = get_current_blog_id();
3995
3996 if ( $current_blog && ( user_can( $user_id, 'manage_network' ) || in_array( $current_blog, array_keys( $blogs ), true ) ) ) {
3997 $url = admin_url( $path, $scheme );
3998 } else {
3999 $active = get_active_blog_for_user( $user_id );
4000 if ( $active ) {
4001 $url = get_admin_url( $active->blog_id, $path, $scheme );
4002 } else {
4003 $url = user_admin_url( $path, $scheme );
4004 }
4005 }
4006 }
4007
4008 /**
4009 * Filters the dashboard URL for a user.
4010 *
4011 * @since 3.1.0
4012 *
4013 * @param string $url The complete URL including scheme and path.
4014 * @param int $user_id The user ID.
4015 * @param string $path Path relative to the URL. Blank string if no path is specified.
4016 * @param string $scheme Scheme to give the URL context. Accepts 'http', 'https', 'login',
4017 * 'login_post', 'admin', 'relative' or null.
4018 */
4019 return apply_filters( 'user_dashboard_url', $url, $user_id, $path, $scheme );
4020}
4021
4022/**
4023 * Retrieves the URL to the user's profile editor.
4024 *
4025 * @since 3.1.0
4026 *
4027 * @param int $user_id Optional. User ID. Defaults to current user.
4028 * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
4029 * and is_ssl(). 'http' or 'https' can be passed to force those schemes.
4030 * @return string Dashboard URL link with optional path appended.
4031 */
4032function get_edit_profile_url( $user_id = 0, $scheme = 'admin' ) {
4033 $user_id = $user_id ? (int) $user_id : get_current_user_id();
4034
4035 if ( is_user_admin() ) {
4036 $url = user_admin_url( 'profile.php', $scheme );
4037 } elseif ( is_network_admin() ) {
4038 $url = network_admin_url( 'profile.php', $scheme );
4039 } else {
4040 $url = get_dashboard_url( $user_id, 'profile.php', $scheme );
4041 }
4042
4043 /**
4044 * Filters the URL for a user's profile editor.
4045 *
4046 * @since 3.1.0
4047 *
4048 * @param string $url The complete URL including scheme and path.
4049 * @param int $user_id The user ID.
4050 * @param string $scheme Scheme to give the URL context. Accepts 'http', 'https', 'login',
4051 * 'login_post', 'admin', 'relative' or null.
4052 */
4053 return apply_filters( 'edit_profile_url', $url, $user_id, $scheme );
4054}
4055
4056/**
4057 * Returns the canonical URL for a post.
4058 *
4059 * When the post is the same as the current requested page the function will handle the
4060 * pagination arguments too.
4061 *
4062 * @since 4.6.0
4063 *
4064 * @param int|WP_Post $post Optional. Post ID or object. Default is global `$post`.
4065 * @return string|false The canonical URL. False if the post does not exist
4066 * or has not been published yet.
4067 */
4068function wp_get_canonical_url( $post = null ) {
4069 $post = get_post( $post );
4070
4071 if ( ! $post ) {
4072 return false;
4073 }
4074
4075 if ( 'publish' !== get_post_status( $post ) ) {
4076 return false;
4077 }
4078
4079 $canonical_url = get_permalink( $post );
4080
4081 // If a canonical is being generated for the current page, make sure it has pagination if needed.
4082 if ( get_queried_object_id() === $post->ID ) {
4083 $page = get_query_var( 'page', 0 );
4084 if ( $page >= 2 ) {
4085 if ( ! get_option( 'permalink_structure' ) ) {
4086 $canonical_url = add_query_arg( 'page', $page, $canonical_url );
4087 } else {
4088 $canonical_url = trailingslashit( $canonical_url ) . user_trailingslashit( $page, 'single_paged' );
4089 }
4090 }
4091
4092 $cpage = get_query_var( 'cpage', 0 );
4093 if ( $cpage ) {
4094 $canonical_url = get_comments_pagenum_link( $cpage );
4095 }
4096 }
4097
4098 /**
4099 * Filters the canonical URL for a post.
4100 *
4101 * @since 4.6.0
4102 *
4103 * @param string $canonical_url The post's canonical URL.
4104 * @param WP_Post $post Post object.
4105 */
4106 return apply_filters( 'get_canonical_url', $canonical_url, $post );
4107}
4108
4109/**
4110 * Outputs rel=canonical for singular queries.
4111 *
4112 * @since 2.9.0
4113 * @since 4.6.0 Adjusted to use `wp_get_canonical_url()`.
4114 */
4115function rel_canonical() {
4116 if ( ! is_singular() ) {
4117 return;
4118 }
4119
4120 $id = get_queried_object_id();
4121
4122 if ( 0 === $id ) {
4123 return;
4124 }
4125
4126 $url = wp_get_canonical_url( $id );
4127
4128 if ( ! empty( $url ) ) {
4129 echo '<link rel="canonical" href="' . esc_url( $url ) . '" />' . "\n";
4130 }
4131}
4132
4133/**
4134 * Returns a shortlink for a post, page, attachment, or site.
4135 *
4136 * This function exists to provide a shortlink tag that all themes and plugins can target.
4137 * A plugin must hook in to provide the actual shortlinks. Default shortlink support is
4138 * limited to providing ?p= style links for posts. Plugins can short-circuit this function
4139 * via the {@see 'pre_get_shortlink'} filter or filter the output via the {@see 'get_shortlink'}
4140 * filter.
4141 *
4142 * @since 3.0.0
4143 *
4144 * @param int $id Optional. A post or site ID. Default is 0, which means the current post or site.
4145 * @param string $context Optional. Whether the ID is a 'site' ID, 'post' ID, or 'media' ID. If 'post',
4146 * the post_type of the post is consulted. If 'query', the current query is consulted
4147 * to determine the ID and context. Default 'post'.
4148 * @param bool $allow_slugs Optional. Whether to allow post slugs in the shortlink. It is up to the plugin how
4149 * and whether to honor this. Default true.
4150 * @return string A shortlink or an empty string if no shortlink exists for the requested resource or if shortlinks
4151 * are not enabled.
4152 */
4153function wp_get_shortlink( $id = 0, $context = 'post', $allow_slugs = true ) {
4154 /**
4155 * Filters whether to preempt generating a shortlink for the given post.
4156 *
4157 * Returning a value other than false from the filter will short-circuit
4158 * the shortlink generation process, returning that value instead.
4159 *
4160 * @since 3.0.0
4161 *
4162 * @param false|string $return Short-circuit return value. Either false or a URL string.
4163 * @param int $id Post ID, or 0 for the current post.
4164 * @param string $context The context for the link. One of 'post' or 'query',
4165 * @param bool $allow_slugs Whether to allow post slugs in the shortlink.
4166 */
4167 $shortlink = apply_filters( 'pre_get_shortlink', false, $id, $context, $allow_slugs );
4168
4169 if ( false !== $shortlink ) {
4170 return $shortlink;
4171 }
4172
4173 $post_id = 0;
4174 if ( 'query' === $context && is_singular() ) {
4175 $post_id = get_queried_object_id();
4176 $post = get_post( $post_id );
4177 } elseif ( 'post' === $context ) {
4178 $post = get_post( $id );
4179 if ( ! empty( $post->ID ) ) {
4180 $post_id = $post->ID;
4181 }
4182 }
4183
4184 $shortlink = '';
4185
4186 // Return `?p=` link for all public post types.
4187 if ( ! empty( $post_id ) ) {
4188 $post_type = get_post_type_object( $post->post_type );
4189
4190 if ( 'page' === $post->post_type
4191 && 'page' === get_option( 'show_on_front' ) && (int) get_option( 'page_on_front' ) === $post->ID
4192 ) {
4193 $shortlink = home_url( '/' );
4194 } elseif ( $post_type && $post_type->public ) {
4195 $shortlink = home_url( '?p=' . $post_id );
4196 }
4197 }
4198
4199 /**
4200 * Filters the shortlink for a post.
4201 *
4202 * @since 3.0.0
4203 *
4204 * @param string $shortlink Shortlink URL.
4205 * @param int $id Post ID, or 0 for the current post.
4206 * @param string $context The context for the link. One of 'post' or 'query',
4207 * @param bool $allow_slugs Whether to allow post slugs in the shortlink. Not used by default.
4208 */
4209 return apply_filters( 'get_shortlink', $shortlink, $id, $context, $allow_slugs );
4210}
4211
4212/**
4213 * Injects rel=shortlink into the head if a shortlink is defined for the current page.
4214 *
4215 * Attached to the {@see 'wp_head'} action.
4216 *
4217 * @since 3.0.0
4218 */
4219function wp_shortlink_wp_head() {
4220 $shortlink = wp_get_shortlink( 0, 'query' );
4221
4222 if ( empty( $shortlink ) ) {
4223 return;
4224 }
4225
4226 echo "<link rel='shortlink' href='" . esc_url( $shortlink ) . "' />\n";
4227}
4228
4229/**
4230 * Sends a Link: rel=shortlink header if a shortlink is defined for the current page.
4231 *
4232 * Attached to the {@see 'wp'} action.
4233 *
4234 * @since 3.0.0
4235 */
4236function wp_shortlink_header() {
4237 if ( headers_sent() ) {
4238 return;
4239 }
4240
4241 $shortlink = wp_get_shortlink( 0, 'query' );
4242
4243 if ( empty( $shortlink ) ) {
4244 return;
4245 }
4246
4247 header( 'Link: <' . $shortlink . '>; rel=shortlink', false );
4248}
4249
4250/**
4251 * Displays the shortlink for a post.
4252 *
4253 * Must be called from inside "The Loop"
4254 *
4255 * Call like the_shortlink( __( 'Shortlinkage FTW' ) )
4256 *
4257 * @since 3.0.0
4258 * @since 6.8.0 Removed title attribute.
4259 *
4260 * @param string $text Optional. The link text or HTML to be displayed. Defaults to 'This is the short link.'
4261 * @param string $title Unused.
4262 * @param string $before Optional. HTML to display before the link. Default empty.
4263 * @param string $after Optional. HTML to display after the link. Default empty.
4264 */
4265function the_shortlink( $text = '', $title = '', $before = '', $after = '' ) {
4266 $post = get_post();
4267
4268 if ( empty( $text ) ) {
4269 $text = __( 'This is the short link.' );
4270 }
4271
4272 $shortlink = wp_get_shortlink( $post->ID );
4273
4274 if ( ! empty( $shortlink ) ) {
4275 $link = '<a rel="shortlink" href="' . esc_url( $shortlink ) . '">' . $text . '</a>';
4276
4277 /**
4278 * Filters the short link anchor tag for a post.
4279 *
4280 * @since 3.0.0
4281 *
4282 * @param string $link Shortlink anchor tag.
4283 * @param string $shortlink Shortlink URL.
4284 * @param string $text Shortlink's text.
4285 * @param string $title Shortlink's title attribute. Unused.
4286 */
4287 $link = apply_filters( 'the_shortlink', $link, $shortlink, $text, $title );
4288 echo $before, $link, $after;
4289 }
4290}
4291
4292/**
4293 * Retrieves the avatar URL.
4294 *
4295 * @since 4.2.0
4296 *
4297 * @param mixed $id_or_email The avatar to retrieve a URL for. Accepts a user ID, Gravatar SHA-256 or MD5 hash,
4298 * user email, WP_User object, WP_Post object, or WP_Comment object.
4299 * @param array $args {
4300 * Optional. Arguments to use instead of the default arguments.
4301 *
4302 * @type int $size Height and width of the avatar in pixels. Default 96.
4303 * @type string $default URL for the default image or a default type. Accepts:
4304 * - '404' (return a 404 instead of a default image)
4305 * - 'retro' (a 8-bit arcade-style pixelated face)
4306 * - 'robohash' (a robot)
4307 * - 'monsterid' (a monster)
4308 * - 'wavatar' (a cartoon face)
4309 * - 'identicon' (the "quilt", a geometric pattern)
4310 * - 'initials' (initials based avatar with background color)
4311 * - 'color' (generated background color)
4312 * - 'mystery', 'mm', or 'mysteryman' (The Oyster Man)
4313 * - 'blank' (transparent GIF)
4314 * - 'gravatar_default' (the Gravatar logo)
4315 * Default is the value of the 'avatar_default' option,
4316 * with a fallback of 'mystery'.
4317 * @type bool $force_default Whether to always show the default image, never the Gravatar.
4318 * Default false.
4319 * @type string $rating What rating to display avatars up to. Accepts:
4320 * - 'G' (suitable for all audiences)
4321 * - 'PG' (possibly offensive, usually for audiences 13 and above)
4322 * - 'R' (intended for adult audiences above 17)
4323 * - 'X' (even more mature than above)
4324 * Default is the value of the 'avatar_rating' option.
4325 * @type string $scheme URL scheme to use. See set_url_scheme() for accepted values.
4326 * Default null.
4327 * @type array $processed_args When the function returns, the value will be the processed/sanitized $args
4328 * plus a "found_avatar" guess. Pass as a reference. Default null.
4329 * }
4330 * @return string|false The URL of the avatar on success, false on failure.
4331 */
4332function get_avatar_url( $id_or_email, $args = null ) {
4333 $args = get_avatar_data( $id_or_email, $args );
4334 return $args['url'];
4335}
4336
4337/**
4338 * Check if this comment type allows avatars to be retrieved.
4339 *
4340 * @since 5.1.0
4341 *
4342 * @param string $comment_type Comment type to check.
4343 * @return bool Whether the comment type is allowed for retrieving avatars.
4344 */
4345function is_avatar_comment_type( $comment_type ) {
4346 /**
4347 * Filters the list of allowed comment types for retrieving avatars.
4348 *
4349 * @since 3.0.0
4350 *
4351 * @since 6.9.0 The 'note' comment type was added.
4352 *
4353 * @param array $types An array of content types. Default contains 'comment' and 'note'.
4354 */
4355 $allowed_comment_types = apply_filters( 'get_avatar_comment_types', array( 'comment', 'note' ) );
4356
4357 return in_array( $comment_type, (array) $allowed_comment_types, true );
4358}
4359
4360/**
4361 * Retrieves default data about the avatar.
4362 *
4363 * @since 4.2.0
4364 * @since 6.7.0 Gravatar URLs always use HTTPS.
4365 * @since 6.8.0 Gravatar URLs use the SHA-256 hashing algorithm.
4366 *
4367 * @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar SHA-256 or MD5 hash,
4368 * user email, WP_User object, WP_Post object, or WP_Comment object.
4369 * @param array $args {
4370 * Optional. Arguments to use instead of the default arguments.
4371 *
4372 * @type int $size Height and width of the avatar in pixels. Default 96.
4373 * @type int $height Display height of the avatar in pixels. Defaults to $size.
4374 * @type int $width Display width of the avatar in pixels. Defaults to $size.
4375 * @type string $default URL for the default image or a default type. Accepts:
4376 * - '404' (return a 404 instead of a default image)
4377 * - 'retro' (a 8-bit arcade-style pixelated face)
4378 * - 'robohash' (a robot)
4379 * - 'monsterid' (a monster)
4380 * - 'wavatar' (a cartoon face)
4381 * - 'identicon' (the "quilt", a geometric pattern)
4382 * - 'initials' (initials based avatar with background color)
4383 * - 'color' (generated background color)
4384 * - 'mystery', 'mm', or 'mysteryman' (The Oyster Man)
4385 * - 'blank' (transparent GIF)
4386 * - 'gravatar_default' (the Gravatar logo)
4387 * Default is the value of the 'avatar_default' option,
4388 * with a fallback of 'mystery'.
4389 * @type bool $force_default Whether to always show the default image, never the Gravatar.
4390 * Default false.
4391 * @type string $rating What rating to display avatars up to. Accepts:
4392 * - 'G' (suitable for all audiences)
4393 * - 'PG' (possibly offensive, usually for audiences 13 and above)
4394 * - 'R' (intended for adult audiences above 17)
4395 * - 'X' (even more mature than above)
4396 * Default is the value of the 'avatar_rating' option.
4397 * @type string $scheme URL scheme to use. See set_url_scheme() for accepted values.
4398 * For Gravatars this setting is ignored and HTTPS is used to avoid
4399 * unnecessary redirects. The setting is retained for systems using
4400 * the {@see 'pre_get_avatar_data'} filter to customize avatars.
4401 * Default null.
4402 * @type array $processed_args When the function returns, the value will be the processed/sanitized $args
4403 * plus a "found_avatar" guess. Pass as a reference. Default null.
4404 * @type string $extra_attr HTML attributes to insert in the IMG element. Is not sanitized.
4405 * Default empty.
4406 * }
4407 * @return array {
4408 * Along with the arguments passed in `$args`, this will contain a couple of extra arguments.
4409 *
4410 * @type bool $found_avatar True if an avatar was found for this user,
4411 * false or not set if none was found.
4412 * @type string|false $url The URL of the avatar that was found, or false.
4413 * }
4414 */
4415function get_avatar_data( $id_or_email, $args = null ) {
4416 $args = wp_parse_args(
4417 $args,
4418 array(
4419 'size' => 96,
4420 'height' => null,
4421 'width' => null,
4422 'default' => get_option( 'avatar_default', 'mystery' ),
4423 'force_default' => false,
4424 'rating' => get_option( 'avatar_rating' ),
4425 'scheme' => null,
4426 'processed_args' => null, // If used, should be a reference.
4427 'extra_attr' => '',
4428 )
4429 );
4430
4431 if ( is_numeric( $args['size'] ) ) {
4432 $args['size'] = absint( $args['size'] );
4433 if ( ! $args['size'] ) {
4434 $args['size'] = 96;
4435 }
4436 } else {
4437 $args['size'] = 96;
4438 }
4439
4440 if ( is_numeric( $args['height'] ) ) {
4441 $args['height'] = absint( $args['height'] );
4442 if ( ! $args['height'] ) {
4443 $args['height'] = $args['size'];
4444 }
4445 } else {
4446 $args['height'] = $args['size'];
4447 }
4448
4449 if ( is_numeric( $args['width'] ) ) {
4450 $args['width'] = absint( $args['width'] );
4451 if ( ! $args['width'] ) {
4452 $args['width'] = $args['size'];
4453 }
4454 } else {
4455 $args['width'] = $args['size'];
4456 }
4457
4458 if ( empty( $args['default'] ) ) {
4459 $args['default'] = get_option( 'avatar_default', 'mystery' );
4460 }
4461
4462 switch ( $args['default'] ) {
4463 case 'mm':
4464 case 'mystery':
4465 case 'mysteryman':
4466 $args['default'] = 'mm';
4467 break;
4468 case 'gravatar_default':
4469 $args['default'] = false;
4470 break;
4471 }
4472
4473 $args['force_default'] = (bool) $args['force_default'];
4474
4475 $args['rating'] = strtolower( $args['rating'] );
4476
4477 $args['found_avatar'] = false;
4478
4479 /**
4480 * Filters whether to retrieve the avatar URL early.
4481 *
4482 * Passing a non-null value in the 'url' member of the return array will
4483 * effectively short circuit get_avatar_data(), passing the value through
4484 * the {@see 'get_avatar_data'} filter and returning early.
4485 *
4486 * @since 4.2.0
4487 *
4488 * @param array $args Arguments passed to get_avatar_data(), after processing.
4489 * @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar SHA-256 or MD5 hash,
4490 * user email, WP_User object, WP_Post object, or WP_Comment object.
4491 */
4492 $args = apply_filters( 'pre_get_avatar_data', $args, $id_or_email );
4493
4494 if ( isset( $args['url'] ) ) {
4495 /** This filter is documented in wp-includes/link-template.php */
4496 return apply_filters( 'get_avatar_data', $args, $id_or_email );
4497 }
4498
4499 $email_hash = '';
4500 $user = false;
4501 $email = false;
4502
4503 if ( is_object( $id_or_email ) && isset( $id_or_email->comment_ID ) ) {
4504 $id_or_email = get_comment( $id_or_email );
4505 }
4506
4507 // Process the user identifier.
4508 if ( is_numeric( $id_or_email ) ) {
4509 $user = get_user_by( 'id', absint( $id_or_email ) );
4510 } elseif ( is_string( $id_or_email ) ) {
4511 if ( str_contains( $id_or_email, '@sha256.gravatar.com' ) ) {
4512 // SHA-256 hash.
4513 list( $email_hash ) = explode( '@', $id_or_email );
4514 } elseif ( str_contains( $id_or_email, '@md5.gravatar.com' ) ) {
4515 // MD5 hash.
4516 list( $email_hash ) = explode( '@', $id_or_email );
4517 } else {
4518 // Email address.
4519 $email = $id_or_email;
4520 }
4521 } elseif ( $id_or_email instanceof WP_User ) {
4522 // User object.
4523 $user = $id_or_email;
4524 } elseif ( $id_or_email instanceof WP_Post ) {
4525 // Post object.
4526 $user = get_user_by( 'id', (int) $id_or_email->post_author );
4527 } elseif ( $id_or_email instanceof WP_Comment ) {
4528 if ( ! is_avatar_comment_type( get_comment_type( $id_or_email ) ) ) {
4529 $args['url'] = false;
4530 /** This filter is documented in wp-includes/link-template.php */
4531 return apply_filters( 'get_avatar_data', $args, $id_or_email );
4532 }
4533
4534 if ( ! empty( $id_or_email->user_id ) ) {
4535 $user = get_user_by( 'id', (int) $id_or_email->user_id );
4536 }
4537 if ( ( ! $user || is_wp_error( $user ) ) && ! empty( $id_or_email->comment_author_email ) ) {
4538 $email = $id_or_email->comment_author_email;
4539 }
4540 }
4541
4542 if ( ! $email_hash ) {
4543 if ( $user ) {
4544 $email = $user->user_email;
4545 }
4546
4547 if ( $email ) {
4548 $email_hash = hash( 'sha256', strtolower( trim( $email ) ) );
4549 }
4550 }
4551
4552 if ( $email_hash ) {
4553 $args['found_avatar'] = true;
4554 }
4555
4556 $url_args = array(
4557 's' => $args['size'],
4558 'd' => $args['default'],
4559 'f' => $args['force_default'] ? 'y' : false,
4560 'r' => $args['rating'],
4561 );
4562
4563 // Handle additional parameters for the 'initials' avatar type.
4564 if ( 'initials' === $args['default'] ) {
4565 $name = '';
4566
4567 if ( $user ) {
4568 if ( '' !== $user->display_name ) {
4569 $name = $user->display_name;
4570 } elseif ( '' !== $user->first_name && '' !== $user->last_name ) {
4571 $name = sprintf(
4572 /* translators: 1: User's first name, 2: Last name. */
4573 _x( '%1$s %2$s', 'Display name based on first name and last name' ),
4574 $user->first_name,
4575 $user->last_name
4576 );
4577 } else {
4578 $name = $user->user_login;
4579 }
4580 } elseif ( $id_or_email instanceof WP_Comment ) {
4581 $name = $id_or_email->comment_author;
4582 } elseif ( is_string( $id_or_email ) && false !== strpos( $id_or_email, '@' ) ) {
4583 $name = str_replace( array( '.', '_', '-' ), ' ', substr( $id_or_email, 0, strpos( $id_or_email, '@' ) ) );
4584 }
4585
4586 if ( '' !== $name ) {
4587 if ( ! str_contains( $name, ' ' ) || preg_match( '/\p{Han}|\p{Hiragana}|\p{Katakana}|\p{Hangul}/u', $name ) ) {
4588 $initials = mb_substr( $name, 0, min( 2, mb_strlen( $name, 'UTF-8' ) ), 'UTF-8' );
4589 } else {
4590 $first = mb_substr( $name, 0, 1, 'UTF-8' );
4591 $last = mb_substr( $name, strrpos( $name, ' ' ) + 1, 1, 'UTF-8' );
4592 $initials = $first . $last;
4593 }
4594
4595 $url_args['initials'] = $initials;
4596 }
4597 }
4598
4599 /*
4600 * Gravatars are always served over HTTPS.
4601 *
4602 * The Gravatar website redirects HTTP requests to HTTPS URLs so always
4603 * use the HTTPS scheme to avoid unnecessary redirects.
4604 */
4605 $url = 'https://secure.gravatar.com/avatar/' . $email_hash;
4606
4607 $url = add_query_arg(
4608 rawurlencode_deep( array_filter( $url_args ) ),
4609 $url
4610 );
4611
4612 /**
4613 * Filters the avatar URL.
4614 *
4615 * @since 4.2.0
4616 *
4617 * @param string $url The URL of the avatar.
4618 * @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar SHA-256 or MD5 hash,
4619 * user email, WP_User object, WP_Post object, or WP_Comment object.
4620 * @param array $args Arguments passed to get_avatar_data(), after processing.
4621 */
4622 $args['url'] = apply_filters( 'get_avatar_url', $url, $id_or_email, $args );
4623
4624 /**
4625 * Filters the avatar data.
4626 *
4627 * @since 4.2.0
4628 *
4629 * @param array $args Arguments passed to get_avatar_data(), after processing.
4630 * @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar SHA-256 or MD5 hash,
4631 * user email, WP_User object, WP_Post object, or WP_Comment object.
4632 */
4633 return apply_filters( 'get_avatar_data', $args, $id_or_email );
4634}
4635
4636/**
4637 * Retrieves the URL of a file in the theme.
4638 *
4639 * Searches in the stylesheet directory before the template directory so themes
4640 * which inherit from a parent theme can just override one file.
4641 *
4642 * @since 4.7.0
4643 *
4644 * @param string $file Optional. File to search for in the stylesheet directory.
4645 * @return string The URL of the file.
4646 */
4647function get_theme_file_uri( $file = '' ) {
4648 $file = ltrim( $file, '/' );
4649
4650 $stylesheet_directory = get_stylesheet_directory();
4651
4652 if ( empty( $file ) ) {
4653 $url = get_stylesheet_directory_uri();
4654 } elseif ( get_template_directory() !== $stylesheet_directory && file_exists( $stylesheet_directory . '/' . $file ) ) {
4655 $url = get_stylesheet_directory_uri() . '/' . $file;
4656 } else {
4657 $url = get_template_directory_uri() . '/' . $file;
4658 }
4659
4660 /**
4661 * Filters the URL to a file in the theme.
4662 *
4663 * @since 4.7.0
4664 *
4665 * @param string $url The file URL.
4666 * @param string $file The requested file to search for.
4667 */
4668 return apply_filters( 'theme_file_uri', $url, $file );
4669}
4670
4671/**
4672 * Retrieves the URL of a file in the parent theme.
4673 *
4674 * @since 4.7.0
4675 *
4676 * @param string $file Optional. File to return the URL for in the template directory.
4677 * @return string The URL of the file.
4678 */
4679function get_parent_theme_file_uri( $file = '' ) {
4680 $file = ltrim( $file, '/' );
4681
4682 if ( empty( $file ) ) {
4683 $url = get_template_directory_uri();
4684 } else {
4685 $url = get_template_directory_uri() . '/' . $file;
4686 }
4687
4688 /**
4689 * Filters the URL to a file in the parent theme.
4690 *
4691 * @since 4.7.0
4692 *
4693 * @param string $url The file URL.
4694 * @param string $file The requested file to search for.
4695 */
4696 return apply_filters( 'parent_theme_file_uri', $url, $file );
4697}
4698
4699/**
4700 * Retrieves the path of a file in the theme.
4701 *
4702 * Searches in the stylesheet directory before the template directory so themes
4703 * which inherit from a parent theme can just override one file.
4704 *
4705 * @since 4.7.0
4706 *
4707 * @param string $file Optional. File to search for in the stylesheet directory.
4708 * @return string The path of the file.
4709 */
4710function get_theme_file_path( $file = '' ) {
4711 $file = ltrim( $file, '/' );
4712
4713 $stylesheet_directory = get_stylesheet_directory();
4714 $template_directory = get_template_directory();
4715
4716 if ( empty( $file ) ) {
4717 $path = $stylesheet_directory;
4718 } elseif ( $stylesheet_directory !== $template_directory && file_exists( $stylesheet_directory . '/' . $file ) ) {
4719 $path = $stylesheet_directory . '/' . $file;
4720 } else {
4721 $path = $template_directory . '/' . $file;
4722 }
4723
4724 /**
4725 * Filters the path to a file in the theme.
4726 *
4727 * @since 4.7.0
4728 *
4729 * @param string $path The file path.
4730 * @param string $file The requested file to search for.
4731 */
4732 return apply_filters( 'theme_file_path', $path, $file );
4733}
4734
4735/**
4736 * Retrieves the path of a file in the parent theme.
4737 *
4738 * @since 4.7.0
4739 *
4740 * @param string $file Optional. File to return the path for in the template directory.
4741 * @return string The path of the file.
4742 */
4743function get_parent_theme_file_path( $file = '' ) {
4744 $file = ltrim( $file, '/' );
4745
4746 if ( empty( $file ) ) {
4747 $path = get_template_directory();
4748 } else {
4749 $path = get_template_directory() . '/' . $file;
4750 }
4751
4752 /**
4753 * Filters the path to a file in the parent theme.
4754 *
4755 * @since 4.7.0
4756 *
4757 * @param string $path The file path.
4758 * @param string $file The requested file to search for.
4759 */
4760 return apply_filters( 'parent_theme_file_path', $path, $file );
4761}
4762
4763/**
4764 * Retrieves the URL to the privacy policy page.
4765 *
4766 * @since 4.9.6
4767 *
4768 * @return string The URL to the privacy policy page. Empty string if it doesn't exist.
4769 */
4770function get_privacy_policy_url() {
4771 $url = '';
4772 $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
4773
4774 if ( ! empty( $policy_page_id ) && get_post_status( $policy_page_id ) === 'publish' ) {
4775 $url = (string) get_permalink( $policy_page_id );
4776 }
4777
4778 /**
4779 * Filters the URL of the privacy policy page.
4780 *
4781 * @since 4.9.6
4782 *
4783 * @param string $url The URL to the privacy policy page. Empty string
4784 * if it doesn't exist.
4785 * @param int $policy_page_id The ID of privacy policy page.
4786 */
4787 return apply_filters( 'privacy_policy_url', $url, $policy_page_id );
4788}
4789
4790/**
4791 * Displays the privacy policy link with formatting, when applicable.
4792 *
4793 * @since 4.9.6
4794 *
4795 * @param string $before Optional. Display before privacy policy link. Default empty.
4796 * @param string $after Optional. Display after privacy policy link. Default empty.
4797 */
4798function the_privacy_policy_link( $before = '', $after = '' ) {
4799 echo get_the_privacy_policy_link( $before, $after );
4800}
4801
4802/**
4803 * Returns the privacy policy link with formatting, when applicable.
4804 *
4805 * @since 4.9.6
4806 * @since 6.2.0 Added 'privacy-policy' rel attribute.
4807 *
4808 * @param string $before Optional. Display before privacy policy link. Default empty.
4809 * @param string $after Optional. Display after privacy policy link. Default empty.
4810 * @return string Markup for the link and surrounding elements. Empty string if it
4811 * doesn't exist.
4812 */
4813function get_the_privacy_policy_link( $before = '', $after = '' ) {
4814 $link = '';
4815 $privacy_policy_url = get_privacy_policy_url();
4816 $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
4817 $page_title = ( $policy_page_id ) ? get_the_title( $policy_page_id ) : '';
4818
4819 if ( $privacy_policy_url && $page_title ) {
4820 $link = sprintf(
4821 '<a class="privacy-policy-link" href="%s" rel="privacy-policy">%s</a>',
4822 esc_url( $privacy_policy_url ),
4823 esc_html( $page_title )
4824 );
4825 }
4826
4827 /**
4828 * Filters the privacy policy link.
4829 *
4830 * @since 4.9.6
4831 *
4832 * @param string $link The privacy policy link. Empty string if it
4833 * doesn't exist.
4834 * @param string $privacy_policy_url The URL of the privacy policy. Empty string
4835 * if it doesn't exist.
4836 */
4837 $link = apply_filters( 'the_privacy_policy_link', $link, $privacy_policy_url );
4838
4839 if ( $link ) {
4840 return $before . $link . $after;
4841 }
4842
4843 return '';
4844}
4845
4846/**
4847 * Returns an array of URL hosts which are considered to be internal hosts.
4848 *
4849 * By default the list of internal hosts is comprised of the host name of
4850 * the site's home_url() (as parsed by wp_parse_url()).
4851 *
4852 * This list is used when determining if a specified URL is a link to a page on
4853 * the site itself or a link offsite (to an external host). This is used, for
4854 * example, when determining if the "nofollow" attribute should be applied to a
4855 * link.
4856 *
4857 * @see wp_is_internal_link
4858 *
4859 * @since 6.2.0
4860 *
4861 * @return string[] An array of URL hosts.
4862 */
4863function wp_internal_hosts() {
4864 static $internal_hosts;
4865
4866 if ( empty( $internal_hosts ) ) {
4867 /**
4868 * Filters the array of URL hosts which are considered internal.
4869 *
4870 * @since 6.2.0
4871 *
4872 * @param string[] $internal_hosts An array of internal URL hostnames.
4873 */
4874 $internal_hosts = apply_filters(
4875 'wp_internal_hosts',
4876 array(
4877 wp_parse_url( home_url(), PHP_URL_HOST ),
4878 )
4879 );
4880 $internal_hosts = array_unique(
4881 array_map( 'strtolower', (array) $internal_hosts )
4882 );
4883 }
4884
4885 return $internal_hosts;
4886}
4887
4888/**
4889 * Determines whether or not the specified URL is of a host included in the internal hosts list.
4890 *
4891 * @see wp_internal_hosts()
4892 *
4893 * @since 6.2.0
4894 *
4895 * @param string $link The URL to test.
4896 * @return bool Returns true for internal URLs and false for all other URLs.
4897 */
4898function wp_is_internal_link( $link ) {
4899 $link = strtolower( $link );
4900 if ( in_array( wp_parse_url( $link, PHP_URL_SCHEME ), wp_allowed_protocols(), true ) ) {
4901 return in_array( wp_parse_url( $link, PHP_URL_HOST ), wp_internal_hosts(), true );
4902 }
4903 return false;
4904}
4905
Ui Ux Design – Teachers Night Out

Get in Touch

© 2024 Teachers Night Out. All Rights Reserved.