PluginProbe
Members – Membership & User Role Editor Plugin / 3.2.23
Members – Membership & User Role Editor Plugin v3.2.23
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 / members.php

members.php in Members – Membership & User Role Editor Plugin 3.2.23, at members.php

612 lines 18.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: Members
4 * Plugin URI: https://members-plugin.com/
5 * Description: A user and role management plugin that puts you in full control of your site's permissions. This plugin allows you to edit your roles and their capabilities, clone existing roles, assign multiple roles per user, block post content, or even make your site completely private.
6 * Version: 3.2.23
7 * Requires PHP: 7.4
8 * Author: MemberPress
9 * Author URI: https://memberpress.com
10 * Text Domain: members
11 *
12 * The members plugin was created because the WordPress community is lacking a solid permissions
13 * plugin that is both open source and works completely within the confines of the APIs in WordPress.
14 * But, the plugin is so much more than just a plugin to control permissions. It is meant to extend
15 * WordPress by making user, role, and content management as simple as using WordPress itself.
16 *
17 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
18 * General Public License as published by the Free Software Foundation; either version 2 of the License,
19 * or (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
22 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
23 *
24 * You should have received a copy of the GNU General Public License along with this program; if not,
25 * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26 */
27 /**
28 * * * * * * * * * * * * * * * * * * * * * * *
29 * *
30 * Reporting a Security Vulnerability *
31 * *
32 * Please disclose any security issues or *
33 * vulnerabilities to security@caseproof.com *
34 * *
35 * * * * * * * * * * * * * * * * * * * * * * *
36 */
37
38 if (!defined('ABSPATH')) {
39 die('You are not allowed to call this page directly.');
40 }
41
42 /**
43 * Singleton class for setting up the plugin.
44 *
45 * @since 1.0.0
46 * @access public
47 */
48 final class Members_Plugin {
49
50 /**
51 * Minimum required PHP version.
52 *
53 * @since 2.0.0
54 * @access public
55 * @var string
56 */
57 private $php_version = '7.4.0';
58
59 /**
60 * Plugin directory path.
61 *
62 * @since 2.0.0
63 * @access public
64 * @var string
65 */
66 public $dir = '';
67
68 /**
69 * Plugin directory URI.
70 *
71 * @since 2.0.0
72 * @access public
73 * @var string
74 */
75 public $uri = '';
76
77 /**
78 * User count of all roles.
79 *
80 * @see members_get_role_user_count()
81 * @since 1.0.0
82 * @access public
83 * @var array
84 */
85 public $role_user_count = array();
86
87 /**
88 * Returns the instance.
89 *
90 * @since 1.0.0
91 * @access public
92 * @return object
93 */
94 public static function get_instance() {
95
96 static $instance = null;
97
98 if ( is_null( $instance ) ) {
99 $instance = new self;
100 $instance->setup();
101 $instance->includes();
102 $instance->setup_actions();
103 }
104
105 return $instance;
106 }
107
108 /**
109 * Constructor method.
110 *
111 * @since 1.0.0
112 * @access private
113 * @return void
114 */
115 private function __construct() {
116 require_once(__DIR__ . '/vendor-prefixed/autoload.php');
117
118 add_action( 'plugins_loaded', array( $this, 'init_growth_tools' ) );
119 }
120
121 /**
122 * Magic method to output a string if trying to use the object as a string.
123 *
124 * @since 1.0.0
125 * @access public
126 * @return void
127 */
128 public function __toString() {
129 return 'members';
130 }
131
132 /**
133 * Magic method to keep the object from being cloned.
134 *
135 * @since 1.0.0
136 * @access public
137 * @return void
138 */
139 public function __clone() {
140 _doing_it_wrong( __FUNCTION__, esc_html__( 'Whoah, partner!', 'members' ), '1.0.0' );
141 }
142
143 /**
144 * Magic method to keep the object from being unserialized.
145 *
146 * @since 1.0.0
147 * @access public
148 * @return void
149 */
150 public function __wakeup() {
151 _doing_it_wrong( __FUNCTION__, esc_html__( 'Whoah, partner!', 'members' ), '1.0.0' );
152 }
153
154 /**
155 * Magic method to prevent a fatal error when calling a method that doesn't exist.
156 *
157 * @since 1.0.0
158 * @access public
159 * @return null
160 */
161 public function __call( $method = '', $args = array() ) {
162 _doing_it_wrong( "Members_Plugin::{$method}", esc_html__( 'Method does not exist.', 'members' ), '1.0.0' );
163 unset( $method, $args );
164 return null;
165 }
166
167 /**
168 * Sets up globals.
169 *
170 * @since 1.0.0
171 * @access private
172 * @return void
173 */
174 private function setup() {
175
176 // Main plugin directory path and URI.
177 $this->dir = trailingslashit( plugin_dir_path( __FILE__ ) );
178 $this->uri = trailingslashit( plugin_dir_url( __FILE__ ) );
179 }
180
181 /**
182 * Loads files needed by the plugin.
183 *
184 * @since 1.0.0
185 * @access private
186 * @return void
187 */
188 private function includes() {
189
190 // Check if we meet the minimum PHP version.
191 if ( version_compare( PHP_VERSION, $this->php_version, '<' ) ) {
192
193 // Add admin notice.
194 add_action( 'admin_notices', array( $this, 'php_admin_notice' ) );
195
196 // Bail.
197 return;
198 }
199
200 // Load class files.
201 require_once( $this->dir . 'inc/class-capability.php' );
202 require_once( $this->dir . 'inc/class-cap-group.php' );
203 require_once( $this->dir . 'inc/class-registry.php' );
204 require_once( $this->dir . 'inc/class-role-group.php' );
205 require_once( $this->dir . 'inc/class-role.php' );
206
207 // Load includes files.
208 require_once( $this->dir . 'inc/functions.php' );
209 require_once( $this->dir . 'inc/functions-admin-bar.php' );
210 require_once( $this->dir . 'inc/functions-capabilities.php' );
211 require_once( $this->dir . 'inc/functions-cap-groups.php' );
212 require_once( $this->dir . 'inc/functions-content-permissions.php' );
213 require_once( $this->dir . 'inc/functions-deprecated.php' );
214 require_once( $this->dir . 'inc/functions-options.php' );
215 require_once( $this->dir . 'inc/functions-private-site.php' );
216 require_once( $this->dir . 'inc/functions-roles.php' );
217 require_once( $this->dir . 'inc/functions-role-groups.php' );
218 require_once( $this->dir . 'inc/functions-shortcodes.php' );
219 require_once( $this->dir . 'inc/functions-users.php' );
220 require_once( $this->dir . 'inc/functions-widgets.php' );
221
222 // Load template files.
223 require_once( $this->dir . 'inc/template.php' );
224
225 // Administrator Rescue (Magic Link) – must load outside is_admin() for wp-login.php.
226 require_once( $this->dir . 'inc/class-rescue-magic-link.php' );
227
228 // Notifications (cannot be included inside is_admin() check or cron won't work)
229 require_once( $this->dir . 'admin/class-notifications.php' );
230
231 // Block editor REST saves run outside is_admin(); post meta + REST routes must load on every request.
232 require_once( $this->dir . 'admin/class-content-permissions-editor.php' );
233
234 // Load admin files.
235 if ( is_admin() ) {
236
237 // General admin functions.
238 require_once( $this->dir . 'admin/functions-admin.php' );
239 require_once( $this->dir . 'admin/functions-help.php' );
240 require_once( $this->dir . 'admin/class-review-prompt.php' );
241
242 // Plugin settings.
243 require_once( $this->dir . 'admin/class-settings.php' );
244
245 // User management.
246 require_once( $this->dir . 'admin/class-manage-users.php' );
247 require_once( $this->dir . 'admin/class-user-edit.php' );
248 require_once( $this->dir . 'admin/class-user-new.php' );
249
250 // Role management.
251 require_once( $this->dir . 'admin/class-manage-roles.php' );
252 require_once( $this->dir . 'admin/class-roles.php' );
253 require_once( $this->dir . 'admin/class-role-edit.php' );
254 require_once( $this->dir . 'admin/class-role-new.php' );
255 require_once( $this->dir . 'admin/class-role-export.php' );
256 require_once( $this->dir . 'admin/class-role-import.php' );
257 require_once( $this->dir . 'admin/class-meta-box-publish-role.php' );
258 require_once( $this->dir . 'admin/class-meta-box-custom-cap.php' );
259 require_once( $this->dir . 'admin/class-meta-box-content-permissions.php' );
260
261 // Edit capabilities tabs and groups.
262 require_once( $this->dir . 'admin/class-cap-tabs.php' );
263 require_once( $this->dir . 'admin/class-cap-section.php' );
264 require_once( $this->dir . 'admin/class-cap-control.php' );
265 }
266
267 $addons = get_option( 'members_active_addons', array() );
268
269 if ( ! empty( $addons ) ) {
270 foreach ( $addons as $addon ) {
271 if ( file_exists( __DIR__ . "/addons/{$addon}/addon.php" ) ) {
272 include __DIR__ . "/addons/{$addon}/addon.php";
273 }
274 }
275 }
276 }
277
278 /**
279 * Sets up main plugin actions and filters.
280 *
281 * @since 1.0.0
282 * @access private
283 * @return void
284 */
285 private function setup_actions() {
286 // Migrate add-ons
287 add_action( 'plugins_loaded', array( $this, 'migrate_addons' ) );
288
289 // Administrator Rescue (Magic Link)
290 add_action( 'plugins_loaded', array( $this, 'init_rescue_magic_link' ), 5 );
291
292 // MemberPress info in block editor
293 add_action( 'enqueue_block_editor_assets', array( $this, 'block_editor_assets' ) );
294
295 // Register activation hook.
296 register_activation_hook( __FILE__, array( $this, 'activation' ) );
297
298 // Reset roles
299 add_action( 'wp_ajax_members_reset_roles', array( $this, 'reset_roles' ) );
300 }
301
302 /**
303 * Initialize Growth Tools.
304 *
305 * @since 3.2.19
306 * @access public
307 * @return void
308 */
309 public function init_growth_tools() {
310 if ( version_compare( phpversion(), '7.4', '>=' ) && class_exists( '\Members\Caseproof\GrowthTools\App' ) ) {
311 $config = new \Members\Caseproof\GrowthTools\Config( [
312 'parentMenuSlug' => 'members',
313 'instanceId' => 'members',
314 'menuSlug' => 'members-growth-tools',
315 ] );
316 new \Members\Caseproof\GrowthTools\App( $config );
317 }
318 }
319
320 /**
321 * Method that runs only when the plugin is activated.
322 *
323 * @since 1.0.0
324 * @access public
325 * @return void
326 */
327 public function activation() {
328
329 // Check PHP version requirements.
330 if ( version_compare( PHP_VERSION, $this->php_version, '<' ) ) {
331
332 // Make sure the plugin is deactivated.
333 deactivate_plugins( plugin_basename( __FILE__ ) );
334
335 // Add an error message and die.
336 wp_die( $this->get_min_php_message() );
337 }
338
339 // Get the administrator role.
340 $role = get_role( 'administrator' );
341
342 // If the administrator role exists, add required capabilities for the plugin.
343 if ( ! empty( $role ) ) {
344
345 $role->add_cap( 'restrict_content' ); // Edit per-post content permissions.
346 $role->add_cap( 'list_roles' ); // View roles in backend.
347
348 // Do not allow administrators to edit, create, or delete roles
349 // in a multisite setup. Super admins should assign these manually.
350 if ( ! is_multisite() ) {
351 $role->add_cap( 'create_roles' ); // Create new roles.
352 $role->add_cap( 'delete_roles' ); // Delete existing roles.
353 $role->add_cap( 'edit_roles' ); // Edit existing roles/caps.
354 }
355 }
356
357 $flag = get_transient( 'members_30days_flag' );
358 if ( empty( $flag ) ) {
359 set_transient( 'members_30days_flag', true, 30 * DAY_IN_SECONDS );
360 }
361 if ( empty( get_option( 'members_activated' ) ) ) {
362 update_option( 'members_activated', time() );
363 }
364 }
365
366 /**
367 * Returns a message noting the minimum version of PHP required.
368 *
369 * @since 2.0.1
370 * @access private
371 * @return void
372 */
373 private function get_min_php_message() {
374
375 return sprintf(
376 __( 'Members requires PHP version %1$s. You are running version %2$s. Please upgrade and try again.', 'members' ),
377 $this->php_version,
378 PHP_VERSION
379 );
380 }
381
382 /**
383 * Outputs the admin notice that the user needs to upgrade their PHP version. It also
384 * auto-deactivates the plugin.
385 *
386 * @since 2.0.1
387 * @access public
388 * @return void
389 */
390 public function php_admin_notice() {
391
392 // Output notice.
393 printf(
394 '<div class="notice notice-error is-dismissible"><p><strong>%s</strong></p></div>',
395 esc_html( $this->get_min_php_message() )
396 );
397
398 // Make sure the plugin is deactivated.
399 deactivate_plugins( plugin_basename( __FILE__ ) );
400 }
401
402 /**
403 * Transition separate add-on plugins into the included add-ons
404 *
405 * @return void
406 */
407 public function migrate_addons() {
408
409 // Bail if we've already migrated the add-ons
410 if ( ! empty( get_option( 'members_addons_migrated' ) ) ) {
411 return;
412 }
413
414 $addons = array();
415
416 $plugins = array(
417 'members-acf-integration' => 'plugin.php',
418 'members-admin-access' => 'members-admin-access.php',
419 'members-block-permissions' => 'plugin.php',
420 'members-category-and-tag-caps' => 'plugin.php',
421 'members-core-create-caps' => 'members-core-create-caps.php',
422 'members-edd-integration' => 'plugin.php',
423 'members-givewp-integration' => 'plugin.php',
424 'members-meta-box-integration' => 'plugin.php',
425 'members-privacy-caps' => 'members-privacy-caps.php',
426 'members-role-hierarchy' => 'members-role-hierarchy.php',
427 'members-role-levels' => 'members-role-levels.php',
428 'members-woocommerce-integration' => 'plugin.php'
429 );
430
431 require_once ABSPATH . 'wp-admin/includes/file.php';
432 require_once ABSPATH . 'wp-admin/includes/plugin.php';
433
434 foreach ( $plugins as $dir => $file ) {
435 if ( is_plugin_active( "{$dir}/{$file}" ) ) {
436
437 // Deactive it
438 deactivate_plugins( "{$dir}/{$file}", true );
439
440 // Delete it
441 delete_plugins( array( "{$dir}/{$file}" ) );
442
443 // Make sure it's stored in our option for active add-ons
444 $addons[] = $dir;
445 }
446 }
447
448 if ( ! empty( $addons ) ) {
449 update_option( 'members_active_addons', $addons );
450 }
451
452 update_option( 'members_addons_migrated', true );
453 }
454
455 /**
456 * Initialize Administrator Rescue (Magic Link).
457 * Only runs when the class was loaded (i.e. PHP version requirement met).
458 *
459 * @since 3.2.20
460 * @access public
461 * @return void
462 */
463 public function init_rescue_magic_link() {
464 if ( class_exists( 'Members_Rescue_Magic_Link' ) ) {
465 new Members_Rescue_Magic_Link();
466 }
467 }
468
469 /**
470 * We need a way to run an add-on's activation hook since the add-ons are no longer separate plugins.
471 *
472 * @param string $addon Add-on directory name
473 *
474 * @return void
475 */
476 public function run_addon_activator( $addon ) {
477
478 if ( file_exists( trailingslashit( __DIR__ ) . "addons/{$addon}/src/Activator.php" ) ) {
479
480 // Require the add-on file
481 include trailingslashit( __DIR__ ) . "addons/{$addon}/src/Activator.php";
482
483 // Read the file contents into memory, and determine the namespace
484 $contents = file_get_contents( trailingslashit( __DIR__ ) . "addons/{$addon}/src/Activator.php" );
485 preg_match( '/[\r\n]namespace\W(.+);[\r\n]/', $contents, $matches );
486 $namespace = $matches[1];
487 // Run the activator
488 if ( ! empty( $namespace ) ) {
489 $namespace .= '\Activator';
490 $namespace::activate();
491 }
492 }
493 }
494
495 public function block_editor_assets() {
496 // Block-level upsell controls conflict with Content Permissions in the block editor.
497 if ( members_content_permissions_enabled() ) {
498 return;
499 }
500
501 $active_addons = get_option( 'members_active_addons', array() );
502 if ( ! in_array( 'members-block-permissions', $active_addons ) && ! members_is_memberpress_active() ) {
503 wp_enqueue_script( 'block-editor-mp-upsell', plugin_dir_url( __FILE__ ) . '/addons/members-block-permissions/public/js/upsell.js' , array(
504 'wp-compose',
505 'wp-element',
506 'wp-hooks',
507 'wp-components'
508 ), null, true );
509 wp_localize_script( 'block-editor-mp-upsell', 'membersUpsell', array(
510 'title' => __( 'Permissions', 'members' ),
511 'message' => __( 'To protect this block by paid membership or centrally with a content protection rule, add MemberPress.', 'members' )
512 ) );
513 }
514 }
515
516 /**
517 * AJAX handler for resetting roles to default WordPress roles.
518 * Only removes roles that were created via the Members UI; roles from other
519 * plugins (e.g. WooCommerce) are left unchanged.
520 *
521 * @since 3.2.18
522 * @access public
523 * @return void
524 */
525 public function reset_roles() {
526
527 // Verify nonce
528 if ( ! wp_verify_nonce( $_POST['nonce'] ?? null, 'members_reset_roles' ) ) {
529 wp_send_json_error();
530 }
531
532 // Check user capabilities
533 if ( ! current_user_can( 'manage_options' ) ) {
534 wp_send_json_error();
535 }
536
537 $default_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
538
539 $members_created_roles = members_get_created_roles();
540 $default_role_option = get_option( 'default_role', 'subscriber' );
541
542 // If the site default is a Members-created role we're about to remove, set default to subscriber.
543 if ( in_array( $default_role_option, $members_created_roles, true ) ) {
544 update_option( 'default_role', 'subscriber' );
545 $default_role_option = 'subscriber';
546 }
547
548 // Fallback for reassigning users: use site default if it's a core role, else subscriber.
549 $fallback_role = in_array( $default_role_option, $default_roles, true ) ? $default_role_option : 'subscriber';
550
551 foreach ( $members_created_roles as $role_name ) {
552 if ( in_array( $role_name, $default_roles, true ) ) {
553 continue;
554 }
555 if ( ! get_role( $role_name ) ) {
556 members_untrack_created_role( $role_name );
557 continue;
558 }
559 $users = get_users( array( 'role' => $role_name ) );
560 if ( ! empty( $users ) ) {
561 foreach ( $users as $user ) {
562 if ( count( $user->roles ) <= 1 ) {
563 $user->set_role( $fallback_role );
564 } else {
565 $user->remove_role( $role_name );
566 }
567 }
568 }
569 remove_role( $role_name );
570 members_untrack_created_role( $role_name );
571 }
572
573 // Reset the five default WordPress roles to core defaults.
574 foreach ( $default_roles as $role_name ) {
575 remove_role( $role_name );
576 }
577
578 // Re-add default roles using WordPress core
579 require_once( ABSPATH . 'wp-admin/includes/schema.php' );
580 populate_roles();
581
582 // Add Members plugin capabilities back to administrator (mirror activation logic)
583 $admin_role = get_role( 'administrator' );
584 if ( $admin_role ) {
585 $admin_role->add_cap( 'restrict_content' ); // Edit per-post content permissions
586 $admin_role->add_cap( 'list_roles' ); // View roles in backend
587 if ( ! is_multisite() ) {
588 $admin_role->add_cap( 'create_roles' ); // Create new roles
589 $admin_role->add_cap( 'delete_roles' ); // Delete existing roles
590 $admin_role->add_cap( 'edit_roles' ); // Edit existing roles/caps
591 }
592 }
593
594 wp_send_json_success();
595 }
596 }
597
598 /**
599 * Gets the instance of the `Members_Plugin` class. This function is useful for quickly grabbing data
600 * used throughout the plugin.
601 *
602 * @since 1.0.0
603 * @access public
604 * @return object
605 */
606 function members_plugin() {
607 return Members_Plugin::get_instance();
608 }
609
610 // Let's roll!
611 members_plugin();
612