PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.5
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.5
16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 All 506 releases
jetpack / jetpack_vendor / automattic / jetpack-seo / src / class-breadcrumb-schema-node.php

class-breadcrumb-schema-node.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.5, at jetpack_vendor/automattic/jetpack-seo/src/class-breadcrumb-schema-node.php

304 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * BreadcrumbList Schema.org node builder.
4 *
5 * @package automattic/jetpack-seo-package
6 */
7
8 namespace Automattic\Jetpack\SEO;
9
10 /**
11 * Builds a BreadcrumbList for the current public request.
12 */
13 class Breadcrumb_Schema_Node {
14
15 /**
16 * Build the breadcrumb node for the current request.
17 *
18 * @return array|null
19 */
20 public static function build() {
21 if ( is_front_page() || is_search() || is_404() ) {
22 return null;
23 }
24
25 $items = array(
26 array(
27 'name' => __( 'Home', 'jetpack-seo' ),
28 'item' => home_url( '/' ),
29 ),
30 );
31
32 if ( is_singular() ) {
33 $items = self::singular_items( $items );
34 } elseif ( is_category() || is_tag() || is_tax() ) {
35 $items = self::taxonomy_items( $items );
36 } elseif ( is_home() ) {
37 $items = self::blog_items( $items );
38 } elseif ( is_post_type_archive() ) {
39 $items = self::post_type_archive_items( $items );
40 } elseif ( is_author() ) {
41 $items = self::author_items( $items );
42 } elseif ( is_date() ) {
43 $items = self::date_items( $items );
44 } else {
45 return null;
46 }
47
48 if ( null === $items || count( $items ) < 2 ) {
49 return null;
50 }
51
52 $list_items = array();
53 $last = count( $items ) - 1;
54 foreach ( $items as $index => $item ) {
55 $name = self::text( $item['name'] ?? '' );
56 if ( '' === $name ) {
57 return null;
58 }
59
60 $list_item = array(
61 '@type' => 'ListItem',
62 'position' => $index + 1,
63 'name' => $name,
64 );
65 if ( $index < $last && ! empty( $item['item'] ) ) {
66 $list_item['item'] = $item['item'];
67 }
68 $list_items[] = $list_item;
69 }
70
71 return array(
72 '@type' => 'BreadcrumbList',
73 'itemListElement' => $list_items,
74 );
75 }
76
77 /**
78 * Add breadcrumbs for a singular post.
79 *
80 * @param array $items Existing items.
81 * @return array|null
82 */
83 private static function singular_items( array $items ) {
84 $post = get_queried_object();
85 if ( ! $post instanceof \WP_Post || 'publish' !== $post->post_status ) {
86 return null;
87 }
88
89 $post_type = get_post_type_object( $post->post_type );
90 if ( ! $post_type || ! is_post_type_viewable( $post_type ) ) {
91 return null;
92 }
93
94 if ( 'post' === $post->post_type ) {
95 self::add_posts_page( $items );
96 } elseif ( 'page' !== $post->post_type && $post_type->has_archive ) {
97 self::add_item( $items, $post_type->labels->name, get_post_type_archive_link( $post->post_type ) );
98 }
99
100 $front_page_id = 'page' === get_option( 'show_on_front' ) ? (int) get_option( 'page_on_front' ) : 0;
101 if ( is_post_type_hierarchical( $post->post_type ) ) {
102 foreach ( array_reverse( get_post_ancestors( $post ) ) as $ancestor_id ) {
103 $ancestor = get_post( $ancestor_id );
104 if ( $ancestor instanceof \WP_Post && $front_page_id === $ancestor->ID ) {
105 continue;
106 }
107 if ( $ancestor instanceof \WP_Post && 'publish' === $ancestor->post_status ) {
108 self::add_item( $items, $ancestor->post_title, get_permalink( $ancestor ) );
109 }
110 }
111 }
112
113 return self::add_current( $items, $post->post_title );
114 }
115
116 /**
117 * Add breadcrumbs for a taxonomy archive.
118 *
119 * @param array $items Existing items.
120 * @return array|null
121 */
122 private static function taxonomy_items( array $items ) {
123 $term = get_queried_object();
124 if ( ! $term instanceof \WP_Term ) {
125 return null;
126 }
127
128 $taxonomy = get_taxonomy( $term->taxonomy );
129 if ( ! $taxonomy || ! $taxonomy->public ) {
130 return null;
131 }
132
133 if ( $taxonomy->hierarchical ) {
134 foreach ( array_reverse( get_ancestors( $term->term_id, $term->taxonomy, 'taxonomy' ) ) as $ancestor_id ) {
135 $ancestor = get_term( $ancestor_id, $term->taxonomy );
136 if ( ! $ancestor instanceof \WP_Term ) {
137 continue;
138 }
139
140 $link = get_term_link( $ancestor );
141 if ( ! is_wp_error( $link ) ) {
142 self::add_item( $items, $ancestor->name, $link );
143 }
144 }
145 }
146
147 return self::add_current( $items, $term->name );
148 }
149
150 /**
151 * Add the configured posts page as the current blog archive.
152 *
153 * @param array $items Existing items.
154 * @return array|null
155 */
156 private static function blog_items( array $items ) {
157 $page = self::posts_page();
158 return null === $page ? null : self::add_current( $items, $page->post_title );
159 }
160
161 /**
162 * Add a custom post type archive.
163 *
164 * @param array $items Existing items.
165 * @return array|null
166 */
167 private static function post_type_archive_items( array $items ) {
168 $post_type = get_queried_object();
169 if ( ! $post_type instanceof \WP_Post_Type || ! is_post_type_viewable( $post_type ) || ! $post_type->has_archive ) {
170 return null;
171 }
172
173 return self::add_current( $items, $post_type->labels->name );
174 }
175
176 /**
177 * Add an author archive, optionally under the posts page.
178 *
179 * @param array $items Existing items.
180 * @return array|null
181 */
182 private static function author_items( array $items ) {
183 $author = get_queried_object();
184 if ( ! $author instanceof \WP_User || ! $author->exists() ) {
185 return null;
186 }
187
188 self::add_posts_page( $items );
189 return self::add_current( $items, $author->display_name );
190 }
191
192 /**
193 * Add a year, month, or day archive under the posts page.
194 *
195 * @param array $items Existing items.
196 * @return array|null
197 */
198 private static function date_items( array $items ) {
199 global $wp_locale;
200
201 $year = (int) get_query_var( 'year' );
202 $month = (int) get_query_var( 'monthnum' );
203 $day = (int) get_query_var( 'day' );
204
205 if ( $year < 1 || $year > 9999 ) {
206 return null;
207 }
208
209 self::add_posts_page( $items );
210 if ( is_year() ) {
211 return self::add_current( $items, (string) $year );
212 }
213
214 if ( $month < 1 || $month > 12 ) {
215 return null;
216 }
217
218 self::add_item( $items, (string) $year, get_year_link( $year ) );
219 $month_name = $wp_locale->get_month( $month );
220 if ( is_month() ) {
221 return self::add_current( $items, $month_name );
222 }
223
224 if ( ! is_day() || ! checkdate( $month, $day, $year ) ) {
225 return null;
226 }
227
228 self::add_item( $items, $month_name, get_month_link( $year, $month ) );
229 return self::add_current( $items, (string) $day );
230 }
231
232 /**
233 * Add the configured, distinct posts page as an ancestor.
234 *
235 * @param array $items Items passed by reference.
236 * @return void
237 */
238 private static function add_posts_page( array &$items ) {
239 $page = self::posts_page();
240 if ( null !== $page && (int) get_option( 'page_on_front' ) !== $page->ID ) {
241 self::add_item( $items, $page->post_title, get_permalink( $page ) );
242 }
243 }
244
245 /**
246 * Get the valid configured posts page.
247 *
248 * @return \WP_Post|null
249 */
250 private static function posts_page() {
251 if ( 'page' !== get_option( 'show_on_front' ) ) {
252 return null;
253 }
254
255 $page = get_post( (int) get_option( 'page_for_posts' ) );
256 return $page instanceof \WP_Post && 'publish' === $page->post_status && '' !== self::text( $page->post_title ) ? $page : null;
257 }
258
259 /**
260 * Add a navigable ancestor when its name and URL are valid.
261 *
262 * @param array $items Items passed by reference.
263 * @param mixed $name Item name.
264 * @param string $url Item URL.
265 * @return void
266 */
267 private static function add_item( array &$items, $name, $url ) {
268 $name = self::text( $name );
269 if ( '' !== $name && is_string( $url ) && '' !== $url ) {
270 $items[] = array(
271 'name' => $name,
272 'item' => $url,
273 );
274 }
275 }
276
277 /**
278 * Add the non-linked current item.
279 *
280 * @param array $items Existing items.
281 * @param mixed $name Current item name.
282 * @return array|null
283 */
284 private static function add_current( array $items, $name ) {
285 $name = self::text( $name );
286 if ( '' === $name ) {
287 return null;
288 }
289
290 $items[] = array( 'name' => $name );
291 return $items;
292 }
293
294 /**
295 * Normalize a breadcrumb name to trimmed plain text.
296 *
297 * @param mixed $value Raw value.
298 * @return string
299 */
300 private static function text( $value ) {
301 return is_string( $value ) ? trim( wp_strip_all_tags( $value ) ) : '';
302 }
303 }
304