Completed
Push — master ( 4713b5...48986a )
by David
05:28 queued 15s
created

Wordlift_Rating_Service::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 3
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
/**
3
 * Services: Rating Service.
4
 *
5
 * @since   3.10.0
6
 * @package Wordlift
7
 */
8
9
/**
10
 * Define the {@link Wordlift_Rating_Service} class.
11
 *
12
 * @since   3.10.0
13
 * @package Wordlift
14
 */
15
class Wordlift_Rating_Service {
16
17
	/**
18
	 * Entity rating max.
19
	 *
20
	 * @since 3.3.0
21
	 */
22
	const RATING_MAX = 7;
23
24
	/**
25
	 * Entity rating score meta key.
26
	 *
27
	 * @since 3.3.0
28
	 */
29
	const RATING_RAW_SCORE_META_KEY = '_wl_entity_rating_raw_score';
30
31
	/**
32
	 * Entity rating warnings meta key.
33
	 *
34
	 * @since 3.3.0
35
	 */
36
	const RATING_WARNINGS_META_KEY = '_wl_entity_rating_warnings';
37
38
	/**
39
	 * Entity warning has related post identifier.
40
	 *
41
	 * @since 3.3.0
42
	 */
43
	const RATING_WARNING_HAS_RELATED_POSTS = 'There are no related posts for the current entity.';
44
45
	/**
46
	 * Entity warning has content post identifier.
47
	 *
48
	 * @since 3.3.0
49
	 */
50
	const RATING_WARNING_HAS_CONTENT_POST = 'This entity has not description.';
51
52
	/**
53
	 * Entity warning has related entities identifier.
54
	 *
55
	 * @since 3.3.0
56
	 */
57
	const RATING_WARNING_HAS_RELATED_ENTITIES = 'There are no related entities for the current entity.';
58
59
	/**
60
	 * Entity warning is published identifier.
61
	 *
62
	 * @since 3.3.0
63
	 */
64
	const RATING_WARNING_IS_PUBLISHED = 'This entity is not published. It will not appear within analysis results.';
65
66
	/**
67
	 * Entity warning has thumbnail identifier.
68
	 *
69
	 * @since 3.3.0
70
	 */
71
	const RATING_WARNING_HAS_THUMBNAIL = 'This entity has no featured image yet.';
72
73
	/**
74
	 * Entity warning has same as identifier.
75
	 *
76
	 * @since 3.3.0
77
	 */
78
	const RATING_WARNING_HAS_SAME_AS = 'There are no sameAs configured for this entity.';
79
80
	/**
81
	 * Entity warning has completed metadata identifier.
82
	 *
83
	 * @since 3.3.0
84
	 */
85
	const RATING_WARNING_HAS_COMPLETED_METADATA = 'Schema.org metadata for this entity are not completed.';
86
87
	/**
88
	 * A {@link Wordlift_Entity_Service} instance.
89
	 *
90
	 * @since  3.10.0
91
	 * @access private
92
	 * @var Wordlift_Entity_Service $entity_service A {@link Wordlift_Entity_Service} instance.
93
	 */
94
	private $entity_service;
95
96
	/**
97
	 *  A {@link Wordlift_Entity_Type_Service} instance.
98
	 *
99
	 * @since  3.10.0
100
	 * @access private
101
	 * @var Wordlift_Entity_Type_Service $entity_type_service A {@link Wordlift_Entity_Type_Service} instance.
102
	 */
103
	private $entity_type_service;
104
105
	/**
106
	 * The Notice service.
107
	 *
108
	 * @since  3.3.0
109
	 * @access private
110
	 * @var \Wordlift_Notice_Service $notice_service The Notice service.
111
	 */
112
	private $notice_service;
113
114
	/**
115
	 * A {@link Wordlift_Log_Service} instance.
116
	 *
117
	 * @since  3.10.0
118
	 * @access private
119
	 * @var Wordlift_Log_Service $log A {@link Wordlift_Log_Service} instance.
120
	 */
121
	private $log;
122
123
	/**
124
	 * Create a {@link Wordlift_Rating_Service} instance.
125
	 *
126
	 * @since 3.10.0
127
	 *
128
	 * @param \Wordlift_Entity_Service      $entity_service      A {@link Wordlift_Entity_Service} instance.
129
	 * @param \Wordlift_Entity_Type_Service $entity_type_service A {@link Wordlift_Entity_Type_Service} instance.
130
	 * @param \Wordlift_Notice_Service      $notice_service      A {@link Wordlift_Notice_Service} instance.
131
	 */
132
	public function __construct( $entity_service, $entity_type_service, $notice_service ) {
133
134
		$this->entity_service      = $entity_service;
135
		$this->entity_type_service = $entity_type_service;
136
		$this->notice_service      = $notice_service;
137
138
		$this->log = Wordlift_Log_Service::get_logger( 'Wordlift_Rating_Service' );
139
140
	}
141
142
	/**
143
	 * Set rating for a given entity.
144
	 *
145
	 * @since 3.3.0
146
	 *
147
	 * @param int $post_id The entity post id.
148
	 *
149
	 * @return array An array representing the rating obj.
150
	 */
151
	public function set_rating_for( $post_id ) {
152
153
		// Calculate rating for the given post.
154
		$rating = $this->calculate_rating_for( $post_id );
155
156
		// Store the rating on db as post meta. Please notice that RATING_RAW_SCORE_META_KEY
157
		// is saved on a different meta to allow score sorting. Both meta are managed as unique.
158
		//
159
		// See https://codex.wordpress.org/Function_Reference/update_post_meta
160
		update_post_meta( $post_id, self::RATING_RAW_SCORE_META_KEY, $rating['raw_score'] );
161
		update_post_meta( $post_id, self::RATING_WARNINGS_META_KEY, $rating['warnings'] );
162
163
		$this->log->trace( sprintf( "Rating set for [ post_id :: $post_id ] [ rating :: %s ]", $rating['raw_score'] ) );
164
165
		// Finally returns the rating
166
		return $rating;
167
	}
168
169
170
	/**
171
	 * Get or calculate rating for a given entity
172
	 *
173
	 * @since 3.3.0
174
	 *
175
	 * @param int $post_id      The entity post id.
176
	 * @param     $force_reload $warnings_needed If true, detailed warnings collection is provided with the rating obj.
0 ignored issues
show
Documentation introduced by
The doc-type $force_reload could not be parsed: Unknown type name "$force_reload" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
Bug introduced by
There is no parameter named $warnings_needed. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
177
	 *
178
	 * @return array An array representing the rating obj.
179
	 */
180
	public function get_rating_for( $post_id, $force_reload = false ) {
181
182
		// If forced reload is required or rating is missing.
183
		if ( $force_reload ) {
184
			$this->log->trace( "Force rating reload [ post_id :: $post_id ]" );
185
186
			return $this->set_rating_for( $post_id );
187
		}
188
189
		$current_raw_score = get_post_meta( $post_id, self::RATING_RAW_SCORE_META_KEY, true );
190
191
		if ( ! is_numeric( $current_raw_score ) ) {
192
			$this->log->trace( "Rating missing for [ post_id :: $post_id ] [ current_raw_score :: $current_raw_score ]" );
193
194
			return $this->set_rating_for( $post_id );
195
		}
196
197
		$current_warnings = get_post_meta( $post_id, self::RATING_WARNINGS_META_KEY, true );
198
199
		// Finally return score and warnings
200
		return array(
201
			'raw_score'           => $current_raw_score,
202
			'traffic_light_score' => $this->convert_raw_score_to_traffic_light( $current_raw_score ),
203
			'percentage_score'    => $this->convert_raw_score_to_percentage( $current_raw_score ),
204
			'warnings'            => $current_warnings,
205
		);
206
207
	}
208
209
	/**
210
	 * Calculate rating for a given entity.
211
	 *
212
	 * Rating depends from following criteria:
213
	 *
214
	 * 1. Is the current entity related to at least 1 post?
215
	 * 2. Is the current entity content post not empty?
216
	 * 3. Is the current entity related to at least 1 entity?
217
	 * 4. Is the entity published?
218
	 * 5. There is a a thumbnail associated to the entity?
219
	 * 6. Has the entity a sameas defined?
220
	 * 7. Are all schema.org required metadata compiled?
221
	 *
222
	 * Each positive check means +1 in terms of rating score.
223
	 *
224
	 * @since 3.3.0
225
	 *
226
	 * @param int $post_id The entity post id.
227
	 *
228
	 * @return array An array representing the rating obj.
229
	 */
230
	private function calculate_rating_for( $post_id ) {
231
232
		// If it's not an entity, return.
233
		if ( ! $this->entity_service->is_entity( $post_id ) ) {
234
			return array();
235
		}
236
		// Retrieve the post object.
237
		$post = get_post( $post_id );
238
239
		// Rating value.
240
		$score = 0;
241
242
		// Store warning messages.
243
		$warnings = array();
244
245
		// Is the current entity related to at least 1 post?
246
		( 0 < count( wl_core_get_related_post_ids( $post->ID ) ) ) ?
247
			$score ++ :
248
			array_push( $warnings, __( self::RATING_WARNING_HAS_RELATED_POSTS, 'wordlift' ) );
249
250
		// Is the post content not empty?
251
		( ! empty( $post->post_content ) ) ?
252
			$score ++ :
253
			array_push( $warnings, __( self::RATING_WARNING_HAS_CONTENT_POST, 'wordlift' ) );
254
255
		// Is the current entity related to at least 1 entity?
256
		// Was the current entity already disambiguated?
257
		( 0 < count( wl_core_get_related_entity_ids( $post->ID ) ) ) ?
258
			$score ++ :
259
			array_push( $warnings, __( self::RATING_WARNING_HAS_RELATED_ENTITIES, 'wordlift' ) );
260
261
		// Is the entity published?
262
		( 'publish' === get_post_status( $post->ID ) ) ?
263
			$score ++ :
264
			array_push( $warnings, __( self::RATING_WARNING_IS_PUBLISHED, 'wordlift' ) );
265
266
		// Has a thumbnail?
267
		( has_post_thumbnail( $post->ID ) ) ?
268
			$score ++ :
269
			array_push( $warnings, __( self::RATING_WARNING_HAS_THUMBNAIL, 'wordlift' ) );
270
271
		// Get all post meta keys for the current post
272
		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...
273
274
		$query = $wpdb->prepare(
275
			"SELECT DISTINCT (meta_key) FROM $wpdb->postmeta  WHERE post_id = %d", $post->ID
276
		);
277
278
		// Check intersection between available meta keys and expected ones
279
		// arrays to detect missing values.
280
		$available_meta_keys = $wpdb->get_col( $query );
281
282
		// If each expected key is contained in available keys array ...
283
		( in_array( Wordlift_Schema_Service::FIELD_SAME_AS, $available_meta_keys ) ) ?
284
			$score ++ :
285
			array_push( $warnings, __( self::RATING_WARNING_HAS_SAME_AS, 'wordlift' ) );
286
287
		$schema = $this->entity_type_service->get( $post_id );
288
289
		$expected_meta_keys = ( null === $schema['custom_fields'] ) ?
290
			array() :
291
			array_keys( $schema['custom_fields'] );
292
293
		$intersection = array_intersect( $expected_meta_keys, $available_meta_keys );
294
		// If each expected key is contained in available keys array ...
295
		( count( $intersection ) === count( $expected_meta_keys ) ) ?
296
			$score ++ :
297
			array_push( $warnings, __( self::RATING_WARNING_HAS_COMPLETED_METADATA, 'wordlift' ) );
298
299
		// Finally return score and warnings
300
		return array(
301
			'raw_score'           => $score,
302
			'traffic_light_score' => $this->convert_raw_score_to_traffic_light( $score ),
303
			'percentage_score'    => $this->convert_raw_score_to_percentage( $score ),
304
			'warnings'            => $warnings,
305
		);
306
307
	}
308
309
	/**
310
	 * Get as rating as input and convert in a traffic-light rating
311
	 *
312
	 * @since 3.3.0
313
	 *
314
	 * @param int $score The rating score for a given entity.
315
	 *
316
	 * @return string The input HTML code.
317
	 */
318
	private function convert_raw_score_to_traffic_light( $score ) {
319
		// RATING_MAX : $score = 3 : x
320
		// See http://php.net/manual/en/function.round.php
321
		$rating = round( ( $score * 3 ) / self::RATING_MAX, 0, PHP_ROUND_HALF_UP );
322
323
		// If rating is 0, return 1, otherwise return rating
324
		return ( 0 == $rating ) ? 1 : $rating;
325
326
	}
327
328
	/**
329
	 * Get as rating as input and convert in a traffic-light rating
330
	 *
331
	 * @since 3.3.0
332
	 *
333
	 * @param int $score The rating score for a given entity.
334
	 *
335
	 * @return string The input HTML code.
336
	 */
337
	public function convert_raw_score_to_percentage( $score ) {
338
339
		// RATING_MAX : $score = 100 : x
340
		return round( ( $score * 100 ) / self::RATING_MAX, 0, PHP_ROUND_HALF_UP );
341
	}
342
343
344
	/**
345
	 * Add admin notices for the current entity depending on the current rating.
346
	 *
347
	 * @since 3.3.0
348
	 */
349
	public function in_admin_header() {
350
351
		// Return safely if get_current_screen() is not defined (yet)
352
		if ( false === function_exists( 'get_current_screen' ) ) {
353
			return;
354
		}
355
356
		$screen = get_current_screen();
357
		// If there is any valid screen nothing to do
358
		if ( null === $screen ) {
359
			return;
360
		}
361
362
		// If you're not in the entity post edit page, return.
363
		if ( Wordlift_Entity_Service::TYPE_NAME !== $screen->id ) {
364
			return;
365
		}
366
		// Retrieve the current global post
367
		global $post;
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...
368
		// If it's not an entity, return.
369
		if ( ! $this->entity_service->is_entity( $post->ID ) ) {
370
			return;
371
		}
372
		// Retrieve an updated rating for the current entity
373
		$rating = $this->get_rating_for( $post->ID, true );
374
375
		// If there is at least 1 warning
376
		if ( isset( $rating['warnings'] ) && 0 < count( $rating['warnings'] ) ) {
377
			// TODO - Pass Wordlift_Notice_Service trough the service constructor
378
			$this->notice_service->add_suggestion( $rating['warnings'] );
379
		}
380
381
	}
382
383
}
384