PluginProbe
wpForo Forum / 3.0.9
wpForo Forum v3.0.9
3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 1.4.2 All 137 releases
wpforo / admin / listtables / Members.php

Members.php in wpForo Forum 3.0.9, at admin/listtables/Members.php

459 lines 22.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace wpforo\admin\listtables;
4
5 use WP_List_Table;
6
7 require_once( ABSPATH . 'wp-admin/includes/template.php' );
8 require_once( ABSPATH . 'wp-admin/includes/screen.php' );
9 require_once( ABSPATH . 'wp-admin/includes/class-wp-screen.php' );
10 require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
11
12 class Members extends WP_List_Table {
13
14 public $wpfitems_count;
15
16 /** ************************************************************************
17 * REQUIRED. Set up a constructor that references the parent constructor. We
18 * use the parent reference to set some default configs.
19 ***************************************************************************/
20 function __construct() {
21 //Set parent defaults
22 parent::__construct( [
23 'singular' => 'member', //singular name of the listed records
24 'plural' => 'members', //plural name of the listed records
25 'ajax' => false, //does this table support ajax?
26 'screen' => 'wpForoMembers',
27 ] );
28
29 }
30
31
32 /** ************************************************************************
33 * Recommended. This method is called when the parent class can't find a method
34 * specifically build for a given column. Generally, it's recommended to include
35 * one method for each column you want to render, keeping your package class
36 * neat and organized. For example, if the class needs to process a column
37 * named 'title', it would first see if a method named $this->column_title()
38 * exists - if it does, that method will be used. If it doesn't, this one will
39 * be used. Generally, you should try to use custom column methods as much as
40 * possible.
41 *
42 * Since we have defined a column_title() method later on, this method doesn't
43 * need to concern itself with any column with a name of 'title'. Instead, it
44 * needs to handle everything else.
45 *
46 * For more detailed insight into how columns are handled, take a look at
47 * WP_List_Table::single_row_columns()
48 *
49 * @param array $item A singular item (one full row's worth of data)
50 * @param string $column_name The name/slug of the column to be processed
51 *
52 * @return string Text or HTML to be placed inside the column <td>
53 **************************************************************************/
54 function column_default( $item, $column_name ) {
55 switch( $column_name ) {
56 case 'groupid':
57 $usergroup = WPF()->usergroup->get_usergroup( $item[ $column_name ] );
58
59 return ! empty( $usergroup['name'] ) ? esc_html( $usergroup['name'] ) : __( 'default', 'wpforo' );
60 case 'secondary_groupids':
61 return implode( ', ', WPF()->usergroup->get_secondary_group_names( $item[ $column_name ] ) );
62 case 'blog_posts':
63 return WPF()->member->blog_posts( $item['userid'] );
64 case 'blog_comments':
65 return WPF()->member->blog_comments( $item['userid'], $item['user_email'] );
66 case 'online_time':
67 return ( $item['online_time'] ) ? wpforo_date_raw( $item['online_time'], 'datetime', false ) : '-';
68 default:
69 return $item[ $column_name ];
70 }
71 }
72
73
74 /** ************************************************************************
75 * Recommended. This is a custom column method and is responsible for what
76 * is rendered in any column with a name/slug of 'title'. Every time the class
77 * needs to render a column, it first looks for a method named
78 * column_{$column_title} - if it exists, that method is run. If it doesn't
79 * exist, column_default() is called instead.
80 *
81 * This example also illustrates how to implement rollover actions. Actions
82 * should be an associative array formatted as 'slug'=>'link html' - and you
83 * will need to generate the URLs yourself. You could even ensure the links
84 *
85 *
86 * @param array $item A singular item (one full row's worth of data)
87 *
88 * @return string Text to be placed inside the column <td> (movie title only)
89 **************************************************************************@see WP_List_Table::::single_row_columns()
90 */
91 function column_userid( $item ) {
92 $edit_user = admin_url( 'user-edit.php?user_id=' . intval( $item['userid'] ) );
93 $edit_profile = WPF()->member->get_profile_url( $item['userid'], 'account' );
94 $ban = wp_nonce_url( admin_url( sprintf( 'admin.php?page=wpforo-members&wpfaction=%1$s&userid=%2$s', 'user_ban', $item['userid'] ) ), 'wpforo-user-ban-' . $item['userid'] );
95 $unban = wp_nonce_url( admin_url( sprintf( 'admin.php?page=wpforo-members&wpfaction=%1$s&userid=%2$s', 'user_unban', $item['userid'] ) ), 'wpforo-user-unban-' . $item['userid'] );
96 $activate = wp_nonce_url( admin_url( sprintf( 'admin.php?page=wpforo-members&wpfaction=%1$s&userid=%2$s', 'user_activate', $item['userid'] ) ), 'wpforo-user-activate-' . $item['userid'] );
97 $deactivate = wp_nonce_url(
98 admin_url( sprintf( 'admin.php?page=wpforo-members&wpfaction=%1$s&userid=%2$s', 'user_deactivate', $item['userid'] ) ),
99 'wpforo-user-deactivate-' . $item['userid']
100 );
101 $delete = wp_nonce_url( "users.php?action=delete&user=" . intval( $item['userid'] ), 'bulk-users' );
102 $actions = [
103 'edit_user' => '<a href="' . esc_url( (string) $edit_user ) . '">' . __( 'Edit User', 'wpforo' ) . '</a>',
104 'edit_profile' => '<a href="' . esc_url( (string) $edit_profile ) . '">' . __( 'Edit Profile', 'wpforo' ) . '</a>',
105 'ban' => '<a href="' . esc_url( (string) $ban ) . '" style="color: orange;" title="' . __( 'Ban', 'wpforo' ) . '" onclick="return confirm(\'' . __(
106 'Are you sure, you want to BAN this user?',
107 'wpforo'
108 ) . '\')">' . __( 'Ban', 'wpforo' ) . '</a>',
109 'unban' => '<a href="' . esc_url( (string) $unban ) . '" style="color: orange;" title="' . __( 'Unban', 'wpforo' ) . '" onclick="return confirm(\'' . __(
110 'Are you sure, you want to UNBAN this user?',
111 'wpforo'
112 ) . '\')">' . __( 'Unban', 'wpforo' ) . '</a>',
113 'activate' => '<a href="' . esc_url( (string) $activate ) . '" style="color: green;" title="' . __( 'Activate', 'wpforo' ) . '" onclick="return confirm(\'' . __(
114 'Are you sure, you want to activate this user?',
115 'wpforo'
116 ) . '\')">' . __( 'Activate', 'wpforo' ) . '</a>',
117 'deactivate' => '<a href="' . esc_url( (string) $deactivate ) . '" style="color: #8a5700;" title="' . __( 'Deactivate', 'wpforo' ) . '" onclick="return confirm(\'' . __(
118 'Are you sure, you want to deactivate this user?',
119 'wpforo'
120 ) . '\')">' . __( 'Deactivate', 'wpforo' ) . '</a>',
121 'delete' => '<a href="' . esc_url( (string) $delete ) . '" title="' . __( 'Delete', 'wpforo' ) . '">' . __( 'Delete', 'wpforo' ) . '</a>',
122 ];
123
124 if( ! WPF()->usergroup->can( 'em' ) ) {
125 unset( $actions['edit_user'] );
126 unset( $actions['edit_profile'] );
127 }
128 if( ! WPF()->usergroup->can( 'bm' ) || intval( $item['userid'] ) === WPF()->current_userid ) {
129 unset( $actions['ban'] );
130 unset( $actions['unban'] );
131 }
132 if( $item['status'] === 'banned' ) {
133 unset( $actions['ban'] );
134 } else {
135 unset( $actions['unban'] );
136 }
137 if( $item['status'] === 'inactive' ) {
138 unset( $actions['deactivate'] );
139 } else {
140 unset( $actions['activate'] );
141 }
142 if( ! WPF()->usergroup->can( 'dm' ) || intval( $item['userid'] ) === WPF()->current_userid ) {
143 unset( $actions['delete'] );
144 }
145
146 //Return the title contents
147 return sprintf(
148 '%1$s %2$s',
149 /*$1%s*/ $item['userid'],
150 /*$2%s*/ $this->row_actions( $actions )
151 );
152 }
153
154
155 /** ************************************************************************
156 * REQUIRED if displaying checkboxes or using bulk actions! The 'cb' column
157 * is given special treatment when columns are processed. It ALWAYS needs to
158 * have its own method.
159 *
160 * @param array $item A singular item (one full row's worth of data)
161 *
162 * @return string Text to be placed inside the column <td> (movie title only)
163 **************************************************************************@see WP_List_Table::::single_row_columns()
164 */
165 function column_cb( $item ) {
166 return sprintf(
167 '<input type="checkbox" name="%1$s[]" value="%2$s" %3$s>',
168 /*$1%s*/ 'userids', //Let's simply repurpose the table's singular label ("movie")
169 /*$2%s*/ $item['userid'], //The value of the checkbox should be the record's id
170 ( intval( $item['userid'] ) === WPF()->current_userid ) ? 'disabled' : ''
171 );
172 }
173
174
175 /** ************************************************************************
176 * REQUIRED! This method dictates the table's columns and titles. This should
177 * return an array where the key is the column slug (and class) and the value
178 * is the column's title text. If you need a checkbox for bulk actions, refer
179 * to the $columns array below.
180 *
181 * The 'cb' column is treated differently than the rest. If including a checkbox
182 * column in your table you must create a column_cb() method. If you don't need
183 * to bulk actions or checkboxes, simply leave the 'cb' entry out of your array.
184 *
185 * @return array An associative array containing column information: 'slugs'=>'Visible Titles'
186 **************************************************************************@see WP_List_Table::::single_row_columns()
187 */
188 function get_columns() {
189 $columns = [
190 'cb' => '<input type="checkbox" />', //Render a checkbox instead of text
191 'userid' => __( 'ID', 'wpforo' ),
192 'display_name' => __( 'Display Name', 'wpforo' ),
193 'user_login' => __( 'Login', 'wpforo' ),
194 'user_email' => __( 'Email', 'wpforo' ),
195 'groupid' => __( 'Group', 'wpforo' ),
196 'secondary_groupids' => __( 'Secondary Groups', 'wpforo' ),
197 'status' => __( 'Status', 'wpforo' ),
198 'online_time' => __( 'Last Login', 'wpforo' ),
199 'posts' => __( 'Forum Posts', 'wpforo' ),
200 'blog_posts' => __( 'Blog Posts', 'wpforo' ),
201 'blog_comments' => __( 'Blog Comments', 'wpforo' ),
202 ];
203
204 return $this->unset_not_permitted_cols( $columns );
205 }
206
207
208 /** ************************************************************************
209 * Optional. If you want one or more columns to be sortable (ASC/DESC toggle),
210 * you will need to register it here. This should return an array where the
211 * key is the column that needs to be sortable, and the value is db column to
212 * sort by. Often, the key and value will be the same, but this is not always
213 * the case (as the value is a column name from the database, not the list table).
214 *
215 * This method merely defines which columns should be sortable and makes them
216 * clickable - it does not handle the actual sorting. You still need to detect
217 * the ORDERBY and ORDER querystring variables within prepare_items() and sort
218 * your data accordingly (usually by modifying your query).
219 *
220 * @return array An associative array containing all the columns that should be sortable: 'slugs'=>array('data_values',bool)
221 **************************************************************************/
222 function get_sortable_columns() {
223 $columns = [
224 'userid' => [ 'userid', false ], //true means it's already sorted
225 'display_name' => [ 'display_name', false ],
226 'user_login' => [ 'user_login', false ],
227 'user_email' => [ 'user_email', false ],
228 'groupid' => [ 'groupid', false ],
229 'status' => [ 'status', false ],
230 'online_time' => [ 'online_time', false ],
231 'posts' => [ 'posts', false ],
232 ];
233
234 return $this->unset_not_permitted_cols( $columns );
235 }
236
237 private function unset_not_permitted_cols( $columns ) {
238 if( ! WPF()->usergroup->can( 'vmu' ) ) {
239 unset( $columns['user_login'] );
240 }
241 if( ! WPF()->usergroup->can( 'vmm' ) ) {
242 unset( $columns['user_email'] );
243 }
244 if( ! WPF()->usergroup->can( 'vmg' ) ) {
245 unset( $columns['groupid'], $columns['secondary_groupids'] );
246 }
247 if( ! WPF()->usergroup->can( 'bm' ) ) {
248 unset( $columns['status'] );
249 }
250 if( ! WPF()->usergroup->can( 'vms' ) ) {
251 unset( $columns['signature'] );
252 }
253
254 return $columns;
255 }
256
257
258 /** ************************************************************************
259 * Optional. If you need to include bulk actions in your list table, this is
260 * the place to define them. Bulk actions are an associative array in the format
261 * 'slug'=>'Visible Title'
262 *
263 * If this method returns an empty value, no bulk action will be rendered. If
264 * you specify any bulk actions, the bulk actions box will be rendered with
265 * the table automatically on display().
266 *
267 * Also note that list tables are not automatically wrapped in <form> elements,
268 * so you will need to create those manually in order for bulk actions to function.
269 *
270 * @return array An associative array containing all the bulk actions: 'slugs'=>'Visible Titles'
271 **************************************************************************/
272 function get_bulk_actions() {
273 $actions = [
274 'activate' => __( 'Activate', 'wpforo' ),
275 'deactivate' => __( 'Deactivate', 'wpforo' ),
276 'ban' => __( 'Ban', 'wpforo' ),
277 'unban' => __( 'Unban', 'wpforo' ),
278 'delete' => __( 'Delete', 'wpforo' ),
279 ];
280 if( ! WPF()->usergroup->can( 'bm' ) ) {
281 unset( $actions['ban'] );
282 unset( $actions['unban'] );
283 unset( $actions['activate'] );
284 unset( $actions['deactivate'] );
285 }
286 if( ! WPF()->usergroup->can( 'dm' ) ) {
287 unset( $actions['delete'] );
288 }
289
290 return $actions;
291 }
292
293 function extra_tablenav( $which ) {
294 $name = 'new_groupid' . ( $which === 'bottom' ? '2' : '' );
295 printf(
296 '<div class="alignleft actions">
297 <label class="screen-reader-text" for="new_role">%1$s</label>
298 <select name="%2$s" id="new_role">
299 <option value="-1">%1$s</option>
300 %3$s
301 </select>
302 <input type="submit" name="change_group" class="button" value="%4$s">
303 </div>',
304 __( 'Change usergroup to…', 'wpforo' ),
305 $name,
306 WPF()->usergroup->get_selectbox( [], 4 ),
307 __( 'Change', 'wpforo' )
308 );
309 }
310
311
312 /** ************************************************************************
313 * REQUIRED! This is where you prepare your data for display. This method will
314 * usually be used to query the database, sort and filter the data, and generally
315 * get it ready to be displayed. At a minimum, we should set $this->items and
316 * $this->set_pagination_args(), although the following properties and methods
317 * are frequently interacted with here...
318 *
319 * @uses $this->_column_headers
320 * @uses $this->items
321 * @uses $this->get_columns()
322 * @uses $this->get_sortable_columns()
323 * @uses $this->get_pagenum()
324 * @uses $this->set_pagination_args()
325 **************************************************************************/
326 function prepare_items() {
327 /**
328 * First, lets decide how many records per page to show
329 */
330 $per_page = wpforo_get_option( 'count_per_page', 10 );
331
332
333 /**
334 * REQUIRED. Now we need to define our column headers. This includes a complete
335 * array of columns to be displayed (slugs & titles), a list of columns
336 * to keep hidden, and a list of columns that are sortable. Each of these
337 * can be defined in another method (as we've done here) before being
338 * used to build the value for our _column_headers property.
339 */
340 $columns = $this->get_columns();
341 $hidden = [];
342 $sortable = $this->get_sortable_columns();
343
344
345 /**
346 * REQUIRED. Finally, we build an array to be used by the class for column
347 * headers. The $this->_column_headers property takes an array which contains
348 * 3 other arrays. One for all columns, one for hidden columns, and one
349 * for sortable columns.
350 */
351 $this->_column_headers = [ $columns, $hidden, $sortable ];
352
353 $search_fields = [
354 'title' => 'title',
355 'display_name' => 'display_name',
356 'user_login' => 'user_login',
357 'user_email' => 'user_email',
358 'signature' => 'signature',
359 ];
360 $search_fields = $this->unset_not_permitted_cols( $search_fields );
361
362 /**
363 * REQUIRED. Now we can add our *sorted* data to the items' property, where
364 * it can be used by the rest of the class.
365 */
366 $args = [];
367 if( $s = wpfval( $_REQUEST, 's' ) ) $args['include'] = WPF()->member->search( $s, $search_fields );
368 $orderby = (string) wpfval( $_REQUEST, 'orderby' );
369 $order = strtoupper( (string) wpfval( $_REQUEST, 'order' ) );
370 $filter_by_group = $this->get_filter_by_group_var();
371 $filter_by_status = $this->get_filter_by_status_var();
372 if( $filter_by_group !== - 1 ) $args['groupid'] = $filter_by_group;
373 if( $filter_by_status !== - 1 ) $args['status'] = (array) sanitize_text_field( $filter_by_status );
374 if( array_key_exists( $orderby, $sortable ) ) $args['orderby'] = sanitize_text_field( $orderby );
375 if( in_array( $order, [ 'ASC', 'DESC' ] ) ) $args['order'] = sanitize_text_field( $order );
376
377 $paged = $this->get_pagenum();
378 $args['offset'] = ( $paged - 1 ) * $per_page;
379 $args['row_count'] = $per_page;
380
381 $items_count = 0;
382 $this->items = ( isset( $args['include'] ) && empty( $args['include'] ) ? [] : WPF()->member->get_members( $args, $items_count ) );
383
384 $this->wpfitems_count = $items_count;
385
386 $this->set_pagination_args( [
387 'total_items' => $items_count, //WE have to calculate the total number of items
388 'per_page' => $per_page, //WE have to determine how many items to show on a page
389 'total_pages' => ceil( $items_count / $per_page ), //WE have to calculate the total number of pages
390 ] );
391 }
392
393 public function get_filter_by_group_var() {
394 $filter_by_group = wpfval( $_REQUEST, 'filter_by_group' );
395 if( ! is_null( $filter_by_group ) && $filter_by_group !== '-1' ) {
396 $groupid = $filter_by_group;
397 } else {
398 $groupid = - 1;
399 }
400
401 return intval( $groupid );
402 }
403
404 public function get_filter_by_status_var() {
405 $filter_by_status = wpfval( $_REQUEST, 'filter_by_status' );
406 if( ! is_null( $filter_by_status ) && $filter_by_status !== '-1' ) {
407 $status = $filter_by_status;
408 } else {
409 $status = - 1;
410 }
411
412 return $status;
413 }
414
415 public function groups_dropdown() { ?>
416 <label>
417 <select name="filter_by_group">
418 <option value="-1">-- <?php _e( 'filter by group', 'wpforo' ) ?> --</option>
419 <?php $selected = $this->get_filter_by_group_var();
420 foreach( WPF()->usergroup->get_usergroups() as $group ) {
421 $group['groupid'] = intval( $group['groupid'] );
422 if( $group['groupid'] !== 4 ) {
423 printf(
424 '<option value="%1$s" %2$s>%3$s</option>',
425 $group['groupid'],
426 ( $group['groupid'] === $selected ) ? 'selected' : '',
427 esc_html( $group['name'] )
428 );
429 }
430 }
431 ?>
432 </select>
433 </label>
434 <?php
435 }
436
437 public function status_dropdown() { ?>
438 <label>
439 <select name="filter_by_status">
440 <option value="-1">-- <?php _e( 'filter by status', 'wpforo' ) ?> --</option>
441 <?php
442 if( $statuses = WPF()->member->get_distinct_status() ) {
443 $selected = $this->get_filter_by_status_var();
444 foreach( $statuses as $status ) {
445 printf(
446 '<option value="%1$s" %2$s>%3$s</option>',
447 esc_attr( $status ),
448 ( $status === $selected ) ? 'selected' : '',
449 esc_html( $status )
450 );
451 }
452 }
453 ?>
454 </select>
455 </label>
456 <?php
457 }
458 }
459