PluginProbe
Members – Membership & User Role Editor Plugin / 3.2.24
Members – Membership & User Role Editor Plugin v3.2.24
3.2.25 3.2.26 3.2.24 3.2.23 3.2.22 3.2.21 trunk 0.1 0.1.1 0.2 0.2.1 0.2.2 0.2.3 0.2.4 0.2.5 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.1.3 2.0.0 2.0.1 2.0.2 All 66 releases
members / admin / class-content-permissions-editor.php

class-content-permissions-editor.php in Members – Membership & User Role Editor Plugin 3.2.24, at admin/class-content-permissions-editor.php

407 lines 11.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Block editor and REST integration for Content Permissions.
4 *
5 * @package Members
6 * @subpackage Admin
7 * @author The MemberPress Team
8 * @copyright Copyright (c) 2009 - 2018, The MemberPress Team
9 * @link https://members-plugin.com/
10 * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
11 */
12 namespace Members\Admin;
13
14 defined( 'ABSPATH' ) || exit;
15
16 /**
17 * Registers post meta, REST routes, and the block editor document panel.
18 *
19 * Loaded on every request so block editor REST saves work outside is_admin().
20 *
21 * @since 3.2.22
22 * @access public
23 */
24 final class Content_Permissions_Editor {
25
26 /**
27 * Holds the instance of this class.
28 *
29 * @since 3.2.22
30 * @access private
31 * @var object
32 */
33 private static $instance;
34
35 /**
36 * Post types that already have a `rest_prepare_{$post_type}` callback registered.
37 *
38 * @since 3.2.22
39 * @access private
40 * @var array
41 */
42 private static $rest_prepare_hooks_added = array();
43
44 /**
45 * Post types that already have a `rest_before_insert_{$post_type}` callback registered.
46 *
47 * @since 3.2.22
48 * @access private
49 * @var array
50 */
51 private static $rest_before_insert_hooks_added = array();
52
53 /**
54 * Sets up hooks.
55 *
56 * @since 3.2.22
57 * @access protected
58 * @return void
59 */
60 protected function __construct() {
61
62 if ( ! members_content_permissions_enabled() ) {
63 return;
64 }
65
66 add_action( 'init', array( $this, 'register_content_permissions_post_meta' ), 999 );
67 add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_panel' ) );
68 }
69
70 /**
71 * Registers post meta for the block editor document panel.
72 *
73 * @since 3.2.22
74 * @access public
75 * @return void
76 */
77 public function register_content_permissions_post_meta() {
78
79 foreach ( members_get_content_permissions_post_types() as $post_type ) {
80
81 register_post_meta(
82 $post_type,
83 '_members_access_role',
84 array(
85 'type' => 'string',
86 'single' => false,
87 'description' => __( 'User roles that may view this content.', 'members' ),
88 'show_in_rest' => true,
89 'auth_callback' => array( $this, 'auth_content_permissions_meta' ),
90 'sanitize_callback' => 'members_sanitize_access_role_meta_value',
91 )
92 );
93
94 register_post_meta(
95 $post_type,
96 '_members_access_error',
97 array(
98 'type' => 'string',
99 'single' => true,
100 'show_in_rest' => true,
101 'auth_callback' => array( $this, 'auth_content_permissions_meta' ),
102 'sanitize_callback' => 'wp_kses_post',
103 )
104 );
105
106 if ( empty( self::$rest_prepare_hooks_added[ $post_type ] ) ) {
107 add_filter( "rest_prepare_{$post_type}", array( $this, 'prepare_rest_content_permissions_meta' ), 10, 3 );
108 self::$rest_prepare_hooks_added[ $post_type ] = true;
109 }
110
111 if ( empty( self::$rest_before_insert_hooks_added[ $post_type ] ) ) {
112 add_action( "rest_before_insert_{$post_type}", array( $this, 'prepare_rest_content_permissions_meta_request' ), 10, 3 );
113 self::$rest_before_insert_hooks_added[ $post_type ] = true;
114 }
115 }
116 }
117
118 /**
119 * Normalizes role meta on REST saves before core persists it.
120 *
121 * Migrates legacy `_role` meta, strips invalid values, and merges orphan slugs so core's
122 * bulk meta write matches the classic meta box save path.
123 *
124 * @since 3.2.22
125 * @access public
126 * @param \stdClass|\WP_Post $prepared_post Post object prepared for insert/update.
127 * @param \WP_REST_Request $request REST request object.
128 * @param bool $creating True when creating a post, false when updating.
129 * @return void
130 */
131 public function prepare_rest_content_permissions_meta_request( $prepared_post, $request, $creating ) {
132
133 if ( ! $request instanceof \WP_REST_Request ) {
134 return;
135 }
136
137 $post_id = $creating ? 0 : ( ! empty( $prepared_post->ID ) ? (int) $prepared_post->ID : (int) $request->get_param( 'id' ) );
138
139 if ( $post_id && ! $creating && current_user_can( 'restrict_content' ) && current_user_can( 'edit_post', $post_id ) ) {
140 members_convert_old_post_meta( $post_id );
141 }
142
143 $meta = $request->get_param( 'meta' );
144
145 if ( ! is_array( $meta ) || ! array_key_exists( '_members_access_role', $meta ) ) {
146 return;
147 }
148
149 if ( ! $this->user_can_modify_content_permissions_via_rest( $post_id, $creating, $prepared_post, $request ) ) {
150 unset( $meta['_members_access_role'] );
151 $request->set_param( 'meta', $meta );
152 return;
153 }
154
155 $roles = $meta['_members_access_role'];
156
157 if ( ! is_array( $roles ) ) {
158 return;
159 }
160
161 $sanitized = members_sanitize_access_role_meta_list( $roles );
162
163 if ( $post_id && ! $creating ) {
164 $orphans = members_get_orphan_post_roles( $post_id );
165
166 if ( ! empty( $orphans ) ) {
167 $sanitized = array_values( array_unique( array_merge( $sanitized, $orphans ) ) );
168 }
169 }
170
171 $meta['_members_access_role'] = $sanitized;
172 $request->set_param( 'meta', $meta );
173 }
174
175 /**
176 * Capability check for REST content-permissions meta writes.
177 *
178 * @since 3.2.22
179 * @access private
180 * @param int $post_id Post ID on update, 0 on create.
181 * @param bool $creating True when creating a post, false when updating.
182 * @param \stdClass|\WP_Post $prepared_post Post object prepared for insert/update.
183 * @param \WP_REST_Request $request REST request object.
184 * @return bool
185 */
186 private function user_can_modify_content_permissions_via_rest( $post_id, $creating, $prepared_post, $request ) {
187
188 if ( ! current_user_can( 'restrict_content' ) ) {
189 return false;
190 }
191
192 if ( ! $creating && $post_id ) {
193 return current_user_can( 'edit_post', $post_id );
194 }
195
196 if ( ! $creating ) {
197 return false;
198 }
199
200 $post_type = '';
201
202 if ( ! empty( $prepared_post->post_type ) ) {
203 $post_type = $prepared_post->post_type;
204 } elseif ( is_string( $request->get_param( 'type' ) ) ) {
205 $post_type = $request->get_param( 'type' );
206 }
207
208 $type = get_post_type_object( $post_type );
209
210 return $type && current_user_can( $type->cap->create_posts );
211 }
212
213 /**
214 * Auth check for content permissions post meta in REST.
215 *
216 * Note: WordPress passes `$allowed` as `! is_protected_meta( $meta_key )`, which is
217 * always false for our underscore-prefixed key. The whole point of an auth_callback is
218 * to grant access to a protected meta key, so we must NOT bail on `$allowed` here or the
219 * meta could never be written via REST (block editor, Elementor, and other page builders
220 * would all fail with "you are not allowed to edit the _members_access_role custom field").
221 *
222 * @since 3.2.22
223 * @access public
224 * @param bool $allowed Whether the user can add the meta. Ignored (see note above).
225 * @param string $meta_key Meta key.
226 * @param int $object_id Post ID.
227 * @return bool
228 */
229 public function auth_content_permissions_meta( $allowed, $meta_key, $object_id ) {
230
231 if ( ! current_user_can( 'restrict_content' ) ) {
232 return false;
233 }
234
235 $object_id = (int) $object_id;
236
237 if ( $object_id ) {
238 return current_user_can( 'edit_post', $object_id );
239 }
240
241 foreach ( members_get_content_permissions_post_types() as $post_type ) {
242 $type = get_post_type_object( $post_type );
243
244 if ( $type && current_user_can( $type->cap->create_posts ) ) {
245 return true;
246 }
247 }
248
249 return false;
250 }
251
252 /**
253 * Ensures the block editor receives stored roles (including legacy formats).
254 *
255 * @since 3.2.22
256 * @access public
257 * @param \WP_REST_Response|mixed $response REST response object.
258 * @param \WP_Post $post Post object.
259 * @param \WP_REST_Request $request REST request object.
260 * @return \WP_REST_Response|mixed
261 */
262 public function prepare_rest_content_permissions_meta( $response, $post, $request ) {
263
264 if ( ! $response instanceof \WP_REST_Response || ! $post instanceof \WP_Post || ! current_user_can( 'restrict_content' ) || ! current_user_can( 'edit_post', $post->ID ) ) {
265 return $response;
266 }
267
268 $data = $response->get_data();
269
270 if ( ! is_array( $data ) || ! isset( $data['meta'] ) || ! is_array( $data['meta'] ) ) {
271 return $response;
272 }
273
274 $data['meta']['_members_access_role'] = members_get_post_roles_for_rest( $post->ID );
275 $response->set_data( $data );
276
277 return $response;
278 }
279
280 /**
281 * Enqueues the block editor document panel script.
282 *
283 * @since 3.2.22
284 * @access public
285 * @return void
286 */
287 public function enqueue_block_editor_panel() {
288
289 if ( ! current_user_can( 'restrict_content' ) ) {
290 return;
291 }
292
293 $post_type = $this->get_block_editor_post_type();
294
295 if ( ! $post_type || ! members_is_content_permissions_enabled_for_post_type( $post_type ) ) {
296 return;
297 }
298
299 $post = members_get_post_for_content_permissions();
300
301 $_wp_roles = apply_filters( 'members_wp_roles', wp_roles()->role_names, $post );
302 asort( $_wp_roles );
303
304 $roles = array();
305
306 foreach ( $_wp_roles as $role => $name ) {
307 $roles[ $role ] = members_translate_role( $role );
308 }
309
310 $post_id = $post ? $post->ID : 0;
311 $default_roles = array();
312
313 if ( $post instanceof \WP_Post && $post_id && empty( members_get_post_roles_for_rest( $post_id ) ) && 'auto-draft' === $post->post_status ) {
314 $default_roles = apply_filters( 'members_default_post_roles', array(), $post_id );
315 }
316
317 $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
318 $panel_file = members_plugin()->dir . "js/editor-content-permissions-panel{$min}.js";
319 $panel_ver = file_exists( $panel_file ) ? filemtime( $panel_file ) : false;
320
321 wp_enqueue_style( 'members-admin' );
322
323 wp_enqueue_script(
324 'members-cp-panel',
325 members_plugin()->uri . "js/editor-content-permissions-panel{$min}.js",
326 array(
327 'wp-plugins',
328 'wp-editor',
329 'wp-edit-post',
330 'wp-block-editor',
331 'wp-components',
332 'wp-data',
333 'wp-core-data',
334 'wp-element',
335 'wp-i18n',
336 ),
337 $panel_ver,
338 true
339 );
340
341 if ( function_exists( 'wp_set_script_translations' ) ) {
342 wp_set_script_translations( 'members-cp-panel', 'members', members_plugin()->dir . 'languages' );
343 }
344
345 $panel_data = array(
346 'roles' => $roles,
347 'defaultRoles' => array_values( $default_roles ),
348 );
349
350 if ( ! members_is_memberpress_active() ) {
351 $panel_data['memberPressUpsell'] = array(
352 'message' => __( 'To protect this block by paid membership or centrally with a content protection rule, add MemberPress.', 'members' ),
353 'cta' => __( 'Add MemberPress', 'members' ),
354 'url' => 'https://memberpress.com/plans/pricing/?utm_source=members_plugin&utm_medium=link&utm_campaign=in_plugin&utm_content=content_protection',
355 );
356 }
357
358 wp_localize_script( 'members-cp-panel', 'membersCpPanel', $panel_data );
359 }
360
361 /**
362 * Returns the post type for the current block editor screen.
363 *
364 * @since 3.2.22
365 * @access private
366 * @return string Post type slug, or empty string when unavailable.
367 */
368 private function get_block_editor_post_type() {
369
370 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
371
372 if ( $screen && ! empty( $screen->post_type ) ) {
373 return $screen->post_type;
374 }
375
376 $post = members_get_post_for_content_permissions();
377
378 if ( $post instanceof \WP_Post ) {
379 return $post->post_type;
380 }
381
382 if ( ! empty( $_GET['post_type'] ) && is_string( $_GET['post_type'] ) ) {
383 return sanitize_key( wp_unslash( $_GET['post_type'] ) );
384 }
385
386 return '';
387 }
388
389 /**
390 * Returns the instance.
391 *
392 * @since 3.2.22
393 * @access public
394 * @return object
395 */
396 public static function get_instance() {
397
398 if ( ! self::$instance ) {
399 self::$instance = new self();
400 }
401
402 return self::$instance;
403 }
404 }
405
406 Content_Permissions_Editor::get_instance();
407