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

Yoast / wordpress-seo / 5066322038

pending completion
5066322038

push

github

GitHub
Merge pull request #20316 from Yoast/JRF/ghactions-run-more-selectively

2550 of 29012 relevant lines covered (8.79%)

0.32 hits per line

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

0.0
/src/integrations/front-end-integration.php
1
<?php
2

3
namespace Yoast\WP\SEO\Integrations;
4

5
use WPSEO_Replace_Vars;
6
use Yoast\WP\SEO\Conditionals\Front_End_Conditional;
7
use Yoast\WP\SEO\Context\Meta_Tags_Context;
8
use Yoast\WP\SEO\Helpers\Options_Helper;
9
use Yoast\WP\SEO\Helpers\Request_Helper;
10
use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer;
11
use Yoast\WP\SEO\Presenters\Abstract_Indexable_Presenter;
12
use Yoast\WP\SEO\Presenters\Debug\Marker_Close_Presenter;
13
use Yoast\WP\SEO\Presenters\Debug\Marker_Open_Presenter;
14
use Yoast\WP\SEO\Presenters\Title_Presenter;
15
use Yoast\WP\SEO\Surfaces\Helpers_Surface;
16
use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface;
17

18
/**
19
 * Class Front_End_Integration.
20
 */
21
class Front_End_Integration implements Integration_Interface {
22

23
        /**
24
         * The memoizer for the meta tags context.
25
         *
26
         * @var Meta_Tags_Context_Memoizer
27
         */
28
        private $context_memoizer;
29

30
        /**
31
         * The container.
32
         *
33
         * @var ContainerInterface
34
         */
35
        protected $container;
36

37
        /**
38
         * Represents the options helper.
39
         *
40
         * @var Options_Helper
41
         */
42
        protected $options;
43

44
        /**
45
         * Represents the request helper.
46
         *
47
         * @var Request_Helper
48
         */
49
        protected $request;
50

51
        /**
52
         * The helpers surface.
53
         *
54
         * @var Helpers_Surface
55
         */
56
        protected $helpers;
57

58
        /**
59
         * The replace vars helper.
60
         *
61
         * @var WPSEO_Replace_Vars
62
         */
63
        protected $replace_vars;
64

65
        /**
66
         * The presenters we loop through on each page load.
67
         *
68
         * @var string[]
69
         */
70
        protected $base_presenters = [
71
                'Title',
72
                'Meta_Description',
73
                'Robots',
74
        ];
75

76
        /**
77
         * The presenters we loop through on each page load.
78
         *
79
         * @var string[]
80
         */
81
        protected $indexing_directive_presenters = [
82
                'Canonical',
83
                'Rel_Prev',
84
                'Rel_Next',
85
        ];
86

87
        /**
88
         * The Open Graph specific presenters.
89
         *
90
         * @var string[]
91
         */
92
        protected $open_graph_presenters = [
93
                'Open_Graph\Locale',
94
                'Open_Graph\Type',
95
                'Open_Graph\Title',
96
                'Open_Graph\Description',
97
                'Open_Graph\Url',
98
                'Open_Graph\Site_Name',
99
                'Open_Graph\Article_Publisher',
100
                'Open_Graph\Article_Author',
101
                'Open_Graph\Article_Published_Time',
102
                'Open_Graph\Article_Modified_Time',
103
                'Open_Graph\Image',
104
                'Meta_Author',
105
        ];
106

107
        /**
108
         * The Open Graph specific presenters that should be output on error pages.
109
         *
110
         * @var array
111
         */
112
        protected $open_graph_error_presenters = [
113
                'Open_Graph\Locale',
114
                'Open_Graph\Title',
115
                'Open_Graph\Site_Name',
116
        ];
117

118
        /**
119
         * The Twitter card specific presenters.
120
         *
121
         * @var string[]
122
         */
123
        protected $twitter_card_presenters = [
124
                'Twitter\Card',
125
                'Twitter\Title',
126
                'Twitter\Description',
127
                'Twitter\Image',
128
                'Twitter\Creator',
129
                'Twitter\Site',
130
        ];
131

132
        /**
133
         * The Slack specific presenters.
134
         *
135
         * @var string[]
136
         */
137
        protected $slack_presenters = [
138
                'Slack\Enhanced_Data',
139
        ];
140

141
        /**
142
         * The Webmaster verification specific presenters.
143
         *
144
         * @var string[]
145
         */
146
        protected $webmaster_verification_presenters = [
147
                'Webmaster\Baidu',
148
                'Webmaster\Bing',
149
                'Webmaster\Google',
150
                'Webmaster\Pinterest',
151
                'Webmaster\Yandex',
152
        ];
153

154
        /**
155
         * Presenters that are only needed on singular pages.
156
         *
157
         * @var string[]
158
         */
159
        protected $singular_presenters = [
160
                'Meta_Author',
161
                'Open_Graph\Article_Author',
162
                'Open_Graph\Article_Publisher',
163
                'Open_Graph\Article_Published_Time',
164
                'Open_Graph\Article_Modified_Time',
165
                'Twitter\Creator',
166
                'Slack\Enhanced_Data',
167
        ];
168

169
        /**
170
         * The presenters we want to be last in our output.
171
         *
172
         * @var string[]
173
         */
174
        protected $closing_presenters = [
175
                'Schema',
176
        ];
177

178
        /**
179
         * Returns the conditionals based on which this loadable should be active.
180
         *
181
         * @return array The conditionals.
182
         */
183
        public static function get_conditionals() {
184
                return [ Front_End_Conditional::class ];
185
        }
186

187
        /**
188
         * Front_End_Integration constructor.
189
         *
190
         * @codeCoverageIgnore It sets dependencies.
191
         *
192
         * @param Meta_Tags_Context_Memoizer $context_memoizer  The meta tags context memoizer.
193
         * @param ContainerInterface         $service_container The DI container.
194
         * @param Options_Helper             $options           The options helper.
195
         * @param Request_Helper             $request           The request helper.
196
         * @param Helpers_Surface            $helpers           The helpers surface.
197
         * @param WPSEO_Replace_Vars         $replace_vars      The replace vars helper.
198
         */
199
        public function __construct(
200
                Meta_Tags_Context_Memoizer $context_memoizer,
201
                ContainerInterface $service_container,
202
                Options_Helper $options,
203
                Request_Helper $request,
204
                Helpers_Surface $helpers,
205
                WPSEO_Replace_Vars $replace_vars
206
        ) {
207
                $this->container        = $service_container;
208
                $this->context_memoizer = $context_memoizer;
209
                $this->options          = $options;
210
                $this->request          = $request;
211
                $this->helpers          = $helpers;
212
                $this->replace_vars     = $replace_vars;
213
        }
214

215
        /**
216
         * Registers the appropriate hooks to show the SEO metadata on the frontend.
217
         *
218
         * Removes some actions to remove metadata that WordPress shows on the frontend,
219
         * to avoid duplicate and/or mismatched metadata.
220
         */
221
        public function register_hooks() {
222
                \add_action( 'wp_head', [ $this, 'call_wpseo_head' ], 1 );
223
                // Filter the title for compatibility with other plugins and themes.
224
                \add_filter( 'wp_title', [ $this, 'filter_title' ], 15 );
225
                // Filter the title for compatibility with block-based themes.
226
                \add_filter( 'pre_get_document_title', [ $this, 'filter_title' ], 15 );
227

228
                // Removes our robots presenter from the list when wp_robots is handling this.
229
                \add_filter( 'wpseo_frontend_presenter_classes', [ $this, 'filter_robots_presenter' ] );
230

231
                \add_action( 'wpseo_head', [ $this, 'present_head' ], -9999 );
232

233
                \remove_action( 'wp_head', 'rel_canonical' );
234
                \remove_action( 'wp_head', 'index_rel_link' );
235
                \remove_action( 'wp_head', 'start_post_rel_link' );
236
                \remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head' );
237
                \remove_action( 'wp_head', 'noindex', 1 );
238
                \remove_action( 'wp_head', '_wp_render_title_tag', 1 );
239
                \remove_action( 'wp_head', '_block_template_render_title_tag', 1 );
240
                \remove_action( 'wp_head', 'gutenberg_render_title_tag', 1 );
241
        }
242

243
        /**
244
         * Filters the title, mainly used for compatibility reasons.
245
         *
246
         * @return string
247
         */
248
        public function filter_title() {
249
                $context = $this->context_memoizer->for_current_page();
250

251
                $title_presenter = new Title_Presenter();
252

253
                /** This filter is documented in src/integrations/front-end-integration.php */
254
                $title_presenter->presentation = \apply_filters( 'wpseo_frontend_presentation', $context->presentation, $context );
255
                $title_presenter->replace_vars = $this->replace_vars;
256
                $title_presenter->helpers      = $this->helpers;
257

258
                \remove_filter( 'pre_get_document_title', [ $this, 'filter_title' ], 15 );
259
                $title = \esc_html( $title_presenter->get() );
260
                \add_filter( 'pre_get_document_title', [ $this, 'filter_title' ], 15 );
261

262
                return $title;
263
        }
264

265
        /**
266
         * Filters our robots presenter, but only when wp_robots is attached to the wp_head action.
267
         *
268
         * @param array $presenters The presenters for current page.
269
         *
270
         * @return array The filtered presenters.
271
         */
272
        public function filter_robots_presenter( $presenters ) {
273
                if ( ! \function_exists( 'wp_robots' ) ) {
274
                        return $presenters;
275
                }
276

277
                if ( ! \has_action( 'wp_head', 'wp_robots' ) ) {
278
                        return $presenters;
279
                }
280

281
                if ( $this->request->is_rest_request() ) {
282
                        return $presenters;
283
                }
284

285
                return \array_diff( $presenters, [ 'Yoast\\WP\\SEO\\Presenters\\Robots_Presenter' ] );
286
        }
287

288
        /**
289
         * Presents the head in the front-end. Resets wp_query if it's not the main query.
290
         *
291
         * @codeCoverageIgnore It just calls a WordPress function.
292
         */
293
        public function call_wpseo_head() {
294
                global $wp_query;
295

296
                $old_wp_query = $wp_query;
297
                // phpcs:ignore WordPress.WP.DiscouragedFunctions.wp_reset_query_wp_reset_query -- Reason: The recommended function, wp_reset_postdata, doesn't reset wp_query.
298
                \wp_reset_query();
299

300
                \do_action( 'wpseo_head' );
301

302
                // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Reason: we have to restore the query.
303
                $GLOBALS['wp_query'] = $old_wp_query;
304
        }
305

306
        /**
307
         * Echoes all applicable presenters for a page.
308
         */
309
        public function present_head() {
310
                $context    = $this->context_memoizer->for_current_page();
311
                $presenters = $this->get_presenters( $context->page_type, $context );
312

313
                /**
314
                 * Filter 'wpseo_frontend_presentation' - Allow filtering the presentation used to output our meta values.
315
                 *
316
                 * @api Indexable_Presention The indexable presentation.
317
                 */
318
                $presentation = \apply_filters( 'wpseo_frontend_presentation', $context->presentation, $context );
319

320
                echo \PHP_EOL;
321
                foreach ( $presenters as $presenter ) {
322
                        $presenter->presentation = $presentation;
323
                        $presenter->helpers      = $this->helpers;
324
                        $presenter->replace_vars = $this->replace_vars;
325

326
                        $output = $presenter->present();
327
                        if ( ! empty( $output ) ) {
328
                                // phpcs:ignore WordPress.Security.EscapeOutput -- Presenters are responsible for correctly escaping their output.
329
                                echo "\t" . $output . \PHP_EOL;
330
                        }
331
                }
332
                echo \PHP_EOL . \PHP_EOL;
333
        }
334

335
        /**
336
         * Returns all presenters for this page.
337
         *
338
         * @param string                 $page_type The page type.
339
         * @param Meta_Tags_Context|null $context   The meta tags context for the current page.
340
         *
341
         * @return Abstract_Indexable_Presenter[] The presenters.
342
         */
343
        public function get_presenters( $page_type, $context = null ) {
344
                if ( \is_null( $context ) ) {
345
                        $context = $this->context_memoizer->for_current_page();
346
                }
347

348
                $needed_presenters = $this->get_needed_presenters( $page_type );
349

350
                $callback   = static function( $presenter ) {
351
                        if ( ! \class_exists( $presenter ) ) {
352
                                return null;
353
                        }
354
                        return new $presenter();
355
                };
356
                $presenters = \array_filter( \array_map( $callback, $needed_presenters ) );
357

358
                /**
359
                 * Filter 'wpseo_frontend_presenters' - Allow filtering the presenter instances in or out of the request.
360
                 *
361
                 * @param array             $presenters The presenters.
362
                 * @param Meta_Tags_Context $context    The meta tags context for the current page.
363
                 *
364
                 * @api Abstract_Indexable_Presenter[] List of presenter instances.
365
                 */
366
                $presenter_instances = \apply_filters( 'wpseo_frontend_presenters', $presenters, $context );
367

368
                if ( ! \is_array( $presenter_instances ) ) {
369
                        $presenter_instances = $presenters;
370
                }
371

372
                $is_presenter_callback = static function ( $presenter_instance ) {
373
                        return $presenter_instance instanceof Abstract_Indexable_Presenter;
374
                };
375
                $presenter_instances   = \array_filter( $presenter_instances, $is_presenter_callback );
376

377
                return \array_merge(
378
                        [ new Marker_Open_Presenter() ],
379
                        $presenter_instances,
380
                        [ new Marker_Close_Presenter() ]
381
                );
382
        }
383

384
        /**
385
         * Generate the array of presenters we need for the current request.
386
         *
387
         * @param string $page_type The page type we're retrieving presenters for.
388
         *
389
         * @return string[] The presenters.
390
         */
391
        private function get_needed_presenters( $page_type ) {
392
                $presenters = $this->get_presenters_for_page_type( $page_type );
393

394
                $presenters = $this->maybe_remove_title_presenter( $presenters );
395

396
                $callback   = static function ( $presenter ) {
397
                        return "Yoast\WP\SEO\Presenters\\{$presenter}_Presenter";
398
                };
399
                $presenters = \array_map( $callback, $presenters );
400

401
                /**
402
                 * Filter 'wpseo_frontend_presenter_classes' - Allow filtering presenters in or out of the request.
403
                 *
404
                 * @param array  $presenters List of presenters.
405
                 * @param string $page_type  The current page type.
406
                 */
407
                $presenters = \apply_filters( 'wpseo_frontend_presenter_classes', $presenters, $page_type );
408

409
                return $presenters;
410
        }
411

412
        /**
413
         * Filters the presenters based on the page type.
414
         *
415
         * @param string $page_type The page type.
416
         *
417
         * @return string[] The presenters.
418
         */
419
        private function get_presenters_for_page_type( $page_type ) {
420
                if ( $page_type === 'Error_Page' ) {
421
                        $presenters = $this->base_presenters;
422
                        if ( $this->options->get( 'opengraph' ) === true ) {
423
                                $presenters = \array_merge( $presenters, $this->open_graph_error_presenters );
424
                        }
425
                        return \array_merge( $presenters, $this->closing_presenters );
426
                }
427

428
                $presenters = $this->get_all_presenters();
429
                if ( \in_array( $page_type, [ 'Static_Home_Page', 'Home_Page' ], true ) ) {
430
                        $presenters = \array_merge( $presenters, $this->webmaster_verification_presenters );
431
                }
432

433
                // Filter out the presenters only needed for singular pages on non-singular pages.
434
                if ( ! \in_array( $page_type, [ 'Post_Type', 'Static_Home_Page' ], true ) ) {
435
                        $presenters = \array_diff( $presenters, $this->singular_presenters );
436
                }
437

438
                return $presenters;
439
        }
440

441
        /**
442
         * Returns a list of all available presenters based on settings.
443
         *
444
         * @return string[] The presenters.
445
         */
446
        private function get_all_presenters() {
447
                $presenters = \array_merge( $this->base_presenters, $this->indexing_directive_presenters );
448
                if ( $this->options->get( 'opengraph' ) === true ) {
449
                        $presenters = \array_merge( $presenters, $this->open_graph_presenters );
450
                }
451
                if ( $this->options->get( 'twitter' ) === true && \apply_filters( 'wpseo_output_twitter_card', true ) !== false ) {
452
                        $presenters = \array_merge( $presenters, $this->twitter_card_presenters );
453
                }
454
                if ( $this->options->get( 'enable_enhanced_slack_sharing' ) === true && \apply_filters( 'wpseo_output_enhanced_slack_data', true ) !== false ) {
455
                        $presenters = \array_merge( $presenters, $this->slack_presenters );
456
                }
457

458
                return \array_merge( $presenters, $this->closing_presenters );
459
        }
460

461
        /**
462
         * Whether the title presenter should be removed.
463
         *
464
         * @return bool True when the title presenter should be removed, false otherwise.
465
         */
466
        public function should_title_presenter_be_removed() {
467
                return ! \get_theme_support( 'title-tag' ) && ! $this->options->get( 'forcerewritetitle', false );
468
        }
469

470
        /**
471
         * Checks if the Title presenter needs to be removed.
472
         *
473
         * @param string[] $presenters The presenters.
474
         *
475
         * @return string[] The presenters.
476
         */
477
        private function maybe_remove_title_presenter( $presenters ) {
478
                // Do not remove the title if we're on a REST request.
479
                if ( $this->request->is_rest_request() ) {
480
                        return $presenters;
481
                }
482

483
                // Remove the title presenter if the theme is hardcoded to output a title tag so we don't have two title tags.
484
                if ( $this->should_title_presenter_be_removed() ) {
485
                        $presenters = \array_diff( $presenters, [ 'Title' ] );
486
                }
487

488
                return $presenters;
489
        }
490
}
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

© 2026 Coveralls, Inc