Completed
Push — master ( 0c53d4...debbec )
by David
09:08 queued 10s
created

Wordlift_Batch_Analysis_Service::complete()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 89
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 33
nc 5
nop 0
dl 0
loc 89
rs 6.5134
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Services: Batch Analysis Service.
4
 *
5
 * The Batch Analysis service allows to queue analysis operations given a list
6
 * of URLs.
7
 *
8
 * The Batch Analysis service should also allow to queue all the posts/pages
9
 * that do not contain any annotation (example annotation:
10
 * <span id="urn:enhancement-{uuid}"
11
 *       class="textannotation disambiguated wl-{class} [wl-[no-]link]"
12
 *       itemid="{itemid-url}">{label}</span>
13
 *
14
 * We must identify the batch analysis status according to 3 stages:
15
 *  1. BATCH_ANALYSIS_SUBMIT, i.e. a post/page has been submitted for batch
16
 *      analysis.
17
 *  2. BATCH_ANALYSIS_REQUEST, i.e. a post/page batch analysis has been
18
 *      requested to the remote service.
19
 *  3. BATCH_ANALYSIS_SUCCESS / BATCH_ANALYSIS_ERROR: the outcome of the batch
20
 *      analysis.
21
 *
22
 * For each state we record the date time, this is especially useful since the
23
 * remote service doesn't provide a state management, therefore we need to
24
 * define a timeout on the client side.
25
 *
26
 * Upon reception of the results we need to check whether there are some
27
 * potential warning due to interpolation issues, i.e.
28
 *
29
 *  `\w<span id="urn:enhancement-` or `\s</span>` or `</span>\w`
30
 *
31
 * and in such a case, set a warning BATCH_ANALYSIS_WARNING in order to provide
32
 * a list of posts/pages that need manual review and allow the editor to clear
33
 * the warning flag.
34
 *
35
 * All the time-consuming operations must be executed asynchronously.
36
 *
37
 * Since setting the post meta for a large number of posts may be time consuming
38
 * in PHP, we can use prepared queries.
39
 *
40
 * @since      3.14.0
41
 * @package    Wordlift
42
 * @subpackage Wordlift/includes
43
 */
44
45
/**
46
 * Define the {@link Wordlift_Batch_Analysis_Service} class.
47
 *
48
 * @since      3.14.0
49
 * @package    Wordlift
50
 * @subpackage Wordlift/includes
51
 */
52
class Wordlift_Batch_Analysis_Service {
53
54
	/**
55
	 * The list of states for the Batch Analysis:
56
	 *  - STATE_META_KEY: the batch analysis state meta key,
57
	 *  - STATE_SUBMIT: a post/page has been submitted for analysis,
58
	 *  - STATE_REQUEST: the plugin requested an analysis for the submitted
59
	 *      post/page,
60
	 *  - STATE_SUCCESS: the analysis has completed successfully,
61
	 *  - STATE_ERROR: the analysis returned an error.
62
	 *
63
	 * @since 3.14.2
64
	 */
65
	const STATE_META_KEY = '_wl_batch_analysis_state';
66
	const STATE_SUBMIT = 0;
67
	const STATE_REQUEST = 1;
68
	// ### COMPLETE states.
69
	const STATE_SUCCESS = 2;
70
	const STATE_ERROR = 2;
71
72
	/**
73
	 * The submit timestamp meta key. A post may have more than one timestamp.
74
	 *
75
	 * @since 3.14.2
76
	 */
77
	const SUBMIT_TIMESTAMP_META_KEY = '_wl_batch_analysis_submit_timestamp';
78
79
	/**
80
	 * The request timestamp meta key. A post may have more than one timestamp.
81
	 *
82
	 * @since 3.14.2
83
	 */
84
	const REQUEST_TIMESTAMP_META_KEY = '_wl_batch_analysis_request_timestamp';
85
86
	/**
87
	 * The complete (success or error) timestamp meta key. A post may have more
88
	 * than one timestamp.
89
	 *
90
	 * @since 3.14.2
91
	 */
92
	const COMPLETE_TIMESTAMP_META_KEY = '_wl_batch_analysis_complete_timestamp';
93
94
	/**
95
	 * The options setting meta key. A post may have more than one setting.
96
	 *
97
	 * @since 3.14.2
98
	 */
99
	const BATCH_ANALYSIS_OPTIONS_META_KEY = '_wl_batch_analysis_options';
100
101
	/**
102
	 * The warning timestamp meta key. A post has only zero/one value.
103
	 *
104
	 * @since 3.14.2
105
	 */
106
	const WARNING_META_KEY = '_wl_batch_analysis_warning';
107
108
	/**
109
	 * Option name.
110
	 *
111
	 * @since  3.14.0
112
	 */
113
	const OPTION_NAME = 'wl_analyze_batch';
114
115
	/**
116
	 * Name of waiting to be processed queue array inside the option.
117
	 *
118
	 * @since  3.14.0
119
	 */
120
	const ANALYZE_QUEUE = 'queue';
121
122
	/**
123
	 * Name of waiting for response queue array inside the option.
124
	 *
125
	 * @since  3.14.0
126
	 */
127
	const RESPONSE_QUEUE = 'processing';
128
129
	/**
130
	 * Regular expressions that match interpolation errors.
131
	 *
132
	 * @since  3.17.0
133
	 */
134
	private static $interpolation_patterns = array(
135
		// Matches word before the annotation.
136
		'~(\w)<[a-z]+ id="urn:[^"]+" class="[^"]+" itemid="[^"]+">(.*?)<\/[a-z]+>~',
137
		// Matches word after the annotation.
138
		'~<[a-z]+ id="urn:[^"]+" class="[^"]+" itemid="[^"]+">(.*?)<\/[a-z]+>(\w)~',
139
		// Matches space in the beginning of annotation name.
140
		'~<[a-z]+ id="urn:[^"]+" class="[^"]+" itemid="[^"]+">(\s)(.*?)<\/[a-z]+>~',
141
	);
142
143
	/**
144
	 * The {@link Wordlift} plugin instance.
145
	 *
146
	 * @since  3.14.0
147
	 * @access private
148
	 * @var \Wordlift $plugin The {@link Wordlift} plugin instance.
149
	 */
150
	private $plugin;
151
152
	/**
153
	 * The {@link Wordlift_Configuration_Service} instance.
154
	 *
155
	 * @since  3.14.0
156
	 * @access private
157
	 * @var \Wordlift_Configuration_Service $configuration_service The {@link Wordlift_Configuration_Service} instance.
158
	 */
159
	private $configuration_service;
160
161
	/**
162
	 * The {@link Wordlift_Cache_Service} instance.
163
	 *
164
	 * @since  3.17.0
165
	 * @access protected
166
	 * @var \Wordlift_Cache_Service $cache_service The {@link Wordlift_Cache_Service} instance.
167
	 */
168
	private $cache_service;
169
170
	/**
171
	 * A {@link Wordlift_Log_Service} instance.
172
	 *
173
	 * @since  3.14.2
174
	 * @access private
175
	 * @var \Wordlift_Log_Service $log A {@link Wordlift_Log_Service} instance.
176
	 */
177
	private $log;
178
179
	/**
180
	 * The {@link Class_Wordlift_Batch_Analys_Service} instance.
181
	 *
182
	 * @since 3.14.0
183
	 *
184
	 * @param \Wordlift                       $plugin                The {@link Wordlift} plugin instance.
185
	 * @param \Wordlift_Configuration_Service $configuration_service The {@link Wordlift_Configuration_Service} instance.
186
	 * @param \Wordlift_Cache_Service         $cache_service         The {@link Wordlift_Cache_Service} instance.
187
	 */
188
	public function __construct( $plugin, $configuration_service, $cache_service ) {
189
190
		$this->log = Wordlift_Log_Service::get_logger( 'Wordlift_Batch_Analysis_Service' );
191
192
		$this->plugin                = $plugin;
193
		$this->configuration_service = $configuration_service;
194
		$this->cache_service         = $cache_service;
195
196
		add_action( 'wl_async_wl_batch_analysis_request', array(
197
			$this,
198
			'request',
199
		) );
200
		add_action( 'wl_async_wl_batch_analysis_complete', array(
201
			$this,
202
			'complete',
203
		) );
204
205
	}
206
207
	/**
208
	 * Submit posts for Batch Analysis.
209
	 *
210
	 * @since 3.14.2
211
	 *
212
	 * @param array       $args               {
213
	 *                                        A list of options for the Batch Analysis.
214
	 *
215
	 * @type string       $link               Either `default`, `no` or `yes` (`default` is used if not specified):
216
	 *                                        * `default` doesn't set the link option - entities
217
	 *                                           will be linked if configured so in WordLift settings.
218
	 *                                        * `yes` links the entities.
219
	 *                                        * `no` doesn't link the entities.
220
	 *                                        This value is forwarded to WLS' Batch Analysis end-point.
221
	 * @type int          $min_occurrences    The minimum number of occurrences to select
222
	 *                                        an entity. Default `1`.
223
	 * @type bool         $include_annotated  Whether to include annotated posts in selection.
224
	 *                                        Default `false`.
225
	 * @type array|int    $include            Explicitly include the specified {@link WP_Post}s.
226
	 * @type array|int    $exclude            Explicitly exclude the specified {@link WP_Post}s.
227
	 * @type string|null  $from               An optional date from filter (used in `post_date_gmt`).
228
	 * @type string|null  $to                 An optional date from filter (used in `post_date_gmt`).
229
	 * @type array|string $post_type          Specify the post type(s), by default only `post`.
230
	 *                      }
231
	 *
232
	 * @return int The number of submitted {@link WP_Post}s or false on error.
233
	 */
234
	public function submit( $args ) {
235
		// Parse the parameters.
236
		$params = wp_parse_args( $args, array(
237
			'links'             => 'default',
238
			'min_occurrences'   => 1,
239
			'include_annotated' => false,
240
			'exclude'           => array(),
241
			'from'              => null,
242
			'to'                => null,
243
			'post_type'         => 'post',
244
		) );
245
246
		// Validation.
247
		if ( ! in_array( $params['links'], array( 'default', 'yes', 'no' ) ) ) {
248
			wp_die( '`link` must be one of the following: `default`, `yes` or `no`.' );
249
		}
250
251
		if ( ! is_numeric( $params['min_occurrences'] ) || 1 > $params['min_occurrences'] ) {
252
			wp_die( '`min_occurrences` must greater or equal 1.' );
253
		}
254
255
		// Get the sql query.
256
		$query = Wordlift_Batch_Analysis_Sql_Helper::get_sql( $params );
257
258
		// Set the post metas and get the value of the posts.
259
		$submitted_posts = $this->update_posts_meta( $params, $query );
260
261
		// Request Batch Analysis (the operation is handled asynchronously).
262
		do_action( 'wl_batch_analysis_request' );
263
264
		// Return the count of the posts.
265
		return $submitted_posts;
266
	}
267
268
	/**
269
	 * Submit one or more {@link WP_Posts} for Batch Analysis.
270
	 *
271
	 * @param array    $args            {
272
	 *                                  An array of arguments.
273
	 *
274
	 * @type string    $link            The link option: `default`, `yes` or
275
	 *                                  `no`. If not set `default`.
276
	 * @type int       $min_occurrences The minimum number of occurrences. If
277
	 *                                  not set `1`.
278
	 * @type array|int $ids             An array of {@link WP_Post}s' ids or one
279
	 *                                  single numeric {@link WP_Post} id.
280
	 *                    }
281
	 *
282
	 * @return float|int
283
	 */
284
	public function submit_posts( $args ) {
285
		// Parse the parameters.
286
		$params = wp_parse_args( $args, array(
287
			'links'           => 'default',
288
			'min_occurrences' => 1,
289
			'ids'             => array(),
290
		) );
291
292
		// Validation.
293
		if ( empty( $params['ids'] ) ) {
294
			wp_die( '`ids` cannot be empty.' );
295
		}
296
297
		// Get the query,
298
		$query = Wordlift_Batch_Analysis_Sql_Helper::get_sql_for_ids( $params );
299
300
		// Set the post metas and get the value of the posts.
301
		$submitted_posts = $this->update_posts_meta( $params, $query );
302
303
		// Request Batch Analysis (the operation is handled asynchronously).
304
		do_action( 'wl_batch_analysis_request' );
305
306
		// Return the count of the posts.
307
		return $submitted_posts;
308
	}
309
310
	/**
311
	 * Add metas to the posts that should be analysed.
312
	 *
313
	 * @param array  $params The request params.
314
	 * @param string $query  The mysql query.
315
	 *
316
	 * @since 3.18.0
317
	 *
318
	 * @return int The number of posts found/submitted.
319
	 */
320
	public function update_posts_meta( $params, $query ) {
321
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
322
323
		// Get the link options.
324
		$link_options = array(
325
			'links'           => $params['links'],
326
			'min_occurrences' => $params['min_occurrences'],
327
		);
328
329
		// Get the posts that should be submitted for analysis.
330
		$posts = $wpdb->get_results( $query ); // WPCS: cache ok, db call ok.
331
332
		// Bail if there are no posts found.
333
		if ( empty( $posts ) ) {
334
			return 0;
335
		}
336
337
		// Add the post metas.
338
		foreach ( $posts as $p ) {
339
			add_post_meta( $p->ID, self::STATE_META_KEY, 0 );
340
			add_post_meta( $p->ID, self::SUBMIT_TIMESTAMP_META_KEY, gmdate( 'Y-m-d H:i:s' ) );
341
			add_post_meta( $p->ID, self::BATCH_ANALYSIS_OPTIONS_META_KEY, $link_options );
342
		}
343
344
		// Finally return the posts count.
345
		return count( $posts );
346
	}
347
348
	/**
349
	 * Cancel the Batch Analysis request for the specified {@link WP_Post}s.
350
	 *
351
	 * @since 3.14.2
352
	 *
353
	 * @param int|array $post_ids A single {@link WP_Post}'s id or an array of
354
	 *                            {@link WP_Post}s' ids.
355
	 *
356
	 * @return false|int The number of cancelled {@link WP_Post}s or false on
357
	 *                   error.
358
	 */
359
	public function cancel( $post_ids ) {
360
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
361
362
		return $wpdb->query( $wpdb->prepare(
363
			"
364
			DELETE FROM $wpdb->postmeta
365
			WHERE meta_key = %s
366
				AND meta_value IN ( %d, %d )
367
				AND post_id IN( " . implode( ',', wp_parse_id_list( $post_ids ) ) . " )
368
			",
369
			self::STATE_META_KEY,
370
			self::STATE_SUBMIT,
371
			self::STATE_REQUEST
372
		) ); // WPCS: cache ok, db call ok.
373
374
	}
375
376
	/**
377
	 * Request the batch analysis for submitted posts.
378
	 *
379
	 * @since 3.14.2
380
	 */
381
	public function request() {
382
383
		$this->log->debug( 'Requesting analysis...' );
384
385
		// By default 5 posts of any post type are returned.
386
		$posts = get_posts( array(
387
			'fields'     => 'ids',
388
			'meta_key'   => self::STATE_META_KEY,
389
			'meta_value' => self::STATE_SUBMIT,
390
			'orderby'    => 'ID',
391
			'post_type'  => 'any',
392
		) );
393
394
		// Bail out if there are no submitted posts.
395
		if ( empty( $posts ) ) {
396
			$this->log->debug( 'No posts to submit found, checking for completed requests...' );
397
398
			do_action( 'wl_batch_analysis_complete' );
399
400
			return;
401
		}
402
403
		// Send a request for each post.
404
		foreach ( $posts as $id ) {
405
406
			$this->log->debug( "Requesting analysis for post $id..." );
407
408
			// Send the actual request to the remote service.
409
			$result = $this->do_request( $id );
410
411
			// Set an error if we received an error.
412
			if ( is_wp_error( $result ) ) {
413
				$this->log->error( "An error occurred while requesting a batch analysis for post $id: " . $result->get_error_message() );
414
415
				$this->set_state( $id, self::STATE_ERROR );
416
			}
417
418
		}
419
420
		// Call the `wl_batch_analysis_request` action again. This is going
421
		// to be handled by the async task.
422
		do_action( 'wl_batch_analysis_request' );
423
424
	}
425
426
	/**
427
	 * Get the results for the Batch Analysis.
428
	 *
429
	 * @since 3.14.2
430
	 */
431
	public function complete() {
432
433
		$this->log->debug( 'Requesting results...' );
434
435
		// By default 5 posts of any post type are returned.
436
		$posts = get_posts( array(
437
			'fields'     => 'ids',
438
			'meta_key'   => self::STATE_META_KEY,
439
			'meta_value' => self::STATE_REQUEST,
440
			'orderby'    => 'ID',
441
			'post_type'  => 'any',
442
		) );
443
444
		// Bail out if there are no submitted posts.
445
		if ( empty( $posts ) ) {
446
			$this->log->debug( 'No posts in request state found.' );
447
448
			return;
449
		}
450
451
		// Send a request for each post.
452
		foreach ( $posts as $id ) {
453
			$this->log->debug( "Requesting results for post $id..." );
454
455
			// Send the actual request to the remote service.
456
			$response = $this->do_complete( $id );
457
458
			// Move to the next item if we don't have a reply for this one.
459
			if ( is_wp_error( $response ) || 200 !== (int) $response['response']['code'] || ! isset( $response['body'] ) ) {
460
				continue;
461
			}
462
463
			$this->log->debug( "Results received for post $id." );
464
465
			// Save the returned content as new revision.
466
			$json = json_decode( $response['body'] );
467
468
			// Continue if the content isn't set.
469
			if ( empty( $json->content ) ) {
470
				// The post content is empty, so is should be marked as completed.
471
				$this->log->error( "An error occurred while decoding the batch analysis response for post $id: {$response['body']}" );
472
473
				$this->set_state( $id, self::STATE_ERROR );
474
				continue;
475
			}
476
477
			// Set the warning flag if needed.
478
			$this->set_warning_based_on_content( $json->content, $id );
479
480
			// Get the content, cleaned up if there are interpolation errors.
481
			$pre_content = $this->fix_interpolation_errors( $json->content, $id );
482
483
			/**
484
			 * Filter: 'wl_batch_analysis_update_post_content' - Allow third
485
			 * parties to perform additional actions when the post content is
486
			 * updated.
487
			 *
488
			 * @since  3.17.0
489
			 * @api    string $data The {@link WP_Post}'s content.
490
			 * @api    int    $id   The {@link WP_Post}'s id.
491
			 */
492
			$content = apply_filters( 'wl_batch_analysis_update_post_content', $pre_content, $id );
493
494
			// Update the post content.
495
			wp_update_post( array(
496
				'ID'           => $id,
497
				'post_content' => wp_slash( $content ),
498
			) );
499
500
			// Update the status.
501
			$this->set_state( $id, self::STATE_SUCCESS );
502
503
			// Invalidating the cache for the current post.
504
			$this->cache_service->delete_cache( $id );
505
506
			$this->log->debug( "Post $id updated with batch analysis results." );
507
508
			// Set default entity type term for posts that didn't have any.
509
			$this->maybe_set_default_term( $id );
510
511
			// @todo: implement a kind of timeout that sets an error if the
512
			// results haven't been received after a long time.
513
		}
514
515
		// Call the `wl_batch_analysis_request` action again. This is going
516
		// to be handled by the async task.
517
		do_action( 'wl_batch_analysis_complete' );
518
519
	}
520
521
	/**
522
	 * Set a warning flag on the {@link WP_Post} if its content has suspicious
523
	 * interpolations.
524
	 *
525
	 * @since 3.14.2
526
	 *
527
	 * @param string $content The {@link WP_Post}'s content.
528
	 * @param int    $post_id The {@link WP_Post}'s id.
529
	 */
530
	protected function set_warning_based_on_content( $content, $post_id ) {
531
532
		// Check for suspicious interpolations.
533
		$is_warning = $this->has_interpolation_errors( $content );
534
535
		// Set the warning flag accordingly.
536
		$this->set_warning( $post_id, $is_warning );
537
538
	}
539
540
	private function has_interpolation_errors( $content ) {
541
		$matches = array();
542
543
		// eg:
544
		// r-pro<span id="urn:local-text-annotation-oxbgy6139gnjgk1n0oxnq9zg62py29pf" class="textannotation disambiguated wl-thing" itemid="http://data.wordlift.it/be2/entity/developing_country">ne region, has shoul</span>dere
545
546
		return 0 < preg_match_all( '/\w<[a-z]+ id="urn:[^"]+" class="[^"]+" itemid="[^"]+">/', $content, $matches )
547
			   || 0 < preg_match_all( ' /<[a-z]+ id="urn:[^"]+ " class="[^"]+" itemid="[^"]+">\s/', $content, $matches );
548
	}
549
550
	/**
551
	 * Fix interpolation errors raised by Batch Analysis
552
	 *
553
	 * @param string $content The {@link WP_Post}'s content.
554
	 * @param int    $id      The {@link WP_Post}'s id.
555
	 *
556
	 * @since 3.17.0
557
	 *
558
	 * @return string Post content without interpolations.
559
	 */
560
	private function fix_interpolation_errors( $content, $id ) {
561
562
		// Bail out if there are no interpolation errors.
563
		if ( ! $this->has_interpolation_errors( $content ) ) {
564
			$this->log->trace( "No interpolation errors found for post $id." );
565
566
			return $content;
567
		}
568
569
		$this->log->debug( "Fixing post $id interpolations..." );
570
571
		// Remove all interpolations from the content.
572
		return preg_replace( self::$interpolation_patterns, '$1$2', $content );
573
	}
574
575
	/**
576
	 * Clear the warning flag for the specified {@link WP_Post}s.
577
	 *
578
	 * @since 3.14.2
579
	 *
580
	 * @param int|array $post_ids A single {@link WP_Post}'s id or an array of
581
	 *                            {@link WP_Post}s' ids.
582
	 */
583
	public function clear_warning( $post_ids ) {
584
585
		foreach ( (array) $post_ids as $post_id ) {
586
			delete_post_meta( $post_id, self::WARNING_META_KEY );
587
		}
588
589
	}
590
591
	/**
592
	 * Set the warning flag for the specified {@link WP_Post}.
593
	 *
594
	 * @since 3.14.2
595
	 *
596
	 * @param int  $post_id The {@link WP_Post}'s id.
597
	 * @param bool $value   The flag's value.
598
	 *
599
	 * @return int|bool Meta ID if the key didn't exist, true on successful update,
600
	 *                  false on failure.
601
	 */
602
	private function set_warning( $post_id, $value ) {
603
604
		return update_post_meta( $post_id, self::WARNING_META_KEY, ( true === $value ? 'yes' : 'no' ) );
605
	}
606
607
//	/**
608
//	 * Get the post/page batch analysis state.
609
//	 *
610
//	 * @since 3.14.2
611
//	 *
612
//	 * @param int $post_id The {@link WP_Post}'s id.
613
//	 *
614
//	 * @return int|string The post state or an empty string if not set.
615
//	 */
616
//	public function get_state( $post_id ) {
617
//
618
//		return get_post_meta( $post_id, self::STATE_META_KEY, true );
619
//	}
620
621
	/**
622
	 * Set the post/page batch analysis state.
623
	 *
624
	 * @since 3.14.2
625
	 *
626
	 * @param int $post_id The {@link WP_Post}'s id.
627
	 * @param int $value   The new state.
628
	 *
629
	 * @return int|bool Meta ID if the key didn't exist, true on successful update,
630
	 *                  false on failure.
631
	 */
632
	private function set_state( $post_id, $value ) {
633
634
		// Update the state.
635
		$result = update_post_meta( $post_id, self::STATE_META_KEY, $value );
636
637
		// Update timestamps as required.
638
		switch ( $value ) {
639
640
			// ### REQUEST state.
641
			case self::STATE_REQUEST:
642
				add_post_meta( $post_id, self::REQUEST_TIMESTAMP_META_KEY, current_time( 'mysql', true ) );
643
				break;
644
645
			// ### SUCCESS/ERROR state.
646
			case self::STATE_SUCCESS:
647
			case self::STATE_ERROR:
648
				add_post_meta( $post_id, self::COMPLETE_TIMESTAMP_META_KEY, current_time( 'mysql', true ) );
649
				break;
650
		}
651
652
		// Finally return the result.
653
		return $result;
654
	}
655
656
	/**
657
	 * Get the options setting for a {@link WP_Post}.
658
	 *
659
	 * If there are multiple link settings, only the last one is returned.
660
	 *
661
	 * @since 3.14.2
662
	 *
663
	 * @param int $post_id The {@link WP_Post}'s id.
664
	 *
665
	 * @return array The link settings.
666
	 */
667
	private function get_options( $post_id ) {
668
669
		$values = get_post_meta( $post_id, self::BATCH_ANALYSIS_OPTIONS_META_KEY );
670
671
		return end( $values ) ?: array(
672
			'links'           => 'default',
673
			'min_occurrences' => 1,
674
		);
675
	}
676
677
	/**
678
	 * Get the array of post IDS waiting in the queue to start processing.
679
	 *
680
	 * @since 3.14.0
681
	 *
682
	 * @return array The waiting to be processed post ids queue.
683
	 */
684 View Code Duplication
	public function waiting_for_analysis() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
685
686
		return get_posts( array(
687
			'posts_per_page' => - 1,
688
			'fields'         => 'ids',
689
			'post_status'    => 'any',
690
			'meta_key'       => self::STATE_META_KEY,
691
			'meta_value'     => self::STATE_SUBMIT,
692
			'orderby'        => 'ID',
693
			'post_type'      => 'any',
694
			// Add any because posts from multiple posts types may be waiting.
695
		) );
696
	}
697
698
	/**
699
	 * Get the array of post IDS waiting for response.
700
	 *
701
	 * @deprecated
702
	 * @since 3.14.0
703
	 *
704
	 * @return array The waiting for response post ids queue.
705
	 */
706 View Code Duplication
	public function waiting_for_response() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
707
708
		return get_posts( array(
709
			'posts_per_page' => - 1,
710
			'fields'         => 'ids',
711
			'post_status'    => 'any',
712
			'meta_key'       => self::STATE_META_KEY,
713
			'meta_value'     => self::STATE_REQUEST,
714
			'orderby'        => 'ID',
715
			'post_type'      => 'any',
716
			// Add any because posts from multiple posts types may be waiting.
717
		) );
718
	}
