query_vars) && !is_network_admin()) // NOT in Network admin panels. if(is_array($qv = $query->query_vars) && ($s = trim($qv["search"], "* \t\n\r\0\x0B")) && ($s = "%".esc_sql(c_ws_plugin__s2member_utils_strings::like_escape($s))."%")) { $query->query_fields = "SQL_CALC_FOUND_ROWS DISTINCT(`".$wpdb->users."`.`ID`)"; $query->query_from = " FROM `".$wpdb->users."`, `".$wpdb->usermeta."`"; // Include meta table also. $query->query_where = " WHERE `".$wpdb->users."`.`ID` = `".$wpdb->usermeta."`.`user_id`"; // Join w/ meta table. $query->query_where .= " AND (".apply_filters("ws_plugin__s2member_before_users_list_search_where_or_before", "", get_defined_vars()); $query->query_where .= " (`".$wpdb->users."`.`user_login` LIKE '".$s."' OR `".$wpdb->users."`.`user_nicename` LIKE '".$s."' OR `".$wpdb->users."`.`display_name` LIKE '".$s."' OR `".$wpdb->users."`.`user_email` LIKE '".$s."' OR `".$wpdb->users."`.`user_url` LIKE '".$s."')"; $query->query_where .= " OR ((`".$wpdb->usermeta."`.`meta_key` = 'first_name' OR `".$wpdb->usermeta."`.`meta_key` = 'last_name') AND `".$wpdb->usermeta."`.`meta_value` LIKE '".$s."')"; $query->query_where .= " OR (`".$wpdb->usermeta."`.`meta_key` = '".$wpdb->base_prefix."s2member_subscr_id' AND `".$wpdb->usermeta."`.`meta_value` LIKE '".$s."')"; $query->query_where .= " OR (`".$wpdb->usermeta."`.`meta_key` = '".$wpdb->base_prefix."s2member_custom' AND `".$wpdb->usermeta."`.`meta_value` LIKE '".$s."')"; $query->query_where .= " OR (`".$wpdb->usermeta."`.`meta_key` = '".$wpdb->base_prefix."s2member_custom_fields' AND `".$wpdb->usermeta."`.`meta_value` LIKE '".$s."')"; if(apply_filters("ws_plugin__s2member_users_list_search_admin_notes", FALSE, get_defined_vars())) // Off by default; this can get very slow on large sites. $query->query_where .= " OR (`".$wpdb->usermeta."`.`meta_key` = '".$wpdb->base_prefix."s2member_notes' AND `".$wpdb->usermeta."`.`meta_value` LIKE '".$s."')"; $query->query_where .= apply_filters("ws_plugin__s2member_before_users_list_search_where_or_after", "", get_defined_vars()).")"; // Leaving room for additional searches here. if(is_multisite()) // On a Multisite Network we need to make sure we're searching only users w/ capabilities on this blog. $query->query_where .= " AND `".$wpdb->users."`.`ID` IN(SELECT DISTINCT(`user_id`) FROM `".$wpdb->usermeta."` WHERE `meta_key` = '".$wpdb->prefix."capabilities')"; $query->query_from = apply_filters("ws_plugin__s2member_before_users_list_search_from", $query->query_from, get_defined_vars()); $query->query_where = apply_filters("ws_plugin__s2member_before_users_list_search_where", $query->query_where, get_defined_vars()); } if(is_admin() && !is_network_admin() && !empty($GLOBALS['pagenow']) && $GLOBALS['pagenow'] === 'users.php' && !empty($_GET['s2member_view']) && isset($query->query_vars)) { $is_eot_current_view = $_GET['s2member_view'] === 'eot_current'; $is_eot_previous_view = $_GET['s2member_view'] === 'eot_previous'; if($is_eot_current_view || $is_eot_previous_view) { $current_eot_key = esc_sql($wpdb->prefix.'s2member_auto_eot_time'); $last_eot_key = esc_sql($wpdb->prefix.'s2member_last_auto_eot_time'); $processed_eot_key = esc_sql($wpdb->prefix.'s2member_last_auto_eot_processed_time'); if($is_eot_current_view) { //260828.0231 Current End-of-Term is the actionable queue: only users with a current EOT belong here, so native ascending EOT sorting never has blank rows to place first. $query->query_where .= " AND EXISTS (SELECT 1 FROM `".$wpdb->usermeta."` `___s2_eot_current` WHERE `___s2_eot_current`.`user_id` = `".$wpdb->users."`.`ID` AND `___s2_eot_current`.`meta_key` = '".$current_eot_key."' AND CAST(`___s2_eot_current`.`meta_value` AS UNSIGNED) > 0)"; if(empty($_REQUEST['orderby']) && empty($_REQUEST['s'])) { $query->query_from .= " LEFT JOIN `".$wpdb->usermeta."` `___s2_eot_order_current` ON (`".$wpdb->users."`.`ID` = `___s2_eot_order_current`.`user_id` AND `___s2_eot_order_current`.`meta_key` = '".$current_eot_key."')"; $query->query_orderby = "ORDER BY CAST(`___s2_eot_order_current`.`meta_value` AS UNSIGNED) ASC"; } } else { //260828.0231 Previous End-of-Term includes either archived EOT or demotion history so partial/legacy records remain discoverable without mixing them into the current queue. $query->query_where .= " AND (EXISTS (SELECT 1 FROM `".$wpdb->usermeta."` `___s2_eot_last` WHERE `___s2_eot_last`.`user_id` = `".$wpdb->users."`.`ID` AND `___s2_eot_last`.`meta_key` = '".$last_eot_key."' AND CAST(`___s2_eot_last`.`meta_value` AS UNSIGNED) > 0) OR EXISTS (SELECT 1 FROM `".$wpdb->usermeta."` `___s2_eot_processed` WHERE `___s2_eot_processed`.`user_id` = `".$wpdb->users."`.`ID` AND `___s2_eot_processed`.`meta_key` = '".$processed_eot_key."' AND CAST(`___s2_eot_processed`.`meta_value` AS UNSIGNED) > 0))"; if(empty($_REQUEST['orderby']) && empty($_REQUEST['s'])) { //260828.0358 Previous End-of-Term is a processing-history list, so show the most recently processed EOT actions first; records without a recoverable demotion time naturally remain after dated records. $query->query_from .= " LEFT JOIN `".$wpdb->usermeta."` `___s2_eot_order_processed` ON (`".$wpdb->users."`.`ID` = `___s2_eot_order_processed`.`user_id` AND `___s2_eot_order_processed`.`meta_key` = '".$processed_eot_key."')"; $query->query_orderby = "ORDER BY CAST(`___s2_eot_order_processed`.`meta_value` AS UNSIGNED) DESC"; } } } } if(is_admin() && !is_network_admin() && !empty($GLOBALS['pagenow']) && $GLOBALS['pagenow'] === 'users.php' && !empty($_GET['role']) && $_GET['role'] === 's2member_pending_deletion' && isset($query->query_vars) && empty($_REQUEST['orderby']) && empty($_REQUEST['s'])) { //260828.0317 Pending Deletion is an action queue, so show the accounts waiting longest first by their actual EOT demotion time instead of WordPress's Username default. $processed_eot_key = esc_sql($wpdb->prefix.'s2member_last_auto_eot_processed_time'); $query->query_from .= " LEFT JOIN `".$wpdb->usermeta."` `___s2_pending_order_processed` ON (`".$wpdb->users."`.`ID` = `___s2_pending_order_processed`.`user_id` AND `___s2_pending_order_processed`.`meta_key` = '".$processed_eot_key."')"; $query->query_orderby = "ORDER BY CAST(`___s2_pending_order_processed`.`meta_value` AS UNSIGNED) ASC"; } foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v; do_action("ws_plugin__s2member_after_users_list_search", get_defined_vars()); unset($__refs, $__v); } /** * Adds columns to the list of Users. * * @package s2Member\Users_List * @since 3.5 * * @attaches-to ``add_filter ("manage_users_columns");`` * * @param array $cols Expects an array of columns to be passed through by the Filter. * * @return array Array of columns, merged with columns introduced by this routine. */ public static function users_list_cols($cols = array()) { foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v; do_action("ws_plugin__s2member_before_users_list_cols", get_defined_vars()); unset($__refs, $__v); $cols["s2member_registration_time"] = "Registration Date"; if(apply_filters("ws_plugin__s2member_users_list_cols_display_paid_registration_times", FALSE)) $cols["s2member_paid_registration_times"] = "Paid Registr. Date"; $cols["s2member_subscr_id"] = "Paid Subscr. ID"; if(!is_multisite() || !c_ws_plugin__s2member_utils_conds::is_multisite_farm() || is_main_site()) $cols["s2member_ccaps"] = "Custom Capabilities"; $cols["s2member_auto_eot_time"] = "EOT Time"; //260822.1509 Keep the triggering EOT and its actual processing time separate; both remain optional history columns on the normal Users screen. $cols["s2member_last_auto_eot_time"] = "Last EOT"; $cols["s2member_last_auto_eot_processed_time"] = "EOT Demotion"; if($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["custom_reg_fields"]) foreach(json_decode($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["custom_reg_fields"], TRUE) as $field) { $field_var = preg_replace("/[^a-z0-9]/i", "_", strtolower($field["id"])); $field_id_class = preg_replace("/_/", "-", $field_var); $field_title = ucwords(preg_replace("/_/", " ", $field_var)); $cols["s2member_custom_field_".$field_var] = $field_title; } $cols["s2member_login_counter"] = "# Of Logins"; $cols["s2member_last_login_time"] = "Last Login Time"; foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v; do_action("ws_plugin__s2member_during_users_list_cols", get_defined_vars()); unset($__refs, $__v); return apply_filters("ws_plugin__s2member_users_list_cols", $cols, get_defined_vars()); } /** * Re-adds required EOT columns late on s2Member's review views after column-management plugins have filtered the Users table. * * @package s2Member\Users_List * @since 260826.0542 * * @attaches-to ``add_filter("manage_users_columns");`` * * @param array $cols User list columns after other plugins have filtered them. * * @return array User list columns with the required EOT review columns restored. */ public static function users_list_required_eot_cols($cols = array()) { if(!is_admin() || empty($GLOBALS['pagenow']) || $GLOBALS['pagenow'] !== 'users.php') return $cols; $is_eot_current_view = !empty($_GET['s2member_view']) && $_GET['s2member_view'] === 'eot_current'; $is_eot_previous_view = !empty($_GET['s2member_view']) && $_GET['s2member_view'] === 'eot_previous'; $is_pending_deletion_view = !empty($_GET['role']) && $_GET['role'] === 's2member_pending_deletion'; //260828.0231 Restore only the columns required by each s2Member review view after Users-table customization plugins apply saved layouts. if($is_eot_current_view || $is_pending_deletion_view) $cols['s2member_auto_eot_time'] = 'EOT Time'; if($is_eot_previous_view || $is_pending_deletion_view) { $cols['s2member_last_auto_eot_time'] = 'Last EOT'; $cols['s2member_last_auto_eot_processed_time'] = 'EOT Demotion'; } return $cols; } /** * Hides optional s2Member history columns by default on the Users screen. * * @package s2Member\Users_List * @since 260822.1509 * * @attaches-to ``add_filter("default_hidden_columns");`` * * @param array $hidden Default hidden column IDs. * @param WP_Screen $screen Current screen object. * * @return array Filtered hidden column IDs. */ public static function users_list_default_hidden_cols($hidden = array(), $screen = NULL) { if(is_object($screen) && !empty($screen->id) && $screen->id === 'users') { //260822.1509 Preserve the normal Users table's compact default while leaving both EOT history columns available through Screen Options. $hidden[] = 's2member_last_auto_eot_time'; $hidden[] = 's2member_last_auto_eot_processed_time'; $hidden = array_values(array_unique($hidden)); } return $hidden; } /** * 260828.0231 Exposes the required End-of-Term columns in s2Member's Users review views. * * @package s2Member\Users_List * @since 260822.1519 * * @attaches-to ``add_filter("hidden_columns");`` * * @param array $hidden Hidden column IDs. * @param WP_Screen $screen Current screen object. * * @return array Filtered hidden column IDs. */ public static function users_list_hidden_cols($hidden = array(), $screen = NULL) { if(is_object($screen) && !empty($screen->id) && $screen->id === 'users') { $is_eot_current_view = !empty($_GET['s2member_view']) && $_GET['s2member_view'] === 'eot_current'; $is_eot_previous_view = !empty($_GET['s2member_view']) && $_GET['s2member_view'] === 'eot_previous'; $is_pending_deletion_view = !empty($_GET['role']) && $_GET['role'] === 's2member_pending_deletion'; //260828.0231 Each review view forces only its primary timestamps visible; other Users columns continue to follow the administrator's Screen Options. if($is_eot_current_view) $hidden = array_diff($hidden, array('s2member_auto_eot_time')); else if($is_eot_previous_view) $hidden = array_diff($hidden, array('s2member_last_auto_eot_time', 's2member_last_auto_eot_processed_time')); else if($is_pending_deletion_view) $hidden = array_diff($hidden, array('s2member_auto_eot_time', 's2member_last_auto_eot_time', 's2member_last_auto_eot_processed_time')); $hidden = array_values($hidden); } return $hidden; } /** * 260828.0231 Adds the Current and Previous End-of-Term reports to WordPress' native Users views. * * @package s2Member\Users_List * @since 260822.1519 * * @attaches-to ``add_filter("views_users");`` * * @param array $views Native Users view links. * * @return array Filtered Users view links. */ public static function users_list_views($views = array()) { global $wpdb; if(!is_admin() || is_network_admin() || empty($GLOBALS['pagenow']) || $GLOBALS['pagenow'] !== 'users.php') return $views; $is_eot_current_view = !empty($_GET['s2member_view']) && $_GET['s2member_view'] === 'eot_current'; $is_eot_previous_view = !empty($_GET['s2member_view']) && $_GET['s2member_view'] === 'eot_previous'; $is_pending_deletion_view = !empty($_GET['role']) && $_GET['role'] === 's2member_pending_deletion'; $current_eot_key = $wpdb->prefix.'s2member_auto_eot_time'; $last_eot_key = $wpdb->prefix.'s2member_last_auto_eot_time'; $processed_eot_key = $wpdb->prefix.'s2member_last_auto_eot_processed_time'; $has_current_eot_users = $is_eot_current_view || (bool)$wpdb->get_var($wpdb->prepare("SELECT 1 FROM `".$wpdb->usermeta."` WHERE `meta_key` = %s AND CAST(`meta_value` AS UNSIGNED) > 0 LIMIT 1", $current_eot_key)); $has_previous_eot_users = $is_eot_previous_view || (bool)$wpdb->get_var($wpdb->prepare("SELECT 1 FROM `".$wpdb->usermeta."` WHERE `meta_key` IN (%s, %s) AND CAST(`meta_value` AS UNSIGNED) > 0 LIMIT 1", $last_eot_key, $processed_eot_key)); if(($is_eot_current_view || $is_eot_previous_view) && isset($views['all'])) $views['all'] = str_replace(' class="current" aria-current="page"', '', $views['all']); //260828.0231 Keep each EOT report out of unused Users screens, while direct report URLs remain self-identifying even when the selected report is empty. if($has_current_eot_users) { $url = add_query_arg('s2member_view', 'eot_current', admin_url('users.php')); $views['s2member_eot_current'] = 'End-of-Term Current'; } if($has_previous_eot_users) { $url = add_query_arg('s2member_view', 'eot_previous', admin_url('users.php')); $views['s2member_eot_previous'] = 'End-of-Term Previous'; } $pending_deletion_view = isset($views['s2member_pending_deletion']) ? $views['s2member_pending_deletion'] : ''; unset($views['s2member_pending_deletion']); $delete_behavior = isset($GLOBALS['WS_PLUGIN__']['s2member']['o']['membership_eot_behavior']) && $GLOBALS['WS_PLUGIN__']['s2member']['o']['membership_eot_behavior'] === 'delete'; //260828.0337 WordPress's native role view already proves pending users exist; preserve it before any fallback query can inherit an active s2Member EOT report filter. $has_pending_deletion_users = $is_pending_deletion_view || (bool)$pending_deletion_view || (!$delete_behavior && (bool)get_users(array('role' => 's2member_pending_deletion', 'number' => 1, 'fields' => 'ID'))); //260823.0130 Pending Deletion is useful navigation while "Delete" is configured or review work exists; hide the otherwise-empty role link without unregistering the role or affecting direct access. if($delete_behavior || $has_pending_deletion_users) { if(!$pending_deletion_view) { $url = add_query_arg('role', 's2member_pending_deletion', admin_url('users.php')); $pending_deletion_view = 'Pending Deletion (0)'; } $views['s2member_pending_deletion'] = $pending_deletion_view; } return $views; } /** * 260828.0231 Explains the native Current and Previous End-of-Term Users views. * * @package s2Member\Users_List * @since 260823.0302 * * @attaches-to ``add_action("admin_notices");`` * * @return null */ public static function users_list_eot_notice() { if(!is_admin() || is_network_admin() || empty($GLOBALS['pagenow']) || $GLOBALS['pagenow'] !== 'users.php' || empty($_GET['s2member_view'])) return; if($_GET['s2member_view'] === 'eot_current') { //260828.0358 Describe this as a user-facing list rather than WordPress's internal "view" terminology; oldest/overdue current EOTs remain first. c_ws_plugin__s2member_admin_notices::display_admin_notice('This list includes users with a current End-of-Term time, ordered by the earliest EOT Time first.'); } else if($_GET['s2member_view'] === 'eot_previous') { //260828.0358 Previous EOTs are processing-history records, so explain both the historical scope and the EOT Demotion ordering directly. c_ws_plugin__s2member_admin_notices::display_admin_notice('This list includes users who had an End-of-Term time before, ordered by the most recent EOT Demotion first.'); } } /** * Explains the native Pending Deletion role view. * * @package s2Member\Users_List * @since 260822.1519 * * @attaches-to ``add_action("admin_notices");`` * * @return null */ public static function users_list_pending_deletion_notice() { if(!is_admin() || is_network_admin() || empty($GLOBALS['pagenow']) || $GLOBALS['pagenow'] !== 'users.php' || empty($_GET['role']) || $_GET['role'] !== 's2member_pending_deletion') return; //260828.0358 Describe Pending Deletion as a list rather than a WordPress "view," while explaining why these accounts remain available for administrator review. c_ws_plugin__s2member_admin_notices::display_admin_notice('This list includes accounts preserved in Pending Deletion after their membership access was removed at End-of-Term. Use WordPress\'s normal bulk Delete action when you are ready to delete them.'); } /** * Displays column data in the row of details. * * @package s2Member\Users_List * @since 3.5 * * @attaches-to ``add_filter ("manage_users_custom_column");`` * * @param string $val A value for this column, passed through by the Filter. * @param string $col The name of the column for which we might need to supply data for. * @param int|string $user_id Expects a WordPress User ID, passed through by the Filter. * * @return string A column value introduced by this routine, or existing value, or, if empty, a dash. */ public static function users_list_display_cols($val = '', $col = '', $user_id = '') { static $user, $last_user_id; // Used internally for optimization. static $fields, $last_fields_id; // Used for optimization. foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v; do_action("ws_plugin__s2member_before_users_list_display_cols", get_defined_vars()); unset($__refs, $__v); $user = (is_object($user) && $user_id === $last_user_id) ? $user : new WP_User ($user_id); if($col === "s2member_registration_time") $val = (($time = strtotime(get_date_from_gmt($user->user_registered)))) ? esc_html(date("D M jS, Y", $time)).'
@ precisely '.esc_html(date("g:i a", $time)).'' : "—"; else if($col === "s2member_paid_registration_times") { $val = ""; // Initialize $val before we begin. if(is_array($v = get_user_option("s2member_paid_registration_times", $user_id))) foreach($v as $level => $time) // Go through each Paid Registration Time. { $time = strtotime(get_date_from_gmt(date("Y-m-d H:i:s", $time))); if($level === "level") // First Payment Time, regardless of Level. $val .= (($val) ? "
" : "").''.esc_html(date("D M jS, Y", $time)).''; else if(preg_match("/^level([0-9]+)$/i", $level) && ($level = preg_replace("/^level/", "", $level))) $val .= (($val) ? "
" : "").'@Level '.esc_html($level).': '.esc_html(date("D M jS, Y", $time)).''; } } else if($col === "s2member_subscr_id") $val = ($v = get_user_option("s2member_subscr_id", $user_id)) ? esc_html($v) : "—"; else if($col === "s2member_ccaps") // Custom Capabilities. { foreach($user->allcaps as $cap => $cap_enabled) if(preg_match("/^access_s2member_ccap_/", $cap)) $ccaps[] = preg_replace("/^access_s2member_ccap_/", "", $cap); $val = (!empty($ccaps)) ? implode("
", $ccaps) : "—"; } else if($col === "s2member_auto_eot_time") $val = ($v = get_user_option("s2member_auto_eot_time", $user_id)) ? date("D M jS, Y", (int)$v)."
@ precisely ".date("g:i a", (int)$v)."" : "—"; else if($col === "s2member_last_auto_eot_time" || $col === "s2member_last_auto_eot_processed_time") { //260822.1509 Match the existing EOT Time presentation so current, triggering, and processed timestamps compare directly in the same Users table. $val = ($v = get_user_option($col, $user_id)) ? date("D M jS, Y", (int)$v)."
@ precisely ".date("g:i a", (int)$v)."" : "—"; } else if(preg_match("/^s2member_custom_field_/", $col)) { if(!$last_fields_id || $last_fields_id !== $user_id) $fields = get_user_option("s2member_custom_fields", $user_id); $field_var = preg_replace("/^s2member_custom_field_/", "", $col); if(isset ($fields[$field_var]) && is_string($fields[$field_var]) && preg_match("/^http(s?)\:/i", $fields[$field_var])) $val = ''.esc_html(substr($fields[$field_var], strpos($fields[$field_var], ":") + 3, 25)."...").''; else if(isset ($fields[$field_var]) && is_array($fields[$field_var]) && !empty($fields[$field_var])) $val = preg_replace("/-\|br\|-/", "
", esc_html(implode("-|br|-", $fields[$field_var]))); else if(isset ($fields[$field_var]) && is_string($fields[$field_var]) && strlen($fields[$field_var])) $val = esc_html($fields[$field_var]); $last_fields_id = $user_id; // Record this. } else if($col === "s2member_login_counter") $val = ($v = get_user_option("s2member_login_counter", $user_id)) ? esc_html($v) : "—"; else if($col === "s2member_last_login_time") { if(($time = get_user_option("s2member_last_login_time", $user_id))) { $time = strtotime(get_date_from_gmt(date("Y-m-d H:i:s", $time))); $val = esc_html(date("D M jS, Y", $time)).'
@ precisely '.esc_html(date("g:i a", $time)).''; } else $val = "—"; // Not applicable (we've never recorded them logging into the site). } $last_user_id = $user_id; // Record this for internal optimizations. return apply_filters("ws_plugin__s2member_users_list_display_cols", ((strlen((string) $val)) ? $val : "—"), get_defined_vars()); } /** * Tells WordPress certain fields s2Member adds are sortable * * @package s2Member\Users_List * @since 140518 * * @attaches-to ``add_filter ("manage_users_sortable_columns");`` * * @param array $columns An array of sortable user list columns. * * @return array The input array after having been filtered here. */ public static function users_list_add_sortable($columns = array()) { if(!empty($_REQUEST['s'])) return $columns; $is_eot_current_view = !empty($_GET['s2member_view']) && $_GET['s2member_view'] === 'eot_current'; $is_eot_previous_view = !empty($_GET['s2member_view']) && $_GET['s2member_view'] === 'eot_previous'; $is_pending_deletion_view = !empty($_GET['role']) && $_GET['role'] === 's2member_pending_deletion'; if($is_eot_current_view || $is_eot_previous_view || $is_pending_deletion_view) { //260828.0317 WordPress normally marks Username as initially sorted; each EOT review view instead exposes its own default timestamp and direction in the native header state. foreach($columns as $_column_key => $_column_data) if(is_array($_column_data) && isset($_column_data[4])) $columns[$_column_key][4] = false; } $columns['s2member_registration_time'] = 's2member_registration_time'; $columns['s2member_subscr_id'] = 's2member_subscr_id'; $columns['s2member_auto_eot_time'] = $is_eot_current_view ? array('s2member_auto_eot_time', false, 'EOT Time', 'Table ordered by current End-of-Term time.', 'asc') : 's2member_auto_eot_time'; $columns['s2member_last_auto_eot_time'] = 's2member_last_auto_eot_time'; $columns['s2member_last_auto_eot_processed_time'] = $is_eot_previous_view ? array('s2member_last_auto_eot_processed_time', false, 'EOT Demotion', 'Table ordered by End-of-Term demotion time.', 'desc') : ($is_pending_deletion_view ? array('s2member_last_auto_eot_processed_time', false, 'EOT Demotion', 'Table ordered by End-of-Term demotion time.', 'asc') : 's2member_last_auto_eot_processed_time'); $columns['s2member_login_counter'] = 's2member_login_counter'; $columns['s2member_last_login_time'] = 's2member_last_login_time'; return $columns; } /** * Alters WP_Query object to make custom columns sortable * * @package s2Member\Users_List * @since 140518 * * @attaches-to ``add_filter ("pre_user_query");`` * * @param WP_User_Query $query `WP_Query` Object passed from WordPress */ public static function users_list_make_sortable($query) { if(!is_admin() || empty($GLOBALS['pagenow']) || $GLOBALS['pagenow'] !== 'users.php' || !isset ($query->query_vars) || !empty($_REQUEST['s']) ) return; global $wpdb; /** @var $wpdb wpdb */ $vars = $query->query_vars; switch($vars['orderby']) { // This isn't a usermeta value, so we don't need to `LEFT JOIN` here. case 's2member_registration_time': $query->query_orderby = "ORDER BY CAST(`user_registered` AS UNSIGNED) ".$vars['order']; break; // s2Member Subscription ID can contain non-integer characters. We don't `CAST` this value as `UNSIGNED` case 's2member_subscr_id': $query->query_from .= " LEFT JOIN `".$wpdb->usermeta."` `___m` ON (`".$wpdb->users."`.`ID` = `___m`.`user_id` AND `___m`.`meta_key` = '".esc_sql($wpdb->prefix.$vars['orderby'])."')"; $query->query_orderby = "ORDER BY `___m`.`meta_value` ".$vars['order']; break; case 's2member_auto_eot_time': case 's2member_last_auto_eot_time': case 's2member_last_auto_eot_processed_time': case 's2member_login_counter': case 's2member_last_login_time': $query->query_from .= " LEFT JOIN `".$wpdb->usermeta."` `___m` ON (`".$wpdb->users."`.`ID` = `___m`.`user_id` AND `___m`.`meta_key` = '".esc_sql($wpdb->prefix.$vars['orderby'])."')"; $query->query_orderby = "ORDER BY CAST(`___m`.`meta_value` AS UNSIGNED) ".$vars['order']; break; } } } }