Simple Login Log - Version 0.5

Version Description

Download this release

Release Info

Developer maxchirkov
Plugin Icon wp plugin Simple Login Log
Version 0.5
Comparing to
See all releases

Code changes from version 0.3 to 0.5

Files changed (3) hide show
  1. languages/simple-login-log.pot +151 -0
  2. readme.txt +48 -7
  3. simple-login-log.php +401 -104
languages/simple-login-log.pot ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2010 Simple Login Log
2
+ # This file is distributed under the same license as the Simple Login Log package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: Simple Login Log 0.4\n"
6
+ "Report-Msgid-Bugs-To: http://wordpress.org/tag/simple-login-log\n"
7
+ "POT-Creation-Date: 2011-12-02 17:27:38+00:00\n"
8
+ "MIME-Version: 1.0\n"
9
+ "Content-Type: text/plain; charset=UTF-8\n"
10
+ "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2010-MO-DA HO:MI+ZONE\n"
12
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
+ "Language-Team: LANGUAGE <LL@li.org>\n"
14
+
15
+ #: simple-login-log.php:63 simple-login-log.php:681
16
+ msgid "Successful"
17
+ msgstr ""
18
+
19
+ #: simple-login-log.php:64 simple-login-log.php:682
20
+ msgid "Failed"
21
+ msgstr ""
22
+
23
+ #: simple-login-log.php:65
24
+ msgid "Login"
25
+ msgstr ""
26
+
27
+ #: simple-login-log.php:66
28
+ msgid "User Agent"
29
+ msgstr ""
30
+
31
+ #: simple-login-log.php:67
32
+ msgid "Login Redirect"
33
+ msgstr ""
34
+
35
+ #: simple-login-log.php:68
36
+ msgid "#"
37
+ msgstr ""
38
+
39
+ #: simple-login-log.php:69
40
+ msgid "User ID"
41
+ msgstr ""
42
+
43
+ #: simple-login-log.php:70
44
+ msgid "Username"
45
+ msgstr ""
46
+
47
+ #: simple-login-log.php:71
48
+ msgid "Name"
49
+ msgstr ""
50
+
51
+ #: simple-login-log.php:72
52
+ msgid "Time"
53
+ msgstr ""
54
+
55
+ #: simple-login-log.php:73
56
+ msgid "IP Address"
57
+ msgstr ""
58
+
59
+ #: simple-login-log.php:74
60
+ msgid "Login Result"
61
+ msgstr ""
62
+
63
+ #: simple-login-log.php:75
64
+ msgid "Data"
65
+ msgstr ""
66
+
67
+ #: simple-login-log.php:112
68
+ msgid "Records"
69
+ msgstr ""
70
+
71
+ #. #-#-#-#-# plugin.pot (Simple Login Log 0.4) #-#-#-#-#
72
+ #. Plugin Name of the plugin/theme
73
+ #: simple-login-log.php:259 simple-login-log.php:268
74
+ msgid "Simple Login Log"
75
+ msgstr ""
76
+
77
+ #: simple-login-log.php:260
78
+ msgid "Truncate Log Entries"
79
+ msgstr ""
80
+
81
+ #: simple-login-log.php:261
82
+ msgid "Log Failed Attempts"
83
+ msgstr ""
84
+
85
+ #: simple-login-log.php:268 simple-login-log.php:386
86
+ msgid "Login Log"
87
+ msgstr ""
88
+
89
+ #: simple-login-log.php:281
90
+ msgid "Leave empty or enter 0 if you don't want the log to be truncated."
91
+ msgstr ""
92
+
93
+ #: simple-login-log.php:286
94
+ msgid ""
95
+ "Logs failed attempts where user name and password are entered. Will not log "
96
+ "if at least one of the mentioned fields is empty."
97
+ msgstr ""
98
+
99
+ #: simple-login-log.php:396
100
+ msgid "Username:"
101
+ msgstr ""
102
+
103
+ #: simple-login-log.php:396
104
+ msgid "Filter User"
105
+ msgstr ""
106
+
107
+ #: simple-login-log.php:421
108
+ msgid "Export Log to CSV"
109
+ msgstr ""
110
+
111
+ #: simple-login-log.php:430
112
+ msgid "Export Current Results to CSV"
113
+ msgstr ""
114
+
115
+ #: simple-login-log.php:460
116
+ msgid "View All"
117
+ msgstr ""
118
+
119
+ #: simple-login-log.php:461
120
+ msgid "Filter"
121
+ msgstr ""
122
+
123
+ #: simple-login-log.php:555
124
+ msgid "Filter log by this name"
125
+ msgstr ""
126
+
127
+ #: simple-login-log.php:680
128
+ msgid "Login Results"
129
+ msgstr ""
130
+
131
+ #: simple-login-log.php:680
132
+ msgid "All"
133
+ msgstr ""
134
+
135
+ #. Plugin URI of the plugin/theme
136
+ msgid "http://simplerealtytheme.com"
137
+ msgstr ""
138
+
139
+ #. Description of the plugin/theme
140
+ msgid ""
141
+ "This plugin keeps a log of WordPress user logins. Offers user filtering and "
142
+ "export features."
143
+ msgstr ""
144
+
145
+ #. Author of the plugin/theme
146
+ msgid "Max Chirkov"
147
+ msgstr ""
148
+
149
+ #. Author URI of the plugin/theme
150
+ msgid "http://SimpleRealtyTheme.com"
151
+ msgstr ""
readme.txt CHANGED
@@ -4,31 +4,35 @@ Donate link: http://www.ibsteam.net/donate
4
  Tags: login, log, users
5
  Requires at least: 3.0
6
  Tested up to: 3.3
7
- Stable tag: 0.3
8
 
9
  This plugin keeps a log of WordPress user logins. Offers user and date filtering, and export features.
10
 
11
  == Description ==
12
 
13
- Simple log of user logins. Tracks username, time of login, IP address and browser user agent.
14
 
