Completed
Push — develop ( d7af47...af6017 )
by David
03:12
created

Wordlift_Entity_Service::get()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Provide entity-related services.
5
 *
6
 * @since 3.1.0
7
 */
8
class Wordlift_Entity_Service {
9
10
	/**
11
	 * The Log service.
12
	 *
13
	 * @since  3.2.0
14
	 * @access private
15
	 * @var \Wordlift_Log_Service $log The Log service.
16
	 */
17
	private $log;
18
19
	/**
20
	 * The UI service.
21
	 *
22
	 * @since  3.2.0
23
	 * @access private
24
	 * @var \Wordlift_UI_Service $ui_service The UI service.
25
	 */
26
	private $ui_service;
27
28
	/**
29
	 * The {@link Wordlift_Relation_Service} instance.
30
	 *
31
	 * @since  3.15.0
32
	 * @access private
33
	 * @var \Wordlift_Relation_Service $relation_service The {@link Wordlift_Relation_Service} instance.
34
	 */
35
	private $relation_service;
36
37
	/**
38
	 * The {@link Wordlift_Entity_Uri_Service} instance.
39
	 *
40
	 * @since  3.16.3
41
	 * @access private
42
	 * @var \Wordlift_Entity_Uri_Service $entity_uri_service The {@link Wordlift_Entity_Uri_Service} instance.
43
	 */
44
	private $entity_uri_service;
45
46
	/**
47
	 * The entity post type name.
48
	 *
49
	 * @since 3.1.0
50
	 */
51
	const TYPE_NAME = 'entity';
52
53
	/**
54
	 * The alternative label meta key.
55
	 *
56
	 * @since 3.2.0
57
	 */
58
	const ALTERNATIVE_LABEL_META_KEY = '_wl_alt_label';
59
60
	/**
61
	 * The alternative label input template.
62
	 *
63
	 * @since 3.2.0
64
	 */
65
	// TODO: this should be moved to a class that deals with HTML code.
66
	const ALTERNATIVE_LABEL_INPUT_TEMPLATE = '<div class="wl-alternative-label">
67
                <label class="screen-reader-text" id="wl-alternative-label-prompt-text" for="wl-alternative-label">Enter alternative label here</label>
68
                <input name="wl_alternative_label[]" size="30" value="%s" id="wl-alternative-label" type="text">
69
                <button class="button wl-delete-button">%s</button>
70
                </div>';
71
72
	/**
73
	 * A singleton instance of the Entity service.
74
	 *
75
	 * @since  3.2.0
76
	 * @access private
77
	 * @var \Wordlift_Entity_Service $instance A singleton instance of the Entity service.
78
	 */
79
	private static $instance;
80
81
	/**
82
	 * Create a Wordlift_Entity_Service instance.
83
	 *
84
	 * @since 3.2.0
85
	 *
86
	 * @param \Wordlift_UI_Service         $ui_service         The UI service.
87
	 * @param \Wordlift_Relation_Service   $relation_service   The {@link Wordlift_Relation_Service} instance.
88
	 * @param \Wordlift_Entity_Uri_Service $entity_uri_service The {@link Wordlift_Entity_Uri_Service} instance.
89
	 */
90 View Code Duplication
	public function __construct( $ui_service, $relation_service, $entity_uri_service ) {
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...
91
92
		$this->log = Wordlift_Log_Service::get_logger( 'Wordlift_Entity_Service' );
93
94
		$this->ui_service         = $ui_service;
95
		$this->relation_service   = $relation_service;
96
		$this->entity_uri_service = $entity_uri_service;
97
98
		// Set the singleton instance.
99
		self::$instance = $this;
100
	}
101
102
	/**
103
	 * Get the singleton instance of the Entity service.
104
	 *
105
	 * @since 3.2.0
106
	 * @return \Wordlift_Entity_Service The singleton instance of the Entity service.
107
	 */
108
	public static function get_instance() {
109
110
		return self::$instance;
111
	}
112
113
	/**
114
	 * Determines whether a post is an entity or not. Entity is in this context
115
	 * something which is not an article.
116
	 *
117
	 * @since 3.1.0
118
	 *
119
	 * @param int $post_id A post id.
120
	 *
121
	 * @return bool Return true if the post is an entity otherwise false.
122
	 */
123
	public function is_entity( $post_id ) {
124
125
		$terms = wp_get_object_terms( $post_id, Wordlift_Entity_Types_Taxonomy_Service::TAXONOMY_NAME );
126
127
		if ( 0 === count( $terms ) ) {
128
			return false;
129
		}
130
131
		// We don't consider an `article` to be an entity.
132
		if ( 'article' !== $terms[0]->slug ) {
133
			return true;
134
		}
135
136
		return false;
137
	}
138
139
	/**
140
	 * Get the proper classification scope for a given entity post
141
	 *
142
	 * @since 3.5.0
143
	 *
144
	 * @param integer $post_id An entity post id.
145
	 *
146
	 * @param string  $default The default classification scope, `what` if not
147
	 *                         provided.
148
	 *
149
	 * @return string Returns a classification scope (e.g. 'what').
150
	 */
151
	public function get_classification_scope_for( $post_id, $default = WL_WHAT_RELATION ) {
152
153
		if ( false === $this->is_entity( $post_id ) ) {
154
			return $default;
155
		}
156
157
		// Retrieve the entity type
158
		$entity_type_arr = Wordlift_Entity_Type_Service::get_instance()->get( $post_id );
159
		$entity_type     = str_replace( 'wl-', '', $entity_type_arr['css_class'] );
160
		// Retrieve classification boxes configuration
161
		$classification_boxes = unserialize( WL_CORE_POST_CLASSIFICATION_BOXES );
162
		foreach ( $classification_boxes as $cb ) {
163
			if ( in_array( $entity_type, $cb['registeredTypes'] ) ) {
164
				return $cb['id'];
165
			}
166
		}
167
168
		return $default;
169
	}
170
171
	/**
172
	 * Check whether a {@link WP_Post} is used.
173
	 *
174
	 * @param int $post_id The {@link WP_Post}'s id.
175
	 *
176
	 * @return bool|null Null if it's not an entity, otherwise true if it's used.
177
	 */
178
	public function is_used( $post_id ) {
179
180
		if ( false === $this->is_entity( $post_id ) ) {
181
			return null;
182
		}
183
		// Retrieve the post
184
		$entity = get_post( $post_id );
185
186
		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...
187
		// Retrieve Wordlift relation instances table name
188
		$table_name = wl_core_get_relation_instances_table_name();
189
190
		// Check is it's referenced / related to another post / entity
191
		$stmt = $wpdb->prepare(
192
			"SELECT COUNT(*) FROM $table_name WHERE  object_id = %d",
193
			$entity->ID
194
		);
195
196
		// Perform the query
197
		$relation_instances = (int) $wpdb->get_var( $stmt );
198
		// If there is at least one relation instance for the current entity, then it's used
199
		if ( 0 < $relation_instances ) {
200
			return true;
201
		}
202
203
		// Check if the entity uri is used as meta_value
204
		$stmt = $wpdb->prepare(
205
			"SELECT COUNT(*) FROM $wpdb->postmeta WHERE post_id != %d AND meta_value = %s",
206
			$entity->ID,
207
			wl_get_entity_uri( $entity->ID )
0 ignored issues
show
Deprecated Code introduced by
The function wl_get_entity_uri() has been deprecated with message: use Wordlift_Entity_Service::get_instance()->get_uri( $post_id )

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
208
		);
209
		// Perform the query
210
		$meta_instances = (int) $wpdb->get_var( $stmt );
211
212
		// If there is at least one meta that refers the current entity uri, then current entity is used
213
		if ( 0 < $meta_instances ) {
214
			return true;
215
		}
216
217
		// If we are here, it means the current entity is not used at the moment
218
		return false;
219
	}
220
221
	/**
222
	 * Find entity posts by the entity URI. Entity as searched by their entity URI or same as.
223
	 *
224
	 * @since      3.16.3 deprecated in favor of Wordlift_Entity_Uri_Service->get_entity( $uri );
225
	 * @since      3.2.0
226
	 *
227
	 * @deprecated in favor of Wordlift_Entity_Uri_Service->get_entity( $uri );
228
	 *
229
	 * @param string $uri The entity URI.
230
	 *
231
	 * @return WP_Post|null A WP_Post instance or null if not found.
232
	 */
233
	public function get_entity_post_by_uri( $uri ) {
234
235
		return $this->entity_uri_service->get_entity( $uri );
236
	}
237
238
	/**
239
	 * Fires once a post has been saved. This function uses the $_REQUEST, therefore
240
	 * we check that the post we're saving is the current post.
241
	 *
242
	 * @see   https://github.com/insideout10/wordlift-plugin/issues/363
243
	 *
244
	 * @since 3.2.0
245
	 *
246
	 * @param int     $post_id Post ID.
247
	 * @param WP_Post $post    Post object.
248
	 * @param bool    $update  Whether this is an existing post being updated or not.
249
	 */
250
	public function save_post( $post_id, $post, $update ) {
0 ignored issues
show
Unused Code introduced by
The parameter $update is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Coding Style introduced by
save_post uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
251
252
		// Avoid doing anything if post is autosave or a revision.
253
		if ( wp_is_post_autosave( $post ) || wp_is_post_revision( $post ) ) {
254
			return;
255
		}
256
257
		// We're setting the alternative label that have been provided via the UI
258
		// (in fact we're using $_REQUEST), while save_post may be also called
259
		// programmatically by some other function: we need to check therefore if
260
		// the $post_id in the save_post call matches the post id set in the request.
261
		//
262
		// If this is not the current post being saved or if it's not an entity, return.
263
		if ( ! isset( $_REQUEST['post_ID'] ) || $_REQUEST['post_ID'] != $post_id || ! $this->is_entity( $post_id ) ) {
264
			return;
265
		}
266
267
		// Get the alt labels from the request (or empty array).
268
		$alt_labels = isset( $_REQUEST['wl_alternative_label'] ) ? $_REQUEST['wl_alternative_label'] : array();
269
270
		// Set the alternative labels.
271
		$this->set_alternative_labels( $post_id, $alt_labels );
272
273
	}
274
275
	/**
276
	 * Set the alternative labels.
277
	 *
278
	 * @since 3.2.0
279
	 *
280
	 * @param int   $post_id    The post id.
281
	 * @param array $alt_labels An array of labels.
282
	 */
283
	public function set_alternative_labels( $post_id, $alt_labels ) {
284
285
		// Force $alt_labels to be an array
286
		if ( ! is_array( $alt_labels ) ) {
287
			$alt_labels = array( $alt_labels );
288
		}
289
290
		$this->log->debug( "Setting alternative labels [ post id :: $post_id ][ alt labels :: " . implode( ',', $alt_labels ) . " ]" );
291
292
		// Delete all the existing alternate labels.
293
		delete_post_meta( $post_id, self::ALTERNATIVE_LABEL_META_KEY );
294
295
		// Set the alternative labels.
296
		foreach ( $alt_labels as $alt_label ) {
297
			if ( ! empty( $alt_label ) ) {
298
				add_post_meta( $post_id, self::ALTERNATIVE_LABEL_META_KEY, $alt_label );
299
			}
300
		}
301
302
	}
303
304
	/**
305
	 * Retrieve the alternate labels.
306
	 *
307
	 * @since 3.2.0
308
	 *
309
	 * @param int $post_id Post id.
310
	 *
311
	 * @return mixed An array  of alternative labels.
312
	 */
313
	public function get_alternative_labels( $post_id ) {
314
315
		return get_post_meta( $post_id, self::ALTERNATIVE_LABEL_META_KEY );
316
	}
317
318
	/**
319
	 * Retrieve the labels for an entity, i.e. the title + the synonyms.
320
	 *
321
	 * @since 3.12.0
322
	 *
323
	 * @param int $post_id The entity {@link WP_Post} id.
324
	 *
325
	 * @return array An array with the entity title and labels.
326
	 */
327
	public function get_labels( $post_id ) {
328
329
		return array_merge( (array) get_the_title( $post_id ), $this->get_alternative_labels( $post_id ) );
330
	}
331
332
	/**
333
	 * Fires before the permalink field in the edit form (this event is available in WP from 4.1.0).
334
	 *
335
	 * @since 3.2.0
336
	 *
337
	 * @param WP_Post $post Post object.
338
	 */
339
	public function edit_form_before_permalink( $post ) {
340
341
		// If it's not an entity, return.
342
		if ( ! $this->is_entity( $post->ID ) ) {
343
			return;
344
		}
345
346
		// Print the input template.
347
		$this->ui_service->print_template( 'wl-tmpl-alternative-label-input', $this->get_alternative_label_input() );
348
349
		// Print all the currently set alternative labels.
350
		foreach ( $this->get_alternative_labels( $post->ID ) as $alt_label ) {
351
352
			echo $this->get_alternative_label_input( $alt_label );
353
354
		};
355
356
		// Print the button.
357
		$this->ui_service->print_button( 'wl-add-alternative-labels-button', __( 'Add more titles', 'wordlift' ) );
358
359
	}
360
361
	/**
362
	 * Get the URI for the entity with the specified post id.
363
	 *
364
	 * @since 3.6.0
365
	 *
366
	 * @param int $post_id The entity post id.
367
	 *
368
	 * @return null|string The entity URI or NULL if not found or the dataset URI is not configured.
369
	 */
370
	public function get_uri( $post_id ) {
371
372
		// If a null is given, nothing to do
373
		if ( null == $post_id ) {
374
			return null;
375
		}
376
377
		$uri = get_post_meta( $post_id, WL_ENTITY_URL_META_NAME, true );
378
379
		// If the dataset uri is not properly configured, null is returned
380
		if ( '' === wl_configuration_get_redlink_dataset_uri() ) {
0 ignored issues
show
Deprecated Code introduced by
The function wl_configuration_get_redlink_dataset_uri() has been deprecated with message: use Wordlift_Configuration_Service::get_instance()->get_dataset_uri();

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
381
			return null;
382
		}
383
384
		// Set the URI if it isn't set yet.
385
		$post_status = get_post_status( $post_id );
386
		if ( empty( $uri ) && 'auto-draft' !== $post_status && 'revision' !== $post_status ) {
387
			$uri = wl_build_entity_uri( $post_id );
388
			wl_set_entity_uri( $post_id, $uri );
389
		}
390
391
		return $uri;
392
	}
393
394
395
	/**
396
	 * Get the alternative label input HTML code.
397
	 *
398
	 * @since 3.2.0
399
	 *
400
	 * @param string $value The input value.
401
	 *
402
	 * @return string The input HTML code.
403
	 */
404
	private function get_alternative_label_input( $value = '' ) {
405
406
		return sprintf( self::ALTERNATIVE_LABEL_INPUT_TEMPLATE, esc_attr( $value ), __( 'Delete', 'wordlift' ) );
407
	}
408
409
	/**
410
	 * Get the number of entity posts published in this blog.
411
	 *
412
	 * @since 3.6.0
413
	 *
414
	 * @return int The number of published entity posts.
415
	 */
416
	public function count() {
417
418
		$posts = get_posts( $this->add_criterias( array(
419
			'post_status' => 'any',
420
			'numberposts' => - 1,
421
		) ) );
422
423
		return count( $posts );
424
	}
425
426
	/**
427
	 * Add the entity filtering criterias to the arguments for a `get_posts`
428
	 * call.
429
	 *
430
	 * @since 3.15.0
431
	 *
432
	 * @param array $args The arguments for a `get_posts` call.
433
	 *
434
	 * @return array The arguments for a `get_posts` call.
435
	 */
436 View Code Duplication
	public static function add_criterias( $args ) {
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...
437
438
		// Build an optimal tax-query.
439
		$tax_query = array(
440
			'relation' => 'AND',
441
			array(
442
				'taxonomy' => Wordlift_Entity_Types_Taxonomy_Service::TAXONOMY_NAME,
443
				'operator' => 'EXISTS',
444
			),
445
			array(
446
				'taxonomy' => Wordlift_Entity_Types_Taxonomy_Service::TAXONOMY_NAME,
447
				'field'    => 'slug',
448
				'terms'    => 'article',
449
				'operator' => 'NOT IN',
450
			),
451
		);
452
453
		return $args + array(
454
				'post_type' => Wordlift_Entity_Service::valid_entity_post_types(),
455
				// Since 3.17.0: should this be faster?
456
				'tax_query' => $tax_query,
457
				//				'tax_query' => array(
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
458
				//					array(
459
				//						'taxonomy' => Wordlift_Entity_Types_Taxonomy_Service::TAXONOMY_NAME,
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
460
				//						'terms'    => self::get_entity_terms(),
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
461
				//					),
462
				//				),
463
			);
464
	}
465
466
//	/**
467
//	 * Get the entity terms IDs which represent an entity.
468
//	 *
469
//	 * @since 3.17.0 deprecated.
470
//	 * @since 3.15.0
471
//	 *
472
//	 * @deprecated
473
//	 * @return array An array of terms' ids.
474
//	 */
475
//	public static function get_entity_terms() {
476
//
477
//		$terms = get_terms( Wordlift_Entity_Types_Taxonomy_Service::TAXONOMY_NAME, array(
478
//			'hide_empty' => false,
479
//			// Because of #334 (and the AAM plugin) we changed fields from 'id=>slug' to 'all'.
480
//			// An issue has been opened with the AAM plugin author as well.
481
//			//
482
//			// see https://github.com/insideout10/wordlift-plugin/issues/334
483
//			// see https://wordpress.org/support/topic/idslug-not-working-anymore?replies=1#post-8806863
484
//			'fields'     => 'all',
485
//		) );
486
//
487
//		return array_map( function ( $term ) {
488
//			return $term->term_id;
489
//		}, array_filter( $terms, function ( $term ) {
490
//			return 'article' !== $term->slug;
491
//		} ) );
492
//	}
493
494
	/**
495
	 * Create a new entity.
496
	 *
497
	 * @since 3.9.0
498
	 *
499
	 * @param string $name     The entity name.
500
	 * @param string $type_uri The entity's type URI.
501
	 * @param null   $logo     The entity logo id (or NULL if none).
502
	 * @param string $status   The post status, by default 'publish'.
503
	 *
504
	 * @return int|WP_Error The entity post id or a {@link WP_Error} in case the `wp_insert_post` call fails.
505
	 */
506
	public function create( $name, $type_uri, $logo = null, $status = 'publish' ) {
507
508
		// Create an entity for the publisher.
509
		$post_id = wp_insert_post( array(
510
			'post_type'    => self::TYPE_NAME,
511
			'post_title'   => $name,
512
			'post_status'  => $status,
513
			'post_content' => '',
514
		) );
515
516
		// Return the error if any.
517
		if ( is_wp_error( $post_id ) ) {
518
			return $post_id;
519
		}
520
521
		// Set the entity logo.
522
		if ( $logo && is_numeric( $logo ) ) {
523
			set_post_thumbnail( $post_id, $logo );
524
		}
525
526
		// Set the entity type.
527
		Wordlift_Entity_Type_Service::get_instance()->set( $post_id, $type_uri );
528
529
		return $post_id;
530
	}
531
532
	/**
533
	 * Get the entities related to the one with the specified id. By default only
534
	 * published entities will be returned.
535
	 *
536
	 * @since 3.10.0
537
	 *
538
	 * @param int    $id          The post id.
539
	 * @param string $post_status The target post status (default = publish).
540
	 *
541
	 * @return array An array of post ids.
542
	 */
543
	public function get_related_entities( $id, $post_status = 'publish' ) {
544
545
		return $this->relation_service->get_objects( $id, 'ids', null, $post_status );
546
	}
547
548
	/**
549
	 * Get the list of entities.
550
	 *
551
	 * @since 3.12.2
552
	 *
553
	 * @param array $params Custom parameters for WordPress' own {@link get_posts} function.
554
	 *
555
	 * @return array An array of entity posts.
556
	 */
557
	public function get( $params = array() ) {
558
559
		// Set the defaults.
560
		$defaults = array( 'post_type' => 'entity' );
561
562
		// Merge the defaults with the provided parameters.
563
		$args = wp_parse_args( $params, $defaults );
564
565
		// Call the `get_posts` function.
566
		return get_posts( $args );
567
	}
568
569
	/**
570
	 * The list of post type names which can be used for entities
571
	 *
572
	 * Criteria is that the post type is public. The list of valid post types
573
	 * can be overridden with a filter.
574
	 *
575
	 * @since 3.15.0
576
	 *
577
	 * @return array Array containing the names of the valid post types.
578
	 */
579
	static function valid_entity_post_types() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
580
581
		// Ignore builtins in the call to avoid getting attachments.
582
		$post_types = array( 'post', 'page', self::TYPE_NAME );
583
584
		return apply_filters( 'wl_valid_entity_post_types', $post_types );
585
	}
586
587
}
588