• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Yoast / wordpress-seo / 7004843404

27 Nov 2023 11:48AM UTC coverage: 49.206% (-0.03%) from 49.232%
7004843404

push

github

web-flow
Merge pull request #20858 from Yoast/improve-copy-in-the-ftc-57

15305 of 31104 relevant lines covered (49.21%)

4.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.98
/src/generators/breadcrumbs-generator.php
1
<?php
2

3
namespace Yoast\WP\SEO\Generators;
4

5
use Yoast\WP\SEO\Context\Meta_Tags_Context;
6
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
7
use Yoast\WP\SEO\Helpers\Options_Helper;
8
use Yoast\WP\SEO\Helpers\Pagination_Helper;
9
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
10
use Yoast\WP\SEO\Helpers\Url_Helper;
11
use Yoast\WP\SEO\Models\Indexable;
12
use Yoast\WP\SEO\Repositories\Indexable_Repository;
13

14
/**
15
 * Represents the generator class for the breadcrumbs.
16
 */
17
class Breadcrumbs_Generator implements Generator_Interface {
18

19
        /**
20
         * The indexable repository.
21
         *
22
         * @var Indexable_Repository
23
         */
24
        private $repository;
25

26
        /**
27
         * The options helper.
28
         *
29
         * @var Options_Helper
30
         */
31
        private $options;
32

33
        /**
34
         * The current page helper.
35
         *
36
         * @var Current_Page_Helper
37
         */
38
        private $current_page_helper;
39

40
        /**
41
         * The post type helper.
42
         *
43
         * @var Post_Type_Helper
44
         */
45
        private $post_type_helper;
46

47
        /**
48
         * The URL helper.
49
         *
50
         * @var Url_Helper
51
         */
52
        private $url_helper;
53

54
        /**
55
         * The pagination helper.
56
         *
57
         * @var Pagination_Helper
58
         */
59
        private $pagination_helper;
60

61
        /**
62
         * Breadcrumbs_Generator constructor.
63
         *
64
         * @param Indexable_Repository $repository          The repository.
65
         * @param Options_Helper       $options             The options helper.
66
         * @param Current_Page_Helper  $current_page_helper The current page helper.
67
         * @param Post_Type_Helper     $post_type_helper    The post type helper.
68
         * @param Url_Helper           $url_helper          The URL helper.
69
         * @param Pagination_Helper    $pagination_helper   The pagination helper.
70
         */
71
        public function __construct(
18✔
72
                Indexable_Repository $repository,
73
                Options_Helper $options,
74
                Current_Page_Helper $current_page_helper,
75
                Post_Type_Helper $post_type_helper,
76
                Url_Helper $url_helper,
77
                Pagination_Helper $pagination_helper
78
        ) {
9✔
79
                $this->repository          = $repository;
18✔
80
                $this->options             = $options;
18✔
81
                $this->current_page_helper = $current_page_helper;
18✔
82
                $this->post_type_helper    = $post_type_helper;
18✔
83
                $this->url_helper          = $url_helper;
18✔
84
                $this->pagination_helper   = $pagination_helper;
18✔
85
        }
9✔
86

87
        /**
88
         * Generates the breadcrumbs.
89
         *
90
         * @param Meta_Tags_Context $context The meta tags context.
91
         *
92
         * @return array An array of associative arrays that each have a 'text' and a 'url'.
93
         */
94
        public function generate( Meta_Tags_Context $context ) {
36✔
95
                $static_ancestors = [];
36✔
96
                $breadcrumbs_home = $this->options->get( 'breadcrumbs-home' );
36✔
97
                if ( $breadcrumbs_home !== '' && ! \in_array( $this->current_page_helper->get_page_type(), [ 'Home_Page', 'Static_Home_Page' ], true ) ) {
36✔
98
                        $front_page_id = $this->current_page_helper->get_front_page_id();
16✔
99
                        if ( $front_page_id === 0 ) {
16✔
100
                                $home_page_ancestor = $this->repository->find_for_home_page();
12✔
101
                                if ( \is_a( $home_page_ancestor, Indexable::class ) ) {
12✔
102
                                        $static_ancestors[] = $home_page_ancestor;
12✔
103
                                }
104
                        }
105
                        else {
106
                                $static_ancestor = $this->repository->find_by_id_and_type( $front_page_id, 'post' );
4✔
107
                                if ( \is_a( $static_ancestor, Indexable::class ) && $static_ancestor->post_status !== 'unindexed' ) {
4✔
108
                                        $static_ancestors[] = $static_ancestor;
4✔
109
                                }
110
                        }
111
                }
112
                $page_for_posts = \get_option( 'page_for_posts' );
36✔
113
                if ( $this->should_have_blog_crumb( $page_for_posts, $context ) ) {
36✔
114
                        $static_ancestor = $this->repository->find_by_id_and_type( $page_for_posts, 'post' );
4✔
115
                        if ( \is_a( $static_ancestor, Indexable::class ) && $static_ancestor->post_status !== 'unindexed' ) {
4✔
116
                                $static_ancestors[] = $static_ancestor;
4✔
117
                        }
118
                }
119
                if (
120
                        $context->indexable->object_type === 'post'
36✔
121
                        && $context->indexable->object_sub_type !== 'post'
36✔
122
                        && $context->indexable->object_sub_type !== 'page'
36✔
123
                        && $this->post_type_helper->has_archive( $context->indexable->object_sub_type )
36✔
124
                ) {
125
                        $static_ancestor = $this->repository->find_for_post_type_archive( $context->indexable->object_sub_type );
2✔
126
                        if ( \is_a( $static_ancestor, Indexable::class ) ) {
2✔
127
                                $static_ancestors[] = $static_ancestor;
2✔
128
                        }
129
                }
130
                if ( $context->indexable->object_type === 'term' ) {
36✔
131
                        $parent = $this->get_taxonomy_post_type_parent( $context->indexable->object_sub_type );
×
132
                        if ( $parent && $parent !== 'post' && $this->post_type_helper->has_archive( $parent ) ) {
×
133
                                $static_ancestor = $this->repository->find_for_post_type_archive( $parent );
×
134
                                if ( \is_a( $static_ancestor, Indexable::class ) ) {
×
135
                                        $static_ancestors[] = $static_ancestor;
×
136
                                }
137
                        }
138
                }
139

140
                // Get all ancestors of the indexable and append itself to get all indexables in the full crumb.
141
                $indexables   = $this->repository->get_ancestors( $context->indexable );
36✔
142
                $indexables[] = $context->indexable;
36✔
143

144
                if ( ! empty( $static_ancestors ) ) {
36✔
145
                        \array_unshift( $indexables, ...$static_ancestors );
16✔
146
                }
147

148
                $indexables = \apply_filters( 'wpseo_breadcrumb_indexables', $indexables, $context );
36✔
149
                $indexables = \is_array( $indexables ) ? $indexables : [];
36✔
150
                $indexables = \array_filter(
36✔
151
                        $indexables,
36✔
152
                        static function ( $indexable ) {
18✔
153
                                return \is_a( $indexable, Indexable::class );
36✔
154
                        }
18✔
155
                );
18✔
156

157
                $callback = function ( Indexable $ancestor ) {
18✔
158
                        $crumb = [
36✔
159
                                'url'  => $ancestor->permalink,
36✔
160
                                'text' => $ancestor->breadcrumb_title,
18✔
161
                        ];
36✔
162
                        switch ( $ancestor->object_type ) {
36✔
163
                                case 'post':
18✔
164
                                        $crumb = $this->get_post_crumb( $crumb, $ancestor );
18✔
165
                                        break;
36✔
166
                                case 'post-type-archive':
18✔
167
                                        $crumb = $this->get_post_type_archive_crumb( $crumb, $ancestor );
18✔
168
                                        break;
18✔
169
                                case 'term':
×
170
                                        $crumb = $this->get_term_crumb( $crumb, $ancestor );
×
171
                                        break;
18✔
172
                                case 'system-page':
×
173
                                        $crumb = $this->get_system_page_crumb( $crumb, $ancestor );
×
174
                                        break;
18✔
175
                                case 'user':
×
176
                                        $crumb = $this->get_user_crumb( $crumb, $ancestor );
×
177
                                        break;
18✔
178
                                case 'date-archive':
18✔
179
                                        $crumb = $this->get_date_archive_crumb( $crumb );
18✔
180
                                        break;
181
                        }
18✔
182
                        return $crumb;
18✔
183
                };
36✔
184
                $crumbs   = \array_map( $callback, $indexables );
185

186
                if ( $breadcrumbs_home !== '' ) {
18✔
187
                        $crumbs[0]['text'] = $breadcrumbs_home;
188
                }
189

190
                $crumbs = $this->add_paged_crumb( $crumbs, $context->indexable );
191

192
                /**
193
                 * Filter: 'wpseo_breadcrumb_links' - Allow the developer to filter the Yoast SEO breadcrumb links, add to them, change order, etc.
194
                 *
195
                 * @param array $crumbs The crumbs array.
196
                 */
197
                $filtered_crumbs = \apply_filters( 'wpseo_breadcrumb_links', $crumbs );
198

199
                // Basic check to make sure the filtered crumbs are in an array.
200
                if ( ! \is_array( $filtered_crumbs ) ) {
×
201
                        \_doing_it_wrong(
×
202
                                'Filter: \'wpseo_breadcrumb_links\'',
×
203
                                'The `wpseo_breadcrumb_links` filter should return a multi-dimensional array.',
×
204
                                'YoastSEO v20.0'
205
                        );
206
                }
207
                else {
18✔
208
                        $crumbs = $filtered_crumbs;
209
                }
210

211
                $filter_callback = static function( $link_info, $index ) use ( $crumbs ) {
18✔
212
                        /**
213
                         * Filter: 'wpseo_breadcrumb_single_link_info' - Allow developers to filter the Yoast SEO Breadcrumb link information.
214
                         *
215
                         * @api array $link_info The breadcrumb link information.
216
                         *
217
                         * @param int $index The index of the breadcrumb in the list.
218
                         * @param array $crumbs The complete list of breadcrumbs.
219
                         */
220
                        return \apply_filters( 'wpseo_breadcrumb_single_link_info', $link_info, $index, $crumbs );
18✔
221
                };
36✔
222
                return \array_map( $filter_callback, $crumbs, \array_keys( $crumbs ) );
223
        }
224

225
        /**
226
         * Returns the modified post crumb.
227
         *
228
         * @param array     $crumb    The crumb.
229
         * @param Indexable $ancestor The indexable.
230
         *
231
         * @return array The crumb.
232
         */
233
        private function get_post_crumb( $crumb, $ancestor ) {
234
                $crumb['id'] = $ancestor->object_id;
235

236
                return $crumb;
237
        }
238

239
        /**
240
         * Returns the modified post type crumb.
241
         *
242
         * @param array     $crumb    The crumb.
243
         * @param Indexable $ancestor The indexable.
244
         *
245
         * @return array The crumb.
246
         */
247
        private function get_post_type_archive_crumb( $crumb, $ancestor ) {
248
                $crumb['ptarchive'] = $ancestor->object_sub_type;
249

250
                return $crumb;
251
        }
252

253
        /**
254
         * Returns the modified term crumb.
255
         *
256
         * @param array     $crumb    The crumb.
257
         * @param Indexable $ancestor The indexable.
258
         *
259
         * @return array The crumb.
260
         */
261
        private function get_term_crumb( $crumb, $ancestor ) {
×
262
                $crumb['term_id']  = $ancestor->object_id;
×
263
                $crumb['taxonomy'] = $ancestor->object_sub_type;
264

265
                return $crumb;
266
        }
267

268
        /**
269
         * Returns the modified system page crumb.
270
         *
271
         * @param array     $crumb    The crumb.
272
         * @param Indexable $ancestor The indexable.
273
         *
274
         * @return array The crumb.
275
         */
276
        private function get_system_page_crumb( $crumb, $ancestor ) {
×
277
                if ( $ancestor->object_sub_type === 'search-result' ) {
×
278
                        $crumb['text'] = $this->options->get( 'breadcrumbs-searchprefix' ) . ' ' . \esc_html( \get_search_query() );
×
279
                        $crumb['url']  = \get_search_link();
280
                }
281
                elseif ( $ancestor->object_sub_type === '404' ) {
×
282
                        $crumb['text'] = $this->options->get( 'breadcrumbs-404crumb' );
283
                }
284

285
                return $crumb;
286
        }
287

288
        /**
289
         * Returns the modified user crumb.
290
         *
291
         * @param array     $crumb    The crumb.
292
         * @param Indexable $ancestor The indexable.
293
         *
294
         * @return array The crumb.
295
         */
296
        private function get_user_crumb( $crumb, $ancestor ) {
×
297
                $display_name  = \get_the_author_meta( 'display_name', $ancestor->object_id );
×
298
                $crumb['text'] = $this->options->get( 'breadcrumbs-archiveprefix' ) . ' ' . $display_name;
299

300
                return $crumb;
301
        }
302

303
        /**
304
         * Returns the modified date archive crumb.
305
         *
306
         * @param array $crumb The crumb.
307
         *
308
         * @return array The crumb.
309
         */
310
        protected function get_date_archive_crumb( $crumb ) {
18✔
311
                $home_url = $this->url_helper->home();
18✔
312
                $prefix   = $this->options->get( 'breadcrumbs-archiveprefix' );
313

314
                if ( \is_day() ) {
6✔
315
                        $day           = \esc_html( \get_the_date() );
6✔
316
                        $crumb['url']  = $home_url . \get_the_date( 'Y/m/d' ) . '/';
6✔
317
                        $crumb['text'] = $prefix . ' ' . $day;
318
                }
6✔
319
                elseif ( \is_month() ) {
6✔
320
                        $month         = \esc_html( \trim( \single_month_title( ' ', false ) ) );
6✔
321
                        $crumb['url']  = $home_url . \get_the_date( 'Y/m' ) . '/';
6✔
322
                        $crumb['text'] = $prefix . ' ' . $month;
323
                }
3✔
324
                elseif ( \is_year() ) {
6✔
325
                        $year          = \get_the_date( 'Y' );
6✔
326
                        $crumb['url']  = $home_url . $year . '/';
6✔
327
                        $crumb['text'] = $prefix . ' ' . $year;
328
                }
329

330
                return $crumb;
331
        }
332

333
        /**
334
         * Returns whether or not a blog crumb should be added.
335
         *
336
         * @param int               $page_for_posts The page for posts ID.
337
         * @param Meta_Tags_Context $context        The meta tags context.
338
         *
339
         * @return bool Whether or not a blog crumb should be added.
340
         */
341
        protected function should_have_blog_crumb( $page_for_posts, $context ) {
8✔
342
                // When there is no page configured as blog page.
343
                if ( \get_option( 'show_on_front' ) !== 'page' || ! $page_for_posts ) {
6✔
344
                        return false;
345
                }
346

347
                if ( $context->indexable->object_type === 'term' ) {
×
348
                        $parent = $this->get_taxonomy_post_type_parent( $context->indexable->object_sub_type );
×
349
                        return $parent === 'post';
350
                }
351

352
                if ( $this->options->get( 'breadcrumbs-display-blog-page' ) !== true ) {
×
353
                        return false;
354
                }
355

356
                // When the current page is the home page, searchpage or isn't a singular post.
357
                if ( \is_home() || \is_search() || ! \is_singular( 'post' ) ) {
8✔
358
                        return false;
359
                }
360

361
                return true;
362
        }
363

364
        /**
365
         * Returns the post type parent of a given taxonomy.
366
         *
367
         * @param string $taxonomy The taxonomy.
368
         *
369
         * @return string|false The parent if it exists, false otherwise.
370
         */
371
        protected function get_taxonomy_post_type_parent( $taxonomy ) {
×
372
                $parent = $this->options->get( 'taxonomy-' . $taxonomy . '-ptparent' );
373

374
                if ( empty( $parent ) || (string) $parent === '0' ) {
×
375
                        return false;
376
                }
377

378
                return $parent;
379
        }
380

381
        /**
382
         * Adds a crumb for the current page, if we're on an archive page or paginated post.
383
         *
384
         * @param array     $crumbs            The array of breadcrumbs.
385
         * @param Indexable $current_indexable The current indexable.
386
         *
387
         * @return array The breadcrumbs.
388
         */
389
        protected function add_paged_crumb( array $crumbs, $current_indexable ) {
12✔
390
                $is_simple_page = $this->current_page_helper->is_simple_page();
391

392
                // If we're not on a paged page do nothing.
393
                if ( ! $is_simple_page && ! $this->current_page_helper->is_paged() ) {
6✔
394
                        return $crumbs;
395
                }
396

397
                // If we're not on a paginated post do nothing.
398
                if ( $is_simple_page && $current_indexable->number_of_pages === null ) {
×
399
                        return $crumbs;
400
                }
401

402
                $current_page_number = $this->pagination_helper->get_current_page_number();
12✔
403
                if ( $current_page_number <= 1 ) {
6✔
404
                        return $crumbs;
405
                }
406

407
                $crumbs[] = [
6✔
408
                        'text' => \sprintf(
3✔
409
                                /* translators: %s expands to the current page number */
410
                                \__( 'Page %s', 'wordpress-seo' ),
6✔
411
                                $current_page_number
3✔
412
                        ),
3✔
413
                ];
3✔
414

415
                return $crumbs;
416
        }
417
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc