Completed
Push — develop ( 946071...2a5344 )
by David
03:25
created

Wordlift_User_Service::get_uri()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 17
rs 9.7
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Manage user-related functions. This class receives notifications when a post is created/updated and pushes the author's
5
 * data to the triple store. It does NOT receive notification when a user is create/updated because we don't want to send
6
 * to the triple stores users that eventually do not write posts (therefore if user data change, the triple store is updated
7
 * only when the user creates/updates a new post).
8
 *
9
 * @since 3.1.7
10
 */
11
class Wordlift_User_Service {
12
13
	/**
14
	 * The meta key where the user's URI is stored.
15
	 *
16
	 * @since 3.1.7
17
	 */
18
	const URI_META_KEY = '_wl_uri';
19
20
	/**
21
	 * The user meta key where the deny entity edit flag is stored.
22
	 *
23
	 * @since 3.14.0
24
	 */
25
	const DENY_ENTITY_CREATE_META_KEY = '_wl_deny_entity_create';
26
27
	/**
28
	 * The meta key holding the entity id representing a {@link WP_User}.
29
	 *
30
	 * @since 3.14.0
31
	 */
32
	const ENTITY_META_KEY = '_wl_entity';
33
34
	/**
35
	 * The Log service.
36
	 *
37
	 * @since  3.1.7
38
	 * @access private
39
	 * @var \Wordlift_Log_Service $log_service The Log service.
40
	 */
41
	private $log_service;
42
43
	/**
44
	 * The singleton instance of the User service.
45
	 *
46
	 * @since  3.1.7
47
	 * @access private
48
	 * @var \Wordlift_User_Service $user_service The singleton instance of the User service.
49
	 */
50
	private static $instance;
51
52
	/**
53
	 * The {@link Wordlift_Sparql_Service} instance.
54
	 *
55
	 * @since  3.18.0
56
	 * @access private
57
	 * @var \Wordlift_Sparql_Service $sparql_service The {@link Wordlift_Sparql_Service} instance.
58
	 */
59
	private $sparql_service;
60
61
	/**
62
	 * The Entity service.
63
	 *
64
	 * @since  3.18.0
65
	 * @access private
66
	 * @var \Wordlift_Entity_Service $entity_service The Entity service.
67
	 */
68
	private $entity_service;
69
70
	/**
71
	 * Create an instance of the User service.
72
	 *
73
	 * @since 3.1.7
74
	 *
75
	 * @param \Wordlift_Sparql_Service $sparql_service The {@link Wordlift_Sparql_Service} instance.
76
	 * @param \Wordlift_Entity_Service $entity_service The {@link Wordlift_Entity_Service} instance.
77
	 */
78
	public function __construct( $sparql_service, $entity_service ) {
79
80
		$this->log_service = Wordlift_Log_Service::get_logger( 'Wordlift_User_Service' );
81
82
		self::$instance = $this;
83
84
		$this->sparql_service = $sparql_service;
85
		$this->entity_service = $entity_service;
86
87
		add_filter( 'user_has_cap', array( $this, 'has_cap' ), 10, 3 );
88
	}
89
90
	/**
91
	 * Get the singleton instance of the User service.
92
	 *
93
	 * @since 3.1.7
94
	 * @return \Wordlift_User_Service The singleton instance of the User service.
95
	 */
96
	public static function get_instance() {
97
98
		return self::$instance;
99
	}
100
101
	/**
102
	 * Get the URI for a user.
103
	 *
104
	 * @since 3.1.7
105
	 *
106
	 * @param int $user_id The user id
107
	 *
108
	 * @return false|string The user's URI or false in case of failure.
109
	 */
110
	public function get_uri( $user_id ) {
111
112
		// Try to get the URI stored in the user's meta and return it if available.
113
		if ( false !== ( $user_uri = $this->_get_uri( $user_id ) ) ) {
114
			return $user_uri;
115
		}
116
117
		// Try to build an URI, return false in case of failure.
118
		if ( false === ( $user_uri = $this->_build_uri( $user_id ) ) ) {
119
			return false;
120
		}
121
122
		// Store the URI for future requests (we need a "permanent" URI).
123
		$this->_set_uri( $user_id, $user_uri );
124
125
		return $user_uri;
126
	}
127
128
	/**
129
	 * Set the `id` of the entity representing a {@link WP_User}.
130
	 *
131
	 * If the `id` is set to 0 (or less) then the meta is deleted.
132
	 *
133
	 * @since 3.14.0
134
	 *
135
	 * @param int $user_id The {@link WP_User}.
136
	 * @param int $value   The entity {@link WP_Post} `id`.
137
	 *
138
	 * @return bool|int  Meta ID if the key didn't exist, true on successful update, false on failure.
139
	 */
140
	public function set_entity( $user_id, $value ) {
141
142
		return 0 < $value
143
			? update_user_meta( $user_id, self::ENTITY_META_KEY, $value )
144
			: delete_user_meta( $user_id, self::ENTITY_META_KEY );
145
	}
146
147
	/**
148
	 * Get the {@link WP_Post} `id` of the entity representing a {@link WP_User}.
149
	 *
150
	 * @since 3.14.0
151
	 *
152
	 * @param int $user_id The {@link WP_User}'s `id`.
153
	 *
154
	 * @return string|false The entity {@link WP_Post} `id` or an empty string if not set or false if the object id is invalid
155
	 */
156
	public function get_entity( $user_id ) {
157
158
		return get_user_meta( $user_id, self::ENTITY_META_KEY, true );
159
	}
160
161
	/**
162
	 * Get the user's URI stored in the user's meta.
163
	 *
164
	 * @since 3.1.7
165
	 *
166
	 * @param int $user_id The user id.
167
	 *
168
	 * @return false|string The user's URI or false if not found.
169
	 */
170
	private function _get_uri( $user_id ) {
171
172
		$user_uri = get_user_meta( $user_id, self::URI_META_KEY, true );
173
174
		if ( empty( $user_uri ) ) {
175
			return false;
176
		}
177
178
		return $user_uri;
179
	}
180
181
	/**
182
	 * Build an URI for a user.
183
	 *
184
	 * @since 3.1.7
185
	 *
186
	 * @param int $user_id The user's id.
187
	 *
188
	 * @return false|string The user's URI or false in case of failure.
189
	 */
190
	private function _build_uri( $user_id ) {
191
192
		// Get the user, return false in case of failure.
193
		if ( false === ( $user = get_userdata( $user_id ) ) ) {
194
			return false;
195
		};
196
197
		// If the nicename is not set, return a failure.
198
		if ( empty( $user->user_nicename ) ) {
199
			return false;
200
		}
201
202
		return wl_configuration_get_redlink_dataset_uri() . "/user/$user->user_nicename";
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...
203
	}
204
205
	/**
206
	 * Store the URI in user's meta.
207
	 *
208
	 * @since 3.1.7
209
	 *
210
	 * @param int    $user_id  The user's id.
211
	 * @param string $user_uri The user's uri.
212
	 *
213
	 * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
214
	 */
215
	private function _set_uri( $user_id, $user_uri ) {
216
217
		return update_user_meta( $user_id, self::URI_META_KEY, $user_uri );
218
	}
219
220
//	/**
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% 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...
221
//	 * Get the delete query.
222
//	 *
223
//	 * @since 3.1.7
224
//	 *
225
//	 * @param int $user_id The user id.
226
//	 *
227
//	 * @return false|string The delete query or false in case of failure.
228
//	 */
229
//	private function get_delete_query( $user_id ) {
230
//
231
//		// Get the URI, return if there's none.
232
//		if ( false === ( $user_uri = $this->get_uri( $user_id ) ) ) {
233
//			return false;
234
//		}
235
//
236
//		// Build the delete query.
237
//		$query = Wordlift_Query_Builder::new_instance()->delete()
238
//									   ->statement( $user_uri, Wordlift_Query_Builder::RDFS_TYPE_URI, '?o' )
239
//									   ->build()
240
//				 . Wordlift_Query_Builder::new_instance()->delete()
241
//										 ->statement( $user_uri, Wordlift_Query_Builder::RDFS_LABEL_URI, '?o' )
242
//										 ->build()
243
//				 . Wordlift_Query_Builder::new_instance()->delete()
244
//										 ->statement( $user_uri, Wordlift_Query_Builder::SCHEMA_GIVEN_NAME_URI, '?o' )
245
//										 ->build()
246
//				 . Wordlift_Query_Builder::new_instance()->delete()
247
//										 ->statement( $user_uri, Wordlift_Query_Builder::SCHEMA_FAMILY_NAME_URI, '?o' )
248
//										 ->build()
249
//				 . Wordlift_Query_Builder::new_instance()->delete()
250
//										 ->statement( $user_uri, Wordlift_Query_Builder::SCHEMA_URL_URI, '?o' )
251
//										 ->build();
252
//
253
//		return $query;
254
//	}
255
256
//	/**
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% 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...
257
//	 * Get the insert query.
258
//	 *
259
//	 * @since 3.1.7
260
//	 *
261
//	 * @param int $user_id The user id.
262
//	 *
263
//	 * @return false|string The insert query or false in case of failure.
264
//	 */
265
//	private function get_insert_query( $user_id ) {
266
//
267
//		// Get the URI, return if there's none.
268
//		if ( false === ( $user_uri = $this->get_uri( $user_id ) ) ) {
269
//			return false;
270
//		}
271
//
272
//		// Try to get the user data, in case of failure return false.
273
//		if ( false === ( $user = get_userdata( $user_id ) ) ) {
274
//			return false;
275
//		};
276
//
277
//		// Build the insert query.
278
//		$query = Wordlift_Query_Builder::new_instance()
279
//									   ->insert()
280
//									   ->statement( $user_uri, Wordlift_Query_Builder::RDFS_TYPE_URI, Wordlift_Query_Builder::SCHEMA_PERSON_URI )
281
//									   ->statement( $user_uri, Wordlift_Query_Builder::RDFS_LABEL_URI, $user->display_name )
282
//									   ->statement( $user_uri, Wordlift_Query_Builder::SCHEMA_GIVEN_NAME_URI, $user->user_firstname )
283
//									   ->statement( $user_uri, Wordlift_Query_Builder::SCHEMA_FAMILY_NAME_URI, $user->user_lastname )
284
//									   ->statement( $user_uri, Wordlift_Query_Builder::SCHEMA_URL_URI, ( ! empty( $user->user_url ) ? $user->user_url : get_author_posts_url( $user_id ) ) )
285
//									   ->build();
286
//
287
//		return $query;
288
//	}
289
290
	/**
291
	 * Mark an editor user as denied from editing entities.
292
	 * Does nothing if the user is not an editor
293
	 *
294
	 * @since 3.14.0
295
	 *
296
	 * @param integer $user_id The ID of the user
297
	 */
298
	public function deny_editor_entity_create( $user_id ) {
299
300
		// Bail out if the user is not an editor.
301
		if ( ! $this->is_editor( $user_id ) ) {
302
			return;
303
		}
304
305
		// The user explicitly do not have the capability.
306
		update_user_option( $user_id, self::DENY_ENTITY_CREATE_META_KEY, 'yes' );
307
308
	}
309
310
	/**
311
	 * Remove the "deny entity editing" mark from an editor user.
312
	 * Does nothing if the user is not an editor
313
	 *
314
	 * @since 3.14.0
315
	 *
316
	 * @param integer $user_id The ID of the user
317
	 */
318
	public function allow_editor_entity_create( $user_id ) {
319
320
		// Bail out if the user is not an editor.
321
		if ( ! $this->is_editor( $user_id ) ) {
322
			return;
323
		}
324
325
		// The user explicitly do not have the capability.
326
		delete_user_option( $user_id, self::DENY_ENTITY_CREATE_META_KEY );
327
328
	}
329
330
	/**
331
	 * Get whether the 'deny editor entity editing' flag is set.
332
	 *
333
	 * @since 3.14.0
334
	 *
335
	 * @param int $user_id The {@link WP_User} `id`.
336
	 *
337
	 * @return int bool True if editing is denied otherwise false.
338
	 */
339
	public function is_deny_editor_entity_create( $user_id ) {
340
341
		return 'yes' === get_user_option( self::DENY_ENTITY_CREATE_META_KEY, $user_id );
342
	}
343
344
	/**
345
	 * Check whether the {@link WP_User} with the specified `id` is an editor,
346
	 * i.e. has the `editor` role.
347
	 *
348
	 * @since 3.14.0
349
	 *
350
	 * @param int $user_id The {@link WP_User} `id`.
351
	 *
352
	 * @return bool True if the {@link WP_User} is an editor otherwise false.
353
	 */
354
	public function is_editor( $user_id ) {
355
356
		// Get the user.
357
		$user = get_user_by( 'id', $user_id );
358
359
		// Return true, if the user is found and has the `editor` role.
360
		return is_a( $user, 'WP_User' ) && in_array( 'editor', (array) $user->roles );
361
	}
362
363
	/**
364
	 * Check if an editor can create entities.
365
	 *
366
	 * @since 3.14.0
367
	 *
368
	 * @param int $user_id The user id of the user being checked.
369
	 *
370
	 * @return bool    false if it is an editor that is denied from edit entities, true otherwise.
371
	 */
372
	public function editor_can_create_entities( $user_id ) {
373
374
		// Return true if not an editor.
375
		if ( ! $this->is_editor( $user_id ) ) {
376
			return true;
377
		}
378
379
		// Check if the user explicitly denied.
380
		return ! $this->is_deny_editor_entity_create( $user_id );
381
	}
382
383
	/**
384
	 * Filter capabilities of user.
385
	 *
386
	 * Deny the capability of managing and editing entities for some users.
387
	 *
388
	 * @since 3.14.0
389
	 *
390
	 * @param array $allcaps All the capabilities of the user
391
	 * @param array $cap     [0] Required capability
392
	 * @param array $args    [0] Requested capability
393
	 *                       [1] User ID
394
	 *                       [2] Associated object ID
395
	 *
396
	 * @return array The capabilities array.
397
	 */
398
	public function has_cap( $allcaps, $cap, $args ) {
399
		/*
400
		 * For entity management/editing related capabilities
401
		 * check that an editor was not explicitly denied (in user profile)
402
		 * the capability.
403
		 */
404
405
		/*
406
		 * Need protection against the case of edit_user and likes which do not
407
		 * require a capability, just request one.
408
		 */
409
		if ( empty( $cap ) || ! isset( $cap[0] ) ) {
410
			return $allcaps;
411
		}
412
413
		if (
414
			( 'edit_wordlift_entity' === $cap[0] ) ||
415
			( 'edit_wordlift_entities' === $cap[0] ) ||
416
			( 'edit_others_wordlift_entities' === $cap[0] ) ||
417
			( 'publish_wordlift_entities' === $cap[0] ) ||
418
			( 'read_private_wordlift_entities' === $cap[0] ) ||
419
			( 'delete_wordlift_entity' === $cap[0] ) ||
420
			( 'delete_wordlift_entities' === $cap[0] ) ||
421
			( 'delete_others_wordlift_entities' === $cap[0] ) ||
422
			( 'delete_published_wordlift_entities' === $cap[0] ) ||
423
			( 'delete_private_wordlift_entities' === $cap[0] )
424
		) {
425
			$user_id = $args[1];
426
427
			if ( ! $this->editor_can_create_entities( $user_id ) ) {
428
				$allcaps[ $cap[0] ] = false;
429
			}
430
		}
431
432
		return $allcaps;
433
	}
434
435
	/**
436
	 * Hook on update user meta to check if the user author has changed.
437
	 * If so we need to execute sparql query that will update all user posts author triple.
438
	 *
439
	 * @since   3.18.0
440
	 *
441
	 * @param   null   $null
442
	 * @param   int    $object_id  The user ID.
443
	 * @param   string $meta_key   The meta key name.
444
	 * @param   mixed  $meta_value Meta value.
445
	 * @param   mixed  $prev_value The previous metadata value.
446
	 *
447
	 * @return  null Null if the `meta_key` is not `Wordlift_User_Service::ENTITY_META_KEY`
448
	 *                or if the author has not changed.
449
	 */
450
	public function update_user_metadata( $null, $object_id, $meta_key, $meta_value, $prev_value ) {
0 ignored issues
show
Unused Code introduced by
The parameter $null 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...
Unused Code introduced by
The parameter $prev_value 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...
451
		// Bail if the meta key is not the author meta.
452
		if ( $meta_key !== Wordlift_User_Service::ENTITY_META_KEY ) {
453
			return null;
454
		}
455
456
		// Check whether the user is associated with any of the existing publishers/
457
		$entity_id = $this->get_entity( $object_id );
458
459
		if ( false === $entity_id ) {
460
			// An error occurred.
461
			$this->log_service->error( "An error occurred: entity_id can't be false." );
462
463
			return;
464
		}
465
466
		// Get the old uri if the entity is set..
467
		$old_uri = ! empty( $entity_id )
468
			? $this->entity_service->get_uri( $entity_id )
469
			: $this->get_uri( $object_id );
470
471
		// Get the new user uri's.
472
		$new_uri = $this->entity_service->get_uri( $meta_value );
473
474
		// Bail if the uri is the same.
475
		if ( $old_uri === $new_uri ) {
476
			return null;
477
		}
478
479
		$this->update_author( $old_uri, $new_uri );
0 ignored issues
show
Security Bug introduced by
It seems like $old_uri defined by !empty($entity_id) ? $th...is->get_uri($object_id) on line 467 can also be of type false; however, Wordlift_User_Service::update_author() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
480
	}
481
482
	/**
483
	 * Hook on delete user meta to execute sparql query
484
	 * that will update all user posts author triple.
485
	 *
486
	 * @since   3.18.0
487
	 *
488
	 * @param   null   $null
489
	 * @param   int    $object_id   The user ID.
490
	 * @param   string $meta_key    The meta key name.
491
	 * @param   mixed  $meta_value  Meta value.
492
	 * @param   bool   $delete_all  Whether to delete the matching metadata entries
493
	 *                              for all objects.
494
	 *
495
	 * @return  null Null if the `meta_key` is not `Wordlift_User_Service::ENTITY_META_KEY`
496
	 *               or if the author has not changed.
497
	 */
498
	public function delete_user_metadata( $null, $object_id, $meta_key, $meta_value, $delete_all ) {
0 ignored issues
show
Unused Code introduced by
The parameter $null 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...
Unused Code introduced by
The parameter $meta_value 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...
Unused Code introduced by
The parameter $delete_all 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...
499
		// Bail if the meta key is not the author meta.
500
		if ( $meta_key !== Wordlift_User_Service::ENTITY_META_KEY ) {
501
			return null;
502
		}
503
504
		// Check whether the user is associated with any of the existing publishers/
505
		$entity_id = $this->get_entity( $object_id );
506
507
		if ( false === $entity_id ) {
508
			// An error occurred.
509
			$this->log_service->error( "An error occurred: entity_id can't be false." );
510
511
			return;
512
		}
513
514
		// Get the old uri if the entity is set.
515
		$old_uri = $this->entity_service->get_uri( $entity_id );
516
517
		$new_uri = $this->get_uri( $object_id );
518
519
		$this->update_author( $old_uri, $new_uri );
0 ignored issues
show
Security Bug introduced by
It seems like $new_uri defined by $this->get_uri($object_id) on line 517 can also be of type false; however, Wordlift_User_Service::update_author() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
520
521
	}
522
523
	/**
524
	 * Update the schema:author when the user author is changed.
525
	 *
526
	 * @since   3.18.0
527
	 *
528
	 * @param   string $old_uri The old uri to remove.
529
	 * @param   string $new_uri The new uri to add.
530
	 */
531
	private function update_author( $old_uri, $new_uri ) {
532
		// Bail in case one of the uris is empty.
533
		if ( empty( $old_uri ) || empty( $new_uri ) ) {
534
			// An error occurred.
535
			$this->log_service->error( "An error occurred: old_uri and/or new_uri can't be null." );
536
537
			return;
538
		}
539
540
		// Build the update query.
541
		$query = sprintf(
542
			'DELETE { ?s <%1$s> <%2$s> } INSERT { ?s <%1$s> <%3$s> } WHERE { ?s <%1$s> <%2$s> }',
543
			// Schema:author triple.
544
			$this->sparql_service->escape_uri( Wordlift_Query_Builder::SCHEMA_AUTHOR_URI ),
545
			// Old author uri to remove,
546
			$this->sparql_service->escape_uri( $old_uri ),
547
			// New author uri to add,
548
			$this->sparql_service->escape_uri( $new_uri )
549
		);
550
551
		// Execute the query and update the author.
552
		$this->sparql_service->execute( $query );
553
554
	}
555
556
}
557