15
- * Author: Max Chirkov
16
- * Author URI: [http://simplerealtytheme.com/](http://simplerealtytheme.com/ "Real Estate Themes & Plugins for WordPress")
17
- * Copyright: Released under GNU GENERAL PUBLIC LICENSE
18
 
19
  **Features include:**
20
 
21
- 1. ability to filter by username, month and year;
22
  2. export into CSV file;
23
  3. log auto-truncation;
24
  4. option to record failed login attempts.
25
 
 
 
 
 
26
  == Installation ==
27
 
28
  1. Install and activate like any other basic plugin.
29
  2. If you wish to set log trancation or opt-in to record failed login attemtps, go to Settings => General. Scroll down to Simple Login Log.
30
  3. To view login log, go to Users => Login Log. You can export the log to CSV file form the same page.
31
 
 
 
32
  == Screenshots ==
33
 
34
  1. Simple Login Log Settings.
@@ -36,7 +40,44 @@ Simple log of user logins. Tracks username, time of login, IP address and browse
36
 
37
  == Changelog ==
38
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  **Version 0.3**
40
 
41
  - Added support for third-party login plugins.
42
- - Added option to log Failed Login Attempts.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  Tags: login, log, users
5
  Requires at least: 3.0
6
  Tested up to: 3.3
7
+ Stable tag: 0.5
8
 
9
  This plugin keeps a log of WordPress user logins. Offers user and date filtering, and export features.
10
 
11
  == Description ==
12
 
13
+ Simple log of user logins. Tracks username, time of login, IP address and browser user agent.
14
 
15
+ [Demo Video](http://screenr.com/kfEs "Demo Video")
 
 
16
 
17
  **Features include:**
18
 
19
+ 1. ability to filter by username, successful/failed logins, month and year;
20
  2. export into CSV file;
21
  3. log auto-truncation;
22
  4. option to record failed login attempts.
23
 
24
+ * Author: Max Chirkov
25
+ * Author URI: [http://simplerealtytheme.com/](http://simplerealtytheme.com/ "Real Estate Themes & Plugins for WordPress")
26
+ * Copyright: Released under GNU GENERAL PUBLIC LICENSE
27
+
28
  == Installation ==
29
 
30
  1. Install and activate like any other basic plugin.
31
  2. If you wish to set log trancation or opt-in to record failed login attemtps, go to Settings => General. Scroll down to Simple Login Log.
32
  3. To view login log, go to Users => Login Log. You can export the log to CSV file form the same page.
33
 
34
+ Screen Options are available at the top of the Login Log page. Click on the *Secreen Options* tab to expand the options section. You'll be able to change the number of results pre page as well as hide/display table columns.
35
+
36
  == Screenshots ==
37
 
38
  1. Simple Login Log Settings.
40
 
41
  == Changelog ==
42
 
43
+ **Version 0.5**
44
+
45
+ - Bug fix: in_array() warning for hidden columns not returning an array.
46
+
47
+ **Version 0.4**
48
+
49
+ - Added option to export filtered log results.
50
+ - Added Views filters All/Successful/Failed logins.
51
+ - Added Screen Options: number of items per page, output visibility options for table columns.
52
+ - Added *sll-output-data* filter, which allows to alter data output in each column of the table.
53
+ - Added support for localization.
54
+
55
  **Version 0.3**
56
 
57
  - Added support for third-party login plugins.
58
+ - Added option to log Failed Login Attempts.
59
+
60
+ == Other Notes ==
61
+
62
+ = Filters =
63
+
64
+ ** Log Output Within the Table **
65
+
66
+ *sll-output-data* - filters table row array where array keys are column names and values is the output
67
+ For example, we can use this filter to link IP addresses to a geo-location service:
68
+ `
69
+ <?php
70
+ add_filter( 'sll-output-data', 'link_location_by_ip' );
71
+ function link_location_by_ip($item){
72
+
73
+ //$item is a single row for columns with their values
74
+
75
+ $item['ip'] = sprintf('<a target="_blank" href="http://infosniper.net/index.php?ip_address=%1$s&map_source=3&two_maps=1&overview_map=1&lang=1&map_type=1&zoom_level=11">%1$s</a>', $item['ip']);
76
+ return $item;
77
+ }
78
+ ?>
79
+ `
80
+
81
+ = Translation =
82
+
83
+ Currently there are no trunslations available. If you would like to contribute, the POT file is available in the *languages* folder. Translation file name convention is *sll-{locale}.mo*, where {locale} is the locale of your language. Fore example, Russian file name would be *sll-ru_RU.po*.
simple-login-log.php CHANGED
@@ -4,21 +4,22 @@
4
  Plugin URI: http://simplerealtytheme.com
5
  Description: This plugin keeps a log of WordPress user logins. Offers user filtering and export features.
6
  Author: Max Chirkov
7
- Version: 0.3
8
  Author URI: http://SimpleRealtyTheme.com
9
  */
10
 
11
-
12
  //TODO: add cleanup method on uninstall
13
 
14
  if( !class_exists( 'SimpleLoginLog' ) )
15
  {
16
  class SimpleLoginLog {
17
- private $table = 'simple_login_log';
 
18
  private $log_duration = null; //days
19
  private $opt_name = 'simple_login_log';
20
  private $opt = false;
21
- private $login_success = 1;
 
22
 
23
  function __construct()
24
  {
@@ -26,14 +27,24 @@ if( !class_exists( 'SimpleLoginLog' ) )
26
  $this->table = $wpdb->prefix . $this->table;
27
  $this->opt = get_option($this->opt_name);
28
 
29
- if(isset($_GET['download-login-log']))
 
 
 
 
 
30
  {
31
- $this->export_to_CSV();
32
- }
 
33
 
34
 
35
  add_action( 'admin_menu', array(&$this, 'sll_admin_menu') );
36
  add_action('admin_init', array(&$this, 'settings_api_init') );
 
 
 
 
37
 
38
  //Init login actions
39
  add_action( 'init', array(&$this, 'init_login_actions') );
@@ -43,6 +54,69 @@ if( !class_exists( 'SimpleLoginLog' ) )
43
 
44
  //Initialize scheduled events
45
  add_action( 'wp', array(&$this, 'init_scheduled_events') );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  }
47
 
48
  function init_login_actions(){
@@ -91,33 +165,102 @@ if( !class_exists( 'SimpleLoginLog' ) )
91
  }
92
  }
93
 
 
 
 
 
94
  function install()
95
  {
96
  global $wpdb;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
- $sql = "CREATE TABLE " . $this->table . "
99
- (
100
- id INT( 11 ) NOT NULL AUTO_INCREMENT ,
101
- uid INT( 11 ) NOT NULL ,
102
- user_login VARCHAR( 60 ) NOT NULL ,
103
- time DATETIME DEFAULT '0000-00-00 00:00:00' NOT NULL ,
104
- ip VARCHAR( 100 ) NOT NULL ,
105
- data LONGTEXT NOT NULL ,
106
- PRIMARY KEY ( id ) ,
107
- INDEX ( uid, ip )
108
- );";
109
-
110
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
111
- dbDelta($sql);
112
  }
 
113
 
114
  //Initializing Settings
115
  function settings_api_init()
116
  {
117
- add_settings_section('simple_login_log', 'Simple Login Log', array(&$this, 'sll_settings'), 'general');
118
- add_settings_field('field_log_duration', 'Truncate Log Entries', array(&$this, 'field_log_duration'), 'general', 'simple_login_log');
119
- add_settings_field('field_log_failed_attempts', 'Log Failed Attempts', array(&$this, 'field_log_failed_attempts'), 'general', 'simple_login_log');
120
- register_setting( 'general', 'simple_login_log' );
 
121
  }
122
 
123
  function sll_admin_menu()
@@ -135,17 +278,18 @@ if( !class_exists( 'SimpleLoginLog' ) )
135
  $duration = (null !== $this->opt['log_duration']) ? $this->opt['log_duration'] : $this->log_duration;
136
  $output = '<input type="text" value="' . $duration . '" name="simple_login_log[log_duration]" size="10" class="code" /> days and older.';
137
  echo $output;
138
- echo "<p>Leave empty or enter 0 if you don't want the log to be truncated.</p>";
139
  }
140
 
141
  function field_log_failed_attempts(){
142
  $failed_attempts = ( isset($this->opt['failed_attempts']) ) ? $this->opt['failed_attempts'] : false;
143
- echo '<input type="checkbox" name="simple_login_log[failed_attempts]" value="1" ' . checked( $failed_attempts, 1, false ) . ' /> Logs failed attempts where user name and password are entered. Will not log if at least one of the mentioned fields is empty.';
144
  }
145
 
146
  function admin_header()
147
  {
148
- if( isset($_GET['page']) && 'login_log' != $_GET['page'] )
 
149
  return;
150
 
151
  echo '<style type="text/css">';
@@ -155,8 +299,9 @@ if( !class_exists( 'SimpleLoginLog' ) )
155
  echo '.wp-list-table .column-name { width: 15%; }';
156
  echo '.wp-list-table .column-time { width: 15%; }';
157
  echo '.wp-list-table .column-ip { width: 10%; }';
 
158
  echo '.wp-list-table .login-failed { background: #ffd5d1; }';
159
- echo '</style>';
160
  }
161
 
162
  //Catch messages on successful login
@@ -165,14 +310,10 @@ if( !class_exists( 'SimpleLoginLog' ) )
165
  $userdata = get_user_by('login', $user_login);
166
 
167
  $uid = ($userdata->ID) ? $userdata->ID : 0;
168
-
169
- //Stop if login form wasn't submitted
170
- //if( !$_REQUEST['wp-submit'] )
171
- // return;
172
 
173
- $data['Login'] = ( 1 == $this->login_success ) ? 'Successful' : 'Failed';
174
- if ( isset( $_REQUEST['redirect_to'] ) ) { $data['Login Redirect'] = $_REQUEST['redirect_to']; }
175
- $data['User Agent'] = $_SERVER['HTTP_USER_AGENT'];
176
 
177
  $serialized_data = serialize($data);
178
 
@@ -181,10 +322,11 @@ if( !class_exists( 'SimpleLoginLog' ) )
181
  'user_login' => $user_login,
182
  'time' => current_time('mysql'),
183
  'ip' => $_SERVER['REMOTE_ADDR'],
 
184
  'data' => $serialized_data,
185
  );
186
 
187
- $format = array('%d', '%s', '%s', '%s', '%s');
188
 
189
  $this->save_data($values, $format);
190
  }
@@ -195,59 +337,108 @@ if( !class_exists( 'SimpleLoginLog' ) )
195
  $wpdb->insert( $this->table, $values, $format );
196
  }
197
 
198
- function log_manager()
199
- {
200
- global $wpdb, $ssl_list_table;
201
-
202
- $log_table = new SLL_List_Table;
203
-
204
- $limit = 20;
205
- $offset = ( isset($_REQUEST['page']) ) ? 'OFFSET ' . $limit * $_REQUEST['page'] : 0;
206
- $where = '';
207
-
208
  if( isset($_GET['filter']) && '' != $_GET['filter'] )
209
  {
210
- $where = "WHERE user_login = '{$_GET['filter']}'";
 
 
 
 
211
  }
212
  if( isset($_GET['datefilter']) && '' != $_GET['datefilter'] )
213
  {
214
  $year = substr($_GET['datefilter'], 0, 4);
215
  $month = substr($_GET['datefilter'], -2);
216
- $where = "WHERE YEAR(time) = {$year} AND MONTH(time) = {$month}";
217
  }
218
 
219
- $sql = "SELECT * FROM $this->table $where ORDER BY time DESC LIMIT $limit $offset";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  $data = $wpdb->get_results($sql, 'ARRAY_A');
 
 
 
 
 
 
 
 
221
 
222
- $log_table->items = $data;
223
- $log_table->prepare_items();
224
 
225
  echo '<div class="wrap srp">';
226
- echo '<h2>Login Log</h2>';
227
- echo '<div class="tablenav top">';
228
- echo '<div class="alignleft actions">';
229
- echo $this->date_filter();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  echo '</div>';
231
- echo '<form method="get">';
232
- echo '<p class="search-box">';
 
 
233
  echo '<input type="hidden" name="page" value="login_log" />';
234
- echo '<label>Username: </label><input type="text" name="filter" class="filter-username" /> <input class="button" type="submit" value="Filter User" />';
235
- echo '</p>';
236
- echo '</form>';
237
- echo '</div>';
238
- $log_table->display();
239
- echo '</div>';
240
- echo '<form method="get" id="export-login-log">';
241
- echo '<input type="hidden" name="page" value="login_log" />';
242
- echo '<input type="hidden" name="download-login-log" value="true" />';
243
- submit_button( __('Export Log to CSV'), 'secondary' );
244
- echo '</form>';
 
 
 
 
 
245
  }
246
 
247
  private function date_filter()
248
  {
249
  global $wpdb;
250
- $sql = "SELECT DISTINCT YEAR(time) as year, MONTH(time)as month FROM {$this->table}";
251
  $results = $wpdb->get_results($sql);
252
 
253
  if(!$results)
@@ -266,24 +457,36 @@ if( !class_exists( 'SimpleLoginLog' ) )
266
 
267
  $output = '<form method="get">';
268
  $output .= '<input type="hidden" name="page" value="login_log" />';
269
- $output .= '<select name="datefilter"><option value="">View All</option>' . $option . '</select>';
270
- $output .= '<input class="button" type="submit" value="Filter" />';
271
  $output .= '</form>';
272
  return $output;
273
  }
274
 
275
- function export_to_CSV(){
276
  global $wpdb;
277
 
278
- $sql = "SELECT * FROM $this->table";
 
 
 
 
 
 
 
 
 
279
  $data = $wpdb->get_results($sql, 'ARRAY_A');
280
 
281
  if(!$data)
282
  return;
283
 
 
 
 
284
  // send response headers to the browser
285
  header( 'Content-Type: text/csv' );
286
- header( 'Content-Disposition: attachment;filename=login_log.csv');
287
  $fp = fopen('php://output', 'w');
288
 
289
  $i = 0;
@@ -301,7 +504,7 @@ if( !class_exists( 'SimpleLoginLog' ) )
301
  }
302
 
303
  fclose($fp);
304
- die();
305
  }
306
 
307
  }
