EditProfile
5 years ago
Login
3 years ago
MemberDirectory
7 months ago
PasswordReset
5 years ago
Registration
3 years ago
UserProfile
1 year ago
AbstractBuildScratch.php
7 months ago
AbstractMemberDirectoryTheme.php
4 months ago
AbstractTheme.php
11 months ago
FieldListing.php
7 months ago
MemberDirectoryListing.php
2 months ago
MemberDirectoryTrait.php
1 year ago
ProfileFieldListing.php
2 months ago
ThemeInterface.php
5 years ago
ThemesRepository.php
1 year ago
index.php
5 years ago
MemberDirectoryTrait.php
411 lines
| 1 | <?php |
| 2 | |
| 3 | namespace ProfilePress\Core\Themes\DragDrop; |
| 4 | |
| 5 | use WP_User; |
| 6 | use WP_User_Query; |
| 7 | |
| 8 | trait MemberDirectoryTrait |
| 9 | { |
| 10 | protected int $_form_id; |
| 11 | |
| 12 | protected array $args; |
| 13 | |
| 14 | public function initializeMemberDirectoryTrait($_form_id, $args) |
| 15 | { |
| 16 | $this->_form_id = $_form_id; |
| 17 | |
| 18 | $this->args = $args; |
| 19 | } |
| 20 | |
| 21 | protected function search_query_key(): string |
| 22 | { |
| 23 | return 'search-' . $this->_form_id; |
| 24 | } |
| 25 | |
| 26 | protected function search_filter_query_params() |
| 27 | { |
| 28 | static $cache = []; |
| 29 | |
| 30 | if ( ! isset($cache[$this->_form_id])) { |
| 31 | |
| 32 | $query_params = []; |
| 33 | |
| 34 | if (empty($query_params) && ! empty($_GET['filter' . $this->_form_id])) { |
| 35 | $query_params = json_decode(base64_decode($_GET['filter' . $this->_form_id]), true); |
| 36 | } |
| 37 | |
| 38 | $cache[$this->_form_id] = $query_params; |
| 39 | } |
| 40 | |
| 41 | return $cache[$this->_form_id]; |
| 42 | } |
| 43 | |
| 44 | protected function wp_user_query() |
| 45 | { |
| 46 | static $cache = []; |
| 47 | |
| 48 | if ( ! isset($cache[$this->_form_id])) { |
| 49 | |
| 50 | $search_q_key = self::search_query_key(); |
| 51 | |
| 52 | $current_page = $this->get_current_page(); |
| 53 | |
| 54 | $users_per_page = $this->args['users_per_page'] ?: 9; |
| 55 | |
| 56 | $roles = array_filter($this->args['user_roles'] ?? []); |
| 57 | |
| 58 | $include_user_ids = $this->args['specific_users'] ?? ''; |
| 59 | |
| 60 | $exclude_user_ids = $this->args['exclude_users'] ?? ''; |
| 61 | |
| 62 | $default_sort_method = $this->args['sort_default'] ?? 'newest'; |
| 63 | |
| 64 | if (empty($include_user_ids)) { |
| 65 | $include_user_ids = []; |
| 66 | } else { |
| 67 | $include_user_ids = array_map(function ($val) { |
| 68 | return absint(trim($val)); |
| 69 | }, explode(',', $include_user_ids)); |
| 70 | } |
| 71 | |
| 72 | if (empty($exclude_user_ids)) { |
| 73 | $exclude_user_ids = []; |
| 74 | } else { |
| 75 | $exclude_user_ids = array_map(function ($val) { |
| 76 | return absint(trim($val)); |
| 77 | }, explode(',', $exclude_user_ids)); |
| 78 | } |
| 79 | |
| 80 | $search_field_harsh_map = [ |
| 81 | 'pp_email_address' => 'user_email', |
| 82 | 'pp_website_url' => 'user_url', |
| 83 | 'pp_display_name' => 'display_name' |
| 84 | ]; |
| 85 | |
| 86 | $db_search_fields = array_filter($this->args['search_fields'] ?? [], function ($item) { |
| 87 | return ! empty($item); |
| 88 | }); |
| 89 | |
| 90 | // get meta fields to be used by meta query of WP_User_Query |
| 91 | $meta_fields = array_filter($db_search_fields, function ($item) use ($search_field_harsh_map) { |
| 92 | return ! in_array($item, array_keys($search_field_harsh_map)); |
| 93 | }); |
| 94 | |
| 95 | // get wp_user table columns to use in searching for users |
| 96 | $search_columns = array_reduce($db_search_fields, function ($carry, $item) use ($search_field_harsh_map) { |
| 97 | |
| 98 | if (isset($search_field_harsh_map[$item])) { |
| 99 | $carry[] = $search_field_harsh_map[$item]; |
| 100 | } |
| 101 | |
| 102 | return $carry; |
| 103 | |
| 104 | }, ['user_login']); |
| 105 | |
| 106 | $sort_method = ppress_var($_GET, 'sortby' . $this->_form_id, $default_sort_method, true); |
| 107 | |
| 108 | $query_params = $this->search_filter_query_params(); |
| 109 | |
| 110 | $offset = $current_page > 1 ? ($current_page - 1) * $users_per_page : 0; |
| 111 | |
| 112 | $filter_meta_fields = isset($query_params['filters']) ? array_filter( |
| 113 | array_map('ppress_recursive_trim', ppress_var($query_params, 'filters', [], true)) |
| 114 | ) : []; |
| 115 | |
| 116 | if ( ! empty($filter_meta_fields['ppress_user_role'])) { |
| 117 | $roles = [sanitize_text_field($filter_meta_fields['ppress_user_role'])]; |
| 118 | } |
| 119 | |
| 120 | $wp_user_query = $this->member_directory_users([ |
| 121 | 'number' => $users_per_page, |
| 122 | 'paged' => $current_page, |
| 123 | 'offset' => $offset, |
| 124 | 'roles' => $roles, |
| 125 | 'include_user_ids' => $include_user_ids, |
| 126 | 'exclude_user_ids' => $exclude_user_ids, |
| 127 | 'sort_method' => $sort_method, |
| 128 | 'search_columns' => $search_columns, |
| 129 | 'search_meta_fields' => $meta_fields, |
| 130 | 'filter_meta_fields' => $filter_meta_fields, |
| 131 | 'is_search_query' => ! empty($query_params['ppmd-search']), |
| 132 | 'search_q' => isset($query_params[$search_q_key]) ? sanitize_text_field($query_params[$search_q_key]) : '' |
| 133 | ]); |
| 134 | |
| 135 | $users = $wp_user_query->get_results(); |
| 136 | |
| 137 | $total_users_found = $wp_user_query->get_total(); |
| 138 | |
| 139 | if ( |
| 140 | apply_filters('ppress_member_directory_enable_user_role_array_filtering', false) && |
| 141 | ! empty($roles) && |
| 142 | ! empty($query_params['ppmd-search']) && |
| 143 | is_array($users) && |
| 144 | ! empty($users) |
| 145 | ) { |
| 146 | |
| 147 | /** |
| 148 | * @var int $key |
| 149 | * @var WP_User $user |
| 150 | */ |
| 151 | foreach ($users as $key => $user) { |
| 152 | |
| 153 | if (empty(array_intersect($user->roles, $roles))) { |
| 154 | unset($users[$key]); |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | $total_users_found = count($users); |
| 159 | |
| 160 | // only necessary because if filtering by role, LIMIT is removed from query. |
| 161 | // limit is removed in pre_user_query action callback. |
| 162 | $users = array_slice($users, $offset, $users_per_page); |
| 163 | } |
| 164 | |
| 165 | $cache[$this->_form_id] = [ |
| 166 | 'users' => $users, |
| 167 | 'total_users_found' => $total_users_found |
| 168 | ]; |
| 169 | } |
| 170 | |
| 171 | return $cache[$this->_form_id]; |
| 172 | } |
| 173 | |
| 174 | public function member_directory_users(array $parsed_args = []): WP_User_Query |
| 175 | { |
| 176 | $parsed_args = apply_filters('ppress_member_directory_parsed_args', wp_parse_args($parsed_args, [ |
| 177 | 'number' => 9, |
| 178 | 'paged' => 1, |
| 179 | 'offset' => 0, |
| 180 | 'roles' => [], |
| 181 | 'include_user_ids' => [], |
| 182 | 'exclude_user_ids' => [], |
| 183 | 'count_total' => true, |
| 184 | 'sort_method' => 'newest', |
| 185 | 'is_search_query' => false, |
| 186 | 'search_q' => '', |
| 187 | 'search_columns' => ['user_login', 'user_email', 'user_url', 'display_name'], |
| 188 | 'search_meta_fields' => ['first_name', 'last_name'], |
| 189 | 'filter_meta_fields' => [] |
| 190 | ])); |
| 191 | |
| 192 | $args = [ |
| 193 | 'number' => $parsed_args['number'], |
| 194 | 'paged' => $parsed_args['paged'], |
| 195 | 'offset' => $parsed_args['offset'], |
| 196 | 'role__in' => $parsed_args['roles'], |
| 197 | 'include' => $parsed_args['include_user_ids'], |
| 198 | 'exclude' => $parsed_args['exclude_user_ids'] |
| 199 | ]; |
| 200 | |
| 201 | // no check for username because it's the default orderby |
| 202 | switch ($parsed_args['sort_method']) { |
| 203 | case 'newest': |
| 204 | $args['orderby'] = 'user_registered'; |
| 205 | $args['order'] = 'DESC'; |
| 206 | break; |
| 207 | case 'oldest': |
| 208 | $args['orderby'] = 'user_registered'; |
| 209 | break; |
| 210 | case 'username': |
| 211 | $args['orderby'] = 'user_login'; |
| 212 | break; |
| 213 | case 'first-name': |
| 214 | $args['meta_key'] = 'first_name'; |
| 215 | $args['orderby'] = 'meta_value'; |
| 216 | $args['order'] = 'ASC'; |
| 217 | break; |
| 218 | case 'last-name': |
| 219 | $args['meta_key'] = 'last_name'; |
| 220 | $args['orderby'] = 'meta_value'; |
| 221 | $args['order'] = 'ASC'; |
| 222 | break; |
| 223 | default: |
| 224 | $args['meta_key'] = $parsed_args['sort_method']; |
| 225 | $args['orderby'] = 'meta_value'; |
| 226 | $args['order'] = 'ASC'; |
| 227 | } |
| 228 | |
| 229 | if ($parsed_args['is_search_query'] === true) { |
| 230 | |
| 231 | $search_term = sanitize_text_field($parsed_args['search_q']); |
| 232 | |
| 233 | $search_columns = $parsed_args['search_columns']; |
| 234 | |
| 235 | $roles = $parsed_args['roles']; |
| 236 | |
| 237 | $filter_meta_fields = array_filter($parsed_args['filter_meta_fields'], function ($v, $k) { |
| 238 | return $k != 'ppress_user_role'; |
| 239 | }, ARRAY_FILTER_USE_BOTH); |
| 240 | |
| 241 | if ( ! empty($search_term)) { |
| 242 | $args['search'] = '*' . $search_term . '*'; |
| 243 | } |
| 244 | |
| 245 | // we need to empty out the search column so wp user query doesn't restrict the search only |
| 246 | // to supplied search columns. We want to also check usermeta too. |
| 247 | add_filter('user_search_columns', '__return_empty_array', 999999999); |
| 248 | |
| 249 | add_action('pre_user_query', function ($query) use ($roles) { |
| 250 | // removes "AND ()" from query which causes the sql to be invalid. |
| 251 | // SELECT DISTINCT wp_users.* FROM wp_users INNER JOIN wp_usermeta ON |
| 252 | // ( wp_users.ID = wp_usermeta.user_id ) WHERE 1=1 AND ( wp_users.user_nicename LIKE '%little%' OR |
| 253 | // wp_users.user_email LIKE '%little%' OR ( ( wp_usermeta.meta_key = 'first_name' AND wp_usermeta.meta_value LIKE '%little%' ) |
| 254 | // OR ( wp_usermeta.meta_key = 'last_name' AND wp_usermeta.meta_value LIKE '%little%' ) OR ( wp_usermeta.meta_key = 'twitter' |
| 255 | // AND wp_usermeta.meta_value LIKE '%little%' ) ) ) AND () ORDER BY user_registered DESC |
| 256 | $query->query_where = str_replace('AND ()', '', $query->query_where); |
| 257 | |
| 258 | if ( apply_filters('ppress_member_directory_enable_user_role_array_filtering', false) && ! empty($roles)) { |
| 259 | // remove query LIMIT so we can get actual total number of result when filitering by roles |
| 260 | unset($query->query_limit); |
| 261 | } |
| 262 | }); |
| 263 | |
| 264 | /** |
| 265 | * Modifies the query so we can tactically include searching of $search_columns in wp_users table |
| 266 | * @see https://wordpress.stackexchange.com/a/248674/59917 |
| 267 | */ |
| 268 | add_filter('get_meta_sql', function ($sql) use ($search_term, $search_columns, $filter_meta_fields) { |
| 269 | |
| 270 | global $wpdb; |
| 271 | |
| 272 | // Only run once: |
| 273 | static $nr = 0; |
| 274 | |
| 275 | if (0 != $nr++) return $sql; |
| 276 | |
| 277 | $OR_placeholders = []; |
| 278 | $queries = []; |
| 279 | |
| 280 | |
| 281 | if (is_array($search_columns) && ! empty($search_columns)) { |
| 282 | |
| 283 | foreach ($search_columns as $search_column) { |
| 284 | |
| 285 | if ( ! empty($search_term)) { |
| 286 | $OR_placeholders[] = '%s'; |
| 287 | $queries[] = $wpdb->prepare("{$wpdb->users}.$search_column LIKE %s", '%' . $wpdb->esc_like($search_term) . '%'); |
| 288 | } |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | $OR_placeholders[] = '%s'; |
| 293 | |
| 294 | $queries[] = ppress_mb_function( |
| 295 | ['mb_substr', 'substr'], |
| 296 | [ |
| 297 | $sql['where'], |
| 298 | 5, |
| 299 | ppress_mb_function(['mb_strlen', 'strlen'], [$sql['where']]) |
| 300 | ] |
| 301 | ); |
| 302 | |
| 303 | $filter_queries = ''; |
| 304 | |
| 305 | /** @see https://stackoverflow.com/a/65653006/2648410 */ |
| 306 | if ( ! empty($filter_meta_fields)) { |
| 307 | |
| 308 | foreach ($filter_meta_fields as $meta_key => $meta_value) { |
| 309 | |
| 310 | $filter_queries .= "AND EXISTS ( SELECT 1 FROM {$wpdb->usermeta} WHERE {$wpdb->users}.ID = {$wpdb->usermeta}.user_id AND "; |
| 311 | |
| 312 | $filter_queries .= '('; |
| 313 | |
| 314 | if (is_array($meta_value)) { |
| 315 | |
| 316 | $meta_value_count = count($meta_value); |
| 317 | |
| 318 | foreach ($meta_value as $index2 => $value) { |
| 319 | $index2++; |
| 320 | |
| 321 | $filter_queries .= $wpdb->prepare( |
| 322 | "({$wpdb->usermeta}.meta_key = %s AND {$wpdb->usermeta}.meta_value LIKE %s)", |
| 323 | $meta_key, |
| 324 | '%' . $wpdb->esc_like($value) . '%' |
| 325 | ); |
| 326 | |
| 327 | if ($index2 != $meta_value_count) { |
| 328 | $filter_queries .= ' OR '; |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | } else { |
| 333 | |
| 334 | $filter_queries .= $wpdb->prepare( |
| 335 | "({$wpdb->usermeta}.meta_key = %s AND {$wpdb->usermeta}.meta_value = %s)", |
| 336 | $meta_key, |
| 337 | $meta_value |
| 338 | ); |
| 339 | } |
| 340 | |
| 341 | $filter_queries .= ')'; |
| 342 | |
| 343 | $filter_queries .= ')'; |
| 344 | } |
| 345 | } |
| 346 | |
| 347 | $queries[] = $filter_queries; |
| 348 | |
| 349 | $where = vsprintf( |
| 350 | " AND ((" . implode(' OR ', $OR_placeholders) . " ) %s )", |
| 351 | $queries |
| 352 | ); |
| 353 | |
| 354 | $sql['where'] = $where; |
| 355 | |
| 356 | return $sql; |
| 357 | }); |
| 358 | |
| 359 | if (is_array($parsed_args['search_meta_fields']) && ! empty($parsed_args['search_meta_fields']) && ! empty($search_term)) { |
| 360 | |
| 361 | $args['meta_query'][0]['relation'] = 'OR'; |
| 362 | |
| 363 | foreach ($parsed_args['search_meta_fields'] as $search_meta_field) { |
| 364 | |
| 365 | $args['meta_query'][0][] = [ |
| 366 | 'key' => $search_meta_field, |
| 367 | 'value' => $search_term, |
| 368 | 'compare' => 'LIKE' |
| 369 | ]; |
| 370 | } |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | return new WP_User_Query(apply_filters('ppress_member_directory_wp_user_args', $args, $this->_form_id, $this)); |
| 375 | } |
| 376 | |
| 377 | protected function get_current_page(): int |
| 378 | { |
| 379 | return absint(max(1, ppressGET_var('mdpage' . $this->_form_id, 1, true))); |
| 380 | } |
| 381 | |
| 382 | protected function display_pagination( |
| 383 | $total_users_found, |
| 384 | $users_per_page, |
| 385 | $prev_text = '<span class="ppress-material-icons">keyboard_arrow_left</span>', |
| 386 | $next_text = '<span class="ppress-material-icons">keyboard_arrow_right</span>' |
| 387 | ) |
| 388 | { |
| 389 | $current_page = $this->get_current_page(); |
| 390 | |
| 391 | $total_pages = ceil($total_users_found / ($users_per_page ?: 9)); |
| 392 | |
| 393 | if ($total_pages > 1) { |
| 394 | |
| 395 | echo '<div class="ppmd-pagination-wrap">'; |
| 396 | |
| 397 | // from https://wordpress.stackexchange.com/questions/275527/paginate-links-ignore-my-format |
| 398 | echo paginate_links([ |
| 399 | //'base' => '%_%', somehow with this enabled, pagination breaks where page 1 link becomes the current page url |
| 400 | 'total' => $total_pages, |
| 401 | 'current' => $current_page, |
| 402 | 'format' => '?mdpage' . $this->_form_id . '=%#%', |
| 403 | 'prev_text' => $prev_text, |
| 404 | 'next_text' => $next_text |
| 405 | ]); |
| 406 | |
| 407 | echo '</div>'; |
| 408 | } |
| 409 | } |
| 410 | } |
| 411 |