diff --git a/.github/workflows/wp_plugin_release.yml b/.github/workflows/wp_plugin_release.yml new file mode 100644 index 0000000..5f05b0e --- /dev/null +++ b/.github/workflows/wp_plugin_release.yml @@ -0,0 +1,16 @@ +name: Deploy to WordPress.org +on: + push: + tags: + - "*" +jobs: + tag: + name: New tag + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: WordPress Plugin Deploy + uses: 10up/action-wordpress-plugin-deploy@stable + env: + SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} + SVN_USERNAME: ${{ secrets.SVN_USERNAME }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa454e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +### WordPress template +*.log +.htaccess + +# PHP Storm files +.idea + +release.sh \ No newline at end of file diff --git a/controllers/checklist.php b/controllers/checklist.php index fabf181..76ad4b1 100644 --- a/controllers/checklist.php +++ b/controllers/checklist.php @@ -8,7 +8,7 @@ class MDMR_Checklist_Controller { /** * The model object. * - * @var object + * @var \MDMR_Model $model */ var $model; @@ -27,9 +27,10 @@ public function __construct( $model ) { * @param string $hook The current admin screen. */ public function remove_dropdown( $hook ) { - if ( $hook != 'user-edit.php' ) + if ( 'user-edit.php' !== $hook && 'user-new.php' !== $hook) { return; - wp_enqueue_script( 'md-multiple-roles', MDMR_URL . 'views/js/scripts.js', array( 'jquery' ) ); + } + wp_enqueue_script( 'md-multiple-roles', MDMR_URL . 'views/js/scripts.js', array( 'jquery' ), '1.0' ); } /** @@ -40,15 +41,16 @@ public function remove_dropdown( $hook ) { */ public function output_checklist( $user ) { - if ( !$this->model->can_update_roles() ) + if ( ! $this->model->can_update_roles() ) { return; + } wp_nonce_field( 'update-md-multiple-roles', 'md_multiple_roles_nonce' ); - $roles = $this->model->get_roles(); - $user_roles = $user->roles; + $roles = $this->model->get_editable_roles(); + $user_roles = ( isset( $user->roles ) ) ? $user->roles : null; - include( MDMR_PATH . 'views/checklist.html.php' ); + include( apply_filters( 'mdmr_checklist_template', MDMR_PATH . 'views/checklist.html.php' ) ); } @@ -60,16 +62,128 @@ public function output_checklist( $user ) { */ public function process_checklist( $user_id ) { - if ( isset( $_POST['md_multiple_roles_nonce'] ) && !wp_verify_nonce( $_POST['md_multiple_roles_nonce'], 'update-md-multiple-roles' ) ) + // The checklist is not always rendered when this method is triggered on 'profile_update' (i.e. when updating a profile programmatically), + // First check that the 'md_multiple_roles_nonce' is available, else bail. If we continue to process and update_roles(), all user roles will be lost. + // We check for 'md_multiple_roles_nonce' rather than 'md_multiple_roles' as this input/variable will be empty if all role inputs are left unchecked. + if ( ! isset( $_POST['md_multiple_roles_nonce'] ) ) { return; + } - if ( !$this->model->can_update_roles() ) + if ( ! wp_verify_nonce( $_POST['md_multiple_roles_nonce'], 'update-md-multiple-roles' ) ) { return; + } - $new_roles = isset( $_POST['md_multiple_roles'] ) ? $_POST['md_multiple_roles'] : array(); + if ( ! $this->model->can_update_roles() ) { + return; + } + + $new_roles = ( isset( $_POST['md_multiple_roles'] ) && is_array( $_POST['md_multiple_roles'] ) ) ? $_POST['md_multiple_roles'] : array(); $this->model->update_roles( $user_id, $new_roles ); + } + /** + * Add multiple roles in the $meta array in wp_signups db table + * + * @since 1.1.4 + * + * @param $user + * @param $user_email + * @param $key + * @param $meta + * + * @return void|WP_Error + */ + public function mu_add_roles_in_signup_meta( $user, $user_email, $key, $meta ) { + if ( ! wp_verify_nonce( $_POST['md_multiple_roles_nonce'], 'update-md-multiple-roles' ) ) { + return; + } + + if ( ! $this->model->can_update_roles() ) { + return; + } + + $new_roles = ( isset( $_POST['md_multiple_roles'] ) && is_array( $_POST['md_multiple_roles'] ) ) ? $_POST['md_multiple_roles'] : array(); + if ( empty( $new_roles ) ) { + return; + } + + global $wpdb; + + // Get user signup + // Suppress errors in case the table doesn't exist + $suppress = $wpdb->suppress_errors(); + $signup = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->signups} WHERE user_email = %s", $user_email ) ); + $wpdb->suppress_errors( $suppress ); + + if ( empty( $signup ) || is_wp_error( $signup ) ) { + return new WP_Error( 'md_get_user_signups_failed' ); + } + + // Add multiple roles to a new array in meta var + $meta = maybe_unserialize( $meta ); + $meta['md_roles'] = $new_roles; + $meta = maybe_serialize( $meta ); + + // Update user signup with good meta + $where = array( 'signup_id' => (int) $signup->signup_id ); + $where_format = array( '%d' ); + $formats = array( '%s' ); + $fields = array( 'meta' => $meta ); + $result = $wpdb->update( $wpdb->signups, $fields, $where, $formats, $where_format ); + + // Check for errors + if ( empty( $result ) && ! empty( $wpdb->last_error ) ) { + return new WP_Error( 'md_update_user_signups_failed' ); + } + } + + /** + * Add roles in signup meta with WP 4.8 filter : better method + * + * @since 1.2.0 + * + * @param $meta + * @param $domain + * @param $path + * @param $title + * @param $user + * @param $user_email + * @param $key + */ + public function mu_add_roles_in_signup_meta_recently( $meta, $domain, $path, $title, $user, $user_email, $key ) { + if ( ! wp_verify_nonce( $_POST['md_multiple_roles_nonce'], 'update-md-multiple-roles' ) ) { + return; + } + + if ( ! $this->model->can_update_roles() ) { + return; + } + + $new_roles = ( isset( $_POST['md_multiple_roles'] ) && is_array( $_POST['md_multiple_roles'] ) ) ? $_POST['md_multiple_roles'] : array(); + if ( empty( $new_roles ) ) { + return; + } + + $meta['md_roles'] = $new_roles; + + return $meta; + + } + + /** + * Add multiple roles after user activation + * + * @since 1.1.4 + * + * @param $user_id + * @param $password + * @param $meta + */ + public function mu_add_roles_after_activation( $user_id, $password, $meta ) { + if ( ! empty( $meta['md_roles'] ) ) { + $this->model->update_roles( $user_id, $meta['md_roles'] ); + } } -} \ No newline at end of file +} diff --git a/controllers/column.php b/controllers/column.php index 95c3ef1..ab00160 100644 --- a/controllers/column.php +++ b/controllers/column.php @@ -8,7 +8,7 @@ class MDMR_Column_Controller { /** * The model object. * - * @var object + * @var \MDMR_Model $model */ var $model; @@ -29,7 +29,7 @@ public function __construct( $model ) { */ public function replace_column( $columns ) { unset( $columns['role'] ); - $columns['md_multiple_roles_column'] = 'Roles'; + $columns['md_multiple_roles_column'] = __( 'Roles', 'multiple-roles' ); return $columns; } @@ -43,13 +43,14 @@ public function replace_column( $columns ) { */ public function output_column_content( $output, $column, $user_id ) { - if ( $column !== 'md_multiple_roles_column' ) + if ( 'md_multiple_roles_column' !== $column ) { return $output; + } $roles = $this->model->get_user_roles( $user_id ); ob_start(); - include( MDMR_PATH . 'views/column.html.php' ); + include( apply_filters( 'mdmr_column_template', MDMR_PATH . 'views/column.html.php' ) ); return ob_get_clean(); } diff --git a/languages/multiple-roles.pot b/languages/multiple-roles.pot new file mode 100644 index 0000000..603884d --- /dev/null +++ b/languages/multiple-roles.pot @@ -0,0 +1,34 @@ +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Multiple Roles\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-26 14:09+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Florian TIAR \n" +"Language-Team: Florian TIAR \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n == 1 ? 0 : 1;\n" +"X-Generator: Poedit 1.8.6\n" +"X-Poedit-KeywordsList: _e;__;_x;esc_html__;esc_html_e;_n;_ex;esc_html_x;" +"esc_attr_x;translate;esc_attr__;esc_attr_e\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-SourceCharset: UTF-8\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: node_modules\n" +"X-Poedit-SearchPathExcluded-1: assets\n" +"X-Poedit-SearchPathExcluded-2: fields\n" + +#: controllers/column.php:32 views/checklist.html.php:11 +msgid "Roles" +msgstr "" + +#: views/checklist.html.php:8 +msgid "Permissions" +msgstr "" + +#: views/column.html.php:14 +msgid "None" +msgstr "" diff --git a/model.php b/model.php index 6a6b03c..7c60789 100644 --- a/model.php +++ b/model.php @@ -11,7 +11,22 @@ class MDMR_Model { */ public function get_roles() { global $wp_roles; - return $wp_roles->role_names; + return apply_filters( 'mdmr_get_roles', $wp_roles->role_names ); + } + + /** + * Get all editable roles by the current user + * + * @return array editable roles + */ + public function get_editable_roles() { + $editable_roles = get_editable_roles(); + $final_roles = array(); + foreach ( $editable_roles as $key => $role ) { + $final_roles[$key] = $role['name']; + } + + return apply_filters( 'mdmr_get_editable_roles', (array) $final_roles ); } /** @@ -22,21 +37,22 @@ public function get_roles() { */ public function get_user_roles( $user = 0 ) { - if ( $user && is_int( $user ) ) - $user = get_user_by( 'id', $user ); + if ( ! $user ) { + return array(); + } - if ( !$user ) + $user = get_user_by( 'id', (int) $user ); + if ( empty( $user->roles ) ) { return array(); + } - global $wp_roles; + $all_roles = $this->get_roles(); $roles = array(); - foreach( $user->roles as $role ) { - $roles[$role] = $wp_roles->role_names[$role]; + $roles[$role] = $all_roles[$role]; } - return $roles; - + return apply_filters( 'mdmr_get_user_roles', $roles ); } /** @@ -44,19 +60,32 @@ public function get_user_roles( $user = 0 ) { * * @param integer $user_id The WordPress user ID. * @param array $roles The new array of roles for the user. + * + * @return bool */ public function update_roles( $user_id = 0, $roles = array() ) { + do_action( 'mdmr_before_update_roles', $user_id, $roles ); + $roles = array_map( 'sanitize_key', (array) $roles ); - $user = get_user_by( 'id', $user_id ); + $roles = array_filter( (array) $roles, 'get_role' ); + + $user = get_user_by( 'id', (int) $user_id ); - // remove all roles - $user->set_role( '' ); + // Remove all editable roles + $editable = get_editable_roles(); + $editable_roles = is_array($editable) ? array_keys($editable) : array(); + foreach( $editable_roles as $role ) { + $user->remove_role( $role ); + } foreach( $roles as $role ) { $user->add_role( $role ); } + do_action( 'mdmr_after_update_roles', $user_id, $roles, $user->roles ); + + return true; } /** @@ -68,10 +97,11 @@ public function update_roles( $user_id = 0, $roles = array() ) { */ public function can_update_roles() { - if ( is_network_admin() - || !current_user_can( 'edit_users' ) - || ( defined( 'IS_PROFILE_PAGE' ) && IS_PROFILE_PAGE && !current_user_can( 'manage_sites' ) ) ) - return false; + do_action( 'mdmr_before_can_update_roles' ); + + if ( is_network_admin() || ! current_user_can( 'promote_users' ) || ( defined( 'IS_PROFILE_PAGE' ) && IS_PROFILE_PAGE && ! current_user_can( 'manage_sites' ) ) ) { + return false; + } return true; diff --git a/multiple-roles.php b/multiple-roles.php index 27337e2..0d366fa 100644 --- a/multiple-roles.php +++ b/multiple-roles.php @@ -2,9 +2,12 @@ /* Plugin Name: Multiple Roles Description: Allow users to have multiple roles on one site. -Version: 1.0 -Author: Michael Dance -Author URI: http://mikedance.com +Version: 1.3.4 +Author: Florian TIAR +Author URI: http://tiar-florian.fr +Plugin URI: https://wordpress.org/plugins/multiple-roles/ +Github URI: https://github.com/Mahjouba91/multiple-roles +Text Domain: multiple-roles */ define( 'MDMR_PATH', plugin_dir_path( __FILE__ ) ); @@ -13,25 +16,37 @@ /** * Load files and add hooks to get things rolling. */ -function md_multiple_roles() { - - require_once( MDMR_PATH . 'model.php' ); - require_once( MDMR_PATH . 'controllers/checklist.php' ); - require_once( MDMR_PATH . 'controllers/column.php' ); - - $model = new MDMR_Model(); - - $checklist = new MDMR_Checklist_Controller( $model ); - add_action( 'admin_enqueue_scripts', array( $checklist, 'remove_dropdown' ) ); - add_action( 'show_user_profile', array( $checklist, 'output_checklist' ) ); - add_action( 'edit_user_profile', array( $checklist, 'output_checklist' ) ); - add_action( 'profile_update', array( $checklist, 'process_checklist' ) ); +require_once( MDMR_PATH . 'model.php' ); +require_once( MDMR_PATH . 'controllers/checklist.php' ); +require_once( MDMR_PATH . 'controllers/column.php' ); + +$model = new MDMR_Model(); + +$checklist = new MDMR_Checklist_Controller( $model ); +add_action( 'admin_enqueue_scripts', array( $checklist, 'remove_dropdown' ) ); +add_action( 'show_user_profile', array( $checklist, 'output_checklist' ) ); +add_action( 'edit_user_profile', array( $checklist, 'output_checklist' ) ); +add_action( 'user_new_form', array( $checklist, 'output_checklist' ) ); +add_action( 'profile_update', array( $checklist, 'process_checklist' ) ); + +// For new user form (in Backoffice) +// In multisite, user_register hook is too early so wp_mu_activate_user add user role after +if ( is_multisite() ) { + if ( version_compare( get_bloginfo( 'version' ), '4.8', '>=' ) ) { + add_filter( 'signup_site_meta', array( $checklist, 'mu_add_roles_in_signup_meta_recently' ), 10, 7 ); + } else { + add_action( 'after_signup_user', array( $checklist, 'mu_add_roles_in_signup_meta' ), 10, 4 ); + } + add_action( 'wpmu_activate_user', array( $checklist, 'mu_add_roles_after_activation' ), 10, 3 ); +} else { add_action( 'user_register', array( $checklist, 'process_checklist' ) ); +} - $column = new MDMR_Column_Controller( $model ); - add_filter( 'manage_users_columns', array( $column, 'replace_column' ), 11 ); - add_filter( 'manage_users_custom_column', array( $column, 'output_column_content' ), 10, 3 ); +$column = new MDMR_Column_Controller( $model ); +add_filter( 'manage_users_columns', array( $column, 'replace_column' ), 11 ); +add_filter( 'manage_users_custom_column', array( $column, 'output_column_content' ), 10, 3 ); +add_action( 'init', 'load_translation' ); +function load_translation() { + load_plugin_textdomain( 'multiple-roles', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); } - -md_multiple_roles(); \ No newline at end of file diff --git a/readme.txt b/readme.txt index fb091ba..e70baa7 100644 --- a/readme.txt +++ b/readme.txt @@ -1,9 +1,11 @@ === Multiple Roles === -Contributors: SeventhSteel +Contributors: SeventhSteel, mista-flo, cneumann Tags: multiple roles, multiple roles per user, user roles, edit user roles, edit roles, more than one role, more than one role per user, more than one role for each user, many roles per user, unlimited roles Requires at least: 3.1 -Tested up to: 4.2.1 -Stable tag: 1.0 +Tested up to: 5.7.2 +Stable tag: 1.3.4 +Requires PHP: 5.4 +Donate link: https://www.paypal.me/FlorianTIAR/5 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -13,9 +15,13 @@ Allow users to have multiple roles on one site. This plugin allows you to select multiple roles for a user - something that WordPress already supports "under the hood", but doesn't provide a user interface for. -User edit screens will display a checklist of roles instead of the default role dropdown. The main user list screen will also display all roles a user has. +User edit and Add new user screens will display a checklist of roles instead of the default role dropdown. The main user list screen will also display all roles a user has. -That's it. No extra settings. This plugin is a good complement to other user plugins that don't support multiple roles, such as Members. +It also supports well Multisite mode. + +That's it. No extra settings. + +If you want to contribute to this plugin, feel free to check the Github repository : https://github.com/Mahjouba91/multiple-roles == Installation == @@ -37,15 +43,15 @@ That's it. No extra settings. This plugin is a good complement to other user plu = Who can edit users roles? = -Anyone with the `edit_users` capability. By default, that means only administrators and, on multi-site networks, super admins. +Anyone with the `promote_users` capability. By default, that means only administrators and network administrators on multi-site. = Can you edit your own roles? = -If you're a network admin on a multi-site setup, yes, you can edit your roles in sites on that network. Otherwise, no. This is how WordPress works normally too. +If you're a network administrator on a multi-site setup, yes, you can edit your roles in sites of that network. Otherwise, no. This is how WordPress works normally too. = I'm on the user edit screen - where's the checklist of roles? = -It's underneath the default profile stuff, under the heading "Permissions". If you still can't find it, you might be on your own profile page, or you might not have the `edit_users` capability. +It's underneath the default profile stuff, under the heading "Permissions". If you still can't find it, you might be on your own profile page, or you might not have the `promote_users` capability. = Can you remove all roles from a user? = @@ -58,5 +64,61 @@ Sure. The user will still be able to log in and out, but won't be able to access == Changelog == += 1.3.4 = +* 25th may 2022 +* Add new plugin maintainer: cneumann + += 1.3.2 = +* 30th june 2021 +* Test the plugin against WordPress 5.7 +* Fix the use of nonce for two functions + += 1.3.1 = +* 1st july 2020 +* Test the plugin against WordPress 5.4 +* Fix an issue when the user role could be lost because of a wrong check in the backend + += 1.3.0 = +* 12 april 2018 +* Use 'promote_users' cap instead of ‘edit_users’ +* Fixed bug preventing us from unsetting a user's roles +* Only remove get_editable_roles() roles on update +* Thanks to thomasfw for the contributions + += 1.2.0 = +* 21 august 2017 +* Check compatibilty with WP 4.8.1 +* Translation of roles names : thanks to Benjamin Niess +* Mutlisite enhancement : Use a WP 4.8 filter to easier edit signup user meta + += 1.1.4 = +* 23 december 2016 +* Fix fatal error in new user in single site : After adding an user, a wp_die error was shown "You can’t give users that role", it was due to changes in 1.1.2 +* Workaround to handle multisite support without breaking single site features + += 1.1.3 = +* 22 december 2016 +* Fix fatal error in user update : After updating an user, a wp_die error was shown "You can’t give users that role", it was due to changes in 1.1.2 + += 1.1.2 = +* 21 december 2016 +* Fix bug in multisite : After adding a new user with email confirmation, the multiple roles were not set, so the user did not have any roles on the site + += 1.1.1 = +* 3 november 2016 +* Remove PHP closure to ensure Backward Compatibility with PHP versions < 5.3 + += 1.1 = +* 24 october 2016 +* New maintainer : Florian TIAR, you're strongly encouraged to update this plugin +* Add support of role checkbox in new user form (admin) +* Add Multisite support (for new user form) +* Add i18n support (text domain, translatable strings and pot file) +* Add some hooks (actions and filters) +* Fix issue where some low level users could add admin users +* Sanitize and escape all data +* Enhance UX of the form + = 1.0 = -* Initial release. \ No newline at end of file +* 2015 +* Initial release diff --git a/views/checklist.html.php b/views/checklist.html.php index 4a7ad40..7a9191a 100644 --- a/views/checklist.html.php +++ b/views/checklist.html.php @@ -5,21 +5,29 @@ * @var $roles array All WordPress roles in name => label pairs. * @var $user_roles array An array of role names belonging to the current user. */ -?>

Permissions

+$creating = isset( $_POST['createuser'] ); +$selected_roles = $creating && isset( $_POST['md_multiple_roles'] ) ? wp_unslash( $_POST['md_multiple_roles'] ) : ''; +?> +

- +
Roles - $label ) : ?> -