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

Yoast / wordpress-seo / 833536d8ae0a44e8a393702dee349368fa18b042

05 Aug 2026 01:10PM UTC coverage: 55.451% (-0.3%) from 55.778%
833536d8ae0a44e8a393702dee349368fa18b042

Pull #23538

github

web-flow
Merge edd499ae5 into 0de900db2
Pull Request #23538: fix(bulk-editor): resolve post type default template when SEO title/description is empty

9798 of 17512 branches covered (55.95%)

Branch coverage included in aggregate %.

49 of 62 new or added lines in 3 files covered. (79.03%)

1 existing line in 1 file now uncovered.

39312 of 71053 relevant lines covered (55.33%)

42357.09 hits per line

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

94.44
/src/bulk-editor/infrastructure/posts/default-template-resolver.php
1
<?php
2

3
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
4
namespace Yoast\WP\SEO\Bulk_Editor\Infrastructure\Posts;
5

6
use Yoast\WP\SEO\Helpers\Options_Helper;
7

8
/**
9
 * Resolves a post's SEO/social fields from the post type's default template when the stored value
10
 * is empty, matching the single-post editor's fallback behaviour.
11
 */
12
class Default_Template_Resolver {
13

14
        /**
15
         * The options helper.
16
         *
17
         * @var Options_Helper
18
         */
19
        private $options_helper;
20

21
        /**
22
         * The constructor.
23
         *
24
         * @param Options_Helper $options_helper The options helper.
25
         */
NEW
26
        public function __construct( Options_Helper $options_helper ) {
×
NEW
27
                $this->options_helper = $options_helper;
×
28
        }
29

30
        /**
31
         * Returns the SEO title for a post, falling back to the post type's configured template when empty.
32
         *
33
         * Priority mirrors the presentation layer: stored value → user-configured post type template
34
         * (SEO > Settings, `title-{post_type}`) → installation default.
35
         *
36
         * @param int    $post_id      The post ID.
37
         * @param string $post_type    The post type slug.
38
         * @param string $stored_value The raw stored title (empty string when never explicitly saved).
39
         *
40
         * @return string The resolved SEO title.
41
         */
42
        public function resolve_seo_title( int $post_id, string $post_type, string $stored_value ): string {
8✔
43
                if ( $stored_value !== '' ) {
8✔
44
                        return $stored_value;
2✔
45
                }
46

47
                $template = (string) $this->options_helper->get( 'title-' . $post_type, '' );
6✔
48
                if ( $template === '' ) {
6✔
49
                        $template = (string) $this->options_helper->get_title_default( 'title-' . $post_type );
4✔
50
                }
51
                if ( $template === '' ) {
6✔
52
                        return '';
2✔
53
                }
54

55
                return (string) \wpseo_replace_vars( $template, \get_post( $post_id ) );
4✔
56
        }
57

58
        /**
59
         * Returns the meta description for a post, falling back to the post type's configured template when empty.
60
         *
61
         * @param int    $post_id      The post ID.
62
         * @param string $post_type    The post type slug.
63
         * @param string $stored_value The raw stored description (empty string when never explicitly saved).
64
         *
65
         * @return string The resolved meta description.
66
         */
67
        public function resolve_meta_description( int $post_id, string $post_type, string $stored_value ): string {
6✔
68
                if ( $stored_value !== '' ) {
6✔
69
                        return $stored_value;
2✔
70
                }
71

72
                $template = (string) $this->options_helper->get( 'metadesc-' . $post_type, '' );
4✔
73
                if ( $template === '' ) {
4✔
74
                        return '';
2✔
75
                }
76

77
                return (string) \wpseo_replace_vars( $template, \get_post( $post_id ) );
2✔
78
        }
79

80
        /**
81
         * Returns the social title for a post, falling back to the post type's configured template when empty.
82
         *
83
         * Mirrors `Social_Data_Provider::get_social_title_template()`: only resolves a template when
84
         * OpenGraph is enabled, and delegates to the `wpseo_social_template_post_type` filter so that
85
         * Premium can supply a value while Free — which cannot configure this setting — always gets an
86
         * empty string.
87
         *
88
         * @param int    $post_id      The post ID.
89
         * @param string $post_type    The post type slug.
90
         * @param string $stored_value The raw stored social title (empty string when never explicitly saved).
91
         *
92
         * @return string The resolved social title.
93
         */
94
        public function resolve_social_title( int $post_id, string $post_type, string $stored_value ): string {
8✔
95
                if ( $stored_value !== '' ) {
8✔
96
                        return $stored_value;
2✔
97
                }
98

99
                if ( $this->options_helper->get( 'opengraph', false ) !== true ) {
6✔
100
                        return '';
2✔
101
                }
102

103
                $template = (string) \apply_filters( 'wpseo_social_template_post_type', '', 'title', $post_type );
4✔
104
                if ( $template === '' ) {
4✔
105
                        return '';
2✔
106
                }
107

108
                return (string) \wpseo_replace_vars( $template, \get_post( $post_id ) );
2✔
109
        }
110

111
        /**
112
         * Returns the social description for a post, falling back to the post type's configured template when empty.
113
         *
114
         * Mirrors `Social_Data_Provider::get_social_description_template()`: only resolves a template when
115
         * OpenGraph is enabled, and delegates to the `wpseo_social_template_post_type` filter so that
116
         * Premium can supply a value while Free always gets an empty string.
117
         *
118
         * @param int    $post_id      The post ID.
119
         * @param string $post_type    The post type slug.
120
         * @param string $stored_value The raw stored social description (empty string when never explicitly saved).
121
         *
122
         * @return string The resolved social description.
123
         */
124
        public function resolve_social_description( int $post_id, string $post_type, string $stored_value ): string {
8✔
125
                if ( $stored_value !== '' ) {
8✔
126
                        return $stored_value;
2✔
127
                }
128

129
                if ( $this->options_helper->get( 'opengraph', false ) !== true ) {
6✔
130
                        return '';
2✔
131
                }
132

133
                $template = (string) \apply_filters( 'wpseo_social_template_post_type', '', 'description', $post_type );
4✔
134
                if ( $template === '' ) {
4✔
135
                        return '';
2✔
136
                }
137

138
                return (string) \wpseo_replace_vars( $template, \get_post( $post_id ) );
2✔
139
        }
140
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc