├── .gitignore ├── composer.json ├── wp-user-groups ├── includes │ ├── functions │ │ ├── hooks.php │ │ ├── taxonomies.php │ │ ├── sponsor.php │ │ ├── admin.php │ │ └── common.php │ └── classes │ │ └── class-user-taxonomy.php └── assets │ └── css │ └── user-groups.css ├── README.md ├── wp-user-groups.php ├── readme.txt └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stuttter/wp-user-groups", 3 | "description": "Group users together with taxonomies & terms", 4 | "homepage": "https://github.com/stuttter/wp-user-groups", 5 | "type": "wordpress-plugin", 6 | "require": { 7 | "php": ">=8.0", 8 | "composer/installers": "^1.0" 9 | }, 10 | "license": "GPL-2.0-or-later", 11 | "authors": [ 12 | { 13 | "name": "John James Jacoby", 14 | "email": "johnjamesjacoby@me.com" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /wp-user-groups/includes/functions/hooks.php: -------------------------------------------------------------------------------- 1 | __( 'Group', 'wp-user-groups' ), 24 | 'plural' => __( 'Groups', 'wp-user-groups' ), 25 | 'managed' => false 26 | ) ); 27 | } 28 | 29 | /** 30 | * Register default user group taxonomies 31 | * 32 | * This function is hooked onto WordPress's `init` action and creates two new 33 | * `WP_User_Taxonomy` objects for user "groups" and "types". It can be unhooked 34 | * and these taxonomies can be replaced with your own custom ones. 35 | * 36 | * @since 0.1.4 37 | */ 38 | function wp_register_default_user_type_taxonomy() { 39 | new WP_User_Taxonomy( 'user-type', 'users/type', array( 40 | 'singular' => __( 'Type', 'wp-user-groups' ), 41 | 'plural' => __( 'Types', 'wp-user-groups' ), 42 | 'managed' => false 43 | ) ); 44 | } 45 | -------------------------------------------------------------------------------- /wp-user-groups/includes/functions/sponsor.php: -------------------------------------------------------------------------------- 1 | '' . esc_html( $text ) . '' 49 | ) ); 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WP User Groups 2 | 3 | WP User Groups allows users to be categorized using custom taxonomies & terms. 4 | 5 | * "Groups" & "Types" are created by default, and can be overridden 6 | * More user group types can be registered with custom arguments 7 | * Edit users and set their relationships 8 | * Bulk edit many users to quickly assign several at once 9 | * Filter the users list to see which users are in what groups 10 | * Not destructive data storage (plugin can be enabled & disabled without damage) 11 | * Works great with all WP User & Term plugins (see below) 12 | 13 | # Installation 14 | 15 | * Download and install using the built in WordPress plugin installer. 16 | * Activate in the "Plugins" area of your admin by clicking the "Activate" link. 17 | * Consider sponsoring future development by clicking "Sponsor". 18 | * Visit "Users > Groups" and create some groups. 19 | * Add users to groups by editing their profile and checking the boxes. 20 | 21 | # FAQ 22 | 23 | ### Does this create new database tables? 24 | 25 | No. There are no new database tables with this plugin. 26 | 27 | ### Does this modify existing database tables? 28 | 29 | No. All of the WordPress core database tables remain untouched. 30 | 31 | ### Does this plugin integrate with user roles? 32 | 33 | No. This is best left to plugins that choose to integrate with this plugin. 34 | 35 | ### Where can I get support? 36 | 37 | * Community: https://wordpress.org/support/plugin/wp-user-groups 38 | * Development: https://github.com/stuttter/wp-user-groups/discussions 39 | 40 | ### Contributing 41 | 42 | Please [open a new issue](/pull/new/master) to discuss whether the feature is a good fit for the project. Once you've decided to work on a pull request, please follow the [WordPress Coding Standards](http://make.wordpress.org/core/handbook/coding-standards/). 43 | -------------------------------------------------------------------------------- /wp-user-groups.php: -------------------------------------------------------------------------------- 1 | $taxonomy, 47 | 'hide_empty' => false, 48 | 'number' => 1, 49 | 'fields' => 'ids', 50 | ) ); 51 | if ( ! empty( $terms ) && ! is_wp_error( $terms ) ) { 52 | $has_terms = true; 53 | break; 54 | } 55 | } 56 | } 57 | 58 | // Bail if no groups are registered 59 | if ( ! $has_terms ) { 60 | return $sections; 61 | } 62 | 63 | // Copy for modifying 64 | $new_sections = $sections; 65 | 66 | // Add the "Groups" section 67 | $new_sections['groups'] = array( 68 | 'id' => 'groups', 69 | 'slug' => 'groups', 70 | 'name' => esc_html__( 'Groups', 'wp-user-groups' ), 71 | 'cap' => 'edit_profile', 72 | 'icon' => 'dashicons-groups', 73 | 'parent' => '', 74 | 'order' => 90 75 | ); 76 | 77 | // Filter & return 78 | return apply_filters( 'wp_user_groups_add_profile_section', $new_sections, $sections ); 79 | } 80 | -------------------------------------------------------------------------------- /wp-user-groups/assets/css/user-groups.css: -------------------------------------------------------------------------------- 1 | table.user-groups { 2 | margin: 0; 3 | } 4 | 5 | table.user-groups .row-actions { 6 | visibility: hidden; 7 | } 8 | table.user-groups tr:hover .row-actions { 9 | visibility: visible; 10 | } 11 | table.user-groups thead td.check-column, 12 | table.user-groups tfoot td.check-column, 13 | table.user-groups .inactive th.check-column{ 14 | padding-left: 6px; 15 | } 16 | table.user-groups tbody th.check-column, 17 | table.user-groups tbody { 18 | padding: 12px 0 0 2px; 19 | } 20 | table.user-groups th, 21 | table.user-groups td { 22 | padding: 10px; 23 | vertical-align: top; 24 | width: auto; 25 | font-weight: normal; 26 | } 27 | 28 | table.user-groups .column-primary { 29 | width: 25%; 30 | } 31 | 32 | table.user-groups .column-primary strong { 33 | display: block; 34 | margin-bottom: .2em; 35 | font-size: 14px; 36 | } 37 | 38 | table.user-groups .column-users { 39 | width: 10%; 40 | padding-right: 0; 41 | text-align: center; 42 | } 43 | 44 | table.user-groups .description { 45 | color: #666; 46 | } 47 | 48 | table.user-groups .inactive td, 49 | table.user-groups .inactive th, 50 | table.user-groups .active td, 51 | table.user-groups .active th { 52 | padding: 10px 9px; 53 | } 54 | 55 | table.user-groups .active td, 56 | table.user-groups .active th { 57 | background-color: #f7fcfe; 58 | } 59 | 60 | table.user-groups .inactive td, 61 | table.user-groups .inactive th, 62 | table.user-groups .active td, 63 | table.user-groups .active th { 64 | -webkit-box-shadow: inset 0 -1px 0 rgba(0,0,0,0.1); 65 | box-shadow: inset 0 -1px 0 rgba(0,0,0,0.1); 66 | } 67 | 68 | table.user-groups tr.active + tr.inactive th, 69 | table.user-groups tr.active + tr.inactive td { 70 | border-top: 1px solid rgba(0,0,0,0.03); 71 | -webkit-box-shadow: inset 0 1px 0 rgba(0,0,0,0.02), inset 0 -1px 0 #e1e1e1; 72 | box-shadow: inset 0 1px 0 rgba(0,0,0,0.02), inset 0 -1px 0 #e1e1e1; 73 | } 74 | 75 | table.user-groups tr.active + tr.inactive.update th, 76 | table.user-groups tr.active + tr.inactive.update td, 77 | table.user-groups tr.active + tr.inactive.updated th, 78 | table.user-groups tr.active + tr.inactive.updated td, 79 | table.user-groups tbody tr:last-of-type td, 80 | table.user-groups tbody tr:last-of-type th { 81 | -webkit-box-shadow: none; 82 | box-shadow: none; 83 | } 84 | 85 | table.user-groups .active th.check-column { 86 | border-left: 4px solid #00a0d2; 87 | } 88 | 89 | table.user-groups .plugin-title, 90 | table.user-groups .theme-title { 91 | padding-right: 12px; 92 | white-space:nowrap; 93 | } 94 | 95 | table.user-groups .inactive .plugin-title strong { 96 | font-weight: 400; 97 | } 98 | 99 | .user-tax-form fieldset { 100 | margin: 8px 10px 0 0; 101 | } 102 | .subsubsub + form + br.clear { 103 | display: none; 104 | } 105 | .tax-actions { 106 | margin-bottom: 5px; 107 | } 108 | 109 | @media screen and ( min-width: 786px ) { 110 | .wp-list-table.tags .column-users { 111 | width: 10%; 112 | text-align: center; 113 | } 114 | } 115 | 116 | @media screen and ( max-width: 786px ) { 117 | table.form-table table th { 118 | display: table-cell; 119 | } 120 | 121 | table.form-table table td { 122 | display: table-cell !important; 123 | } 124 | } 125 | 126 | @media screen and ( min-width: 1110px ) { 127 | table.user-groups { 128 | min-width: 650px; 129 | } 130 | } 131 | 132 | #wp_user_taxonomy_user-group .inside, 133 | #wp_user_taxonomy_user-type .inside { 134 | margin: 0; 135 | padding: 0; 136 | } 137 | 138 | body.toplevel_page_groups .metabox-holder table.user-groups, 139 | body.users_page_groups .metabox-holder table.user-groups { 140 | border: none; 141 | margin: 0; 142 | padding: 0; 143 | } 144 | -------------------------------------------------------------------------------- /wp-user-groups/includes/functions/common.php: -------------------------------------------------------------------------------- 1 | ID ) 26 | ? $user->ID 27 | : absint( $user ); 28 | 29 | // Bail if empty 30 | if ( empty( $user_id ) ) { 31 | return false; 32 | } 33 | 34 | // Return user terms 35 | return wp_get_object_terms( $user_id, $taxonomy, array( 36 | 'fields' => 'all_with_object_id' 37 | ) ); 38 | } 39 | 40 | /** 41 | * Save taxonomy terms for a specific user 42 | * 43 | * @since 0.1.0 44 | * 45 | * @param mixed $user 46 | * @param string $taxonomy 47 | * @param array $terms 48 | * 49 | * @return void 50 | */ 51 | function wp_set_terms_for_user( $user = false, $taxonomy = '', $terms = array() ) { 52 | 53 | // Verify user ID 54 | $user_id = is_object( $user ) && ! empty( $user->ID ) 55 | ? $user->ID 56 | : absint( $user ); 57 | 58 | // Bail if empty 59 | if ( empty( $user_id ) ) { 60 | return false; 61 | } 62 | 63 | // Delete all terms for the user 64 | if ( empty( $terms ) ) { 65 | wp_delete_object_term_relationships( $user_id, $taxonomy ); 66 | 67 | // Sets the terms for the user 68 | } else { 69 | wp_set_object_terms( $user_id, $terms, $taxonomy, false ); 70 | } 71 | 72 | // Clean the cache 73 | clean_object_term_cache( $user_id, $taxonomy ); 74 | } 75 | 76 | /** 77 | * Get all user groups 78 | * 79 | * @uses get_taxonomies() To get user-group taxonomies 80 | * 81 | * @since 0.1.5 82 | * 83 | * @param array $args Optional. An array of `key => value` arguments to 84 | * match against the taxonomy objects. Default empty array. 85 | * @param string $output Optional. The type of output to return in the array. 86 | * Accepts either taxonomy 'names' or 'objects'. Default 'names'. 87 | * @param string $operator Optional. The logical operation to perform. 88 | * Accepts 'and' or 'or'. 'or' means only one element from 89 | * the array needs to match; 'and' means all elements must 90 | * match. Default 'and'. 91 | * 92 | * @return array A list of taxonomy names or objects. 93 | */ 94 | function wp_get_user_groups( $args = array(), $output = 'names', $operator = 'and' ) { 95 | 96 | // Parse arguments 97 | $r = wp_parse_args( $args, array( 98 | 'user_group' => true 99 | ) ); 100 | 101 | // Return user group taxonomies 102 | return get_taxonomies( $r, $output, $operator ); 103 | } 104 | 105 | /** 106 | * Get all user group objects 107 | * 108 | * @uses wp_get_user_groups() To get user group objects 109 | * 110 | * @since 0.1.5 111 | * 112 | * @param array $args See wp_get_user_groups() 113 | * @param string $operator See wp_get_user_groups() 114 | * 115 | * @return array 116 | */ 117 | function wp_get_user_group_objects( $args = array(), $operator = 'and' ) { 118 | return wp_get_user_groups( $args, 'objects', $operator ); 119 | } 120 | 121 | /** 122 | * Return a list of users in a specific group. 123 | * 124 | * @since 0.1.0 125 | 126 | * @param array $args { 127 | * Array or term information. 128 | * 129 | * @type string $taxomony Taxonomy name. Default is 'user-group'. 130 | * @type string|int $term Search for this term value. 131 | * @type string $term_by Either 'slug', 'name', 'id' (term_id), or 'term_taxonomy_id'. 132 | * Default is 'slug'. 133 | * } 134 | * @param array $user_args Optional. WP_User_Query arguments. 135 | * 136 | * @return array List of users in the user group. 137 | */ 138 | function wp_get_users_of_group( $args = array(), $user_args = array() ) { 139 | 140 | // Parse arguments. 141 | $r = wp_parse_args( $args, array( 142 | 'taxonomy' => 'user-group', 143 | 'term' => '', 144 | 'term_by' => 'slug' 145 | ) ); 146 | 147 | // Get user IDs in group. 148 | $term = get_term_by( $r['term_by'], $r['term'], $r['taxonomy'] ); 149 | $user_ids = get_objects_in_term( $term->term_id, $r['taxonomy'] ); 150 | 151 | // Bail if no users in this term. 152 | if ( empty( $term ) || empty( $user_ids ) ) { 153 | return array(); 154 | } 155 | 156 | // Parse optional user arguments 157 | $user_args = wp_parse_args( $user_args, array( 158 | 'orderby' => 'display_name', 159 | ) ); 160 | 161 | // Strictly enforce the inclusion of user IDs to this group 162 | $user_args['include'] = $user_ids; 163 | 164 | // Return queried users. 165 | return get_users( $user_args ); 166 | } 167 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === WP User Groups === 2 | Author: Triple J Software, Inc. 3 | Author URI: https://jjj.software 4 | Donate link: https://buy.stripe.com/7sI3cd2tK1Cy2lydQR 5 | Plugin URI: https://wordpress.org/plugins/wp-user-groups/ 6 | License URI: https://www.gnu.org/licenses/gpl-2.0.html 7 | License: GPLv2 or later 8 | Contributors: johnjamesjacoby 9 | Tags: user, profile, group, taxonomy, term 10 | Requires PHP: 8.0 11 | Requires at least: 5.2 12 | Tested up to: 5.8 13 | Stable tag: 2.5.1 14 | 15 | == Description == 16 | 17 | WP User Groups allows users to be categorized using custom taxonomies & terms. 18 | 19 | * "Groups" & "Types" are created by default, and can be overridden 20 | * More user group types can be registered with custom arguments 21 | * Edit users and set their relationships 22 | * Bulk edit many users to quickly assign several at once 23 | * Filter the users list to see which users are in what groups 24 | * Not destructive data storage (plugin can be enabled & disabled without damage) 25 | * Works great with all WP User & Term plugins (see below) 26 | 27 | = Recommended Plugins = 28 | 29 | If you like this plugin, you'll probably like these! 30 | 31 | * [WP User Profiles](https://wordpress.org/plugins/wp-user-profiles/ "A sophisticated way to edit users in WordPress.") 32 | * [WP User Activity](https://wordpress.org/plugins/wp-user-activity/ "The best way to log activity in WordPress.") 33 | * [WP User Avatars](https://wordpress.org/plugins/wp-user-avatars/ "Allow users to upload avatars or choose them from your media library.") 34 | * [WP User Groups](https://wordpress.org/plugins/wp-user-groups/ "Group users together with taxonomies & terms.") 35 | * [WP User Signups](https://wordpress.org/plugins/wp-user-signups/ "The best way to manage user & site sign-ups in WordPress.") 36 | * [WP Term Authors](https://wordpress.org/plugins/wp-term-authors/ "Authors for categories, tags, and other taxonomy terms.") 37 | * [WP Term Colors](https://wordpress.org/plugins/wp-term-colors/ "Pretty colors for categories, tags, and other taxonomy terms.") 38 | * [WP Term Families](https://wordpress.org/plugins/wp-term-families/ "Associate taxonomy terms with other taxonomy terms.") 39 | * [WP Term Icons](https://wordpress.org/plugins/wp-term-icons/ "Pretty icons for categories, tags, and other taxonomy terms.") 40 | * [WP Term Images](https://wordpress.org/plugins/wp-term-images/ "Pretty images for categories, tags, and other taxonomy terms.") 41 | * [WP Term Locks](https://wordpress.org/plugins/wp-term-locks/ "Protect categories, tags, and other taxonomy terms from being edited or deleted.") 42 | * [WP Term Order](https://wordpress.org/plugins/wp-term-order/ "Sort taxonomy terms, your way.") 43 | * [WP Term Visibility](https://wordpress.org/plugins/wp-term-visibility/ "Visibilities for categories, tags, and other taxonomy terms.") 44 | * [WP Media Categories](https://wordpress.org/plugins/wp-media-categories/ "Add categories to media & attachments.") 45 | * [WP Pretty Filters](https://wordpress.org/plugins/wp-pretty-filters/ "Makes post filters better match what's already in Media & Attachments.") 46 | * [WP Chosen](https://wordpress.org/plugins/wp-chosen/ "Make long, unwieldy select boxes much more user-friendly.") 47 | 48 | == Screenshots == 49 | 50 | 1. Menu Items 51 | 2. Groups Taxonomy 52 | 3. Types Taxonomy 53 | 4. User Edit & Assignment 54 | 5. Users List 55 | 6. Users List (Filtered) 56 | 57 | == Installation == 58 | 59 | 1. Download and install using the built in WordPress plugin installer. 60 | 1. Activate in the "Plugins" area of your admin by clicking the "Activate" link. 61 | 1. Visit "Users > Groups" and create some groups 62 | 1. Add users to groups by editing their profile and checking the boxes 63 | 64 | == Frequently Asked Questions == 65 | 66 | = Does this create new database tables? = 67 | 68 | No. There are no new database tables with this plugin. 69 | 70 | = Does this modify existing database tables? = 71 | 72 | No. All of the WordPress core database tables remain untouched. 73 | 74 | = Does this plugin integrate with user roles? = 75 | 76 | No. This is best left to plugins that choose to integrate with this plugin. 77 | 78 | = Where can I get support? = 79 | 80 | * Community: https://wordpress.org/support/plugin/wp-user-groups 81 | * Development: https://github.com/stuttter/wp-user-groups/discussions 82 | 83 | == Changelog == 84 | 85 | = [2.5.1] - 2021/05/29 = 86 | * Update author info 87 | * Add sponsor link 88 | 89 | = [2.5.0] - 2021/03/23 = 90 | * Improve compatibility with WP User Profiles plugin (props John Blackbourn) 91 | 92 | = [2.4.0] - 2018/10/04 = 93 | * Simplify get and set functions for user terms 94 | * Add support for advanced WP_User_Query arguments 95 | * Fix custom column support in user taxonomies 96 | 97 | = [2.3.0] - 2018/10/03 = 98 | * More descriptive text for bulk actions 99 | * Fix bulk actions not working 100 | 101 | = [2.2.0] - 2018/06/05 = 102 | * Add "Managed" taxonomy type, so users cannot assign their own groups 103 | 104 | = [2.1.0] - 2018/04/16 = 105 | * Add a dedicated nonce for each user taxonomy (thanks Tom Adams!) 106 | 107 | = [2.0.0] - 2017/10/24 = 108 | * Fix bug with user filtering 109 | * Fix bug with setting user terms 110 | * Add `exclusive` group argument to use radios instead of checkboxes 111 | 112 | = [1.1.0] - 2017/03/28 = 113 | * Change default taxonomy to `user-group` in wp_get_users_of_group() 114 | 115 | = [1.0.0] - 2016/12/07 = 116 | * WordPress 4.7 compatibility 117 | * Improved bulk actions (requires WordPress 4.7) 118 | * Official stable release 119 | 120 | = [0.2.1] - 2016/05/25 = 121 | * Fix bug with user list 122 | * Introduce wp_get_users_of_group() helper function 123 | * Add unique class to administration forms 124 | 125 | = [0.2.0] - 2015/12/23 = 126 | * Support for WP User Profiles 0.2.0 127 | 128 | = [0.1.9] - 2015/12/21 = 129 | * Fix bug with User Profiles integration 130 | 131 | = [0.1.8] - 2015/11/11 = 132 | * Support for WP User Profiles 0.1.9 133 | 134 | = [0.1.7] - 2015/11/09 = 135 | * Update assets & meta 136 | 137 | = [0.1.6] - 2015/10/23 = 138 | * Add support for WP User Profiles 139 | 140 | = [0.1.5] - 2015/10/13 = 141 | * Added `user_group` property to taxonomies 142 | * Added functions for retrieving only user-groups from taxonomies global 143 | 144 | = [0.1.0] - 2015/09/10 = 145 | * Refactor 146 | * Improve asset management 147 | * Styling tweaks 148 | 149 | = [0.1.2] - 2015/09/01 = 150 | * Namespace default taxonomy IDs 151 | 152 | = [0.1.1] - 2015/08/24 = 153 | * User profile UI uses a mock list-table 154 | 155 | = [0.1.0] - 2015/08/19 = 156 | * Initial release 157 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /wp-user-groups/includes/classes/class-user-taxonomy.php: -------------------------------------------------------------------------------- 1 | taxonomy = sanitize_key( $taxonomy ); 129 | $this->slug = sanitize_text_field( $slug ); 130 | $this->args = $args; 131 | $this->labels = $labels; 132 | $this->caps = $caps; 133 | 134 | // Label helpers 135 | $this->tax_singular = $args['singular']; 136 | $this->tax_plural = $args['plural']; 137 | $this->tax_singular_low = strtolower( $this->tax_singular ); 138 | $this->tax_plural_low = strtolower( $this->tax_plural ); 139 | 140 | // Register the taxonomy 141 | $this->register_user_taxonomy(); 142 | 143 | // Hook into actions & filters 144 | $this->hooks(); 145 | 146 | // JIT 147 | do_action( 'wp_user_taxonomy', $this ); 148 | } 149 | 150 | /** 151 | * Hook in to actions & filters 152 | * 153 | * @since 0.1.1 154 | */ 155 | protected function hooks() { 156 | 157 | // Bulk edit 158 | add_filter( 'admin_notices', array( $this, 'bulk_notice' ) ); 159 | add_filter( 'bulk_actions-users', array( $this, 'bulk_actions' ) ); 160 | add_filter( 'bulk_actions-users', array( $this, 'bulk_actions_sort' ), 99 ); 161 | add_action( 'handle_bulk_actions-users', array( $this, 'handle_bulk_actions' ), 10, 3 ); 162 | 163 | // Include users by taxonomy term in users.php 164 | add_action( 'pre_get_users', array( $this, 'pre_get_users' ) ); 165 | 166 | // Custom list-table views 167 | add_filter( 'views_users', array( $this, 'list_table_views' ) ); 168 | 169 | // Column styling 170 | add_action( 'admin_head', array( $this, 'admin_head' ) ); 171 | add_action( 'admin_menu', array( $this, 'add_admin_page' ) ); 172 | 173 | // WP User Profile support 174 | add_action( 'wp_user_profiles_add_meta_boxes', array( $this, 'add_meta_box' ), 10, 2 ); 175 | 176 | // Taxonomy columns 177 | add_action( "manage_{$this->taxonomy}_custom_column", array( $this, 'manage_custom_column' ), 10, 3 ); 178 | add_filter( "manage_edit-{$this->taxonomy}_columns", array( $this, 'manage_edit_users_column' ) ); 179 | 180 | // User columns 181 | add_filter( 'manage_users_columns', array( $this, 'add_manage_users_columns' ), 15, 1 ); 182 | add_action( 'manage_users_custom_column', array( $this, 'user_column_data' ), 15, 3 ); 183 | 184 | // Update the groups when the edit user page is updated 185 | add_action( 'personal_options_update', array( $this, 'save_terms_for_user' ) ); 186 | add_action( 'edit_user_profile_update', array( $this, 'save_terms_for_user' ) ); 187 | 188 | // Add section to the edit user page in the admin to select group 189 | if ( ! function_exists( '_wp_user_profiles' ) ) { 190 | add_action( 'show_user_profile', array( $this, 'edit_user_relationships' ), 99 ); 191 | add_action( 'edit_user_profile', array( $this, 'edit_user_relationships' ), 99 ); 192 | } 193 | 194 | // Cleanup stuff 195 | add_action( 'delete_user', array( $this, 'delete_term_relationships' ) ); 196 | add_filter( 'sanitize_user', array( $this, 'disable_username' ) ); 197 | } 198 | 199 | /** 200 | * Add the administration page for this taxonomy 201 | * 202 | * @since 0.1.0 203 | */ 204 | public function add_admin_page() { 205 | 206 | // Setup the URL 207 | $tax = get_taxonomy( $this->taxonomy ); 208 | 209 | // No UI 210 | if ( false === $tax->show_ui ) { 211 | return; 212 | } 213 | 214 | // URL for the taxonomy 215 | $url = add_query_arg( array( 'taxonomy' => $tax->name ), 'edit-tags.php' ); 216 | 217 | // Add page to users 218 | add_users_page( 219 | esc_attr( $tax->labels->menu_name ), 220 | esc_attr( $tax->labels->menu_name ), 221 | $tax->cap->manage_terms, 222 | $url 223 | ); 224 | 225 | // Hook into early actions to load custom CSS and our init handler. 226 | add_action( 'load-users.php', array( $this, 'admin_load' ) ); 227 | add_action( 'load-edit-tags.php', array( $this, 'admin_load' ) ); 228 | add_action( 'load-term.php', array( $this, 'admin_menu_highlight' ) ); 229 | add_action( 'load-edit-tags.php', array( $this, 'admin_menu_highlight' ) ); 230 | } 231 | 232 | /** 233 | * This tells WordPress to highlight the "Users" menu item when viewing a 234 | * user taxonomy. 235 | * 236 | * @since 0.1.0 237 | * 238 | * @global string $plugin_page 239 | */ 240 | public function admin_menu_highlight() { 241 | global $plugin_page; 242 | 243 | // Set plugin page to "users.php" to get highlighting to be correct 244 | if ( isset( $_GET['taxonomy'] ) && ( $_GET['taxonomy'] === $this->taxonomy ) ) { 245 | $plugin_page = 'users.php'; 246 | } 247 | } 248 | 249 | /** 250 | * Filter the body class 251 | * 252 | * @since 0.1.0 253 | */ 254 | public function admin_load() { 255 | add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) ); 256 | } 257 | 258 | /** 259 | * Add a class for this taxonomy 260 | * 261 | * @since 0.1.0 262 | * 263 | * @param string $classes 264 | * @return string 265 | */ 266 | public function admin_body_class( $classes = '' ) { 267 | 268 | // Add a body class for this taxonomy if it's currently selected 269 | if ( isset( $_GET[ $this->taxonomy ] ) ) { 270 | $classes .= " tax-{$this->taxonomy}"; 271 | } 272 | 273 | // Return maybe modified class 274 | return $classes; 275 | } 276 | 277 | /** 278 | * Stylize custom columns 279 | * 280 | * @since 0.1.0 281 | */ 282 | public function admin_head() { 283 | 284 | // Compile the style 285 | $style = " 286 | .column-{$this->taxonomy} { 287 | width: 10%; 288 | } 289 | body.users-php.tax-{$this->taxonomy} .wrap > h1, 290 | body.users-php.tax-{$this->taxonomy} .wrap > h1 + .page-title-action { 291 | display: none; 292 | }"; 293 | 294 | // Add inline style 295 | wp_add_inline_style( 'wp_user_groups', $style ); 296 | } 297 | 298 | /** 299 | * Metaboxes for profile sections 300 | * 301 | * @since 0.1.6 302 | */ 303 | public function add_meta_box( $type = '' ) { 304 | 305 | // Get hookname 306 | $hooks = wp_user_profiles_get_section_hooknames( 'groups' ); 307 | 308 | // Bail if not the correct type 309 | if ( ! in_array( $type, $hooks, true ) ) { 310 | return; 311 | } 312 | 313 | // Get the taxonomy 314 | $tax = get_taxonomy( $this->taxonomy ); 315 | $user_id = ! empty( $_GET['user_id'] ) 316 | ? (int) $_GET['user_id'] 317 | : get_current_user_id(); 318 | 319 | // Bail if current user cannot assign terms to this user for this taxonomy 320 | if ( ! $this->can_assign( $user_id ) ) { 321 | return; 322 | } 323 | 324 | // Bail if no UI for taxonomy 325 | if ( false === $tax->show_ui ) { 326 | return; 327 | } 328 | 329 | // Get the terms of the taxonomy. 330 | $terms = get_terms( $this->taxonomy, array( 331 | 'hide_empty' => false 332 | ) ); 333 | 334 | // Maybe add the metabox 335 | add_meta_box( 336 | 'wp_user_taxonomy_' . $this->taxonomy, 337 | $tax->label, 338 | array( $this, 'user_profile_metabox' ), 339 | $hooks[0], 340 | 'normal', 341 | 'default', 342 | array( 343 | 'user_id' => $user_id, 344 | 'tax' => $tax, 345 | 'terms' => $terms 346 | ) 347 | ); 348 | } 349 | 350 | /** 351 | * Save terms for a user for this taxonomy 352 | * 353 | * @since 0.1.0 354 | * 355 | * @param int $user_id 356 | */ 357 | public function save_terms_for_user( $user_id = 0 ) { 358 | 359 | // Bail if nonce problem 360 | if ( ! $this->verify_nonce() ) { 361 | return; 362 | } 363 | 364 | // Additional checks if User Profiles is active 365 | if ( function_exists( 'wp_user_profiles_get_section_hooknames' ) ) { 366 | 367 | // Bail if no page 368 | if ( empty( $_GET['page'] ) ) { 369 | return; 370 | } 371 | 372 | // Bail if not saving this section 373 | if ( sanitize_key( $_GET['page'] ) !== 'groups' ) { 374 | return; 375 | } 376 | } 377 | 378 | // Make sure the current user can edit the user and assign terms before proceeding 379 | if ( ! $this->can_assign( $user_id ) ) { 380 | return false; 381 | } 382 | 383 | // Get terms from the $_POST global if available 384 | $terms = isset( $_POST[ $this->taxonomy ] ) 385 | ? $_POST[ $this->taxonomy ] 386 | : null; 387 | 388 | // Set terms for user 389 | wp_set_terms_for_user( $user_id, $this->taxonomy, $terms ); 390 | } 391 | 392 | /** 393 | * Update the term count for a user and taxonomy 394 | * 395 | * @since 0.1.0 396 | * 397 | * @param int $user_id 398 | */ 399 | public function update_term_user_count( $terms = array(), $taxonomy = '' ) { 400 | 401 | // Fallback to this taxonomy 402 | if ( empty( $taxonomy ) ) { 403 | $taxonomy = $this->taxonomy; 404 | } 405 | 406 | // Update counts 407 | _update_generic_term_count( $terms, $taxonomy ); 408 | } 409 | 410 | /** 411 | * Manage columns for user taxonomies 412 | * 413 | * @since 0.1.0 414 | * 415 | * @param array $columns 416 | * @return array 417 | */ 418 | public function manage_edit_users_column( $columns = array() ) { 419 | 420 | // Unset the "Posts" column 421 | unset( $columns['posts'] ); 422 | 423 | // Add the "Users" column 424 | $columns['users'] = esc_html__( 'Users', 'wp-user-groups' ); 425 | 426 | // Return modified columns 427 | return $columns; 428 | } 429 | 430 | /** 431 | * Output the data for the "Users" column when viewing user taxonomies 432 | * 433 | * @since 0.1.0 434 | * 435 | * @param string $display 436 | * @param string $column 437 | * @param string $term_id 438 | */ 439 | public function manage_custom_column( $display = false, $column = '', $term_id = 0 ) { 440 | 441 | // Users column gets custom content 442 | if ( 'users' === $column ) { 443 | $term = get_term( $term_id, $this->taxonomy ); 444 | $args = array( $this->taxonomy => $term->slug ); 445 | $users = admin_url( 'users.php' ); 446 | $url = add_query_arg( $args, $users ); 447 | $text = number_format_i18n( $term->count ); 448 | $display = '' . esc_html( $text ) . ''; 449 | } 450 | 451 | // Return the new content for display 452 | return $display; 453 | } 454 | 455 | /** 456 | * Output a "Relationships" section to show off taxonomy groupings 457 | * 458 | * @since 0.1.0 459 | * 460 | * @param mixed $user 461 | */ 462 | public function edit_user_relationships( $user = false ) { 463 | 464 | // Bail if current user cannot assign terms to this user for this taxonomy 465 | if ( ! $this->can_assign( $user->ID ) ) { 466 | return; 467 | } 468 | 469 | $tax = get_taxonomy( $this->taxonomy ); 470 | 471 | // Bail if no UI for taxonomy 472 | if ( false === $tax->show_ui ) { 473 | return; 474 | } 475 | 476 | // Get the terms of the taxonomy. 477 | $terms = get_terms( $this->taxonomy, array( 478 | 'hide_empty' => false 479 | ) ); ?> 480 | 481 | 485 | 486 |

487 | 488 |

489 | 490 | 496 | 497 | 498 | 499 | 504 | 507 | 508 |
500 | 503 | 505 | table_contents( $user, $tax, $terms ); ?> 506 |
509 | 510 | table_contents( $user, $args['args']['tax'], $args['args']['terms'] ); 520 | } 521 | 522 | /** 523 | * Output metabox contents 524 | * 525 | * @since 0.1.6 526 | */ 527 | protected function table_contents( $user, $tax, $terms ) { 528 | ?> 529 | 530 | 531 | 532 | 533 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | ID, $this->taxonomy, $term->slug ); ?> 550 | 551 | 552 | 558 | 564 | 565 | 566 | 567 | 568 | 574 | 575 | 576 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 594 | 595 | 596 | 597 | 598 | 599 |
534 | is_managed() && ! $this->is_exclusive() ) : ?> 535 | 536 | 537 | 538 |
553 | is_managed() ) : ?> 554 | /> 555 | 556 | 557 | 559 | name ); ?> 560 |
561 | row_actions( $tax, $term ); ?> 562 |
563 |
description ) ? esc_html( $term->description ) : '—'; ?>count ); ?>
577 | 578 | labels->not_found ); ?> 579 | 580 |
589 | is_managed() && ! $this->is_exclusive() ) : ?> 590 | 591 | 592 | 593 |
600 | 601 | nonce_field(); 605 | } 606 | 607 | /** 608 | * Output row actions when editing a user 609 | * 610 | * @since 0.1.1 611 | * 612 | * @param object $term 613 | */ 614 | protected function row_actions( $tax = array(), $term = false ) { 615 | $actions = array(); 616 | 617 | // List users in group 618 | if ( current_user_can( 'list_users' ) ) { 619 | $args = array( $tax->name => $term->slug ); 620 | $users = admin_url( 'users.php' ); 621 | $url = add_query_arg( $args, $users ); 622 | $actions[] = '' . esc_html__( 'View', 'wp-user-groups' ) . ''; 623 | } 624 | 625 | // Edit term 626 | if ( current_user_can( 'edit_term', $term->term_id ) ) { 627 | $args = array( 'action' => 'edit', 'taxonomy' => $tax->name, 'tag_ID' => $term->term_id, 'post_type' => 'post' ); 628 | $edit_tags = admin_url( 'edit-tags.php' ); 629 | $url = add_query_arg( $args, $edit_tags ); 630 | $actions[] = '' . esc_html__( 'Edit', 'wp-user-groups' ) . ''; 631 | } 632 | 633 | // Filter 634 | $actions = apply_filters( 'wp_user_groups_row_actions', $actions, $tax, $term, $this ); 635 | 636 | return implode( ' | ', $actions ); 637 | } 638 | 639 | /** 640 | * Disallow taxonomy as a username 641 | * 642 | * @since 0.1.0 643 | * 644 | * @param string $username 645 | * @return string 646 | */ 647 | public function disable_username( $username = '' ) { 648 | 649 | // Set username to empty if it's this taxonomy 650 | if ( $this->taxonomy === $username ) { 651 | $username = ''; 652 | } 653 | 654 | // Return possible emptied username 655 | return $username; 656 | } 657 | 658 | /** 659 | * Delete term relationships 660 | * 661 | * @since 0.1.0 662 | * 663 | * @param int $user_id 664 | */ 665 | public function delete_term_relationships( $user_id = 0 ) { 666 | wp_delete_object_term_relationships( $user_id, $this->taxonomy ); 667 | } 668 | 669 | /** Post Type *************************************************************/ 670 | 671 | /** 672 | * Register the taxonomy 673 | * 674 | * @since 0.1.0 675 | */ 676 | protected function register_user_taxonomy() { 677 | 678 | // Parse the options 679 | $options = $this->parse_options(); 680 | 681 | /** 682 | * Filter the objects for this taxonomy, allowing for multiple 683 | * relationships to exist. This is risky, as ID collisions may occur, so 684 | * make sure that you're using it correctly 685 | * 686 | * @since 2.4.0 687 | * 688 | * @param array $defaults Default object types. 'user' by default. 689 | * @param string $taxonomy The current taxonomy 690 | * @param 691 | */ 692 | $objects = (array) apply_filters( 'wp_user_groups_taxonomy_objects', array( 693 | 'user' 694 | ) , $this->taxonomy, $options ); 695 | 696 | // Register the taxonomy 697 | register_taxonomy( 698 | $this->taxonomy, 699 | $objects, 700 | $options 701 | ); 702 | } 703 | 704 | /** 705 | * Parse taxonomy labels 706 | * 707 | * @since 0.1.0 708 | * 709 | * @return array 710 | */ 711 | protected function parse_labels() { 712 | return wp_parse_args( $this->labels, array( 713 | 'menu_name' => $this->tax_plural, 714 | 'name' => $this->tax_plural, 715 | 'singular_name' => $this->tax_singular, 716 | 'search_items' => sprintf( __( 'Search %s', 'wp-user-groups' ), $this->tax_plural ), 717 | 'popular_items' => sprintf( __( 'Popular %s', 'wp-user-groups' ), $this->tax_plural ), 718 | 'all_items' => sprintf( __( 'All %s', 'wp-user-groups' ), $this->tax_plural ), 719 | 'parent_item' => sprintf( __( 'Parent %s', 'wp-user-groups' ), $this->tax_singular ), 720 | 'parent_item_colon' => sprintf( __( 'Parent %s:', 'wp-user-groups' ), $this->tax_singular ), 721 | 'edit_item' => sprintf( __( 'Edit %s', 'wp-user-groups' ), $this->tax_singular ), 722 | 'view_item' => sprintf( __( 'View %s', 'wp-user-groups' ), $this->tax_singular ), 723 | 'update_item' => sprintf( __( 'Update %s', 'wp-user-groups' ), $this->tax_singular ), 724 | 'add_new_item' => sprintf( __( 'Add New %s', 'wp-user-groups' ), $this->tax_singular ), 725 | 'new_item_name' => sprintf( __( 'New %s Name', 'wp-user-groups' ), $this->tax_singular ), 726 | 'separate_items_with_commas' => sprintf( __( 'Separate %s with commas', 'wp-user-groups' ), $this->tax_plural_low ), 727 | 'add_or_remove_items' => sprintf( __( 'Add or remove %s', 'wp-user-groups' ), $this->tax_plural_low ), 728 | 'choose_from_most_used' => sprintf( __( 'Choose from most used %s', 'wp-user-groups' ), $this->tax_plural_low ), 729 | 'not_found' => sprintf( __( 'No %s found', 'wp-user-groups' ), $this->tax_plural_low ), 730 | 'no_item' => sprintf( __( 'No %s', 'wp-user-groups' ), $this->tax_singular ), 731 | 'no_items' => sprintf( __( 'No %s', 'wp-user-groups' ), $this->tax_plural_low ) 732 | ) ); 733 | } 734 | 735 | /** 736 | * Parse taxonomy capabilities 737 | * 738 | * @since 2.2.0 739 | * 740 | * @return array 741 | */ 742 | protected function parse_caps() { 743 | return wp_parse_args( $this->caps, array( 744 | 'manage_terms' => 'list_users', 745 | 'edit_terms' => 'list_users', 746 | 'delete_terms' => 'list_users', 747 | 'assign_terms' => $this->is_managed() 748 | ? 'list_users' 749 | : 'read' 750 | ) ); 751 | } 752 | 753 | /** 754 | * Parse taxonomy options 755 | * 756 | * @since 0.1.0 757 | * 758 | * @return array 759 | */ 760 | protected function parse_options() { 761 | return wp_parse_args( $this->args, array( 762 | 763 | // Custom 764 | 'user_group' => true, // Make it easy to identify user groups 765 | 'exclusive' => false, // Check vs. Radio 766 | 767 | // Core 768 | 'hierarchical' => true, 769 | 'public' => false, 770 | 'show_ui' => true, 771 | 'meta_box_cb' => '', 772 | 'labels' => $this->parse_labels(), 773 | 'capabilities' => $this->parse_caps(), 774 | 'rewrite' => array( 775 | 'with_front' => false, 776 | 'slug' => $this->slug, 777 | 'hierarchical' => true 778 | ), 779 | 780 | // @see _update_post_term_count() 781 | 'update_count_callback' => array( $this, 'update_term_user_count' ) 782 | ) ); 783 | } 784 | 785 | /** Bulk Edit *************************************************************/ 786 | 787 | /** 788 | * Add custom bulk actions 789 | * 790 | * @since 1.0.0 791 | * 792 | * @param array $actions 793 | * 794 | * @return array 795 | */ 796 | public function bulk_actions( $actions = array() ) { 797 | 798 | // Get taxonomy & terms 799 | $tax = get_taxonomy( $this->taxonomy ); 800 | $terms = get_terms( $this->taxonomy, array( 801 | 'hide_empty' => false 802 | ) ); 803 | 804 | // Add to bulk actions array 805 | if ( ! empty( $terms ) ) { 806 | foreach ( $terms as $term ) { 807 | $actions[ "add-{$term->slug}-{$this->taxonomy}" ] = sprintf( esc_html__( 'Add to %s %s', 'wp-user-groups' ), $term->name, $tax->labels->singular_name ); 808 | $actions[ "remove-{$term->slug}-{$this->taxonomy}" ] = sprintf( esc_html__( 'Remove from %s %s', 'wp-user-groups' ), $term->name, $tax->labels->singular_name ); 809 | } 810 | } 811 | 812 | // Return actions, maybe with our bulks added 813 | return $actions; 814 | } 815 | 816 | /** 817 | * Group add/remove options together for improved UX 818 | * 819 | * @since 1.0.0 820 | * 821 | * @param array $actions 822 | */ 823 | public function bulk_actions_sort( $actions = array() ) { 824 | 825 | // Actions array 826 | $old_actions = $add_actions = $rem_actions = array(); 827 | 828 | // Loop through and separate out actions 829 | foreach ( $actions as $key => $name ) { 830 | 831 | // Add 832 | if ( 0 === strpos( $key, 'add-' ) ) { 833 | $add_actions[ $key ] = $name; 834 | 835 | // Remove 836 | } elseif ( 0 === strpos( $key, 'remove-' ) ) { 837 | $rem_actions[ $key ] = $name; 838 | 839 | // Old 840 | } else { 841 | $old_actions[ $key ] = $name; 842 | } 843 | } 844 | 845 | $new = array_merge( $old_actions, $add_actions, $rem_actions ); 846 | 847 | return $new; 848 | } 849 | 850 | /** 851 | * Is this an exclusive user group type, where a user can only belong to one 852 | * group within the taxonomy? 853 | * 854 | * @since 2.0.0 855 | * 856 | * @return bool 857 | */ 858 | public function is_exclusive() { 859 | return ! empty( $this->args['exclusive'] ); 860 | } 861 | 862 | /** 863 | * Is this a managed user group type, where a user cannot assign their own 864 | * groups within the taxonomy? 865 | * 866 | * @since 2.2.0 867 | * 868 | * @return bool 869 | */ 870 | public function is_managed() { 871 | if ( current_user_can('administrator') ) { 872 | return false; 873 | } else { 874 | return ! empty( $this->args['managed'] ); 875 | } 876 | } 877 | 878 | /** 879 | * Handle bulk editing of users 880 | * 881 | * @since 1.0.0 882 | */ 883 | public function handle_bulk_actions( $redirect_to = '', $action = '', $user_ids = array() ) { 884 | 885 | // Get terms 886 | $terms = get_terms( $this->taxonomy, array( 887 | 'hide_empty' => false 888 | ) ); 889 | 890 | // Bail if no users or terms to work with 891 | if ( empty( $user_ids ) || empty( $terms ) ) { 892 | return $redirect_to; 893 | } 894 | 895 | // New actions array 896 | $actions = $changed_users = array(); 897 | 898 | // Compile available actions 899 | foreach ( $terms as $term ) { 900 | $key = "{$term->slug}-{$this->taxonomy}"; 901 | $actions[] = "add-{$key}"; 902 | $actions[] = "remove-{$key}"; 903 | } 904 | 905 | // Bail if not a supported bulk action 906 | if ( ! in_array( $action, $actions, true ) ) { 907 | return $redirect_to; 908 | } 909 | 910 | // Type & term 911 | $type = strstr( $action, '-', true ); 912 | $term = str_replace( "{$type}-", '', $action ); 913 | $term = str_replace( "-{$this->taxonomy}", '', $term ); 914 | 915 | // Loop through users 916 | foreach ( $user_ids as $user_id ) { 917 | 918 | // Should we update this user's terms? 919 | $should_update = false; 920 | 921 | // Skip if current user cannot assign terms to this user for this taxonomy 922 | if ( ! $this->can_assign( $user_id ) ) { 923 | continue; 924 | } 925 | 926 | // Get term slugs of user for this taxonomy 927 | $terms = wp_get_terms_for_user( $user_id, $this->taxonomy ); 928 | $update_terms = wp_list_pluck( $terms, 'slug' ); 929 | 930 | // Adding 931 | if ( 'add' === $type ) { 932 | if ( ! in_array( $term, $update_terms, true ) ) { 933 | $update_terms[] = $term; 934 | $should_update = true; 935 | } 936 | 937 | // Removing 938 | } elseif ( 'remove' === $type ) { 939 | 940 | // Skip if nothing to remove 941 | if ( empty( $update_terms ) ) { 942 | continue; 943 | } 944 | 945 | // Check the terms for this one 946 | $index = array_search( $term, $update_terms ); 947 | if ( ( false !== $index ) && isset( $update_terms[ $index ] ) ) { 948 | unset( $update_terms[ $index ] ); 949 | $should_update = true; 950 | } 951 | } 952 | 953 | // Delete all groups if they're empty 954 | if ( empty( $update_terms ) ) { 955 | $update_terms = null; 956 | } 957 | 958 | // Update terms for users 959 | if ( ( $update_terms !== $terms ) && ( true === $should_update ) ) { 960 | $changed_users[] = $user_id; 961 | wp_set_terms_for_user( $user_id, $this->taxonomy, $update_terms, true ); 962 | } 963 | } 964 | 965 | // Add count to redirection 966 | $redirect_to = add_query_arg( array( 967 | 'user_groups_count' => count( $changed_users ), 968 | 'action_type' => $type, 969 | 'term_slug' => $term, 970 | 'tax' => $this->taxonomy 971 | ), $redirect_to ); 972 | 973 | // Return redirection 974 | return $redirect_to; 975 | } 976 | 977 | /** 978 | * Maybe output a notice when bulk actions occur 979 | * 980 | * @since 1.0.0 981 | * 982 | * @return void 983 | */ 984 | public function bulk_notice() { 985 | 986 | // Bail if no count 987 | if ( ! isset( $_REQUEST['user_groups_count'] ) || empty( $_REQUEST['action_type'] ) || empty( $_REQUEST['tax'] ) ) { 988 | return; 989 | } 990 | 991 | // Get the changed count and sanitize a few keys 992 | $count = intval( $_REQUEST['user_groups_count'] ); 993 | $action = sanitize_key( $_REQUEST['action_type'] ); 994 | $group = sanitize_key( $_REQUEST['term_slug'] ); 995 | $tax = sanitize_key( $_REQUEST['tax'] ); 996 | 997 | // Bail if group is not for this taxonomy 998 | if ( $this->taxonomy !== $tax ) { 999 | return; 1000 | } 1001 | 1002 | // Get the labels 1003 | $tax = get_taxonomy( $this->taxonomy )->labels->singular_name; 1004 | $term = get_term_by( 'slug', $group, $this->taxonomy )->name; 1005 | 1006 | // Bail if term does not exist in taxonomy 1007 | if ( empty( $term ) ) { 1008 | return; 1009 | } 1010 | 1011 | // No users 1012 | if ( 0 === $count ) { 1013 | $type = 'warning'; 1014 | $text = ( 'add' === $action ) 1015 | ? sprintf( __( 'No users added to the "%s" %s.', 'wp-user-groups' ), $term, $tax ) 1016 | : sprintf( __( 'No users removed from the "%s" %s.', 'wp-user-groups' ), $term, $tax ); 1017 | 1018 | // Add/remove 1019 | } else { 1020 | $type = 'success'; 1021 | $text = ( 'add' === $action ) 1022 | ? sprintf( _n( '%s user added to the "%s" %s.', '%s users added to the "%s" %s.', $count, 'wp-user-groups' ), number_format_i18n( $count ), $term, $tax ) 1023 | : sprintf( _n( '%s user removed from the "%s" %s.', '%s users removed from the "%s" %s.', $count, 'wp-user-groups' ), number_format_i18n( $count ), $term, $tax ); 1024 | } 1025 | 1026 | // Output message 1027 | ?>

1030 |

taxonomy, array( 'hide_empty' => false ) ); 1048 | $slugs = wp_list_pluck( $terms, 'slug' ); 1049 | $current = isset( $_GET[ $this->taxonomy ] ) ? sanitize_key( $_GET[ $this->taxonomy ] ) : ''; 1050 | $viewing = array_search( $current, $slugs, true ); 1051 | 1052 | // Viewing a specific taxonomy term 1053 | if ( false !== $viewing ) { 1054 | 1055 | // Assemble the "Edit" h1 link 1056 | $edit = admin_url( 'edit-tags.php' ); 1057 | $args = array( 1058 | 'action' => 'edit', 1059 | 'taxonomy' => $this->taxonomy, 1060 | 'tag_ID' => $terms[ $viewing ]->term_id, 1061 | ); 1062 | $url = add_query_arg( $args, $edit ); ?> 1063 | 1064 |
1065 |

