Completed
Push — develop ( 13b240...504ca3 )
by David
02:16 queued 15s
created
deliciousbrains/wp-background-processing/classes/wp-background-process.php 1 patch
Indentation   +764 added lines, -764 removed lines patch added patch discarded remove patch
@@ -13,774 +13,774 @@
 block discarded – undo
13 13
  */
14 14
 abstract class WP_Background_Process extends WP_Async_Request {
15 15
 
16
-	/**
17
-	 * Action
18
-	 *
19
-	 * (default value: 'background_process')
20
-	 *
21
-	 * @var string
22
-	 * @access protected
23
-	 */
24
-	protected $action = 'background_process';
25
-
26
-	/**
27
-	 * Start time of current process.
28
-	 *
29
-	 * (default value: 0)
30
-	 *
31
-	 * @var int
32
-	 * @access protected
33
-	 */
34
-	protected $start_time = 0;
35
-
36
-	/**
37
-	 * Cron_hook_identifier
38
-	 *
39
-	 * @var string
40
-	 * @access protected
41
-	 */
42
-	protected $cron_hook_identifier;
43
-
44
-	/**
45
-	 * Cron_interval_identifier
46
-	 *
47
-	 * @var string
48
-	 * @access protected
49
-	 */
50
-	protected $cron_interval_identifier;
51
-
52
-	/**
53
-	 * Restrict object instantiation when using unserialize.
54
-	 *
55
-	 * @var bool|array
56
-	 */
57
-	protected $allowed_batch_data_classes = true;
58
-
59
-	/**
60
-	 * The status set when process is cancelling.
61
-	 *
62
-	 * @var int
63
-	 */
64
-	const STATUS_CANCELLED = 1;
65
-
66
-	/**
67
-	 * The status set when process is paused or pausing.
68
-	 *
69
-	 * @var int;
70
-	 */
71
-	const STATUS_PAUSED = 2;
72
-
73
-	/**
74
-	 * Initiate new background process.
75
-	 *
76
-	 * @param bool|array $allowed_batch_data_classes Optional. Array of class names that can be unserialized. Default true (any class).
77
-	 */
78
-	public function __construct( $allowed_batch_data_classes = true ) {
79
-		parent::__construct();
80
-
81
-		if ( empty( $allowed_batch_data_classes ) && false !== $allowed_batch_data_classes ) {
82
-			$allowed_batch_data_classes = true;
83
-		}
84
-
85
-		if ( ! is_bool( $allowed_batch_data_classes ) && ! is_array( $allowed_batch_data_classes ) ) {
86
-			$allowed_batch_data_classes = true;
87
-		}
88
-
89
-		// If allowed_batch_data_classes property set in subclass,
90
-		// only apply override if not allowing any class.
91
-		if ( true === $this->allowed_batch_data_classes || true !== $allowed_batch_data_classes ) {
92
-			$this->allowed_batch_data_classes = $allowed_batch_data_classes;
93
-		}
94
-
95
-		$this->cron_hook_identifier     = $this->identifier . '_cron';
96
-		$this->cron_interval_identifier = $this->identifier . '_cron_interval';
97
-
98
-		add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) );
99
-		add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) );
100
-	}
101
-
102
-	/**
103
-	 * Schedule the cron healthcheck and dispatch an async request to start processing the queue.
104
-	 *
105
-	 * @access public
106
-	 * @return array|WP_Error|false HTTP Response array, WP_Error on failure, or false if not attempted.
107
-	 */
108
-	public function dispatch() {
109
-		if ( $this->is_processing() ) {
110
-			// Process already running.
111
-			return false;
112
-		}
113
-
114
-		// Schedule the cron healthcheck.
115
-		$this->schedule_event();
116
-
117
-		// Perform remote post.
118
-		return parent::dispatch();
119
-	}
120
-
121
-	/**
122
-	 * Push to the queue.
123
-	 *
124
-	 * Note, save must be called in order to persist queued items to a batch for processing.
125
-	 *
126
-	 * @param mixed $data Data.
127
-	 *
128
-	 * @return $this
129
-	 */
130
-	public function push_to_queue( $data ) {
131
-		$this->data[] = $data;
132
-
133
-		return $this;
134
-	}
135
-
136
-	/**
137
-	 * Save the queued items for future processing.
138
-	 *
139
-	 * @return $this
140
-	 */
141
-	public function save() {
142
-		$key = $this->generate_key();
143
-
144
-		if ( ! empty( $this->data ) ) {
145
-			update_site_option( $key, $this->data );
146
-		}
147
-
148
-		// Clean out data so that new data isn't prepended with closed session's data.
149
-		$this->data = array();
150
-
151
-		return $this;
152
-	}
153
-
154
-	/**
155
-	 * Update a batch's queued items.
156
-	 *
157
-	 * @param string $key  Key.
158
-	 * @param array  $data Data.
159
-	 *
160
-	 * @return $this
161
-	 */
162
-	public function update( $key, $data ) {
163
-		if ( ! empty( $data ) ) {
164
-			update_site_option( $key, $data );
165
-		}
166
-
167
-		return $this;
168
-	}
169
-
170
-	/**
171
-	 * Delete a batch of queued items.
172
-	 *
173
-	 * @param string $key Key.
174
-	 *
175
-	 * @return $this
176
-	 */
177
-	public function delete( $key ) {
178
-		delete_site_option( $key );
179
-
180
-		return $this;
181
-	}
182
-
183
-	/**
184
-	 * Delete entire job queue.
185
-	 */
186
-	public function delete_all() {
187
-		$batches = $this->get_batches();
188
-
189
-		foreach ( $batches as $batch ) {
190
-			$this->delete( $batch->key );
191
-		}
192
-
193
-		delete_site_option( $this->get_status_key() );
194
-
195
-		$this->cancelled();
196
-	}
197
-
198
-	/**
199
-	 * Cancel job on next batch.
200
-	 */
201
-	public function cancel() {
202
-		update_site_option( $this->get_status_key(), self::STATUS_CANCELLED );
203
-
204
-		// Just in case the job was paused at the time.
205
-		$this->dispatch();
206
-	}
207
-
208
-	/**
209
-	 * Has the process been cancelled?
210
-	 *
211
-	 * @return bool
212
-	 */
213
-	public function is_cancelled() {
214
-		$status = get_site_option( $this->get_status_key(), 0 );
215
-
216
-		return absint( $status ) === self::STATUS_CANCELLED;
217
-	}
218
-
219
-	/**
220
-	 * Called when background process has been cancelled.
221
-	 */
222
-	protected function cancelled() {
223
-		do_action( $this->identifier . '_cancelled' );
224
-	}
225
-
226
-	/**
227
-	 * Pause job on next batch.
228
-	 */
229
-	public function pause() {
230
-		update_site_option( $this->get_status_key(), self::STATUS_PAUSED );
231
-	}
232
-
233
-	/**
234
-	 * Is the job paused?
235
-	 *
236
-	 * @return bool
237
-	 */
238
-	public function is_paused() {
239
-		$status = get_site_option( $this->get_status_key(), 0 );
240
-
241
-		return absint( $status ) === self::STATUS_PAUSED;
242
-	}
243
-
244
-	/**
245
-	 * Called when background process has been paused.
246
-	 */
247
-	protected function paused() {
248
-		do_action( $this->identifier . '_paused' );
249
-	}
250
-
251
-	/**
252
-	 * Resume job.
253
-	 */
254
-	public function resume() {
255
-		delete_site_option( $this->get_status_key() );
256
-
257
-		$this->schedule_event();
258
-		$this->dispatch();
259
-		$this->resumed();
260
-	}
261
-
262
-	/**
263
-	 * Called when background process has been resumed.
264
-	 */
265
-	protected function resumed() {
266
-		do_action( $this->identifier . '_resumed' );
267
-	}
268
-
269
-	/**
270
-	 * Is queued?
271
-	 *
272
-	 * @return bool
273
-	 */
274
-	public function is_queued() {
275
-		return ! $this->is_queue_empty();
276
-	}
277
-
278
-	/**
279
-	 * Is the tool currently active, e.g. starting, working, paused or cleaning up?
280
-	 *
281
-	 * @return bool
282
-	 */
283
-	public function is_active() {
284
-		return $this->is_queued() || $this->is_processing() || $this->is_paused() || $this->is_cancelled();
285
-	}
286
-
287
-	/**
288
-	 * Generate key for a batch.
289
-	 *
290
-	 * Generates a unique key based on microtime. Queue items are
291
-	 * given a unique key so that they can be merged upon save.
292
-	 *
293
-	 * @param int    $length Optional max length to trim key to, defaults to 64 characters.
294
-	 * @param string $key    Optional string to append to identifier before hash, defaults to "batch".
295
-	 *
296
-	 * @return string
297
-	 */
298
-	protected function generate_key( $length = 64, $key = 'batch' ) {
299
-		$unique  = md5( microtime() . wp_rand() );
300
-		$prepend = $this->identifier . '_' . $key . '_';
301
-
302
-		return substr( $prepend . $unique, 0, $length );
303
-	}
304
-
305
-	/**
306
-	 * Get the status key.
307
-	 *
308
-	 * @return string
309
-	 */
310
-	protected function get_status_key() {
311
-		return $this->identifier . '_status';
312
-	}
313
-
314
-	/**
315
-	 * Maybe process a batch of queued items.
316
-	 *
317
-	 * Checks whether data exists within the queue and that
318
-	 * the process is not already running.
319
-	 */
320
-	public function maybe_handle() {
321
-		// Don't lock up other requests while processing.
322
-		session_write_close();
323
-
324
-		if ( $this->is_processing() ) {
325
-			// Background process already running.
326
-			return $this->maybe_wp_die();
327
-		}
328
-
329
-		if ( $this->is_cancelled() ) {
330
-			$this->clear_scheduled_event();
331
-			$this->delete_all();
332
-
333
-			return $this->maybe_wp_die();
334
-		}
335
-
336
-		if ( $this->is_paused() ) {
337
-			$this->clear_scheduled_event();
338
-			$this->paused();
339
-
340
-			return $this->maybe_wp_die();
341
-		}
342
-
343
-		if ( $this->is_queue_empty() ) {
344
-			// No data to process.
345
-			return $this->maybe_wp_die();
346
-		}
347
-
348
-		check_ajax_referer( $this->identifier, 'nonce' );
349
-
350
-		$this->handle();
351
-
352
-		return $this->maybe_wp_die();
353
-	}
354
-
355
-	/**
356
-	 * Is queue empty?
357
-	 *
358
-	 * @return bool
359
-	 */
360
-	protected function is_queue_empty() {
361
-		return empty( $this->get_batch() );
362
-	}
363
-
364
-	/**
365
-	 * Is process running?
366
-	 *
367
-	 * Check whether the current process is already running
368
-	 * in a background process.
369
-	 *
370
-	 * @return bool
371
-	 *
372
-	 * @deprecated 1.1.0 Superseded.
373
-	 * @see        is_processing()
374
-	 */
375
-	protected function is_process_running() {
376
-		return $this->is_processing();
377
-	}
378
-
379
-	/**
380
-	 * Is the background process currently running?
381
-	 *
382
-	 * @return bool
383
-	 */
384
-	public function is_processing() {
385
-		if ( get_site_transient( $this->identifier . '_process_lock' ) ) {
386
-			// Process already running.
387
-			return true;
388
-		}
389
-
390
-		return false;
391
-	}
392
-
393
-	/**
394
-	 * Lock process.
395
-	 *
396
-	 * Lock the process so that multiple instances can't run simultaneously.
397
-	 * Override if applicable, but the duration should be greater than that
398
-	 * defined in the time_exceeded() method.
399
-	 */
400
-	protected function lock_process() {
401
-		$this->start_time = time(); // Set start time of current process.
402
-
403
-		$lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute
404
-		$lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration );
405
-
406
-		set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration );
407
-	}
408
-
409
-	/**
410
-	 * Unlock process.
411
-	 *
412
-	 * Unlock the process so that other instances can spawn.
413
-	 *
414
-	 * @return $this
415
-	 */
416
-	protected function unlock_process() {
417
-		delete_site_transient( $this->identifier . '_process_lock' );
418
-
419
-		return $this;
420
-	}
421
-
422
-	/**
423
-	 * Get batch.
424
-	 *
425
-	 * @return stdClass Return the first batch of queued items.
426
-	 */
427
-	protected function get_batch() {
428
-		return array_reduce(
429
-			$this->get_batches( 1 ),
430
-			static function ( $carry, $batch ) {
431
-				return $batch;
432
-			},
433
-			array()
434
-		);
435
-	}
436
-
437
-	/**
438
-	 * Get batches.
439
-	 *
440
-	 * @param int $limit Number of batches to return, defaults to all.
441
-	 *
442
-	 * @return array of stdClass
443
-	 */
444
-	public function get_batches( $limit = 0 ) {
445
-		global $wpdb;
446
-
447
-		if ( empty( $limit ) || ! is_int( $limit ) ) {
448
-			$limit = 0;
449
-		}
450
-
451
-		$table        = $wpdb->options;
452
-		$column       = 'option_name';
453
-		$key_column   = 'option_id';
454
-		$value_column = 'option_value';
455
-
456
-		if ( is_multisite() ) {
457
-			$table        = $wpdb->sitemeta;
458
-			$column       = 'meta_key';
459
-			$key_column   = 'meta_id';
460
-			$value_column = 'meta_value';
461
-		}
462
-
463
-		$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
464
-
465
-		$sql = '
16
+    /**
17
+     * Action
18
+     *
19
+     * (default value: 'background_process')
20
+     *
21
+     * @var string
22
+     * @access protected
23
+     */
24
+    protected $action = 'background_process';
25
+
26
+    /**
27
+     * Start time of current process.
28
+     *
29
+     * (default value: 0)
30
+     *
31
+     * @var int
32
+     * @access protected
33
+     */
34
+    protected $start_time = 0;
35
+
36
+    /**
37
+     * Cron_hook_identifier
38
+     *
39
+     * @var string
40
+     * @access protected
41
+     */
42
+    protected $cron_hook_identifier;
43
+
44
+    /**
45
+     * Cron_interval_identifier
46
+     *
47
+     * @var string
48
+     * @access protected
49
+     */
50
+    protected $cron_interval_identifier;
51
+
52
+    /**
53
+     * Restrict object instantiation when using unserialize.
54
+     *
55
+     * @var bool|array
56
+     */
57
+    protected $allowed_batch_data_classes = true;
58
+
59
+    /**
60
+     * The status set when process is cancelling.
61
+     *
62
+     * @var int
63
+     */
64
+    const STATUS_CANCELLED = 1;
65
+
66
+    /**
67
+     * The status set when process is paused or pausing.
68
+     *
69
+     * @var int;
70
+     */
71
+    const STATUS_PAUSED = 2;
72
+
73
+    /**
74
+     * Initiate new background process.
75
+     *
76
+     * @param bool|array $allowed_batch_data_classes Optional. Array of class names that can be unserialized. Default true (any class).
77
+     */
78
+    public function __construct( $allowed_batch_data_classes = true ) {
79
+        parent::__construct();
80
+
81
+        if ( empty( $allowed_batch_data_classes ) && false !== $allowed_batch_data_classes ) {
82
+            $allowed_batch_data_classes = true;
83
+        }
84
+
85
+        if ( ! is_bool( $allowed_batch_data_classes ) && ! is_array( $allowed_batch_data_classes ) ) {
86
+            $allowed_batch_data_classes = true;
87
+        }
88
+
89
+        // If allowed_batch_data_classes property set in subclass,
90
+        // only apply override if not allowing any class.
91
+        if ( true === $this->allowed_batch_data_classes || true !== $allowed_batch_data_classes ) {
92
+            $this->allowed_batch_data_classes = $allowed_batch_data_classes;
93
+        }
94
+
95
+        $this->cron_hook_identifier     = $this->identifier . '_cron';
96
+        $this->cron_interval_identifier = $this->identifier . '_cron_interval';
97
+
98
+        add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) );
99
+        add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) );
100
+    }
101
+
102
+    /**
103
+     * Schedule the cron healthcheck and dispatch an async request to start processing the queue.
104
+     *
105
+     * @access public
106
+     * @return array|WP_Error|false HTTP Response array, WP_Error on failure, or false if not attempted.
107
+     */
108
+    public function dispatch() {
109
+        if ( $this->is_processing() ) {
110
+            // Process already running.
111
+            return false;
112
+        }
113
+
114
+        // Schedule the cron healthcheck.
115
+        $this->schedule_event();
116
+
117
+        // Perform remote post.
118
+        return parent::dispatch();
119
+    }
120
+
121
+    /**
122
+     * Push to the queue.
123
+     *
124
+     * Note, save must be called in order to persist queued items to a batch for processing.
125
+     *
126
+     * @param mixed $data Data.
127
+     *
128
+     * @return $this
129
+     */
130
+    public function push_to_queue( $data ) {
131
+        $this->data[] = $data;
132
+
133
+        return $this;
134
+    }
135
+
136
+    /**
137
+     * Save the queued items for future processing.
138
+     *
139
+     * @return $this
140
+     */
141
+    public function save() {
142
+        $key = $this->generate_key();
143
+
144
+        if ( ! empty( $this->data ) ) {
145
+            update_site_option( $key, $this->data );
146
+        }
147
+
148
+        // Clean out data so that new data isn't prepended with closed session's data.
149
+        $this->data = array();
150
+
151
+        return $this;
152
+    }
153
+
154
+    /**
155
+     * Update a batch's queued items.
156
+     *
157
+     * @param string $key  Key.
158
+     * @param array  $data Data.
159
+     *
160
+     * @return $this
161
+     */
162
+    public function update( $key, $data ) {
163
+        if ( ! empty( $data ) ) {
164
+            update_site_option( $key, $data );
165
+        }
166
+
167
+        return $this;
168
+    }
169
+
170
+    /**
171
+     * Delete a batch of queued items.
172
+     *
173
+     * @param string $key Key.
174
+     *
175
+     * @return $this
176
+     */
177
+    public function delete( $key ) {
178
+        delete_site_option( $key );
179
+
180
+        return $this;
181
+    }
182
+
183
+    /**
184
+     * Delete entire job queue.
185
+     */
186
+    public function delete_all() {
187
+        $batches = $this->get_batches();
188
+
189
+        foreach ( $batches as $batch ) {
190
+            $this->delete( $batch->key );
191
+        }
192
+
193
+        delete_site_option( $this->get_status_key() );
194
+
195
+        $this->cancelled();
196
+    }
197
+
198
+    /**
199
+     * Cancel job on next batch.
200
+     */
201
+    public function cancel() {
202
+        update_site_option( $this->get_status_key(), self::STATUS_CANCELLED );
203
+
204
+        // Just in case the job was paused at the time.
205
+        $this->dispatch();
206
+    }
207
+
208
+    /**
209
+     * Has the process been cancelled?
210
+     *
211
+     * @return bool
212
+     */
213
+    public function is_cancelled() {
214
+        $status = get_site_option( $this->get_status_key(), 0 );
215
+
216
+        return absint( $status ) === self::STATUS_CANCELLED;
217
+    }
218
+
219
+    /**
220
+     * Called when background process has been cancelled.
221
+     */
222
+    protected function cancelled() {
223
+        do_action( $this->identifier . '_cancelled' );
224
+    }
225
+
226
+    /**
227
+     * Pause job on next batch.
228
+     */
229
+    public function pause() {
230
+        update_site_option( $this->get_status_key(), self::STATUS_PAUSED );
231
+    }
232
+
233
+    /**
234
+     * Is the job paused?
235
+     *
236
+     * @return bool
237
+     */
238
+    public function is_paused() {
239
+        $status = get_site_option( $this->get_status_key(), 0 );
240
+
241
+        return absint( $status ) === self::STATUS_PAUSED;
242
+    }
243
+
244
+    /**
245
+     * Called when background process has been paused.
246
+     */
247
+    protected function paused() {
248
+        do_action( $this->identifier . '_paused' );
249
+    }
250
+
251
+    /**
252
+     * Resume job.
253
+     */
254
+    public function resume() {
255
+        delete_site_option( $this->get_status_key() );
256
+
257
+        $this->schedule_event();
258
+        $this->dispatch();
259
+        $this->resumed();
260
+    }
261
+
262
+    /**
263
+     * Called when background process has been resumed.
264
+     */
265
+    protected function resumed() {
266
+        do_action( $this->identifier . '_resumed' );
267
+    }
268
+
269
+    /**
270
+     * Is queued?
271
+     *
272
+     * @return bool
273
+     */
274
+    public function is_queued() {
275
+        return ! $this->is_queue_empty();
276
+    }
277
+
278
+    /**
279
+     * Is the tool currently active, e.g. starting, working, paused or cleaning up?
280
+     *
281
+     * @return bool
282
+     */
283
+    public function is_active() {
284
+        return $this->is_queued() || $this->is_processing() || $this->is_paused() || $this->is_cancelled();
285
+    }
286
+
287
+    /**
288
+     * Generate key for a batch.
289
+     *
290
+     * Generates a unique key based on microtime. Queue items are
291
+     * given a unique key so that they can be merged upon save.
292
+     *
293
+     * @param int    $length Optional max length to trim key to, defaults to 64 characters.
294
+     * @param string $key    Optional string to append to identifier before hash, defaults to "batch".
295
+     *
296
+     * @return string
297
+     */
298
+    protected function generate_key( $length = 64, $key = 'batch' ) {
299
+        $unique  = md5( microtime() . wp_rand() );
300
+        $prepend = $this->identifier . '_' . $key . '_';
301
+
302
+        return substr( $prepend . $unique, 0, $length );
303
+    }
304
+
305
+    /**
306
+     * Get the status key.
307
+     *
308
+     * @return string
309
+     */
310
+    protected function get_status_key() {
311
+        return $this->identifier . '_status';
312
+    }
313
+
314
+    /**
315
+     * Maybe process a batch of queued items.
316
+     *
317
+     * Checks whether data exists within the queue and that
318
+     * the process is not already running.
319
+     */
320
+    public function maybe_handle() {
321
+        // Don't lock up other requests while processing.
322
+        session_write_close();
323
+
324
+        if ( $this->is_processing() ) {
325
+            // Background process already running.
326
+            return $this->maybe_wp_die();
327
+        }
328
+
329
+        if ( $this->is_cancelled() ) {
330
+            $this->clear_scheduled_event();
331
+            $this->delete_all();
332
+
333
+            return $this->maybe_wp_die();
334
+        }
335
+
336
+        if ( $this->is_paused() ) {
337
+            $this->clear_scheduled_event();
338
+            $this->paused();
339
+
340
+            return $this->maybe_wp_die();
341
+        }
342
+
343
+        if ( $this->is_queue_empty() ) {
344
+            // No data to process.
345
+            return $this->maybe_wp_die();
346
+        }
347
+
348
+        check_ajax_referer( $this->identifier, 'nonce' );
349
+
350
+        $this->handle();
351
+
352
+        return $this->maybe_wp_die();
353
+    }
354
+
355
+    /**
356
+     * Is queue empty?
357
+     *
358
+     * @return bool
359
+     */
360
+    protected function is_queue_empty() {
361
+        return empty( $this->get_batch() );
362
+    }
363
+
364
+    /**
365
+     * Is process running?
366
+     *
367
+     * Check whether the current process is already running
368
+     * in a background process.
369
+     *
370
+     * @return bool
371
+     *
372
+     * @deprecated 1.1.0 Superseded.
373
+     * @see        is_processing()
374
+     */
375
+    protected function is_process_running() {
376
+        return $this->is_processing();
377
+    }
378
+
379
+    /**
380
+     * Is the background process currently running?
381
+     *
382
+     * @return bool
383
+     */
384
+    public function is_processing() {
385
+        if ( get_site_transient( $this->identifier . '_process_lock' ) ) {
386
+            // Process already running.
387
+            return true;
388
+        }
389
+
390
+        return false;
391
+    }
392
+
393
+    /**
394
+     * Lock process.
395
+     *
396
+     * Lock the process so that multiple instances can't run simultaneously.
397
+     * Override if applicable, but the duration should be greater than that
398
+     * defined in the time_exceeded() method.
399
+     */
400
+    protected function lock_process() {
401
+        $this->start_time = time(); // Set start time of current process.
402
+
403
+        $lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute
404
+        $lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration );
405
+
406
+        set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration );
407
+    }
408
+
409
+    /**
410
+     * Unlock process.
411
+     *
412
+     * Unlock the process so that other instances can spawn.
413
+     *
414
+     * @return $this
415
+     */
416
+    protected function unlock_process() {
417
+        delete_site_transient( $this->identifier . '_process_lock' );
418
+
419
+        return $this;
420
+    }
421
+
422
+    /**
423
+     * Get batch.
424
+     *
425
+     * @return stdClass Return the first batch of queued items.
426
+     */
427
+    protected function get_batch() {
428
+        return array_reduce(
429
+            $this->get_batches( 1 ),
430
+            static function ( $carry, $batch ) {
431
+                return $batch;
432
+            },
433
+            array()
434
+        );
435
+    }
436
+
437
+    /**
438
+     * Get batches.
439
+     *
440
+     * @param int $limit Number of batches to return, defaults to all.
441
+     *
442
+     * @return array of stdClass
443
+     */
444
+    public function get_batches( $limit = 0 ) {
445
+        global $wpdb;
446
+
447
+        if ( empty( $limit ) || ! is_int( $limit ) ) {
448
+            $limit = 0;
449
+        }
450
+
451
+        $table        = $wpdb->options;
452
+        $column       = 'option_name';
453
+        $key_column   = 'option_id';
454
+        $value_column = 'option_value';
455
+
456
+        if ( is_multisite() ) {
457
+            $table        = $wpdb->sitemeta;
458
+            $column       = 'meta_key';
459
+            $key_column   = 'meta_id';
460
+            $value_column = 'meta_value';
461
+        }
462
+
463
+        $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
464
+
465
+        $sql = '
466 466
 			SELECT *
467 467
 			FROM ' . $table . '
468 468
 			WHERE ' . $column . ' LIKE %s
469 469
 			ORDER BY ' . $key_column . ' ASC
470 470
 			';
471 471
 
472
-		$args = array( $key );
473
-
474
-		if ( ! empty( $limit ) ) {
475
-			$sql .= ' LIMIT %d';
476
-
477
-			$args[] = $limit;
478
-		}
479
-
480
-		$items = $wpdb->get_results( $wpdb->prepare( $sql, $args ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
481
-
482
-		$batches = array();
483
-
484
-		if ( ! empty( $items ) ) {
485
-			$allowed_classes = $this->allowed_batch_data_classes;
486
-
487
-			$batches = array_map(
488
-				static function ( $item ) use ( $column, $value_column, $allowed_classes ) {
489
-					$batch       = new stdClass();
490
-					$batch->key  = $item->{$column};
491
-					$batch->data = static::maybe_unserialize( $item->{$value_column}, $allowed_classes );
492
-
493
-					return $batch;
494
-				},
495
-				$items
496
-			);
497
-		}
498
-
499
-		return $batches;
500
-	}
501
-
502
-	/**
503
-	 * Handle a dispatched request.
504
-	 *
505
-	 * Pass each queue item to the task handler, while remaining
506
-	 * within server memory and time limit constraints.
507
-	 */
508
-	protected function handle() {
509
-		$this->lock_process();
510
-
511
-		/**
512
-		 * Number of seconds to sleep between batches. Defaults to 0 seconds, minimum 0.
513
-		 *
514
-		 * @param int $seconds
515
-		 */
516
-		$throttle_seconds = max(
517
-			0,
518
-			apply_filters(
519
-				$this->identifier . '_seconds_between_batches',
520
-				apply_filters(
521
-					$this->prefix . '_seconds_between_batches',
522
-					0
523
-				)
524
-			)
525
-		);
526
-
527
-		do {
528
-			$batch = $this->get_batch();
529
-
530
-			foreach ( $batch->data as $key => $value ) {
531
-				$task = $this->task( $value );
532
-
533
-				if ( false !== $task ) {
534
-					$batch->data[ $key ] = $task;
535
-				} else {
536
-					unset( $batch->data[ $key ] );
537
-				}
538
-
539
-				// Keep the batch up to date while processing it.
540
-				if ( ! empty( $batch->data ) ) {
541
-					$this->update( $batch->key, $batch->data );
542
-				}
543
-
544
-				// Let the server breathe a little.
545
-				sleep( $throttle_seconds );
546
-
547
-				// Batch limits reached, or pause or cancel request.
548
-				if ( $this->time_exceeded() || $this->memory_exceeded() || $this->is_paused() || $this->is_cancelled() ) {
549
-					break;
550
-				}
551
-			}
552
-
553
-			// Delete current batch if fully processed.
554
-			if ( empty( $batch->data ) ) {
555
-				$this->delete( $batch->key );
556
-			}
557
-		} while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() && ! $this->is_paused() && ! $this->is_cancelled() );
558
-
559
-		$this->unlock_process();
560
-
561
-		// Start next batch or complete process.
562
-		if ( ! $this->is_queue_empty() ) {
563
-			$this->dispatch();
564
-		} else {
565
-			$this->complete();
566
-		}
567
-
568
-		return $this->maybe_wp_die();
569
-	}
570
-
571
-	/**
572
-	 * Memory exceeded?
573
-	 *
574
-	 * Ensures the batch process never exceeds 90%
575
-	 * of the maximum WordPress memory.
576
-	 *
577
-	 * @return bool
578
-	 */
579
-	protected function memory_exceeded() {
580
-		$memory_limit   = $this->get_memory_limit() * 0.9; // 90% of max memory
581
-		$current_memory = memory_get_usage( true );
582
-		$return         = false;
583
-
584
-		if ( $current_memory >= $memory_limit ) {
585
-			$return = true;
586
-		}
587
-
588
-		return apply_filters( $this->identifier . '_memory_exceeded', $return );
589
-	}
590
-
591
-	/**
592
-	 * Get memory limit in bytes.
593
-	 *
594
-	 * @return int
595
-	 */
596
-	protected function get_memory_limit() {
597
-		if ( function_exists( 'ini_get' ) ) {
598
-			$memory_limit = ini_get( 'memory_limit' );
599
-		} else {
600
-			// Sensible default.
601
-			$memory_limit = '128M';
602
-		}
603
-
604
-		if ( ! $memory_limit || -1 === intval( $memory_limit ) ) {
605
-			// Unlimited, set to 32GB.
606
-			$memory_limit = '32000M';
607
-		}
608
-
609
-		return wp_convert_hr_to_bytes( $memory_limit );
610
-	}
611
-
612
-	/**
613
-	 * Time limit exceeded?
614
-	 *
615
-	 * Ensures the batch never exceeds a sensible time limit.
616
-	 * A timeout limit of 30s is common on shared hosting.
617
-	 *
618
-	 * @return bool
619
-	 */
620
-	protected function time_exceeded() {
621
-		$finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds
622
-		$return = false;
623
-
624
-		if ( time() >= $finish ) {
625
-			$return = true;
626
-		}
627
-
628
-		return apply_filters( $this->identifier . '_time_exceeded', $return );
629
-	}
630
-
631
-	/**
632
-	 * Complete processing.
633
-	 *
634
-	 * Override if applicable, but ensure that the below actions are
635
-	 * performed, or, call parent::complete().
636
-	 */
637
-	protected function complete() {
638
-		delete_site_option( $this->get_status_key() );
639
-
640
-		// Remove the cron healthcheck job from the cron schedule.
641
-		$this->clear_scheduled_event();
642
-
643
-		$this->completed();
644
-	}
645
-
646
-	/**
647
-	 * Called when background process has completed.
648
-	 */
649
-	protected function completed() {
650
-		do_action( $this->identifier . '_completed' );
651
-	}
652
-
653
-	/**
654
-	 * Get the cron healthcheck interval in minutes.
655
-	 *
656
-	 * Default is 5 minutes, minimum is 1 minute.
657
-	 *
658
-	 * @return int
659
-	 */
660
-	public function get_cron_interval() {
661
-		$interval = 5;
662
-
663
-		if ( property_exists( $this, 'cron_interval' ) ) {
664
-			$interval = $this->cron_interval;
665
-		}
666
-
667
-		$interval = apply_filters( $this->cron_interval_identifier, $interval );
668
-
669
-		return is_int( $interval ) && 0 < $interval ? $interval : 5;
670
-	}
671
-
672
-	/**
673
-	 * Schedule the cron healthcheck job.
674
-	 *
675
-	 * @access public
676
-	 *
677
-	 * @param mixed $schedules Schedules.
678
-	 *
679
-	 * @return mixed
680
-	 */
681
-	public function schedule_cron_healthcheck( $schedules ) {
682
-		$interval = $this->get_cron_interval();
683
-
684
-		if ( 1 === $interval ) {
685
-			$display = __( 'Every Minute' );
686
-		} else {
687
-			$display = sprintf( __( 'Every %d Minutes' ), $interval );
688
-		}
689
-
690
-		// Adds an "Every NNN Minute(s)" schedule to the existing cron schedules.
691
-		$schedules[ $this->cron_interval_identifier ] = array(
692
-			'interval' => MINUTE_IN_SECONDS * $interval,
693
-			'display'  => $display,
694
-		);
695
-
696
-		return $schedules;
697
-	}
698
-
699
-	/**
700
-	 * Handle cron healthcheck event.
701
-	 *
702
-	 * Restart the background process if not already running
703
-	 * and data exists in the queue.
704
-	 */
705
-	public function handle_cron_healthcheck() {
706
-		if ( $this->is_processing() ) {
707
-			// Background process already running.
708
-			exit;
709
-		}
710
-
711
-		if ( $this->is_queue_empty() ) {
712
-			// No data to process.
713
-			$this->clear_scheduled_event();
714
-			exit;
715
-		}
716
-
717
-		$this->dispatch();
718
-	}
719
-
720
-	/**
721
-	 * Schedule the cron healthcheck event.
722
-	 */
723
-	protected function schedule_event() {
724
-		if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
725
-			wp_schedule_event( time() + ( $this->get_cron_interval() * MINUTE_IN_SECONDS ), $this->cron_interval_identifier, $this->cron_hook_identifier );
726
-		}
727
-	}
728
-
729
-	/**
730
-	 * Clear scheduled cron healthcheck event.
731
-	 */
732
-	protected function clear_scheduled_event() {
733
-		$timestamp = wp_next_scheduled( $this->cron_hook_identifier );
734
-
735
-		if ( $timestamp ) {
736
-			wp_unschedule_event( $timestamp, $this->cron_hook_identifier );
737
-		}
738
-	}
739
-
740
-	/**
741
-	 * Cancel the background process.
742
-	 *
743
-	 * Stop processing queue items, clear cron job and delete batch.
744
-	 *
745
-	 * @deprecated 1.1.0 Superseded.
746
-	 * @see        cancel()
747
-	 */
748
-	public function cancel_process() {
749
-		$this->cancel();
750
-	}
751
-
752
-	/**
753
-	 * Perform task with queued item.
754
-	 *
755
-	 * Override this method to perform any actions required on each
756
-	 * queue item. Return the modified item for further processing
757
-	 * in the next pass through. Or, return false to remove the
758
-	 * item from the queue.
759
-	 *
760
-	 * @param mixed $item Queue item to iterate over.
761
-	 *
762
-	 * @return mixed
763
-	 */
764
-	abstract protected function task( $item );
765
-
766
-	/**
767
-	 * Maybe unserialize data, but not if an object.
768
-	 *
769
-	 * @param mixed      $data            Data to be unserialized.
770
-	 * @param bool|array $allowed_classes Array of class names that can be unserialized.
771
-	 *
772
-	 * @return mixed
773
-	 */
774
-	protected static function maybe_unserialize( $data, $allowed_classes ) {
775
-		if ( is_serialized( $data ) ) {
776
-			$options = array();
777
-			if ( is_bool( $allowed_classes ) || is_array( $allowed_classes ) ) {
778
-				$options['allowed_classes'] = $allowed_classes;
779
-			}
780
-
781
-			return @unserialize( $data, $options ); // @phpcs:ignore
782
-		}
783
-
784
-		return $data;
785
-	}
472
+        $args = array( $key );
473
+
474
+        if ( ! empty( $limit ) ) {
475
+            $sql .= ' LIMIT %d';
476
+
477
+            $args[] = $limit;
478
+        }
479
+
480
+        $items = $wpdb->get_results( $wpdb->prepare( $sql, $args ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
481
+
482
+        $batches = array();
483
+
484
+        if ( ! empty( $items ) ) {
485
+            $allowed_classes = $this->allowed_batch_data_classes;
486
+
487
+            $batches = array_map(
488
+                static function ( $item ) use ( $column, $value_column, $allowed_classes ) {
489
+                    $batch       = new stdClass();
490
+                    $batch->key  = $item->{$column};
491
+                    $batch->data = static::maybe_unserialize( $item->{$value_column}, $allowed_classes );
492
+
493
+                    return $batch;
494
+                },
495
+                $items
496
+            );
497
+        }
498
+
499
+        return $batches;
500
+    }
501
+
502
+    /**
503
+     * Handle a dispatched request.
504
+     *
505
+     * Pass each queue item to the task handler, while remaining
506
+     * within server memory and time limit constraints.
507
+     */
508
+    protected function handle() {
509
+        $this->lock_process();
510
+
511
+        /**
512
+         * Number of seconds to sleep between batches. Defaults to 0 seconds, minimum 0.
513
+         *
514
+         * @param int $seconds
515
+         */
516
+        $throttle_seconds = max(
517
+            0,
518
+            apply_filters(
519
+                $this->identifier . '_seconds_between_batches',
520
+                apply_filters(
521
+                    $this->prefix . '_seconds_between_batches',
522
+                    0
523
+                )
524
+            )
525
+        );
526
+
527
+        do {
528
+            $batch = $this->get_batch();
529
+
530
+            foreach ( $batch->data as $key => $value ) {
531
+                $task = $this->task( $value );
532
+
533
+                if ( false !== $task ) {
534
+                    $batch->data[ $key ] = $task;
535
+                } else {
536
+                    unset( $batch->data[ $key ] );
537
+                }
538
+
539
+                // Keep the batch up to date while processing it.
540
+                if ( ! empty( $batch->data ) ) {
541
+                    $this->update( $batch->key, $batch->data );
542
+                }
543
+
544
+                // Let the server breathe a little.
545
+                sleep( $throttle_seconds );
546
+
547
+                // Batch limits reached, or pause or cancel request.
548
+                if ( $this->time_exceeded() || $this->memory_exceeded() || $this->is_paused() || $this->is_cancelled() ) {
549
+                    break;
550
+                }
551
+            }
552
+
553
+            // Delete current batch if fully processed.
554
+            if ( empty( $batch->data ) ) {
555
+                $this->delete( $batch->key );
556
+            }
557
+        } while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() && ! $this->is_paused() && ! $this->is_cancelled() );
558
+
559
+        $this->unlock_process();
560
+
561
+        // Start next batch or complete process.
562
+        if ( ! $this->is_queue_empty() ) {
563
+            $this->dispatch();
564
+        } else {
565
+            $this->complete();
566
+        }
567
+
568
+        return $this->maybe_wp_die();
569
+    }
570
+
571
+    /**
572
+     * Memory exceeded?
573
+     *
574
+     * Ensures the batch process never exceeds 90%
575
+     * of the maximum WordPress memory.
576
+     *
577
+     * @return bool
578
+     */
579
+    protected function memory_exceeded() {
580
+        $memory_limit   = $this->get_memory_limit() * 0.9; // 90% of max memory
581
+        $current_memory = memory_get_usage( true );
582
+        $return         = false;
583
+
584
+        if ( $current_memory >= $memory_limit ) {
585
+            $return = true;
586
+        }
587
+
588
+        return apply_filters( $this->identifier . '_memory_exceeded', $return );
589
+    }
590
+
591
+    /**
592
+     * Get memory limit in bytes.
593
+     *
594
+     * @return int
595
+     */
596
+    protected function get_memory_limit() {
597
+        if ( function_exists( 'ini_get' ) ) {
598
+            $memory_limit = ini_get( 'memory_limit' );
599
+        } else {
600
+            // Sensible default.
601
+            $memory_limit = '128M';
602
+        }
603
+
604
+        if ( ! $memory_limit || -1 === intval( $memory_limit ) ) {
605
+            // Unlimited, set to 32GB.
606
+            $memory_limit = '32000M';
607
+        }
608
+
609
+        return wp_convert_hr_to_bytes( $memory_limit );
610
+    }
611
+
612
+    /**
613
+     * Time limit exceeded?
614
+     *
615
+     * Ensures the batch never exceeds a sensible time limit.
616
+     * A timeout limit of 30s is common on shared hosting.
617
+     *
618
+     * @return bool
619
+     */
620
+    protected function time_exceeded() {
621
+        $finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds
622
+        $return = false;
623
+
624
+        if ( time() >= $finish ) {
625
+            $return = true;
626
+        }
627
+
628
+        return apply_filters( $this->identifier . '_time_exceeded', $return );
629
+    }
630
+
631
+    /**
632
+     * Complete processing.
633
+     *
634
+     * Override if applicable, but ensure that the below actions are
635
+     * performed, or, call parent::complete().
636
+     */
637
+    protected function complete() {
638
+        delete_site_option( $this->get_status_key() );
639
+
640
+        // Remove the cron healthcheck job from the cron schedule.
641
+        $this->clear_scheduled_event();
642
+
643
+        $this->completed();
644
+    }
645
+
646
+    /**
647
+     * Called when background process has completed.
648
+     */
649
+    protected function completed() {
650
+        do_action( $this->identifier . '_completed' );
651
+    }
652
+
653
+    /**
654
+     * Get the cron healthcheck interval in minutes.
655
+     *
656
+     * Default is 5 minutes, minimum is 1 minute.
657
+     *
658
+     * @return int
659
+     */
660
+    public function get_cron_interval() {
661
+        $interval = 5;
662
+
663
+        if ( property_exists( $this, 'cron_interval' ) ) {
664
+            $interval = $this->cron_interval;
665
+        }
666
+
667
+        $interval = apply_filters( $this->cron_interval_identifier, $interval );
668
+
669
+        return is_int( $interval ) && 0 < $interval ? $interval : 5;
670
+    }
671
+
672
+    /**
673
+     * Schedule the cron healthcheck job.
674
+     *
675
+     * @access public
676
+     *
677
+     * @param mixed $schedules Schedules.
678
+     *
679
+     * @return mixed
680
+     */
681
+    public function schedule_cron_healthcheck( $schedules ) {
682
+        $interval = $this->get_cron_interval();
683
+
684
+        if ( 1 === $interval ) {
685
+            $display = __( 'Every Minute' );
686
+        } else {
687
+            $display = sprintf( __( 'Every %d Minutes' ), $interval );
688
+        }
689
+
690
+        // Adds an "Every NNN Minute(s)" schedule to the existing cron schedules.
691
+        $schedules[ $this->cron_interval_identifier ] = array(
692
+            'interval' => MINUTE_IN_SECONDS * $interval,
693
+            'display'  => $display,
694
+        );
695
+
696
+        return $schedules;
697
+    }
698
+
699
+    /**
700
+     * Handle cron healthcheck event.
701
+     *
702
+     * Restart the background process if not already running
703
+     * and data exists in the queue.
704
+     */
705
+    public function handle_cron_healthcheck() {
706
+        if ( $this->is_processing() ) {
707
+            // Background process already running.
708
+            exit;
709
+        }
710
+
711
+        if ( $this->is_queue_empty() ) {
712
+            // No data to process.
713
+            $this->clear_scheduled_event();
714
+            exit;
715
+        }
716
+
717
+        $this->dispatch();
718
+    }
719
+
720
+    /**
721
+     * Schedule the cron healthcheck event.
722
+     */
723
+    protected function schedule_event() {
724
+        if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
725
+            wp_schedule_event( time() + ( $this->get_cron_interval() * MINUTE_IN_SECONDS ), $this->cron_interval_identifier, $this->cron_hook_identifier );
726
+        }
727
+    }
728
+
729
+    /**
730
+     * Clear scheduled cron healthcheck event.
731
+     */
732
+    protected function clear_scheduled_event() {
733
+        $timestamp = wp_next_scheduled( $this->cron_hook_identifier );
734
+
735
+        if ( $timestamp ) {
736
+            wp_unschedule_event( $timestamp, $this->cron_hook_identifier );
737
+        }
738
+    }
739
+
740
+    /**
741
+     * Cancel the background process.
742
+     *
743
+     * Stop processing queue items, clear cron job and delete batch.
744
+     *
745
+     * @deprecated 1.1.0 Superseded.
746
+     * @see        cancel()
747
+     */
748
+    public function cancel_process() {
749
+        $this->cancel();
750
+    }
751
+
752
+    /**
753
+     * Perform task with queued item.
754
+     *
755
+     * Override this method to perform any actions required on each
756
+     * queue item. Return the modified item for further processing
757
+     * in the next pass through. Or, return false to remove the
758
+     * item from the queue.
759
+     *
760
+     * @param mixed $item Queue item to iterate over.
761
+     *
762
+     * @return mixed
763
+     */
764
+    abstract protected function task( $item );
765
+
766
+    /**
767
+     * Maybe unserialize data, but not if an object.
768
+     *
769
+     * @param mixed      $data            Data to be unserialized.
770
+     * @param bool|array $allowed_classes Array of class names that can be unserialized.
771
+     *
772
+     * @return mixed
773
+     */
774
+    protected static function maybe_unserialize( $data, $allowed_classes ) {
775
+        if ( is_serialized( $data ) ) {
776
+            $options = array();
777
+            if ( is_bool( $allowed_classes ) || is_array( $allowed_classes ) ) {
778
+                $options['allowed_classes'] = $allowed_classes;
779
+            }
780
+
781
+            return @unserialize( $data, $options ); // @phpcs:ignore
782
+        }
783
+
784
+        return $data;
785
+    }
786 786
 }
