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

Yoast / wordpress-seo / 56db0408fe2a0dbffe1c2c6cfad9b5c2f6941e34

14 Apr 2025 12:24PM UTC coverage: 52.454% (-2.1%) from 54.594%
56db0408fe2a0dbffe1c2c6cfad9b5c2f6941e34

Pull #22077

github

enricobattocchi
Adjust carryforward in Coveralls action
Pull Request #22077: Drop compatibility with PHP 7.2 and 7.3

7827 of 13877 branches covered (56.4%)

Branch coverage included in aggregate %.

29025 of 56379 relevant lines covered (51.48%)

42277.18 hits per line

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

12.05
/src/surfaces/values/meta.php
1
<?php
2

3
namespace Yoast\WP\SEO\Surfaces\Values;
4

5
use WPSEO_Replace_Vars;
6
use Yoast\WP\SEO\Context\Meta_Tags_Context;
7
use Yoast\WP\SEO\Exceptions\Forbidden_Property_Mutation_Exception;
8
use Yoast\WP\SEO\Integrations\Front_End_Integration;
9
use Yoast\WP\SEO\Models\Indexable;
10
use Yoast\WP\SEO\Presenters\Abstract_Indexable_Presenter;
11
use Yoast\WP\SEO\Presenters\Rel_Next_Presenter;
12
use Yoast\WP\SEO\Presenters\Rel_Prev_Presenter;
13
use Yoast\WP\SEO\Surfaces\Helpers_Surface;
14
use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface;
15

16
/**
17
 * Meta value object.
18
 *
19
 * @property array       $breadcrumbs                       The breadcrumbs array for the current page.
20
 * @property string      $canonical                         The canonical URL for the current page.
21
 * @property string      $company_name                      The company name from the Knowledge graph settings.
22
 * @property int         $company_logo_id                   The attachment ID for the company logo.
23
 * @property string      $description                       The meta description for the current page, if set.
24
 * @property int         $estimated_reading_time_minutes    The estimated reading time in minutes for posts.
25
 * @property Indexable   $indexable                         The indexable object.
26
 * @property string      $main_schema_id                    Schema ID that points to the main Schema thing on the page, usually the webpage or article Schema piece.
27
 * @property string      $meta_description                  The meta description for the current page, if set.
28
 * @property string      $open_graph_article_author         The article:author value.
29
 * @property string      $open_graph_article_modified_time  The article:modified_time value.
30
 * @property string      $open_graph_article_published_time The article:published_time value.
31
 * @property string      $open_graph_article_publisher      The article:publisher value.
32
 * @property string      $open_graph_description            The og:description.
33
 * @property bool        $open_graph_enabled                Whether OpenGraph is enabled on this site.
34
 * @property string      $open_graph_fb_app_id              The Facebook App ID.
35
 * @property array       $open_graph_images                 The array of images we have for this page.
36
 * @property string      $open_graph_locale                 The og:locale for the current page.
37
 * @property string      $open_graph_publisher              The OpenGraph publisher reference.
38
 * @property string      $open_graph_site_name              The og:site_name.
39
 * @property string      $open_graph_title                  The og:title.
40
 * @property string      $open_graph_type                   The og:type.
41
 * @property string      $open_graph_url                    The og:url.
42
 * @property string      $page_type                         The Schema page type.
43
 * @property array       $robots                            An array of the robots values set for the current page.
44
 * @property string      $rel_next                          The next page in the series, if any.
45
 * @property string      $rel_prev                          The previous page in the series, if any.
46
 * @property array       $schema                            The entire Schema array for the current page.
47
 * @property string      $schema_page_type                  The Schema page type.
48
 * @property string      $site_name                         The site name from the Yoast SEO settings.
49
 * @property string      $site_represents                   Whether the site represents a 'person' or a 'company'.
50
 * @property array|false $site_represents_reference         The schema reference ID for what this site represents.
51
 * @property string      $site_url                          The site's main URL.
52
 * @property int         $site_user_id                      If the site represents a 'person', this is the ID of the accompanying user profile.
53
 * @property string      $title                             The SEO title for the current page.
54
 * @property string      $twitter_card                      The Twitter card type for the current page.
55
 * @property string      $twitter_creator                   The Twitter card author for the current page.
56
 * @property string      $twitter_description               The Twitter card description for the current page.
57
 * @property string      $twitter_image                     The Twitter card image for the current page.
58
 * @property string      $twitter_site                      The Twitter card site reference for the current page.
59
 * @property string      $twitter_title                     The Twitter card title for the current page.
60
 * @property string      $wordpress_site_name               The site name from the WordPress settings.
61
 */
