PluginProbe
Hash Form – Drag & Drop Form Builder / trunk
Hash Form – Drag & Drop Form Builder vtrunk
1.4.4 1.4.3 1.4.2 1.4.1 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.6.1 1.2.7 1.2.8 1.2.9 1.3.0 All 47 releases
hash-form / includes / HashFormCapabilities.php

HashFormCapabilities.php in Hash Form – Drag & Drop Form Builder trunk, at includes/HashFormCapabilities.php

249 lines 8.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 defined('ABSPATH') || die();
4
5 /**
6 * Granular permissions for the plugin's admin screens and ajax endpoints.
7 *
8 * Everything used to be gated on manage_options, which is a single switch:
9 * either somebody administers the whole site or they cannot look at a form
10 * entry. That is the wrong shape for the common cases - a marketing user who
11 * should read submissions but not edit forms, a developer who builds forms but
12 * should not hold the payment credentials.
13 *
14 * Backward compatibility is the constraint that shapes this class. Nobody may
15 * lose access on upgrade, so anyone with manage_options is granted every one
16 * of these capabilities through the user_has_cap filter below, whether or not
17 * the role migration has run. Sites that want true least privilege can turn
18 * that off with the hashform_grant_caps_to_admins filter and assign the
19 * capabilities by hand.
20 *
21 * The capabilities are also written onto the administrator role so they show
22 * up in the role editors people actually use.
23 */
24 class HashFormCapabilities {
25
26 /** Bumped when the capability list changes, to re-run the role migration. */
27 const VERSION = 1;
28
29 const OPTION = 'hashform_caps_version';
30
31 public function __construct() {
32 // Runs before admin_menu, so a menu registered against one of these
33 // capabilities is always visible to an administrator.
34 add_filter('user_has_cap', array($this, 'grant_to_admins'), 10, 1);
35 add_action('plugins_loaded', array($this, 'maybe_add_role_caps'), 5);
36 }
37
38 /**
39 * The capability names, with no translation involved.
40 *
41 * This is what the permission checks use. It must stay free of gettext:
42 * the user_has_cap filter below runs on the very first capability check a
43 * request makes, which can be long before init, and calling a translation
44 * function there loads the text domain too early - WordPress 6.7 reports
45 * exactly that as a notice.
46 *
47 * @return string[]
48 */
49 public static function slugs() {
50 return array(
51 'hashform_view_forms',
52 'hashform_create_forms',
53 'hashform_edit_forms',
54 'hashform_delete_forms',
55 'hashform_view_entries',
56 'hashform_edit_entries',
57 'hashform_delete_entries',
58 'hashform_export_entries',
59 'hashform_manage_settings',
60 'hashform_manage_integrations',
61 'hashform_manage_payments',
62 );
63 }
64
65 /**
66 * The same capabilities with labels, for anything that displays them.
67 *
68 * Only call this from a screen: it translates, so it must not run before
69 * init.
70 *
71 * @return array<string,string>
72 */
73 public static function all() {
74 return array(
75 'hashform_view_forms' => esc_html__('View forms', 'hash-form'),
76 'hashform_create_forms' => esc_html__('Create forms', 'hash-form'),
77 'hashform_edit_forms' => esc_html__('Edit forms', 'hash-form'),
78 'hashform_delete_forms' => esc_html__('Delete forms', 'hash-form'),
79 'hashform_view_entries' => esc_html__('View entries', 'hash-form'),
80 'hashform_edit_entries' => esc_html__('Edit entries', 'hash-form'),
81 'hashform_delete_entries' => esc_html__('Delete entries', 'hash-form'),
82 'hashform_export_entries' => esc_html__('Export entries', 'hash-form'),
83 'hashform_manage_settings' => esc_html__('Manage settings', 'hash-form'),
84 'hashform_manage_integrations' => esc_html__('Manage integrations', 'hash-form'),
85 'hashform_manage_payments' => esc_html__('Manage payments', 'hash-form'),
86 );
87 }
88
89 /**
90 * Does the current user hold this capability?
91 *
92 * Use this rather than current_user_can() directly, so an unknown
93 * capability cannot silently pass and so the administrator fallback stays
94 * in one place.
95 *
96 * @param string $cap One of the capabilities above.
97 * @param int $user_id Optional. Defaults to the current user.
98 * @return bool
99 */
100 public static function user_can($cap, $user_id = 0) {
101 // A typo in a capability name must fail closed rather than fall back
102 // to something more permissive.
103 if (!in_array($cap, self::slugs(), true)) {
104 return false;
105 }
106
107 $allowed = $user_id ? user_can($user_id, $cap) : current_user_can($cap);
108
109 /**
110 * Final say on a Hash Form permission check.
111 *
112 * @param bool $allowed
113 * @param string $cap
114 * @param int $user_id 0 for the current user.
115 */
116 return (bool) apply_filters('hashform_user_can', $allowed, $cap, $user_id);
117 }
118
119 /**
120 * Stop and say so when the current user may not do something.
121 *
122 * For screen callbacks, where the right answer is the standard WordPress
123 * permissions page rather than a blank screen.
124 *
125 * @param string $cap
126 */
127 public static function require_cap($cap) {
128 if (self::user_can($cap)) {
129 return;
130 }
131
132 wp_die(
133 esc_html__('You do not have permission to do that.', 'hash-form'),
134 esc_html__('Permission denied', 'hash-form'),
135 array('response' => 403)
136 );
137 }
138
139 /**
140 * The same check for an ajax endpoint, which needs json rather than a page.
141 *
142 * @param string $cap
143 */
144 public static function require_cap_ajax($cap) {
145 if (self::user_can($cap)) {
146 return;
147 }
148
149 wp_send_json_error(
150 array('message' => esc_html__('You do not have permission to do that.', 'hash-form')),
151 403
152 );
153 }
154
155 /**
156 * Anyone who administers the site keeps the access they have always had.
157 *
158 * Done with a filter rather than only by writing capabilities onto the
159 * role, so an install that has not run the migration yet - or a site with
160 * a custom administrator-equivalent role - is never locked out of its own
161 * forms.
162 *
163 * @param array $allcaps
164 * @return array
165 */
166 public function grant_to_admins($allcaps) {
167 if (empty($allcaps['manage_options'])) {
168 return $allcaps;
169 }
170
171 /**
172 * Whether holding manage_options implies every Hash Form capability.
173 *
174 * Return false to enforce the capabilities strictly, so an
175 * administrator only has what has actually been assigned to them.
176 */
177 if (!apply_filters('hashform_grant_caps_to_admins', true)) {
178 return $allcaps;
179 }
180
181 foreach (self::slugs() as $cap) {
182 if (!isset($allcaps[$cap])) {
183 $allcaps[$cap] = true;
184 }
185 }
186
187 return $allcaps;
188 }
189
190 /**
191 * Write the capabilities onto the administrator role once per version.
192 *
193 * The filter above is what actually grants access; this exists so the
194 * capabilities are visible to the role editing plugins site owners use to
195 * hand them to other roles.
196 */
197 public function maybe_add_role_caps() {
198 if ((int) get_option(self::OPTION) === self::VERSION) {
199 return;
200 }
201
202 self::add_caps();
203 update_option(self::OPTION, self::VERSION);
204 }
205
206 public static function add_caps() {
207 $role = get_role('administrator');
208
209 if (!$role) {
210 return;
211 }
212
213 foreach (self::slugs() as $cap) {
214 $role->add_cap($cap);
215 }
216 }
217
218 /**
219 * Called from uninstall.php, so the plugin does not leave capabilities
220 * behind on every role it ever touched.
221 */
222 public static function remove_caps() {
223 global $wp_roles;
224
225 if (!isset($wp_roles) && class_exists('WP_Roles')) {
226 $wp_roles = new WP_Roles();
227 }
228
229 if (!isset($wp_roles)) {
230 return;
231 }
232
233 foreach (array_keys($wp_roles->roles) as $role_name) {
234 $role = get_role($role_name);
235
236 if (!$role) {
237 continue;
238 }
239
240 foreach (self::slugs() as $cap) {
241 $role->remove_cap($cap);
242 }
243 }
244 }
245
246 }
247
248 new HashFormCapabilities();
249