PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.14
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.14
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
← All changes | app/Services/Manager/ManagerService.php +65 -36 6.2.56.2.14 View file →
@@ -13,9 +13,9 @@
13 13 public function getManagers($attributes = [])
14 14 {
15 15 $limit = Arr::get($attributes, 'per_page', 10);
16 16 $page = Arr::get($attributes, 'page', 1);
17 - $offset = $page == 1 ? 0 : ($page - 1) * $limit;
17 + $offset = 1 == $page ? 0 : ($page - 1) * $limit;
18 18
19 19 $query = new \WP_User_Query([
20 20 'meta_key' => '_fluent_forms_has_role',
21 21 'meta_value' => 1,
@@ -54,36 +54,51 @@
54 54 'permissions' => Acl::getReadablePermissions(),
55 55 'forms' => $forms,
56 56 ]);
57 57 }
58 -
58 +
59 59 public function addManager($attributes = [])
60 60 {
61 61 $manager = Arr::get($attributes, 'manager');
62 -
62 +
63 63 $this->validate($manager);
64 -
64 +
65 65 $permissions = Arr::get($manager, 'permissions', []);
66 -
66 +
67 + // Defense in depth: only a full WordPress administrator may grant the
68 + // crown-jewel `fluentform_full_access` permission. A holder of that ACL
69 + // permission is not necessarily a WP admin (managers can be delegated),
70 + // so without this a compromised full-access session — e.g. a script
71 + // running via XSS in a privileged session — could mint new full-access
72 + // managers and escalate. Lesser permission grants are unaffected.
73 + if (in_array('fluentform_full_access', (array) $permissions, true) && !current_user_can('manage_options')) {
74 + $permissions = array_values(array_filter((array) $permissions, function ($permission) {
75 + return 'fluentform_full_access' !== $permission;
76 + }));
77 + }
78 +
67 79 $user = get_user_by('email', $manager['email']);
68 -
80 +
69 81 if (!$user) {
70 82 throw new ValidationException('', 0, null, ['message' => 'Please Provide Valid Email']);
71 83 }
72 -
84 +
73 85 Acl::attachPermissions($user, $permissions);
74 -
86 +
75 87 update_user_meta($user->ID, '_fluent_forms_has_role', 1);
76 -
88 +
77 89 $hasSpecificFormsPermission = 'yes' === Arr::get($manager, 'has_specific_forms_permission');
78 90 $allowedForms = array_values(array_filter(array_map('intval', (array) Arr::get($manager, 'forms', []))));
79 91
92 + // The UI always sends this flag, so an absent one is a partial payload, not a request to clear.
93 + $restrictionWasSubmitted = null !== Arr::get($manager, 'has_specific_forms_permission');
94 +
80 95 // Keep an empty selection unrestricted so the manager UI's
81 96 // "leave blank for all forms" behavior matches the saved ACL state.
82 97 if ($hasSpecificFormsPermission && $allowedForms) {
83 98 FormManagerService::updateHasSpecificFormsPermission($user->ID, 'yes');
84 99 FormManagerService::addUserAllowedForms($allowedForms, $user->ID);
85 - } else {
100 + } elseif ($restrictionWasSubmitted) {
86 101 FormManagerService::updateHasSpecificFormsPermission($user->ID, 'no');
87 102 FormManagerService::deleteUserAllowedForms($user->ID);
88 103 }
89 104
@@ -91,46 +106,60 @@
91 106 'id' => $user->ID,
92 107 'first_name' => $user->first_name,
93 108 'last_name' => $user->last_name,
94 109 'email' => $user->user_email,
95 - 'permissions' => Acl::getUserPermissions($user)
110 + 'permissions' => Acl::getUserPermissions($user),
96 111 ];
97 -
112 +
98 113 return ([
99 114 'message' => __('Manager has been saved.', 'fluentform'),
100 - 'manager' => $updatedUser
115 + 'manager' => $updatedUser,
101 116 ]);
102 117 }
103 -
118 +
104 119 public function removeManager($attributes = [])
105 120 {
106 121 $userID = intval(Arr::get($attributes, 'id'));
107 122 $user = get_user_by('ID', $userID);
108 -
123 +
109 124 if (!$user) {
110 125 return ([
111 126 'message' => __('Associate user could not be found', 'fluentform'),
112 127 ]);
113 128 }
114 -
129 +
130 + // Removal deletes _fluent_forms_has_role, which is what suppresses the role fallback.
131 + if (get_current_user_id() === $user->ID) {
132 + return ([
133 + 'message' => __('You cannot remove yourself as a manager.', 'fluentform'),
134 + ]);
135 + }
136 +
137 + // Mirrors the grant-side guard at :73.
138 + if (in_array('fluentform_full_access', (array) Acl::getUserPermissions($user), true) && !current_user_can('manage_options')) {
139 + return ([
140 + 'message' => __('You do not have permission to remove a full access manager.', 'fluentform'),
141 + ]);
142 + }
143 +
115 144 Acl::attachPermissions($user, []);
116 -
145 +
117 146 delete_user_meta($user->ID, '_fluent_forms_has_role');
118 -
147 +
119 148 $deletedUser = [
120 149 'id' => $user->ID,
121 150 'first_name' => $user->first_name,
122 151 'last_name' => $user->last_name,
123 152 'email' => $user->user_email,
124 - 'permissions' => Acl::getUserPermissions($user)
153 + 'permissions' => Acl::getUserPermissions($user),
125 154 ];
126 -
155 +
127 156 return ([
128 157 'message' => __('Manager has been removed.', 'fluentform'),
129 - 'manager' => $deletedUser
158 + 'manager' => $deletedUser,
130 159 ]);
131 160 }
132 -
161 +
133 162 private function validate($manager)
134 163 {
135 164 $rules = [
136 165 'permissions' => 'required',
@@ -135,21 +164,21 @@
135 164 $rules = [
136 165 'permissions' => 'required',
137 166 'email' => 'required|email',
138 167 ];
139 -
168 +
140 169 $validatorInstance = new Validator();
141 170 $validator = $validatorInstance->make($manager, $rules);
142 -
171 +
143 172 $errors = null;
144 -
173 +
145 174 if ($validator->validate()->fails()) {
146 175 $errors = $validator->errors();
147 176 }
148 -
177 +
149 178 if (!isset($errors['email'])) {
150 179 $user = get_user_by('email', $manager['email']);
151 -
180 +
152 181 if (!$user) {
153 182 $errors['email'] = [
154 183 'no_user' => __('We could not found any user with this email.', 'fluentform'),
155 184 ];
@@ -154,12 +183,12 @@
154 183 'no_user' => __('We could not found any user with this email.', 'fluentform'),
155 184 ];
156 185 }
157 186 }
158 -
187 +
159 188 if (!isset($errors['permissions'])) {
160 189 $message = $this->dependencyValidate($manager['permissions']);
161 -
190 +
162 191 if ($message) {
163 192 $errors['permissions'] = [
164 193 'dependency' => $message,
165 194 ];
@@ -164,12 +193,12 @@
164 193 'dependency' => $message,
165 194 ];
166 195 }
167 196 }
168 -
197 +
169 198 if ($errors) {
170 199 // Escape all error messages before throwing
171 - $escapedErrors = array_map(function($errorMessages) {
200 + $escapedErrors = array_map(function ($errorMessages) {
172 201 if (is_array($errorMessages)) {
173 202 return array_map('esc_html', $errorMessages);
174 203 }
175 204 return esc_html($errorMessages);
@@ -180,24 +209,24 @@
180 209 'errors' => $escapedErrors,
181 210 ]);
182 211 }
183 212 }
184 -
213 +
185 214 private function dependencyValidate($permissions)
186 215 {
187 216 $allPermissions = Acl::getReadablePermissions();
188 -
217 +
189 218 foreach ($permissions as $permission) {
190 219 $depends = Arr::get($allPermissions, $permission . '.depends', []);
191 -
220 +
192 221 if ($depends && $more = array_values(array_diff($depends, $permissions))) {
193 222 $message = $allPermissions[$permission]['title'] . ' requires permission: ';
194 -
223 +
195 224 foreach ($more as $i => $p) {
196 225 $joiner = $i ? ', ' : '';
197 226 $message = $message . $joiner . $allPermissions[$p]['title'];
198 227 }
199 -
228 +
200 229 return $message;
201 230 }
202 231 }
203 232 }