Completed
Pull Request — develop (#1701)
by
unknown
01:22
created
src/public/class-wordlift-term-jsonld-adapter.php 1 patch
Indentation   +378 added lines, -378 removed lines patch added patch discarded remove patch
@@ -22,382 +22,382 @@
 block discarded – undo
22 22
  */
23 23
 class Wordlift_Term_JsonLd_Adapter {
24 24
 
25
-	/**
26
-	 * The {@link Wordlift_Entity_Uri_Service} instance.
27
-	 *
28
-	 * @since 3.20.0
29
-	 * @access private
30
-	 * @var \Wordlift_Entity_Uri_Service $entity_uri_service The {@link Wordlift_Entity_Uri_Service} instance.
31
-	 */
32
-	private $entity_uri_service;
33
-
34
-	/**
35
-	 * Instance.
36
-	 *
37
-	 * @var Wordlift_Term_JsonLd_Adapter
38
-	 */
39
-	private static $instance;
40
-
41
-	/**
42
-	 * @var Wordlift_Post_Converter
43
-	 */
44
-	private $post_id_to_jsonld_converter;
45
-
46
-	/**
47
-	 * The {@link Api_Service} used to communicate with the remote APIs.
48
-	 *
49
-	 * @access private
50
-	 * @var Default_Api_Service
51
-	 */
52
-	private $api_service;
53
-
54
-	/**
55
-	 * Wordlift_Term_JsonLd_Adapter constructor.
56
-	 *
57
-	 * @param \Wordlift_Entity_Uri_Service $entity_uri_service The {@link Wordlift_Entity_Uri_Service} instance.
58
-	 * @param \Wordlift_Post_Converter     $post_id_to_jsonld_converter The {@link Wordlift_Post_Converter} instance.
59
-	 *
60
-	 * @since 3.20.0
61
-	 */
62
-	public function __construct( $entity_uri_service, $post_id_to_jsonld_converter ) {
63
-
64
-		add_action( 'wp_head', array( $this, 'wp_head' ) );
65
-
66
-		$this->api_service                 = Default_Api_Service::get_instance();
67
-		$this->entity_uri_service          = $entity_uri_service;
68
-		$this->post_id_to_jsonld_converter = $post_id_to_jsonld_converter;
69
-
70
-		self::$instance = $this;
71
-	}
72
-
73
-	/**
74
-	 * Get instance.
75
-	 *
76
-	 * @return Wordlift_Term_JsonLd_Adapter|static
77
-	 */
78
-	public static function get_instance() {
79
-		return self::$instance;
80
-	}
81
-
82
-	/**
83
-	 * Adds carousel json ld data to term page if the conditions match
84
-	 *
85
-	 * @return array|boolean
86
-	 */
87
-	public function get_carousel_jsonld( $id = null ) {
88
-		$posts       = $this->get_posts( $id );
89
-		$post_jsonld = array();
90
-		if ( ! is_array( $posts ) || count( $posts ) < 2 ) {
91
-			// Bail out if no posts are present.
92
-			return false;
93
-		}
94
-
95
-		if ( $id !== null ) {
96
-			$term                       = get_term( $id );
97
-			$post_jsonld['description'] = wp_strip_all_tags( strip_shortcodes( $term->description ) );
98
-			$thumbnail_id               = get_term_meta( $id, 'thumbnail_id', true );
99
-			if ( ! empty( $thumbnail_id ) ) {
100
-				$post_jsonld['image'] = wp_get_attachment_url( $thumbnail_id );
101
-			}
102
-		}
103
-
104
-		// More than 2 items are present, so construct the post_jsonld data
105
-		$post_jsonld['@context']        = 'https://schema.org';
106
-		$post_jsonld['@type']           = 'ItemList';
107
-		$post_jsonld['url']             = $this->get_term_url( $id );
108
-		$post_jsonld['itemListElement'] = array();
109
-		$position                       = 1;
110
-
111
-		foreach ( $posts as $post_id ) {
112
-			$result = array(
113
-				'@type'    => 'ListItem',
114
-				'position' => $position,
115
-				/**
116
-				 * We can't use `item` here unless we change the URL for the item to point to the current page.
117
-				 *
118
-				 * See https://developers.google.com/search/docs/data-types/carousel
119
-				 */
120
-				'url'      => apply_filters( 'wl_carousel_post_list_item_url', get_permalink( $post_id ), $post_id ),
121
-			);
122
-			array_push( $post_jsonld['itemListElement'], $result );
123
-			++ $position;
124
-		}
125
-
126
-		return $post_jsonld;
127
-	}
128
-
129
-	/**
130
-	 * Get posts.
131
-	 *
132
-	 * @param $id
133
-	 *
134
-	 * @return array|int[]|string[]|WP_Error|null
135
-	 */
136
-	private function get_posts( $id ) {
137
-		global $wp_query;
138
-
139
-		if ( $wp_query->posts !== null ) {
140
-			return array_map(
141
-				function ( $post ) {
142
-					return $post->ID;
143
-				},
144
-				$wp_query->posts
145
-			);
146
-		}
147
-
148
-		if ( $id === null ) {
149
-			return null;
150
-		}
151
-
152
-		$term = get_term( $id );
153
-
154
-		return get_objects_in_term( $id, $term->taxonomy );
155
-	}
156
-
157
-	/**
158
-	 * Hook to `wp_head` to print the JSON-LD.
159
-	 *
160
-	 * @since 3.20.0
161
-	 */
162
-	public function wp_head() {
163
-		$query_object = get_queried_object();
164
-
165
-		// Check if it is a term page.
166
-		if ( ! $query_object instanceof WP_Term ) {
167
-			return;
168
-		}
169
-
170
-		// Bail out if `wl_jsonld_enabled` isn't enabled.
171
-		if ( ! apply_filters( 'wl_jsonld_enabled', true ) ) {
172
-			return;
173
-		}
174
-
175
-		$term_id = $query_object->term_id;
176
-
177
-		$jsonld = $this->get( $term_id, Jsonld_Context_Enum::PAGE );
178
-
179
-		// Bail out if the JSON-LD is empty.
180
-		if ( empty( $jsonld ) ) {
181
-			return;
182
-		}
183
-
184
-		$jsonld_string = wp_json_encode( $jsonld );
185
-
186
-		$jsonld_term_html_output = '<script type="application/ld+json" id="wl-jsonld-term">' . $jsonld_string . '</script>';
187
-		$jsonld_term_html_output = apply_filters( 'wl_jsonld_term_html_output', $jsonld_term_html_output, $term_id );
188
-
189
-		echo $jsonld_term_html_output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- It's an application/ld+json output.
190
-
191
-	}
192
-
193
-	/**
194
-	 * Get.
195
-	 *
196
-	 * @param $id
197
-	 * @param $context
198
-	 * @param $is_recursive_call
199
-	 *
200
-	 * @return array
201
-	 */
202
-	public function get( $id, $context, $is_recursive_call = false ) {
203
-		/**
204
-		 * Support for carousel rich snippet, get jsonld data present
205
-		 * for all the posts shown in the term page, and add the jsonld data
206
-		 * to list
207
-		 *
208
-		 * see here: https://developers.google.com/search/docs/data-types/carousel
209
-		 *
210
-		 * @since 3.26.0
211
-		 */
212
-		$jsonld_array = array();
213
-
214
-		if ( Jsonld_Context_Enum::PAGE === $context ) {
215
-			$carousel_data = $this->get_carousel_jsonld( $id );
216
-			if ( $carousel_data ) {
217
-				$jsonld_array[] = $carousel_data;
218
-			}
219
-		}
220
-
221
-		$entities_jsonld_array = $this->get_entity_jsonld( $id, $context );
222
-
223
-		$result = array(
224
-			'jsonld'     => array_merge( $jsonld_array, $entities_jsonld_array ),
225
-			'references' => array(),
226
-		);
227
-
228
-		/**
229
-		 * @since 3.26.3
230
-		 * Filter: wl_term_jsonld_array
231
-		 * @var $id int Term id
232
-		 * @var $jsonld_array array An array containing jsonld for term and entities.
233
-		 */
234
-		$arr = apply_filters( 'wl_term_jsonld_array', $result, $id );
235
-
236
-		$references = array();
237
-
238
-		// Don't expand nested references, it will lead to an infinite loop.
239
-		if ( ! $is_recursive_call ) {
240
-			/**
241
-			 * @since 3.32.0
242
-			 * Expand the references returned by this filter.
243
-			 */
244
-			$references = $this->expand_references( $arr['references'] );
245
-		}
246
-
247
-		$jsonld_array = array_merge( $arr['jsonld'], $references );
248
-
249
-		$this->set_terms_request( $jsonld_array, $id, $context );
250
-
251
-		return $jsonld_array;
252
-	}
253
-
254
-	/**
255
-	 * Get term url.
256
-	 *
257
-	 * @param $id
258
-	 *
259
-	 * @return array|false|int|mixed|string|WP_Error|WP_Term|null
260
-	 */
261
-	private function get_term_url( $id ) {
262
-		if ( null === $id ) {
263
-			return isset( $_SERVER['REQUEST_URI'] ) ? filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ), FILTER_SANITIZE_URL ) : '';
264
-		}
265
-
266
-		$maybe_url = get_term_meta( $id, Wordlift_Url_Property_Service::META_KEY, true );
267
-		if ( ! empty( $maybe_url ) ) {
268
-			return $maybe_url;
269
-		}
270
-
271
-		return get_term_link( $id );
272
-	}
273
-
274
-	/**
275
-	 * Return jsonld for entities bound to terms.
276
-	 *
277
-	 * @param int $term_id Term ID.
278
-	 * @param int $context A context for the JSON-LD generation, valid values in Jsonld_Context_Enum.
279
-	 *
280
-	 * @return array
281
-	 */
282
-	// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
283
-	private function get_entity_jsonld( $term_id, $context ) {
284
-
285
-		// The `_wl_entity_id` are URIs.
286
-		$entity_ids         = get_term_meta( $term_id, '_wl_entity_id' );
287
-		$entity_uri_service = $this->entity_uri_service;
288
-
289
-		$wordlift_jsonld_service = Wordlift_Jsonld_Service::get_instance();
290
-
291
-		$local_entity_ids = array_filter(
292
-			$entity_ids,
293
-			function ( $uri ) use ( $entity_uri_service ) {
294
-				return $entity_uri_service->is_internal( $uri );
295
-			}
296
-		);
297
-
298
-		// Bail out if there are no entities.
299
-		if ( empty( $local_entity_ids ) ) {
300
-			return array();
301
-		}
302
-
303
-		$post            = $this->entity_uri_service->get_entity( array_shift( $local_entity_ids ) );
304
-		$entities_jsonld = $wordlift_jsonld_service->get_jsonld( false, $post->ID );
305
-		// Reset the `url` to the term page.
306
-		$entities_jsonld[0]['url'] = get_term_link( $term_id );
307
-
308
-		return $entities_jsonld;
309
-	}
310
-
311
-	/**
312
-	 * @param $references
313
-	 *
314
-	 * @return array
315
-	 */
316
-	private function expand_references( $references ) {
317
-		if ( ! is_array( $references ) ) {
318
-			return array();
319
-		}
320
-		$references_jsonld = array();
321
-		// Expand the references.
322
-		foreach ( $references as $reference ) {
323
-			if ( $reference instanceof Term_Reference ) {
324
-				// Second level references won't be expanded.
325
-				$references_jsonld[] = current( $this->get( $reference->get_id(), Jsonld_Context_Enum::UNKNOWN, true ) );
326
-			} elseif ( is_numeric( $reference ) ) {
327
-				$ref_2               = array();
328
-				$ref_info_2          = array();
329
-				$references_jsonld[] = $this->post_id_to_jsonld_converter->convert( $reference, $ref_2, $ref_info_2, new Relations() );
330
-			} elseif ( $reference instanceof Post_Reference ) {
331
-				$ref_2               = array();
332
-				$ref_info_2          = array();
333
-				$references_jsonld[] = $this->post_id_to_jsonld_converter->convert( $reference->get_id(), $ref_2, $ref_info_2, new Relations() );
334
-			}
335
-		}
336
-
337
-		return $references_jsonld;
338
-	}
339
-
340
-	/**
341
-	 * Set events request.
342
-	 *
343
-	 * @param $jsonld_arr array The final jsonld before outputting to page.
344
-	 * @param $term_id int The term id for which the jsonld is generated.
345
-	 * @param $context int A context for the JSON-LD generation, valid values in Jsonld_Context_Enum
346
-	 */
347
-	private function set_terms_request( $jsonld_arr, $term_id, $context ) {
348
-		// If context is not PAGE or the array is empty, return early.
349
-		if ( Jsonld_Context_Enum::PAGE !== $context || empty( $jsonld_arr[0] ) ) {
350
-			return;
351
-		}
352
-
353
-		// Flag to indicate if we should make an API request.
354
-		$change_status = false;
355
-
356
-		// Get data from the array.
357
-		$data = $jsonld_arr[0];
358
-
359
-		// Fetch the initial 'about' and 'mentions' counts from term meta.
360
-		$counts = [
361
-			'about'    => get_term_meta( $term_id, 'wl_about_count', true ) ? : 0,
362
-			'mentions' => get_term_meta( $term_id, 'wl_mentions_count', true ) ? : 0,
363
-		];
364
-
365
-		// Iterate over the counts array.
366
-		foreach ( $counts as $key => $count ) {
367
-			// Check if data has 'about' or 'mentions' and the count is different from the existing meta value.
368
-			if ( ! empty( $data[ $key ] ) ) {
369
-				$new_count = count( $data[ $key ] );
370
-				if ( $count !== $new_count ) {
371
-					// Set flag to true if counts have changed.
372
-					$change_status = true;
373
-
374
-					// Update the counts array with new count.
375
-					$counts[ $key ] = $new_count;
376
-
377
-					// Update term meta with new count.
378
-					update_term_meta( $term_id, 'wl_' . $key . '_count', $new_count );
379
-				}
380
-			}
381
-		}
382
-
383
-		// If the count has changed, make the API request.
384
-		if ( $change_status ) {
385
-			$this->api_service->request(
386
-				'POST',
387
-				'/plugin/events',
388
-				[ 'Content-Type' => 'application/json' ],
389
-				wp_json_encode( [
390
-					'source' => 'jsonld',
391
-					'args'   => [
392
-						[ 'about_count' => $counts['about'] ],
393
-						[ 'mentions_count' => $counts['mentions'] ],
394
-					],
395
-					'url'    => $this->get_term_url( $term_id ),
396
-				] ),
397
-				0.001,
398
-				null,
399
-				[ 'blocking' => false ]
400
-			);
401
-		}
402
-	}
25
+    /**
26
+     * The {@link Wordlift_Entity_Uri_Service} instance.
27
+     *
28
+     * @since 3.20.0
29
+     * @access private
30
+     * @var \Wordlift_Entity_Uri_Service $entity_uri_service The {@link Wordlift_Entity_Uri_Service} instance.
31
+     */
32
+    private $entity_uri_service;
33
+
34
+    /**
35
+     * Instance.
36
+     *
37
+     * @var Wordlift_Term_JsonLd_Adapter
38
+     */
39
+    private static $instance;
40
+
41
+    /**
42
+     * @var Wordlift_Post_Converter
43
+     */
44
+    private $post_id_to_jsonld_converter;
45
+
46
+    /**
47
+     * The {@link Api_Service} used to communicate with the remote APIs.
48
+     *
49
+     * @access private
50
+     * @var Default_Api_Service
51
+     */
52
+    private $api_service;
53
+
54
+    /**
55
+     * Wordlift_Term_JsonLd_Adapter constructor.
56
+     *
57
+     * @param \Wordlift_Entity_Uri_Service $entity_uri_service The {@link Wordlift_Entity_Uri_Service} instance.
58
+     * @param \Wordlift_Post_Converter     $post_id_to_jsonld_converter The {@link Wordlift_Post_Converter} instance.
59
+     *
60
+     * @since 3.20.0
61
+     */
62
+    public function __construct( $entity_uri_service, $post_id_to_jsonld_converter ) {
63
+
64
+        add_action( 'wp_head', array( $this, 'wp_head' ) );
65
+
66
+        $this->api_service                 = Default_Api_Service::get_instance();
67
+        $this->entity_uri_service          = $entity_uri_service;
68
+        $this->post_id_to_jsonld_converter = $post_id_to_jsonld_converter;
69
+
70
+        self::$instance = $this;
71
+    }
72
+
73
+    /**
74
+     * Get instance.
75
+     *
76
+     * @return Wordlift_Term_JsonLd_Adapter|static
77
+     */
78
+    public static function get_instance() {
79
+        return self::$instance;
80
+    }
81
+
82
+    /**
83
+     * Adds carousel json ld data to term page if the conditions match
84
+     *
85
+     * @return array|boolean
86
+     */
87
+    public function get_carousel_jsonld( $id = null ) {
88
+        $posts       = $this->get_posts( $id );
89
+        $post_jsonld = array();
90
+        if ( ! is_array( $posts ) || count( $posts ) < 2 ) {
91
+            // Bail out if no posts are present.
92
+            return false;
93
+        }
94
+
95
+        if ( $id !== null ) {
96
+            $term                       = get_term( $id );
97
+            $post_jsonld['description'] = wp_strip_all_tags( strip_shortcodes( $term->description ) );
98
+            $thumbnail_id               = get_term_meta( $id, 'thumbnail_id', true );
99
+            if ( ! empty( $thumbnail_id ) ) {
100
+                $post_jsonld['image'] = wp_get_attachment_url( $thumbnail_id );
101
+            }
102
+        }
103
+
104
+        // More than 2 items are present, so construct the post_jsonld data
105
+        $post_jsonld['@context']        = 'https://schema.org';
106
+        $post_jsonld['@type']           = 'ItemList';
107
+        $post_jsonld['url']             = $this->get_term_url( $id );
108
+        $post_jsonld['itemListElement'] = array();
109
+        $position                       = 1;
110
+
111
+        foreach ( $posts as $post_id ) {
112
+            $result = array(
113
+                '@type'    => 'ListItem',
114
+                'position' => $position,
115
+                /**
116
+                 * We can't use `item` here unless we change the URL for the item to point to the current page.
117
+                 *
118
+                 * See https://developers.google.com/search/docs/data-types/carousel
119
+                 */
120
+                'url'      => apply_filters( 'wl_carousel_post_list_item_url', get_permalink( $post_id ), $post_id ),
121
+            );
122
+            array_push( $post_jsonld['itemListElement'], $result );
123
+            ++ $position;
124
+        }
125
+
126
+        return $post_jsonld;
127
+    }
128
+
129
+    /**
130
+     * Get posts.
131
+     *
132
+     * @param $id
133
+     *
134
+     * @return array|int[]|string[]|WP_Error|null
135
+     */
136
+    private function get_posts( $id ) {
137
+        global $wp_query;
138
+
139
+        if ( $wp_query->posts !== null ) {
140
+            return array_map(
141
+                function ( $post ) {
142
+                    return $post->ID;
143
+                },
144
+                $wp_query->posts
145
+            );
146
+        }
147
+
148
+        if ( $id === null ) {
149
+            return null;
150
+        }
151
+
152
+        $term = get_term( $id );
153
+
154
+        return get_objects_in_term( $id, $term->taxonomy );
155
+    }
156
+
157
+    /**
158
+     * Hook to `wp_head` to print the JSON-LD.
159
+     *
160
+     * @since 3.20.0
161
+     */
162
+    public function wp_head() {
163
+        $query_object = get_queried_object();
164
+
165
+        // Check if it is a term page.
166
+        if ( ! $query_object instanceof WP_Term ) {
167
+            return;
168
+        }
169
+
170
+        // Bail out if `wl_jsonld_enabled` isn't enabled.
171
+        if ( ! apply_filters( 'wl_jsonld_enabled', true ) ) {
172
+            return;
173
+        }
174
+
175
+        $term_id = $query_object->term_id;
176
+
177
+        $jsonld = $this->get( $term_id, Jsonld_Context_Enum::PAGE );
178
+
179
+        // Bail out if the JSON-LD is empty.
180
+        if ( empty( $jsonld ) ) {
181
+            return;
182
+        }
183
+
184
+        $jsonld_string = wp_json_encode( $jsonld );
185
+
186
+        $jsonld_term_html_output = '<script type="application/ld+json" id="wl-jsonld-term">' . $jsonld_string . '</script>';
187
+        $jsonld_term_html_output = apply_filters( 'wl_jsonld_term_html_output', $jsonld_term_html_output, $term_id );
188
+
189
+        echo $jsonld_term_html_output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- It's an application/ld+json output.
190
+
191
+    }
192
+
193
+    /**
194
+     * Get.
195
+     *
196
+     * @param $id
197
+     * @param $context
198
+     * @param $is_recursive_call
199
+     *
200
+     * @return array
201
+     */
202
+    public function get( $id, $context, $is_recursive_call = false ) {
203
+        /**
204
+         * Support for carousel rich snippet, get jsonld data present
205
+         * for all the posts shown in the term page, and add the jsonld data
206
+         * to list
207
+         *
208
+         * see here: https://developers.google.com/search/docs/data-types/carousel
209
+         *
210
+         * @since 3.26.0
211
+         */
212
+        $jsonld_array = array();
213
+
214
+        if ( Jsonld_Context_Enum::PAGE === $context ) {
215
+            $carousel_data = $this->get_carousel_jsonld( $id );
216
+            if ( $carousel_data ) {
217
+                $jsonld_array[] = $carousel_data;
218
+            }
219
+        }
220
+
221
+        $entities_jsonld_array = $this->get_entity_jsonld( $id, $context );
222
+
223
+        $result = array(
224
+            'jsonld'     => array_merge( $jsonld_array, $entities_jsonld_array ),
225
+            'references' => array(),
226
+        );
227
+
228
+        /**
229
+         * @since 3.26.3
230
+         * Filter: wl_term_jsonld_array
231
+         * @var $id int Term id
232
+         * @var $jsonld_array array An array containing jsonld for term and entities.
233
+         */
234
+        $arr = apply_filters( 'wl_term_jsonld_array', $result, $id );
235
+
236
+        $references = array();
237
+
238
+        // Don't expand nested references, it will lead to an infinite loop.
239
+        if ( ! $is_recursive_call ) {
240
+            /**
241
+             * @since 3.32.0
242
+             * Expand the references returned by this filter.
243
+             */
244
+            $references = $this->expand_references( $arr['references'] );
245
+        }
246
+
247
+        $jsonld_array = array_merge( $arr['jsonld'], $references );
248
+
249
+        $this->set_terms_request( $jsonld_array, $id, $context );
250
+
251
+        return $jsonld_array;
252
+    }
253
+
254
+    /**
255
+     * Get term url.
256
+     *
257
+     * @param $id
258
+     *
259
+     * @return array|false|int|mixed|string|WP_Error|WP_Term|null
260
+     */
261
+    private function get_term_url( $id ) {
262
+        if ( null === $id ) {
263
+            return isset( $_SERVER['REQUEST_URI'] ) ? filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ), FILTER_SANITIZE_URL ) : '';
264
+        }
265
+
266
+        $maybe_url = get_term_meta( $id, Wordlift_Url_Property_Service::META_KEY, true );
267
+        if ( ! empty( $maybe_url ) ) {
268
+            return $maybe_url;
269
+        }
270
+
271
+        return get_term_link( $id );
272
+    }
273
+
274
+    /**
275
+     * Return jsonld for entities bound to terms.
276
+     *
277
+     * @param int $term_id Term ID.
278
+     * @param int $context A context for the JSON-LD generation, valid values in Jsonld_Context_Enum.
279
+     *
280
+     * @return array
281
+     */
282
+    // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
283
+    private function get_entity_jsonld( $term_id, $context ) {
284
+
285
+        // The `_wl_entity_id` are URIs.
286
+        $entity_ids         = get_term_meta( $term_id, '_wl_entity_id' );
287
+        $entity_uri_service = $this->entity_uri_service;
288
+
289
+        $wordlift_jsonld_service = Wordlift_Jsonld_Service::get_instance();
290
+
291
+        $local_entity_ids = array_filter(
292
+            $entity_ids,
293
+            function ( $uri ) use ( $entity_uri_service ) {
294
+                return $entity_uri_service->is_internal( $uri );
295
+            }
296
+        );
297
+
298
+        // Bail out if there are no entities.
299
+        if ( empty( $local_entity_ids ) ) {
300
+            return array();
301
+        }
302
+
303
+        $post            = $this->entity_uri_service->get_entity( array_shift( $local_entity_ids ) );
304
+        $entities_jsonld = $wordlift_jsonld_service->get_jsonld( false, $post->ID );
305
+        // Reset the `url` to the term page.
306
+        $entities_jsonld[0]['url'] = get_term_link( $term_id );
307
+
308
+        return $entities_jsonld;
309
+    }
310
+
311
+    /**
312
+     * @param $references
313
+     *
314
+     * @return array
315
+     */
316
+    private function expand_references( $references ) {
317
+        if ( ! is_array( $references ) ) {
318
+            return array();
319
+        }
320
+        $references_jsonld = array();
321
+        // Expand the references.
322
+        foreach ( $references as $reference ) {
323
+            if ( $reference instanceof Term_Reference ) {
324
+                // Second level references won't be expanded.
325
+                $references_jsonld[] = current( $this->get( $reference->get_id(), Jsonld_Context_Enum::UNKNOWN, true ) );
326
+            } elseif ( is_numeric( $reference ) ) {
327
+                $ref_2               = array();
328
+                $ref_info_2          = array();
329
+                $references_jsonld[] = $this->post_id_to_jsonld_converter->convert( $reference, $ref_2, $ref_info_2, new Relations() );
330
+            } elseif ( $reference instanceof Post_Reference ) {
331
+                $ref_2               = array();
332
+                $ref_info_2          = array();
333
+                $references_jsonld[] = $this->post_id_to_jsonld_converter->convert( $reference->get_id(), $ref_2, $ref_info_2, new Relations() );
334
+            }
335
+        }
336
+
337
+        return $references_jsonld;
338
+    }
339
+
340
+    /**
341
+     * Set events request.
342
+     *
343
+     * @param $jsonld_arr array The final jsonld before outputting to page.
344
+     * @param $term_id int The term id for which the jsonld is generated.
345
+     * @param $context int A context for the JSON-LD generation, valid values in Jsonld_Context_Enum
346
+     */
347
+    private function set_terms_request( $jsonld_arr, $term_id, $context ) {
348
+        // If context is not PAGE or the array is empty, return early.
349
+        if ( Jsonld_Context_Enum::PAGE !== $context || empty( $jsonld_arr[0] ) ) {
350
+            return;
351
+        }
352
+
353
+        // Flag to indicate if we should make an API request.
354
+        $change_status = false;
355
+
356
+        // Get data from the array.
357
+        $data = $jsonld_arr[0];
358
+
359
+        // Fetch the initial 'about' and 'mentions' counts from term meta.
360
+        $counts = [
361
+            'about'    => get_term_meta( $term_id, 'wl_about_count', true ) ? : 0,
362
+            'mentions' => get_term_meta( $term_id, 'wl_mentions_count', true ) ? : 0,
363
+        ];
364
+
365
+        // Iterate over the counts array.
366
+        foreach ( $counts as $key => $count ) {
367
+            // Check if data has 'about' or 'mentions' and the count is different from the existing meta value.
368
+            if ( ! empty( $data[ $key ] ) ) {
369
+                $new_count = count( $data[ $key ] );
370
+                if ( $count !== $new_count ) {
371
+                    // Set flag to true if counts have changed.
372
+                    $change_status = true;
373
+
374
+                    // Update the counts array with new count.
375
+                    $counts[ $key ] = $new_count;
376
+
377
+                    // Update term meta with new count.
378
+                    update_term_meta( $term_id, 'wl_' . $key . '_count', $new_count );
379
+                }
380
+            }
381
+        }
382
+
383
+        // If the count has changed, make the API request.
384
+        if ( $change_status ) {
385
+            $this->api_service->request(
386
+                'POST',
387
+                '/plugin/events',
388
+                [ 'Content-Type' => 'application/json' ],
389
+                wp_json_encode( [
390
+                    'source' => 'jsonld',
391
+                    'args'   => [
392
+                        [ 'about_count' => $counts['about'] ],
393
+                        [ 'mentions_count' => $counts['mentions'] ],
394
+                    ],
395
+                    'url'    => $this->get_term_url( $term_id ),
396
+                ] ),
397
+                0.001,
398
+                null,
399
+                [ 'blocking' => false ]
400
+            );
401
+        }
402
+    }
403 403
 }
