PluginProbe
WPSSO Core – Complete Schema Markup and Meta Tags / 22.7.0
WPSSO Core – Complete Schema Markup and Meta Tags v22.7.0
22.7.0 22.6.1 22.6.0 22.5.3 22.5.2 22.5.1 22.4.0 22.3.0 22.2.1 22.2.0 22.1.3 22.1.2 22.1.1 22.1.0 22.0.0 21.13.3 21.13.2 trunk 21.13.1
wpsso / lib / json / prop / haspart.php

haspart.php in WPSSO Core – Complete Schema Markup and Meta Tags 22.7.0, at lib/json/prop/haspart.php

425 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 * License: GPLv3
4 * License URI: https://www.gnu.org/licenses/gpl.txt
5 * Copyright 2016-2026 Jean-Sebastien Morisset (https://wpsso.com/)
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9
10 die( 'These aren\'t the droids you\'re looking for.' );
11 }
12
13 if ( ! class_exists( 'WpssoJsonPropHasPart' ) ) {
14
15 class WpssoJsonPropHasPart {
16
17 private $p; // Wpsso class object.
18
19 private static $meta_name = '_wpsso_json_haspart';
20 private static $meta_saved = false;
21
22 /*
23 * Instantiated by Wpsso->init_json_filters().
24 */
25 public function __construct( &$plugin ) {
26
27 $this->p =& $plugin;
28
29 if ( $this->p->debug->enabled ) {
30
31 $this->p->debug->mark();
32 }
33
34 $this->p->util->add_plugin_filters( $this, array(
35 'json_data_https_schema_org_thing' => 5,
36 'json_data_https_schema_org_creativework' => 5,
37 ), $prio = 10000 );
38
39 /*
40 * Comment json scripts saved in the self::$meta_name metadata array.
41 *
42 * See wordpress/wp-includes/default-filters.php:
43 *
44 * add_filter( 'the_content', 'do_shortcode', 11 ); // After wpautop().
45 */
46 add_filter( 'the_content', array( $this, 'maybe_comment_json_scripts' ), 12 ); // After do_shortcode().
47
48 if ( $this->p->debug->enabled ) {
49
50 $this->p->debug->log( 'added maybe_comment_json_scripts filter hook for the_content' );
51 }
52 }
53
54 /*
55 * Cleanup self::$meta_name here in case the Schema type has changed from CreativeWork to something else.
56 */
57 public function filter_json_data_https_schema_org_thing( $json_data, $mod, $mt_og, $page_type_id, $is_main ) {
58
59 if ( self::$meta_saved ) {
60
61 if ( $is_main ) {
62
63 if ( $mod[ 'is_post' ] ) {
64
65 if ( $this->p->debug->enabled ) {
66
67 $this->p->debug->log( 'deleting ' . self::$meta_name . ' metadata for post id ' . $mod[ 'id' ] );
68 }
69
70 delete_metadata( 'post', $mod[ 'id' ], self::$meta_name );
71 }
72 }
73 }
74
75 return $json_data;
76 }
77
78 public function filter_json_data_https_schema_org_creativework( $json_data, $mod, $mt_og, $page_type_id, $is_main ) {
79
80 if ( $this->p->debug->enabled ) {
81
82 $this->p->debug->mark();
83 }
84
85 $prop_data = array( 'hasPart' => array() );
86
87 return $this->filter_json_data_post_content_json_ld_scripts( $json_data, $prop_data, $mod, $mt_og, $page_type_id, $is_main );
88 }
89
90 private function filter_json_data_post_content_json_ld_scripts( $json_data, $prop_data, $mod, $mt_og, $page_type_id, $is_main ) {
91
92 if ( $this->p->debug->enabled ) {
93
94 $this->p->debug->mark();
95 }
96
97 if ( ! $is_main ) {
98
99 if ( $this->p->debug->enabled ) {
100
101 $this->p->debug->log( 'exiting early: json data is not the main entity' );
102 }
103
104 return $json_data;
105
106 } elseif ( ! $mod[ 'is_post' ] ) {
107
108 if ( $this->p->debug->enabled ) {
109
110 $this->p->debug->log( 'exiting early: no content to check for module type ' . $mod[ 'name' ] );
111 }
112
113 return $json_data;
114 }
115
116 if ( $this->p->debug->enabled ) {
117
118 $this->p->debug->log( 'getting the content for post id ' . $mod[ 'id' ] );
119 }
120
121 $content = $this->p->page->get_the_content( $mod, $flatten = false );
122
123 if ( $this->p->debug->enabled ) {
124
125 $this->p->debug->log( 'getting json scripts from the content' );
126 }
127
128 $scripts_data = SucomUtil::get_json_scripts( $content, $do_decode = true ); // Return the decoded json data.
129
130 if ( empty( $scripts_data ) ) { // Nothing to do.
131
132 if ( $this->p->debug->enabled ) {
133
134 $this->p->debug->log( 'exiting early: no json scripts found in the content' );
135 }
136
137 return $json_data;
138 }
139
140 $json_ret = array();
141
142 /*
143 * Save existing properties in $json_data so we can filter them and add new ones.
144 */
145 foreach ( $prop_data as $prop_name => $prop_values ) {
146
147 if ( isset( $json_data[ $prop_name ] ) ) {
148
149 if ( $this->p->debug->enabled ) {
150
151 $this->p->debug->log( 'saving ' . $prop_name . ' property value(s)' );
152 }
153
154 if ( isset( $json_data[ $prop_name ][ 0 ] ) ) { // Has an array of types.
155
156 $prop_data[ $prop_name ] = $json_data[ $prop_name ];
157
158 } elseif ( ! empty( $json_data[ $prop_name ] ) ) {
159
160 $prop_data[ $prop_name ][] = $json_data[ $prop_name ]; // Markup for a single type.
161 }
162
163 unset( $json_data[ $prop_name ] );
164 }
165 }
166
167 $added_script_ids = array();
168
169 foreach ( $scripts_data as $single_id => $single_data ) {
170
171 if ( is_array( $single_data ) ) { // Just in case.
172
173 if ( $this->p->debug->enabled ) {
174
175 $this->p->debug->log( 'adding json scripts data for ' . $single_id );
176 }
177
178 $this->maybe_add_single_data( $added_script_ids, $prop_data, $single_id, $single_data, $page_type_id );
179
180 } else {
181
182 if ( $this->p->debug->enabled ) {
183
184 $this->p->debug->log( 'skipped ' . $single_id . ': single data is not an array' );
185
186 $this->p->debug->log( $single_data );
187 }
188 }
189 }
190
191 if ( ! empty( $added_script_ids ) ) {
192
193 if ( $this->p->debug->enabled ) {
194
195 $this->p->debug->log_arr( 'added_script_ids', $added_script_ids );
196 }
197
198 self::$meta_saved = true;
199
200 update_metadata( 'post', $mod[ 'id' ], self::$meta_name, $added_script_ids );
201 }
202
203 foreach ( $prop_data as $prop_name => $prop_values ) {
204
205 $filter_name = 'wpsso_json_prop_https_schema_org_' . strtolower( $prop_name );
206
207 $prop_values = apply_filters( $filter_name, $prop_values, $mod, $mt_og, $page_type_id, $is_main );
208
209 if ( isset( $prop_values[ 0 ] ) ) {
210
211 foreach ( $prop_values as $array_key => &$array_data ) {
212
213 if ( ! empty( $array_data[ 'mainEntityOfPage' ] ) && ! empty( $json_data[ 'url' ] ) &&
214 $array_data[ 'mainEntityOfPage' ] === $json_data[ 'url' ] ) {
215
216 unset( $array_data[ 'mainEntityOfPage' ] );
217 }
218 }
219 }
220
221 if ( ! empty( $prop_values ) ) {
222
223 $json_ret[ $prop_name ] = $prop_values;
224 }
225 }
226
227 return WpssoSchema::return_data_from_filter( $json_data, $json_ret, $is_main );
228 }
229
230 /*
231 * Recurse for each @graph element, including nested @graph elements (ie. @graph within another @graph).
232 */
233 private function maybe_add_single_data( array &$added_script_ids, array &$prop_data, $single_id, array $single_data, $page_type_id, $def_context = null ) {
234
235 if ( null === $def_context ) {
236
237 $def_context = empty( $single_data[ '@context' ] ) ? 'https://schema.org' : $single_data[ '@context' ];
238 }
239
240 if ( isset( $single_data[ 0 ] ) ) {
241
242 foreach ( $single_data as $array_key => $array_data ) {
243
244 $this->maybe_add_single_data( $added_script_ids, $prop_data, $single_id, $array_data, $page_type_id, $def_context );
245 }
246
247 } elseif ( isset( $single_data[ '@graph' ] ) ) {
248
249 foreach ( $single_data[ '@graph' ] as $graph_key => $graph_data ) {
250
251 if ( '@context' === $graph_key ) { // Nested @graph.
252
253 $def_context = $graph_data;
254
255 continue;
256
257 } elseif ( '@graph' === $graph_key ) {
258
259 $this->maybe_add_single_data( $added_script_ids, $prop_data, $single_id, $graph_data, $page_type_id, $def_context );
260
261 } elseif ( is_numeric( $graph_key ) ) {
262
263 $this->maybe_add_single_data( $added_script_ids, $prop_data, $single_id, $graph_data, $page_type_id, $def_context );
264 }
265 }
266
267 } else {
268
269 if ( empty( $single_data[ '@context' ] ) ) { // Just in case.
270
271 $single_data[ '@context' ] = $def_context;
272 }
273
274 $type_url = WpssoSchema::get_data_type_url( $single_data );
275
276 $type_ids = $this->p->schema->get_schema_type_url_ids( $type_url );
277
278 foreach ( $type_ids as $child_id ) {
279
280 if ( isset( $prop_data[ 'hasPart' ] ) ) {
281
282 /*
283 * The hasPart property value must be a Schema CreativeWork type or sub-type.
284 */
285 if ( $this->p->schema->is_schema_type_child( $child_id, 'creative.work' ) ) {
286
287 $prop_data[ 'hasPart' ][] = WpssoSchema::get_schema_type_context( $type_url, $single_data );
288
289 $added_script_ids[ $single_id ] = 'hasPart';
290
291 break; // Child id is valid - no need to check the other child ids.
292 }
293 }
294 }
295 }
296 }
297
298 /*
299 * Comment json scripts saved in the self::$meta_name metadata array.
300 */
301 public function maybe_comment_json_scripts( $content ) {
302
303 if ( $this->p->debug->enabled ) {
304
305 $this->p->debug->mark();
306 }
307
308 if ( ! empty( $GLOBALS[ 'wpsso_doing_filter_the_content' ] ) ) {
309
310 if ( $this->p->debug->enabled ) {
311
312 $this->p->debug->log( 'exiting early: wpsso_doing_filter_the_content is true' );
313 }
314
315 return $content;
316 }
317
318 if ( ! empty( $GLOBALS[ 'post' ]->ID ) ) {
319
320 $post_id = $GLOBALS[ 'post' ]->ID;
321
322 } else {
323
324 if ( $this->p->debug->enabled ) {
325
326 $this->p->debug->log( 'exiting early: no global post object' );
327 }
328
329 $failure = '<!-- no global post object to comment json scripts -->' . "\n";
330
331 return $failure . $content;
332 }
333
334 $added_script_ids = get_metadata( 'post', $post_id, self::$meta_name, $single = true );
335
336 if ( empty( $added_script_ids ) || ! is_array( $added_script_ids ) ) { // Nothing to do.
337
338 if ( $this->p->debug->enabled ) {
339
340 $this->p->debug->log( 'exiting early: no json scripts to comment in the content' );
341 }
342
343 $failure = '<!-- no json scripts to comment in the content -->' . "\n";
344
345 return $failure . $content;
346
347 }
348
349 if ( $this->p->debug->enabled ) {
350
351 $this->p->debug->log_arr( 'added_script_ids', $added_script_ids );
352 }
353
354 $json_scripts = SucomUtil::get_json_scripts( $content, $do_decode = false );
355
356 if ( empty( $json_scripts ) ) { // Nothing to do.
357
358 if ( $this->p->debug->enabled ) {
359
360 $this->p->debug->log( 'exiting early: no json scripts found in the content' );
361 }
362
363 $failure = '<!-- no json scripts found in the content -->' . "\n";
364
365 return $failure . $content;
366 }
367
368 if ( $this->p->debug->enabled ) {
369
370 $this->p->debug->log_arr( 'json_scripts', $json_scripts );
371 }
372
373 foreach ( $json_scripts as $single_id => $single_json ) {
374
375 if ( empty( $added_script_ids[ $single_id ] ) ) {
376
377 $failure = '<!-- json script ' . $single_id . ' found but not added -->' . "\n";
378
379 $content = $failure . $content;
380
381 continue;
382 }
383
384 $prop_name = $added_script_ids[ $single_id ];
385
386 $replace = '<!-- json script ' . $single_id . ' added to ' . $prop_name . ' and commented -->';
387
388 $count = null;
389
390 if ( 0 === strpos( $single_id, 'id:' ) ) {
391
392 $css_id = preg_quote( substr( $single_id, strlen( 'id:' ) ), '/' );
393
394 $content = preg_replace( '/<script\b[^>]*id=["\']' . $css_id . '["\'][^>]*>.+<\/script>/Uis', $replace, $content, $limit = -1, $count );
395
396 } elseif ( 0 === strpos( $single_id, 'md5:' ) ) {
397
398 $content = str_replace( $single_json, $replace, $content, $count );
399 }
400
401 if ( $count ) {
402
403 if ( $this->p->debug->enabled ) {
404
405 $this->p->debug->log( 'json script ' . $single_id . ' successfully commented' );
406 }
407
408 } else {
409
410 if ( $this->p->debug->enabled ) {
411
412 $this->p->debug->log( 'json script ' . $single_id . ' not found in the content' );
413 }
414
415 $failure = '<!-- json script ' . $single_id . ' added to ' . $prop_name . ' but not found in the content -->' . "\n";
416
417 $content = $failure . $content;
418 }
419 }
420
421 return $content;
422 }
423 }
424 }
425