PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.1
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 13.9.2 14.0.1 All 504 releases
← All changes | modules/seo-tools/class-jetpack-seo-posts.php +101 -3 13.5.216.3-a.1 View file →
@@ -14,15 +14,25 @@
14 14 */
15 15 const DESCRIPTION_META_KEY = 'advanced_seo_description';
16 16 const HTML_TITLE_META_KEY = 'jetpack_seo_html_title';
17 17 const NOINDEX_META_KEY = 'jetpack_seo_noindex';
18 + const SCHEMA_TYPE_META_KEY = 'jetpack_seo_schema_type';
18 19 const POST_META_KEYS_ARRAY = array(
19 20 self::DESCRIPTION_META_KEY,
20 21 self::HTML_TITLE_META_KEY,
21 22 self::NOINDEX_META_KEY,
23 + self::SCHEMA_TYPE_META_KEY,
22 24 );
23 25
24 26 /**
27 + * Allowed Schema.org types that can be stored in the per-post schema-type
28 + * meta. Empty string means "no override" — Schema_Builder picks a sensible
29 + * default for the post. Single source of truth for the meta enum, the
30 + * block-editor panel options, and Schema_Builder.
31 + */
32 + const ALLOWED_SCHEMA_TYPES = array( '', 'article', 'faq' );
33 +
34 + /**
25 35 * Build meta description for post SEO.
26 36 *
27 37 * @param WP_Post|null $post Source of data for custom description.
28 38 *
@@ -33,9 +43,10 @@
33 43 if ( ! ( $post instanceof WP_Post ) ) {
34 44 return '';
35 45 }
36 46
37 - if ( post_password_required() || ! is_singular() ) {
47 + // Check the post being described, not the global one.
48 + if ( post_password_required( $post ) || ! is_singular() ) {
38 49 return '';
39 50 }
40 51
41 52 // Business users can overwrite the description.
@@ -48,9 +59,19 @@
48 59 if ( ! empty( $post->post_excerpt ) ) {
49 60 return $post->post_excerpt;
50 61 }
51 62
52 - return $post->post_content;
63 + // The fall-through reads raw post_content, which never passes the `the_content` paywall.
64 + $content = $post->post_content;
65 + if ( \Automattic\Jetpack\SEO\Content_Gate::is_gated( $post ) ) {
66 + $content = \Automattic\Jetpack\SEO\Content_Gate::public_teaser( $post );
67 + if ( '' === $content ) {
68 + return '';
69 + }
70 + }
71 +
72 + // Remove content within wp:query blocks and return.
73 + return Jetpack_SEO_Utils::remove_query_blocks( $content );
53 74 }
54 75
55 76 /**
56 77 * Returns post's custom meta description if it is set, and if
@@ -138,11 +159,13 @@
138 159 return $skip;
139 160 }
140 161
141 162 /**
142 - * Registers the following meta keys for use in the REST API:
163 + * Registers the SEO post meta keys for use in the REST API:
143 164 * - self::DESCRIPTION_META_KEY
144 165 * - self::HTML_TITLE_META_KEY
166 + * - self::NOINDEX_META_KEY
167 + * - self::SCHEMA_TYPE_META_KEY
145 168 */
146 169 public static function register_post_meta() {
147 170 $description_args = array(
148 171 'type' => 'string',
@@ -173,9 +196,84 @@
173 196 'name' => self::NOINDEX_META_KEY,
174 197 ),
175 198 );
176 199
200 + $schema_type_args = array(
201 + 'type' => 'string',
202 + 'description' => __( 'Schema.org type to emit as JSON-LD for this post.', 'jetpack' ),
203 + 'single' => true,
204 + 'default' => '',
205 + 'sanitize_callback' => array( __CLASS__, 'sanitize_schema_type' ),
206 + 'show_in_rest' => array(
207 + 'name' => self::SCHEMA_TYPE_META_KEY,
208 + // Enum so core REST rejects an unknown schema type with a proper
209 + // rest_invalid_param error; the sanitize_callback is the
210 + // defense-in-depth fallback for non-REST writes.
211 + 'schema' => array(
212 + 'type' => 'string',
213 + 'enum' => self::ALLOWED_SCHEMA_TYPES,
214 + ),
215 + ),
216 + );
217 +
177 218 register_meta( 'post', self::DESCRIPTION_META_KEY, $description_args );
178 219 register_meta( 'post', self::HTML_TITLE_META_KEY, $html_title_args );
179 220 register_meta( 'post', self::NOINDEX_META_KEY, $noindex_args );
221 + register_meta( 'post', self::SCHEMA_TYPE_META_KEY, $schema_type_args );
222 + }
223 +
224 + /**
225 + * Sanitize a schema type to the allowed list. Unknown values become ''
226 + * (no override) rather than erroring, so a non-REST write can't store junk.
227 + *
228 + * @param string $value The submitted value.
229 + * @return string A value from self::ALLOWED_SCHEMA_TYPES.
230 + */
231 + public static function sanitize_schema_type( $value ) {
232 + $value = is_string( $value ) ? sanitize_key( $value ) : '';
233 + return in_array( $value, self::ALLOWED_SCHEMA_TYPES, true ) ? $value : '';
234 + }
235 +
236 + /**
237 + * Get the per-post schema-type override, if any.
238 + *
239 + * @param WP_Post|int|null $post Post or post ID.
240 + * @return string A value from self::ALLOWED_SCHEMA_TYPES ('' = no override).
241 + */
242 + public static function get_post_schema_type( $post = null ) {
243 + $post = get_post( $post );
244 + if ( ! ( $post instanceof WP_Post ) ) {
245 + return '';
246 + }
247 + return self::sanitize_schema_type( (string) get_post_meta( $post->ID, self::SCHEMA_TYPE_META_KEY, true ) );
248 + }
249 +
250 + /**
251 + * Factual per-post SEO field coverage — presence/state only, never a score.
252 + *
253 + * Single source of truth shared by the Content tab, the edit.php columns,
254 + * and the Overview coverage card so the three never drift. Reports whether
255 + * each field has been *set*, independent of whether SEO tools are currently
256 + * active (this is an authoring/audit view, not front-end emission).
257 + *
258 + * @param WP_Post|int|null $post Post or post ID.
259 + * @return array{has_custom_title:bool,has_description:bool,has_schema_type:bool,noindex:bool}
260 + */
261 + public static function get_post_seo_coverage( $post = null ) {
262 + $post = get_post( $post );
263 + if ( ! ( $post instanceof WP_Post ) ) {
264 + return array(
265 + 'has_custom_title' => false,
266 + 'has_description' => false,
267 + 'has_schema_type' => false,
268 + 'noindex' => false,
269 + );
270 + }
271 +
272 + return array(
273 + 'has_custom_title' => '' !== (string) get_post_meta( $post->ID, self::HTML_TITLE_META_KEY, true ),
274 + 'has_description' => '' !== (string) get_post_meta( $post->ID, self::DESCRIPTION_META_KEY, true ),
275 + 'has_schema_type' => '' !== self::get_post_schema_type( $post ),
276 + 'noindex' => (bool) get_post_meta( $post->ID, self::NOINDEX_META_KEY, true ),
277 + );
180 278 }
181 279 }