62
class Meta {
63

64
        /**
65
         * The container.
66
         *
67
         * @var ContainerInterface
68
         */
69
        protected $container;
70

71
        /**
72
         * The meta tags context.
73
         *
74
         * @var Meta_Tags_Context
75
         */
76
        protected $context;
77

78
        /**
79
         * The front end integration.
80
         *
81
         * @var Front_End_Integration
82
         */
83
        protected $front_end;
84

85
        /**
86
         * The helpers surface.
87
         *
88
         * @var Helpers_Surface
89
         */
90
        protected $helpers;
91

92
        /**
93
         * The replace vars helper
94
         *
95
         * @var WPSEO_Replace_Vars
96
         */
97
        protected $replace_vars;
98

99
        /**
100
         * Collection of properties dynamically set via the magic __get() method.
101
         *
102
         * @var array<string, mixed> Key is the property name.
103
         */
104
        private $properties_bin = [];
105

106
        /**
107
         * Create a meta value object.
108
         *
109
         * @param Meta_Tags_Context  $context   The indexable presentation.
110
         * @param ContainerInterface $container The DI container.
111
         */
112
        public function __construct( Meta_Tags_Context $context, ContainerInterface $container ) {
×
113
                $this->container = $container;
×
114
                $this->context   = $context;
×
115

116
                $this->helpers      = $this->container->get( Helpers_Surface::class );
×
117
                $this->replace_vars = $this->container->get( WPSEO_Replace_Vars::class );
×
118
                $this->front_end    = $this->container->get( Front_End_Integration::class );
×
119
        }
120

121
        /**
122
         * Returns the output as would be presented in the head.
123
         *
124
         * @return object The HTML and JSON presentation of the head metadata.
125
         */
126
        public function get_head() {
×
127
                $presenters = $this->get_presenters();
×
128

129
                /** This filter is documented in src/integrations/front-end-integration.php */
130
                $presentation = \apply_filters( 'wpseo_frontend_presentation', $this->context->presentation, $this->context );
×
131

132
                $html_output      = '';
×
133
                $json_head_fields = [];
×
134

135
                foreach ( $presenters as $presenter ) {
×
136
                        $presenter->presentation = $presentation;
×
137
                        $presenter->replace_vars = $this->replace_vars;
×
138
                        $presenter->helpers      = $this->helpers;
×
139

140
                        $html_output .= $this->create_html_presentation( $presenter );
×
141
                        $json_field   = $this->create_json_field( $presenter );
×
142

143
                        // Only use the output of presenters that could successfully present their data.
144
                        if ( $json_field !== null && ! empty( $json_field->key ) ) {
×
145
                                $json_head_fields[ $json_field->key ] = $json_field->value;
×
146
                        }
147
                }
148
                $html_output = \trim( $html_output );
×
149

150
                return (object) [
×
151
                        'html' => $html_output,
×
152
                        'json' => $json_head_fields,
×
153
                ];
×
154
        }
155

156
        /**
157
         * Magic getter for presenting values through the appropriate presenter, if it exists.
158
         *
159
         * @param string $name The property to get.
160
         *
161
         * @return mixed The value, as presented by the appropriate presenter.
162
         */
163
        public function __get( $name ) {
12✔
164
                if ( \array_key_exists( $name, $this->properties_bin ) ) {
12✔
165
                        return $this->properties_bin[ $name ];
×
166
                }
167

168
                /** This filter is documented in src/integrations/front-end-integration.php */
169
                $presentation = \apply_filters( 'wpseo_frontend_presentation', $this->context->presentation, $this->context );
12✔
170

171
                if ( ! isset( $presentation->{$name} ) ) {
12✔
172
                        if ( isset( $this->context->{$name} ) ) {
12✔
173
                                $this->properties_bin[ $name ] = $this->context->{$name};
×
174
                                return $this->properties_bin[ $name ];
×
175
                        }
176
                        return null;
12✔
177
                }
178

179
                $presenter_namespace = 'Yoast\WP\SEO\Presenters\\';
×
180
                $parts               = \explode( '_', $name );
×
181
                if ( $parts[0] === 'twitter' ) {
×
182
                        $presenter_namespace .= 'Twitter\\';
×
183
                        $parts                = \array_slice( $parts, 1 );
×
184
                }
185
                elseif ( $parts[0] === 'open' && $parts[1] === 'graph' ) {
×
186
                        $presenter_namespace .= 'Open_Graph\\';
×
187
                        $parts                = \array_slice( $parts, 2 );
×
188
                }
189

190
                $presenter_class = $presenter_namespace . \implode( '_', \array_map( 'ucfirst', $parts ) ) . '_Presenter';
×
191

192
                if ( \class_exists( $presenter_class ) ) {
×
193
                        /**
194
                         * The indexable presenter.
195
                         *
196
                         * @var Abstract_Indexable_Presenter $presenter
197
                         */
198
                        $presenter               = new $presenter_class();
×
199
                        $presenter->presentation = $presentation;
×
200
                        $presenter->helpers      = $this->helpers;
×
201
                        $presenter->replace_vars = $this->replace_vars;
×
202
                        $value                   = $presenter->get();
×
203
                }
204
                else {
205
                        $value = $presentation->{$name};
×
206
                }
207

208
                $this->properties_bin[ $name ] = $value;
×
209
                return $this->properties_bin[ $name ];
×
210
        }
211

212
        /**
213
         * Magic isset for ensuring properties on the presentation are recognised.
214
         *
215
         * @param string $name The property to get.
216
         *
217
         * @return bool Whether or not the requested property exists.
218
         */
219
        public function __isset( $name ) {
×
220
                if ( \array_key_exists( $name, $this->properties_bin ) ) {
×
221
                        return true;
×
222
                }
223

224
                return isset( $this->context->presentation->{$name} );
×
225
        }
226

227
        /**
228
         * Prevents setting dynamic properties and overwriting the value of declared properties
229
         * from an inaccessible context.
230
         *
231
         * @param string $name  The property name.
232
         * @param mixed  $value The property value.
233
         *
234
         * @return void
235
         *
236
         * @throws Forbidden_Property_Mutation_Exception Set is never meant to be called.
237
         */
238
        public function __set( $name, $value ) { // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- __set must have a name and value - PHPCS #3715.
16✔
239
                throw Forbidden_Property_Mutation_Exception::cannot_set_because_property_is_immutable( $name );
16✔
240
        }
241

242
        /**
243
         * Prevents unsetting dynamic properties and unsetting declared properties
244
         * from an inaccessible context.
245
         *
246
         * @param string $name The property name.
247
         *
248
         * @return void
249
         *
250
         * @throws Forbidden_Property_Mutation_Exception Unset is never meant to be called.
251
         */
252
        public function __unset( $name ) {
16✔
253
                throw Forbidden_Property_Mutation_Exception::cannot_unset_because_property_is_immutable( $name );
16✔
254
        }
255

256
        /**
257
         * Strips all nested dependencies from the debug info.
258
         *
259
         * @return array
260
         */
261
        public function __debugInfo() {
×
262
                return [ 'context' => $this->context ];
×
263
        }
264

265
        /**
266
         * Returns all presenters.
267
         *
268
         * @return Abstract_Indexable_Presenter[]
269
         */
270
        protected function get_presenters() {
×
271
                $presenters = $this->front_end->get_presenters( $this->context->page_type, $this->context );
×
272

273
                if ( $this->context->page_type === 'Date_Archive' ) {
×
274
                        /**
275
                         * Define a filter that removes objects of type Rel_Next_Presenter or Rel_Prev_Presenter from a list.
276
                         *
277
                         * @param object $presenter The presenter to verify.
278
                         *
279
                         * @return bool True if the presenter is not a Rel_Next or Rel_Prev presenter.
280
                         */
281
                        $callback   = static function ( $presenter ) {
282
                                return ! \is_a( $presenter, Rel_Next_Presenter::class )
×
283
                                        && ! \is_a( $presenter, Rel_Prev_Presenter::class );
×
284
                        };
285
                        $presenters = \array_filter( $presenters, $callback );
×
286
                }
287

288
                return $presenters;
×
289
        }
290

291
        /**
292
         * Uses the presenter to create a line of HTML.
293
         *
294
         * @param Abstract_Indexable_Presenter $presenter The presenter.
295
         *
296
         * @return string
297
         */
298
        protected function create_html_presentation( $presenter ) {
×
299
                $presenter_output = $presenter->present();
×
300
                if ( ! empty( $presenter_output ) ) {
×
301
                        return $presenter_output . \PHP_EOL;
×
302
                }
303
                return '';
×
304
        }
305

306
        /**
307
         * Converts a presenter's key and value to JSON.
308
         *
309
         * @param Abstract_Indexable_Presenter $presenter The presenter whose key and value are to be converted to JSON.
310
         *
311
         * @return object|null
312
         */
313
        protected function create_json_field( $presenter ) {
×
314
                if ( $presenter->get_key() === 'NO KEY PROVIDED' ) {
×
315
                        return null;
×
316
                }
317

318
                $value = $presenter->get();
×
319
                if ( empty( $value ) ) {
×
320
                        return null;
×
321
                }
322

323
                return (object) [
×
324
                        'key'   => $presenter->escape_key(),
×
325
                        'value' => $value,
×
326
                ];
×
327
        }
328
}
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