diff --git a/includes/admin/assets/css/um-admin-forms.css b/includes/admin/assets/css/um-admin-forms.css index 36692707..6beb8ecf 100644 --- a/includes/admin/assets/css/um-admin-forms.css +++ b/includes/admin/assets/css/um-admin-forms.css @@ -146,6 +146,14 @@ textarea.um-forms-field.um-small-field { flex: 0.9; } +.um-multi-selects-option-line .um-field-wrapper.um-custom-order-fields label select{ + width: auto; + display: inline; + font-weight: normal; + max-width: 70%; + flex: 0.9; +} + .um-multi-selects-option-line.um-admin-drag-fld .um-field-wrapper { width: calc( 100% - 90px ); } diff --git a/includes/admin/assets/js/um-admin-forms.js b/includes/admin/assets/js/um-admin-forms.js index bd183335..5bd8e40f 100644 --- a/includes/admin/assets/js/um-admin-forms.js +++ b/includes/admin/assets/js/um-admin-forms.js @@ -282,11 +282,23 @@ jQuery(document).ready( function() { html += ''; } + let dataTypesOptions = ''; + jQuery.each( um_forms_data.md_sorting_data_types, function( key, label ) { + dataTypesOptions += ''; + } ); + html += '' + selector_html + '' + '' + '' + wp.i18n.__( 'Remove', 'ultimate-member' ) + '' + '' + '' + + '' + + '' + '' + ''; list.append( html ); @@ -296,6 +308,8 @@ jQuery(document).ready( function() { jQuery( '#' + list.data('id_attr') + '-' + k ).parents('li').find('.um-field-wrapper.um-custom-order-fields input[name="meta_key"]').attr('name', 'um_metadata[_um_sorting_fields][other_data][' + k + '][meta_key]'); jQuery( '#' + list.data('id_attr') + '-' + k ).parents('li').find('.um-field-wrapper.um-custom-order-fields input[name="label"]').attr('name', 'um_metadata[_um_sorting_fields][other_data][' + k + '][label]'); + jQuery( '#' + list.data('id_attr') + '-' + k ).parents('li').find('.um-field-wrapper.um-custom-order-fields select[name="data_type"]').attr('name', 'um_metadata[_um_sorting_fields][other_data][' + k + '][data_type]'); + jQuery( '#' + list.data('id_attr') + '-' + k ).parents('li').find('.um-field-wrapper.um-custom-order-fields select[name="order"]').attr('name', 'um_metadata[_um_sorting_fields][other_data][' + k + '][order]'); }); diff --git a/includes/admin/class-admin.php b/includes/admin/class-admin.php index e9b5ed80..7a848412 100644 --- a/includes/admin/class-admin.php +++ b/includes/admin/class-admin.php @@ -1,21 +1,18 @@ array( 'sanitize' => 'text', ), + '_um_sortby_custom_type' => array( + 'sanitize' => 'text', + ), + '_um_sortby_custom_order' => array( + 'sanitize' => 'text', + ), '_um_enable_sorting' => array( 'sanitize' => 'bool', ), @@ -867,6 +870,12 @@ if ( ! class_exists( 'um\admin\Admin' ) ) { if ( isset( $item['label'] ) ) { $item['label'] = sanitize_text_field( $item['label'] ); } + if ( isset( $item['order'] ) ) { + $item['order'] = sanitize_text_field( $item['order'] ); + } + if ( isset( $item['data_type'] ) ) { + $item['data_type'] = sanitize_text_field( $item['data_type'] ); + } return $item; } else { diff --git a/includes/admin/core/class-admin-enqueue.php b/includes/admin/core/class-admin-enqueue.php index d77e4c76..8320386d 100644 --- a/includes/admin/core/class-admin-enqueue.php +++ b/includes/admin/core/class-admin-enqueue.php @@ -375,6 +375,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Enqueue' ) ) { ), admin_url( 'admin.php' ) ), + 'md_sorting_data_types' => UM()->member_directory()->sort_data_types, ) ); diff --git a/includes/admin/core/class-admin-forms.php b/includes/admin/core/class-admin-forms.php index e4fa63fb..6f4cb86d 100644 --- a/includes/admin/core/class-admin-forms.php +++ b/includes/admin/core/class-admin-forms.php @@ -1617,16 +1617,30 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) { if ( ! empty( $values ) && is_array( $values ) ) { foreach ( $values as $k => $value ) { - $other_key = ''; + $other_key = ''; $other_label = ''; + $other_type = ''; + $other_order = ''; + if ( is_array( $value ) ) { - $keys = array_keys( $value ); + $keys = array_keys( $value ); $other_key = $keys[0]; - $labels = array_values( $value ); - $other_label = $labels[0]; + if ( ! empty( $value['label'] ) ) { + $other_label = $value['label']; + } else { + $labels = array_values( $value ); + $other_label = $labels[0]; + } + + if ( ! empty( $value['type'] ) ) { + $other_type = $value['type']; + } + if ( ! empty( $value['order'] ) ) { + $other_order = $value['order']; + } } else { - if ( ! in_array( $value, array_keys( $field_data['options'] ) ) ) { + if ( ! array_key_exists( $value, $field_data['options'] ) ) { continue; } } @@ -1636,7 +1650,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) { $options = ''; foreach ( $field_data['options'] as $key => $option ) { if ( is_array( $value ) ) { - $selected = selected( $key == 'other', true, false ); + $selected = selected( 'other' === $key, true, false ); } else { $selected = selected( $key == $value, true, false ); } @@ -1648,12 +1662,25 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) { if ( $sorting ) { $html .= ''; } - $html .= " - - " . __( 'Remove', 'ultimate-member' ) . " - - - "; + + $data_types_html = ''; + foreach ( UM()->member_directory()->sort_data_types as $type_key => $type_label ) { + $data_types_html .= ''; + } + + $html .= ' + + ' . __( 'Remove', 'ultimate-member' ) . ' + + + + + '; } } elseif ( ! empty( $field_data['show_default_number'] ) && is_numeric( $field_data['show_default_number'] ) && $field_data['show_default_number'] > 0 ) { $i = 0; diff --git a/includes/admin/templates/directory/sorting.php b/includes/admin/templates/directory/sorting.php index b7f7832a..9a9566d0 100644 --- a/includes/admin/templates/directory/sorting.php +++ b/includes/admin/templates/directory/sorting.php @@ -24,6 +24,27 @@ $fields = array( 'value' => UM()->query()->get_meta_value( '_um_sortby_custom', null, 'na' ), 'conditional' => array( '_um_sortby', '=', 'other' ), ), + array( + 'id' => '_um_sortby_custom_type', + 'type' => 'select', + 'label' => __( 'Data type', 'ultimate-member' ), + 'tooltip' => __( 'To correct sort by a custom field, choose a data type', 'ultimate-member' ), + 'value' => UM()->query()->get_meta_value( '_um_sortby_custom_type', null, 'CHAR' ), + 'options' => UM()->member_directory()->sort_data_types, + 'conditional' => array( '_um_sortby', '=', 'other' ), + ), + array( + 'id' => '_um_sortby_custom_order', + 'type' => 'select', + 'label' => __( 'Order', 'ultimate-member' ), + 'tooltip' => __( 'To correct sort by a custom field, choose an order', 'ultimate-member' ), + 'value' => UM()->query()->get_meta_value( '_um_sortby_custom_order', null, 'ASC' ), + 'options' => array( + 'ASC' => __( 'ASC', 'ultimate-member' ), + 'DESC' => __( 'DESC', 'ultimate-member' ), + ), + 'conditional' => array( '_um_sortby', '=', 'other' ), + ), array( 'id' => '_um_sortby_custom_label', 'type' => 'text', diff --git a/includes/class-config.php b/includes/class-config.php index 1892d6fe..3273fa67 100644 --- a/includes/class-config.php +++ b/includes/class-config.php @@ -1,13 +1,12 @@ get_var( $wpdb->prepare( - "SELECT umeta_id - FROM {$wpdb->prefix}um_metadata - WHERE user_id = %d AND - um_key = %s + "SELECT umeta_id + FROM {$wpdb->prefix}um_metadata + WHERE user_id = %d AND + um_key = %s LIMIT 1", $object_id, $meta_key @@ -501,7 +510,6 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { } } - $profile_photo_where = ''; if ( $directory_data['has_profile_photo'] == 1 ) { $profile_photo_where = " AND umm_general.um_value LIKE '%s:13:\"profile_photo\";b:1;%'"; @@ -517,7 +525,7 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { $this->joins[] = "LEFT JOIN {$wpdb->prefix}um_metadata umm_general ON umm_general.user_id = u.ID"; $this->general_meta_joined = true; } - $this->where_clauses[] = "( umm_general.um_key = 'um_member_directory_data' AND + $this->where_clauses[] = "( umm_general.um_key = 'um_member_directory_data' AND umm_general.um_value LIKE '%s:14:\"account_status\";s:8:\"approved\";%' AND umm_general.um_value LIKE '%s:15:\"hide_in_members\";b:0;%'{$profile_photo_where}{$cover_photo_where} )"; } else { if ( ! empty( $cover_photo_where ) || ! empty( $profile_photo_where ) ) { @@ -578,7 +586,6 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { } } - if ( ! empty( $_POST['search'] ) ) { $search_line = trim( stripslashes( sanitize_text_field( $_POST['search'] ) ) ); @@ -600,7 +607,6 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { $this->is_search = true; } - //filters $filter_query = array(); if ( ! empty( $directory_data['search_fields'] ) ) { @@ -638,7 +644,6 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { } } - //unable default filter in case if we select other filters in frontend filters //if ( empty( $this->custom_filters_in_query ) ) { $default_filters = array(); @@ -676,7 +681,7 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { if ( ! empty( UM()->builtin()->saved_fields ) ) { foreach ( UM()->builtin()->saved_fields as $key => $data ) { - if ( $key == '_um_last_login' ) { + if ( '_um_last_login' === $key ) { continue; } @@ -694,12 +699,29 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { // handle sorting options // sort members by if ( $sortby == $directory_data['sortby_custom'] || in_array( $sortby, $custom_sort ) ) { + $custom_sort_order = ! empty( $directory_data['sortby_custom_order'] ) ? $directory_data['sortby_custom_order'] : 'ASC'; $this->joins[] = "LEFT JOIN {$wpdb->prefix}um_metadata umm_sort ON ( umm_sort.user_id = u.ID AND umm_sort.um_key = '{$sortby}' )"; - $custom_sort_type = apply_filters( 'um_member_directory_custom_sorting_type', 'CHAR', $sortby, $directory_data ); + $meta_query = new \WP_Meta_Query(); + $custom_sort_type = ! empty( $directory_data['sortby_custom_type'] ) ? $meta_query->get_cast_for_type( $directory_data['sortby_custom_type'] ) : 'CHAR'; + if ( ! empty( $directory_data['sorting_fields'] ) ) { + // phpcs:ignore WordPress.Security.NonceVerification -- already verified here + $sorting = sanitize_text_field( $_POST['sorting'] ); + $sorting_fields = maybe_unserialize( $directory_data['sorting_fields'] ); - $this->sql_order = " ORDER BY CAST( umm_sort.um_value AS {$custom_sort_type} ) {$order} "; + foreach ( $sorting_fields as $field ) { + if ( isset( $field[ $sorting ] ) ) { + $custom_sort_type = ! empty( $field['type'] ) ? $meta_query->get_cast_for_type( $field['type'] ) : 'CHAR'; + $custom_sort_order = $field['order']; + } + } + } + + /** This filter is documented in includes/core/class-member-directory.php */ + $custom_sort_type = apply_filters( 'um_member_directory_custom_sorting_type', $custom_sort_type, $sortby, $directory_data ); + + $this->sql_order = " ORDER BY CAST( umm_sort.um_value AS {$custom_sort_type} ) {$custom_sort_order} "; } elseif ( count( $numeric_sorting_keys ) && in_array( $sortby, $numeric_sorting_keys ) ) { @@ -802,7 +824,6 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { $this->sql_order = apply_filters( 'um_modify_sortby_parameter_meta', $this->sql_order, $sortby ); - $profiles_per_page = $directory_data['profiles_per_page']; if ( UM()->mobile()->isMobile() && isset( $directory_data['profiles_per_page_mobile'] ) ) { $profiles_per_page = $directory_data['profiles_per_page_mobile']; diff --git a/includes/core/class-member-directory.php b/includes/core/class-member-directory.php index b04cbca9..7d9c40af 100644 --- a/includes/core/class-member-directory.php +++ b/includes/core/class-member-directory.php @@ -1,20 +1,18 @@ ! empty( $metalabel ) ? $metalabel : $metakey, + $metakey => $metakey, + 'label' => ! empty( $metalabel ) ? $metalabel : $metakey, + 'type' => ! empty( $data_type ) ? $data_type : '', + 'order' => ! empty( $order ) ? $order : '', ); } } @@ -275,6 +285,10 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) { $value = sanitize_text_field( $value ); } elseif ( '_um_sortby_custom_label' === $key ) { $value = wp_strip_all_tags( $value ); + } elseif ( '_um_sortby_custom_type' === $key ) { + $value = sanitize_text_field( $value ); + } elseif ( '_um_sortby_custom_order' === $key ) { + $value = sanitize_text_field( $value ); } } @@ -308,6 +322,20 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) { $this->sorting_supported_fields = apply_filters( 'um_members_directory_custom_field_types_supported_sorting', array( 'number' ) ); + $this->sort_data_types = array( + 'CHAR' => __( 'CHAR', 'ultimate-member' ), + 'NUMERIC' => __( 'NUMERIC', 'ultimate-member' ), + 'BINARY' => __( 'BINARY', 'ultimate-member' ), + 'DATE' => __( 'DATE', 'ultimate-member' ), + 'DATETIME' => __( 'DATETIME', 'ultimate-member' ), + 'DECIMAL' => __( 'DECIMAL', 'ultimate-member' ), + 'SIGNED' => __( 'SIGNED', 'ultimate-member' ), + 'TIME' => __( 'TIME', 'ultimate-member' ), + 'UNSIGNED' => __( 'UNSIGNED', 'ultimate-member' ), + ); + + $this->sort_data_types = apply_filters( 'um_members_directory_sort_data_types', $this->sort_data_types ); + if ( ! empty( UM()->builtin()->saved_fields ) ) { foreach ( UM()->builtin()->saved_fields as $key => $data ) { if ( $key == '_um_last_login' ) { @@ -1422,23 +1450,64 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) { unset( $this->query_args['order'] ); } elseif ( ( ! empty( $directory_data['sortby_custom'] ) && $sortby == $directory_data['sortby_custom'] ) || in_array( $sortby, $custom_sort ) ) { + $custom_sort_order = ! empty( $directory_data['sortby_custom_order'] ) ? $directory_data['sortby_custom_order'] : 'ASC'; - $custom_sort_type = apply_filters( 'um_member_directory_custom_sorting_type', 'CHAR', $sortby, $directory_data ); + $meta_query = new \WP_Meta_Query(); + $custom_sort_type = ! empty( $directory_data['sortby_custom_type'] ) ? $meta_query->get_cast_for_type( $directory_data['sortby_custom_type'] ) : 'CHAR'; + if ( ! empty( $directory_data['sorting_fields'] ) ) { + // phpcs:ignore WordPress.Security.NonceVerification -- already verified here + $sorting = sanitize_text_field( $_POST['sorting'] ); + $sorting_fields = maybe_serialize( $directory_data['sorting_fields'] ); + + foreach ( $sorting_fields as $field ) { + if ( isset( $field[ $sorting ] ) ) { + $custom_sort_type = ! empty( $field['type'] ) ? $meta_query->get_cast_for_type( $field['type'] ) : 'CHAR'; + $custom_sort_order = $field['order']; + } + } + } + /** + * Filters the sorting MySQL type in member directory custom sorting query. + * + * Note: Possible MySQL types are BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|DECIMAL + * + * @since 2.1.3 + * @hook um_member_directory_custom_sorting_type + * + * @param {string} $custom_sort_type MySQL type to cast meta_value. 'CHAR' is default. + * @param {string} $sortby meta_key used for sorting. + * @param {array} $directory_data Member directory data. + * + * @return {string} MySQL type to cast meta_value. + * @example Change type to DATE by the directory ID and mete_key. + * function my_um_member_directory_custom_sorting_type( $custom_sort_type, $sortby, $directory_data ) { + * if ( '{selected member directory ID}' == $directory_data['form_id'] && '{custom_date_key}' === $sortby ) { + * $custom_sort_type = 'DATE'; + * } + * + * return $custom_sort_type; + * } + * add_filter( 'um_member_directory_custom_sorting_type', 'my_um_member_directory_custom_sorting_type', 10, 3 ); + */ + $custom_sort_type = apply_filters( 'um_member_directory_custom_sorting_type', $custom_sort_type, $sortby, $directory_data ); $this->query_args['meta_query'][] = array( - 'relation' => 'OR', + 'relation' => 'OR', $sortby . '_cs' => array( - 'key' => $sortby, - 'compare' => 'EXISTS', - 'type' => $custom_sort_type, + 'key' => $sortby, + 'compare' => 'EXISTS', + 'type' => $custom_sort_type, ), array( - 'key' => $sortby, - 'compare' => 'NOT EXISTS', - ) + 'key' => $sortby, + 'compare' => 'NOT EXISTS', + ), ); - $this->query_args['orderby'] = array( $sortby . '_cs' => 'ASC', 'user_login' => 'ASC' ); + $this->query_args['orderby'] = array( + $sortby . '_cs' => $custom_sort_order, + 'user_login' => 'ASC', + ); } else {