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 https://cardgames4educators.com Wed, 16 Oct 2024 22:24:18 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://cardgames4educators.com/wp-content/uploads/2024/06/cropped-Card-4-Educators-logo-32x32.png Ui Ux Design – Teachers Night Out https://cardgames4educators.com 32 32 Masters In English How English Speaker https://cardgames4educators.com/masters-in-english-how-english-speaker/ https://cardgames4educators.com/masters-in-english-how-english-speaker/#comments Mon, 27 May 2024 08:54:45 +0000 https://themexriver.com/wp/kadu/?p=1

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1