719
720
	/**
721
	 * Request the analysis for the specified {@link WP_Post}.
722
	 *
723
	 * @since 3.14.2
724
	 *
725
	 * @param int $post_id The {@link WP_Post}'s id.
726
	 *
727
	 * @return WP_Error|array The response or WP_Error on failure.
728
	 */
729
	private function do_request( $post_id ) {
730
731
		// Change the state to `REQUEST`.
732
		$this->set_state( $post_id, self::STATE_REQUEST );
733
734
		// Get the post.
735
		$post = get_post( $post_id );
736
737
		// Bail out if the post isn't found.
738
		if ( null === $post ) {
739
			$this->log->warn( "Post $post_id not found." );
740
741
			return new WP_Error( 0, "Cannot find post $post_id." );
742
		}
743
744
		// Get the link setting.
745
		$options = $this->get_options( $post_id );
746
747
		$this->log->debug( 'Sending analysis request for post $post_id [ links :: ' . $options['links'] . ', min_occurrences :: ' . $options['min_occurrences'] . ' ] ...' );
748
749
		// Get the batch analysis URL.
750
		$url = $this->configuration_service->get_batch_analysis_url();
751
752
		// Prepare the POST parameters.
753
		$params = array(
754
			'id'              => $post->ID,
755
			'key'             => $this->configuration_service->get_key(),
756
			'content'         => $post->post_content,
757
			'contentLanguage' => $this->configuration_service->get_language_code(),
758
			'version'         => $this->plugin->get_version(),
759
			'scope'           => 'local',
760
			'links'           => $options['links'],
761
			'minOccurrences'  => $options['min_occurrences'],
762
		);
763
764
		// Get the HTTP options.
765
		$args = array_merge_recursive( unserialize( WL_REDLINK_API_HTTP_OPTIONS ), array(
766
			'method'      => 'POST',
767
			'headers'     => array(
768
				'Accept'       => 'application/json',
769
				'Content-type' => 'application/json; charset=UTF-8',
770
			),
771
			// we need to downgrade the HTTP version in this case since chunked encoding is dumping numbers in the response.
772
			'httpversion' => '1.0',
773
			'body'        => wp_json_encode( $params ),
774
		) );
775
776
		$this->log->debug( "Posting analysis request for post $post_id to $url..." );
777
778
		// Post the parameter.
779
		return wp_remote_post( $url, $args );
780
	}
