Completed
Push — milestone/2.0 ( 77f3ad...371be2 )
by
unknown
05:56
created

User_Meta_Container::show_for()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 1
Metric Value
cc 1
eloc 3
c 4
b 0
f 1
nc 1
nop 1
dl 0
loc 5
rs 9.4285
1
<?php
2
3
namespace Carbon_Fields\Container;
4
5
use Carbon_Fields\Datastore\Datastore;
6
use Carbon_Fields\Exception\Incorrect_Syntax_Exception;
7
8
class User_Meta_Container extends Container {
9
	protected $user_id;
10
11
	public $settings = array(
12
		'show_on' => array(
13
			'role' => array(),
14
		),
15
		'show_for' => array(
16
			'relation' => 'AND',
17
			'edit_users',
18
		),
19
	);
20
21
	/**
22
	 * Create a new container
23
	 *
24
	 * @param string $unique_id Unique id of the container
25
	 * @param string $title title of the container
26
	 * @param string $type Type of the container
27
	 **/
28 View Code Duplication
	public function __construct( $unique_id, $title, $type ) {
29
		parent::__construct( $unique_id, $title, $type );
30
31
		if ( ! $this->get_datastore() ) {
32
			$this->set_datastore( Datastore::make( 'user_meta' ), $this->has_default_datastore() );
33
		}
34
	}
35
36
	/**
37
	 * Bind attach() and save() to the appropriate WordPress actions.
38
	 **/
39
	public function init() {
40
		add_action( 'admin_init', array( $this, '_attach' ) );
41
		add_action( 'profile_update', array( $this, '_save' ), 10, 1 );
42
		add_action( 'user_register', array( $this, '_save' ), 10, 1 );
43
	}
44
45
	/**
46
	 * Checks whether the current request is valid
47
	 *
48
	 * @return bool
49
	 **/
50
	public function is_valid_save( $user_id = 0 ) {
51
		if ( ! $this->verified_nonce_in_request() ) {
52
			return false;
53
		}
54
55
		if ( ! $this->is_valid_attach() ) {
56
			return false;
57
		}
58
59
		return $this->is_valid_save_conditions( $user_id );
60
	}
61
62
	/**
63
	 * Perform checks whether the current save() request is valid
64
	 *
65
	 * @param int $user_id ID of the user against which save() is ran
66
	 * @return bool
67
	 **/
68
	public function is_valid_save_conditions( $user_id ) {
69
		$valid = true;
70
		$user = get_userdata( $user_id );
71
72
		if ( empty( $user->roles ) ) {
73
			return;
74
		}
75
76
		// Check user role
77
		if ( ! empty( $this->settings['show_on']['role'] ) ) {
78
			$allowed_roles = (array) $this->settings['show_on']['role'];
79
80
			// array_shift removed the returned role from the $user_profile->roles
81
			// $roles_to_shift prevents changing of the $user_profile->roles variable
82
			$roles_to_shift = $user->roles;
83
			$profile_role = array_shift( $roles_to_shift );
84
			if ( ! in_array( $profile_role, $allowed_roles ) ) {
85
				$valid = false;
86
			}
87
		}
88
89
		return $valid;
90
	}
91
92
	/**
93
	 * Perform save operation after successful is_valid_save() check.
94
	 * The call is propagated to all fields in the container.
95
	 *
96
	 * @param int $user_id ID of the user against which save() is ran
97
	 **/
98 View Code Duplication
	public function save( $user_id ) {
99
		// Unhook action to garantee single save
100
		remove_action( 'profile_update', array( $this, '_save' ) );
101
102
		$this->set_user_id( $user_id );
103
104
		foreach ( $this->fields as $field ) {
105
			$field->set_value_from_input();
106
			$field->save();
107
		}
108
109
		do_action( 'carbon_after_save_user_meta', $user_id );
110
	}
111
112
	/**
113
	 * Perform checks whether the container should be attached during the current request
114
	 *
115
	 * @return bool True if the container is allowed to be attached
116
	 **/
117
	public function _is_valid_attach() {
118
		if ( ! $this->is_profile_page() || ! $this->is_valid_show_for() ) {
119
			return false;
120
		}
121
122
		return true;
123
	}
124
125
	/**
126
	 * Add the container to the user
127
	 **/
128
	public function attach() {
129
		add_action( 'show_user_profile', array( $this, 'render' ), 10, 1 );
130
		add_action( 'edit_user_profile', array( $this, 'render' ), 10, 1 );
131
		add_action( 'user_new_form', array( $this, 'render' ), 10, 1 );
132
	}
133
134
	/**
135
	 * Whether we're on the user profile page
136
	 **/
137
	public function is_profile_page() {
138
		global $pagenow;
139
140
		return $pagenow === 'profile.php' || $pagenow === 'user-new.php' || $pagenow === 'user-edit.php';
141
	}
142
143
	/**
144
	 * Perform checks whether the container should be seen for the currently logged in user
145
	 *
146
	 * @return bool True if the current user is allowed to see the container
147
	 **/
148
	public function is_valid_show_for() {
149
		$show_for = $this->settings['show_for'];
150
151
		$relation = $show_for['relation'];
152
		unset( $show_for['relation'] );
153
154
		$validated_capabilities_count = 0;
155
		foreach ( $show_for as $capability ) {
156
			if ( current_user_can( $capability ) ) {
157
				$validated_capabilities_count++;
158
			}
159
		}
160
161
		/**
162
		 * When the relation is AND all capabilities must be evaluated to true
163
		 * When the relation is OR at least 1 must be evaluated to true
164
		 */
165
		$min_valid_capabilities_count = $relation === 'AND' ? count( $show_for ) : 1;
166
167
		return apply_filters( 'carbon_container_user_meta_is_valid_show_for', $validated_capabilities_count >= $min_valid_capabilities_count, $this );
168
	}
169
170
	/**
171
	 * Output the container markup
172
	 **/
173
	public function render( $user_profile = null ) {
174
		$profile_role = '';
175
176
		if ( is_object( $user_profile ) ) {
177
			$this->set_user_id( $user_profile->ID );
178
179
			// array_shift removed the returned role from the $user_profile->roles
180
			// $roles_to_shift prevents changing of the $user_profile->roles variable
181
			$roles_to_shift = $user_profile->roles;
182
			$profile_role = array_shift( $roles_to_shift );
183
		}
184
185
		include \Carbon_Fields\DIR . '/templates/Container/user_meta.php';
186
	}
187
188
	/**
189
	 * Set the user ID the container will operate with.
190
	 *
191
	 * @param int $user_id
192
	 **/
193
	public function set_user_id( $user_id ) {
194
		$this->user_id = $user_id;
195
		$this->get_datastore()->set_id( $user_id );
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Carbon_Fields\Datastore\Datastore_Interface as the method set_id() does only exist in the following implementations of said interface: Carbon_Fields\Datastore\Comment_Meta_Datastore, Carbon_Fields\Datastore\Meta_Datastore, Carbon_Fields\Datastore\Nav_Menu_Item_Datastore, Carbon_Fields\Datastore\Post_Meta_Datastore, Carbon_Fields\Datastore\Term_Meta_Datastore, Carbon_Fields\Datastore\User_Meta_Datastore.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
196
	}
197
198
	/**
199
	 * Validate and parse the show_for logic rules.
200
	 *
201
	 * @param array $show_for
202
	 * @return array
203
	 */
204
	protected function parse_show_for( $show_for ) {
205
		if ( ! is_array( $show_for ) ) {
206
			Incorrect_Syntax_Exception::raise( 'Show for argument should be an array.' );
207
		}
208
209
		$allowed_relations = array( 'AND', 'OR' );
210
211
		$parsed_show_for = array(
212
			'relation' => 'AND',
213
		);
214
215
		foreach ( $show_for as $key => $rule ) {
216
			// Check if we have a relation key
217 View Code Duplication
			if ( $key === 'relation' ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
218
				$relation = strtoupper( $rule );
219
220
				if ( ! in_array( $relation, $allowed_relations ) ) {
221
					Incorrect_Syntax_Exception::raise( 'Invalid relation type ' . $rule . '. ' .
222
					'The rule should be one of the following: "' . implode( '", "', $allowed_relations ) . '"' );
223
				}
224
225
				$parsed_show_for['relation'] = $relation;
226
				continue;
227
			}
228
229
			// Check if the rule is valid
230
			if ( ! is_string( $rule ) || empty( $rule ) ) {
231
				Incorrect_Syntax_Exception::raise( 'Invalid show_for logic rule format. ' .
232
				'The rule should be a string, containing an user capability/role.' );
233
			}
234
235
			$parsed_show_for[] = $rule;
236
		}
237
238
		return $parsed_show_for;
239
	}
240
241
	/**
242
	 * COMMON USAGE METHODS
243
	 */
244
245
	/**
246
	 * Show the container only on users who have the $role role.
247
	 *
248
	 * @param string $role
249
	 * @return object $this
250
	 **/
251
	public function show_on_user_role( $role ) {
252
		$this->settings['show_on']['role'] = (array) $role;
253
254
		return $this;
255
	}
256
257
	/**
258
	 * Show the container only for users who have either capabilities or roles setup
259
	 *
260
	 * @param array $show_for
261
	 * @return object $this
262
	 **/
263
	public function show_for( $show_for ) {
264
		$this->settings['show_for'] = $this->parse_show_for( $show_for );
265
266
		return $this;
267
	}
268
}
269