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
← All changes | lib/json/prop/haspart.php +424 -0 22.2.022.7.0 View file →
@@ -1,0 +1,424 @@
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 +}