@@ -322,7 +525,7 @@ class SLL_List_Table extends WP_List_Table
322
  {
323
  function __construct()
324
  {
325
- global $status, $page;
326
 
327
  //Set parent defaults
328
  parent::__construct( array(
@@ -330,24 +533,34 @@ class SLL_List_Table extends WP_List_Table
330
  'plural' => 'users', //plural name of the listed records
331
  'ajax' => false //does this table support ajax?
332
  ) );
333
-
334
- }
 
 
335
 
336
  function column_default($item, $column_name)
337
  {
 
338
  switch($column_name){
339
  case 'id':
340
  case 'uid':
341
- case 'time':
342
  case 'ip':
343
- return $item[$column_name];
344
  case 'user_login':
345
- return "<a href='" . get_admin_url() . "users.php?page=login_log&filter={$item[$column_name]}' title='Filter log by this name'>{$item[$column_name]}</a>";
 
 
 
 
346
  case 'name';
347
  $user_info = get_userdata($item['uid']);
348
  return ( is_object($user_info) ) ? $user_info->first_name . " " . $user_info->last_name : false;
 
 
 
349
  case 'data':
350
- $data = unserialize($item[$column_name]);
351
  if(is_array($data))
352
  {
353
  $output = '';
@@ -355,28 +568,33 @@ class SLL_List_Table extends WP_List_Table
355
  {
356
  $output .= $k .': '. $v .'<br />';
357
  }
358
- if( isset($data['Login']) && $data['Login'] == 'Failed' ){
 
 
 
359
  return '<div class="login-failed">' . $output . '</div>';
360
  }
361
  return $output;
362
  }
363
  break;
364
  default:
365
- print_r($item);
366
  }
367
  }
368
 
369
  function get_columns()
370
- {
 
371
  $columns = array(
372
- 'id' => '#',
373
- 'uid' => 'User ID',
374
- 'user_login' => 'Username',
375
- 'name' => 'Name',
376
- 'time' => 'Time',
377
- 'ip' => 'IP Address',
378
- 'data' => 'Data',
379
- );
 
380
  return $columns;
381
  }
382
 
@@ -384,20 +602,98 @@ class SLL_List_Table extends WP_List_Table
384
  {
385
  $sortable_columns = array(
386
  //'id' => array('id',true), //doesn't sort correctly
387
- 'uid' => array('uid',false),
388
- 'time' => array('time',true),
389
- 'ip' => array('ip', false),
 
390
  );
391
  return $sortable_columns;
392
  }
393
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
  function prepare_items()
395
  {
396
-
 
397
  /**
398
  * First, lets decide how many records per page to show
399
- */
400
- $per_page = 20;
 
401
 
402
 
403
  /**
@@ -408,7 +704,8 @@ class SLL_List_Table extends WP_List_Table
408
  * used to build the value for our _column_headers property.
409
  */
410
  $columns = $this->get_columns();
411
- $hidden = array();
 
412
  $sortable = $this->get_sortable_columns();
413
 
414
 
@@ -418,8 +715,7 @@ class SLL_List_Table extends WP_List_Table
418
  * 3 other arrays. One for all columns, one for hidden columns, and one
419
  * for sortable columns.
420
  */
421
- $this->_column_headers = array($columns, $hidden, $sortable);
422
-
423
 
424
  /**
425
  * Optional. You can handle your bulk actions however you see fit. In this
@@ -508,6 +804,7 @@ class SLL_List_Table extends WP_List_Table
508
  'per_page' => $per_page, //WE have to determine how many items to show on a page
509
  'total_pages' => ceil($total_items/$per_page) //WE have to calculate the total number of pages
510
  ) );
 
511
  }
512
 
513
- }
4
  Plugin URI: http://simplerealtytheme.com
5
  Description: This plugin keeps a log of WordPress user logins. Offers user filtering and export features.
6
  Author: Max Chirkov
7
+ Version: 0.5
8
  Author URI: http://SimpleRealtyTheme.com
9
  */
10
 
 
11
  //TODO: add cleanup method on uninstall
12
 
13
  if( !class_exists( 'SimpleLoginLog' ) )
14
  {
15
  class SimpleLoginLog {
16
+ private $db_ver = "1.1";
17
+ public $table = 'simple_login_log';
18
  private $log_duration = null; //days
19
  private $opt_name = 'simple_login_log';
20
  private $opt = false;
21
+ private $login_success = 1;
22
+ public $data_labels = array();
23
 
24
  function __construct()
25
  {
27
  $this->table = $wpdb->prefix . $this->table;
28
  $this->opt = get_option($this->opt_name);
29
 
30
+ //Get plugin's DB version
31
+ $this->installed_ver = get_option( "sll_db_ver" );
32
+
33
+ //Check if download was initiated
34
+ $download = @esc_attr( $_GET['download-login-log'] );
35
+ if($download)
36
  {
37
+ $where = ( isset($_GET['where']) ) ? $_GET['where'] : false;
38
+ $this->export_to_CSV($where);
39
+ }
40
 
41
 
42
  add_action( 'admin_menu', array(&$this, 'sll_admin_menu') );
43
  add_action('admin_init', array(&$this, 'settings_api_init') );
44
+ add_action('admin_head', array(&$this, 'screen_options') );
45
+
46
+ //check if db needs to be upgraded after plugin update was completed
47
+ add_action('plugins_loaded', array(&$this, 'update_db_check') );
48
 
49
  //Init login actions
50
  add_action( 'init', array(&$this, 'init_login_actions') );
54
 
55
  //Initialize scheduled events
56
  add_action( 'wp', array(&$this, 'init_scheduled_events') );
57
+
58
+ //Load Locale
59
+ add_action('init', array(&$this, 'load_locale'), 10 );
60
+
61
+ //For translation purposes
62
+ $this->data_labels = array(
63
+ 'Successful' => __('Successful', 'sll'),
64
+ 'Failed' => __('Failed', 'sll'),
65
+ 'Login' => __('Login', 'sll'),
66
+ 'User Agent' => __('User Agent', 'sll'),
67
+ 'Login Redirect' => __('Login Redirect', 'sll'),
68
+ 'id' => __('#', 'sll'),
69
+ 'uid' => __('User ID', 'sll'),
70
+ 'user_login' => __('Username', 'sll'),
71
+ 'name' => __('Name', 'sll'),
72
+ 'time' => __('Time', 'sll'),
73
+ 'ip' => __('IP Address', 'sll'),
74
+ 'login_result' => __('Login Result', 'sll'),
75
+ 'data' => __('Data', 'sll'),
76
+ );
77
+
78
+ }
79
+
80
+ function load_locale() {
81
+ $locale = get_locale();
82
+ if( empty( $locale ) )
83
+ $locale = 'en_US';
84
+
85
+ $mofile = dirname( __FILE__ )."/languages/sll-{$locale}.mo";
86
+ load_textdomain( 'sll', $mofile );
87
+ }
88
+
89
+ function screen_options() {
90
+
91
+ //execute only on login_log page, othewise return null
92
+ $page = ( isset($_GET['page']) ) ? esc_attr($_GET['page']) : false;
93
+ if( 'login_log' != $page )
94
+ return;
95
+
96
+ $current_screen = get_current_screen();
97
+
98
+ //define options
99
+ $per_page_field = 'per_page';
100
+ $per_page_option = $current_screen->id . '_' . $per_page_field;
101
+
102
+ //Save options that were applied
103
+ if( isset($_REQUEST['wp_screen_options']) && isset($_REQUEST['wp_screen_options']['value']) )
104
+ {
105
+ update_option( $per_page_option, esc_html($_REQUEST['wp_screen_options']['value']) );
106
+ }
107
+
108
+ //prepare options for display
109
+
110
+ //if per page option is not set, use default
111
+ $per_page_val = get_option($per_page_option, 20);
112
+ $args = array('label' => __('Records', 'sll'), 'default' => $per_page_val );
113
+
114
+ //display options
115
+ add_screen_option($per_page_field, $args);
116
+ $_per_page = get_option('users_page_login_log_per_page');
117
+
118
+ global $_wp_column_headers;
119
+ $_wp_column_headers[ $current_screen->id ] = SLL_List_Table::get_columns();
120
  }
121
 
122
  function init_login_actions(){
165
  }
166
  }
167
 
168
+
169
+ /**
170
+ * Runs via plugin activation hook & creates a database
171
+ */
172
  function install()
173
  {
174
  global $wpdb;
175
+
176
+ if( $this->installed_ver != $this->db_ver )
177
+ {
178
+ //if table does't exist, create a new one
179
+ if( !$wpdb->get_row("SHOW TABLES LIKE '{$this->table}'") ){
180
+ $sql = "CREATE TABLE " . $this->table . "
181
+ (
182
+ id INT( 11 ) NOT NULL AUTO_INCREMENT ,
183
+ uid INT( 11 ) NOT NULL ,
184
+ user_login VARCHAR( 60 ) NOT NULL ,
185
+ time DATETIME DEFAULT '0000-00-00 00:00:00' NOT NULL ,
186
+ ip VARCHAR( 100 ) NOT NULL ,
187
+ login_result VARCHAR (1) ,
188
+ data LONGTEXT NOT NULL ,
189
+ PRIMARY KEY ( id ) ,
190
+ INDEX ( uid, ip, login_result )
191
+ );";
192
+
193
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
194
+ dbDelta($sql);
195
+
196
+ update_option( "sll_db_ver", $this->db_ver );
197
+ }
198
+ }
199
+
200
+
201
+ }
202
+
203
+
204
+ /**
205
+ * Checks if the installed database version is the same as the db version of the current plugin
206
+ * calles the version specific function if upgrade is required
207
+ */
208
+ function update_db_check() {
209
+ if ( get_site_option( 'sll_db_ver' ) != $this->db_ver )
210
+ {
211
+ switch( $this->db_ver )
212
+ {
213
+ case "1.1":
214
+ $this->db_update_1_1();
215
+ break;
216
+ }
217
+ }
218
+ }
219
+
220
+ /**
221
+ * DB version specific updates
222
+ */
223
+ function db_update_1_1()
224
+ {
225
+
226
+ /* this version adds a new field "login_result"
227
+ * check if this field exists
228
+ */
229
+ global $wpdb;
230
+
231
+ $sql = "SELECT * FROM {$this->table}";
232
+ $fields = $wpdb->get_row($sql, 'ARRAY_A');
233
+
234
+ if( !$fields ){
235
+ $this->install();
236
+ return;
237
+ }
238
+
239
+ $field_names = array_keys( $fields );
240
+
241
+ if( !array_search('login_result', $field_names) )
242
+ {
243
+ //add the new field since it doesn't exist
244
+ $sql = "ALTER TABLE {$this->table} ADD COLUMN login_result varchar(1) NOT NULL AFTER ip, ADD INDEX (login_result);";
245
+ $insert = $wpdb->query( $sql );
246
+
247
+ //update version record if it has been updated
248
+ if( false !== $insert )
249
+ update_option( "sll_db_ver", $this->db_ver );
250
+
251
+ }
252
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  }
254
+
255
 
256
  //Initializing Settings
257
  function settings_api_init()
258
  {
259
+ add_settings_section('simple_login_log', __('Simple Login Log', 'sll'), array(&$this, 'sll_settings'), 'general');
260
+ add_settings_field('field_log_duration', __('Truncate Log Entries', 'sll'), array(&$this, 'field_log_duration'), 'general', 'simple_login_log');
261
+ add_settings_field('field_log_failed_attempts', __('Log Failed Attempts', 'sll'), array(&$this, 'field_log_failed_attempts'), 'general', 'simple_login_log');
262
+ register_setting( 'general', 'simple_login_log' );
263
+
264
  }
265
 
266
  function sll_admin_menu()
278
  $duration = (null !== $this->opt['log_duration']) ? $this->opt['log_duration'] : $this->log_duration;
279
  $output = '<input type="text" value="' . $duration . '" name="simple_login_log[log_duration]" size="10" class="code" /> days and older.';
280
  echo $output;
281
+ echo "<p>" . __("Leave empty or enter 0 if you don't want the log to be truncated.", 'sll') . "</p>";
282
  }
283
 
284
  function field_log_failed_attempts(){
285
  $failed_attempts = ( isset($this->opt['failed_attempts']) ) ? $this->opt['failed_attempts'] : false;
286
+ echo '<input type="checkbox" name="simple_login_log[failed_attempts]" value="1" ' . checked( $failed_attempts, 1, false ) . ' /> ' . __('Logs failed attempts where user name and password are entered. Will not log if at least one of the mentioned fields is empty.', 'sll');
287
  }
288
 
289
  function admin_header()
290
  {
291
+ $page = ( isset($_GET['page']) ) ? esc_attr($_GET['page']) : false;
292
+ if( 'login_log' != $page )
293
  return;
294
 
295
  echo '<style type="text/css">';
299
  echo '.wp-list-table .column-name { width: 15%; }';
300
  echo '.wp-list-table .column-time { width: 15%; }';
301
  echo '.wp-list-table .column-ip { width: 10%; }';
302
+ echo '.wp-list-table .column-login_result { width: 10%; }';
303
  echo '.wp-list-table .login-failed { background: #ffd5d1; }';
304
+ echo '</style>';
305
  }
306
 
307
  //Catch messages on successful login
310
  $userdata = get_user_by('login', $user_login);
311
 
312
  $uid = ($userdata->ID) ? $userdata->ID : 0;
 
 
 
 
313
 
314
+ $data[$this->data_labels['Login']] = ( 1 == $this->login_success ) ? $this->data_labels['Successful'] : $this->data_labels['Failed'];
315
+ if ( isset( $_REQUEST['redirect_to'] ) ) { $data[$this->data_labels['Login Redirect']] = $_REQUEST['redirect_to']; }
316
+ $data[$this->data_labels['User Agent']] = $_SERVER['HTTP_USER_AGENT'];
317
 
318
  $serialized_data = serialize($data);
319
 
322
  'user_login' => $user_login,
323
  'time' => current_time('mysql'),
324
  'ip' => $_SERVER['REMOTE_ADDR'],
325
+ 'login_result' => $this->login_success,
326
  'data' => $serialized_data,
327
  );
328
 
329
+ $format = array('%d', '%s', '%s', '%s', '%s', '%s');
330
 
331
  $this->save_data($values, $format);
332
  }