781
782
	/**
783
	 * Get the Batch Analysis results for the specified {@link WP_Post}.
784
	 *
785
	 * @since 3.14.2
786
	 *
787
	 * @param int $post_id The {@link WP_Post}'s id.
788
	 *
789
	 * @return WP_Error|array The response or WP_Error on failure.
790
	 */
791
	private function do_complete( $post_id ) {
792
793
		$post = get_post( $post_id );
794
795
		if ( null === $post ) {
796
			// Post was possibly deleted, just bailout.
797
			return new WP_Error( 0, "Post $post_id not found . " );
798
		}
799
800
		$url = $this->configuration_service->get_batch_analysis_url();
801
		$key = $this->configuration_service->get_key();
802
		$url = $url . '/' . $post->ID . '?key=' . $key;
803
804
		return wp_remote_get( $url, unserialize( WL_REDLINK_API_HTTP_OPTIONS ) );
805
	}
806
807
	/**
808
	 * Get the {@link WP_Post}s' ids flagged with warnings.
809
	 *
810
	 * @since 3.14.2
811
	 *
812
	 * @return array An array of {@link WP_Post}s' ids.
813
	 */
814 View Code Duplication
	public function get_warnings() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
815
816
		return get_posts( array(
817
			'fields'      => 'ids',
818
			'numberposts' => - 1,
819
			'post_status' => 'any',
820
			'meta_key'    => self::WARNING_META_KEY,
821
			'meta_value'  => 'yes',
822
			'post_type'   => 'any',
823
			// Add any because posts from multiple posts types may be waiting.
824
		) );
825
	}
826
827
	/**
828
	 * Check whether the term has entity type associated and set default term if it hasn't.
829
	 *
830
	 * @since 3.17.0
831
	 *
832
	 * @param int $id The {@link WP_Post}'s id.
833
	 */
834
	private function maybe_set_default_term( $id ) {
835
		// Check whether the post has any of the WordLift entity types.
836
		$has_term = has_term( '', Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME, $id );
837
838
		// Bail if the term is associated with entity types already.
839
		if ( ! empty( $has_term ) ) {
840
			return;
841
		}
842
843
		// Set the default `article` term.
844
		wp_set_object_terms( $id, 'article', Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME );
845
846
	}
847
848
}
849