Please login to merge, or discard this patch.
deliciousbrains/wp-background-processing/classes/wp-async-request.php 1 patch
Indentation   +187 added lines, -187 removed lines patch added patch discarded remove patch
@@ -12,191 +12,191 @@
 block discarded – undo
12 12
  */
13 13
 abstract class WP_Async_Request {
14 14
 
15
-	/**
16
-	 * Prefix
17
-	 *
18
-	 * (default value: 'wp')
19
-	 *
20
-	 * @var string
21
-	 * @access protected
22
-	 */
23
-	protected $prefix = 'wp';
24
-
25
-	/**
26
-	 * Action
27
-	 *
28
-	 * (default value: 'async_request')
29
-	 *
30
-	 * @var string
31
-	 * @access protected
32
-	 */
33
-	protected $action = 'async_request';
34
-
35
-	/**
36
-	 * Identifier
37
-	 *
38
-	 * @var mixed
39
-	 * @access protected
40
-	 */
41
-	protected $identifier;
42
-
43
-	/**
44
-	 * Data
45
-	 *
46
-	 * (default value: array())
47
-	 *
48
-	 * @var array
49
-	 * @access protected
50
-	 */
51
-	protected $data = array();
52
-
53
-	/**
54
-	 * Initiate new async request.
55
-	 */
56
-	public function __construct() {
57
-		$this->identifier = $this->prefix . '_' . $this->action;
58
-
59
-		add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) );
60
-		add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) );
61
-	}
62
-
63
-	/**
64
-	 * Set data used during the request.
65
-	 *
66
-	 * @param array $data Data.
67
-	 *
68
-	 * @return $this
69
-	 */
70
-	public function data( $data ) {
71
-		$this->data = $data;
72
-
73
-		return $this;
74
-	}
75
-
76
-	/**
77
-	 * Dispatch the async request.
78
-	 *
79
-	 * @return array|WP_Error|false HTTP Response array, WP_Error on failure, or false if not attempted.
80
-	 */
81
-	public function dispatch() {
82
-		$url  = add_query_arg( $this->get_query_args(), $this->get_query_url() );
83
-		$args = $this->get_post_args();
84
-
85
-		return wp_remote_post( esc_url_raw( $url ), $args );
86
-	}
87
-
88
-	/**
89
-	 * Get query args.
90
-	 *
91
-	 * @return array
92
-	 */
93
-	protected function get_query_args() {
94
-		if ( property_exists( $this, 'query_args' ) ) {
95
-			return $this->query_args;
96
-		}
97
-
98
-		$args = array(
99
-			'action' => $this->identifier,
100
-			'nonce'  => wp_create_nonce( $this->identifier ),
101
-		);
102
-
103
-		/**
104
-		 * Filters the post arguments used during an async request.
105
-		 *
106
-		 * @param array $url
107
-		 */
108
-		return apply_filters( $this->identifier . '_query_args', $args );
109
-	}
110
-
111
-	/**
112
-	 * Get query URL.
113
-	 *
114
-	 * @return string
115
-	 */
116
-	protected function get_query_url() {
117
-		if ( property_exists( $this, 'query_url' ) ) {
118
-			return $this->query_url;
119
-		}
120
-
121
-		$url = admin_url( 'admin-ajax.php' );
122
-
123
-		/**
124
-		 * Filters the post arguments used during an async request.
125
-		 *
126
-		 * @param string $url
127
-		 */
128
-		return apply_filters( $this->identifier . '_query_url', $url );
129
-	}
130
-
131
-	/**
132
-	 * Get post args.
133
-	 *
134
-	 * @return array
135
-	 */
136
-	protected function get_post_args() {
137
-		if ( property_exists( $this, 'post_args' ) ) {
138
-			return $this->post_args;
139
-		}
140
-
141
-		$args = array(
142
-			'timeout'   => 5,
143
-			'blocking'  => false,
144
-			'body'      => $this->data,
145
-			'cookies'   => $_COOKIE, // Passing cookies ensures request is performed as initiating user.
146
-			'sslverify' => apply_filters( 'https_local_ssl_verify', false ), // Local requests, fine to pass false.
147
-		);
148
-
149
-		/**
150
-		 * Filters the post arguments used during an async request.
151
-		 *
152
-		 * @param array $args
153
-		 */
154
-		return apply_filters( $this->identifier . '_post_args', $args );
155
-	}
156
-
157
-	/**
158
-	 * Maybe handle a dispatched request.
159
-	 *
160
-	 * Check for correct nonce and pass to handler.
161
-	 *
162
-	 * @return void|mixed
163
-	 */
164
-	public function maybe_handle() {
165
-		// Don't lock up other requests while processing.
166
-		session_write_close();
167
-
168
-		check_ajax_referer( $this->identifier, 'nonce' );
169
-
170
-		$this->handle();
171
-
172
-		return $this->maybe_wp_die();
173
-	}
174
-
175
-	/**
176
-	 * Should the process exit with wp_die?
177
-	 *
178
-	 * @param mixed $return What to return if filter says don't die, default is null.
179
-	 *
180
-	 * @return void|mixed
181
-	 */
182
-	protected function maybe_wp_die( $return = null ) {
183
-		/**
184
-		 * Should wp_die be used?
185
-		 *
186
-		 * @return bool
187
-		 */
188
-		if ( apply_filters( $this->identifier . '_wp_die', true ) ) {
189
-			wp_die();
190
-		}
191
-
192
-		return $return;
193
-	}
194
-
195
-	/**
196
-	 * Handle a dispatched request.
197
-	 *
198
-	 * Override this method to perform any actions required
199
-	 * during the async request.
200
-	 */
201
-	abstract protected function handle();
15
+    /**
16
+     * Prefix
17
+     *
18
+     * (default value: 'wp')
19
+     *
20
+     * @var string
21
+     * @access protected
22
+     */
23
+    protected $prefix = 'wp';
24
+
25
+    /**
26
+     * Action
27
+     *
28
+     * (default value: 'async_request')
29
+     *
30
+     * @var string
31
+     * @access protected
32
+     */
33
+    protected $action = 'async_request';
34
+
35
+    /**
36
+     * Identifier
37
+     *
38
+     * @var mixed
39
+     * @access protected
40
+     */
41
+    protected $identifier;
42
+
43
+    /**
44
+     * Data
45
+     *
46
+     * (default value: array())
47
+     *
48
+     * @var array
49
+     * @access protected
50
+     */
51
+    protected $data = array();
52
+
53
+    /**
54
+     * Initiate new async request.
55
+     */
56
+    public function __construct() {
57
+        $this->identifier = $this->prefix . '_' . $this->action;
58
+
59
+        add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) );
60
+        add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) );
61
+    }
62
+
63
+    /**
64
+     * Set data used during the request.
65
+     *
66
+     * @param array $data Data.
67
+     *
68
+     * @return $this
69
+     */
70
+    public function data( $data ) {
71
+        $this->data = $data;
72
+
73
+        return $this;
74
+    }
75
+
76
+    /**
77
+     * Dispatch the async request.
78
+     *
79
+     * @return array|WP_Error|false HTTP Response array, WP_Error on failure, or false if not attempted.
80
+     */
81
+    public function dispatch() {
82
+        $url  = add_query_arg( $this->get_query_args(), $this->get_query_url() );
83
+        $args = $this->get_post_args();
84
+
85
+        return wp_remote_post( esc_url_raw( $url ), $args );
86
+    }
87
+
88
+    /**
89
+     * Get query args.
90
+     *
91
+     * @return array
92
+     */
93
+    protected function get_query_args() {
94
+        if ( property_exists( $this, 'query_args' ) ) {
95
+            return $this->query_args;
96
+        }
97
+
98
+        $args = array(
99
+            'action' => $this->identifier,
100
+            'nonce'  => wp_create_nonce( $this->identifier ),
101
+        );
102
+
103
+        /**
104
+         * Filters the post arguments used during an async request.
105
+         *
106
+         * @param array $url
107
+         */
108
+        return apply_filters( $this->identifier . '_query_args', $args );
109
+    }
110
+
111
+    /**
112
+     * Get query URL.
113
+     *
114
+     * @return string
115
+     */
116
+    protected function get_query_url() {
117
+        if ( property_exists( $this, 'query_url' ) ) {
118
+            return $this->query_url;
119
+        }
120
+
121
+        $url = admin_url( 'admin-ajax.php' );
122
+
123
+        /**
124
+         * Filters the post arguments used during an async request.
125
+         *
126
+         * @param string $url
127
+         */
128
+        return apply_filters( $this->identifier . '_query_url', $url );
129
+    }
130
+
131
+    /**
132
+     * Get post args.
133
+     *
134
+     * @return array
135
+     */
136
+    protected function get_post_args() {
137
+        if ( property_exists( $this, 'post_args' ) ) {
138
+            return $this->post_args;
139
+        }
140
+
141
+        $args = array(
142
+            'timeout'   => 5,
143
+            'blocking'  => false,
144
+            'body'      => $this->data,
145
+            'cookies'   => $_COOKIE, // Passing cookies ensures request is performed as initiating user.
146
+            'sslverify' => apply_filters( 'https_local_ssl_verify', false ), // Local requests, fine to pass false.
147
+        );
148
+
149
+        /**
150
+         * Filters the post arguments used during an async request.
151
+         *
152
+         * @param array $args
153
+         */
154
+        return apply_filters( $this->identifier . '_post_args', $args );
155
+    }
156
+
157
+    /**
158
+     * Maybe handle a dispatched request.
159
+     *
160
+     * Check for correct nonce and pass to handler.
161
+     *
162
+     * @return void|mixed
163
+     */
164
+    public function maybe_handle() {
165
+        // Don't lock up other requests while processing.
166
+        session_write_close();
167
+
168
+        check_ajax_referer( $this->identifier, 'nonce' );
169
+
170
+        $this->handle();
171
+
172
+        return $this->maybe_wp_die();
173
+    }
174
+
175
+    /**
176
+     * Should the process exit with wp_die?
177
+     *
178
+     * @param mixed $return What to return if filter says don't die, default is null.
179
+     *
180
+     * @return void|mixed
181
+     */
182
+    protected function maybe_wp_die( $return = null ) {
183
+        /**
184
+         * Should wp_die be used?
185
+         *
186
+         * @return bool
187
+         */
188
+        if ( apply_filters( $this->identifier . '_wp_die', true ) ) {
189
+            wp_die();
190
+        }
191
+
192
+        return $return;
193
+    }
194
+
195
+    /**
196
+     * Handle a dispatched request.
197
+     *
198
+     * Override this method to perform any actions required
199
+     * during the async request.
200
+     */
201
+    abstract protected function handle();
202 202
 }