337
  $wpdb->insert( $this->table, $values, $format );
338
  }
339
 
340
+ function make_where_query(){
341
+ $where = false;
 
 
 
 
 
 
 
 
342
  if( isset($_GET['filter']) && '' != $_GET['filter'] )
343
  {
344
+ $where['filter'] = "user_login = '{$_GET['filter']}'";
345
+ }
346
+ if( isset($_GET['result']) && '' != $_GET['result'] )
347
+ {
348
+ $where['result'] = "login_result = '{$_GET['result']}'";
349
  }
350
  if( isset($_GET['datefilter']) && '' != $_GET['datefilter'] )
351
  {
352
  $year = substr($_GET['datefilter'], 0, 4);
353
  $month = substr($_GET['datefilter'], -2);
354
+ $where['datefilter'] = "YEAR(time) = {$year} AND MONTH(time) = {$month}";
355
  }
356
 
357
+ return $where;
358
+ }
359
+
360
+ function log_get_data(){
361
+ global $wpdb;
362
+
363
+ $limit = 20;
364
+ $where = '';
365
+
366
+ $where = $this->make_where_query();
367
+
368
+ if( is_array($where) && !empty($where) )
369
+ $where = 'WHERE ' . implode(' AND ', $where);
370
+
371
+ $sql = "SELECT * FROM $this->table $where ORDER BY time DESC LIMIT $limit";
372
  $data = $wpdb->get_results($sql, 'ARRAY_A');
373
+
374
+ return $data;
375
+ }
376
+
377
+ function log_manager()
378
+ {
379
+
380
+ $log_table = new SLL_List_Table;
381
 
382
+ $log_table->items = $this->log_get_data();
383
+ $log_table->prepare_items();
384
 
385
  echo '<div class="wrap srp">';
386
+ echo '<h2>' . __('Login Log', 'sll') . '</h2>';
387
+ echo '<div class="tablenav top">';
388
+ echo '<div class="alignleft actions">';
389
+ echo $this->date_filter();
390
+ echo '</div>';
391
+
392
+ $username = ( isset($_GET['filter']) ) ? esc_attr($_GET['filter']) : false;
393
+ echo '<form method="get" class="alignright">';
394
+ echo '<p class="search-box">';
395
+ echo '<input type="hidden" name="page" value="login_log" />';
396
+ echo '<label>' . __('Username:', 'sll') . ' </label><input type="text" name="filter" class="filter-username" value="' . $username . '" /> <input class="button" type="submit" value="' . __('Filter User', 'sll') . '" />';
397
+ echo '<br />';
398
+ echo '</p>';
399
+ echo '</form>';
400
+ echo '</div>';
401
+ echo '<div class="tablenav top">';
402
+
403
+ //if log failed attempts is set in the settings, then output views filter
404
+ if( isset($this->opt['failed_attempts']) ){
405
+ echo '<div class="alignleft actions">';
406
+ $log_table->views();
407
+ echo '</div>';
408
+ }
409
+
410
+ echo '<div class="alignright actions">';
411
+ $mode = ( isset($_GET['mode']) ) ? esc_attr($_GET['mode']) : false;
412
+ $log_table->view_switcher($mode);
413
+ echo '</div>';
414
  echo '</div>';
415
+
416
+ $log_table->display();
417
+
418
+ echo '<form method="get" id="export-login-log">';
419
  echo '<input type="hidden" name="page" value="login_log" />';
420
+ echo '<input type="hidden" name="download-login-log" value="true" />';
421
+ submit_button( __('Export Log to CSV', 'sll'), 'secondary' );
422
+ echo '</form>';
423
+ //if filtered results - add export filtered results button
424
+ if( $where = $this->make_where_query() ){
425
+
426
+ echo '<form method="get" id="export-login-log">';
427
+ echo '<input type="hidden" name="page" value="login_log" />';
428
+ echo '<input type="hidden" name="download-login-log" value="true" />';
429
+ echo '<input type="hidden" name="where" value="' . esc_attr(serialize($where)) . '" />';
430
+ submit_button( __('Export Current Results to CSV', 'sll'), 'secondary' );
431
+ echo '</form>';
432
+
433
+ }
434
+
435
+ echo '</div>';
436
  }
