PluginProbe
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions / trunk
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions vtrunk
260909 260829 260814 260805 110710 110731 110812 110815 110912 110913 110915 110926 110927 111002 111003 111011 111017 111029 111105 111206 111216 111220 120213 120219 120301 All 187 releases
s2member / src / includes / classes / users-list.inc.php

users-list.inc.php in s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions trunk, at src/includes/classes/users-list.inc.php

594 lines 29.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // @codingStandardsIgnoreFile
3 /**
4 * Users list.
5 *
6 * Copyright: © 2009-2011
7 * {@link http://websharks-inc.com/ WebSharks, Inc.}
8 * (coded in the USA)
9 *
10 * Released under the terms of the GNU General Public License.
11 * You should have received a copy of the GNU General Public License,
12 * along with this software. In the main directory, see: /licensing/
13 * If not, see: {@link http://www.gnu.org/licenses/}.
14 *
15 * @package s2Member\Users_List
16 * @since 3.5
17 */
18 if(!defined('WPINC')) // MUST have WordPress.
19 exit ("Do not access this file directly.");
20
21 if(!class_exists("c_ws_plugin__s2member_users_list"))
22 {
23 /**
24 * Users list.
25 *
26 * @package s2Member\Users_List
27 * @since 3.5
28 */
29 class c_ws_plugin__s2member_users_list
30 {
31 /**
32 * Adds Custom Fields to the admin Profile editing page.
33 *
34 * @package s2Member\Users_List
35 * @since 3.5
36 *
37 * @attaches-to ``add_action("edit_user_profile");``
38 * @attaches-to ``add_action("show_user_profile");``
39 *
40 * @param WP_User $user Expects a `WP_User` object passed in by the Action Hook.
41 */
42 public static function users_list_edit_cols($user = NULL)
43 {
44 c_ws_plugin__s2member_users_list_in::users_list_edit_cols($user);
45 }
46
47 /**
48 * Saves Custom Fields after an admin updates Profile.
49 *
50 * @package s2Member\Users_List
51 * @since 3.5
52 *
53 * @attaches-to ``add_action("edit_user_profile_update");``
54 * @attaches-to ``add_action("personal_options_update");``
55 *
56 * @param int|string $user_id Expects a numeric WordPress User ID passed in by the Action Hook.
57 */
58 public static function users_list_update_cols($user_id = NULL)
59 {
60 return c_ws_plugin__s2member_users_list_in::users_list_update_cols($user_id);
61 }
62
63 /**
64 * Modifies the search query.
65 *
66 * Affects searches performed in the list of Users.
67 *
68 * @package s2Member\Users_List
69 * @since 3.5
70 *
71 * @attaches-to ``add_action("pre_user_query");``
72 *
73 * @param WP_User_Query $query Expects a `WP_User_Query` object, by reference.
74 */
75 public static function users_list_query(&$query = NULL)
76 {
77 global $wpdb;
78 /** @var $wpdb wpdb */
79
80 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
81 do_action("ws_plugin__s2member_before_users_list_search", get_defined_vars());
82 unset($__refs, $__v);
83
84 if(is_admin() && !empty($GLOBALS['pagenow']) && $GLOBALS['pagenow'] === 'users.php')
85 if(isset ($query->query_vars) && !is_network_admin()) // NOT in Network admin panels.
86 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))."%"))
87 {
88 $query->query_fields = "SQL_CALC_FOUND_ROWS DISTINCT(`".$wpdb->users."`.`ID`)";
89 $query->query_from = " FROM `".$wpdb->users."`, `".$wpdb->usermeta."`"; // Include meta table also.
90 $query->query_where = " WHERE `".$wpdb->users."`.`ID` = `".$wpdb->usermeta."`.`user_id`"; // Join w/ meta table.
91 $query->query_where .= " AND (".apply_filters("ws_plugin__s2member_before_users_list_search_where_or_before", "", get_defined_vars());
92 $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."')";
93 $query->query_where .= " OR ((`".$wpdb->usermeta."`.`meta_key` = 'first_name' OR `".$wpdb->usermeta."`.`meta_key` = 'last_name') AND `".$wpdb->usermeta."`.`meta_value` LIKE '".$s."')";
94 $query->query_where .= " OR (`".$wpdb->usermeta."`.`meta_key` = '".$wpdb->base_prefix."s2member_subscr_id' AND `".$wpdb->usermeta."`.`meta_value` LIKE '".$s."')";
95 $query->query_where .= " OR (`".$wpdb->usermeta."`.`meta_key` = '".$wpdb->base_prefix."s2member_custom' AND `".$wpdb->usermeta."`.`meta_value` LIKE '".$s."')";
96 $query->query_where .= " OR (`".$wpdb->usermeta."`.`meta_key` = '".$wpdb->base_prefix."s2member_custom_fields' AND `".$wpdb->usermeta."`.`meta_value` LIKE '".$s."')";
97 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.
98 $query->query_where .= " OR (`".$wpdb->usermeta."`.`meta_key` = '".$wpdb->base_prefix."s2member_notes' AND `".$wpdb->usermeta."`.`meta_value` LIKE '".$s."')";
99 $query->query_where .= apply_filters("ws_plugin__s2member_before_users_list_search_where_or_after", "", get_defined_vars()).")"; // Leaving room for additional searches here.
100
101 if(is_multisite()) // On a Multisite Network we need to make sure we're searching only users w/ capabilities on this blog.
102 $query->query_where .= " AND `".$wpdb->users."`.`ID` IN(SELECT DISTINCT(`user_id`) FROM `".$wpdb->usermeta."` WHERE `meta_key` = '".$wpdb->prefix."capabilities')";
103
104 $query->query_from = apply_filters("ws_plugin__s2member_before_users_list_search_from", $query->query_from, get_defined_vars());
105 $query->query_where = apply_filters("ws_plugin__s2member_before_users_list_search_where", $query->query_where, get_defined_vars());
106 }
107
108 if(is_admin() && !is_network_admin() && !empty($GLOBALS['pagenow']) && $GLOBALS['pagenow'] === 'users.php' && !empty($_GET['s2member_view']) && isset($query->query_vars))
109 {
110 $is_eot_current_view = $_GET['s2member_view'] === 'eot_current';
111 $is_eot_previous_view = $_GET['s2member_view'] === 'eot_previous';
112
113 if($is_eot_current_view || $is_eot_previous_view)
114 {
115 $current_eot_key = esc_sql($wpdb->prefix.'s2member_auto_eot_time');
116 $last_eot_key = esc_sql($wpdb->prefix.'s2member_last_auto_eot_time');
117 $processed_eot_key = esc_sql($wpdb->prefix.'s2member_last_auto_eot_processed_time');
118
119 if($is_eot_current_view)
120 {
121 //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.
122 $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)";
123
124 if(empty($_REQUEST['orderby']) && empty($_REQUEST['s']))
125 {
126 $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."')";
127 $query->query_orderby = "ORDER BY CAST(`___s2_eot_order_current`.`meta_value` AS UNSIGNED) ASC";
128 }
129 }
130 else
131 {
132 //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.
133 $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))";
134
135 if(empty($_REQUEST['orderby']) && empty($_REQUEST['s']))
136 {
137 //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.
138 $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."')";
139 $query->query_orderby = "ORDER BY CAST(`___s2_eot_order_processed`.`meta_value` AS UNSIGNED) DESC";
140 }
141 }
142 }
143 }
144
145 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']))
146 {
147 //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.
148 $processed_eot_key = esc_sql($wpdb->prefix.'s2member_last_auto_eot_processed_time');
149 $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."')";
150 $query->query_orderby = "ORDER BY CAST(`___s2_pending_order_processed`.`meta_value` AS UNSIGNED) ASC";
151 }
152 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
153 do_action("ws_plugin__s2member_after_users_list_search", get_defined_vars());
154 unset($__refs, $__v);
155 }
156
157 /**
158 * Adds columns to the list of Users.
159 *
160 * @package s2Member\Users_List
161 * @since 3.5
162 *
163 * @attaches-to ``add_filter ("manage_users_columns");``
164 *
165 * @param array $cols Expects an array of columns to be passed through by the Filter.
166 *
167 * @return array Array of columns, merged with columns introduced by this routine.
168 */
169 public static function users_list_cols($cols = array())
170 {
171 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
172 do_action("ws_plugin__s2member_before_users_list_cols", get_defined_vars());
173 unset($__refs, $__v);
174
175 $cols["s2member_registration_time"] = "Registration Date";
176
177 if(apply_filters("ws_plugin__s2member_users_list_cols_display_paid_registration_times", FALSE))
178 $cols["s2member_paid_registration_times"] = "Paid Registr. Date";
179
180 $cols["s2member_subscr_id"] = "Paid Subscr. ID";
181
182 if(!is_multisite() || !c_ws_plugin__s2member_utils_conds::is_multisite_farm() || is_main_site())
183 $cols["s2member_ccaps"] = "Custom Capabilities";
184
185 $cols["s2member_auto_eot_time"] = "EOT Time";
186 //260822.1509 Keep the triggering EOT and its actual processing time separate; both remain optional history columns on the normal Users screen.
187 $cols["s2member_last_auto_eot_time"] = "Last EOT";
188 $cols["s2member_last_auto_eot_processed_time"] = "EOT Demotion";
189
190 if($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["custom_reg_fields"])
191 foreach(json_decode($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["custom_reg_fields"], TRUE) as $field)
192 {
193 $field_var = preg_replace("/[^a-z0-9]/i", "_", strtolower($field["id"]));
194 $field_id_class = preg_replace("/_/", "-", $field_var);
195
196 $field_title = ucwords(preg_replace("/_/", " ", $field_var));
197 $cols["s2member_custom_field_".$field_var] = $field_title;
198 }
199
200 $cols["s2member_login_counter"] = "# Of Logins";
201 $cols["s2member_last_login_time"] = "Last Login Time";
202
203 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
204 do_action("ws_plugin__s2member_during_users_list_cols", get_defined_vars());
205 unset($__refs, $__v);
206
207 return apply_filters("ws_plugin__s2member_users_list_cols", $cols, get_defined_vars());
208 }
209
210 /**
211 * Re-adds required EOT columns late on s2Member's review views after column-management plugins have filtered the Users table.
212 *
213 * @package s2Member\Users_List
214 * @since 260826.0542
215 *
216 * @attaches-to ``add_filter("manage_users_columns");``
217 *
218 * @param array $cols User list columns after other plugins have filtered them.
219 *
220 * @return array User list columns with the required EOT review columns restored.
221 */
222 public static function users_list_required_eot_cols($cols = array())
223 {
224 if(!is_admin() || empty($GLOBALS['pagenow']) || $GLOBALS['pagenow'] !== 'users.php')
225 return $cols;
226
227 $is_eot_current_view = !empty($_GET['s2member_view']) && $_GET['s2member_view'] === 'eot_current';
228 $is_eot_previous_view = !empty($_GET['s2member_view']) && $_GET['s2member_view'] === 'eot_previous';
229 $is_pending_deletion_view = !empty($_GET['role']) && $_GET['role'] === 's2member_pending_deletion';
230
231 //260828.0231 Restore only the columns required by each s2Member review view after Users-table customization plugins apply saved layouts.
232 if($is_eot_current_view || $is_pending_deletion_view)
233 $cols['s2member_auto_eot_time'] = 'EOT Time';
234
235 if($is_eot_previous_view || $is_pending_deletion_view)
236 {
237 $cols['s2member_last_auto_eot_time'] = 'Last EOT';
238 $cols['s2member_last_auto_eot_processed_time'] = 'EOT Demotion';
239 }
240 return $cols;
241 }
242
243 /**
244 * Hides optional s2Member history columns by default on the Users screen.
245 *
246 * @package s2Member\Users_List
247 * @since 260822.1509
248 *
249 * @attaches-to ``add_filter("default_hidden_columns");``
250 *
251 * @param array $hidden Default hidden column IDs.
252 * @param WP_Screen $screen Current screen object.
253 *
254 * @return array Filtered hidden column IDs.
255 */
256 public static function users_list_default_hidden_cols($hidden = array(), $screen = NULL)
257 {
258 if(is_object($screen) && !empty($screen->id) && $screen->id === 'users')
259 {
260 //260822.1509 Preserve the normal Users table's compact default while leaving both EOT history columns available through Screen Options.
261 $hidden[] = 's2member_last_auto_eot_time';
262 $hidden[] = 's2member_last_auto_eot_processed_time';
263 $hidden = array_values(array_unique($hidden));
264 }
265 return $hidden;
266 }
267
268 /**
269 * 260828.0231 Exposes the required End-of-Term columns in s2Member's Users review views.
270 *
271 * @package s2Member\Users_List
272 * @since 260822.1519
273 *
274 * @attaches-to ``add_filter("hidden_columns");``
275 *
276 * @param array $hidden Hidden column IDs.
277 * @param WP_Screen $screen Current screen object.
278 *
279 * @return array Filtered hidden column IDs.
280 */
281 public static function users_list_hidden_cols($hidden = array(), $screen = NULL)
282 {
283 if(is_object($screen) && !empty($screen->id) && $screen->id === 'users')
284 {
285 $is_eot_current_view = !empty($_GET['s2member_view']) && $_GET['s2member_view'] === 'eot_current';
286 $is_eot_previous_view = !empty($_GET['s2member_view']) && $_GET['s2member_view'] === 'eot_previous';
287 $is_pending_deletion_view = !empty($_GET['role']) && $_GET['role'] === 's2member_pending_deletion';
288
289 //260828.0231 Each review view forces only its primary timestamps visible; other Users columns continue to follow the administrator's Screen Options.
290 if($is_eot_current_view)
291 $hidden = array_diff($hidden, array('s2member_auto_eot_time'));
292 else if($is_eot_previous_view)
293 $hidden = array_diff($hidden, array('s2member_last_auto_eot_time', 's2member_last_auto_eot_processed_time'));
294 else if($is_pending_deletion_view)
295 $hidden = array_diff($hidden, array('s2member_auto_eot_time', 's2member_last_auto_eot_time', 's2member_last_auto_eot_processed_time'));
296
297 $hidden = array_values($hidden);
298 }
299 return $hidden;
300 }
301
302 /**
303 * 260828.0231 Adds the Current and Previous End-of-Term reports to WordPress' native Users views.
304 *
305 * @package s2Member\Users_List
306 * @since 260822.1519
307 *
308 * @attaches-to ``add_filter("views_users");``
309 *
310 * @param array $views Native Users view links.
311 *
312 * @return array Filtered Users view links.
313 */
314 public static function users_list_views($views = array())
315 {
316 global $wpdb;
317
318 if(!is_admin() || is_network_admin() || empty($GLOBALS['pagenow']) || $GLOBALS['pagenow'] !== 'users.php')
319 return $views;
320
321 $is_eot_current_view = !empty($_GET['s2member_view']) && $_GET['s2member_view'] === 'eot_current';
322 $is_eot_previous_view = !empty($_GET['s2member_view']) && $_GET['s2member_view'] === 'eot_previous';
323 $is_pending_deletion_view = !empty($_GET['role']) && $_GET['role'] === 's2member_pending_deletion';
324 $current_eot_key = $wpdb->prefix.'s2member_auto_eot_time';
325 $last_eot_key = $wpdb->prefix.'s2member_last_auto_eot_time';
326 $processed_eot_key = $wpdb->prefix.'s2member_last_auto_eot_processed_time';
327 $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));
328 $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));
329
330 if(($is_eot_current_view || $is_eot_previous_view) && isset($views['all']))
331 $views['all'] = str_replace(' class="current" aria-current="page"', '', $views['all']);
332
333 //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.
334 if($has_current_eot_users)
335 {
336 $url = add_query_arg('s2member_view', 'eot_current', admin_url('users.php'));
337 $views['s2member_eot_current'] = '<a href="'.esc_url($url).'"'.($is_eot_current_view ? ' class="current" aria-current="page"' : '').'>End-of-Term Current</a>';
338 }
339
340 if($has_previous_eot_users)
341 {
342 $url = add_query_arg('s2member_view', 'eot_previous', admin_url('users.php'));
343 $views['s2member_eot_previous'] = '<a href="'.esc_url($url).'"'.($is_eot_previous_view ? ' class="current" aria-current="page"' : '').'>End-of-Term Previous</a>';
344 }
345
346 $pending_deletion_view = isset($views['s2member_pending_deletion']) ? $views['s2member_pending_deletion'] : '';
347 unset($views['s2member_pending_deletion']);
348 $delete_behavior = isset($GLOBALS['WS_PLUGIN__']['s2member']['o']['membership_eot_behavior']) && $GLOBALS['WS_PLUGIN__']['s2member']['o']['membership_eot_behavior'] === 'delete';
349 //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.
350 $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')));
351
352 //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.
353 if($delete_behavior || $has_pending_deletion_users)
354 {
355 if(!$pending_deletion_view)
356 {
357 $url = add_query_arg('role', 's2member_pending_deletion', admin_url('users.php'));
358 $pending_deletion_view = '<a href="'.esc_url($url).'"'.($is_pending_deletion_view ? ' class="current" aria-current="page"' : '').'>Pending Deletion <span class="count">(0)</span></a>';
359 }
360 $views['s2member_pending_deletion'] = $pending_deletion_view;
361 }
362
363 return $views;
364 }
365
366 /**
367 * 260828.0231 Explains the native Current and Previous End-of-Term Users views.
368 *
369 * @package s2Member\Users_List
370 * @since 260823.0302
371 *
372 * @attaches-to ``add_action("admin_notices");``
373 *
374 * @return null
375 */
376 public static function users_list_eot_notice()
377 {
378 if(!is_admin() || is_network_admin() || empty($GLOBALS['pagenow']) || $GLOBALS['pagenow'] !== 'users.php' || empty($_GET['s2member_view']))
379 return;
380
381 if($_GET['s2member_view'] === 'eot_current')
382 {
383 //260828.0358 Describe this as a user-facing list rather than WordPress's internal "view" terminology; oldest/overdue current EOTs remain first.
384 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.');
385 }
386 else if($_GET['s2member_view'] === 'eot_previous')
387 {
388 //260828.0358 Previous EOTs are processing-history records, so explain both the historical scope and the EOT Demotion ordering directly.
389 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.');
390 }
391 }
392
393 /**
394 * Explains the native Pending Deletion role view.
395 *
396 * @package s2Member\Users_List
397 * @since 260822.1519
398 *
399 * @attaches-to ``add_action("admin_notices");``
400 *
401 * @return null
402 */
403 public static function users_list_pending_deletion_notice()
404 {
405 if(!is_admin() || is_network_admin() || empty($GLOBALS['pagenow']) || $GLOBALS['pagenow'] !== 'users.php' || empty($_GET['role']) || $_GET['role'] !== 's2member_pending_deletion')
406 return;
407
408 //260828.0358 Describe Pending Deletion as a list rather than a WordPress "view," while explaining why these accounts remain available for administrator review.
409 c_ws_plugin__s2member_admin_notices::display_admin_notice('This list includes accounts preserved in <strong>Pending Deletion</strong> after their membership access was removed at End-of-Term. Use WordPress\'s normal bulk Delete action when you are ready to delete them.');
410 }
411
412 /**
413 * Displays column data in the row of details.
414 *
415 * @package s2Member\Users_List
416 * @since 3.5
417 *
418 * @attaches-to ``add_filter ("manage_users_custom_column");``
419 *
420 * @param string $val A value for this column, passed through by the Filter.
421 * @param string $col The name of the column for which we might need to supply data for.
422 * @param int|string $user_id Expects a WordPress User ID, passed through by the Filter.
423 *
424 * @return string A column value introduced by this routine, or existing value, or, if empty, a dash.
425 */
426 public static function users_list_display_cols($val = '', $col = '', $user_id = '')
427 {
428 static $user, $last_user_id; // Used internally for optimization.
429 static $fields, $last_fields_id; // Used for optimization.
430
431 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
432 do_action("ws_plugin__s2member_before_users_list_display_cols", get_defined_vars());
433 unset($__refs, $__v);
434
435 $user = (is_object($user) && $user_id === $last_user_id) ? $user : new WP_User ($user_id);
436
437 if($col === "s2member_registration_time")
438 $val = (($time = strtotime(get_date_from_gmt($user->user_registered)))) ? esc_html(date("D M jS, Y", $time)).'<br /><small>@ precisely '.esc_html(date("g:i a", $time)).'</small>' : "";
439
440 else if($col === "s2member_paid_registration_times")
441 {
442 $val = ""; // Initialize $val before we begin.
443 if(is_array($v = get_user_option("s2member_paid_registration_times", $user_id)))
444 foreach($v as $level => $time) // Go through each Paid Registration Time.
445 {
446 $time = strtotime(get_date_from_gmt(date("Y-m-d H:i:s", $time)));
447
448 if($level === "level") // First Payment Time, regardless of Level.
449 $val .= (($val) ? "<br />" : "").'<span title="'.esc_attr(date("D M jS, Y", $time)).' @ precisely '.esc_attr(date("g:i a", $time)).'">'.esc_html(date("D M jS, Y", $time)).'</span>';
450 else if(preg_match("/^level([0-9]+)$/i", $level) && ($level = preg_replace("/^level/", "", $level)))
451 $val .= (($val) ? "<br />" : "").'<small><em>@Level '.esc_html($level).': <span title="'.esc_attr(date("D M jS, Y", $time)).' @ precisely '.esc_attr(date("g:i a", $time)).'">'.esc_html(date("D M jS, Y", $time)).'</span></em></small>';
452 }
453 }
454 else if($col === "s2member_subscr_id")
455 $val = ($v = get_user_option("s2member_subscr_id", $user_id)) ? esc_html($v) : "";
456
457 else if($col === "s2member_ccaps") // Custom Capabilities.
458 {
459 foreach($user->allcaps as $cap => $cap_enabled)
460 if(preg_match("/^access_s2member_ccap_/", $cap))
461 $ccaps[] = preg_replace("/^access_s2member_ccap_/", "", $cap);
462
463 $val = (!empty($ccaps)) ? implode("<br />", $ccaps) : "";
464 }
465 else if($col === "s2member_auto_eot_time")
466 $val = ($v = get_user_option("s2member_auto_eot_time", $user_id)) ? date("D M jS, Y", (int)$v)."<br /><small>@ precisely ".date("g:i a", (int)$v)."</small>" : "";
467
468 else if($col === "s2member_last_auto_eot_time" || $col === "s2member_last_auto_eot_processed_time")
469 {
470 //260822.1509 Match the existing EOT Time presentation so current, triggering, and processed timestamps compare directly in the same Users table.
471 $val = ($v = get_user_option($col, $user_id)) ? date("D M jS, Y", (int)$v)."<br /><small>@ precisely ".date("g:i a", (int)$v)."</small>" : "";
472 }
473
474 else if(preg_match("/^s2member_custom_field_/", $col))
475 {
476 if(!$last_fields_id || $last_fields_id !== $user_id)
477 $fields = get_user_option("s2member_custom_fields", $user_id);
478
479 $field_var = preg_replace("/^s2member_custom_field_/", "", $col);
480
481 if(isset ($fields[$field_var]) && is_string($fields[$field_var]) && preg_match("/^http(s?)\:/i", $fields[$field_var]))
482 $val = '<a href="'.esc_attr($fields[$field_var]).'" target="_blank">'.esc_html(substr($fields[$field_var], strpos($fields[$field_var], ":") + 3, 25)."...").'</a>';
483
484 else if(isset ($fields[$field_var]) && is_array($fields[$field_var]) && !empty($fields[$field_var]))
485 $val = preg_replace("/-\|br\|-/", "<br />", esc_html(implode("-|br|-", $fields[$field_var])));
486
487 else if(isset ($fields[$field_var]) && is_string($fields[$field_var]) && strlen($fields[$field_var]))
488 $val = esc_html($fields[$field_var]);
489
490 $last_fields_id = $user_id; // Record this.
491 }
492 else if($col === "s2member_login_counter")
493 $val = ($v = get_user_option("s2member_login_counter", $user_id)) ? esc_html($v) : "";
494
495 else if($col === "s2member_last_login_time")
496 {
497 if(($time = get_user_option("s2member_last_login_time", $user_id)))
498 {
499 $time = strtotime(get_date_from_gmt(date("Y-m-d H:i:s", $time)));
500 $val = esc_html(date("D M jS, Y", $time)).'<br /><small>@ precisely '.esc_html(date("g:i a", $time)).'</small>';
501 }
502 else $val = ""; // Not applicable (we've never recorded them logging into the site).
503 }
504 $last_user_id = $user_id; // Record this for internal optimizations.
505
506 return apply_filters("ws_plugin__s2member_users_list_display_cols", ((strlen((string) $val)) ? $val : ""), get_defined_vars());
507 }
508
509 /**
510 * Tells WordPress certain fields s2Member adds are sortable
511 *
512 * @package s2Member\Users_List
513 * @since 140518
514 *
515 * @attaches-to ``add_filter ("manage_users_sortable_columns");``
516 *
517 * @param array $columns An array of sortable user list columns.
518 *
519 * @return array The input array after having been filtered here.
520 */
521 public static function users_list_add_sortable($columns = array())
522 {
523 if(!empty($_REQUEST['s']))
524 return $columns;
525
526 $is_eot_current_view = !empty($_GET['s2member_view']) && $_GET['s2member_view'] === 'eot_current';
527 $is_eot_previous_view = !empty($_GET['s2member_view']) && $_GET['s2member_view'] === 'eot_previous';
528 $is_pending_deletion_view = !empty($_GET['role']) && $_GET['role'] === 's2member_pending_deletion';
529 if($is_eot_current_view || $is_eot_previous_view || $is_pending_deletion_view)
530 {
531 //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.
532 foreach($columns as $_column_key => $_column_data)
533 if(is_array($_column_data) && isset($_column_data[4]))
534 $columns[$_column_key][4] = false;
535 }
536
537 $columns['s2member_registration_time'] = 's2member_registration_time';
538 $columns['s2member_subscr_id'] = 's2member_subscr_id';
539 $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';
540 $columns['s2member_last_auto_eot_time'] = 's2member_last_auto_eot_time';
541 $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');
542 $columns['s2member_login_counter'] = 's2member_login_counter';
543 $columns['s2member_last_login_time'] = 's2member_last_login_time';
544
545 return $columns;
546 }
547
548 /**
549 * Alters WP_Query object to make custom columns sortable
550 *
551 * @package s2Member\Users_List
552 * @since 140518
553 *
554 * @attaches-to ``add_filter ("pre_user_query");``
555 *
556 * @param WP_User_Query $query `WP_Query` Object passed from WordPress
557 */
558 public static function users_list_make_sortable($query)
559 {
560 if(!is_admin()
561 || empty($GLOBALS['pagenow']) || $GLOBALS['pagenow'] !== 'users.php'
562 || !isset ($query->query_vars) || !empty($_REQUEST['s'])
563 ) return;
564
565 global $wpdb;
566 /** @var $wpdb wpdb */
567 $vars = $query->query_vars;
568
569 switch($vars['orderby'])
570 {
571 // This isn't a usermeta value, so we don't need to `LEFT JOIN` here.
572 case 's2member_registration_time':
573 $query->query_orderby = "ORDER BY CAST(`user_registered` AS UNSIGNED) ".$vars['order'];
574 break;
575
576 // s2Member Subscription ID can contain non-integer characters. We don't `CAST` this value as `UNSIGNED`
577 case 's2member_subscr_id':
578 $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'])."')";
579 $query->query_orderby = "ORDER BY `___m`.`meta_value` ".$vars['order'];
580 break;
581
582 case 's2member_auto_eot_time':
583 case 's2member_last_auto_eot_time':
584 case 's2member_last_auto_eot_processed_time':
585 case 's2member_login_counter':
586 case 's2member_last_login_time':
587 $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'])."')";
588 $query->query_orderby = "ORDER BY CAST(`___m`.`meta_value` AS UNSIGNED) ".$vars['order'];
589 break;
590 }
591 }
592 }
593 }
594