Completed
Push — milestone/2.0 ( 784680...06cd51 )
by
unknown
02:57
created

User_Meta_Container::save()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 13
Ratio 100 %

Importance

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

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
216
217
		$parsed_show_for = array(
218
			'relation' => Helper::get_relation_type_from_array( $show_for ),
219
		);
220
221
		foreach ( $show_for as $key => $rule ) {
222
			if ( $key === 'relation' ) {
223
				continue; // Skip the relation key as it is already handled above
224
			}
225
226
			// Check if the rule is valid
227
			if ( ! is_string( $rule ) || empty( $rule ) ) {
228
				Incorrect_Syntax_Exception::raise( 'Invalid show_for logic rule format. ' .
229
				'The rule should be a string, containing an user capability/role.' );
230
			}
231
232
			$parsed_show_for[] = $rule;
233
		}
234
235
		return $parsed_show_for;
236
	}
237
238
	/**
239
	 * COMMON USAGE METHODS
240
	 */
241
242
	/**
243
	 * Show the container only on users who have the $role role.
244
	 *
245
	 * @param string $role
246
	 * @return object $this
247
	 **/
248
	public function show_on_user_role( $role ) {
249
		$this->settings['show_on']['role'] = (array) $role;
250
251
		return $this;
252
	}
253
254
	/**
255
	 * Show the container only for users who have either capabilities or roles setup
256
	 *
257
	 * @param array $show_for
258
	 * @return object $this
259
	 **/
260
	public function show_for( $show_for ) {
261
		$this->settings['show_for'] = $this->parse_show_for( $show_for );
262
263
		return $this;
264
	}
265
}
266