437
 
438
  private function date_filter()
439
  {
440
  global $wpdb;
441
+ $sql = "SELECT DISTINCT YEAR(time) as year, MONTH(time)as month FROM {$this->table} ORDER BY YEAR(time), MONTH(time) desc";
442
  $results = $wpdb->get_results($sql);
443
 
444
  if(!$results)
457
 
458
  $output = '<form method="get">';
459
  $output .= '<input type="hidden" name="page" value="login_log" />';
460
+ $output .= '<select name="datefilter"><option value="">' . __('View All', 'sll') . '</option>' . $option . '</select>';
461
+ $output .= '<input class="button" type="submit" value="' . __('Filter', 'sll') . '" />';
462
  $output .= '</form>';
463
  return $output;
464
  }
465
 
466
+ function export_to_CSV($where = false){
467
  global $wpdb;
468
 
469
+ //if $where is set, then contemplate WHERE sql query
470
+ if( $where ){
471
+ $where = unserialize($where);
472
+
473
+ if( is_array($where) && !empty($where) )
474
+ $where = ' WHERE ' . implode(' AND ', $where);
475
+
476
+ }
477
+
478
+ $sql = "SELECT * FROM {$this->table}{$where}";
479
  $data = $wpdb->get_results($sql, 'ARRAY_A');
480
 
481
  if(!$data)
482
  return;
483
 
484
+ //date string to suffix the file nanme: month - day - year - hour - minute
485
+ $suffix = date('n-j-y_H-i');
486
+
487
  // send response headers to the browser
488
  header( 'Content-Type: text/csv' );
489
+ header( 'Content-Disposition: attachment;filename=login_log_' . $suffix . '.csv');
490
  $fp = fopen('php://output', 'w');
491
 
492
  $i = 0;
504
  }
505
 
506
  fclose($fp);
507
+ die();
508
  }
509
 
510
  }
