PluginProbe
wpForo Forum / 2.3.5
wpForo Forum v2.3.5
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 2.3.5, at admin/listtables/Members.php

444 lines 19.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( admin_url( sprintf( 'admin.php?page=wpforo-members&wpfaction=%1$s&userid=%2$s', 'user_deactivate', $item['userid'] ) ), 'wpforo-user-deactivate-' . $item['userid'] );
98 $delete = wp_nonce_url( "users.php?action=delete&user=" . intval( $item['userid'] ), 'bulk-users' );
99 $actions = [
100 'edit_user' => '<a href="' . esc_url( (string) $edit_user ) . '">' . __( 'Edit User', 'wpforo' ) . '</a>',
101 'edit_profile' => '<a href="' . esc_url( (string) $edit_profile ) . '">' . __( 'Edit Profile', 'wpforo' ) . '</a>',
102 'ban' => '<a href="' . esc_url( (string) $ban ) . '" style="color: orange;" title="' . __( 'Ban', 'wpforo' ) . '" onclick="return confirm(\'' . __( 'Are you sure, you want to BAN this user?', 'wpforo' ) . '\')">' . __( 'Ban', 'wpforo' ) . '</a>',
103 'unban' => '<a href="' . esc_url( (string) $unban ) . '" style="color: orange;" title="' . __( 'Unban', 'wpforo' ) . '" onclick="return confirm(\'' . __( 'Are you sure, you want to UNBAN this user?', 'wpforo' ) . '\')">' . __( 'Unban', 'wpforo' ) . '</a>',
104 'activate' => '<a href="' . esc_url( (string) $activate ) . '" style="color: green;" title="' . __( 'Activate', 'wpforo' ) . '" onclick="return confirm(\'' . __( 'Are you sure, you want to activate this user?', 'wpforo' ) . '\')">' . __( 'Activate', 'wpforo' ) . '</a>',
105 'deactivate' => '<a href="' . esc_url( (string) $deactivate ) . '" style="color: #8a5700;" title="' . __( 'Deactivate', 'wpforo' ) . '" onclick="return confirm(\'' . __( 'Are you sure, you want to deactivate this user?', 'wpforo' ) . '\')">' . __( 'Deactivate', 'wpforo' ) . '</a>',
106 'delete' => '<a href="' . esc_url( (string) $delete ) . '" title="' . __( 'Delete', 'wpforo' ) . '">' . __( 'Delete', 'wpforo' ) . '</a>',
107 ];
108
109 if( ! WPF()->usergroup->can( 'em' ) ) {
110 unset( $actions['edit_user'] );
111 unset( $actions['edit_profile'] );
112 }
113 if( ! WPF()->usergroup->can( 'bm' ) || intval( $item['userid'] ) === WPF()->current_userid ) {
114 unset( $actions['ban'] );
115 unset( $actions['unban'] );
116 }
117 if( $item['status'] === 'banned' ) {
118 unset( $actions['ban'] );
119 } else {
120 unset( $actions['unban'] );
121 }
122 if( $item['status'] === 'inactive' ) {
123 unset( $actions['deactivate'] );
124 } else {
125 unset( $actions['activate'] );
126 }
127 if( ! WPF()->usergroup->can( 'dm' ) || intval( $item['userid'] ) === WPF()->current_userid ) {
128 unset( $actions['delete'] );
129 }
130
131 //Return the title contents
132 return sprintf(
133 '%1$s %2$s',
134 /*$1%s*/ $item['userid'],
135 /*$2%s*/ $this->row_actions( $actions )
136 );
137 }
138
139
140 /** ************************************************************************
141 * REQUIRED if displaying checkboxes or using bulk actions! The 'cb' column
142 * is given special treatment when columns are processed. It ALWAYS needs to
143 * have its own method.
144 *
145 * @param array $item A singular item (one full row's worth of data)
146 *
147 * @return string Text to be placed inside the column <td> (movie title only)
148 **************************************************************************@see WP_List_Table::::single_row_columns()
149 */
150 function column_cb( $item ) {
151 return sprintf(
152 '<input type="checkbox" name="%1$s[]" value="%2$s" %3$s>',
153 /*$1%s*/ 'userids', //Let's simply repurpose the table's singular label ("movie")
154 /*$2%s*/ $item['userid'], //The value of the checkbox should be the record's id
155 ( intval( $item['userid'] ) === WPF()->current_userid ) ? 'disabled' : ''
156 );
157 }
158
159
160 /** ************************************************************************
161 * REQUIRED! This method dictates the table's columns and titles. This should
162 * return an array where the key is the column slug (and class) and the value
163 * is the column's title text. If you need a checkbox for bulk actions, refer
164 * to the $columns array below.
165 *
166 * The 'cb' column is treated differently than the rest. If including a checkbox
167 * column in your table you must create a column_cb() method. If you don't need
168 * to bulk actions or checkboxes, simply leave the 'cb' entry out of your array.
169 *
170 * @return array An associative array containing column information: 'slugs'=>'Visible Titles'
171 **************************************************************************@see WP_List_Table::::single_row_columns()
172 */
173 function get_columns() {
174 $columns = [
175 'cb' => '<input type="checkbox" />', //Render a checkbox instead of text
176 'userid' => __( 'ID', 'wpforo' ),
177 'display_name' => __( 'Display Name', 'wpforo' ),
178 'user_login' => __( 'Login', 'wpforo' ),
179 'user_email' => __( 'Email', 'wpforo' ),
180 'groupid' => __( 'Group', 'wpforo' ),
181 'secondary_groupids' => __( 'Secondary Groups', 'wpforo' ),
182 'status' => __( 'Status', 'wpforo' ),
183 'online_time' => __( 'Last Login', 'wpforo' ),
184 'posts' => __( 'Forum Posts', 'wpforo' ),
185 'blog_posts' => __( 'Blog Posts', 'wpforo' ),
186 'blog_comments' => __( 'Blog Comments', 'wpforo' ),
187 ];
188
189 return $this->unset_not_permitted_cols( $columns );
190 }
191
192
193 /** ************************************************************************
194 * Optional. If you want one or more columns to be sortable (ASC/DESC toggle),
195 * you will need to register it here. This should return an array where the
196 * key is the column that needs to be sortable, and the value is db column to
197 * sort by. Often, the key and value will be the same, but this is not always
198 * the case (as the value is a column name from the database, not the list table).
199 *
200 * This method merely defines which columns should be sortable and makes them
201 * clickable - it does not handle the actual sorting. You still need to detect
202 * the ORDERBY and ORDER querystring variables within prepare_items() and sort
203 * your data accordingly (usually by modifying your query).
204 *
205 * @return array An associative array containing all the columns that should be sortable: 'slugs'=>array('data_values',bool)
206 **************************************************************************/
207 function get_sortable_columns() {
208 $columns = [
209 'userid' => [ 'userid', false ], //true means it's already sorted
210 'display_name' => [ 'display_name', false ],
211 'user_login' => [ 'user_login', false ],
212 'user_email' => [ 'user_email', false ],
213 'groupid' => [ 'groupid', false ],
214 'status' => [ 'status', false ],
215 'online_time' => [ 'online_time', false ],
216 'posts' => [ 'posts', false ],
217 ];
218
219 return $this->unset_not_permitted_cols( $columns );
220 }
221
222 private function unset_not_permitted_cols( $columns ) {
223 if( ! WPF()->usergroup->can( 'vmu' ) ) {
224 unset( $columns['user_login'] );
225 }
226 if( ! WPF()->usergroup->can( 'vmm' ) ) {
227 unset( $columns['user_email'] );
228 }
229 if( ! WPF()->usergroup->can( 'vmg' ) ) {
230 unset( $columns['groupid'], $columns['secondary_groupids'] );
231 }
232 if( ! WPF()->usergroup->can( 'bm' ) ) {
233 unset( $columns['status'] );
234 }
235 if( ! WPF()->usergroup->can( 'vms' ) ) {
236 unset( $columns['signature'] );
237 }
238
239 return $columns;
240 }
241
242
243 /** ************************************************************************
244 * Optional. If you need to include bulk actions in your list table, this is
245 * the place to define them. Bulk actions are an associative array in the format
246 * 'slug'=>'Visible Title'
247 *
248 * If this method returns an empty value, no bulk action will be rendered. If
249 * you specify any bulk actions, the bulk actions box will be rendered with
250 * the table automatically on display().
251 *
252 * Also note that list tables are not automatically wrapped in <form> elements,
253 * so you will need to create those manually in order for bulk actions to function.
254 *
255 * @return array An associative array containing all the bulk actions: 'slugs'=>'Visible Titles'
256 **************************************************************************/
257 function get_bulk_actions() {
258 $actions = [
259 'activate' => __( 'Activate', 'wpforo' ),
260 'deactivate' => __( 'Deactivate', 'wpforo' ),
261 'ban' => __( 'Ban', 'wpforo' ),
262 'unban' => __( 'Unban', 'wpforo' ),
263 'delete' => __( 'Delete', 'wpforo' ),
264 ];
265 if( ! WPF()->usergroup->can( 'bm' ) ) {
266 unset( $actions['ban'] );
267 unset( $actions['unban'] );
268 unset( $actions['activate'] );
269 unset( $actions['deactivate'] );
270 }
271 if( ! WPF()->usergroup->can( 'dm' ) ) {
272 unset( $actions['delete'] );
273 }
274
275 return $actions;
276 }
277
278 function extra_tablenav( $which ) {
279 $name = 'new_groupid' . ( $which === 'bottom' ? '2' : '' );
280 printf(
281 '<div class="alignleft actions">
282 <label class="screen-reader-text" for="new_role">%1$s</label>
283 <select name="%2$s" id="new_role">
284 <option value="-1">%1$s</option>
285 %3$s
286 </select>
287 <input type="submit" name="change_group" class="button" value="%4$s">
288 </div>',
289 __( 'Change usergroup to…', 'wpforo' ),
290 $name,
291 WPF()->usergroup->get_selectbox( [], 4 ),
292 __( 'Change', 'wpforo' )
293 );
294 }
295
296
297 /** ************************************************************************
298 * REQUIRED! This is where you prepare your data for display. This method will
299 * usually be used to query the database, sort and filter the data, and generally
300 * get it ready to be displayed. At a minimum, we should set $this->items and
301 * $this->set_pagination_args(), although the following properties and methods
302 * are frequently interacted with here...
303 *
304 * @uses $this->_column_headers
305 * @uses $this->items
306 * @uses $this->get_columns()
307 * @uses $this->get_sortable_columns()
308 * @uses $this->get_pagenum()
309 * @uses $this->set_pagination_args()
310 **************************************************************************/
311 function prepare_items() {
312 /**
313 * First, lets decide how many records per page to show
314 */
315 $per_page = wpforo_get_option( 'count_per_page', 10 );
316
317
318 /**
319 * REQUIRED. Now we need to define our column headers. This includes a complete
320 * array of columns to be displayed (slugs & titles), a list of columns
321 * to keep hidden, and a list of columns that are sortable. Each of these
322 * can be defined in another method (as we've done here) before being
323 * used to build the value for our _column_headers property.
324 */
325 $columns = $this->get_columns();
326 $hidden = [];
327 $sortable = $this->get_sortable_columns();
328
329
330 /**
331 * REQUIRED. Finally, we build an array to be used by the class for column
332 * headers. The $this->_column_headers property takes an array which contains
333 * 3 other arrays. One for all columns, one for hidden columns, and one
334 * for sortable columns.
335 */
336 $this->_column_headers = [ $columns, $hidden, $sortable ];
337
338 $search_fields = [
339 'title' => 'title',
340 'display_name' => 'display_name',
341 'user_login' => 'user_login',
342 'user_email' => 'user_email',
343 'signature' => 'signature',
344 ];
345 $search_fields = $this->unset_not_permitted_cols( $search_fields );
346
347 /**
348 * REQUIRED. Now we can add our *sorted* data to the items' property, where
349 * it can be used by the rest of the class.
350 */
351 $args = [];
352 if( $s = wpfval( $_REQUEST, 's' ) ) $args['include'] = WPF()->member->search( $s, $search_fields );
353 $orderby = wpfval( $_REQUEST, 'orderby' );
354 $order = strtoupper( (string) wpfval( $_REQUEST, 'order' ) );
355 $filter_by_group = $this->get_filter_by_group_var();
356 $filter_by_status = $this->get_filter_by_status_var();
357 if( $filter_by_group !== - 1 ) $args['groupid'] = $filter_by_group;
358 if( $filter_by_status !== - 1 ) $args['status'] = (array) sanitize_text_field( $filter_by_status );
359 if( array_key_exists( $orderby, $sortable ) ) $args['orderby'] = sanitize_text_field( $orderby );
360 if( in_array( $order, [ 'ASC', 'DESC' ] ) ) $args['order'] = sanitize_text_field( $order );
361
362 $paged = $this->get_pagenum();
363 $args['offset'] = ( $paged - 1 ) * $per_page;
364 $args['row_count'] = $per_page;
365
366 $items_count = 0;
367 $this->items = ( isset( $args['include'] ) && empty( $args['include'] ) ? [] : WPF()->member->get_members( $args, $items_count ) );
368
369 $this->wpfitems_count = $items_count;
370
371 $this->set_pagination_args( [
372 'total_items' => $items_count, //WE have to calculate the total number of items
373 'per_page' => $per_page, //WE have to determine how many items to show on a page
374 'total_pages' => ceil( $items_count / $per_page ) //WE have to calculate the total number of pages
375 ] );
376 }
377
378 public function get_filter_by_group_var() {
379 $filter_by_group = wpfval( $_REQUEST, 'filter_by_group' );
380 if( ! is_null( $filter_by_group ) && $filter_by_group !== '-1' ) {
381 $groupid = $filter_by_group;
382 } else {
383 $groupid = - 1;
384 }
385
386 return intval( $groupid );
387 }
388
389 public function get_filter_by_status_var() {
390 $filter_by_status = wpfval( $_REQUEST, 'filter_by_status' );
391 if( ! is_null( $filter_by_status ) && $filter_by_status !== '-1' ) {
392 $status = $filter_by_status;
393 } else {
394 $status = - 1;
395 }
396
397 return $status;
398 }
399
400 public function groups_dropdown() { ?>
401 <label>
402 <select name="filter_by_group">
403 <option value="-1">-- <?php _e( 'filter by group', 'wpforo' ) ?> --</option>
404 <?php $selected = $this->get_filter_by_group_var();
405 foreach( WPF()->usergroup->get_usergroups() as $group ) {
406 $group['groupid'] = intval( $group['groupid'] );
407 if( $group['groupid'] !== 4 ) {
408 printf(
409 '<option value="%1$s" %2$s>%3$s</option>',
410 $group['groupid'],
411 ( $group['groupid'] === $selected ) ? 'selected' : '',
412 esc_html( $group['name'] )
413 );
414 }
415 }
416 ?>
417 </select>
418 </label>
419 <?php
420 }
421
422 public function status_dropdown() { ?>
423 <label>
424 <select name="filter_by_status">
425 <option value="-1">-- <?php _e( 'filter by status', 'wpforo' ) ?> --</option>
426 <?php
427 if( $statuses = WPF()->member->get_distinct_status() ) {
428 $selected = $this->get_filter_by_status_var();
429 foreach( $statuses as $status ) {
430 printf(
431 '<option value="%1$s" %2$s>%3$s</option>',
432 esc_attr( $status ),
433 ( $status === $selected ) ? 'selected' : '',
434 esc_html( $status )
435 );
436 }
437 }
438 ?>
439 </select>
440 </label>
441 <?php
442 }
443 }
444