Completed
Push — development ( aae1cf...c8c082 )
by
unknown
05:59
created

User_Meta_Container::parse_show_for()   C

Complexity

Conditions 7
Paths 10

Size

Total Lines 36
Code Lines 19

Duplication

Lines 11
Ratio 30.56 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 7
eloc 19
c 2
b 0
f 1
nc 10
nop 1
dl 11
loc 36
rs 6.7272
1
<?php
2
3
namespace Carbon_Fields\Container;
4
5
use Carbon_Fields\Datastore\Meta_Datastore;
6
use Carbon_Fields\Datastore\User_Meta_Datastore;
7
use Carbon_Fields\Exception\Incorrect_Syntax_Exception;
8
9
class User_Meta_Container extends Container {
10
	protected $user_id;
11
12
	public $settings = array(
13
		'show_on' => array(
14
			'role' => array(),
15
		),
16
		'show_for' => array(
17
			'relation' => 'AND',
18
			'edit_users',
19
		),
20
	);
21
22
	/**
23
	 * Create a new user meta container
24
	 *
25
	 * @param string $title Unique title of the container
26
	 **/
27
	public function __construct( $title ) {
28
		parent::__construct( $title );
29
30
		if ( ! $this->get_datastore() ) {
31
			$this->set_datastore( new User_Meta_Datastore(), $this->has_default_datastore() );
0 ignored issues
show
Documentation introduced by
$this->has_default_datastore() is of type object<Carbon_Fields\Dat...re\Datastore_Interface>, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
32
		}
33
	}
34
35
	/**
36
	 * Bind attach() and save() to the appropriate WordPress actions.
37
	 **/
38
	public function init() {
39
		add_action( 'admin_init', array( $this, '_attach' ) );
40
		add_action( 'profile_update', array( $this, '_save' ), 10, 1 );
41
		add_action( 'user_register', array( $this, '_save' ), 10, 1 );
42
	}
43
44
	/**
45
	 * Add the container to the user
46
	 **/
47
	public function attach() {
48
		add_action( 'show_user_profile', array( $this, 'render' ), 10, 1 );
49
		add_action( 'edit_user_profile', array( $this, 'render' ), 10, 1 );
50
		add_action( 'user_new_form', array( $this, 'render' ), 10, 1 );
51
	}
52
53
	/**
54
	 * Whether we're on the user profile page
55
	 **/
56
	public function is_profile_page() {
57
		global $pagenow;
58
59
		return $pagenow === 'profile.php' || $pagenow === 'user-new.php' || $pagenow === 'user-edit.php';
60
	}
61
62
	/**
63
	 * Perform checks whether the container should be seen for the currently logged in user
64
	 *
65
	 * @return bool True if the current user is allowed to see the container
66
	 **/
67
	public function is_valid_show_for() {
68
		$show_for = $this->settings['show_for'];
69
70
		$relation = $show_for['relation'];
71
		unset( $show_for['relation'] );
72
73
		$validated_capabilities_count = 0;
1 ignored issue
show
Comprehensibility Naming introduced by
The variable name $validated_capabilities_count exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
74
		foreach ( $show_for as $capability ) {
75
			if ( current_user_can( $capability ) ) {
76
				$validated_capabilities_count++;
77
			}
78
		}
79
80
		/**
81
		 * When the relation is AND all capabilities must be evaluated to true
82
		 * When the relation is OR at least 1 must be evaluated to true
83
		 */
84
		$min_valid_capabilities_count = $relation === 'AND' ? count( $show_for ) : 1;
1 ignored issue
show
Comprehensibility Naming introduced by
The variable name $min_valid_capabilities_count exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
85
86
		return apply_filters( 'carbon_container_user_meta_is_valid_show_for', $validated_capabilities_count >= $min_valid_capabilities_count, $this );
87
	}
88
89
	/**
90
	 * Perform checks whether the container should be attached during the current request
91
	 *
92
	 * @return bool True if the container is allowed to be attached
93
	 **/
94
	public function is_valid_attach() {
95
		if ( ! $this->is_profile_page() || ! $this->is_valid_show_for() ) {
96
			return false;
97
		}
98
99
		return true;
100
	}
101
102
	/**
103
	 * Perform checks whether the current save() request is valid
104
	 *
105
	 * @param int $user_id ID of the user against which save() is ran
106
	 * @return bool
107
	 **/
108
	public function is_valid_save_conditions( $user_id ) {
109
		$valid = true;
110
		$user = get_userdata( $user_id );
111
112
		if ( empty( $user->roles ) ) {
113
			return;
114
		}
115
116
		// Check user role
117
		if ( ! empty( $this->settings['show_on']['role'] ) ) {
118
			$allowed_roles = (array) $this->settings['show_on']['role'];
119
120
			// array_shift removed the returned role from the $user_profile->roles
121
			// $roles_to_shift prevents changing of the $user_profile->roles variable
122
			$roles_to_shift = $user->roles;
123
			$profile_role = array_shift( $roles_to_shift );
124
			if ( ! in_array( $profile_role, $allowed_roles ) ) {
125
				$valid = false;
126
			}
127
		}
128
129
		return $valid;
130
	}
131
132
	/**
133
	 * Checks whether the current request is valid
134
	 *
135
	 * @return bool
136
	 **/
137
	public function is_valid_save( $user_id = 0 ) {
138 View Code Duplication
		if ( ! isset( $_REQUEST[ $this->get_nonce_name() ] ) || ! wp_verify_nonce( $_REQUEST[ $this->get_nonce_name() ], $this->get_nonce_name() ) ) {
0 ignored issues
show
introduced by
Detected access of super global var $_REQUEST, probably need manual inspection.
Loading history...
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...
introduced by
Detected usage of a non-sanitized input variable: $_REQUEST
Loading history...
139
			return false;
140
		}
141
142
		if ( ! $this->is_valid_attach() ) {
143
			return false;
144
		}
145
146
		return $this->is_valid_save_conditions( $user_id );
147
	}
148
149
	/**
150
	 * Perform save operation after successful is_valid_save() check.
151
	 * The call is propagated to all fields in the container.
152
	 *
153
	 * @param int $user_id ID of the user against which save() is ran
154
	 **/
155 View Code Duplication
	public function save( $user_id ) {
1 ignored issue
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...
156
		// Unhook action to garantee single save
157
		remove_action( 'profile_update', array( $this, '_save' ) );
158
159
		$this->set_user_id( $user_id );
160
161
		foreach ( $this->fields as $field ) {
162
			$field->set_value_from_input();
163
			$field->save();
164
		}
165
166
		do_action( 'carbon_after_save_user_meta', $user_id );
167
	}
168
169
	/**
170
	 * Output the container markup
171
	 **/
172
	public function render( $user_profile = null ) {
173
		$profile_role = '';
174
175
		if ( is_object( $user_profile ) ) {
176
			$this->set_user_id( $user_profile->ID );
177
178
			// array_shift removed the returned role from the $user_profile->roles
179
			// $roles_to_shift prevents changing of the $user_profile->roles variable
180
			$roles_to_shift = $user_profile->roles;
181
			$profile_role = array_shift( $roles_to_shift );
182
		}
183
184
		include \Carbon_Fields\DIR . '/templates/Container/user_meta.php';
185
	}
186
187
	/**
188
	 * Set the user ID the container will operate with.
189
	 *
190
	 * @param int $user_id
191
	 **/
192
	public function set_user_id( $user_id ) {
193
		$this->user_id = $user_id;
194
		$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_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...
195
	}
196
197
	/**
198
	 * Show the container only on users who have the $role role.
199
	 *
200
	 * @param string $role
201
	 * @return object $this
202
	 **/
203
	public function show_on_user_role( $role ) {
204
		$this->settings['show_on']['role'] = (array) $role;
205
206
		return $this;
207
	}
208
209
	/**
210
	 * Show the container only for users who have either capabilities or roles setup
211
	 *
212
	 * @param array $show_for
213
	 * @return object $this
214
	 **/
215
	public function show_for( $show_for ) {
216
		$this->settings['show_for'] = $this->parse_show_for( $show_for );
217
218
		return $this;
219
	}
220
221
	/**
222
	 * Validate and parse the show_for logic rules.
223
	 *
224
	 * @param array $rules
0 ignored issues
show
Bug introduced by
There is no parameter named $rules. 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...
225
	 * @return array
226
	 */
227
	protected function parse_show_for( $show_for ) {
228
		if ( ! is_array( $show_for ) ) {
229
			Incorrect_Syntax_Exception::raise( 'Show for argument should be an array.' );
230
		}
231
232
		$allowed_relations = array( 'AND', 'OR' );
233
234
		$parsed_show_for = array(
235
			'relation' => 'AND',
236
		);
237
238
		foreach ( $show_for as $key => $rule ) {
239
			// Check if we have a relation key
240 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...
241
				$relation = strtoupper( $rule );
242
243
				if ( ! in_array( $relation, $allowed_relations ) ) {
244
					Incorrect_Syntax_Exception::raise( 'Invalid relation type ' . $rule . '. ' .
245
					'The rule should be one of the following: "' . implode( '", "', $allowed_relations ) . '"' );
246
				}
247
248
				$parsed_show_for['relation'] = $relation;
249
				continue;
250
			}
251
252
			// Check if the rule is valid
253
			if ( ! is_string( $rule ) || empty( $rule ) ) {
254
				Incorrect_Syntax_Exception::raise( 'Invalid show_for logic rule format. ' .
255
				'The rule should be a string, containing an user capability/role.' );
256
			}
257
258
			$parsed_show_for[] = $rule;
259
		}
260
261
		return $parsed_show_for;
262
	}
263
}
264