1066 | 1067 | 1068 | 1069 | 1070 | 1071 | 1072 | 1073 | 1074 | 1075 | 1076 | 1077 | 1078 | tax_singular_low, '' . $terms[ $viewing ]->name . '' ); ?> 1079 |

1080 | description ); ?> 1081 |
1082 |
1083 | 1084 | taxonomy ] ) ) { 1109 | return; 1110 | } 1111 | 1112 | // Sanitize taxonomies 1113 | $groups = array_map( 'sanitize_key', explode( ',', $_GET[ $this->taxonomy ] ) ); 1114 | 1115 | // Get terms 1116 | foreach ( $groups as $group ) { 1117 | $term = get_term_by( 'slug', $group, $this->taxonomy ); 1118 | $user_ids = get_objects_in_term( $term->term_id, $this->taxonomy ); 1119 | } 1120 | 1121 | // If no users are in this group, pass a 0 user ID 1122 | if ( empty( $user_ids ) ) { 1123 | $user_ids = array( 0 ); 1124 | } 1125 | 1126 | // Set IDs to be included 1127 | $user_query->query_vars['include'] = $user_ids; 1128 | } 1129 | 1130 | /** 1131 | * Generated user taxonomy query SQL 1132 | * 1133 | * @since 0.1.0 1134 | * 1135 | * @param object $user_query 1136 | */ 1137 | public function user_tax_query( $user_query = '' ) { 1138 | return get_tax_sql( $user_query->tax_query, $GLOBALS['wpdb']->users, 'ID' ); 1139 | } 1140 | 1141 | /** 1142 | * Get links to user taxonomy terms 1143 | * 1144 | * @since 0.1.0 1145 | * 1146 | * @param mixed $user 1147 | * @param string $page 1148 | * 1149 | * @return string 1150 | */ 1151 | private function get_user_term_links( $user, $page = null ) { 1152 | 1153 | // Get terms for user and this taxonomy 1154 | $terms = wp_get_terms_for_user( $user, $this->taxonomy ); 1155 | 1156 | // Bail if user has no terms 1157 | if ( empty( $terms ) ) { 1158 | return false; 1159 | } 1160 | 1161 | $in = array(); 1162 | $url = admin_url( 'users.php' ); 1163 | 1164 | // Loop through terms 1165 | foreach ( $terms as $term ) { 1166 | $args = array( $this->taxonomy => $term->slug ); 1167 | $href = empty( $page ) 1168 | ? add_query_arg( $args, $url ) 1169 | : add_query_arg( $args, $page ); 1170 | 1171 | // Add link to array 1172 | $in[] = '' . esc_html( $term->name ) . ''; 1173 | } 1174 | 1175 | return implode( ', ', $in ); 1176 | } 1177 | 1178 | /** 1179 | * Add taxonomy links for a column 1180 | * 1181 | * @since 0.1.0 1182 | * 1183 | * @param string $value 1184 | * @param string $column_name 1185 | * @param string $user_id 1186 | * @return string 1187 | */ 1188 | public function user_column_data( $value = '', $column_name = '', $user_id = 0 ) { 1189 | 1190 | // Only for this column name 1191 | if ( $column_name === $this->taxonomy ) { 1192 | 1193 | // Get term links 1194 | $links = $this->get_user_term_links( $user_id ); 1195 | 1196 | // Use links 1197 | if ( ! empty( $links ) ) { 1198 | $value = $links; 1199 | 1200 | // No links 1201 | } else { 1202 | $value = '—'; 1203 | } 1204 | } 1205 | 1206 | // Return possibly modified value 1207 | return $value; 1208 | } 1209 | 1210 | /** 1211 | * Add the label to the table header 1212 | * 1213 | * @since 0.1.0 1214 | * 1215 | * @param array $defaults 1216 | * 1217 | * @return array 1218 | */ 1219 | public function add_manage_users_columns( $defaults = array() ) { 1220 | 1221 | // Get the taxonomy 1222 | $tax = get_taxonomy( $this->taxonomy ); 1223 | 1224 | // Bail if no UI 1225 | if ( false === $tax->show_ui ) { 1226 | return $defaults; 1227 | } 1228 | 1229 | // Add the taxonomy 1230 | $defaults[ $this->taxonomy ] = $tax->labels->name; 1231 | 1232 | // Return columns 1233 | return $defaults; 1234 | } 1235 | 1236 | /** Nonce *****************************************************************/ 1237 | 1238 | /** 1239 | * Return the concatenated nonce key 1240 | * 1241 | * @since 2.1.0 1242 | * 1243 | * @return string 1244 | */ 1245 | private function get_nonce_key() { 1246 | return "wp_user_taxonomy_{$this->taxonomy}"; 1247 | } 1248 | 1249 | /** 1250 | * Output the nonce field for this user taxonomy table 1251 | * 1252 | * @since 2.1.0 1253 | */ 1254 | private function nonce_field() { 1255 | wp_nonce_field( $this->taxonomy, $this->get_nonce_key() ); 1256 | } 1257 | 1258 | /** 1259 | * Try to verify the nonce for this use taxonomy 1260 | * 1261 | * @since 2.1.0 1262 | * 1263 | * @return boolean 1264 | */ 1265 | private function verify_nonce() { 1266 | 1267 | // Nonce exists? 1268 | $retval = false; 1269 | $key = $this->get_nonce_key(); 1270 | $nonce = isset( $_REQUEST[ $key ] ) 1271 | ? $_REQUEST[ $key ] 1272 | : $retval; 1273 | 1274 | // Return true if nonce was verified 1275 | if ( ! empty( $nonce ) && wp_verify_nonce( $nonce, $this->taxonomy ) ) { 1276 | $retval = true; 1277 | } 1278 | 1279 | // Default return value 1280 | return $retval; 1281 | } 1282 | 1283 | /** Caps ******************************************************************/ 1284 | 1285 | /** 1286 | * Whether the current user can assign terms to another user 1287 | * 1288 | * @since 2.2.0 1289 | * 1290 | * @param int $user_id 1291 | * 1292 | * @return boolean 1293 | */ 1294 | private function can_assign( $user_id = 0 ) { 1295 | 1296 | // Default return value 1297 | $retval = false; 1298 | 1299 | // Get the taxonomy 1300 | $tax = get_taxonomy( $this->taxonomy ); 1301 | 1302 | // Check edit_user and assign 1303 | if ( current_user_can( 'edit_user', $user_id ) && current_user_can( $tax->cap->assign_terms ) ) { 1304 | $retval = true; 1305 | } 1306 | 1307 | // Return 1308 | return (bool) $retval; 1309 | } 1310 | } 1311 | endif; 1312 | --------------------------------------------------------------------------------