module_url = $this->get_module_url( __FILE__ ); // Register the User Groups module with Edit Flow. $args = array( 'title' => __( 'User Groups', 'edit-flow' ), 'short_description' => __( 'Organize your users into groups to mimic your organizational structure.', 'edit-flow' ), 'extended_description' => __( 'Configure user groups to organize all of the users on your site. Each user can be in many user groups and you can change them at any time.', 'edit-flow' ), 'module_url' => $this->module_url, 'img_url' => $this->module_url . 'lib/usergroups_s128.png', 'slug' => 'user-groups', 'default_options' => array( 'enabled' => 'on', 'post_types' => array( 'post' => 'on', 'page' => 'off', ), ), 'messages' => array( 'usergroup-added' => __( 'User group created. Feel free to add users to the usergroup.', 'edit-flow' ), 'usergroup-updated' => __( 'User group updated.', 'edit-flow' ), 'usergroup-missing' => __( "User group doesn't exist.", 'edit-flow' ), 'usergroup-deleted' => __( 'User group deleted.', 'edit-flow' ), ), 'configure_page_cb' => 'print_configure_view', 'configure_link_text' => __( 'Manage User Groups', 'edit-flow' ), 'autoload' => false, 'settings_help_tab' => array( 'id' => 'ef-user-groups-overview', 'title' => __( 'Overview', 'edit-flow' ), 'content' => __( '
For those with many people involved in the publishing process, user groups helps you keep them organized.
Currently, user groups are primarily used for subscribing a set of users to a post for notifications.
', 'edit-flow' ), ), 'settings_help_sidebar' => __( 'For more information:
', 'edit-flow' ), ); $this->module = EditFlow()->register_module( 'user_groups', $args ); } /** * Module startup */ /** * Initialize the rest of the stuff in the class if the module is active * * @since 0.7 */ public function init() { // Register the objects where we'll be storing data and relationships. $this->register_usergroup_objects(); $this->manage_usergroups_cap = apply_filters( 'ef_manage_usergroups_cap', $this->manage_usergroups_cap ); // Register our settings. add_action( 'admin_init', array( $this, 'register_settings' ) ); // Handle any adding, editing or saving. add_action( 'admin_init', array( $this, 'handle_add_usergroup' ) ); add_action( 'admin_init', array( $this, 'handle_edit_usergroup' ) ); add_action( 'admin_init', array( $this, 'handle_delete_usergroup' ) ); add_action( 'wp_ajax_inline_save_usergroup', array( $this, 'handle_ajax_inline_save_usergroup' ) ); // Usergroups can be managed from the User profile view. add_action( 'show_user_profile', array( $this, 'user_profile_page' ) ); add_action( 'edit_user_profile', array( $this, 'user_profile_page' ) ); add_action( 'user_profile_update_errors', array( $this, 'user_profile_update' ), 10, 3 ); // Javascript and CSS if we need it. add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) ); } /** * Load the capabilities onto users the first time the module is run * * @since 0.7 */ public function install() { // Add necessary capabilities to allow management of user groups. $usergroup_roles = array( 'administrator' => array( 'edit_usergroups' ), ); foreach ( $usergroup_roles as $role => $caps ) { $this->add_caps_to_role( $role, $caps ); } // Create our default usergroups. $default_usergroups = array( array( 'name' => __( 'Copy Editors', 'edit-flow' ), 'description' => __( 'Making sure the quality is top-notch.', 'edit-flow' ), ), array( 'name' => __( 'Photographers', 'edit-flow' ), 'description' => __( 'Capturing the story visually.', 'edit-flow' ), ), array( 'name' => __( 'Reporters', 'edit-flow' ), 'description' => __( 'Out in the field, writing stories.', 'edit-flow' ), ), array( 'name' => __( 'Section Editors', 'edit-flow' ), 'description' => __( 'Providing feedback and direction.', 'edit-flow' ), ), ); foreach ( $default_usergroups as $args ) { $this->add_usergroup( $args ); } } /** * Upgrade our data in case we need to. * * @since 0.7 * * @param string $previous_version The previous plugin version. */ public function upgrade( $previous_version ) { global $edit_flow; // Upgrade path to v0.7. if ( version_compare( $previous_version, '0.7', '<' ) ) { global $wpdb; // Set all of the user group terms to our new taxonomy. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-time upgrade query. $wpdb->update( $wpdb->term_taxonomy, array( 'taxonomy' => self::taxonomy_key ), array( 'taxonomy' => 'following_usergroups' ) ); // Get all of the users who are a part of user groups and assign them to their new user group values. $query = "SELECT * FROM $wpdb->usermeta WHERE meta_key='wp_ef_usergroups';"; // There's no userdata here in this query and it's an upgrade query. // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $usergroup_users = $wpdb->get_results( $query ); // Sort all of the users based on their usergroup(s). $users_to_add = array(); foreach ( (array) $usergroup_users as $usergroup_user ) { if ( is_object( $usergroup_user ) ) { $users_to_add[ $usergroup_user->meta_value ][] = (int) $usergroup_user->user_id; } } // Add user IDs to each usergroup. foreach ( $users_to_add as $usergroup_slug => $users_array ) { $usergroup = $this->get_usergroup_by( 'slug', $usergroup_slug ); $this->add_users_to_usergroup( $users_array, $usergroup->term_id ); } // Update the term slugs for each user group. $all_usergroups = $this->get_usergroups(); foreach ( $all_usergroups as $usergroup ) { $new_slug = str_replace( 'ef_', self::term_prefix, $usergroup->slug ); $this->update_usergroup( $usergroup->term_id, array( 'slug' => $new_slug ) ); } // Delete all of the previous usermeta values. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-time upgrade query. $wpdb->query( "DELETE FROM $wpdb->usermeta WHERE meta_key='wp_ef_usergroups';" ); // Technically we've run this code before so we don't want to auto-install new data. $edit_flow->update_module_option( $this->module->name, 'loaded_once', true ); } // Upgrade path to v0.7.4. if ( version_compare( $previous_version, '0.7.4', '<' ) ) { // Usergroup descriptions become base64_encoded, instead of maybe json_encoded. $this->upgrade_074_term_descriptions( self::taxonomy_key ); } } /** * Register usergroup objects. * * Individual Usergroups are stored using a custom taxonomy. * Posts are associated with usergroups based on taxonomy relationship. * User associations are stored serialized in the term's description field. * * @since 0.7 * * @uses register_taxonomy() */ public function register_usergroup_objects() { // Load the currently supported post types so we only register against those. $supported_post_types = $this->get_post_types_for_module( $this->module ); // Use a taxonomy to manage relationships between posts and usergroups. $args = array( 'public' => false, 'rewrite' => false, ); register_taxonomy( self::taxonomy_key, $supported_post_types, $args ); } /** * Enqueue necessary admin scripts * * @since 0.7 * * @uses wp_enqueue_script() */ public function enqueue_admin_scripts() { if ( $this->is_post_management_page( $this->module->name ) || $this->is_whitelisted_settings_view( $this->module->name ) ) { wp_enqueue_script( 'jquery-listfilterizer' ); wp_enqueue_script( 'edit-flow-user-groups-js', $this->module_url . 'lib/user-groups.js', array( 'jquery', 'jquery-listfilterizer' ), EDIT_FLOW_VERSION, true ); } if ( $this->is_whitelisted_settings_view( $this->module->name ) ) { wp_enqueue_script( 'edit-flow-user-groups-configure-js', $this->module_url . 'lib/user-groups-configure.js', array( 'jquery' ), EDIT_FLOW_VERSION, true ); } } /** * Enqueue necessary admin styles, but only on the proper pages * * @since 0.7 * * @uses wp_enqueue_style() */ public function enqueue_admin_styles() { if ( $this->is_post_management_page( $this->module->name ) || $this->is_whitelisted_settings_view( $this->module->name ) ) { wp_enqueue_style( 'jquery-listfilterizer' ); wp_enqueue_style( 'edit-flow-user-groups-css', $this->module_url . 'lib/user-groups.css', false, EDIT_FLOW_VERSION ); } } /** * Module ??? */ /** * Handles a POST request to add a new Usergroup. Redirects to edit view after * for admin to add users to usergroup * Hooked into 'admin_init' and kicks out right away if no action * * @since 0.7 */ public function handle_add_usergroup() { // phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended -- Nonce verified below. if ( ! isset( $_POST['submit'], $_POST['form-action'], $_GET['page'] ) || $_GET['page'] != $this->module->settings_slug || 'add-usergroup' != $_POST['form-action'] ) { return; } // phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce value passed directly to wp_verify_nonce(). if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'add-usergroup' ) ) { wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); } if ( ! current_user_can( $this->manage_usergroups_cap ) ) { wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); } // Sanitize all of the user-entered values. // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Values are sanitized below. $name = ( isset( $_POST['name'] ) ) ? sanitize_text_field( trim( $_POST['name'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Values are sanitized below. $description = ( isset( $_POST['description'] ) ) ? stripslashes( wp_filter_nohtml_kses( trim( $_POST['description'] ) ) ) : ''; EditFlow()->settings->form_errors = array(); /* * Form validation for adding new Usergroup. * * Details: * - 'name' is a required field, but can't match an existing name or slug. Needs to be 40 characters or less. * - "description" can accept a limited amount of HTML, and is optional. */ // Field is required. if ( empty( $name ) ) { EditFlow()->settings->form_errors['name'] = __( 'Please enter a name for the user group.', 'edit-flow' ); } // Check to ensure a term with the same name doesn't exist. if ( $this->get_usergroup_by( 'name', $name ) ) { EditFlow()->settings->form_errors['name'] = __( 'Name already in use. Please choose another.', 'edit-flow' ); } // Check to ensure a term with the same slug doesn't exist. if ( $this->get_usergroup_by( 'slug', sanitize_title( $name ) ) ) { EditFlow()->settings->form_errors['name'] = __( 'Name conflicts with slug for another term. Please choose again.', 'edit-flow' ); } if ( strlen( $name ) > 40 ) { EditFlow()->settings->form_errors['name'] = __( 'User group name cannot exceed 40 characters. Please try a shorter name.', 'edit-flow' ); } // Kick out if there are any errors. if ( count( EditFlow()->settings->form_errors ) ) { $_REQUEST['error'] = 'form-error'; return; } // Try to add the Usergroup. $args = array( 'name' => $name, 'description' => $description, ); $usergroup = $this->add_usergroup( $args ); if ( is_wp_error( $usergroup ) ) { wp_die( esc_html__( 'Error adding usergroup.', 'edit-flow' ) ); } $args = array( 'action' => 'edit-usergroup', 'usergroup-id' => $usergroup->term_id, 'message' => 'usergroup-added', ); $redirect_url = $this->get_link( $args ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- Redirect URL is constructed internally. wp_redirect( $redirect_url ); exit; } /** * Handles a POST request to edit a Usergroup. * * Hooked into 'admin_init' and kicks out right away if no action. * * @since 0.7 */ public function handle_edit_usergroup() { // phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended -- Nonce verified below. if ( ! isset( $_POST['submit'], $_POST['form-action'], $_GET['page'] ) || $_GET['page'] != $this->module->settings_slug || 'edit-usergroup' != $_POST['form-action'] ) { return; } // phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce value passed directly to wp_verify_nonce(). if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'edit-usergroup' ) ) { wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); } if ( ! current_user_can( $this->manage_usergroups_cap ) ) { wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); } $usergroup_id = isset( $_POST['usergroup_id'] ) ? (int) $_POST['usergroup_id'] : 0; $existing_usergroup = $this->get_usergroup_by( 'id', $usergroup_id ); if ( ! $existing_usergroup ) { wp_die( esc_html( $this->module->messages['usergroup-missing'] ) ); } // Sanitize all of the user-entered values. // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Values are sanitized below. $name = isset( $_POST['name'] ) ? sanitize_text_field( trim( $_POST['name'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Values are sanitized below. $description = isset( $_POST['description'] ) ? stripslashes( wp_filter_nohtml_kses( trim( $_POST['description'] ) ) ) : ''; EditFlow()->settings->form_errors = array(); /* * Form validation for editing a Usergroup. * * Details: * - 'name' is a required field, but can't match an existing name or slug. Needs to be 40 characters or less. * - "description" can accept a limited amount of HTML, and is optional. */ // Field is required. if ( empty( $name ) ) { EditFlow()->settings->form_errors['name'] = __( 'Please enter a name for the user group.', 'edit-flow' ); } // Check to ensure a term with the same name doesn't exist. $search_term = $this->get_usergroup_by( 'name', $name ); if ( is_object( $search_term ) && (int) $search_term->term_id !== (int) $existing_usergroup->term_id ) { EditFlow()->settings->form_errors['name'] = __( 'Name already in use. Please choose another.', 'edit-flow' ); } // Check to ensure a term with the same slug doesn't exist. $search_term = $this->get_usergroup_by( 'slug', sanitize_title( $name ) ); if ( is_object( $search_term ) && (int) $search_term->term_id !== (int) $existing_usergroup->term_id ) { EditFlow()->settings->form_errors['name'] = __( 'Name conflicts with slug for another term. Please choose again.', 'edit-flow' ); } if ( strlen( $name ) > 40 ) { EditFlow()->settings->form_errors['name'] = __( 'User group name cannot exceed 40 characters. Please try a shorter name.', 'edit-flow' ); } // Kick out if there are any errors. if ( count( EditFlow()->settings->form_errors ) ) { $_REQUEST['error'] = 'form-error'; return; } // Try to edit the Usergroup. $args = array( 'name' => $name, 'description' => $description, ); // Gracefully handle the case where all users have been unsubscribed from the user group. // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Values are sanitized with intval. $users = isset( $_POST['usergroup_users'] ) ? (array) $_POST['usergroup_users'] : array(); $users = array_map( 'intval', $users ); $usergroup = $this->update_usergroup( $existing_usergroup->term_id, $args, $users ); if ( is_wp_error( $usergroup ) ) { wp_die( esc_html__( 'Error updating user group.', 'edit-flow' ) ); } $args = array( 'message' => 'usergroup-updated', ); $redirect_url = $this->get_link( $args ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- Redirect URL is constructed internally. wp_redirect( $redirect_url ); exit; } /** * Handles a request to delete a Usergroup. * * Hooked into 'admin_init' and kicks out right away if no action. * * @since 0.7 */ public function handle_delete_usergroup() { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Nonce verified below. if ( ! isset( $_GET['page'], $_GET['action'], $_GET['usergroup-id'] ) || $_GET['page'] != $this->module->settings_slug || 'delete-usergroup' != $_GET['action'] ) { return; } // phpcs:enable WordPress.Security.NonceVerification.Recommended // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce value passed directly to wp_verify_nonce(). if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'], 'delete-usergroup' ) ) { wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); } if ( ! current_user_can( $this->manage_usergroups_cap ) ) { wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); } $result = $this->delete_usergroup( (int) $_GET['usergroup-id'] ); if ( ! $result || is_wp_error( $result ) ) { wp_die( esc_html__( 'Error deleting user group.', 'edit-flow' ) ); } $redirect_url = $this->get_link( array( 'message' => 'usergroup-deleted' ) ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- Redirect URL is constructed internally. wp_redirect( $redirect_url ); exit; } /** * Handle the request to update a given Usergroup via inline edit. * * @since 0.7 */ public function handle_ajax_inline_save_usergroup() { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce value passed directly to wp_verify_nonce(). if ( ! isset( $_POST['inline_edit'] ) || ! wp_verify_nonce( $_POST['inline_edit'], 'usergroups-inline-edit-nonce' ) ) { wp_die( esc_html( $this->module->messages['nonce-failed'] ) ); } if ( ! current_user_can( $this->manage_usergroups_cap ) ) { wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); } $usergroup_id = isset( $_POST['usergroup_id'] ) ? (int) $_POST['usergroup_id'] : 0; $existing_term = $this->get_usergroup_by( 'id', $usergroup_id ); if ( ! $existing_term ) { wp_die( esc_html( $this->module->messages['usergroup-missing'] ) ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Values are sanitized below. $name = isset( $_POST['name'] ) ? sanitize_text_field( trim( $_POST['name'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPressVIPMinimum.Functions.StripTagsRegistry.strip_tags -- Values are sanitized. $description = isset( $_POST['description'] ) ? stripslashes( wp_filter_nohtml_kses( trim( $_POST['description'] ) ) ) : ''; // Form validation for editing Usergroup. // Check if name field was filled in. if ( empty( $name ) ) { $change_error = new WP_Error( 'invalid', esc_html__( 'Please enter a name for the user group.', 'edit-flow' ) ); wp_die( esc_html( $change_error->get_error_message() ) ); } // Check that the name doesn't exceed 40 chars. if ( strlen( $name ) > 40 ) { $change_error = new WP_Error( 'invalid', esc_html__( 'User group name cannot exceed 40 characters. Please try a shorter name.', 'edit-flow' ) ); wp_die( esc_html( $change_error->get_error_message() ) ); } // Check to ensure a term with the same name doesn't exist. $search_term = $this->get_usergroup_by( 'name', $name ); if ( is_object( $search_term ) && (int) $search_term->term_id !== (int) $existing_term->term_id ) { $change_error = new WP_Error( 'invalid', esc_html__( 'Name already in use. Please choose another.', 'edit-flow' ) ); wp_die( esc_html( $change_error->get_error_message() ) ); } // Check to ensure a term with the same slug doesn't exist. $search_term = $this->get_usergroup_by( 'slug', sanitize_title( $name ) ); if ( is_object( $search_term ) && (int) $search_term->term_id !== (int) $existing_term->term_id ) { $change_error = new WP_Error( 'invalid', esc_html__( 'Name conflicts with slug for another term. Please choose again.', 'edit-flow' ) ); wp_die( esc_html( $change_error->get_error_message() ) ); } // Prepare the term name and description for saving. $args = array( 'name' => $name, 'description' => $description, ); $return = $this->update_usergroup( $existing_term->term_id, $args ); if ( ! is_wp_error( $return ) ) { set_current_screen( 'edit-usergroup' ); $wp_list_table = new EF_Usergroups_List_Table(); $wp_list_table->prepare_items(); // single_row() echoes its own output; column_* callbacks are // responsible for escaping, as per WP_List_Table's contract. $wp_list_table->single_row( $return ); wp_die(); } else { // translators: %s is the name of the user group. $change_error = new WP_Error( 'invalid', sprintf( __( 'Could not update the user group: %s', 'edit-flow' ), esc_html( $name ) ) ); wp_die( wp_kses( $change_error->get_error_message(), array( 'strong' => array() ) ) ); } } /** * Register settings for notifications so we can partially use the Settings API. * * We use the Settings API for form generation, but not saving. * * @since 0.7 * @uses add_settings_section(), add_settings_field() */ public function register_settings() { add_settings_section( $this->module->options_group_name . '_general', false, '__return_false', $this->module->options_group_name ); add_settings_field( 'post_types', __( 'Add to these post types:', 'edit-flow' ), array( $this, 'settings_post_types_option' ), $this->module->options_group_name, $this->module->options_group_name . '_general' ); } /** * Choose the post types for Usergroups. * * @since 0.7 */ public function settings_post_types_option() { global $edit_flow; $edit_flow->settings->helper_option_custom_post_type( $this->module ); } /** * Validate data entered by the user. * * @since 0.7 * * @param array $new_options New values that have been entered by the user. * @return array Form values after they've been sanitized. */ public function settings_validate( $new_options ) { // Whitelist validation for the post type options. if ( ! isset( $new_options['post_types'] ) ) { $new_options['post_types'] = array(); } $new_options['post_types'] = $this->clean_post_type_options( $new_options['post_types'], $this->module->post_type_support ); return $new_options; } /** * Build a configuration view so we can manage our usergroups. * * @since 0.7 */ public function print_configure_view() { global $edit_flow; // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- Nonce verification happens in handler functions, this is just rendering. if ( isset( $_GET['action'], $_GET['usergroup-id'] ) && 'edit-usergroup' == $_GET['action'] ) : // Full page width view for editing a given usergroup. // Check whether the usergroup exists. $usergroup_id = (int) $_GET['usergroup-id']; $usergroup = $this->get_usergroup_by( 'id', $usergroup_id ); if ( ! $usergroup ) { echo '' . esc_html( $this->module->messages['usergroup-missing'] ) . '
| ID === $user_id ) : ?> | usergroups_select_form( $selected_usergroups, $usergroups_form_args ); ?> |
|---|