525
  {
526
  function __construct()
527
  {
528
+ global $sll, $_wp_column_headers;
529
 
530
  //Set parent defaults
531
  parent::__construct( array(
533
  'plural' => 'users', //plural name of the listed records
534
  'ajax' => false //does this table support ajax?
535
  ) );
536
+
537
+ $this->data_labels = $sll->data_labels;
538
+
539
+ }
540
 
541
  function column_default($item, $column_name)
542
  {
543
+ $item = apply_filters('sll-output-data', $item);
544
  switch($column_name){
545
  case 'id':
546
  case 'uid':
547
+ case 'time':
548
  case 'ip':
549
+ return $item[$column_name];
550
  case 'user_login':
551
+ //unset existing filter and pagination
552
+ $args = wp_parse_args( parse_url($_SERVER["REQUEST_URI"], PHP_URL_QUERY) );
553
+ unset($args['filter']);
554
+ unset($args['paged']);
555
+ return "<a href='" . add_query_arg( array('filter' => $item[$column_name]), menu_page_url('login_log', false) ) . "' title='" . __('Filter log by this name', 'sll') . "'>{$item[$column_name]}</a>";
556
  case 'name';
557
  $user_info = get_userdata($item['uid']);
558
  return ( is_object($user_info) ) ? $user_info->first_name . " " . $user_info->last_name : false;
559
+ case 'login_result':
560
+ if ( '' == $item[$column_name]) return '';
561
+ return ( '1' == $item[$column_name] ) ? $this->data_labels['Successful'] : '<div class="login-failed">' . $this->data_labels['Failed'] . '</div>';
562
  case 'data':
563
+ $data = unserialize($item[$column_name]);
564
  if(is_array($data))
565
  {
566
  $output = '';
568
  {
569
  $output .= $k .': '. $v .'<br />';
570
  }
571
+
572
+ $output = ( isset($_GET['mode']) && 'excerpt' == $_GET['mode'] ) ? $output : substr($output, 0, 50) . '...';
573
+
574
+ if( isset($data[$this->data_labels['Login']]) && $data[$this->data_labels['Login']] == $this->data_labels['Failed'] ){
575
  return '<div class="login-failed">' . $output . '</div>';
576
  }
577
  return $output;
578
  }
579
  break;
580
  default:
581
+ return $item[$column_name];
582
  }
583
  }
584
 
585
  function get_columns()
586
+ {
587
+ global $status;
588
  $columns = array(
589
+ 'id' => $this->data_labels['id'],
590
+ 'uid' => $this->data_labels['uid'],
591
+ 'user_login' => $this->data_labels['user_login'],
592
+ 'name' => $this->data_labels['name'],
593
+ 'time' => $this->data_labels['time'],
594
+ 'ip' => $this->data_labels['ip'],
595
+ 'login_result' => $this->data_labels['login_result'],
596
+ 'data' => $this->data_labels['data'],
597
+ );
598
  return $columns;
599
  }
600
 
602
  {
603
  $sortable_columns = array(
604
  //'id' => array('id',true), //doesn't sort correctly
605
+ 'uid' => array('uid',false),
606
+ 'user_login' => array('user_login', false),
607
+ 'time' => array('time',true),
608
+ 'ip' => array('ip', false),
609
  );
610
  return $sortable_columns;
611
  }
612
 
613
+ function get_views()
614
+ {
615
+ //creating class="current" variables
616
+ if( !isset($_GET['result']) ){
617
+ $all = 'class="current"';
618
+ $success = '';
619
+ $failed = '';
620
+ }else{
621
+ $all = '';
622
+ $success = ( '1' == $_GET['result'] ) ? 'class="current"' : '';
623
+ $failed = ( '0' == $_GET['result'] ) ? 'class="current"' : '';
624
+ }
625
+
626
+ //get number of successful and failed logins so we can display them in parentheces for each view
627
+ global $wpdb, $sll;
628
+
629
+ //building a WHERE SQL query for each view
630
+ $where = $sll->make_where_query();
631
+ //we only need the date filter, everything else need to be unset
632
+ if( is_array($where) && isset($where['datefilter']) ){
633
+ $where = array( 'datefilter' => $where['datefilter'] );
634
+ }else{
635
+ $where = false;
636
+ }
637
+
638
+ $where3 = $where2 = $where1 = $where;
639
+ $where2['login_result'] = "login_result = '1'";
640
+ $where3['login_result'] = "login_result = '0'";
641
+
642
+ if(is_array($where1) && !empty($where1)){
643
+ $where1 = 'WHERE ' . implode(' AND ', $where1);
644
+ }
645
+ $where2 = 'WHERE ' . implode(' AND ', $where2);
646
+ $where3 = 'WHERE ' . implode(' AND ', $where3);
647
+
648
+ $sql1 = "SELECT * FROM {$sll->table} {$where1}";
649
+ $a = $wpdb->query($sql1);
650
+ $sql2 = "SELECT * FROM {$sll->table} {$where2}";
651
+ $s = $wpdb->query($sql2);
652
+ $sql3 = "SELECT * FROM {$sll->table} {$where3}";
653
+ $f = $wpdb->query($sql3);
654
+
655
+ //if date filter is set, adjust views label to reflect the date
656
+ $date_label = false;
657
+ if( isset($_GET['datefilter']) && !empty($_GET['datefilter']) ){
658
+ $year = substr($_GET['datefilter'], 0, 4);
659
+ $month = substr($_GET['datefilter'], -2);
660
+ $timestamp = mktime(0, 0, 0, $month, 1, $year);
661
+ $date_label = date('F', $timestamp) . ' ' . $year . ' ';
662
+ }
663
+
664
+ //get args from the URL
665
+ $args = wp_parse_args( parse_url($_SERVER["REQUEST_URI"], PHP_URL_QUERY) );
666
+ //the only arguments we can pass are mode and datefilter
667
+ $param = false;
668
+ if( isset($args['mode']) )
669
+ $param['mode'] = $args['mode'];
670
+
671
+ if( isset($args['datefilter']) )
672
+ $param['datefilter'] = $args['datefilter'];
673
+
674
+ //creating base url for the views links
675
+ $menu_page_url = menu_page_url('login_log', false);
676
+ ( is_array($param) && !empty($param) ) ? $url = add_query_arg( $param, $menu_page_url) : $url = $menu_page_url;
677
+
678
+ //definition for views array
679
+ $views = array(
680
+ 'all' => $date_label . __('Login Results', 'sll') . ': <a ' . $all . ' href="' . $url . '">' . __('All', 'sll') . '</a>' . '(' .$a . ')',
681
+ 'success' => '<a ' . $success . ' href="' . $url . '&result=1">' . __('Successful', 'sll') . '</a> (' . $s . ')',
682
+ 'failed' => '<a ' . $failed . ' href="' . $url . '&result=0">' . __('Failed', 'sll') . '</a>' . '(' . $f . ')',
683
+ );
684
+
685
+ return $views;
686
+ }
687
+
688
  function prepare_items()
689
  {
690
+ $screen = get_current_screen();
691
+
692
  /**
693
  * First, lets decide how many records per page to show
694
+ */
695
+ $per_page_option = $screen->id . '_per_page';
696
+ $per_page = get_option($per_page_option, 20);
697
 
698
 
699
  /**
704
  * used to build the value for our _column_headers property.
705
  */
706
  $columns = $this->get_columns();
707
+ $hidden_cols = get_user_option( 'manage' . $screen->id . 'columnshidden' );
708
+ $hidden = ( $hidden_cols ) ? $hidden_cols : array();
709
  $sortable = $this->get_sortable_columns();
710
 
711
 
715
  * 3 other arrays. One for all columns, one for hidden columns, and one
716
  * for sortable columns.
717
  */
718
+ $this->_column_headers = array($columns, $hidden, $sortable);
 
719
 
720
  /**
721
  * Optional. You can handle your bulk actions however you see fit. In this
804
  'per_page' => $per_page, //WE have to determine how many items to show on a page
805
  'total_pages' => ceil($total_items/$per_page) //WE have to calculate the total number of pages
806
  ) );
807
+
808
  }
809
 
810
+ }