Please login to merge, or discard this patch.
deliciousbrains/wp-background-processing/wp-background-processing.php 1 patch
Indentation   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -17,8 +17,8 @@
 block discarded – undo
17 17
  */
18 18
 
19 19
 if ( ! class_exists( 'WP_Async_Request' ) ) {
20
-	require_once plugin_dir_path( __FILE__ ) . 'classes/wp-async-request.php';
20
+    require_once plugin_dir_path( __FILE__ ) . 'classes/wp-async-request.php';
21 21
 }
22 22
 if ( ! class_exists( 'WP_Background_Process' ) ) {
23
-	require_once plugin_dir_path( __FILE__ ) . 'classes/wp-background-process.php';
23
+    require_once plugin_dir_path( __FILE__ ) . 'classes/wp-background-process.php';
24 24
 }
Please login to merge, or discard this patch.
src/includes/class-wordlift-jsonld-service.php 1 patch
Indentation   +474 added lines, -474 removed lines patch added patch discarded remove patch
@@ -20,481 +20,481 @@
 block discarded – undo
20 20
  */
21 21
 class Wordlift_Jsonld_Service {
22 22
 
23
-	/**
24
-	 * Creative work types.
25
-	 *
26
-	 * @var string[]
27
-	 */
28
-	private static $creative_work_types = array(
29
-		'AmpStory',
30
-		'ArchiveComponent',
31
-		'Article',
32
-		'Atlas',
33
-		'Blog',
34
-		'Book',
35
-		'Chapter',
36
-		'Claim',
37
-		'Clip',
38
-		'Code',
39
-		'Collection',
40
-		'ComicStory',
41
-		'Comment',
42
-		'Conversation',
43
-		'Course',
44
-		'CreativeWork',
45
-		'CreativeWorkSeason',
46
-		'CreativeWorkSeries',
47
-		'DataCatalog',
48
-		'Dataset',
49
-		'DefinedTermSet',
50
-		'Diet',
51
-		'DigitalDocument',
52
-		'Drawing',
53
-		'EducationalOccupationalCredential',
54
-		'Episode',
55
-		'ExercisePlan',
56
-		'Game',
57
-		'Guide',
58
-		'HowTo',
59
-		'HowToDirection',
60
-		'HowToSection',
61
-		'HowToStep',
62
-		'HowToTip',
63
-		'HyperToc',
64
-		'HyperTocEntry',
65
-		'LearningResource',
66
-		'Legislation',
67
-		'Manuscript',
68
-		'Map',
69
-		'MathSolver',
70
-		'MediaObject',
71
-		'Menu',
72
-		'MenuSection',
73
-		'Message',
74
-		'Movie',
75
-		'MusicComposition',
76
-		'MusicPlaylist',
77
-		'MusicRecording',
78
-		'Painting',
79
-		'Photograph',
80
-		'Play',
81
-		'Poster',
82
-		'PublicationIssue',
83
-		'PublicationVolume',
84
-		'Quotation',
85
-		'Review',
86
-		'Sculpture',
87
-		'Season',
88
-		'SheetMusic',
89
-		'ShortStory',
90
-		'SoftwareApplication',
91
-		'SoftwareSourceCode',
92
-		'SpecialAnnouncement',
93
-		'Thesis',
94
-		'TvSeason',
95
-		'TvSeries',
96
-		'VisualArtwork',
97
-		'WebContent',
98
-		'WebPage',
99
-		'WebPageElement',
100
-		'WebSite',
101
-		'AdvertiserContentArticle',
102
-		'NewsArticle',
103
-		'Report',
104
-		'SatiricalArticle',
105
-		'ScholarlyArticle',
106
-		'SocialMediaPosting',
107
-		'TechArticle',
108
-		'AnalysisNewsArticle',
109
-		'AskPublicNewsArticle',
110
-		'BackgroundNewsArticle',
111
-		'OpinionNewsArticle',
112
-		'ReportageNewsArticle',
113
-		'ReviewNewsArticle',
114
-		'MedicalScholarlyArticle',
115
-		'BlogPosting',
116
-		'DiscussionForumPosting',
117
-		'LiveBlogPosting',
118
-		'ApiReference',
119
-		'Audiobook',
120
-		'MovieClip',
121
-		'RadioClip',
122
-		'TvClip',
123
-		'VideoGameClip',
124
-		'ProductCollection',
125
-		'ComicCoverArt',
126
-		'Answer',
127
-		'CorrectionComment',
128
-		'Question',
129
-		'PodcastSeason',
130
-		'RadioSeason',
131
-		'TvSeason',
132
-		'BookSeries',
133
-		'MovieSeries',
134
-		'Periodical',
135
-		'PodcastSeries',
136
-		'RadioSeries',
137
-		'TvSeries',
138
-		'VideoGameSeries',
139
-		'ComicSeries',
140
-		'Newspaper',
141
-		'DataFeed',
142
-		'CompleteDataFeed',
143
-		'CategoryCodeSet',
144
-		'NoteDigitalDocument',
145
-		'PresentationDigitalDocument',
146
-		'SpreadsheetDigitalDocument',
147
-		'TextDigitalDocument',
148
-		'PodcastEpisode',
149
-		'RadioEpisode',
150
-		'TvEpisode',
151
-		'VideoGame',
152
-		'Recipe',
153
-		'Course',
154
-		'Quiz',
155
-		'LegislationObject',
156
-		'AudioObject',
157
-		'DModel',
158
-		'DataDownload',
159
-		'ImageObject',
160
-		'LegislationObject',
161
-		'MusicVideoObject',
162
-		'VideoObject',
163
-		'Audiobook',
164
-		'Barcode',
165
-		'EmailMessage',
166
-		'MusicAlbum',
167
-		'MusicRelease',
168
-		'ComicIssue',
169
-		'ClaimReview',
170
-		'CriticReview',
171
-		'EmployerReview',
172
-		'MediaReview',
173
-		'Recommendation',
174
-		'UserReview',
175
-		'ReviewNewsArticle',
176
-		'MobileApplication',
177
-		'VideoGame',
178
-		'WebApplication',
179
-		'CoverArt',
180
-		'ComicCoverArt',
181
-		'HealthTopicContent',
182
-		'AboutPage',
183
-		'CheckoutPage',
184
-		'CollectionPage',
185
-		'ContactPage',
186
-		'FaqPage',
187
-		'ItemPage',
188
-		'MedicalWebPage',
189
-		'ProfilePage',
190
-		'QaPage',
191
-		'RealEstateListing',
192
-		'SearchResultsPage',
193
-		'MediaGallery',
194
-		'ImageGallery',
195
-		'VideoGallery',
196
-		'SiteNavigationElement',
197
-		'Table',
198
-		'WpAdBlock',
199
-		'WpFooter',
200
-		'WpHeader',
201
-		'WpSideBar',
202
-	);
203
-
204
-	/**
205
-	 * The singleton instance for the JSON-LD service.
206
-	 *
207
-	 * @since 3.15.1
208
-	 *
209
-	 * @var \Wordlift_Jsonld_Service $instance The singleton instance for the JSON-LD service.
210
-	 */
211
-	private static $instance;
212
-
213
-	/**
214
-	 * A {@link Wordlift_Entity_Service} instance.
215
-	 *
216
-	 * @since  3.8.0
217
-	 * @access private
218
-	 * @var Wordlift_Entity_Service $entity_service A {@link Wordlift_Entity_Service} instance.
219
-	 */
220
-	private $entity_service;
221
-
222
-	/**
223
-	 * A {@link Wordlift_Term_JsonLd_Adapter} instance.
224
-	 *
225
-	 * @since  3.32.0
226
-	 * @access private
227
-	 * @var Wordlift_Term_JsonLd_Adapter $entity_service A {@link Wordlift_Term_JsonLd_Adapter} instance.
228
-	 */
229
-	private $term_jsonld_adapter;
230
-
231
-	/**
232
-	 * A {@link Wordlift_Post_Converter} instance.
233
-	 *
234
-	 * @since  3.8.0
235
-	 * @access private
236
-	 * @var \Wordlift_Post_Converter A {@link Wordlift_Post_Converter} instance.
237
-	 */
238
-	private $converter;
239
-
240
-	/**
241
-	 * A {@link Wordlift_Website_Jsonld_Converter} instance.
242
-	 *
243
-	 * @since  3.14.0
244
-	 * @access private
245
-	 * @var \Wordlift_Website_Jsonld_Converter A {@link Wordlift_Website_Jsonld_Converter} instance.
246
-	 */
247
-	private $website_converter;
248
-
249
-	/**
250
-	 * Create a JSON-LD service.
251
-	 *
252
-	 * @param \Wordlift_Entity_Service           $entity_service A {@link Wordlift_Entity_Service} instance.
253
-	 * @param \Wordlift_Post_Converter           $converter A {@link Wordlift_Uri_To_Jsonld_Converter} instance.
254
-	 * @param \Wordlift_Website_Jsonld_Converter $website_converter A {@link Wordlift_Website_Jsonld_Converter} instance.
255
-	 * @param \Wordlift_Term_JsonLd_Adapter      $term_jsonld_adapter
256
-	 *
257
-	 * @since 3.8.0
258
-	 */
259
-	public function __construct( $entity_service, $converter, $website_converter, $term_jsonld_adapter ) {
260
-		$this->entity_service      = $entity_service;
261
-		$this->converter           = $converter;
262
-		$this->website_converter   = $website_converter;
263
-		$this->term_jsonld_adapter = $term_jsonld_adapter;
264
-		self::$instance            = $this;
265
-	}
266
-
267
-	/**
268
-	 * Get the singleton instance for the JSON-LD service.
269
-	 *
270
-	 * @return \Wordlift_Jsonld_Service The singleton instance for the JSON-LD service.
271
-	 * @since 3.15.1
272
-	 */
273
-	public static function get_instance() {
274
-
275
-		return self::$instance;
276
-	}
277
-
278
-	/**
279
-	 * Process calls to the AJAX 'wl_jsonld' endpoint.
280
-	 *
281
-	 * @since 3.8.0
282
-	 */
283
-	public function get() {
284
-		// Clear the buffer to be sure someone doesn't mess with our response.
285
-		//
286
-		// See https://github.com/insideout10/wordlift-plugin/issues/406.
287
-		// See https://codex.wordpress.org/AJAX_in_Plugins.
288
-		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
289
-		@ob_clean();
290
-
291
-		// Get the parameter from the request.
292
-		$is_homepage = isset( $_REQUEST['homepage'] ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
293
-		$post_id     = isset( $_REQUEST['id'] ) && is_numeric( $_REQUEST['id'] ) ? intval( $_REQUEST['id'] ) : null; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
294
-
295
-		// Send the generated JSON-LD.
296
-		$this->send_jsonld( $this->get_jsonld( $is_homepage, $post_id ) );
297
-
298
-	}
299
-
300
-	/**
301
-	 * A close of WP's own `wp_send_json` function which uses `application/ld+json` as content type.
302
-	 *
303
-	 * @param mixed $response Variable (usually an array or object) to encode as JSON,
304
-	 *                           then print and die.
305
-	 *
306
-	 * @since 3.18.5
307
-	 */
308
-	private function send_jsonld( $response ) {
309
-		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
310
-		@header( 'Content-Type: application/ld+json; charset=' . get_option( 'blog_charset' ) );
311
-		echo wp_json_encode( $response );
312
-		if ( apply_filters( 'wp_doing_ajax', defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
313
-			wp_die();
314
-		} else {
315
-			die;
316
-		}
317
-	}
318
-
319
-	/**
320
-	 * Get the JSON-LD.
321
-	 *
322
-	 * @param bool     $is_homepage Whether the JSON-LD for the homepage is being requested.
323
-	 * @param int|null $post_id The JSON-LD for the specified {@link WP_Post} id.
324
-	 * @param int      $context A context for the JSON-LD generation, valid values in Jsonld_Context_Enum.
325
-	 *
326
-	 * @return array A JSON-LD structure.
327
-	 * @since 3.15.1
328
-	 */
329
-	public function get_jsonld( $is_homepage = false, $post_id = null, $context = Jsonld_Context_Enum::UNKNOWN ) {
330
-
331
-		/**
332
-		 * Filter name: wl_before_get_jsonld
333
-		 *
334
-		 * @var bool $is_homepage Whether the JSON-LD for the homepage is being requested.
335
-		 * @var int|null $post_id The JSON-LD for the specified {@link WP_Post} id.
336
-		 * @var int $context A context for the JSON-LD generation, valid values in Jsonld_Context_Enum.
337
-		 *
338
-		 * @since 3.52.7
339
-		 */
340
-		do_action( 'wl_before_get_jsonld', $is_homepage, $post_id, $context );
341
-
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
-		return $jsonld_arr;
419
-	}
420
-
421
-	/**
422
-	 * Write the JSON-LD in the head.
423
-	 *
424
-	 * This function isn't actually used, but may be used to quickly enable writing the JSON-LD synchronously to the
425
-	 * document head, using the `wp_head` hook.
426
-	 *
427
-	 * @since 3.18.5
428
-	 */
429
-	public function wp_head() {
430
-
431
-		// Determine whether this is the home page or whether we're displaying a single post.
432
-		$is_homepage = is_home() || is_front_page();
433
-		$post_id     = is_singular() ? get_the_ID() : null;
434
-
435
-		$jsonld = wp_json_encode( $this->get_jsonld( $is_homepage, $post_id, Jsonld_Context_Enum::PAGE ) );
436
-		?>
23
+    /**
24
+     * Creative work types.
25
+     *
26
+     * @var string[]
27
+     */
28
+    private static $creative_work_types = array(
29
+        'AmpStory',
30
+        'ArchiveComponent',
31
+        'Article',
32
+        'Atlas',
33
+        'Blog',
34
+        'Book',
35
+        'Chapter',
36
+        'Claim',
37
+        'Clip',
38
+        'Code',
39
+        'Collection',
40
+        'ComicStory',
41
+        'Comment',
42
+        'Conversation',
43
+        'Course',
44
+        'CreativeWork',
45
+        'CreativeWorkSeason',
46
+        'CreativeWorkSeries',
47
+        'DataCatalog',
48
+        'Dataset',
49
+        'DefinedTermSet',
50
+        'Diet',
51
+        'DigitalDocument',
52
+        'Drawing',
53
+        'EducationalOccupationalCredential',
54
+        'Episode',
55
+        'ExercisePlan',
56
+        'Game',
57
+        'Guide',
58
+        'HowTo',
59
+        'HowToDirection',
60
+        'HowToSection',
61
+        'HowToStep',
62
+        'HowToTip',
63
+        'HyperToc',
64
+        'HyperTocEntry',
65
+        'LearningResource',
66
+        'Legislation',
67
+        'Manuscript',
68
+        'Map',
69
+        'MathSolver',
70
+        'MediaObject',
71
+        'Menu',
72
+        'MenuSection',
73
+        'Message',
74
+        'Movie',
75
+        'MusicComposition',
76
+        'MusicPlaylist',
77
+        'MusicRecording',
78
+        'Painting',
79
+        'Photograph',
80
+        'Play',
81
+        'Poster',
82
+        'PublicationIssue',
83
+        'PublicationVolume',
84
+        'Quotation',
85
+        'Review',
86
+        'Sculpture',
87
+        'Season',
88
+        'SheetMusic',
89
+        'ShortStory',
90
+        'SoftwareApplication',
91
+        'SoftwareSourceCode',
92
+        'SpecialAnnouncement',
93
+        'Thesis',
94
+        'TvSeason',
95
+        'TvSeries',
96
+        'VisualArtwork',
97
+        'WebContent',
98
+        'WebPage',
99
+        'WebPageElement',
100
+        'WebSite',
101
+        'AdvertiserContentArticle',
102
+        'NewsArticle',
103
+        'Report',
104
+        'SatiricalArticle',
105
+        'ScholarlyArticle',
106
+        'SocialMediaPosting',
107
+        'TechArticle',
108
+        'AnalysisNewsArticle',
109
+        'AskPublicNewsArticle',
110
+        'BackgroundNewsArticle',
111
+        'OpinionNewsArticle',
112
+        'ReportageNewsArticle',
113
+        'ReviewNewsArticle',
114
+        'MedicalScholarlyArticle',
115
+        'BlogPosting',
116
+        'DiscussionForumPosting',
117
+        'LiveBlogPosting',
118
+        'ApiReference',
119
+        'Audiobook',
120
+        'MovieClip',
121
+        'RadioClip',
122
+        'TvClip',
123
+        'VideoGameClip',
124
+        'ProductCollection',
125
+        'ComicCoverArt',
126
+        'Answer',
127
+        'CorrectionComment',
128
+        'Question',
129
+        'PodcastSeason',
130
+        'RadioSeason',
131
+        'TvSeason',
132
+        'BookSeries',
133
+        'MovieSeries',
134
+        'Periodical',
135
+        'PodcastSeries',
136
+        'RadioSeries',
137
+        'TvSeries',
138
+        'VideoGameSeries',
139
+        'ComicSeries',
140
+        'Newspaper',
141
+        'DataFeed',
142
+        'CompleteDataFeed',
143
+        'CategoryCodeSet',
144
+        'NoteDigitalDocument',
145
+        'PresentationDigitalDocument',
146
+        'SpreadsheetDigitalDocument',
147
+        'TextDigitalDocument',
148
+        'PodcastEpisode',
149
+        'RadioEpisode',
150
+        'TvEpisode',
151
+        'VideoGame',
152
+        'Recipe',
153
+        'Course',
154
+        'Quiz',
155
+        'LegislationObject',
156
+        'AudioObject',
157
+        'DModel',
158
+        'DataDownload',
159
+        'ImageObject',
160
+        'LegislationObject',
161
+        'MusicVideoObject',
162
+        'VideoObject',
163
+        'Audiobook',
164
+        'Barcode',
165
+        'EmailMessage',
166
+        'MusicAlbum',
167
+        'MusicRelease',
168
+        'ComicIssue',
169
+        'ClaimReview',
170
+        'CriticReview',
171
+        'EmployerReview',
172
+        'MediaReview',
173
+        'Recommendation',
174
+        'UserReview',
175
+        'ReviewNewsArticle',
176
+        'MobileApplication',
177
+        'VideoGame',
178
+        'WebApplication',
179
+        'CoverArt',
180
+        'ComicCoverArt',
181
+        'HealthTopicContent',
182
+        'AboutPage',
183
+        'CheckoutPage',
184
+        'CollectionPage',
185
+        'ContactPage',
186
+        'FaqPage',
187
+        'ItemPage',
188
+        'MedicalWebPage',
189
+        'ProfilePage',
190
+        'QaPage',
191
+        'RealEstateListing',
192
+        'SearchResultsPage',
193
+        'MediaGallery',
194
+        'ImageGallery',
195
+        'VideoGallery',
196
+        'SiteNavigationElement',
197
+        'Table',
198
+        'WpAdBlock',
199
+        'WpFooter',
200
+        'WpHeader',
201
+        'WpSideBar',
202
+    );
203
+
204
+    /**
205
+     * The singleton instance for the JSON-LD service.
206
+     *
207
+     * @since 3.15.1
208
+     *
209
+     * @var \Wordlift_Jsonld_Service $instance The singleton instance for the JSON-LD service.
210
+     */
211
+    private static $instance;
212
+
213
+    /**
214
+     * A {@link Wordlift_Entity_Service} instance.
215
+     *
216
+     * @since  3.8.0
217
+     * @access private
218
+     * @var Wordlift_Entity_Service $entity_service A {@link Wordlift_Entity_Service} instance.
219
+     */
220
+    private $entity_service;
221
+
222
+    /**
223
+     * A {@link Wordlift_Term_JsonLd_Adapter} instance.
224
+     *
225
+     * @since  3.32.0
226
+     * @access private
227
+     * @var Wordlift_Term_JsonLd_Adapter $entity_service A {@link Wordlift_Term_JsonLd_Adapter} instance.
228
+     */
229
+    private $term_jsonld_adapter;
230
+
231
+    /**
232
+     * A {@link Wordlift_Post_Converter} instance.
233
+     *
234
+     * @since  3.8.0
235
+     * @access private
236
+     * @var \Wordlift_Post_Converter A {@link Wordlift_Post_Converter} instance.
237
+     */
238
+    private $converter;
239
+
240
+    /**
241
+     * A {@link Wordlift_Website_Jsonld_Converter} instance.
242
+     *
243
+     * @since  3.14.0
244
+     * @access private
245
+     * @var \Wordlift_Website_Jsonld_Converter A {@link Wordlift_Website_Jsonld_Converter} instance.
246
+     */
247
+    private $website_converter;
248
+
249
+    /**
250
+     * Create a JSON-LD service.
251
+     *
252
+     * @param \Wordlift_Entity_Service           $entity_service A {@link Wordlift_Entity_Service} instance.
253
+     * @param \Wordlift_Post_Converter           $converter A {@link Wordlift_Uri_To_Jsonld_Converter} instance.
254
+     * @param \Wordlift_Website_Jsonld_Converter $website_converter A {@link Wordlift_Website_Jsonld_Converter} instance.
255
+     * @param \Wordlift_Term_JsonLd_Adapter      $term_jsonld_adapter
256
+     *
257
+     * @since 3.8.0
258
+     */
259
+    public function __construct( $entity_service, $converter, $website_converter, $term_jsonld_adapter ) {
260
+        $this->entity_service      = $entity_service;
261
+        $this->converter           = $converter;
262
+        $this->website_converter   = $website_converter;
263
+        $this->term_jsonld_adapter = $term_jsonld_adapter;
264
+        self::$instance            = $this;
265
+    }
266
+
267
+    /**
268
+     * Get the singleton instance for the JSON-LD service.
269
+     *
270
+     * @return \Wordlift_Jsonld_Service The singleton instance for the JSON-LD service.
271
+     * @since 3.15.1
272
+     */
273
+    public static function get_instance() {
274
+
275
+        return self::$instance;
276
+    }
277
+
278
+    /**
279
+     * Process calls to the AJAX 'wl_jsonld' endpoint.
280
+     *
281
+     * @since 3.8.0
282
+     */
283
+    public function get() {
284
+        // Clear the buffer to be sure someone doesn't mess with our response.
285
+        //
286
+        // See https://github.com/insideout10/wordlift-plugin/issues/406.
287
+        // See https://codex.wordpress.org/AJAX_in_Plugins.
288
+        // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
289
+        @ob_clean();
290
+
291
+        // Get the parameter from the request.
292
+        $is_homepage = isset( $_REQUEST['homepage'] ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
293
+        $post_id     = isset( $_REQUEST['id'] ) && is_numeric( $_REQUEST['id'] ) ? intval( $_REQUEST['id'] ) : null; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
294
+
295
+        // Send the generated JSON-LD.
296
+        $this->send_jsonld( $this->get_jsonld( $is_homepage, $post_id ) );
297
+
298
+    }
299
+
300
+    /**
301
+     * A close of WP's own `wp_send_json` function which uses `application/ld+json` as content type.
302
+     *
303
+     * @param mixed $response Variable (usually an array or object) to encode as JSON,
304
+     *                           then print and die.
305
+     *
306
+     * @since 3.18.5
307
+     */
308
+    private function send_jsonld( $response ) {
309
+        // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
310
+        @header( 'Content-Type: application/ld+json; charset=' . get_option( 'blog_charset' ) );
311
+        echo wp_json_encode( $response );
312
+        if ( apply_filters( 'wp_doing_ajax', defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
313
+            wp_die();
314
+        } else {
315
+            die;
316
+        }
317
+    }
318
+
319
+    /**
320
+     * Get the JSON-LD.
321
+     *
322
+     * @param bool     $is_homepage Whether the JSON-LD for the homepage is being requested.
323
+     * @param int|null $post_id The JSON-LD for the specified {@link WP_Post} id.
324
+     * @param int      $context A context for the JSON-LD generation, valid values in Jsonld_Context_Enum.
325
+     *
326
+     * @return array A JSON-LD structure.
327
+     * @since 3.15.1
328
+     */
329
+    public function get_jsonld( $is_homepage = false, $post_id = null, $context = Jsonld_Context_Enum::UNKNOWN ) {
330
+
331
+        /**
332
+         * Filter name: wl_before_get_jsonld
333
+         *
334
+         * @var bool $is_homepage Whether the JSON-LD for the homepage is being requested.
335
+         * @var int|null $post_id The JSON-LD for the specified {@link WP_Post} id.
336
+         * @var int $context A context for the JSON-LD generation, valid values in Jsonld_Context_Enum.
337
+         *
338
+         * @since 3.52.7
339
+         */
340
+        do_action( 'wl_before_get_jsonld', $is_homepage, $post_id, $context );
341
+
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
+        return $jsonld_arr;
419
+    }
420
+
421
+    /**
422
+     * Write the JSON-LD in the head.
423
+     *
424
+     * This function isn't actually used, but may be used to quickly enable writing the JSON-LD synchronously to the
425
+     * document head, using the `wp_head` hook.
426
+     *
427
+     * @since 3.18.5
428
+     */
429
+    public function wp_head() {
430
+
431
+        // Determine whether this is the home page or whether we're displaying a single post.
432
+        $is_homepage = is_home() || is_front_page();
433
+        $post_id     = is_singular() ? get_the_ID() : null;
434
+
435
+        $jsonld = wp_json_encode( $this->get_jsonld( $is_homepage, $post_id, Jsonld_Context_Enum::PAGE ) );
436
+        ?>
437 437
 		<script type="application/ld+json"><?php echo esc_html( $jsonld ); ?></script>
438 438
 		<?php
439
-	}
440
-
441
-	/**
442
-	 * @param array    $jsonld
443
-	 * @param Relation $relation
444
-	 *
445
-	 * @return void
446
-	 */
447
-	private function add_mention_or_about( &$jsonld, $post_id, $relation ) {
448
-		$content_service = Wordpress_Content_Service::get_instance();
449
-		$entity_service  = Wordlift_Entity_Service::get_instance();
450
-
451
-		$object     = $relation->get_object();
452
-		$entity_uri = $content_service->get_entity_id( $object );
453
-		$labels     = $entity_service->get_labels( $object->get_id(), $object->get_type() );
454
-
455
-		$escaped_labels = array_map(
456
-			function ( $value ) {
457
-				return preg_quote( $value, '/' );
458
-			},
459
-			$labels
460
-		);
461
-
462
-		$matches = false;
463
-
464
-		// When the title is empty, then we shouldn't yield a match to about section.
465
-		if ( array_filter( $escaped_labels ) ) {
466
-			// Check if the labels match any part of the title.
467
-			$post    = get_post( $post_id );
468
-			$matches = $this->check_title_match( $escaped_labels, $post->post_title );
469
-		}
470
-
471
-		if ( $entity_uri ) {
472
-			// If the title matches, assign the entity to the about, otherwise to the mentions.
473
-			$property_name              = $matches ? 'about' : 'mentions';
474
-			$jsonld[ $property_name ]   = isset( $jsonld[ $property_name ] ) ? (array) $jsonld[ $property_name ] : array();
475
-			$jsonld[ $property_name ][] = array( '@id' => $entity_uri );
476
-		}
477
-
478
-	}
479
-
480
-	/**
481
-	 * Check if the labels match any part of the title.
482
-	 *
483
-	 * @param $labels array The labels to check.
484
-	 * @param $title string The title to check.
485
-	 *
486
-	 * @return boolean
487
-	 */
488
-	public function check_title_match( $labels, $title ) {
489
-
490
-		// If the title is empty, then we shouldn't yield a match to about section.
491
-		if ( empty( $title ) ) {
492
-			return false;
493
-		}
494
-
495
-		// Check if the labels match any part of the title.
496
-		return 1 === preg_match( '/\b(' . implode( '|', $labels ) . ')\b/iu', $title );
497
-
498
-	}
439
+    }
440
+
441
+    /**
442
+     * @param array    $jsonld
443
+     * @param Relation $relation
444
+     *
445
+     * @return void
446
+     */
447
+    private function add_mention_or_about( &$jsonld, $post_id, $relation ) {
448
+        $content_service = Wordpress_Content_Service::get_instance();
449
+        $entity_service  = Wordlift_Entity_Service::get_instance();
450
+
451
+        $object     = $relation->get_object();
452
+        $entity_uri = $content_service->get_entity_id( $object );
453
+        $labels     = $entity_service->get_labels( $object->get_id(), $object->get_type() );
454
+
455
+        $escaped_labels = array_map(
456
+            function ( $value ) {
457
+                return preg_quote( $value, '/' );
458
+            },
459
+            $labels
460
+        );
461
+
462
+        $matches = false;
463
+
464
+        // When the title is empty, then we shouldn't yield a match to about section.
465
+        if ( array_filter( $escaped_labels ) ) {
466
+            // Check if the labels match any part of the title.
467
+            $post    = get_post( $post_id );
468
+            $matches = $this->check_title_match( $escaped_labels, $post->post_title );
469
+        }
470
+
471
+        if ( $entity_uri ) {
472
+            // If the title matches, assign the entity to the about, otherwise to the mentions.
473
+            $property_name              = $matches ? 'about' : 'mentions';
474
+            $jsonld[ $property_name ]   = isset( $jsonld[ $property_name ] ) ? (array) $jsonld[ $property_name ] : array();
475
+            $jsonld[ $property_name ][] = array( '@id' => $entity_uri );
476
+        }
477
+
478
+    }
479
+
480
+    /**
481
+     * Check if the labels match any part of the title.
482
+     *
483
+     * @param $labels array The labels to check.
484
+     * @param $title string The title to check.
485
+     *
486
+     * @return boolean
487
+     */
488
+    public function check_title_match( $labels, $title ) {
489
+
490
+        // If the title is empty, then we shouldn't yield a match to about section.
491
+        if ( empty( $title ) ) {
492
+            return false;
493
+        }
494
+
495
+        // Check if the labels match any part of the title.
496
+        return 1 === preg_match( '/\b(' . implode( '|', $labels ) . ')\b/iu', $title );
497
+
498
+    }
499 499
 
500 500
 }
Please login to merge, or discard this patch.
src/includes/class-wordlift-post-to-jsonld-converter.php 1 patch
Indentation   +440 added lines, -440 removed lines patch added patch discarded remove patch
@@ -18,470 +18,470 @@
 block discarded – undo
18 18
  */
19 19
 class Wordlift_Post_To_Jsonld_Converter extends Wordlift_Abstract_Post_To_Jsonld_Converter {
20 20
 
21
-	/**
22
-	 * @var Wordlift_Post_To_Jsonld_Converter
23
-	 */
24
-	private static $instance;
25
-
26
-	/**
27
-	 * A {@link Wordlift_Log_Service} instance.
28
-	 *
29
-	 * @since  3.10.0
30
-	 * @access private
31
-	 * @var Wordlift_Log_Service $log A {@link Wordlift_Log_Service} instance.
32
-	 */
33
-	private $log;
34
-
35
-	/**
36
-	 * @var false
37
-	 */
38
-	private $disable_convert_filters;
39
-
40
-	/**
41
-	 * Wordlift_Post_To_Jsonld_Converter constructor.
42
-	 *
43
-	 * @param Wordlift_Entity_Type_Service $entity_type_service A {@link Wordlift_Entity_Type_Service} instance.
44
-	 * @param Wordlift_User_Service        $user_service A {@link Wordlift_User_Service} instance.
45
-	 * @param Wordlift_Attachment_Service  $attachment_service A {@link Wordlift_Attachment_Service} instance.
46
-	 *
47
-	 * @since 3.10.0
48
-	 */
49
-	public function __construct( $entity_type_service, $user_service, $attachment_service, $disable_convert_filters = false ) {
50
-		parent::__construct( $entity_type_service, $user_service, $attachment_service, Wordlift_Property_Getter_Factory::create() );
51
-		$this->disable_convert_filters = $disable_convert_filters;
52
-		// Set a reference to the logger.
53
-		$this->log = Wordlift_Log_Service::get_logger( 'Wordlift_Post_To_Jsonld_Converter' );
54
-
55
-		self::$instance = $this;
56
-
57
-	}
58
-
59
-	public static function get_instance() {
60
-
61
-		return self::$instance;
62
-	}
63
-
64
-	public function new_instance_with_filters_disabled() {
65
-		return new static( $this->entity_type_service, $this->user_service, $this->attachment_service, true );
66
-	}
67
-
68
-	/**
69
-	 * Convert the provided {@link WP_Post} to a JSON-LD array. Any entity reference
70
-	 * found while processing the post is set in the $references array.
71
-	 *
72
-	 * @param int              $post_id The post id.
73
-	 * @param array<Reference> $references An array of entity references.
74
-	 * @param array            $references_infos
75
-	 *
76
-	 * @return array A JSON-LD array.
77
-	 * @since 3.10.0
78
-	 */
79
-	public function convert( $post_id, &$references = array(), &$references_infos = array(), $relations = null ) {
80
-
81
-		// Get the post instance.
82
-		$post = get_post( $post_id );
83
-		if ( null === $post ) {
84
-			// Post not found.
85
-			return null;
86
-		}
87
-
88
-		// Get the base JSON-LD and the list of entities referenced by this entity.
89
-		$jsonld = parent::convert( $post_id, $references, $references_infos, $relations );
90
-
91
-		// Set WebPage by default.
92
-		if ( empty( $jsonld['@type'] ) ) {
93
-			$jsonld['@type'] = 'WebPage';
94
-		}
95
-
96
-		// Get the entity name.
97
-		$jsonld['headline'] = $post->post_title;
98
-
99
-		// Convert entities as `Article`.
100
-		//
101
-		// @see https://github.com/insideout10/wordlift-plugin/issues/1731
102
-		$custom_fields = Wordlift_Entity_Service::get_instance()->is_entity( $post_id )
103
-			? $this->entity_type_service->get_custom_fields_for_term( $this->entity_type_service->get_term_by_slug( 'article' ) )
104
-			: $this->entity_type_service->get_custom_fields_for_post( $post_id );
105
-
106
-		if ( isset( $custom_fields ) ) {
107
-			$this->process_type_custom_fields( $jsonld, $custom_fields, $post, $references, $references_infos );
108
-		}
109
-
110
-		// Set the published and modified dates.
111
-		/*
21
+    /**
22
+     * @var Wordlift_Post_To_Jsonld_Converter
23
+     */
24
+    private static $instance;
25
+
26
+    /**
27
+     * A {@link Wordlift_Log_Service} instance.
28
+     *
29
+     * @since  3.10.0
30
+     * @access private
31
+     * @var Wordlift_Log_Service $log A {@link Wordlift_Log_Service} instance.
32
+     */
33
+    private $log;
34
+
35
+    /**
36
+     * @var false
37
+     */
38
+    private $disable_convert_filters;
39
+
40
+    /**
41
+     * Wordlift_Post_To_Jsonld_Converter constructor.
42
+     *
43
+     * @param Wordlift_Entity_Type_Service $entity_type_service A {@link Wordlift_Entity_Type_Service} instance.
44
+     * @param Wordlift_User_Service        $user_service A {@link Wordlift_User_Service} instance.
45
+     * @param Wordlift_Attachment_Service  $attachment_service A {@link Wordlift_Attachment_Service} instance.
46
+     *
47
+     * @since 3.10.0
48
+     */
49
+    public function __construct( $entity_type_service, $user_service, $attachment_service, $disable_convert_filters = false ) {
50
+        parent::__construct( $entity_type_service, $user_service, $attachment_service, Wordlift_Property_Getter_Factory::create() );
51
+        $this->disable_convert_filters = $disable_convert_filters;
52
+        // Set a reference to the logger.
53
+        $this->log = Wordlift_Log_Service::get_logger( 'Wordlift_Post_To_Jsonld_Converter' );
54
+
55
+        self::$instance = $this;
56
+
57
+    }
58
+
59
+    public static function get_instance() {
60
+
61
+        return self::$instance;
62
+    }
63
+
64
+    public function new_instance_with_filters_disabled() {
65
+        return new static( $this->entity_type_service, $this->user_service, $this->attachment_service, true );
66
+    }
67
+
68
+    /**
69
+     * Convert the provided {@link WP_Post} to a JSON-LD array. Any entity reference
70
+     * found while processing the post is set in the $references array.
71
+     *
72
+     * @param int              $post_id The post id.
73
+     * @param array<Reference> $references An array of entity references.
74
+     * @param array            $references_infos
75
+     *
76
+     * @return array A JSON-LD array.
77
+     * @since 3.10.0
78
+     */
79
+    public function convert( $post_id, &$references = array(), &$references_infos = array(), $relations = null ) {
80
+
81
+        // Get the post instance.
82
+        $post = get_post( $post_id );
83
+        if ( null === $post ) {
84
+            // Post not found.
85
+            return null;
86
+        }
87
+
88
+        // Get the base JSON-LD and the list of entities referenced by this entity.
89
+        $jsonld = parent::convert( $post_id, $references, $references_infos, $relations );
90
+
91
+        // Set WebPage by default.
92
+        if ( empty( $jsonld['@type'] ) ) {
93
+            $jsonld['@type'] = 'WebPage';
94
+        }
95
+
96
+        // Get the entity name.
97
+        $jsonld['headline'] = $post->post_title;
98
+
99
+        // Convert entities as `Article`.
100
+        //
101
+        // @see https://github.com/insideout10/wordlift-plugin/issues/1731
102
+        $custom_fields = Wordlift_Entity_Service::get_instance()->is_entity( $post_id )
103
+            ? $this->entity_type_service->get_custom_fields_for_term( $this->entity_type_service->get_term_by_slug( 'article' ) )
104
+            : $this->entity_type_service->get_custom_fields_for_post( $post_id );
105
+
106
+        if ( isset( $custom_fields ) ) {
107
+            $this->process_type_custom_fields( $jsonld, $custom_fields, $post, $references, $references_infos );
108
+        }
109
+
110
+        // Set the published and modified dates.
111
+        /*
112 112
 		 * Set the `datePublished` and `dateModified` using the local timezone.
113 113
 		 *
114 114
 		 * @see https://github.com/insideout10/wordlift-plugin/issues/887
115 115
 		 *
116 116
 		 * @since 3.20.0
117 117
 		 */
118
-		try {
119
-			$default_timezone = date_default_timezone_get();
120
-			$timezone         = get_option( 'timezone_string' );
121
-			if ( ! empty( $timezone ) ) {
122
-				date_default_timezone_set( $timezone ); //phpcs:ignore WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set
123
-				$jsonld['datePublished'] = get_post_time( 'Y-m-d\TH:i:sP', false, $post );
124
-				$jsonld['dateModified']  = get_post_modified_time( 'Y-m-d\TH:i:sP', false, $post );
125
-				date_default_timezone_set( $default_timezone ); //phpcs:ignore WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set
126
-			} else {
127
-				$jsonld['datePublished'] = get_post_time( 'Y-m-d\TH:i', true, $post, false );
128
-				$jsonld['dateModified']  = get_post_modified_time( 'Y-m-d\TH:i', true, $post, false );
129
-			}
130
-		} catch ( Exception $e ) {
131
-			$jsonld['datePublished'] = get_post_time( 'Y-m-d\TH:i', true, $post, false );
132
-			$jsonld['dateModified']  = get_post_modified_time( 'Y-m-d\TH:i', true, $post, false );
133
-		}
134
-
135
-		// Get the word count for the post.
136
-		/*
118
+        try {
119
+            $default_timezone = date_default_timezone_get();
120
+            $timezone         = get_option( 'timezone_string' );
121
+            if ( ! empty( $timezone ) ) {
122
+                date_default_timezone_set( $timezone ); //phpcs:ignore WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set
123
+                $jsonld['datePublished'] = get_post_time( 'Y-m-d\TH:i:sP', false, $post );
124
+                $jsonld['dateModified']  = get_post_modified_time( 'Y-m-d\TH:i:sP', false, $post );
125
+                date_default_timezone_set( $default_timezone ); //phpcs:ignore WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set
126
+            } else {
127
+                $jsonld['datePublished'] = get_post_time( 'Y-m-d\TH:i', true, $post, false );
128
+                $jsonld['dateModified']  = get_post_modified_time( 'Y-m-d\TH:i', true, $post, false );
129
+            }
130
+        } catch ( Exception $e ) {
131
+            $jsonld['datePublished'] = get_post_time( 'Y-m-d\TH:i', true, $post, false );
132
+            $jsonld['dateModified']  = get_post_modified_time( 'Y-m-d\TH:i', true, $post, false );
133
+        }
134
+
135
+        // Get the word count for the post.
136
+        /*
137 137
 		 * Do not display the `wordCount` on a `WebPage`.
138 138
 		 *
139 139
 		 * @see https://github.com/insideout10/wordlift-plugin/issues/888
140 140
 		 *
141 141
 		 * @since 3.20.0
142 142
 		 */
143
-		if ( ! empty( $jsonld['@type'] ) && 'WebPage' !== $jsonld['@type'] ) {
144
-			$post_adapter        = new Wordlift_Post_Adapter( $post_id );
145
-			$jsonld['wordCount'] = $post_adapter->word_count();
146
-		}
143
+        if ( ! empty( $jsonld['@type'] ) && 'WebPage' !== $jsonld['@type'] ) {
144
+            $post_adapter        = new Wordlift_Post_Adapter( $post_id );
145
+            $jsonld['wordCount'] = $post_adapter->word_count();
146
+        }
147 147
 
148
-		/*
148
+        /*
149 149
 		 * Add keywords, articleSection, commentCount and inLanguage properties to `Article` JSON-LD
150 150
 		 *
151 151
 		 * @see https://github.com/insideout10/wordlift-plugin/issues/1140
152 152
 		 *
153 153
 		 * @since 3.27.2
154 154
 		 */
155
-		if ( ! empty( $jsonld['@type'] ) && 'WebPage' !== $jsonld['@type'] ) {
156
-			$post_adapter    = new Wordlift_Post_Adapter( $post_id );
157
-			$keywords        = $post_adapter->keywords();
158
-			$article_section = $post_adapter->article_section();
159
-			$comment_count   = $post_adapter->comment_count();
160
-			$locale          = $post_adapter->locale();
161
-
162
-			if ( isset( $keywords ) ) {
163
-				$jsonld['keywords'] = $keywords;
164
-			}
165
-			if ( ! empty( $article_section ) ) {
166
-				$jsonld['articleSection'] = $article_section;
167
-			}
168
-			$jsonld['commentCount'] = $comment_count;
169
-			$jsonld['inLanguage']   = $locale;
170
-		}
171
-
172
-		// Set the publisher.
173
-		$this->set_publisher( $jsonld );
174
-
175
-		/**
176
-		 * Call the `wl_post_jsonld_author` filter.
177
-		 *
178
-		 * This filter checks if there are co-authors or a single author and
179
-		 * returns a JSON-LD fragment for the author(s).
180
-		 *
181
-		 * @param array $value {
182
-		 *
183
-		 * @type array $jsonld The JSON-LD structure.
184
-		 * @type int[] $references An array of post IDs.
185
-		 * }
186
-		 *
187
-		 * @param int $post_id The {@link WP_Post} `id`.
188
-		 *
189
-		 * @since 3.51.4
190
-		 *
191
-		 * @see https://www.geeklab.info/2010/04/wordpress-pass-variables-by-reference-with-apply_filter/
192
-		 */
193
-		$ret_val = apply_filters(
194
-			'wl_jsonld_author',
195
-			array(
196
-				'author'     => $this->get_author( $post->post_author, $references ),
197
-				'references' => $references,
198
-			),
199
-			$post_id
200
-		);
201
-
202
-		// Set the values returned by the author filter.
203
-		/*
155
+        if ( ! empty( $jsonld['@type'] ) && 'WebPage' !== $jsonld['@type'] ) {
156
+            $post_adapter    = new Wordlift_Post_Adapter( $post_id );
157
+            $keywords        = $post_adapter->keywords();
158
+            $article_section = $post_adapter->article_section();
159
+            $comment_count   = $post_adapter->comment_count();
160
+            $locale          = $post_adapter->locale();
161
+
162
+            if ( isset( $keywords ) ) {
163
+                $jsonld['keywords'] = $keywords;
164
+            }
165
+            if ( ! empty( $article_section ) ) {
166
+                $jsonld['articleSection'] = $article_section;
167
+            }
168
+            $jsonld['commentCount'] = $comment_count;
169
+            $jsonld['inLanguage']   = $locale;
170
+        }
171
+
172
+        // Set the publisher.
173
+        $this->set_publisher( $jsonld );
174
+
175
+        /**
176
+         * Call the `wl_post_jsonld_author` filter.
177
+         *
178
+         * This filter checks if there are co-authors or a single author and
179
+         * returns a JSON-LD fragment for the author(s).
180
+         *
181
+         * @param array $value {
182
+         *
183
+         * @type array $jsonld The JSON-LD structure.
184
+         * @type int[] $references An array of post IDs.
185
+         * }
186
+         *
187
+         * @param int $post_id The {@link WP_Post} `id`.
188
+         *
189
+         * @since 3.51.4
190
+         *
191
+         * @see https://www.geeklab.info/2010/04/wordpress-pass-variables-by-reference-with-apply_filter/
192
+         */
193
+        $ret_val = apply_filters(
194
+            'wl_jsonld_author',
195
+            array(
196
+                'author'     => $this->get_author( $post->post_author, $references ),
197
+                'references' => $references,
198
+            ),
199
+            $post_id
200
+        );
201
+
202
+        // Set the values returned by the author filter.
203
+        /*
204 204
 		 * Do not add the author JSON-LD if an invalid author was referenced in a post.
205 205
 		 *
206 206
 		 * @see https://github.com/insideout10/wordlift-plugin/issues/1728
207 207
 		 *
208 208
 		 * @since 3.53.2
209 209
 		 */
210
-		if ( ! empty( $ret_val['author'] ) ) {
211
-			$jsonld['author'] = $ret_val['author'];
212
-			$references       = $ret_val['references'];
213
-		}
214
-
215
-		// Return the JSON-LD if filters are disabled by the client.
216
-		if ( $this->disable_convert_filters ) {
217
-			return $jsonld;
218
-		}
219
-
220
-		/**
221
-		 * Call the `wl_post_jsonld_array` filter. This filter allows 3rd parties to also modify the references.
222
-		 *
223
-		 * @param array $value {
224
-		 *
225
-		 * @type array $jsonld The JSON-LD structure.
226
-		 * @type int[] $references An array of post IDs.
227
-		 * @type Relations $relations A set of `Relation`s.
228
-		 * }
229
-		 * @since 3.25.0
230
-		 * @since 3.43.0 The filter provides a `Relations` instance.
231
-		 *
232
-		 * @see https://www.geeklab.info/2010/04/wordpress-pass-variables-by-reference-with-apply_filter/
233
-		 *
234
-		 * @api
235
-		 */
236
-		$ret_val = apply_filters(
237
-			'wl_post_jsonld_array',
238
-			array(
239
-				'jsonld'           => $jsonld,
240
-				'references'       => $references, // This one is only an array of post IDs.
241
-				'references_infos' => $references_infos,
242
-				'relations'        => $relations,
243
-			),
244
-			$post_id
245
-		);
246
-
247
-		$jsonld     = $ret_val['jsonld'];
248
-		$references = $ret_val['references'];
249
-
250
-		/**
251
-		 * Call the `wl_post_jsonld` filter.
252
-		 *
253
-		 * @param array $jsonld The JSON-LD structure.
254
-		 * @param int $post_id The {@link WP_Post} `id`.
255
-		 * @param array $references The array of referenced entities.
256
-		 *
257
-		 * @since 3.14.0
258
-		 *
259
-		 * @api
260
-		 */
261
-		return apply_filters( 'wl_post_jsonld', $jsonld, $post_id, $references );
262
-	}
263
-
264
-	/**
265
-	 * Get the author's JSON-LD fragment.
266
-	 *
267
-	 * The JSON-LD fragment is generated using the {@link WP_User}'s data or
268
-	 * the referenced entity if configured for the {@link WP_User}.
269
-	 *
270
-	 * @param int   $author_id The author {@link WP_User}'s `id`.
271
-	 * @param array $references An array of referenced entities.
272
-	 *
273
-	 * @return string|array A JSON-LD structure.
274
-	 * @since 3.14.0
275
-	 */
276
-	public function get_author( $author_id, &$references ) {
277
-
278
-		// Get the entity bound to this user.
279
-		$entity_id = $this->user_service->get_entity( $author_id );
280
-
281
-		if ( ! empty( $entity_id ) && 'publish' === get_post_status( $entity_id ) ) {
282
-			// Add the author to the references.
283
-			$author_uri   = Wordlift_Entity_Service::get_instance()->get_uri( $entity_id );
284
-			$references[] = $entity_id;
285
-
286
-			// Return the JSON-LD for the referenced entity.
287
-			return array(
288
-				'@id' => $author_uri,
289
-			);
290
-		}
291
-
292
-		// If there's no entity bound return a simple author structure.
293
-		if ( false !== get_userdata( $author_id ) ) {
294
-			$author            = get_the_author_meta( 'display_name', $author_id );
295
-			$author_first_name = get_the_author_meta( 'first_name', $author_id );
296
-			$author_last_name  = get_the_author_meta( 'last_name', $author_id );
297
-			$author_uri        = $this->user_service->get_uri( $author_id );
298
-
299
-			return array(
300
-				'@type'      => 'Person',
301
-				'@id'        => $author_uri,
302
-				'name'       => $author,
303
-				'givenName'  => $author_first_name,
304
-				'familyName' => $author_last_name,
305
-				'url'        => get_author_posts_url( $author_id ),
306
-			);
307
-		}
308
-
309
-		// No valid entity or author so return empty array
310
-		return array();
311
-	}
312
-
313
-	/**
314
-	 * Enrich the provided params array with publisher data, if available.
315
-	 *
316
-	 * @param array $params The parameters array.
317
-	 *
318
-	 * @since 3.10.0
319
-	 */
320
-	protected function set_publisher( &$params ) {
321
-
322
-		// If the publisher id isn't set don't do anything.
323
-		$publisher_id = Wordlift_Configuration_Service::get_instance()->get_publisher_id();
324
-		if ( empty( $publisher_id ) ) {
325
-			return;
326
-		}
327
-
328
-		// Get the post instance.
329
-		$post = get_post( $publisher_id );
330
-		if ( ! is_a( $post, '\WP_Post' ) ) {
331
-			// Publisher not found.
332
-			return;
333
-		}
334
-
335
-		// Get the item id.
336
-		$id = Wordlift_Entity_Service::get_instance()->get_uri( $publisher_id );
337
-
338
-		// Get the type.
339
-		$type = $this->entity_type_service->get( $publisher_id );
340
-
341
-		// Get the name.
342
-		$name = $post->post_title;
343
-
344
-		// Set the publisher data.
345
-		$params['publisher'] = array(
346
-			'@type' => $this->relative_to_context( $type['uri'] ),
347
-			'@id'   => $id,
348
-			'name'  => $name,
349
-		);
350
-
351
-		// Add the sameAs values associated with the publisher.
352
-		$storage_factory = Wordlift_Storage_Factory::get_instance();
353
-		$sameas          = $storage_factory->post_meta( Wordlift_Schema_Service::FIELD_SAME_AS )->get( $publisher_id );
354
-		if ( ! empty( $sameas ) ) {
355
-			$params['publisher']['sameAs'] = $sameas;
356
-		}
357
-
358
-		// Set the logo, only for http://schema.org/Organization as Person doesn't
359
-		// support the logo property.
360
-		//
361
-		// See http://schema.org/logo.
362
-		if ( 1 !== preg_match( '~Organization$~', $type['uri'] ) ) {
363
-			return;
364
-		}
365
-
366
-		// Get the publisher logo.
367
-		$publisher_logo = $this->get_publisher_logo( $post->ID );
368
-
369
-		// Bail out if the publisher logo isn't set.
370
-		if ( false === $publisher_logo ) {
371
-			return;
372
-		}
373
-
374
-		// Copy over some useful properties.
375
-		//
376
-		// See https://developers.google.com/search/docs/data-types/articles.
377
-		$params['publisher']['logo']['@type'] = 'ImageObject';
378
-		$params['publisher']['logo']['url']   = $publisher_logo['url'];
379
-
380
-		// If you specify a "width" or "height" value you should leave out
381
-		// 'px'. For example: "width":"4608px" should be "width":"4608".
382
-		//
383
-		// See https://github.com/insideout10/wordlift-plugin/issues/451.
384
-		$params['publisher']['logo']['width']  = $publisher_logo['width'];
385
-		$params['publisher']['logo']['height'] = $publisher_logo['height'];
386
-
387
-	}
388
-
389
-	/**
390
-	 * Get the publisher logo structure.
391
-	 *
392
-	 * The function returns false when the publisher logo cannot be determined, i.e.:
393
-	 *  - the post has no featured image.
394
-	 *  - the featured image has no file.
395
-	 *  - a wp_image_editor instance cannot be instantiated on the original file or on the publisher logo file.
396
-	 *
397
-	 * @param int $post_id The post id.
398
-	 *
399
-	 * @return array|false Returns an array with the `url`, `width` and `height` for the publisher logo or false in case
400
-	 *  of errors.
401
-	 * @since 3.19.2
402
-	 * @see https://github.com/insideout10/wordlift-plugin/issues/823 related issue.
403
-	 */
404
-	private function get_publisher_logo( $post_id ) {
405
-
406
-		// Get the featured image for the post.
407
-		$thumbnail_id = get_post_thumbnail_id( $post_id );
408
-
409
-		// Bail out if thumbnail not available.
410
-		if ( empty( $thumbnail_id ) || 0 === $thumbnail_id ) {
411
-			$this->log->info( "Featured image not set for post $post_id." );
412
-
413
-			return false;
414
-		}
415
-
416
-		// Get the uploads base URL.
417
-		$uploads_dir = wp_upload_dir();
418
-
419
-		// Get the attachment metadata.
420
-		$metadata = wp_get_attachment_metadata( $thumbnail_id );
421
-
422
-		// Bail out if the file isn't set.
423
-		if ( ! isset( $metadata['file'] ) ) {
424
-			$this->log->warn( "Featured image file not found for post $post_id." );
425
-
426
-			return false;
427
-		}
428
-
429
-		// Retrieve the relative filename, e.g. "2018/05/logo_publisher.png"
430
-		$path = $uploads_dir['basedir'] . DIRECTORY_SEPARATOR . $metadata['file'];
431
-
432
-		// Use image src, if local file does not exist. @see https://github.com/insideout10/wordlift-plugin/issues/1149
433
-		if ( ! file_exists( $path ) ) {
434
-			$this->log->warn( "Featured image file $path doesn't exist for post $post_id." );
435
-
436
-			$attachment_image_src = wp_get_attachment_image_src( $thumbnail_id, '' );
437
-			if ( $attachment_image_src ) {
438
-				return array(
439
-					'url'    => $attachment_image_src[0],
440
-					'width'  => $attachment_image_src[1],
441
-					'height' => $attachment_image_src[2],
442
-				);
443
-			}
444
-
445
-			// Bail out if we cant fetch wp_get_attachment_image_src
446
-			return false;
447
-
448
-		}
449
-
450
-		// Try to get the image editor and bail out if the editor cannot be instantiated.
451
-		$original_file_editor = wp_get_image_editor( $path );
452
-		if ( is_wp_error( $original_file_editor ) ) {
453
-			$this->log->warn( "Cannot instantiate WP Image Editor on file $path for post $post_id." );
454
-
455
-			return false;
456
-		}
457
-
458
-		// Generate the publisher logo filename, we cannot use the `width` and `height` because we're scaling
459
-		// and we don't actually know the end values.
460
-		$publisher_logo_path = $original_file_editor->generate_filename( '-publisher-logo' );
461
-
462
-		// If the file doesn't exist yet, create it.
463
-		if ( ! file_exists( $publisher_logo_path ) ) {
464
-			$original_file_editor->resize( 600, 60 );
465
-			$original_file_editor->save( $publisher_logo_path );
466
-		}
467
-
468
-		// Try to get the image editor and bail out if the editor cannot be instantiated.
469
-		$publisher_logo_editor = wp_get_image_editor( $publisher_logo_path );
470
-		if ( is_wp_error( $publisher_logo_editor ) ) {
471
-			$this->log->warn( "Cannot instantiate WP Image Editor on file $publisher_logo_path for post $post_id." );
472
-
473
-			return false;
474
-		}
475
-
476
-		// Get the actual size.
477
-		$size = $publisher_logo_editor->get_size();
478
-
479
-		// Finally return the array with data.
480
-		return array(
481
-			'url'    => $uploads_dir['baseurl'] . substr( $publisher_logo_path, strlen( $uploads_dir['basedir'] ) ),
482
-			'width'  => $size['width'],
483
-			'height' => $size['height'],
484
-		);
485
-	}
210
+        if ( ! empty( $ret_val['author'] ) ) {
211
+            $jsonld['author'] = $ret_val['author'];
212
+            $references       = $ret_val['references'];
213
+        }
214
+
215
+        // Return the JSON-LD if filters are disabled by the client.
216
+        if ( $this->disable_convert_filters ) {
217
+            return $jsonld;
218
+        }
219
+
220
+        /**
221
+         * Call the `wl_post_jsonld_array` filter. This filter allows 3rd parties to also modify the references.
222
+         *
223
+         * @param array $value {
224
+         *
225
+         * @type array $jsonld The JSON-LD structure.
226
+         * @type int[] $references An array of post IDs.
227
+         * @type Relations $relations A set of `Relation`s.
228
+         * }
229
+         * @since 3.25.0
230
+         * @since 3.43.0 The filter provides a `Relations` instance.
231
+         *
232
+         * @see https://www.geeklab.info/2010/04/wordpress-pass-variables-by-reference-with-apply_filter/
233
+         *
234
+         * @api
235
+         */
236
+        $ret_val = apply_filters(
237
+            'wl_post_jsonld_array',
238
+            array(
239
+                'jsonld'           => $jsonld,
240
+                'references'       => $references, // This one is only an array of post IDs.
241
+                'references_infos' => $references_infos,
242
+                'relations'        => $relations,
243
+            ),
244
+            $post_id
245
+        );
246
+
247
+        $jsonld     = $ret_val['jsonld'];
248
+        $references = $ret_val['references'];
249
+
250
+        /**
251
+         * Call the `wl_post_jsonld` filter.
252
+         *
253
+         * @param array $jsonld The JSON-LD structure.
254
+         * @param int $post_id The {@link WP_Post} `id`.
255
+         * @param array $references The array of referenced entities.
256
+         *
257
+         * @since 3.14.0
258
+         *
259
+         * @api
260
+         */
261
+        return apply_filters( 'wl_post_jsonld', $jsonld, $post_id, $references );
262
+    }
263
+
264
+    /**
265
+     * Get the author's JSON-LD fragment.
266
+     *
267
+     * The JSON-LD fragment is generated using the {@link WP_User}'s data or
268
+     * the referenced entity if configured for the {@link WP_User}.
269
+     *
270
+     * @param int   $author_id The author {@link WP_User}'s `id`.
271
+     * @param array $references An array of referenced entities.
272
+     *
273
+     * @return string|array A JSON-LD structure.
274
+     * @since 3.14.0
275
+     */
276
+    public function get_author( $author_id, &$references ) {
277
+
278
+        // Get the entity bound to this user.
279
+        $entity_id = $this->user_service->get_entity( $author_id );
280
+
281
+        if ( ! empty( $entity_id ) && 'publish' === get_post_status( $entity_id ) ) {
282
+            // Add the author to the references.
283
+            $author_uri   = Wordlift_Entity_Service::get_instance()->get_uri( $entity_id );
284
+            $references[] = $entity_id;
285
+
286
+            // Return the JSON-LD for the referenced entity.
287
+            return array(
288
+                '@id' => $author_uri,
289
+            );
290
+        }
291
+
292
+        // If there's no entity bound return a simple author structure.
293
+        if ( false !== get_userdata( $author_id ) ) {
294
+            $author            = get_the_author_meta( 'display_name', $author_id );
295
+            $author_first_name = get_the_author_meta( 'first_name', $author_id );
296
+            $author_last_name  = get_the_author_meta( 'last_name', $author_id );
297
+            $author_uri        = $this->user_service->get_uri( $author_id );
298
+
299
+            return array(
300
+                '@type'      => 'Person',
301
+                '@id'        => $author_uri,
302
+                'name'       => $author,
303
+                'givenName'  => $author_first_name,
304
+                'familyName' => $author_last_name,
305
+                'url'        => get_author_posts_url( $author_id ),
306
+            );
307
+        }
308
+
309
+        // No valid entity or author so return empty array
310
+        return array();
311
+    }
312
+
313
+    /**
314
+     * Enrich the provided params array with publisher data, if available.
315
+     *
316
+     * @param array $params The parameters array.
317
+     *
318
+     * @since 3.10.0
319
+     */
320
+    protected function set_publisher( &$params ) {
321
+
322
+        // If the publisher id isn't set don't do anything.
323
+        $publisher_id = Wordlift_Configuration_Service::get_instance()->get_publisher_id();
324
+        if ( empty( $publisher_id ) ) {
325
+            return;
326
+        }
327
+
328
+        // Get the post instance.
329
+        $post = get_post( $publisher_id );
330
+        if ( ! is_a( $post, '\WP_Post' ) ) {
331
+            // Publisher not found.
332
+            return;
333
+        }
334
+
335
+        // Get the item id.
336
+        $id = Wordlift_Entity_Service::get_instance()->get_uri( $publisher_id );
337
+
338
+        // Get the type.
339
+        $type = $this->entity_type_service->get( $publisher_id );
340
+
341
+        // Get the name.
342
+        $name = $post->post_title;
343
+
344
+        // Set the publisher data.
345
+        $params['publisher'] = array(
346
+            '@type' => $this->relative_to_context( $type['uri'] ),
347
+            '@id'   => $id,
348
+            'name'  => $name,
349
+        );
350
+
351
+        // Add the sameAs values associated with the publisher.
352
+        $storage_factory = Wordlift_Storage_Factory::get_instance();
353
+        $sameas          = $storage_factory->post_meta( Wordlift_Schema_Service::FIELD_SAME_AS )->get( $publisher_id );
354
+        if ( ! empty( $sameas ) ) {
355
+            $params['publisher']['sameAs'] = $sameas;
356
+        }
357
+
358
+        // Set the logo, only for http://schema.org/Organization as Person doesn't
359
+        // support the logo property.
360
+        //
361
+        // See http://schema.org/logo.
362
+        if ( 1 !== preg_match( '~Organization$~', $type['uri'] ) ) {
363
+            return;
364
+        }
365
+
366
+        // Get the publisher logo.
367
+        $publisher_logo = $this->get_publisher_logo( $post->ID );
368
+
369
+        // Bail out if the publisher logo isn't set.
370
+        if ( false === $publisher_logo ) {
371
+            return;
372
+        }
373
+
374
+        // Copy over some useful properties.
375
+        //
376
+        // See https://developers.google.com/search/docs/data-types/articles.
377
+        $params['publisher']['logo']['@type'] = 'ImageObject';
378
+        $params['publisher']['logo']['url']   = $publisher_logo['url'];
379
+
380
+        // If you specify a "width" or "height" value you should leave out
381
+        // 'px'. For example: "width":"4608px" should be "width":"4608".
382
+        //
383
+        // See https://github.com/insideout10/wordlift-plugin/issues/451.
384
+        $params['publisher']['logo']['width']  = $publisher_logo['width'];
385
+        $params['publisher']['logo']['height'] = $publisher_logo['height'];
386
+
387
+    }
388
+
389
+    /**
390
+     * Get the publisher logo structure.
391
+     *
392
+     * The function returns false when the publisher logo cannot be determined, i.e.:
393
+     *  - the post has no featured image.
394
+     *  - the featured image has no file.
395
+     *  - a wp_image_editor instance cannot be instantiated on the original file or on the publisher logo file.
396
+     *
397
+     * @param int $post_id The post id.
398
+     *
399
+     * @return array|false Returns an array with the `url`, `width` and `height` for the publisher logo or false in case
400
+     *  of errors.
401
+     * @since 3.19.2
402
+     * @see https://github.com/insideout10/wordlift-plugin/issues/823 related issue.
403
+     */
404
+    private function get_publisher_logo( $post_id ) {
405
+
406
+        // Get the featured image for the post.
407
+        $thumbnail_id = get_post_thumbnail_id( $post_id );
408
+
409
+        // Bail out if thumbnail not available.
410
+        if ( empty( $thumbnail_id ) || 0 === $thumbnail_id ) {
411
+            $this->log->info( "Featured image not set for post $post_id." );
412
+
413
+            return false;
414
+        }
415
+
416
+        // Get the uploads base URL.
417
+        $uploads_dir = wp_upload_dir();
418
+
419
+        // Get the attachment metadata.
420
+        $metadata = wp_get_attachment_metadata( $thumbnail_id );
421
+
422
+        // Bail out if the file isn't set.
423
+        if ( ! isset( $metadata['file'] ) ) {
424
+            $this->log->warn( "Featured image file not found for post $post_id." );
425
+
426
+            return false;
427
+        }
428
+
429
+        // Retrieve the relative filename, e.g. "2018/05/logo_publisher.png"
430
+        $path = $uploads_dir['basedir'] . DIRECTORY_SEPARATOR . $metadata['file'];
431
+
432
+        // Use image src, if local file does not exist. @see https://github.com/insideout10/wordlift-plugin/issues/1149
433
+        if ( ! file_exists( $path ) ) {
434
+            $this->log->warn( "Featured image file $path doesn't exist for post $post_id." );
435
+
436
+            $attachment_image_src = wp_get_attachment_image_src( $thumbnail_id, '' );
437
+            if ( $attachment_image_src ) {
438
+                return array(
439
+                    'url'    => $attachment_image_src[0],
440
+                    'width'  => $attachment_image_src[1],
441
+                    'height' => $attachment_image_src[2],
442
+                );
443
+            }
444
+
445
+            // Bail out if we cant fetch wp_get_attachment_image_src
446
+            return false;
447
+
448
+        }
449
+
450
+        // Try to get the image editor and bail out if the editor cannot be instantiated.
451
+        $original_file_editor = wp_get_image_editor( $path );
452
+        if ( is_wp_error( $original_file_editor ) ) {
453
+            $this->log->warn( "Cannot instantiate WP Image Editor on file $path for post $post_id." );
454
+
455
+            return false;
456
+        }
457
+
458
+        // Generate the publisher logo filename, we cannot use the `width` and `height` because we're scaling
459
+        // and we don't actually know the end values.
460
+        $publisher_logo_path = $original_file_editor->generate_filename( '-publisher-logo' );
461
+
462
+        // If the file doesn't exist yet, create it.
463
+        if ( ! file_exists( $publisher_logo_path ) ) {
464
+            $original_file_editor->resize( 600, 60 );
465
+            $original_file_editor->save( $publisher_logo_path );
466
+        }
467
+
468
+        // Try to get the image editor and bail out if the editor cannot be instantiated.
469
+        $publisher_logo_editor = wp_get_image_editor( $publisher_logo_path );
470
+        if ( is_wp_error( $publisher_logo_editor ) ) {
471
+            $this->log->warn( "Cannot instantiate WP Image Editor on file $publisher_logo_path for post $post_id." );
472
+
473
+            return false;
474
+        }
475
+
476
+        // Get the actual size.
477
+        $size = $publisher_logo_editor->get_size();
478
+
479
+        // Finally return the array with data.
480
+        return array(
481
+            'url'    => $uploads_dir['baseurl'] . substr( $publisher_logo_path, strlen( $uploads_dir['basedir'] ) ),
482
+            'width'  => $size['width'],
483
+            'height' => $size['height'],
484
+        );
485
+    }
486 486
 
487 487
 }
Please login to merge, or discard this patch.
src/includes/class-wordlift-entity-type-service.php 1 patch
Indentation   +468 added lines, -468 removed lines patch added patch discarded remove patch
@@ -19,147 +19,147 @@  discard block
 block discarded – undo
19 19
  */
20 20
 class Wordlift_Entity_Type_Service {
21 21
 
22
-	/**
23
-	 * The {@link Wordlift_Schema_Service} instance.
24
-	 *
25
-	 * @since  3.7.0
26
-	 * @access private
27
-	 * @var \Wordlift_Schema_Service $schema_service The {@link Wordlift_Schema_Service} instance.
28
-	 */
29
-	private $schema_service;
30
-
31
-	/**
32
-	 * A {@link Wordlift_Log_Service} instance.
33
-	 *
34
-	 * @since  3.8.0
35
-	 * @access private
36
-	 * @var \Wordlift_Log_Service $log A {@link Wordlift_Log_Service} instance.
37
-	 */
38
-	private $log;
39
-
40
-	/**
41
-	 * Wordlift_Entity_Type_Service constructor.
42
-	 *
43
-	 * @since 3.7.0
44
-	 */
45
-	protected function __construct() {
46
-
47
-		$this->log = Wordlift_Log_Service::get_logger( 'Wordlift_Entity_Type_Service' );
48
-
49
-		$this->schema_service = Wordlift_Schema_Service::get_instance();
50
-
51
-		$this->prepare_post_types();
52
-
53
-	}
54
-
55
-	/**
56
-	 * Prepare post types for Gutenberg use
57
-	 *
58
-	 * @since 3.26.0
59
-	 */
60
-	private function prepare_post_types() {
61
-
62
-		add_action(
63
-			'init',
64
-			function () {
65
-				// Add post type support for 'custom-fields' for all post types. Specifically needed in Gutenberg
66
-				$post_types = get_post_types();
67
-				foreach ( $post_types as $post_type ) {
68
-					add_post_type_support( $post_type, 'custom-fields' );
69
-				}
70
-			}
71
-		);
72
-	}
73
-
74
-	/**
75
-	 * The {@link Wordlift_Entity_Type_Service} singleton instance.
76
-	 *
77
-	 * @since  3.7.0
78
-	 * @access private
79
-	 * @var \Wordlift_Entity_Type_Service $instance The {@link Wordlift_Entity_Type_Service} singleton instance.
80
-	 */
81
-	private static $instance = null;
82
-
83
-	/**
84
-	 * Get the {@link Wordlift_Entity_Type_Service} singleton instance.
85
-	 *
86
-	 * @return \Wordlift_Entity_Type_Service The {@link Wordlift_Entity_Type_Service} singleton instance.
87
-	 * @since 3.7.0
88
-	 */
89
-	public static function get_instance() {
90
-
91
-		if ( ! isset( self::$instance ) ) {
92
-			self::$instance = new self();
93
-		}
94
-
95
-		return self::$instance;
96
-	}
97
-
98
-	/**
99
-	 * Get the types associated with the specified entity post id.
100
-	 *
101
-	 * We have a strategy to define the entity type, given that everything is
102
-	 * an entity, i.e. also posts/pages and custom post types.
103
-	 *
104
-	 * @param int $post_id The post id.
105
-	 *
106
-	 * @return array|null {
107
-	 * An array of type properties or null if no term is associated
108
-	 *
109
-	 * @type string css_class     The css class, e.g. `wl-thing`.
110
-	 * @type string uri           The schema.org class URI, e.g. `http://schema.org/Thing`.
111
-	 * @type array  same_as       An array of same as attributes.
112
-	 * @type array  custom_fields An array of custom fields.
113
-	 * }
114
-	 * @since 3.33.9 The `linked_data` key has been removed.
115
-	 *
116
-	 * @since 3.20.0 This function will **not** return entity types introduced with 3.20.0.
117
-	 *
118
-	 * @since 3.18.0 The cases are the following:
119
-	 *  1. the post has a term from the Entity Types Taxonomy: the term defines
120
-	 *     the entity type, e.g. Organization, Person, ...
121
-	 *  2. the post doesn't have a term from the Entity Types Taxonomy:
122
-	 *      a) the post is a `wl_entity` custom post type, then the post is
123
-	 *           assigned the `Thing` entity type by default.
124
-	 *      b) the post is a `post` post type, then the post is
125
-	 *           assigned the `Article` entity type by default.
126
-	 *      c) the post is a custom post type then it is
127
-	 *          assigned the `WebPage` entity type by default.
128
-	 */
129
-	public function get( $post_id ) {
130
-
131
-		$this->log->trace( "Getting the post type for post $post_id..." );
132
-
133
-		// Get the post type.
134
-		$post_type = get_post_type( $post_id );
135
-
136
-		// Return `web-page` for non entities.
137
-		if ( ! self::is_valid_entity_post_type( $post_type ) ) {
138
-			$this->log->info( "Returning `web-page` for post $post_id." );
139
-
140
-			return $this->schema_service->get_schema( 'web-page' );
141
-		}
142
-
143
-		// Get the type from the associated classification.
144
-		$terms = wp_get_object_terms( $post_id, Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME );
145
-
146
-		// Return the schema type if there is a term found.
147
-		if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
148
-			// Cycle through the terms and return the first one with a valid schema.
149
-			foreach ( $terms as $term ) {
150
-				$this->log->debug( "Found `{$term->slug}` term for post $post_id." );
151
-
152
-				// Try to get the schema for the term.
153
-				$schema = $this->schema_service->get_schema( $term->slug );
154
-
155
-				// If found, return it, ignoring the other types.
156
-				if ( null !== $schema ) {
157
-					// Return the entity type with the specified id.
158
-					return $schema;
159
-				}
160
-			}
161
-
162
-			/*
22
+    /**
23
+     * The {@link Wordlift_Schema_Service} instance.
24
+     *
25
+     * @since  3.7.0
26
+     * @access private
27
+     * @var \Wordlift_Schema_Service $schema_service The {@link Wordlift_Schema_Service} instance.
28
+     */
29
+    private $schema_service;
30
+
31
+    /**
32
+     * A {@link Wordlift_Log_Service} instance.
33
+     *
34
+     * @since  3.8.0
35
+     * @access private
36
+     * @var \Wordlift_Log_Service $log A {@link Wordlift_Log_Service} instance.
37
+     */
38
+    private $log;
39
+
40
+    /**
41
+     * Wordlift_Entity_Type_Service constructor.
42
+     *
43
+     * @since 3.7.0
44
+     */
45
+    protected function __construct() {
46
+
47
+        $this->log = Wordlift_Log_Service::get_logger( 'Wordlift_Entity_Type_Service' );
48
+
49
+        $this->schema_service = Wordlift_Schema_Service::get_instance();
50
+
51
+        $this->prepare_post_types();
52
+
53
+    }
54
+
55
+    /**
56
+     * Prepare post types for Gutenberg use
57
+     *
58
+     * @since 3.26.0
59
+     */
60
+    private function prepare_post_types() {
61
+
62
+        add_action(
63
+            'init',
64
+            function () {
65
+                // Add post type support for 'custom-fields' for all post types. Specifically needed in Gutenberg
66
+                $post_types = get_post_types();
67
+                foreach ( $post_types as $post_type ) {
68
+                    add_post_type_support( $post_type, 'custom-fields' );
69
+                }
70
+            }
71
+        );
72
+    }
73
+
74
+    /**
75
+     * The {@link Wordlift_Entity_Type_Service} singleton instance.
76
+     *
77
+     * @since  3.7.0
78
+     * @access private
79
+     * @var \Wordlift_Entity_Type_Service $instance The {@link Wordlift_Entity_Type_Service} singleton instance.
80
+     */
81
+    private static $instance = null;
82
+
83
+    /**
84
+     * Get the {@link Wordlift_Entity_Type_Service} singleton instance.
85
+     *
86
+     * @return \Wordlift_Entity_Type_Service The {@link Wordlift_Entity_Type_Service} singleton instance.
87
+     * @since 3.7.0
88
+     */
89
+    public static function get_instance() {
90
+
91
+        if ( ! isset( self::$instance ) ) {
92
+            self::$instance = new self();
93
+        }
94
+
95
+        return self::$instance;
96
+    }
97
+
98
+    /**
99
+     * Get the types associated with the specified entity post id.
100
+     *
101
+     * We have a strategy to define the entity type, given that everything is
102
+     * an entity, i.e. also posts/pages and custom post types.
103
+     *
104
+     * @param int $post_id The post id.
105
+     *
106
+     * @return array|null {
107
+     * An array of type properties or null if no term is associated
108
+     *
109
+     * @type string css_class     The css class, e.g. `wl-thing`.
110
+     * @type string uri           The schema.org class URI, e.g. `http://schema.org/Thing`.
111
+     * @type array  same_as       An array of same as attributes.
112
+     * @type array  custom_fields An array of custom fields.
113
+     * }
114
+     * @since 3.33.9 The `linked_data` key has been removed.
115
+     *
116
+     * @since 3.20.0 This function will **not** return entity types introduced with 3.20.0.
117
+     *
118
+     * @since 3.18.0 The cases are the following:
119
+     *  1. the post has a term from the Entity Types Taxonomy: the term defines
120
+     *     the entity type, e.g. Organization, Person, ...
121
+     *  2. the post doesn't have a term from the Entity Types Taxonomy:
122
+     *      a) the post is a `wl_entity` custom post type, then the post is
123
+     *           assigned the `Thing` entity type by default.
124
+     *      b) the post is a `post` post type, then the post is
125
+     *           assigned the `Article` entity type by default.
126
+     *      c) the post is a custom post type then it is
127
+     *          assigned the `WebPage` entity type by default.
128
+     */
129
+    public function get( $post_id ) {
130
+
131
+        $this->log->trace( "Getting the post type for post $post_id..." );
132
+
133
+        // Get the post type.
134
+        $post_type = get_post_type( $post_id );
135
+
136
+        // Return `web-page` for non entities.
137
+        if ( ! self::is_valid_entity_post_type( $post_type ) ) {
138
+            $this->log->info( "Returning `web-page` for post $post_id." );
139
+
140
+            return $this->schema_service->get_schema( 'web-page' );
141
+        }
142
+
143
+        // Get the type from the associated classification.
144
+        $terms = wp_get_object_terms( $post_id, Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME );
145
+
146
+        // Return the schema type if there is a term found.
147
+        if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
148
+            // Cycle through the terms and return the first one with a valid schema.
149
+            foreach ( $terms as $term ) {
150
+                $this->log->debug( "Found `{$term->slug}` term for post $post_id." );
151
+
152
+                // Try to get the schema for the term.
153
+                $schema = $this->schema_service->get_schema( $term->slug );
154
+
155
+                // If found, return it, ignoring the other types.
156
+                if ( null !== $schema ) {
157
+                    // Return the entity type with the specified id.
158
+                    return $schema;
159
+                }
160
+            }
161
+
162
+            /*
163 163
 			 * When a schema isn't found, we return `thing`. Schema may not be found because
164 164
 			 * the new schema classes that we support since #852 aren't configured in the schema
165 165
 			 * service.
@@ -169,93 +169,93 @@  discard block
 block discarded – undo
169 169
 			 * @since 3.20.0
170 170
 			 */
171 171
 
172
-			return $this->schema_service->get_schema( 'thing' );
173
-		}
174
-
175
-		// If it's a page or post return `Article`.
176
-		if ( in_array( $post_type, array( 'post', 'page' ), true ) ) {
177
-			$this->log->debug( "Post $post_id has no terms, and it's a `post` type, returning `Article`." );
178
-
179
-			// Return "Article" schema type for posts.
180
-			return $this->schema_service->get_schema( 'article' );
181
-		}
182
-
183
-		// Return "Thing" schema type for entities.
184
-		$this->log->debug( "Post $post_id has no terms, but it's a `wl_entity` type, returning `Thing`." );
185
-
186
-		// Return the entity type with the specified id.
187
-		return $this->schema_service->get_schema( 'thing' );
188
-
189
-	}
190
-
191
-	/**
192
-	 * Get the term ids of the entity types associated to the specified post.
193
-	 *
194
-	 * @param int $post_id The post id.
195
-	 *
196
-	 * @return array|WP_Error An array of entity types ids or a {@link WP_Error}.
197
-	 * @since 3.20.0
198
-	 */
199
-	public function get_ids( $post_id ) {
200
-
201
-		return wp_get_object_terms( $post_id, Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME, array( 'fields' => 'ids' ) );
202
-	}
203
-
204
-	/**
205
-	 * Get the camel case names of the entity types associated to the specified post.
206
-	 *
207
-	 * @param int $post_id The post id.
208
-	 *
209
-	 * @return array|WP_Error An array of entity types camel case names or a {@link WP_Error}.
210
-	 * @since 3.20.0
211
-	 */
212
-	public function get_names( $post_id ) {
213
-
214
-		$ids = $this->get_ids( $post_id );
215
-
216
-		// Filter out invalid terms (ones without _wl_name term meta)
217
-		return array_values(
218
-			array_filter(
219
-				array_map(
220
-					function ( $id ) {
221
-						return get_term_meta( $id, '_wl_name', true );
222
-					},
223
-					$ids
224
-				)
225
-			)
226
-		);
227
-	}
228
-
229
-	/**
230
-	 * Set the main type for the specified entity post, given the type URI.
231
-	 *
232
-	 * @param int    $post_id The post id.
233
-	 * @param string $type_uri The type URI.
234
-	 * @param bool   $replace Whether the provided type must replace the existing types, by default `true`.
235
-	 *
236
-	 * @since 3.8.0
237
-	 */
238
-	public function set( $post_id, $type_uri, $replace = true ) {
239
-
240
-		// If the type URI is empty we remove the type.
241
-		if ( empty( $type_uri ) ) {
242
-			$this->log->debug( "Removing entity type for post $post_id..." );
243
-
244
-			wp_set_object_terms( $post_id, null, Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME );
245
-
246
-			return;
247
-		}
248
-
249
-		$this->log->debug( "Setting entity type for post $post_id..." );
250
-
251
-		// if the `$type_uri` starts with `wl-`, we're looking at the class name, which is `wl-` + slug.
252
-		$term = ( 0 === strpos( $type_uri, 'wl-' ) )
253
-			// Get term by slug.
254
-			? $this->get_term_by_slug( substr( $type_uri, 3 ) )
255
-			// Get term by URI.
256
-			: $this->get_term_by_uri( $type_uri );
257
-
258
-		/*
172
+            return $this->schema_service->get_schema( 'thing' );
173
+        }
174
+
175
+        // If it's a page or post return `Article`.
176
+        if ( in_array( $post_type, array( 'post', 'page' ), true ) ) {
177
+            $this->log->debug( "Post $post_id has no terms, and it's a `post` type, returning `Article`." );
178
+
179
+            // Return "Article" schema type for posts.
180
+            return $this->schema_service->get_schema( 'article' );
181
+        }
182
+
183
+        // Return "Thing" schema type for entities.
184
+        $this->log->debug( "Post $post_id has no terms, but it's a `wl_entity` type, returning `Thing`." );
185
+
186
+        // Return the entity type with the specified id.
187
+        return $this->schema_service->get_schema( 'thing' );
188
+
189
+    }
190
+
191
+    /**
192
+     * Get the term ids of the entity types associated to the specified post.
193
+     *
194
+     * @param int $post_id The post id.
195
+     *
196
+     * @return array|WP_Error An array of entity types ids or a {@link WP_Error}.
197
+     * @since 3.20.0
198
+     */
199
+    public function get_ids( $post_id ) {
200
+
201
+        return wp_get_object_terms( $post_id, Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME, array( 'fields' => 'ids' ) );
202
+    }
203
+
204
+    /**
205
+     * Get the camel case names of the entity types associated to the specified post.
206
+     *
207
+     * @param int $post_id The post id.
208
+     *
209
+     * @return array|WP_Error An array of entity types camel case names or a {@link WP_Error}.
210
+     * @since 3.20.0
211
+     */
212
+    public function get_names( $post_id ) {
213
+
214
+        $ids = $this->get_ids( $post_id );
215
+
216
+        // Filter out invalid terms (ones without _wl_name term meta)
217
+        return array_values(
218
+            array_filter(
219
+                array_map(
220
+                    function ( $id ) {
221
+                        return get_term_meta( $id, '_wl_name', true );
222
+                    },
223
+                    $ids
224
+                )
225
+            )
226
+        );
227
+    }
228
+
229
+    /**
230
+     * Set the main type for the specified entity post, given the type URI.
231
+     *
232
+     * @param int    $post_id The post id.
233
+     * @param string $type_uri The type URI.
234
+     * @param bool   $replace Whether the provided type must replace the existing types, by default `true`.
235
+     *
236
+     * @since 3.8.0
237
+     */
238
+    public function set( $post_id, $type_uri, $replace = true ) {
239
+
240
+        // If the type URI is empty we remove the type.
241
+        if ( empty( $type_uri ) ) {
242
+            $this->log->debug( "Removing entity type for post $post_id..." );
243
+
244
+            wp_set_object_terms( $post_id, null, Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME );
245
+
246
+            return;
247
+        }
248
+
249
+        $this->log->debug( "Setting entity type for post $post_id..." );
250
+
251
+        // if the `$type_uri` starts with `wl-`, we're looking at the class name, which is `wl-` + slug.
252
+        $term = ( 0 === strpos( $type_uri, 'wl-' ) )
253
+            // Get term by slug.
254
+            ? $this->get_term_by_slug( substr( $type_uri, 3 ) )
255
+            // Get term by URI.
256
+            : $this->get_term_by_uri( $type_uri );
257
+
258
+        /*
259 259
 		 * We always want to assign a type to an entity otherwise it won't show in the Vocabulary and it won't be
260 260
 		 * connected to Articles via mentions. We realized that the client JS code is passing `wl-other` when the
261 261
 		 * entity type isn't "notable". In which case we couldn't find an entity type.
@@ -266,245 +266,245 @@  discard block
 block discarded – undo
266 266
 		 *
267 267
 		 * @since 3.23.4
268 268
 		 */
269
-		if ( false === $term ) {
270
-			$this->log->warn( "No term found for URI $type_uri, will use Thing." );
271
-
272
-			$term = $this->get_term_by_slug( 'thing' );
273
-
274
-			// We still need to be able to bali out here, for example WordPress 5.1 tests create posts before our taxonomy
275
-			// is installed.
276
-			if ( false === $term ) {
277
-				return;
278
-			}
279
-		}
280
-
281
-		$this->log->debug( "Setting entity type [ post id :: $post_id ][ term id :: $term->term_id ][ term slug :: $term->slug ][ type uri :: $type_uri ]..." );
282
-
283
-		// `$replace` is passed to decide whether to replace or append the term.
284
-		wp_set_object_terms( $post_id, $term->term_id, Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME, ! $replace );
285
-
286
-	}
287
-
288
-	/**
289
-	 * Get an entity type term given its slug.
290
-	 *
291
-	 * @param string $slug The slug.
292
-	 *
293
-	 * @return false|WP_Term WP_Term instance on success. Will return false if `$taxonomy` does not exist
294
-	 *                             or `$term` was not found.
295
-	 * @since 3.20.0
296
-	 */
297
-	public function get_term_by_slug( $slug ) {
298
-
299
-		return get_term_by( 'slug', $slug, Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME );
300
-	}
301
-
302
-	/**
303
-	 * Get an entity type term given its URI.
304
-	 *
305
-	 * @param string $uri The uri.
306
-	 *
307
-	 * @return false|WP_Term WP_Term instance on success. Will return false if `$taxonomy` does not exist
308
-	 *                             or `$term` was not found.
309
-	 * @since 3.20.0
310
-	 */
311
-	public function get_term_by_uri( $uri ) {
312
-
313
-		$terms = get_terms(
314
-			Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME,
315
-			array(
316
-				'fields'     => 'all',
317
-				'get'        => 'all',
318
-				'number'     => 1,
319
-				'meta_query' => array(
320
-					array(
321
-						// Don't use a reference to Wordlift_Schemaorg_Class_Service, unless
322
-						// `WL_ALL_ENTITY_TYPES` is set to true.
323
-						'key'   => '_wl_uri',
324
-						'value' => $uri,
325
-					),
326
-				),
327
-				'orderby'    => 'term_id',
328
-				'order'      => 'ASC',
329
-			)
330
-		);
331
-
332
-		return is_array( $terms ) && ! empty( $terms ) ? $terms[0] : false;
333
-	}
334
-
335
-	/**
336
-	 * Check whether an entity type is set for the {@link WP_Post} with the
337
-	 * specified id.
338
-	 *
339
-	 * @param int    $post_id The {@link WP_Post}'s `id`.
340
-	 * @param string $uri The entity type URI.
341
-	 *
342
-	 * @return bool True if an entity type is set otherwise false.
343
-	 * @since 3.15.0
344
-	 */
345
-	public function has_entity_type( $post_id, $uri = null ) {
346
-
347
-		$this->log->debug( "Checking if post $post_id has an entity type [ $uri ]..." );
348
-
349
-		// If an URI hasn't been specified just check whether we have at least
350
-		// one entity type.
351
-		if ( null === $uri ) {
352
-			return has_term( '', Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME, $post_id );
353
-		}
354
-
355
-		$has_entity_type = $this->has_post_term_by_uri( $post_id, $uri );
356
-
357
-		$this->log->debug( "Post $post_id has $uri type: " . ( $has_entity_type ? 'yes' : 'no' ) );
358
-
359
-		// Check whether the post has an entity type with that URI.
360
-		return $has_entity_type;
361
-	}
362
-
363
-	/**
364
-	 * Get the list of entity types' terms for the specified {@link WP_Post}.
365
-	 *
366
-	 * @param int $post_id The {@link WP_Post} id.
367
-	 *
368
-	 * @return array|WP_Error An array of entity types' terms or {@link WP_Error}.
369
-	 * @since 3.15.0
370
-	 */
371
-	private function get_post_terms( $post_id ) {
372
-
373
-		return wp_get_object_terms(
374
-			$post_id,
375
-			Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME,
376
-			array(
377
-				'hide_empty' => false,
378
-				// Because of #334 (and the AAM plugin) we changed fields from 'id=>slug' to 'all'.
379
-				// An issue has been opened with the AAM plugin author as well.
380
-				//
381
-				// see https://github.com/insideout10/wordlift-plugin/issues/334
382
-				// see https://wordpress.org/support/topic/idslug-not-working-anymore?replies=1#post-8806863
383
-				'fields'     => 'all',
384
-			)
385
-		);
386
-	}
387
-
388
-	/**
389
-	 * Get an entity type term given its URI.
390
-	 *
391
-	 * @param int    $post_id The {@link WP_Post} id.
392
-	 * @param string $uri The entity type URI.
393
-	 *
394
-	 * @return bool True if the post has that type URI bound to it otherwise false.
395
-	 * @since 3.15.0
396
-	 *
397
-	 * @since 3.20.0 function renamed to `has_post_term_by_uri` and return type changed to `bool`.
398
-	 */
399
-	private function has_post_term_by_uri( $post_id, $uri ) {
400
-
401
-		// Get the post terms bound to the specified post.
402
-		$terms = $this->get_post_terms( $post_id );
403
-
404
-		// Look for a term if the specified URI.
405
-		foreach ( $terms as $term ) {
406
-			$term_uri = get_term_meta( $term->term_id, '_wl_uri', true );
407
-			if ( $uri === $term_uri ) {
408
-				return true;
409
-			}
410
-		}
411
-
412
-		// Return null.
413
-		return false;
414
-	}
415
-
416
-	/**
417
-	 * Get the custom fields for a specific post.
418
-	 *
419
-	 * @param int $post_id The post ID.
420
-	 *
421
-	 * @return array An array of custom fields (see `custom_fields` in Wordlift_Schema_Service).
422
-	 * @since 3.25.2
423
-	 */
424
-	public function get_custom_fields_for_post( $post_id ) {
425
-
426
-		// Return custom fields for this specific entity's type.
427
-		$types = $this->get_ids( $post_id );
428
-
429
-		/** @var WP_Term[] $terms */
430
-		$terms = array_filter(
431
-			array_map(
432
-				function ( $item ) {
433
-					return get_term( $item );
434
-				},
435
-				$types
436
-			),
437
-			function ( $item ) {
438
-				return isset( $item ) && is_a( $item, 'WP_Term' );
439
-			}
440
-		);
441
-
442
-		$term_slugs = array_map(
443
-			function ( $item ) {
444
-				return $item->slug;
445
-			},
446
-			$terms
447
-		);
448
-
449
-		$term_slugs[] = 'thing';
450
-
451
-		return $this->get_custom_fields_by_term_slugs( $term_slugs );
452
-	}
453
-
454
-	/**
455
-	 * Get the custom fields for a specific term.
456
-	 *
457
-	 * @param int $term_id The term ID.
458
-	 *
459
-	 * @return array An array of custom fields (see `custom_fields` in Wordlift_Schema_Service).
460
-	 * @since 3.32.0
461
-	 */
462
-	public function get_custom_fields_for_term( $term_id ) {
463
-		$selected_entity_types   = get_term_meta( $term_id, 'wl_entity_type' );
464
-		$selected_entity_types[] = 'thing';
465
-		$selected_entity_types   = array_unique( $selected_entity_types );
466
-
467
-		return $this->get_custom_fields_by_term_slugs( $selected_entity_types );
468
-	}
469
-
470
-	/**
471
-	 * Determines whether a post type can be used for entities.
472
-	 *
473
-	 * Criteria is that the post type is public. The list of valid post types
474
-	 * can be overridden with a filter.
475
-	 *
476
-	 * @param string $post_type A post type name.
477
-	 *
478
-	 * @return bool Return true if the post type can be used for entities, otherwise false.
479
-	 * @since 3.15.0
480
-	 */
481
-	public static function is_valid_entity_post_type( $post_type ) {
482
-
483
-		return in_array( $post_type, Wordlift_Entity_Service::valid_entity_post_types(), true );
484
-	}
485
-
486
-	/**
487
-	 * @param $term_slugs
488
-	 *
489
-	 * @return array
490
-	 */
491
-	private function get_custom_fields_by_term_slugs( $term_slugs ) {
492
-		$schema_service = Wordlift_Schema_Service::get_instance();
493
-
494
-		return array_reduce(
495
-			$term_slugs,
496
-			function ( $carry, $item ) use ( $schema_service ) {
497
-
498
-				$schema = $schema_service->get_schema( $item );
499
-
500
-				if ( ! isset( $schema['custom_fields'] ) ) {
501
-					return $carry;
502
-				}
503
-
504
-				return $carry + $schema['custom_fields'];
505
-			},
506
-			array()
507
-		);
508
-	}
269
+        if ( false === $term ) {
270
+            $this->log->warn( "No term found for URI $type_uri, will use Thing." );
271
+
272
+            $term = $this->get_term_by_slug( 'thing' );
273
+
274
+            // We still need to be able to bali out here, for example WordPress 5.1 tests create posts before our taxonomy
275
+            // is installed.
276
+            if ( false === $term ) {
277
+                return;
278
+            }
279
+        }
280
+
281
+        $this->log->debug( "Setting entity type [ post id :: $post_id ][ term id :: $term->term_id ][ term slug :: $term->slug ][ type uri :: $type_uri ]..." );
282
+
283
+        // `$replace` is passed to decide whether to replace or append the term.
284
+        wp_set_object_terms( $post_id, $term->term_id, Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME, ! $replace );
285
+
286
+    }
287
+
288
+    /**
289
+     * Get an entity type term given its slug.
290
+     *
291
+     * @param string $slug The slug.
292
+     *
293
+     * @return false|WP_Term WP_Term instance on success. Will return false if `$taxonomy` does not exist
294
+     *                             or `$term` was not found.
295
+     * @since 3.20.0
296
+     */
297
+    public function get_term_by_slug( $slug ) {
298
+
299
+        return get_term_by( 'slug', $slug, Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME );
300
+    }
301
+
302
+    /**
303
+     * Get an entity type term given its URI.
304
+     *
305
+     * @param string $uri The uri.
306
+     *
307
+     * @return false|WP_Term WP_Term instance on success. Will return false if `$taxonomy` does not exist
308
+     *                             or `$term` was not found.
309
+     * @since 3.20.0
310
+     */
311
+    public function get_term_by_uri( $uri ) {
312
+
313
+        $terms = get_terms(
314
+            Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME,
315
+            array(
316
+                'fields'     => 'all',
317
+                'get'        => 'all',
318
+                'number'     => 1,
319
+                'meta_query' => array(
320
+                    array(
321
+                        // Don't use a reference to Wordlift_Schemaorg_Class_Service, unless
322
+                        // `WL_ALL_ENTITY_TYPES` is set to true.
323
+                        'key'   => '_wl_uri',
324
+                        'value' => $uri,
325
+                    ),
326
+                ),
327
+                'orderby'    => 'term_id',
328
+                'order'      => 'ASC',
329
+            )
330
+        );
331
+
332
+        return is_array( $terms ) && ! empty( $terms ) ? $terms[0] : false;
333
+    }
334
+
335
+    /**
336
+     * Check whether an entity type is set for the {@link WP_Post} with the
337
+     * specified id.
338
+     *
339
+     * @param int    $post_id The {@link WP_Post}'s `id`.
340
+     * @param string $uri The entity type URI.
341
+     *
342
+     * @return bool True if an entity type is set otherwise false.
343
+     * @since 3.15.0
344
+     */
345
+    public function has_entity_type( $post_id, $uri = null ) {
346
+
347
+        $this->log->debug( "Checking if post $post_id has an entity type [ $uri ]..." );
348
+
349
+        // If an URI hasn't been specified just check whether we have at least
350
+        // one entity type.
351
+        if ( null === $uri ) {
352
+            return has_term( '', Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME, $post_id );
353
+        }
354
+
355
+        $has_entity_type = $this->has_post_term_by_uri( $post_id, $uri );
356
+
357
+        $this->log->debug( "Post $post_id has $uri type: " . ( $has_entity_type ? 'yes' : 'no' ) );
358
+
359
+        // Check whether the post has an entity type with that URI.
360
+        return $has_entity_type;
361
+    }
362
+
363
+    /**
364
+     * Get the list of entity types' terms for the specified {@link WP_Post}.
365
+     *
366
+     * @param int $post_id The {@link WP_Post} id.
367
+     *
368
+     * @return array|WP_Error An array of entity types' terms or {@link WP_Error}.
369
+     * @since 3.15.0
370
+     */
371
+    private function get_post_terms( $post_id ) {
372
+
373
+        return wp_get_object_terms(
374
+            $post_id,
375
+            Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME,
376
+            array(
377
+                'hide_empty' => false,
378
+                // Because of #334 (and the AAM plugin) we changed fields from 'id=>slug' to 'all'.
379
+                // An issue has been opened with the AAM plugin author as well.
380
+                //
381
+                // see https://github.com/insideout10/wordlift-plugin/issues/334
382
+                // see https://wordpress.org/support/topic/idslug-not-working-anymore?replies=1#post-8806863
383
+                'fields'     => 'all',
384
+            )
385
+        );
386
+    }
387
+
388
+    /**
389
+     * Get an entity type term given its URI.
390
+     *
391
+     * @param int    $post_id The {@link WP_Post} id.
392
+     * @param string $uri The entity type URI.
393
+     *
394
+     * @return bool True if the post has that type URI bound to it otherwise false.
395
+     * @since 3.15.0
396
+     *
397
+     * @since 3.20.0 function renamed to `has_post_term_by_uri` and return type changed to `bool`.
398
+     */
399
+    private function has_post_term_by_uri( $post_id, $uri ) {
400
+
401
+        // Get the post terms bound to the specified post.
402
+        $terms = $this->get_post_terms( $post_id );
403
+
404
+        // Look for a term if the specified URI.
405
+        foreach ( $terms as $term ) {
406
+            $term_uri = get_term_meta( $term->term_id, '_wl_uri', true );
407
+            if ( $uri === $term_uri ) {
408
+                return true;
409
+            }
410
+        }
411
+
412
+        // Return null.
413
+        return false;
414
+    }
415
+
416
+    /**
417
+     * Get the custom fields for a specific post.
418
+     *
419
+     * @param int $post_id The post ID.
420
+     *
421
+     * @return array An array of custom fields (see `custom_fields` in Wordlift_Schema_Service).
422
+     * @since 3.25.2
423
+     */
424
+    public function get_custom_fields_for_post( $post_id ) {
425
+
426
+        // Return custom fields for this specific entity's type.
427
+        $types = $this->get_ids( $post_id );
428
+
429
+        /** @var WP_Term[] $terms */
430
+        $terms = array_filter(
431
+            array_map(
432
+                function ( $item ) {
433
+                    return get_term( $item );
434
+                },
435
+                $types
436
+            ),
437
+            function ( $item ) {
438
+                return isset( $item ) && is_a( $item, 'WP_Term' );
439
+            }
440
+        );
441
+
442
+        $term_slugs = array_map(
443
+            function ( $item ) {
444
+                return $item->slug;
445
+            },
446
+            $terms
447
+        );
448
+
449
+        $term_slugs[] = 'thing';
450
+
451
+        return $this->get_custom_fields_by_term_slugs( $term_slugs );
452
+    }
453
+
454
+    /**
455
+     * Get the custom fields for a specific term.
456
+     *
457
+     * @param int $term_id The term ID.
458
+     *
459
+     * @return array An array of custom fields (see `custom_fields` in Wordlift_Schema_Service).
460
+     * @since 3.32.0
461
+     */
462
+    public function get_custom_fields_for_term( $term_id ) {
463
+        $selected_entity_types   = get_term_meta( $term_id, 'wl_entity_type' );
464
+        $selected_entity_types[] = 'thing';
465
+        $selected_entity_types   = array_unique( $selected_entity_types );
466
+
467
+        return $this->get_custom_fields_by_term_slugs( $selected_entity_types );
468
+    }
469
+
470
+    /**
471
+     * Determines whether a post type can be used for entities.
472
+     *
473
+     * Criteria is that the post type is public. The list of valid post types
474
+     * can be overridden with a filter.
475
+     *
476
+     * @param string $post_type A post type name.
477
+     *
478
+     * @return bool Return true if the post type can be used for entities, otherwise false.
479
+     * @since 3.15.0
480
+     */
481
+    public static function is_valid_entity_post_type( $post_type ) {
482
+
483
+        return in_array( $post_type, Wordlift_Entity_Service::valid_entity_post_types(), true );
484
+    }
485
+
486
+    /**
487
+     * @param $term_slugs
488
+     *
489
+     * @return array
490
+     */
491
+    private function get_custom_fields_by_term_slugs( $term_slugs ) {
492
+        $schema_service = Wordlift_Schema_Service::get_instance();
493
+
494
+        return array_reduce(
495
+            $term_slugs,
496
+            function ( $carry, $item ) use ( $schema_service ) {
497
+
498
+                $schema = $schema_service->get_schema( $item );
499
+
500
+                if ( ! isset( $schema['custom_fields'] ) ) {
501
+                    return $carry;
502
+                }
503
+
504
+                return $carry + $schema['custom_fields'];
505
+            },
506
+            array()
507
+        );
508
+    }
509 509
 
510 510
 }
Please login to merge, or discard this patch.
src/classes/videoobject/jsonld/class-jsonld.php 1 patch
Indentation   +170 added lines, -170 removed lines patch added patch discarded remove patch
@@ -11,175 +11,175 @@
 block discarded – undo
11 11
 use Wordlift\Videoobject\Data\Video_Storage\Storage;
12 12
 
13 13
 class Jsonld {
14
-	/**
15
-	 * @var Storage
16
-	 */
17
-	private $video_storage;
18
-
19
-	/**
20
-	 * Jsonld constructor.
21
-	 *
22
-	 * @param $video_storage Storage
23
-	 */
24
-	public function __construct( $video_storage ) {
25
-		add_action( 'wl_post_jsonld', array( $this, 'wl_post_jsonld' ), 10, 2 );
26
-		add_filter( 'wl_after_get_jsonld', array( $this, 'wl_after_get_jsonld' ), 10, 2 );
27
-		$this->video_storage = $video_storage;
28
-	}
29
-
30
-	public function wl_after_get_jsonld( $jsonld, $post_id ) {
31
-		if ( 0 === count( $jsonld ) ) {
32
-			return $jsonld;
33
-		}
34
-		$current_item = $jsonld[0];
35
-
36
-		if ( ! is_array( $current_item ) || ! array_key_exists( '@type', $current_item ) ) {
37
-			// Cant determine type return early.
38
-			return $jsonld;
39
-		}
40
-
41
-		$type = $current_item['@type'];
42
-		if ( is_string( $type ) ) {
43
-			$type = array( $type );
44
-		}
45
-		// If its a article or descendant of article, then dont add the
46
-		// videoobject in this hook, they will be already added to video property.
47
-		if ( array_intersect( Jsonld_Article_Wrapper::$article_types, $type ) ) {
48
-			return $jsonld;
49
-		}
50
-
51
-		$videos_jsonld = $this->get_videos_jsonld( $post_id );
52
-		if ( 0 === count( $videos_jsonld ) ) {
53
-			return $jsonld;
54
-		}
55
-
56
-		// check if we have @id in jsonld for first item.
57
-		$id = array_key_exists( '@id', $current_item ) ? $current_item['@id'] : '';
58
-
59
-		foreach ( $videos_jsonld as &$video_jsonld ) {
60
-			if ( ! $id ) {
61
-				continue;
62
-			}
63
-			if ( ! array_key_exists( 'mentions', $video_jsonld ) ) {
64
-				$video_jsonld['mentions'] = array( '@id' => $id );
65
-			} else {
66
-				$video_jsonld['mentions'] = array_merge( $video_jsonld['mentions'], array( '@id' => $id ) );
67
-			}
68
-		}
69
-
70
-		return array_merge( $jsonld, $videos_jsonld );
71
-	}
72
-
73
-	/**
74
-	 * @param $existing_video_data string | array associative or sequential array.
75
-	 * @param $new_video_data array Sequential array.
76
-	 *
77
-	 * @return array
78
-	 */
79
-	private function merge_video_data( $existing_video_data, $new_video_data ) {
80
-		if ( ! is_array( $existing_video_data ) ) {
81
-			$new_video_data[] = $existing_video_data;
82
-
83
-			return $new_video_data;
84
-		}
85
-
86
-		if ( $this->is_associative_array( $existing_video_data ) ) {
87
-			$new_video_data[] = $existing_video_data;
88
-
89
-			return $new_video_data;
90
-		}
91
-
92
-		return array_merge( $existing_video_data, $new_video_data );
93
-	}
94
-
95
-	public function wl_post_jsonld( $jsonld, $post_id ) {
96
-
97
-		$video_jsonld = $this->get_videos_jsonld( $post_id );
98
-		if ( count( $video_jsonld ) === 0 ) {
99
-			return $jsonld;
100
-		}
101
-		// Before adding the video jsonld check if the key
102
-		// is present and additional data might be present,
103
-		// if not present just add the data and return early.
104
-		if ( ! array_key_exists( 'video', $jsonld ) ) {
105
-			$jsonld['video'] = $video_jsonld;
106
-
107
-			return $jsonld;
108
-		}
109
-
110
-		// since key exists, we need to merge the data based on type.
111
-		$previous_video_data = $jsonld['video'];
112
-		$jsonld['video']     = $this->merge_video_data( $previous_video_data, $video_jsonld );
113
-
114
-		return $jsonld;
115
-	}
116
-
117
-	/**
118
-	 * @param $post_id int Post id.
119
-	 *
120
-	 * @return array
121
-	 */
122
-	public function get_videos_jsonld( $post_id ) {
123
-
124
-		$videos = $this->video_storage->get_all_videos( $post_id );
125
-
126
-		$jsonld = array();
127
-
128
-		foreach ( $videos as $video ) {
129
-			/**
130
-			 * @var $video Video
131
-			 */
132
-			$description = $video->description;
133
-			if ( ! $video->description ) {
134
-				// If description is empty then use the video title as description
135
-				$description = $video->name;
136
-			}
137
-			$single_jsonld = array(
138
-				'@context'     => 'http://schema.org',
139
-				'@type'        => 'VideoObject',
140
-				'name'         => $video->name,
141
-				'description'  => $description,
142
-				'contentUrl'   => $video->content_url,
143
-				'uploadDate'   => $video->upload_date,
144
-				'thumbnailUrl' => $video->thumbnail_urls,
145
-				'duration'     => $video->duration,
146
-			);
147
-
148
-			if ( $video->embed_url ) {
149
-				$single_jsonld['embedUrl'] = $video->embed_url;
150
-			}
151
-
152
-			if ( $video->views ) {
153
-				$single_jsonld['interactionStatistic'] = array(
154
-					'@type'                => 'InteractionCounter',
155
-					'interactionType'      => array(
156
-						'@type' => 'http://schema.org/WatchAction',
157
-					),
158
-					'userInteractionCount' => $video->views,
159
-				);
160
-			}
161
-
162
-			if ( $video->is_live_video ) {
163
-				$single_jsonld['publication'] = array(
164
-					'@type'           => 'BroadcastEvent',
165
-					'isLiveBroadcast' => true,
166
-					'startDate'       => $video->live_video_start_date,
167
-					'endDate'         => $video->live_video_end_date,
168
-				);
169
-			}
170
-
171
-			$jsonld[] = $single_jsonld;
172
-		}
173
-
174
-		return $jsonld;
175
-	}
176
-
177
-	private function is_associative_array( $arr ) {
178
-		if ( array() === $arr ) {
179
-			return false;
180
-		}
181
-
182
-		return array_keys( $arr ) !== range( 0, count( $arr ) - 1 );
183
-	}
14
+    /**
15
+     * @var Storage
16
+     */
17
+    private $video_storage;
18
+
19
+    /**
20
+     * Jsonld constructor.
21
+     *
22
+     * @param $video_storage Storage
23
+     */
24
+    public function __construct( $video_storage ) {
25
+        add_action( 'wl_post_jsonld', array( $this, 'wl_post_jsonld' ), 10, 2 );
26
+        add_filter( 'wl_after_get_jsonld', array( $this, 'wl_after_get_jsonld' ), 10, 2 );
27
+        $this->video_storage = $video_storage;
28
+    }
29
+
30
+    public function wl_after_get_jsonld( $jsonld, $post_id ) {
31
+        if ( 0 === count( $jsonld ) ) {
32
+            return $jsonld;
33
+        }
34
+        $current_item = $jsonld[0];
35
+
36
+        if ( ! is_array( $current_item ) || ! array_key_exists( '@type', $current_item ) ) {
37
+            // Cant determine type return early.
38
+            return $jsonld;
39
+        }
40
+
41
+        $type = $current_item['@type'];
42
+        if ( is_string( $type ) ) {
43
+            $type = array( $type );
44
+        }
45
+        // If its a article or descendant of article, then dont add the
46
+        // videoobject in this hook, they will be already added to video property.
47
+        if ( array_intersect( Jsonld_Article_Wrapper::$article_types, $type ) ) {
48
+            return $jsonld;
49
+        }
50
+
51
+        $videos_jsonld = $this->get_videos_jsonld( $post_id );
52
+        if ( 0 === count( $videos_jsonld ) ) {
53
+            return $jsonld;
54
+        }
55
+
56
+        // check if we have @id in jsonld for first item.
57
+        $id = array_key_exists( '@id', $current_item ) ? $current_item['@id'] : '';
58
+
59
+        foreach ( $videos_jsonld as &$video_jsonld ) {
60
+            if ( ! $id ) {
61
+                continue;
62
+            }
63
+            if ( ! array_key_exists( 'mentions', $video_jsonld ) ) {
64
+                $video_jsonld['mentions'] = array( '@id' => $id );
65
+            } else {
66
+                $video_jsonld['mentions'] = array_merge( $video_jsonld['mentions'], array( '@id' => $id ) );
67
+            }
68
+        }
69
+
70
+        return array_merge( $jsonld, $videos_jsonld );
71
+    }
72
+
73
+    /**
74
+     * @param $existing_video_data string | array associative or sequential array.
75
+     * @param $new_video_data array Sequential array.
76
+     *
77
+     * @return array
78
+     */
79
+    private function merge_video_data( $existing_video_data, $new_video_data ) {
80
+        if ( ! is_array( $existing_video_data ) ) {
81
+            $new_video_data[] = $existing_video_data;
82
+
83
+            return $new_video_data;
84
+        }
85
+
86
+        if ( $this->is_associative_array( $existing_video_data ) ) {
87
+            $new_video_data[] = $existing_video_data;
88
+
89
+            return $new_video_data;
90
+        }
91
+
92
+        return array_merge( $existing_video_data, $new_video_data );
93
+    }
94
+
95
+    public function wl_post_jsonld( $jsonld, $post_id ) {
96
+
97
+        $video_jsonld = $this->get_videos_jsonld( $post_id );
98
+        if ( count( $video_jsonld ) === 0 ) {
99
+            return $jsonld;
100
+        }
101
+        // Before adding the video jsonld check if the key
102
+        // is present and additional data might be present,
103
+        // if not present just add the data and return early.
104
+        if ( ! array_key_exists( 'video', $jsonld ) ) {
105
+            $jsonld['video'] = $video_jsonld;
106
+
107
+            return $jsonld;
108
+        }
109
+
110
+        // since key exists, we need to merge the data based on type.
111
+        $previous_video_data = $jsonld['video'];
112
+        $jsonld['video']     = $this->merge_video_data( $previous_video_data, $video_jsonld );
113
+
114
+        return $jsonld;
115
+    }
116
+
117
+    /**
118
+     * @param $post_id int Post id.
119
+     *
120
+     * @return array
121
+     */
122
+    public function get_videos_jsonld( $post_id ) {
123
+
124
+        $videos = $this->video_storage->get_all_videos( $post_id );
125
+
126
+        $jsonld = array();
127
+
128
+        foreach ( $videos as $video ) {
129
+            /**
130
+             * @var $video Video
131
+             */
132
+            $description = $video->description;
133
+            if ( ! $video->description ) {
134
+                // If description is empty then use the video title as description
135
+                $description = $video->name;
136
+            }
137
+            $single_jsonld = array(
138
+                '@context'     => 'http://schema.org',
139
+                '@type'        => 'VideoObject',
140
+                'name'         => $video->name,
141
+                'description'  => $description,
142
+                'contentUrl'   => $video->content_url,
143
+                'uploadDate'   => $video->upload_date,
144
+                'thumbnailUrl' => $video->thumbnail_urls,
145
+                'duration'     => $video->duration,
146
+            );
147
+
148
+            if ( $video->embed_url ) {
149
+                $single_jsonld['embedUrl'] = $video->embed_url;
150
+            }
151
+
152
+            if ( $video->views ) {
153
+                $single_jsonld['interactionStatistic'] = array(
154
+                    '@type'                => 'InteractionCounter',
155
+                    'interactionType'      => array(
156
+                        '@type' => 'http://schema.org/WatchAction',
157
+                    ),
158
+                    'userInteractionCount' => $video->views,
159
+                );
160
+            }
161
+
162
+            if ( $video->is_live_video ) {
163
+                $single_jsonld['publication'] = array(
164
+                    '@type'           => 'BroadcastEvent',
165
+                    'isLiveBroadcast' => true,
166
+                    'startDate'       => $video->live_video_start_date,
167
+                    'endDate'         => $video->live_video_end_date,
168
+                );
169
+            }
170
+
171
+            $jsonld[] = $single_jsonld;
172
+        }
173
+
174
+        return $jsonld;
175
+    }
176
+
177
+    private function is_associative_array( $arr ) {
178
+        if ( array() === $arr ) {
179
+            return false;
180
+        }
181
+
182
+        return array_keys( $arr ) !== range( 0, count( $arr ) - 1 );
183
+    }
184 184
 
185 185
 }
Please login to merge, or discard this patch.
src/modules/include-exclude/includes/Jsonld_Interceptor.php 1 patch
Indentation   +46 added lines, -46 removed lines patch added patch discarded remove patch
@@ -4,51 +4,51 @@
 block discarded – undo
4 4
 
5 5
 class Jsonld_Interceptor {
6 6
 
7
-	/** @var Plugin_Enabled $plugin_enabled */
8
-	private $plugin_enabled;
9
-
10
-	public function __construct( $plugin_enabled ) {
11
-		$this->plugin_enabled = $plugin_enabled;
12
-	}
13
-
14
-	public function register_hooks() {
15
-		add_action( 'wl_before_get_jsonld', array( $this, 'before_get_jsonld' ), 10, 2 );
16
-		add_filter( 'wl_after_get_jsonld', array( $this, 'after_get_jsonld' ) );
17
-	}
18
-
19
-	public function before_get_jsonld( $is_homepage = false, $post_id = null ) {
20
-		if ( null !== filter_input( INPUT_SERVER, 'HTTP_X_WORDLIFT_BYPASS_INCLUDE_EXCLUDE' ) ) {
21
-			clean_post_cache( $post_id );
22
-		}
23
-	}
24
-
25
-	public function after_get_jsonld( $jsonld_arr ) {
26
-		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
27
-		@header( 'X-Wordlift-IncludeExclude-Stage-0: Filter Called with default ' . $this->plugin_enabled->get_configuration()->get_default() );
28
-		if ( ! is_array( $jsonld_arr ) || empty( $jsonld_arr ) || ! isset( $jsonld_arr[0]['url'] ) || null !== filter_input( INPUT_SERVER, 'HTTP_X_WORDLIFT_BYPASS_INCLUDE_EXCLUDE' ) ) {
29
-			// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
30
-			@header( 'X-Wordlift-IncludeExclude-Stage-1: Condition Not Matched' );
31
-
32
-			return $jsonld_arr;
33
-		}
34
-
35
-		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
36
-		@header( 'X-Wordlift-IncludeExclude-Stage-1: Condition Matched for ' . $jsonld_arr[0]['url'] );
37
-		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
38
-		@header( 'X-Wordlift-IncludeExclude-Note: To bypass the Include/Exclude filter add a `x-wordlift-bypass-include-exclude` HTTP request header with any value.' );
39
-
40
-		// If the URLs are included then publish them.
41
-		if ( $this->plugin_enabled->are_urls_included( $jsonld_arr[0]['url'] ) ) {
42
-			// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
43
-			@header( 'X-Wordlift-IncludeExclude-Stage-2: URL Included' );
44
-
45
-			return $jsonld_arr;
46
-		}
47
-
48
-		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
49
-		@header( 'X-Wordlift-IncludeExclude-Stage-2: URL Excluded' );
50
-
51
-		return array();
52
-	}
7
+    /** @var Plugin_Enabled $plugin_enabled */
8
+    private $plugin_enabled;
9
+
10
+    public function __construct( $plugin_enabled ) {
11
+        $this->plugin_enabled = $plugin_enabled;
12
+    }
13
+
14
+    public function register_hooks() {
15
+        add_action( 'wl_before_get_jsonld', array( $this, 'before_get_jsonld' ), 10, 2 );
16
+        add_filter( 'wl_after_get_jsonld', array( $this, 'after_get_jsonld' ) );
17
+    }
18
+
19
+    public function before_get_jsonld( $is_homepage = false, $post_id = null ) {
20
+        if ( null !== filter_input( INPUT_SERVER, 'HTTP_X_WORDLIFT_BYPASS_INCLUDE_EXCLUDE' ) ) {
21
+            clean_post_cache( $post_id );
22
+        }
23
+    }
24
+
25
+    public function after_get_jsonld( $jsonld_arr ) {
26
+        // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
27
+        @header( 'X-Wordlift-IncludeExclude-Stage-0: Filter Called with default ' . $this->plugin_enabled->get_configuration()->get_default() );
28
+        if ( ! is_array( $jsonld_arr ) || empty( $jsonld_arr ) || ! isset( $jsonld_arr[0]['url'] ) || null !== filter_input( INPUT_SERVER, 'HTTP_X_WORDLIFT_BYPASS_INCLUDE_EXCLUDE' ) ) {
29
+            // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
30
+            @header( 'X-Wordlift-IncludeExclude-Stage-1: Condition Not Matched' );
31
+
32
+            return $jsonld_arr;
33
+        }
34
+
35
+        // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
36
+        @header( 'X-Wordlift-IncludeExclude-Stage-1: Condition Matched for ' . $jsonld_arr[0]['url'] );
37
+        // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
38
+        @header( 'X-Wordlift-IncludeExclude-Note: To bypass the Include/Exclude filter add a `x-wordlift-bypass-include-exclude` HTTP request header with any value.' );
39
+
40
+        // If the URLs are included then publish them.
41
+        if ( $this->plugin_enabled->are_urls_included( $jsonld_arr[0]['url'] ) ) {
42
+            // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
43
+            @header( 'X-Wordlift-IncludeExclude-Stage-2: URL Included' );
44
+
45
+            return $jsonld_arr;
46
+        }
47
+
48
+        // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
49
+        @header( 'X-Wordlift-IncludeExclude-Stage-2: URL Excluded' );
50
+
51
+        return array();
52
+    }
53 53
 
54 54
 }
Please login to merge, or discard this patch.