Please login to merge, or discard this patch.
src/includes/class-wordlift-jsonld-service.php 1 patch
Indentation   +539 added lines, -539 removed lines patch added patch discarded remove patch
@@ -21,546 +21,546 @@
 block discarded – undo
21 21
  */
22 22
 class Wordlift_Jsonld_Service {
23 23
 
24
-	/**
25
-	 * Creative work types.
26
-	 *
27
-	 * @var string[]
28
-	 */
29
-	private static $creative_work_types = array(
30
-		'AmpStory',
31
-		'ArchiveComponent',
32
-		'Article',
33
-		'Atlas',
34
-		'Blog',
35
-		'Book',
36
-		'Chapter',
37
-		'Claim',
38
-		'Clip',
39
-		'Code',
40
-		'Collection',
41
-		'ComicStory',
42
-		'Comment',
43
-		'Conversation',
44
-		'Course',
45
-		'CreativeWork',
46
-		'CreativeWorkSeason',
47
-		'CreativeWorkSeries',
48
-		'DataCatalog',
49
-		'Dataset',
50
-		'DefinedTermSet',
51
-		'Diet',
52
-		'DigitalDocument',
53
-		'Drawing',
54
-		'EducationalOccupationalCredential',
55
-		'Episode',
56
-		'ExercisePlan',
57
-		'Game',
58
-		'Guide',
59
-		'HowTo',
60
-		'HowToDirection',
61
-		'HowToSection',
62
-		'HowToStep',
63
-		'HowToTip',
64
-		'HyperToc',
65
-		'HyperTocEntry',
66
-		'LearningResource',
67
-		'Legislation',
68
-		'Manuscript',
69
-		'Map',
70
-		'MathSolver',
71
-		'MediaObject',
72
-		'Menu',
73
-		'MenuSection',
74
-		'Message',
75
-		'Movie',
76
-		'MusicComposition',
77
-		'MusicPlaylist',
78
-		'MusicRecording',
79
-		'Painting',
80
-		'Photograph',
81
-		'Play',
82
-		'Poster',
83
-		'PublicationIssue',
84
-		'PublicationVolume',
85
-		'Quotation',
86
-		'Review',
87
-		'Sculpture',
88
-		'Season',
89
-		'SheetMusic',
90
-		'ShortStory',
91
-		'SoftwareApplication',
92
-		'SoftwareSourceCode',
93
-		'SpecialAnnouncement',
94
-		'Thesis',
95
-		'TvSeason',
96
-		'TvSeries',
97
-		'VisualArtwork',
98
-		'WebContent',
99
-		'WebPage',
100
-		'WebPageElement',
101
-		'WebSite',
102
-		'AdvertiserContentArticle',
103
-		'NewsArticle',
104
-		'Report',
105
-		'SatiricalArticle',
106
-		'ScholarlyArticle',
107
-		'SocialMediaPosting',
108
-		'TechArticle',
109
-		'AnalysisNewsArticle',
110
-		'AskPublicNewsArticle',
111
-		'BackgroundNewsArticle',
112
-		'OpinionNewsArticle',
113
-		'ReportageNewsArticle',
114
-		'ReviewNewsArticle',
115
-		'MedicalScholarlyArticle',
116
-		'BlogPosting',
117
-		'DiscussionForumPosting',
118
-		'LiveBlogPosting',
119
-		'ApiReference',
120
-		'Audiobook',
121
-		'MovieClip',
122
-		'RadioClip',
123
-		'TvClip',
124
-		'VideoGameClip',
125
-		'ProductCollection',
126
-		'ComicCoverArt',
127
-		'Answer',
128
-		'CorrectionComment',
129
-		'Question',
130
-		'PodcastSeason',
131
-		'RadioSeason',
132
-		'TvSeason',
133
-		'BookSeries',
134
-		'MovieSeries',
135
-		'Periodical',
136
-		'PodcastSeries',
137
-		'RadioSeries',
138
-		'TvSeries',
139
-		'VideoGameSeries',
140
-		'ComicSeries',
141
-		'Newspaper',
142
-		'DataFeed',
143
-		'CompleteDataFeed',
144
-		'CategoryCodeSet',
145
-		'NoteDigitalDocument',
146
-		'PresentationDigitalDocument',
147
-		'SpreadsheetDigitalDocument',
148
-		'TextDigitalDocument',
149
-		'PodcastEpisode',
150
-		'RadioEpisode',
151
-		'TvEpisode',
152
-		'VideoGame',
153
-		'Recipe',
154
-		'Course',
155
-		'Quiz',
156
-		'LegislationObject',
157
-		'AudioObject',
158
-		'DModel',
159
-		'DataDownload',
160
-		'ImageObject',
161
-		'LegislationObject',
162
-		'MusicVideoObject',
163
-		'VideoObject',
164
-		'Audiobook',
165
-		'Barcode',
166
-		'EmailMessage',
167
-		'MusicAlbum',
168
-		'MusicRelease',
169
-		'ComicIssue',
170
-		'ClaimReview',
171
-		'CriticReview',
172
-		'EmployerReview',
173
-		'MediaReview',
174
-		'Recommendation',
175
-		'UserReview',
176
-		'ReviewNewsArticle',
177
-		'MobileApplication',
178
-		'VideoGame',
179
-		'WebApplication',
180
-		'CoverArt',
181
-		'ComicCoverArt',
182
-		'HealthTopicContent',
183
-		'AboutPage',
184
-		'CheckoutPage',
185
-		'CollectionPage',
186
-		'ContactPage',
187
-		'FaqPage',
188
-		'ItemPage',
189
-		'MedicalWebPage',
190
-		'ProfilePage',
191
-		'QaPage',
192
-		'RealEstateListing',
193
-		'SearchResultsPage',
194
-		'MediaGallery',
195
-		'ImageGallery',
196
-		'VideoGallery',
197
-		'SiteNavigationElement',
198
-		'Table',
199
-		'WpAdBlock',
200
-		'WpFooter',
201
-		'WpHeader',
202
-		'WpSideBar',
203
-	);
204
-
205
-	/**
206
-	 * The singleton instance for the JSON-LD service.
207
-	 *
208
-	 * @since 3.15.1
209
-	 *
210
-	 * @var \Wordlift_Jsonld_Service $instance The singleton instance for the JSON-LD service.
211
-	 */
212
-	private static $instance;
213
-
214
-	/**
215
-	 * A {@link Wordlift_Entity_Service} instance.
216
-	 *
217
-	 * @since  3.8.0
218
-	 * @access private
219
-	 * @var Wordlift_Entity_Service $entity_service A {@link Wordlift_Entity_Service} instance.
220
-	 */
221
-	private $entity_service;
222
-
223
-	/**
224
-	 * A {@link Wordlift_Term_JsonLd_Adapter} instance.
225
-	 *
226
-	 * @since  3.32.0
227
-	 * @access private
228
-	 * @var Wordlift_Term_JsonLd_Adapter $entity_service A {@link Wordlift_Term_JsonLd_Adapter} instance.
229
-	 */
230
-	private $term_jsonld_adapter;
231
-
232
-	/**
233
-	 * A {@link Wordlift_Post_Converter} instance.
234
-	 *
235
-	 * @since  3.8.0
236
-	 * @access private
237
-	 * @var \Wordlift_Post_Converter A {@link Wordlift_Post_Converter} instance.
238
-	 */
239
-	private $converter;
240
-
241
-	/**
242
-	 * A {@link Wordlift_Website_Jsonld_Converter} instance.
243
-	 *
244
-	 * @since  3.14.0
245
-	 * @access private
246
-	 * @var \Wordlift_Website_Jsonld_Converter A {@link Wordlift_Website_Jsonld_Converter} instance.
247
-	 */
248
-	private $website_converter;
249
-
250
-	/**
251
-	 * The {@link Api_Service} used to communicate with the remote APIs.
252
-	 *
253
-	 * @access private
254
-	 * @var Default_Api_Service
255
-	 */
256
-	private $api_service;
257
-
258
-	/**
259
-	 * Create a JSON-LD service.
260
-	 *
261
-	 * @param \Wordlift_Entity_Service           $entity_service A {@link Wordlift_Entity_Service} instance.
262
-	 * @param \Wordlift_Post_Converter           $converter A {@link Wordlift_Uri_To_Jsonld_Converter} instance.
263
-	 * @param \Wordlift_Website_Jsonld_Converter $website_converter A {@link Wordlift_Website_Jsonld_Converter} instance.
264
-	 * @param \Wordlift_Term_JsonLd_Adapter      $term_jsonld_adapter
265
-	 *
266
-	 * @since 3.8.0
267
-	 */
268
-	public function __construct( $entity_service, $converter, $website_converter, $term_jsonld_adapter ) {
269
-
270
-		$this->api_service         = Default_Api_Service::get_instance();
271
-		$this->entity_service      = $entity_service;
272
-		$this->converter           = $converter;
273
-		$this->website_converter   = $website_converter;
274
-		$this->term_jsonld_adapter = $term_jsonld_adapter;
275
-		self::$instance            = $this;
276
-
277
-	}
278
-
279
-	/**
280
-	 * Get the singleton instance for the JSON-LD service.
281
-	 *
282
-	 * @return \Wordlift_Jsonld_Service The singleton instance for the JSON-LD service.
283
-	 * @since 3.15.1
284
-	 */
285
-	public static function get_instance() {
286
-
287
-		return self::$instance;
288
-	}
289
-
290
-	/**
291
-	 * Process calls to the AJAX 'wl_jsonld' endpoint.
292
-	 *
293
-	 * @since 3.8.0
294
-	 */
295
-	public function get() {
296
-		// Clear the buffer to be sure someone doesn't mess with our response.
297
-		//
298
-		// See https://github.com/insideout10/wordlift-plugin/issues/406.
299
-		// See https://codex.wordpress.org/AJAX_in_Plugins.
300
-		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
301
-		@ob_clean();
302
-
303
-		// Get the parameter from the request.
304
-		$is_homepage = isset( $_REQUEST['homepage'] ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
305
-		$post_id     = isset( $_REQUEST['id'] ) && is_numeric( $_REQUEST['id'] ) ? intval( $_REQUEST['id'] ) : null; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
306
-
307
-		// Send the generated JSON-LD.
308
-		$this->send_jsonld( $this->get_jsonld( $is_homepage, $post_id ) );
309
-
310
-	}
311
-
312
-	/**
313
-	 * A close of WP's own `wp_send_json` function which uses `application/ld+json` as content type.
314
-	 *
315
-	 * @param mixed $response Variable (usually an array or object) to encode as JSON,
316
-	 *                           then print and die.
317
-	 *
318
-	 * @since 3.18.5
319
-	 */
320
-	private function send_jsonld( $response ) {
321
-		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
322
-		@header( 'Content-Type: application/ld+json; charset=' . get_option( 'blog_charset' ) );
323
-		echo wp_json_encode( $response );
324
-		if ( apply_filters( 'wp_doing_ajax', defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
325
-			wp_die();
326
-		} else {
327
-			die;
328
-		}
329
-	}
330
-
331
-	/**
332
-	 * Get the JSON-LD.
333
-	 *
334
-	 * @param bool     $is_homepage Whether the JSON-LD for the homepage is being requested.
335
-	 * @param int|null $post_id The JSON-LD for the specified {@link WP_Post} id.
336
-	 * @param int      $context A context for the JSON-LD generation, valid values in Jsonld_Context_Enum.
337
-	 *
338
-	 * @return array A JSON-LD structure.
339
-	 * @since 3.15.1
340
-	 */
341
-	public function get_jsonld( $is_homepage = false, $post_id = null, $context = Jsonld_Context_Enum::UNKNOWN ) {
342
-		// Tell NewRelic to ignore us, otherwise NewRelic customers might receive
343
-		// e-mails with a low apdex score.
344
-		//
345
-		// See https://github.com/insideout10/wordlift-plugin/issues/521
346
-		Wordlift_NewRelic_Adapter::ignore_apdex();
347
-
348
-		// Switch to Website converter if is home page.
349
-		if ( $is_homepage ) {
350
-			/**
351
-			 * Filter: 'wordlift_disable_website_json_ld' - Allow disabling of the json+ld output.
352
-			 *
353
-			 * @since  3.14.0
354
-			 * @api    bool $display_search Whether or not to display json+ld search on the frontend.
355
-			 */
356
-			if ( apply_filters( 'wordlift_disable_website_json_ld', false ) ) {
357
-				return array();
358
-			}
359
-
360
-			// Set a reference to the website_converter.
361
-			$website_converter = $this->website_converter;
362
-
363
-			// Send JSON-LD.
364
-			return $website_converter->create_schema();
365
-		}
366
-
367
-		// If no id has been provided return an empty array.
368
-		if ( ! isset( $post_id ) ) {
369
-			return array();
370
-		}
371
-
372
-		// An array of references which is captured when converting an URI to a
373
-		// json which we gather to further expand our json-ld.
374
-		$references       = array();
375
-		$references_infos = array();
376
-
377
-		// Set a reference to the entity_to_jsonld_converter to use in the closures.
378
-		$entity_to_jsonld_converter = $this->converter;
379
-
380
-		$relations = new Relations();
381
-		$jsonld    = $entity_to_jsonld_converter->convert( $post_id, $references, $references_infos, $relations );
382
-
383
-		$graph = new Graph( $jsonld, $entity_to_jsonld_converter, Wordlift_Term_JsonLd_Adapter::get_instance() );
384
-
385
-		$schema_type = is_array( $jsonld['@type'] ) ? $jsonld['@type'] : array( $jsonld['@type'] );
386
-
387
-		// Add `about`/`mentions` only for `CreativeWork` and descendants.
388
-		if ( array_intersect( $schema_type, self::$creative_work_types ) ) {
389
-
390
-			foreach ( $relations->toArray() as $relation ) {
391
-
392
-				// Setting about or mentions by label match is currently supported only for posts
393
-				if ( Object_Type_Enum::POST !== $relation->get_object()->get_type() ) {
394
-					continue;
395
-				}
396
-
397
-				// Add the `mentions`/`about` prop.
398
-				$this->add_mention_or_about( $jsonld, $post_id, $relation );
399
-			}
400
-			$graph->set_main_jsonld( $jsonld );
401
-		}
402
-
403
-		$jsonld_arr = $graph->add_references( $references )
404
-			->add_relations( $relations )
405
-			->add_required_reference_infos( $references_infos )
406
-			->render( $context );
407
-
408
-		/**
409
-		 * Filter name: wl_after_get_jsonld
410
-		 *
411
-		 * @return array
412
-		 * @since 3.27.2
413
-		 * @var $jsonld_arr array The final jsonld before outputting to page.
414
-		 * @var $post_id int The post id for which the jsonld is generated.
415
-		 */
416
-		$jsonld_arr = apply_filters( 'wl_after_get_jsonld', $jsonld_arr, $post_id, $context );
417
-
418
-		$this->set_events_request( $jsonld_arr, $post_id, $context );
419
-
420
-		return $jsonld_arr;
421
-	}
422
-
423
-	/**
424
-	 * Write the JSON-LD in the head.
425
-	 *
426
-	 * This function isn't actually used, but may be used to quickly enable writing the JSON-LD synchronously to the
427
-	 * document head, using the `wp_head` hook.
428
-	 *
429
-	 * @since 3.18.5
430
-	 */
431
-	public function wp_head() {
432
-
433
-		// Determine whether this is the home page or whether we're displaying a single post.
434
-		$is_homepage = is_home() || is_front_page();
435
-		$post_id     = is_singular() ? get_the_ID() : null;
436
-
437
-		$jsonld = wp_json_encode( $this->get_jsonld( $is_homepage, $post_id, Jsonld_Context_Enum::PAGE ) );
438
-		?>
24
+    /**
25
+     * Creative work types.
26
+     *
27
+     * @var string[]
28
+     */
29
+    private static $creative_work_types = array(
30
+        'AmpStory',
31
+        'ArchiveComponent',
32
+        'Article',
33
+        'Atlas',
34
+        'Blog',
35
+        'Book',
36
+        'Chapter',
37
+        'Claim',
38
+        'Clip',
39
+        'Code',
40
+        'Collection',
41
+        'ComicStory',
42
+        'Comment',
43
+        'Conversation',
44
+        'Course',
45
+        'CreativeWork',
46
+        'CreativeWorkSeason',
47
+        'CreativeWorkSeries',
48
+        'DataCatalog',
49
+        'Dataset',
50
+        'DefinedTermSet',
51
+        'Diet',
52
+        'DigitalDocument',
53
+        'Drawing',
54
+        'EducationalOccupationalCredential',
55
+        'Episode',
56
+        'ExercisePlan',
57
+        'Game',
58
+        'Guide',
59
+        'HowTo',
60
+        'HowToDirection',
61
+        'HowToSection',
62
+        'HowToStep',
63
+        'HowToTip',
64
+        'HyperToc',
65
+        'HyperTocEntry',
66
+        'LearningResource',
67
+        'Legislation',
68
+        'Manuscript',
69
+        'Map',
70
+        'MathSolver',
71
+        'MediaObject',
72
+        'Menu',
73
+        'MenuSection',
74
+        'Message',
75
+        'Movie',
76
+        'MusicComposition',
77
+        'MusicPlaylist',
78
+        'MusicRecording',
79
+        'Painting',
80
+        'Photograph',
81
+        'Play',
82
+        'Poster',
83
+        'PublicationIssue',
84
+        'PublicationVolume',
85
+        'Quotation',
86
+        'Review',
87
+        'Sculpture',
88
+        'Season',
89
+        'SheetMusic',
90
+        'ShortStory',
91
+        'SoftwareApplication',
92
+        'SoftwareSourceCode',
93
+        'SpecialAnnouncement',
94
+        'Thesis',
95
+        'TvSeason',
96
+        'TvSeries',
97
+        'VisualArtwork',
98
+        'WebContent',
99
+        'WebPage',
100
+        'WebPageElement',
101
+        'WebSite',
102
+        'AdvertiserContentArticle',
103
+        'NewsArticle',
104
+        'Report',
105
+        'SatiricalArticle',
106
+        'ScholarlyArticle',
107
+        'SocialMediaPosting',
108
+        'TechArticle',
109
+        'AnalysisNewsArticle',
110
+        'AskPublicNewsArticle',
111
+        'BackgroundNewsArticle',
112
+        'OpinionNewsArticle',
113
+        'ReportageNewsArticle',
114
+        'ReviewNewsArticle',
115
+        'MedicalScholarlyArticle',
116
+        'BlogPosting',
117
+        'DiscussionForumPosting',
118
+        'LiveBlogPosting',
119
+        'ApiReference',
120
+        'Audiobook',
121
+        'MovieClip',
122
+        'RadioClip',
123
+        'TvClip',
124
+        'VideoGameClip',
125
+        'ProductCollection',
126
+        'ComicCoverArt',
127
+        'Answer',
128
+        'CorrectionComment',
129
+        'Question',
130
+        'PodcastSeason',
131
+        'RadioSeason',
132
+        'TvSeason',
133
+        'BookSeries',
134
+        'MovieSeries',
135
+        'Periodical',
136
+        'PodcastSeries',
137
+        'RadioSeries',
138
+        'TvSeries',
139
+        'VideoGameSeries',
140
+        'ComicSeries',
141
+        'Newspaper',
142
+        'DataFeed',
143
+        'CompleteDataFeed',
144
+        'CategoryCodeSet',
145
+        'NoteDigitalDocument',
146
+        'PresentationDigitalDocument',
147
+        'SpreadsheetDigitalDocument',
148
+        'TextDigitalDocument',
149
+        'PodcastEpisode',
150
+        'RadioEpisode',
151
+        'TvEpisode',
152
+        'VideoGame',
153
+        'Recipe',
154
+        'Course',
155
+        'Quiz',
156
+        'LegislationObject',
157
+        'AudioObject',
158
+        'DModel',
159
+        'DataDownload',
160
+        'ImageObject',
161
+        'LegislationObject',
162
+        'MusicVideoObject',
163
+        'VideoObject',
164
+        'Audiobook',
165
+        'Barcode',
166
+        'EmailMessage',
167
+        'MusicAlbum',
168
+        'MusicRelease',
169
+        'ComicIssue',
170
+        'ClaimReview',
171
+        'CriticReview',
172
+        'EmployerReview',
173
+        'MediaReview',
174
+        'Recommendation',
175
+        'UserReview',
176
+        'ReviewNewsArticle',
177
+        'MobileApplication',
178
+        'VideoGame',
179
+        'WebApplication',
180
+        'CoverArt',
181
+        'ComicCoverArt',
182
+        'HealthTopicContent',
183
+        'AboutPage',
184
+        'CheckoutPage',
185
+        'CollectionPage',
186
+        'ContactPage',
187
+        'FaqPage',
188
+        'ItemPage',
189
+        'MedicalWebPage',
190
+        'ProfilePage',
191
+        'QaPage',
192
+        'RealEstateListing',
193
+        'SearchResultsPage',
194
+        'MediaGallery',
195
+        'ImageGallery',
196
+        'VideoGallery',
197
+        'SiteNavigationElement',
198
+        'Table',
199
+        'WpAdBlock',
200
+        'WpFooter',
201
+        'WpHeader',
202
+        'WpSideBar',
203
+    );
204
+
205
+    /**
206
+     * The singleton instance for the JSON-LD service.
207
+     *
208
+     * @since 3.15.1
209
+     *
210
+     * @var \Wordlift_Jsonld_Service $instance The singleton instance for the JSON-LD service.
211
+     */
212
+    private static $instance;
213
+
214
+    /**
215
+     * A {@link Wordlift_Entity_Service} instance.
216
+     *
217
+     * @since  3.8.0
218
+     * @access private
219
+     * @var Wordlift_Entity_Service $entity_service A {@link Wordlift_Entity_Service} instance.
220
+     */
221
+    private $entity_service;
222
+
223
+    /**
224
+     * A {@link Wordlift_Term_JsonLd_Adapter} instance.
225
+     *
226
+     * @since  3.32.0
227
+     * @access private
228
+     * @var Wordlift_Term_JsonLd_Adapter $entity_service A {@link Wordlift_Term_JsonLd_Adapter} instance.
229
+     */
230
+    private $term_jsonld_adapter;
231
+
232
+    /**
233
+     * A {@link Wordlift_Post_Converter} instance.
234
+     *
235
+     * @since  3.8.0
236
+     * @access private
237
+     * @var \Wordlift_Post_Converter A {@link Wordlift_Post_Converter} instance.
238
+     */
239
+    private $converter;
240
+
241
+    /**
242
+     * A {@link Wordlift_Website_Jsonld_Converter} instance.
243
+     *
244
+     * @since  3.14.0
245
+     * @access private
246
+     * @var \Wordlift_Website_Jsonld_Converter A {@link Wordlift_Website_Jsonld_Converter} instance.
247
+     */
248
+    private $website_converter;
249
+
250
+    /**
251
+     * The {@link Api_Service} used to communicate with the remote APIs.
252
+     *
253
+     * @access private
254
+     * @var Default_Api_Service
255
+     */
256
+    private $api_service;
257
+
258
+    /**
259
+     * Create a JSON-LD service.
260
+     *
261
+     * @param \Wordlift_Entity_Service           $entity_service A {@link Wordlift_Entity_Service} instance.
262
+     * @param \Wordlift_Post_Converter           $converter A {@link Wordlift_Uri_To_Jsonld_Converter} instance.
263
+     * @param \Wordlift_Website_Jsonld_Converter $website_converter A {@link Wordlift_Website_Jsonld_Converter} instance.
264
+     * @param \Wordlift_Term_JsonLd_Adapter      $term_jsonld_adapter
265
+     *
266
+     * @since 3.8.0
267
+     */
268
+    public function __construct( $entity_service, $converter, $website_converter, $term_jsonld_adapter ) {
269
+
270
+        $this->api_service         = Default_Api_Service::get_instance();
271
+        $this->entity_service      = $entity_service;
272
+        $this->converter           = $converter;
273
+        $this->website_converter   = $website_converter;
274
+        $this->term_jsonld_adapter = $term_jsonld_adapter;
275
+        self::$instance            = $this;
276
+
277
+    }
278
+
279
+    /**
280
+     * Get the singleton instance for the JSON-LD service.
281
+     *
282
+     * @return \Wordlift_Jsonld_Service The singleton instance for the JSON-LD service.
283
+     * @since 3.15.1
284
+     */
285
+    public static function get_instance() {
286
+
287
+        return self::$instance;
288
+    }
289
+
290
+    /**
291
+     * Process calls to the AJAX 'wl_jsonld' endpoint.
292
+     *
293
+     * @since 3.8.0
294
+     */
295
+    public function get() {
296
+        // Clear the buffer to be sure someone doesn't mess with our response.
297
+        //
298
+        // See https://github.com/insideout10/wordlift-plugin/issues/406.
299
+        // See https://codex.wordpress.org/AJAX_in_Plugins.
300
+        // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
301
+        @ob_clean();
302
+
303
+        // Get the parameter from the request.
304
+        $is_homepage = isset( $_REQUEST['homepage'] ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
305
+        $post_id     = isset( $_REQUEST['id'] ) && is_numeric( $_REQUEST['id'] ) ? intval( $_REQUEST['id'] ) : null; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
306
+
307
+        // Send the generated JSON-LD.
308
+        $this->send_jsonld( $this->get_jsonld( $is_homepage, $post_id ) );
309
+
310
+    }
311
+
312
+    /**
313
+     * A close of WP's own `wp_send_json` function which uses `application/ld+json` as content type.
314
+     *
315
+     * @param mixed $response Variable (usually an array or object) to encode as JSON,
316
+     *                           then print and die.
317
+     *
318
+     * @since 3.18.5
319
+     */
320
+    private function send_jsonld( $response ) {
321
+        // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
322
+        @header( 'Content-Type: application/ld+json; charset=' . get_option( 'blog_charset' ) );
323
+        echo wp_json_encode( $response );
324
+        if ( apply_filters( 'wp_doing_ajax', defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
325
+            wp_die();
326
+        } else {
327
+            die;
328
+        }
329
+    }
330
+
331
+    /**
332
+     * Get the JSON-LD.
333
+     *
334
+     * @param bool     $is_homepage Whether the JSON-LD for the homepage is being requested.
335
+     * @param int|null $post_id The JSON-LD for the specified {@link WP_Post} id.
336
+     * @param int      $context A context for the JSON-LD generation, valid values in Jsonld_Context_Enum.
337
+     *
338
+     * @return array A JSON-LD structure.
339
+     * @since 3.15.1
340
+     */
341
+    public function get_jsonld( $is_homepage = false, $post_id = null, $context = Jsonld_Context_Enum::UNKNOWN ) {
342
+        // Tell NewRelic to ignore us, otherwise NewRelic customers might receive
343
+        // e-mails with a low apdex score.
344
+        //
345
+        // See https://github.com/insideout10/wordlift-plugin/issues/521
346
+        Wordlift_NewRelic_Adapter::ignore_apdex();
347
+
348
+        // Switch to Website converter if is home page.
349
+        if ( $is_homepage ) {
350
+            /**
351
+             * Filter: 'wordlift_disable_website_json_ld' - Allow disabling of the json+ld output.
352
+             *
353
+             * @since  3.14.0
354
+             * @api    bool $display_search Whether or not to display json+ld search on the frontend.
355
+             */
356
+            if ( apply_filters( 'wordlift_disable_website_json_ld', false ) ) {
357
+                return array();
358
+            }
359
+
360
+            // Set a reference to the website_converter.
361
+            $website_converter = $this->website_converter;
362
+
363
+            // Send JSON-LD.
364
+            return $website_converter->create_schema();
365
+        }
366
+
367
+        // If no id has been provided return an empty array.
368
+        if ( ! isset( $post_id ) ) {
369
+            return array();
370
+        }
371
+
372
+        // An array of references which is captured when converting an URI to a
373
+        // json which we gather to further expand our json-ld.
374
+        $references       = array();
375
+        $references_infos = array();
376
+
377
+        // Set a reference to the entity_to_jsonld_converter to use in the closures.
378
+        $entity_to_jsonld_converter = $this->converter;
379
+
380
+        $relations = new Relations();
381
+        $jsonld    = $entity_to_jsonld_converter->convert( $post_id, $references, $references_infos, $relations );
382
+
383
+        $graph = new Graph( $jsonld, $entity_to_jsonld_converter, Wordlift_Term_JsonLd_Adapter::get_instance() );
384
+
385
+        $schema_type = is_array( $jsonld['@type'] ) ? $jsonld['@type'] : array( $jsonld['@type'] );
386
+
387
+        // Add `about`/`mentions` only for `CreativeWork` and descendants.
388
+        if ( array_intersect( $schema_type, self::$creative_work_types ) ) {
389
+
390
+            foreach ( $relations->toArray() as $relation ) {
391
+
392
+                // Setting about or mentions by label match is currently supported only for posts
393
+                if ( Object_Type_Enum::POST !== $relation->get_object()->get_type() ) {
394
+                    continue;
395
+                }
396
+
397
+                // Add the `mentions`/`about` prop.
398
+                $this->add_mention_or_about( $jsonld, $post_id, $relation );
399
+            }
400
+            $graph->set_main_jsonld( $jsonld );
401
+        }
402
+
403
+        $jsonld_arr = $graph->add_references( $references )
404
+            ->add_relations( $relations )
405
+            ->add_required_reference_infos( $references_infos )
406
+            ->render( $context );
407
+
408
+        /**
409
+         * Filter name: wl_after_get_jsonld
410
+         *
411
+         * @return array
412
+         * @since 3.27.2
413
+         * @var $jsonld_arr array The final jsonld before outputting to page.
414
+         * @var $post_id int The post id for which the jsonld is generated.
415
+         */
416
+        $jsonld_arr = apply_filters( 'wl_after_get_jsonld', $jsonld_arr, $post_id, $context );
417
+
418
+        $this->set_events_request( $jsonld_arr, $post_id, $context );
419
+
420
+        return $jsonld_arr;
421
+    }
422
+
423
+    /**
424
+     * Write the JSON-LD in the head.
425
+     *
426
+     * This function isn't actually used, but may be used to quickly enable writing the JSON-LD synchronously to the
427
+     * document head, using the `wp_head` hook.
428
+     *
429
+     * @since 3.18.5
430
+     */
431
+    public function wp_head() {
432
+
433
+        // Determine whether this is the home page or whether we're displaying a single post.
434
+        $is_homepage = is_home() || is_front_page();
435
+        $post_id     = is_singular() ? get_the_ID() : null;
436
+
437
+        $jsonld = wp_json_encode( $this->get_jsonld( $is_homepage, $post_id, Jsonld_Context_Enum::PAGE ) );
438
+        ?>
439 439
 		<script type="application/ld+json"><?php echo esc_html( $jsonld ); ?></script>
440 440
 		<?php
441
-	}
442
-
443
-	/**
444
-	 * @param array    $jsonld
445
-	 * @param Relation $relation
446
-	 *
447
-	 * @return void
448
-	 */
449
-	private function add_mention_or_about( &$jsonld, $post_id, $relation ) {
450
-		$content_service = Wordpress_Content_Service::get_instance();
451
-		$entity_service  = Wordlift_Entity_Service::get_instance();
452
-
453
-		$object     = $relation->get_object();
454
-		$entity_uri = $content_service->get_entity_id( $object );
455
-		$labels     = $entity_service->get_labels( $object->get_id(), $object->get_type() );
456
-
457
-		$escaped_labels = array_map(
458
-			function ( $value ) {
459
-				return preg_quote( $value, '/' );
460
-			},
461
-			$labels
462
-		);
463
-
464
-		$matches = false;
465
-
466
-		// When the title is empty, then we shouldn't yield a match to about section.
467
-		if ( array_filter( $escaped_labels ) ) {
468
-			// Check if the labels match any part of the title.
469
-			$post    = get_post( $post_id );
470
-			$matches = $this->check_title_match( $escaped_labels, $post->post_title );
471
-		}
472
-
473
-		if ( $entity_uri ) {
474
-			// If the title matches, assign the entity to the about, otherwise to the mentions.
475
-			$property_name              = $matches ? 'about' : 'mentions';
476
-			$jsonld[ $property_name ]   = isset( $jsonld[ $property_name ] ) ? (array) $jsonld[ $property_name ] : array();
477
-			$jsonld[ $property_name ][] = array( '@id' => $entity_uri );
478
-		}
479
-
480
-	}
481
-
482
-	/**
483
-	 * Check if the labels match any part of the title.
484
-	 *
485
-	 * @param $labels array The labels to check.
486
-	 * @param $title string The title to check.
487
-	 *
488
-	 * @return boolean
489
-	 */
490
-	public function check_title_match( $labels, $title ) {
491
-
492
-		// If the title is empty, then we shouldn't yield a match to about section.
493
-		if ( empty( $title ) ) {
494
-			return false;
495
-		}
496
-
497
-		// Check if the labels match any part of the title.
498
-		return 1 === preg_match( '/\b(' . implode( '|', $labels ) . ')\b/iu', $title );
499
-
500
-	}
501
-
502
-	/**
503
-	 * Set events request.
504
-	 *
505
-	 * @param $jsonld_arr array The final jsonld before outputting to page.
506
-	 * @param $post_id int The post id for which the jsonld is generated.
507
-	 * @param $context int A context for the JSON-LD generation, valid values in Jsonld_Context_Enum
508
-	 */
509
-	private function set_events_request( $jsonld_arr, $post_id, $context ) {
510
-		// If context is not PAGE or the array is empty, return early.
511
-		if ( Jsonld_Context_Enum::PAGE !== $context || empty( $jsonld_arr[0] ) ) {
512
-			return;
513
-		}
514
-
515
-		// Flag to indicate if we should make an API request.
516
-		$change_status = false;
517
-
518
-		// Get data from the array.
519
-		$data = $jsonld_arr[0];
520
-
521
-		// Fetch the initial 'about' and 'mentions' counts from post meta.
522
-		$counts = [
523
-			'about'    => get_post_meta( $post_id, 'wl_about_count', true ) ? : 0,
524
-			'mentions' => get_post_meta( $post_id, 'wl_mentions_count', true ) ? : 0,
525
-		];
526
-
527
-		// Iterate over the counts array.
528
-		foreach ( $counts as $key => $count ) {
529
-			// Check if data has 'about' or 'mentions' and the count is different from the existing meta value.
530
-			if ( ! empty( $data[ $key ] ) ) {
531
-				$new_count = count( $data[ $key ] );
532
-				if ( $count !== $new_count ) {
533
-					// Set flag to true if counts have changed.
534
-					$change_status = true;
535
-
536
-					// Update the counts array with new count.
537
-					$counts[ $key ] = $new_count;
538
-
539
-					// Update post meta with new count.
540
-					update_post_meta( $post_id, 'wl_' . $key . '_count', $new_count );
541
-				}
542
-			}
543
-		}
544
-
545
-		// If the count has changed, make the API request.
546
-		if ( $change_status ) {
547
-			$this->api_service->request(
548
-				'POST',
549
-				'/plugin/events',
550
-				[ 'Content-Type' => 'application/json' ],
551
-				wp_json_encode( [
552
-					'source' => 'jsonld',
553
-					'args'   => [
554
-						[ 'about_count' => $counts['about'] ],
555
-						[ 'mentions_count' => $counts['mentions'] ],
556
-					],
557
-					'url'    => get_permalink( $post_id ),
558
-				] ),
559
-				0.001,
560
-				null,
561
-				[ 'blocking' => false ]
562
-			);
563
-		}
564
-	}
441
+    }
442
+
443
+    /**
444
+     * @param array    $jsonld
445
+     * @param Relation $relation
446
+     *
447
+     * @return void
448
+     */
449
+    private function add_mention_or_about( &$jsonld, $post_id, $relation ) {
450
+        $content_service = Wordpress_Content_Service::get_instance();
451
+        $entity_service  = Wordlift_Entity_Service::get_instance();
452
+
453
+        $object     = $relation->get_object();
454
+        $entity_uri = $content_service->get_entity_id( $object );
455
+        $labels     = $entity_service->get_labels( $object->get_id(), $object->get_type() );
456
+
457
+        $escaped_labels = array_map(
458
+            function ( $value ) {
459
+                return preg_quote( $value, '/' );
460
+            },
461
+            $labels
462
+        );
463
+
464
+        $matches = false;
465
+
466
+        // When the title is empty, then we shouldn't yield a match to about section.
467
+        if ( array_filter( $escaped_labels ) ) {
468
+            // Check if the labels match any part of the title.
469
+            $post    = get_post( $post_id );
470
+            $matches = $this->check_title_match( $escaped_labels, $post->post_title );
471
+        }
472
+
473
+        if ( $entity_uri ) {
474
+            // If the title matches, assign the entity to the about, otherwise to the mentions.
475
+            $property_name              = $matches ? 'about' : 'mentions';
476
+            $jsonld[ $property_name ]   = isset( $jsonld[ $property_name ] ) ? (array) $jsonld[ $property_name ] : array();
477
+            $jsonld[ $property_name ][] = array( '@id' => $entity_uri );
478
+        }
479
+
480
+    }
481
+
482
+    /**
483
+     * Check if the labels match any part of the title.
484
+     *
485
+     * @param $labels array The labels to check.
486
+     * @param $title string The title to check.
487
+     *
488
+     * @return boolean
489
+     */
490
+    public function check_title_match( $labels, $title ) {
491
+
492
+        // If the title is empty, then we shouldn't yield a match to about section.
493
+        if ( empty( $title ) ) {
494
+            return false;
495
+        }
496
+
497
+        // Check if the labels match any part of the title.
498
+        return 1 === preg_match( '/\b(' . implode( '|', $labels ) . ')\b/iu', $title );
499
+
500
+    }
501
+
502
+    /**
503
+     * Set events request.
504
+     *
505
+     * @param $jsonld_arr array The final jsonld before outputting to page.
506
+     * @param $post_id int The post id for which the jsonld is generated.
507
+     * @param $context int A context for the JSON-LD generation, valid values in Jsonld_Context_Enum
508
+     */
509
+    private function set_events_request( $jsonld_arr, $post_id, $context ) {
510
+        // If context is not PAGE or the array is empty, return early.
511
+        if ( Jsonld_Context_Enum::PAGE !== $context || empty( $jsonld_arr[0] ) ) {
512
+            return;
513
+        }
514
+
515
+        // Flag to indicate if we should make an API request.
516
+        $change_status = false;
517
+
518
+        // Get data from the array.
519
+        $data = $jsonld_arr[0];
520
+
521
+        // Fetch the initial 'about' and 'mentions' counts from post meta.
522
+        $counts = [
523
+            'about'    => get_post_meta( $post_id, 'wl_about_count', true ) ? : 0,
524
+            'mentions' => get_post_meta( $post_id, 'wl_mentions_count', true ) ? : 0,
525
+        ];
526
+
527
+        // Iterate over the counts array.
528
+        foreach ( $counts as $key => $count ) {
529
+            // Check if data has 'about' or 'mentions' and the count is different from the existing meta value.
530
+            if ( ! empty( $data[ $key ] ) ) {
531
+                $new_count = count( $data[ $key ] );
532
+                if ( $count !== $new_count ) {
533
+                    // Set flag to true if counts have changed.
534
+                    $change_status = true;
535
+
536
+                    // Update the counts array with new count.
537
+                    $counts[ $key ] = $new_count;
538
+
539
+                    // Update post meta with new count.
540
+                    update_post_meta( $post_id, 'wl_' . $key . '_count', $new_count );
541
+                }
542
+            }
543
+        }
544
+
545
+        // If the count has changed, make the API request.
546
+        if ( $change_status ) {
547
+            $this->api_service->request(
548
+                'POST',
549
+                '/plugin/events',
550
+                [ 'Content-Type' => 'application/json' ],
551
+                wp_json_encode( [
552
+                    'source' => 'jsonld',
553
+                    'args'   => [
554
+                        [ 'about_count' => $counts['about'] ],
555
+                        [ 'mentions_count' => $counts['mentions'] ],
556
+                    ],
557
+                    'url'    => get_permalink( $post_id ),
558
+                ] ),
559
+                0.001,
560
+                null,
561
+                [ 'blocking' => false ]
562
+            );
563
+        }
564
+    }
565 565
 
566 566
 }
Please login to merge, or discard this patch.