WP Security Audit Log - Version 1.2.0

Version Description

(2014-07-2) = * New Features * Unlimited Alerts can be stored (removed the 5000 alerts limit) * Alert time now includes milliseconds for more precision (ideal for auditing and compliance) * Reported alert time is now relative to user's configured timezone * Alerts automatic pruning procedures can now be enabled / disabled * Option to hide WP Security Audit Log from plugins page in WordPress * If there are more than 15 websites in a multisite installation, an auto complete site search box is shown instead of the drop down menu

  • New WordPress Security Alerts
    • Alert 5007: User has uninstalled / deleted a theme
    • Alert 5008: Super administrator network activated a theme on multisite
    • Alert 5009: Super administrator network deactivated a theme on multisite
Download this release

Release Info

Developer WPWhiteSecurity
Plugin Icon 128x128 WP Security Audit Log
Version 1.2.0
Comparing to
See all releases

Code changes from version 1.1.0 to 1.2.0

classes/AbstractSandboxTask.php ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ abstract class WSAL_AbstractSandboxTask {
4
+ public function __construct() {
5
+ // remove time limit and clear output buffers
6
+ set_time_limit(0);
7
+ ob_implicit_flush(true);
8
+ while(ob_get_level())ob_end_flush();
9
+
10
+ // set up shutdown handler
11
+ register_shutdown_function(array($this, 'Shutdown'));
12
+
13
+ // run event sequence
14
+ $this->Header();
15
+ try{
16
+ $this->Execute();
17
+ }catch(Exception $ex){
18
+ $this->Message(get_class($ex) . ' [' . basename($ex->getFile()) . ':' . $ex->getLine() . ']: ' . $ex->getMessage());
19
+ $this->Message($ex->getTraceAsString(), true);
20
+ }
21
+ $this->Footer();
22
+
23
+ // shutdown
24
+ die();
25
+ }
26
+
27
+ protected function Header(){
28
+ echo '<!DOCTYPE html><html><body style="margin: 0; padding: 8px; font: 12px Arial; color: #333;">';
29
+ echo '<div style="position: fixed; top: 0; left: 0; right: 0; padding: 8px; background: #F0F0F0;">';
30
+ echo ' <div id="bar" style=" border-top: 2px solid #0AE; top: 20px; height: 0; width: 0%;"> </div>';
31
+ echo ' <span id="msg"></span> <span id="prg"></span>';
32
+ echo '</div>';
33
+ echo '<div id="msgs" style="font-family: Consolas; margin-top: 30px; white-space: pre;"></div>';
34
+ echo '<script>';
35
+ echo ' var bar = document.getElementById("bar");';
36
+ echo ' var msg = document.getElementById("msg");';
37
+ echo ' var prg = document.getElementById("prg");';
38
+ echo ' var msgs = document.getElementById("msgs");';
39
+ echo '</script>';
40
+ flush();
41
+ }
42
+
43
+ protected function Footer(){
44
+ echo '<div style="display: none;">';
45
+ flush();
46
+ }
47
+
48
+ protected abstract function Execute();
49
+
50
+ public function Shutdown(){
51
+ echo '</div></body></html>';
52
+ flush();
53
+ }
54
+
55
+ protected function Progress($percent){
56
+ echo '<script>bar.style.width=prg.innerHTML="' . number_format($percent, 2) . '%";</script>';
57
+ flush();
58
+ }
59
+
60
+ protected function Message($message, $sticky = false){
61
+ if($sticky){
62
+ echo '<script>msgs.appendChild(document.createTextNode(' . json_encode($message . PHP_EOL) . ')); window.scroll(0, document.body.scrollHeight);</script>';
63
+ }else{
64
+ echo '<script>msg.innerHTML=' . json_encode($message) . ';</script>';
65
+ }
66
+ flush();
67
+ }
68
+ }
classes/AlertManager.php CHANGED
@@ -186,7 +186,7 @@ final class WSAL_AlertManager {
186
  $data['UserAgent'] = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
187
  if(!isset($data['CurrentUserID']))
188
  $data['CurrentUserID'] = function_exists('get_current_user_id') ? get_current_user_id() : 0;
189
- if(!isset($data['CurrentUserRoles']) && is_user_logged_in())
190
  $data['CurrentUserRoles'] = wp_get_current_user()->roles;
191
 
192
  foreach($this->_loggers as $logger)
186
  $data['UserAgent'] = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
187
  if(!isset($data['CurrentUserID']))
188
  $data['CurrentUserID'] = function_exists('get_current_user_id') ? get_current_user_id() : 0;
189
+ if(!isset($data['CurrentUserRoles']) && function_exists('is_user_logged_in') && is_user_logged_in())
190
  $data['CurrentUserRoles'] = wp_get_current_user()->roles;
191
 
192
  foreach($this->_loggers as $logger)
classes/DB/ActiveRecord.php CHANGED
@@ -52,7 +52,7 @@ abstract class WSAL_DB_ActiveRecord {
52
  $sql .= $key . ' BIGINT NOT NULL,'.PHP_EOL;
53
  break;
54
  case is_float($copy->$key):
55
- $sql .= $key . ' FLOAT NOT NULL,'.PHP_EOL;
56
  break;
57
  case is_string($copy->$key):
58
  $sql .= $key . ' TEXT NOT NULL,'.PHP_EOL;
@@ -123,6 +123,7 @@ abstract class WSAL_DB_ActiveRecord {
123
  }
124
 
125
  /**
 
126
  * @return boolean Returns whether table structure is installed or not.
127
  */
128
  public function IsInstalled(){
@@ -135,20 +136,16 @@ abstract class WSAL_DB_ActiveRecord {
135
  * Install this ActiveRecord structure into DB.
136
  */
137
  public function Install(){
138
- if(!$this->IsInstalled()) {
139
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
140
- dbDelta($this->_GetInstallQuery());
141
- }
142
  }
143
 
144
  /**
145
  * Remove this ActiveRecord structure into DB.
146
  */
147
  public function Uninstall(){
148
- if($this->IsInstalled()) {
149
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
150
- dbDelta($this->_GetUninstallQuery());
151
- }
152
  }
153
 
154
  /**
@@ -269,7 +266,10 @@ abstract class WSAL_DB_ActiveRecord {
269
  $class = get_called_class();
270
  $result = array();
271
  $temp = new $class();
272
- $sql = $wpdb->prepare('SELECT * FROM ' . $temp->GetTable() . ' WHERE '.$cond, $args);
 
 
 
273
  foreach($wpdb->get_results($sql, ARRAY_A) as $data){
274
  $result[] = new $class($data);
275
  }
52
  $sql .= $key . ' BIGINT NOT NULL,'.PHP_EOL;
53
  break;
54
  case is_float($copy->$key):
55
+ $sql .= $key . ' DOUBLE NOT NULL,'.PHP_EOL;
56
  break;
57
  case is_string($copy->$key):
58
  $sql .= $key . ' TEXT NOT NULL,'.PHP_EOL;
123
  }
124
 
125
  /**
126
+ * @deprecated
127
  * @return boolean Returns whether table structure is installed or not.
128
  */
129
  public function IsInstalled(){
136
  * Install this ActiveRecord structure into DB.
137
  */
138
  public function Install(){
139
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
140
+ dbDelta($this->_GetInstallQuery());
 
 
141
  }
142
 
143
  /**
144
  * Remove this ActiveRecord structure into DB.
145
  */
146
  public function Uninstall(){
147
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
148
+ dbDelta($this->_GetUninstallQuery());
 
 
149
  }
150
 
151
  /**
266
  $class = get_called_class();
267
  $result = array();
268
  $temp = new $class();
269
+ $sql = (!is_array($args) || !count($args)) // do we really need to prepare() or not?
270
+ ? ('SELECT * FROM ' . $temp->GetTable() . ' WHERE ' . $cond)
271
+ : $wpdb->prepare('SELECT * FROM ' . $temp->GetTable() . ' WHERE ' . $cond, $args)
272
+ ;
273
  foreach($wpdb->get_results($sql, ARRAY_A) as $data){
274
  $result[] = new $class($data);
275
  }
classes/DB/Occurrence.php CHANGED
@@ -7,7 +7,7 @@ class WSAL_DB_Occurrence extends WSAL_DB_ActiveRecord {
7
  public $id = 0;
8
  public $site_id = 0;
9
  public $alert_id = 0;
10
- public $created_on = 0;
11
  public $is_read = false;
12
  public $is_migrated = false;
13
 
@@ -222,4 +222,19 @@ class WSAL_DB_Occurrence extends WSAL_DB_ActiveRecord {
222
  return $this->GetMetaValue('CurrentUserRoles', array());
223
  }
224
 
225
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  public $id = 0;
8
  public $site_id = 0;
9
  public $alert_id = 0;
10
+ public $created_on = 0.0;
11
  public $is_read = false;
12
  public $is_migrated = false;
13
 
222
  return $this->GetMetaValue('CurrentUserRoles', array());
223
  }
224
 
225
+ /**
226
+ * @return float Number of seconds (and microseconds as fraction) since unix Day 0.
227
+ * @todo This needs some caching.
228
+ */
229
+ protected function GetMicrotime(){
230
+ return microtime(true);// + get_option('gmt_offset') * HOUR_IN_SECONDS;
231
+ }
232
+
233
+ public function Save(){
234
+ // use today's date if not set up
235
+ if(is_null($this->created_on))
236
+ $this->created_on = $this->GetMicrotime();
237
+
238
+ return parent::Save();
239
+ }
240
+ }
classes/Loggers/Database.php CHANGED
@@ -8,10 +8,6 @@ class WSAL_Loggers_Database extends WSAL_AbstractLogger {
8
  }
9
 
10
  public function Log($type, $data = array(), $date = null, $siteid = null, $migrated = false) {
11
-
12
- // use today's date if not set up
13
- if(is_null($date))$date = current_time('timestamp');
14
-
15
  // create new occurrence
16
  $occ = new WSAL_DB_Occurrence();
17
  $occ->is_migrated = $migrated;
@@ -34,8 +30,8 @@ class WSAL_Loggers_Database extends WSAL_AbstractLogger {
34
  if($cnt_items == $max_count)return;
35
  $max_items = max(($cnt_items - $max_count) + 1, 0);
36
 
37
- $is_date_e = true;
38
- $is_limt_e = true;
39
 
40
  switch(true){
41
  case $is_date_e && $is_limt_e:
@@ -50,6 +46,8 @@ class WSAL_Loggers_Database extends WSAL_AbstractLogger {
50
  $cond = '1 ORDER BY created_on ASC LIMIT %d';
51
  $args = array($max_items);
52
  break;
 
 
53
  }
54
  if(!isset($cond))return;
55
 
8
  }
9
 
10
  public function Log($type, $data = array(), $date = null, $siteid = null, $migrated = false) {
 
 
 
 
11
  // create new occurrence
12
  $occ = new WSAL_DB_Occurrence();
13
  $occ->is_migrated = $migrated;
30
  if($cnt_items == $max_count)return;
31
  $max_items = max(($cnt_items - $max_count) + 1, 0);
32
 
33
+ $is_date_e = $this->plugin->settings->IsPruningDateEnabled();
34
+ $is_limt_e = $this->plugin->settings->IsPruningLimitEnabled();
35
 
36
  switch(true){
37
  case $is_date_e && $is_limt_e:
46
  $cond = '1 ORDER BY created_on ASC LIMIT %d';
47
  $args = array($max_items);
48
  break;
49
+ case !$is_date_e && !$is_limt_e:
50
+ return;
51
  }
52
  if(!isset($cond))return;
53
 
classes/Nicer.php CHANGED
@@ -1,186 +1,289 @@
1
  <?php
2
-
3
- /**
4
- * Inspects and prints out PHP values as HTML in a nicer way than print_r().
5
- * @author Christian Sciberras <christian@sciberras.me>
6
- * @copyright (c) 2013, Christian Sciberras
7
- * @license https://raw.github.com/uuf6429/nice_r/master/LICENSE MIT License
8
- * @link https://github.com/uuf6429/nice_r GitHub Repository
9
- * @version 2.0
10
- * @since 2.0
11
- */
12
- class WSAL_Nicer {
13
- protected $value;
14
-
15
- /**
16
- * Allows modification of CSS class prefix.
17
- * @var string
18
- */
19
- public $css_class = 'nice_r';
20
-
21
- /**
22
- * Allows modification of HTML id prefix.
23
- * @var string
24
- */
25
- public $html_id = 'nice_r_v';
26
-
27
- /**
28
- * Allows modification of JS function used to toggle sections.
29
- * @var string
30
- */
31
- public $js_func = 'nice_r_toggle';
32
-
33
- /**
34
- * Since PHP does not support private constants, we'll have to settle for private static fields.
35
- * @var string
36
- */
37
- protected static $BEEN_THERE = '__NICE_R_INFINITE_RECURSION_PROTECT__';
38
-
39
- /**
40
- * Constructs new renderer instance.
41
- * @param mixed $value The value to inspect and render.
42
- */
43
- public function __construct($value){
44
- $this->value = $value;
45
- }
46
-
47
- /**
48
- * Generates the inspector HTML and returns it as a string.
49
- * @return string Generated HTML.
50
- */
51
- public function generate(){
52
- return $this->_generate_value($this->value, $this->css_class);
53
- }
54
-
55
- /**
56
- * Renders the inspector HTML directly to the browser.
57
- */
58
- public function render(){
59
- echo $this->generate();
60
- }
61
-
62
- /**
63
- * Converts a string to HTML, encoding any special characters.
64
- * @param string $text The original string.
65
- * @return string The string as HTML.
66
- */
67
- protected function _esc_html($text){
68
- return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
69
- }
70
-
71
- /**
72
- * Render a single particular value.
73
- * @param mixed $var The value to render
74
- * @param string $class Parent CSS class.
75
- * @param string $id Item HTML id.
76
- */
77
- protected function _generate_value($var, $class = '', $id = ''){
78
- $BEENTHERE = self::$BEEN_THERE;
79
- $class .= ' '.$this->css_class.'_t_'.gettype($var);
80
-
81
- $html = '<div id="'.$id.'" class="'.$class.'">';
82
-
83
- switch(true){
84
-
85
- // handle arrays
86
- case is_array($var):
87
- if(isset($var[$BEENTHERE])){
88
- $html .= '<span class="'.$this->css_class.'_ir">Infinite Recursion Detected!</span>';
89
- }else{
90
- $var[$BEENTHERE] = true;
91
- $has_subitems = false;
92
- foreach($var as $k=>$v){
93
- if($k!==$BEENTHERE){
94
- $html .= $this->_generate_keyvalue($k, $v);
95
- $has_subitems = true;
96
- }
97
- }
98
- if(!$has_subitems){
99
- $html .= '<span class="'.$this->css_class.'_ni">Empty Array</span>';
100
- }
101
- unset($var[$BEENTHERE]);
102
- }
103
- break;
104
-
105
- // handle objects
106
- case is_object($var):
107
- if(isset($var->$BEENTHERE)){
108
- $html .= '<span class="'.$this->css_class.'_ir">Infinite Recursion Detected!</span>';
109
- }else{
110
- $var->$BEENTHERE = true;
111
- $has_subitems = false;
112
- foreach((array)$var as $k=>$v){
113
- if($k!==$BEENTHERE){
114
- $html .= $this->_generate_keyvalue($k, $v);
115
- $has_subitems = true;
116
- }
117
- }
118
- if(!$has_subitems){
119
- $html .= '<span class="'.$this->css_class.'_ni">No Properties</span>';
120
- }
121
- unset($var->$BEENTHERE);
122
- }
123
- break;
124
-
125
- // handle simple types
126
- default:
127
- $html .= $this->_generate_keyvalue('', $var);
128
- break;
129
- }
130
-
131
- return $html . '</div>';
132
- }
133
-
134
- /**
135
- * Render a key-value pair.
136
- * @staticvar int $id Specifies element id.
137
- * @param string $key Key name.
138
- * @param mixed $val Key value.
139
- */
140
- protected function _generate_keyvalue($key, $val){
141
- static $id = 0; $id++; // unique (per rquest) id
142
- $p = ''; // preview
143
- $d = ''; // description
144
- $t = gettype($val); // get data type
145
- $is_hash = ($t=='array') || ($t=='object');
146
-
147
- switch($t){
148
- case 'boolean':
149
- $p = $val ? 'TRUE' : 'FALSE';
150
- break;
151
- case 'integer':
152
- case 'double':
153
- $p = (string)$val;
154
- break;
155
- case 'string':
156
- $d .= ', '.strlen($val).' characters';
157
- $p = $val;
158
- break;
159
- case 'resource':
160
- $d .= ', '.get_resource_type($val).' type';
161
- $p = (string)$val;
162
- break;
163
- case 'array':
164
- $d .= ', '.count($val).' elements';
165
- break;
166
- case 'object':
167
- $d .= ', '.get_class($val).', '.count(get_object_vars($val)).' properties';
168
- break;
169
- }
170
-
171
- $cls = $this->css_class;
172
- $xcls = !$is_hash ? $cls.'_ad' : '';
173
- $html = '<a href="javascript:;" onclick="'.$this->js_func.'(\''.$this->html_id.'\',\''.$id.'\');">';
174
- $html .= ' <span class="'.$cls.'_a '.$xcls.'" id="'.$this->html_id.'_a'.$id.'">&#9658;</span>';
175
- $html .= ' <span class="'.$cls.'_k">'.$this->_esc_html($key).'</span>';
176
- $html .= ' <span class="'.$cls.'_d">(<span>'.ucwords($t).'</span>'.$d.')</span>';
177
- $html .= ' <span class="'.$cls.'_p '.$cls.'_t_'.$t.'">'.$this->_esc_html($p).'</span>';
178
- $html .= '</a>';
179
-
180
- if($is_hash){
181
- $html .= $this->_generate_value($val, $cls.'_v', $this->html_id.'_v'.$id);
182
- }
183
-
184
- return $html;
185
- }
186
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <?php
2
+
3
+ /**
4
+ * Inspects and prints out PHP values as HTML in a nicer way than print_r().
5
+ * @author Christian Sciberras <christian@sciberras.me>
6
+ * @copyright (c) 2013, Christian Sciberras
7
+ * @license https://raw.github.com/uuf6429/nice_r/master/LICENSE MIT License
8
+ * @link https://github.com/uuf6429/nice_r GitHub Repository
9
+ * @version 2.0
10
+ * @since 2.0
11
+ */
12
+ class WSAL_Nicer {
13
+ protected $value;
14
+
15
+ /**
16
+ * Allows modification of CSS class prefix.
17
+ * @var string
18
+ */
19
+ public $css_class = 'nice_r';
20
+
21
+ /**
22
+ * Allows modification of HTML id prefix.
23
+ * @var string
24
+ */
25
+ public $html_id = 'nice_r_v';
26
+
27
+ /**
28
+ * Allows modification of JS function used to toggle sections.
29
+ * @var string
30
+ */
31
+ public $js_func = 'nice_r_toggle';
32
+
33
+ /**
34
+ * Whether to inspect and output methods for objects or not.
35
+ * @var boolean
36
+ */
37
+ public $inspect_methods = false;
38
+
39
+ /**
40
+ * Since PHP does not support private constants, we'll have to settle for private static fields.
41
+ * @var string
42
+ */
43
+ protected static $BEEN_THERE = '__NICE_R_INFINITE_RECURSION_PROTECT__';
44
+
45
+ protected $_has_reflection = null;
46
+
47
+ /**
48
+ * Constructs new renderer instance.
49
+ * @param mixed $value The value to inspect and render.
50
+ * @param boolean $inspectMethods Whether to inspect and output methods for objects or not.
51
+ */
52
+ public function __construct($value, $inspectMethods = false){
53
+ $this->value = $value;
54
+ $this->inspect_methods = $inspectMethods;
55
+
56
+ if(is_null($this->_has_reflection))
57
+ $this->_has_reflection = class_exists('ReflectionClass');
58
+ }
59
+
60
+ /**
61
+ * Generates the inspector HTML and returns it as a string.
62
+ * @return string Generated HTML.
63
+ */
64
+ public function generate(){
65
+ return $this->_generate_value($this->value, $this->css_class);
66
+ }
67
+
68
+ /**
69
+ * Renders the inspector HTML directly to the browser.
70
+ */
71
+ public function render(){
72
+ echo $this->generate();
73
+ }
74
+
75
+ /**
76
+ * Converts a string to HTML, encoding any special characters.
77
+ * @param string $text The original string.
78
+ * @return string The string as HTML.
79
+ */
80
+ protected function _esc_html($text){
81
+ return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
82
+ }
83
+
84
+ protected function _inspect_array(&$html, &$var){
85
+ $has_subitems = false;
86
+
87
+ foreach($var as $k => $v){
88
+ if($k !== self::$BEEN_THERE){
89
+ $html .= $this->_generate_keyvalue($k, $v);
90
+ $has_subitems = true;
91
+ }
92
+ }
93
+
94
+ if(!$has_subitems){
95
+ $html .= '<span class="'.$this->css_class.'_ni">Empty Array</span>';
96
+ }
97
+ }
98
+
99
+ protected function _inspect_object(&$html, &$var){
100
+ // render properties
101
+ $has_subitems = false;
102
+
103
+ foreach((array)$var as $k=>$v){
104
+ if($k !== self::$BEEN_THERE){
105
+ $html .= $this->_generate_keyvalue($k, $v);
106
+ $has_subitems = true;
107
+ }
108
+ }
109
+
110
+ if(!$has_subitems){
111
+ $html .= '<span class="'.$this->css_class.'_ni">No Properties</span>';
112
+ }
113
+
114
+ // render methods (if enabled)
115
+ if($this->inspect_methods){
116
+ $has_subitems = false;
117
+
118
+ foreach((array)get_class_methods($var) as $method){
119
+ $html .= $this->_generate_callable($var, $method);
120
+ $has_subitems = true;
121
+ }
122
+
123
+ if(!$has_subitems){
124
+ $html .= '<span class="'.$this->css_class.'_ni">No Methods</span>';
125
+ }
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Render a single particular value.
131
+ * @param mixed $var The value to render
132
+ * @param string $class Parent CSS class.
133
+ * @param string $id Item HTML id.
134
+ */
135
+ protected function _generate_value($var, $class = '', $id = ''){
136
+ $BEENTHERE = self::$BEEN_THERE;
137
+ $class .= ' '.$this->css_class.'_t_'.gettype($var);
138
+
139
+ $html = '<div id="'.$id.'" class="'.$class.'">';
140
+
141
+ switch(true){
142
+
143
+ // handle arrays
144
+ case is_array($var):
145
+ if(isset($var[$BEENTHERE])){
146
+ $html .= '<span class="'.$this->css_class.'_ir">Infinite Recursion Detected!</span>';
147
+ }else{
148
+ $var[$BEENTHERE] = true;
149
+ $this->_inspect_array($html, $var);
150
+ unset($var[$BEENTHERE]);
151
+ }
152
+ break;
153
+
154
+ // handle objects
155
+ case is_object($var):
156
+ if(isset($var->$BEENTHERE)){
157
+ $html .= '<span class="'.$this->css_class.'_ir">Infinite Recursion Detected!</span>';
158
+ }else{
159
+ $var->$BEENTHERE = true;
160
+ $this->_inspect_object($html, $var);
161
+ unset($var->$BEENTHERE);
162
+ }
163
+ break;
164
+
165
+ // handle simple types
166
+ default:
167
+ $html .= $this->_generate_keyvalue('', $var);
168
+ break;
169
+ }
170
+
171
+ return $html . '</div>';
172
+ }
173
+
174
+ /**
175
+ * Generates a new unique ID for tagging elements.
176
+ * @staticvar int $id
177
+ * @return integer An ID unique per request.
178
+ */
179
+ protected function _generate_dropid(){
180
+ static $id = 0;
181
+ return ++$id;
182
+ }
183
+
184
+ /**
185
+ * Render a key-value pair.
186
+ * @staticvar int $id Specifies element id.
187
+ * @param string $key Key name.
188
+ * @param mixed $val Key value.
189
+ */
190
+ protected function _generate_keyvalue($key, $val){
191
+ $id = $this->_generate_dropid();
192
+ $p = ''; // preview
193
+ $d = ''; // description
194
+ $t = gettype($val); // get data type
195
+ $is_hash = ($t=='array') || ($t=='object');
196
+
197
+ switch($t){
198
+ case 'boolean':
199
+ $p = $val ? 'TRUE' : 'FALSE';
200
+ break;
201
+ case 'integer':
202
+ case 'double':
203
+ $p = (string)$val;
204
+ break;
205
+ case 'string':
206
+ $d .= ', '.strlen($val).' characters';
207
+ $p = $val;
208
+ break;
209
+ case 'resource':
210
+ $d .= ', '.get_resource_type($val).' type';
211
+ $p = (string)$val;
212
+ break;
213
+ case 'array':
214
+ $d .= ', '.count($val).' elements';
215
+ break;
216
+ case 'object':
217
+ $d .= ', '.get_class($val).', '.count(get_object_vars($val)).' properties';
218
+ break;
219
+ }
220
+
221
+ $cls = $this->css_class;
222
+ $xcls = !$is_hash ? $cls.'_ad' : '';
223
+ $html = '<a '.($is_hash?'href="javascript:;"':'').' onclick="'.$this->js_func.'(\''.$this->html_id.'\',\''.$id.'\');">';
224
+ $html .= ' <span class="'.$cls.'_a '.$xcls.'" id="'.$this->html_id.'_a'.$id.'">&#9658;</span>';
225
+ $html .= ' <span class="'.$cls.'_k">'.$this->_esc_html($key).'</span>';
226
+ $html .= ' <span class="'.$cls.'_d">(<span>'.ucwords($t).'</span>'.$d.')</span>';
227
+ $html .= ' <span class="'.$cls.'_p '.$cls.'_t_'.$t.'">'.$this->_esc_html($p).'</span>';
228
+ $html .= '</a>';
229
+
230
+ if($is_hash){
231
+ $html .= $this->_generate_value($val, $cls.'_v', $this->html_id.'_v'.$id);
232
+ }
233
+
234
+ return $html;
235
+ }
236
+
237
+ protected function _generate_callable($context, $callback){
238
+ $id = $this->_generate_dropid();
239
+ $ref = null;
240
+ $name = 'Anonymous';
241
+ $cls = $this->css_class;
242
+
243
+ if($this->_has_reflection){
244
+ if(is_null($context)){
245
+ $ref = new ReflectionFunction($callback);
246
+ }else{
247
+ $ref = new ReflectionMethod($context, $callback);
248
+ }
249
+ $name = $ref->getName();
250
+ }elseif(is_string($callback)){
251
+ $name = $callback;
252
+ }
253
+
254
+ if(!is_null($ref)){
255
+ $doc = $ref->getDocComment();
256
+ $prms = array();
257
+ foreach($ref->getParameters() as $p){
258
+ $prms[] = '$' . $p->getName() . (
259
+ $p->isDefaultValueAvailable()
260
+ ? (
261
+ ' = <span class="'.$cls.'_mv">' . (
262
+ $p->isDefaultValueConstant()
263
+ ? $p->getDefaultValueConstantName()
264
+ : var_export($p->getDefaultValue(), true)
265
+ ) . '</span>'
266
+ )
267
+ : ''
268
+ );
269
+ }
270
+ }else{
271
+ $doc = null;
272
+ $prms = array('???');
273
+ }
274
+
275
+ $xcls = !$doc ? $cls.'_ad' : '';
276
+ $html = '<a class="'.$cls.'_c" '.($doc?'href="javascript:;"':'').' onclick="'.$this->js_func.'(\''.$this->html_id.'\',\''.$id.'\');">';
277
+ $html .= ' <span class="'.$cls.'_a '.$xcls.'">&#9658;</span>';
278
+ $html .= ' <span class="'.$cls.'_k">'.$this->_esc_html($name).'<span class="'.$cls.'_ma">(<span>'.implode(', ', $prms).'</span>)</span></span>';
279
+ $html .= '</a>';
280
+
281
+ if($doc){
282
+ $html .= '<div id="'.$this->html_id.'_v'.$id.'" class="nice_r_v '.$this->css_class.'_t_comment">';
283
+ $html .= nl2br(str_replace(' ', '&nbsp;', $this->_esc_html($doc)));
284
+ $html .= '</div>';
285
+ }
286
+
287
+ return $html;
288
+ }
289
+ }
classes/Sensors/LogInOut.php CHANGED
@@ -53,7 +53,7 @@ class WSAL_Sensors_LogInOut extends WSAL_AbstractSensor {
53
  $occ->SetMetaValue('Attempts',
54
  $occ->GetMetaValue('Attempts', 0) + 1
55
  );
56
- $occ->created_on = current_time('timestamp');
57
  $occ->Save();
58
  }else{
59
  // create a new record
53
  $occ->SetMetaValue('Attempts',
54
  $occ->GetMetaValue('Attempts', 0) + 1
55
  );
56
+ $occ->created_on = null;
57
  $occ->Save();
58
  }else{
59
  // create a new record
classes/Sensors/Multisite.php CHANGED
@@ -3,14 +3,60 @@
3
  class WSAL_Sensors_Multisite extends WSAL_AbstractSensor {
4
 
5
  public function HookEvents() {
6
- add_action('wpmu_new_blog', array($this, 'EventNewBlog'), 10, 1);
7
- add_action('archive_blog', array($this, 'EventArchiveBlog'));
8
- add_action('unarchive_blog', array($this, 'EventUnarchiveBlog'));
9
- add_action('activate_blog', array($this, 'EventActivateBlog'));
10
- add_action('deactivate_blog', array($this, 'EventDeactivateBlog'));
11
- add_action('delete_blog', array($this, 'EventDeleteBlog'));
12
- add_action('add_user_to_blog', array($this, 'EventUserAddedToBlog'), 10, 3);
13
- add_action('remove_user_from_blog', array($this, 'EventUserRemovedFromBlog'));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  }
15
 
16
  public function EventNewBlog($blog_id){
3
  class WSAL_Sensors_Multisite extends WSAL_AbstractSensor {
4
 
5
  public function HookEvents() {
6
+ if($this->plugin->IsMultisite()){
7
+ add_action('admin_init', array($this, 'EventAdminInit'));
8
+ if(is_admin())add_action('shutdown', array($this, 'EventAdminShutdown'));
9
+ add_action('wpmu_new_blog', array($this, 'EventNewBlog'), 10, 1);
10
+ add_action('archive_blog', array($this, 'EventArchiveBlog'));
11
+ add_action('unarchive_blog', array($this, 'EventUnarchiveBlog'));
12
+ add_action('activate_blog', array($this, 'EventActivateBlog'));
13
+ add_action('deactivate_blog', array($this, 'EventDeactivateBlog'));
14
+ add_action('delete_blog', array($this, 'EventDeleteBlog'));
15
+ add_action('add_user_to_blog', array($this, 'EventUserAddedToBlog'), 10, 3);
16
+ add_action('remove_user_from_blog', array($this, 'EventUserRemovedFromBlog'));
17
+ }
18
+ }
19
+
20
+ protected $old_allowedthemes;
21
+
22
+ public function EventAdminInit(){
23
+ $this->old_allowedthemes = array_keys(get_site_option('allowedthemes'));
24
+ }
25
+
26
+ public function EventAdminShutdown(){
27
+ $new_allowedthemes = array_keys(get_site_option('allowedthemes'));
28
+
29
+ // check for enabled themes
30
+ foreach($new_allowedthemes as $theme)
31
+ if(!in_array($theme, $this->old_allowedthemes)){
32
+ $theme = wp_get_theme($theme);
33
+ $this->plugin->alerts->Trigger(5008, array(
34
+ 'Theme' => (object)array(
35
+ 'Name' => $theme->Name,
36
+ 'ThemeURI' => $theme->ThemeURI,
37
+ 'Description' => $theme->Description,
38
+ 'Author' => $theme->Author,
39
+ 'Version' => $theme->Version,
40
+ 'get_template_directory' => $theme->get_template_directory(),
41
+ ),
42
+ ));
43
+ }
44
+
45
+ // check for disabled themes
46
+ foreach($this->old_allowedthemes as $theme)
47
+ if(!in_array($theme, $new_allowedthemes)){
48
+ $theme = wp_get_theme($theme);
49
+ $this->plugin->alerts->Trigger(5009, array(
50
+ 'Theme' => (object)array(
51
+ 'Name' => $theme->Name,
52
+ 'ThemeURI' => $theme->ThemeURI,
53
+ 'Description' => $theme->Description,
54
+ 'Author' => $theme->Author,
55
+ 'Version' => $theme->Version,
56
+ 'get_template_directory' => $theme->get_template_directory(),
57
+ ),
58
+ ));
59
+ }
60
  }
61
 
62
  public function EventNewBlog($blog_id){
classes/Sensors/PhpErrors.php CHANGED
@@ -27,12 +27,17 @@ class WSAL_Sensors_PhpErrors extends WSAL_AbstractSensor {
27
  public function EventError($errno, $errstr, $errfile = 'unknown', $errline = 0, $errcontext = array()){
28
  if($this->_avoid_error_recursion)return;
29
 
 
 
 
 
30
  $data = array(
31
  'Code' => $errno,
32
  'Message' => $errstr,
33
  'File' => $errfile,
34
  'Line' => $errline,
35
  'Context' => $errcontext,
 
36
  );
37
 
38
  $type = 0002; // default (middle ground)
27
  public function EventError($errno, $errstr, $errfile = 'unknown', $errline = 0, $errcontext = array()){
28
  if($this->_avoid_error_recursion)return;
29
 
30
+ ob_start();
31
+ debug_print_backtrace();
32
+ $errbacktrace = ob_get_clean();
33
+
34
  $data = array(
35
  'Code' => $errno,
36
  'Message' => $errstr,
37
  'File' => $errfile,
38
  'Line' => $errline,
39
  'Context' => $errcontext,
40
+ 'Trace' => $errbacktrace,
41
  );
42
 
43
  $type = 0002; // default (middle ground)
classes/Sensors/PluginsThemes.php CHANGED
@@ -25,24 +25,24 @@ class WSAL_Sensors_PluginsThemes extends WSAL_AbstractSensor {
25
 
26
  // install plugin
27
  if(($action=='install-plugin' || $action=='upload-plugin')){
28
- $newPlugin = array_values(array_diff(array_keys(get_plugins()), array_keys($this->old_plugins)));
29
- if(count($newPlugin) != 1)
30
  return $this->LogError(
31
- 'Expected exactly one new plugin but found ' . count($newPlugin),
32
- array('NewPlugin' => $newPlugin, 'OldPlugins' => $this->old_plugins, 'NewPlugins' => get_plugins())
33
  );
34
- $newPluginPath = $newPlugin[0];
35
- $newPlugin = get_plugins();
36
- $newPlugin = $newPlugin[$newPluginPath];
37
- $newPluginPath = plugin_dir_path(WP_PLUGIN_DIR . '/' . $newPluginPath[0]);
38
  $this->plugin->alerts->Trigger(5000, array(
39
- 'NewPlugin' => (object)array(
40
- 'Name' => $newPlugin['Name'],
41
- 'PluginURI' => $newPlugin['PluginURI'],
42
- 'Version' => $newPlugin['Version'],
43
- 'Author' => $newPlugin['Author'],
44
- 'Network' => $newPlugin['Network'] ? 'True' : 'False',
45
- 'plugin_dir_path' => $newPluginPath,
46
  ),
47
  ));
48
  }
@@ -96,7 +96,6 @@ class WSAL_Sensors_PluginsThemes extends WSAL_AbstractSensor {
96
  // uninstall plugin
97
  if($is_plugins && in_array($action, array('delete-selected'))){
98
  if(!isset($_REQUEST['verify-delete'])){
99
-
100
  // first step, before user approves deletion
101
  // TODO store plugin data in session here
102
  }else{
@@ -114,7 +113,6 @@ class WSAL_Sensors_PluginsThemes extends WSAL_AbstractSensor {
114
  ),
115
  ));
116
  }
117
-
118
  }
119
  }
120
 
@@ -147,66 +145,70 @@ class WSAL_Sensors_PluginsThemes extends WSAL_AbstractSensor {
147
  }
148
 
149
  // install theme
150
- if($action=='install-theme' || $action=='upload-theme'){
151
- $newTheme = array_diff(wp_get_themes(), $this->old_themes);
152
- if(count($newTheme) != 1)
153
- return $this->LogError(
154
- 'Expected exactly one new theme but found ' . count($newTheme),
155
- array('OldThemes' => $this->old_themes, 'NewThemes' => wp_get_themes())
156
- );
157
- $newTheme = array_shift($newTheme);
158
- $this->plugin->alerts->Trigger(5005, array(
159
- 'NewTheme' => (object)array(
160
- 'Name' => $newTheme->Name,
161
- 'ThemeURI' => $newTheme->ThemeURI,
162
- 'Description' => $newTheme->Description,
163
- 'Author' => $newTheme->Author,
164
- 'Version' => $newTheme->Version,
165
- 'get_template_directory' => $newTheme->get_template_directory(),
166
- ),
167
- ));
168
  }
169
 
170
  // uninstall theme
171
- if($is_themes && in_array($action, array('delete-selected'))){
172
- if(!isset($_REQUEST['verify-delete'])){
173
-
174
- // first step, before user approves deletion
175
- // TODO store plugin data in session here
176
- }else{
177
- // second step, after deletion approval
178
- // TODO use plugin data from session
179
- /*foreach($_REQUEST['checked'] as $themeFile){
180
-
181
- }*/
182
-
183
  }
184
  }
185
  }
186
 
187
  public function EventThemeActivated($themeName){
188
- $newTheme = null;
189
- foreach(wp_get_themes() as $theme){
190
- if($theme->Name == $themeName){
191
- $newTheme = $theme;
192
  break;
193
  }
194
  }
195
- if($newTheme == null)
196
  return $this->LogError(
197
- 'Could not locate theme named "'.$newTheme.'".',
198
  array('ThemeName' => $themeName, 'Themes' => wp_get_themes())
199
  );
200
  $this->plugin->alerts->Trigger(5006, array(
201
- 'NewTheme' => (object)array(
202
- 'Name' => $newTheme->Name,
203
- 'ThemeURI' => $newTheme->ThemeURI,
204
- 'Description' => $newTheme->Description,
205
- 'Author' => $newTheme->Author,
206
- 'Version' => $newTheme->Version,
207
- 'get_template_directory' => $newTheme->get_template_directory(),
208
  ),
209
  ));
210
  }
211
 
 
 
 
 
 
 
 
 
212
  }
25
 
26
  // install plugin
27
  if(($action=='install-plugin' || $action=='upload-plugin')){
28
+ $plugin = array_values(array_diff(array_keys(get_plugins()), array_keys($this->old_plugins)));
29
+ if(count($plugin) != 1)
30
  return $this->LogError(
31
+ 'Expected exactly one new plugin but found ' . count($plugin),
32
+ array('NewPlugin' => $plugin, 'OldPlugins' => $this->old_plugins, 'NewPlugins' => get_plugins())
33
  );
34
+ $pluginPath = $plugin[0];
35
+ $plugin = get_plugins();
36
+ $plugin = $plugin[$pluginPath];
37
+ $pluginPath = plugin_dir_path(WP_PLUGIN_DIR . '/' . $pluginPath[0]);
38
  $this->plugin->alerts->Trigger(5000, array(
39
+ 'Plugin' => (object)array(
40
+ 'Name' => $plugin['Name'],
41
+ 'PluginURI' => $plugin['PluginURI'],
42
+ 'Version' => $plugin['Version'],
43
+ 'Author' => $plugin['Author'],
44
+ 'Network' => $plugin['Network'] ? 'True' : 'False',
45
+ 'plugin_dir_path' => $pluginPath,
46
  ),
47
  ));
48
  }
96
  // uninstall plugin
97
  if($is_plugins && in_array($action, array('delete-selected'))){
98
  if(!isset($_REQUEST['verify-delete'])){
 
99
  // first step, before user approves deletion
100
  // TODO store plugin data in session here
101
  }else{
113
  ),
114
  ));
115
  }
 
116
  }
117
  }
118
 
145
  }
146
 
147
  // install theme
148
+ if(in_array($action, array('install-theme', 'upload-theme'))){
149
+ $themes = array_diff(wp_get_themes(), $this->old_themes);
150
+ foreach($themes as $theme){
151
+ $this->plugin->alerts->Trigger(5005, array(
152
+ 'Theme' => (object)array(
153
+ 'Name' => $theme->Name,
154
+ 'ThemeURI' => $theme->ThemeURI,
155
+ 'Description' => $theme->Description,
156
+ 'Author' => $theme->Author,
157
+ 'Version' => $theme->Version,
158
+ 'get_template_directory' => $theme->get_template_directory(),
159
+ ),
160
+ ));
161
+ }
 
 
 
 
162
  }
163
 
164
  // uninstall theme
165
+ if($is_themes && in_array($action, array('delete-selected', 'delete'))){
166
+ foreach($this->GetRemovedThemes() as $theme){
167
+ $this->plugin->alerts->Trigger(5007, array(
168
+ 'Theme' => (object)array(
169
+ 'Name' => $theme->Name,
170
+ 'ThemeURI' => $theme->ThemeURI,
171
+ 'Description' => $theme->Description,
172
+ 'Author' => $theme->Author,
173
+ 'Version' => $theme->Version,
174
+ 'get_template_directory' => $theme->get_template_directory(),
175
+ ),
176
+ ));
177
  }
178
  }
179
  }
180
 
181
  public function EventThemeActivated($themeName){
182
+ $theme = null;
183
+ foreach(wp_get_themes() as $item){
184
+ if($item->Name == $themeName){
185
+ $theme = $item;
186
  break;
187
  }
188
  }
189
+ if($theme == null)
190
  return $this->LogError(
191
+ 'Could not locate theme named "' . $theme . '".',
192
  array('ThemeName' => $themeName, 'Themes' => wp_get_themes())
193
  );
194
  $this->plugin->alerts->Trigger(5006, array(
195
+ 'Theme' => (object)array(
196
+ 'Name' => $theme->Name,
197
+ 'ThemeURI' => $theme->ThemeURI,
198
+ 'Description' => $theme->Description,
199
+ 'Author' => $theme->Author,
200
+ 'Version' => $theme->Version,
201
+ 'get_template_directory' => $theme->get_template_directory(),
202
  ),
203
  ));
204
  }
205
 
206
+ protected function GetRemovedThemes(){
207
+ $result = $this->old_themes;
208
+ foreach($result as $i => $theme)
209
+ if(file_exists($theme->get_template_directory()))
210
+ unset($result[$i]);
211
+ return array_values($result);
212
+ }
213
+
214
  }
classes/Sensors/Request.php CHANGED
@@ -17,9 +17,8 @@ class WSAL_Sensors_Request extends WSAL_AbstractSensor {
17
  . (!empty(self::$envvars) ? str_pad(PHP_EOL, 24) . json_encode(self::$envvars) : '')
18
  . PHP_EOL;
19
 
20
- if(!file_exists($file))
21
- if(!file_put_contents($file, '<'.'?php die(\'Access Denied\');' . PHP_EOL))
22
- return $this->LogError('Could not initialize request log file', array('file' => $file));
23
 
24
  $f = fopen($file, 'a');
25
  if($f){
17
  . (!empty(self::$envvars) ? str_pad(PHP_EOL, 24) . json_encode(self::$envvars) : '')
18
  . PHP_EOL;
19
 
20
+ if(!file_exists($file) && !file_put_contents($file, '<'.'?php die(\'Access Denied\'); ?>' . PHP_EOL))
21
+ return $this->LogError('Could not initialize request log file', array('file' => $file));
 
22
 
23
  $f = fopen($file, 'a');
24
  if($f){
classes/Settings.php CHANGED
@@ -207,10 +207,26 @@ class WSAL_Settings {
207
  * @param integer $newvalue The new maximum number of alerts.
208
  */
209
  public function SetPruningLimit($newvalue){
210
- $newvalue = max(min((int)$newvalue, $this->GetMaxAllowedAlerts()), 1);
211
  $this->SetGlobalOption(self::OPT_PRFX . 'pruning-limit', $newvalue);
212
  }
213
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  protected $_disabled = null;
215
 
216
  public function GetDefaultDisabledAlerts(){
@@ -363,4 +379,12 @@ class WSAL_Settings {
363
 
364
  return false;
365
  }
 
 
 
 
 
 
 
 
366
  }
207
  * @param integer $newvalue The new maximum number of alerts.
208
  */
209
  public function SetPruningLimit($newvalue){
210
+ $newvalue = max(/*min(*/(int)$newvalue/*, $this->GetMaxAllowedAlerts())*/, 1);
211
  $this->SetGlobalOption(self::OPT_PRFX . 'pruning-limit', $newvalue);
212
  }
213
 
214
+ public function SetPruningDateEnabled($enabled){
215
+ $this->SetGlobalOption(self::OPT_PRFX . 'pruning-date-e', $enabled);
216
+ }
217
+
218
+ public function SetPruningLimitEnabled($enabled){
219
+ $this->SetGlobalOption(self::OPT_PRFX . 'pruning-limit-e', $enabled);
220
+ }
221
+
222
+ public function IsPruningDateEnabled(){
223
+ return $this->GetGlobalOption(self::OPT_PRFX . 'pruning-date-e', true);
224
+ }
225
+
226
+ public function IsPruningLimitEnabled(){
227
+ return $this->GetGlobalOption(self::OPT_PRFX . 'pruning-limit-e', true);
228
+ }
229
+
230
  protected $_disabled = null;
231
 
232
  public function GetDefaultDisabledAlerts(){
379
 
380
  return false;
381
  }
382
+
383
+ public function IsIncognito(){
384
+ return $this->GetGlobalOption(self::OPT_PRFX . 'hide-plugin');
385
+ }
386
+
387
+ public function SetIncognito($enabled){
388
+ return $this->SetGlobalOption(self::OPT_PRFX . 'hide-plugin', $enabled);
389
+ }
390
  }
classes/Views/AuditLog.php CHANGED
@@ -10,6 +10,7 @@ class WSAL_Views_AuditLog extends WSAL_AbstractView {
10
  add_action('wp_ajax_AjaxInspector', array($this, 'AjaxInspector'));
11
  add_action('wp_ajax_AjaxRefresh', array($this, 'AjaxRefresh'));
12
  add_action('wp_ajax_AjaxSetIpp', array($this, 'AjaxSetIpp'));
 
13
  }
14
 
15
  public function HasPluginShortcutLink(){
@@ -52,6 +53,8 @@ class WSAL_Views_AuditLog extends WSAL_AbstractView {
52
  'ajaxurl' => admin_url('admin-ajax.php'),
53
  'tr8n' => array(
54
  'numofitems' => __('Please enter the number of alerts you would like to see on one page:', 'wp-security-audit-log'),
 
 
55
  ),
56
  'autorefresh' => array(
57
  'enabled' => $this->_plugin->settings->IsRefreshAlertsEnabled(),
@@ -112,6 +115,28 @@ class WSAL_Views_AuditLog extends WSAL_AbstractView {
112
  die;
113
  }
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  public function Header(){
116
  add_thickbox();
117
  wp_enqueue_style(
@@ -123,6 +148,8 @@ class WSAL_Views_AuditLog extends WSAL_AbstractView {
123
  }
124
 
125
  public function Footer() {
 
 
126
  wp_enqueue_script(
127
  'auditlog',
128
  $this->_plugin->GetBaseUrl() . '/js/auditlog.js',
@@ -146,6 +173,8 @@ class WSAL_Views_AuditLogList_Internal extends WP_List_Table {
146
  public function __construct($plugin){
147
  $this->_plugin = $plugin;
148
 
 
 
149
  parent::__construct(array(
150
  'singular' => 'log',
151
  'plural' => 'logs',
@@ -153,6 +182,8 @@ class WSAL_Views_AuditLogList_Internal extends WP_List_Table {
153
  'screen' => 'interval-list',
154
  ));
155
  }
 
 
156
 
157
  public function no_items(){
158
  _e('No events so far.', 'wp-security-audit-log');
@@ -182,24 +213,58 @@ class WSAL_Views_AuditLogList_Internal extends WP_List_Table {
182
 
183
  // show site alerts widget
184
  if($this->is_multisite() && $this->is_main_blog()){
185
- // TODO should I check wp_is_large_network()?
186
  $curr = $this->get_view_site_id();
187
- $sites = wp_get_sites();
188
  ?><div class="wsal-ssa wsal-ssa-<?php echo $which; ?>">
189
- <select class="wsal-ssas" onchange="WsalSsasChange(value);">
190
- <option value="0"><?php _e('All Sites', 'wp-security-audit-log'); ?></option>
191
- <?php foreach($sites as $site){ ?>
192
- <?php $info = get_blog_details($site['blog_id'], true); ?>
193
- <option
194
- value="<?php echo $info->blog_id; ?>"
195
- <?php if($info->blog_id == $curr)echo 'selected="selected"'; ?>><?php
196
- echo esc_html($info->blogname) . ' (' . esc_html($info->domain) . ')';
197
- ?></option>
198
- <?php } ?>
199
- </select>
 
 
 
 
200
  </div><?php
201
  }
202
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
  public function get_columns(){
205
  $cols = array(
@@ -254,10 +319,16 @@ class WSAL_Views_AuditLogList_Internal extends WP_List_Table {
254
  return '<span class="log-type log-type-' . $const->value
255
  . '" title="' . esc_html($const->name . ': ' . $const->description) . '"></span>';
256
  case 'crtd':
257
- return $item->created_on ? date('Y-m-d h:i:s A', $item->created_on) : '<i>unknown</i>';
 
 
 
 
 
 
258
  case 'user':
259
  $username = $item->GetUsername();
260
- if($username && ($user = get_userdatabylogin($username))){
261
  $image = get_avatar($user->ID, 32);
262
  $uhtml = '<a href="' . admin_url('user-edit.php?user_id=' . $user->ID)
263
  . '" target="_blank">' . esc_html($user->display_name) . '</a>';
10
  add_action('wp_ajax_AjaxInspector', array($this, 'AjaxInspector'));
11
  add_action('wp_ajax_AjaxRefresh', array($this, 'AjaxRefresh'));
12
  add_action('wp_ajax_AjaxSetIpp', array($this, 'AjaxSetIpp'));
13
+ add_action('wp_ajax_AjaxSearchSite', array($this, 'AjaxSearchSite'));
14
  }
15
 
16
  public function HasPluginShortcutLink(){
53
  'ajaxurl' => admin_url('admin-ajax.php'),
54
  'tr8n' => array(
55
  'numofitems' => __('Please enter the number of alerts you would like to see on one page:', 'wp-security-audit-log'),
56
+ 'searchback' => __('All Sites', 'wp-security-audit-log'),
57
+ 'searchnone' => __('No Results', 'wp-security-audit-log'),
58
  ),
59
  'autorefresh' => array(
60
  'enabled' => $this->_plugin->settings->IsRefreshAlertsEnabled(),
115
  die;
116
  }
117
 
118
+ public function AjaxSearchSite(){
119
+ if(!$this->_plugin->settings->CurrentUserCan('view'))
120
+ die('Access Denied.');
121
+ if(!isset($_REQUEST['search']))
122
+ die('Search parameter expected.');
123
+
124
+ $grp1 = array();
125
+ $grp2 = array();
126
+
127
+ $search = $_REQUEST['search'];
128
+
129
+ foreach($this->_listview->get_sites() as $site){
130
+ if(stripos($site->blogname, $search) !== false)
131
+ $grp1[] = $site;
132
+ else
133
+ if(stripos($site->domain, $search) !== false)
134
+ $grp2[] = $site;
135
+ }
136
+
137
+ die(json_encode(array_slice($grp1 + $grp2, 0, 7)));
138
+ }
139
+
140
  public function Header(){
141
  add_thickbox();
142
  wp_enqueue_style(
148
  }
149
 
150
  public function Footer() {
151
+ wp_enqueue_script('jquery');
152
+ wp_enqueue_script('suggest');
153
  wp_enqueue_script(
154
  'auditlog',
155
  $this->_plugin->GetBaseUrl() . '/js/auditlog.js',
173
  public function __construct($plugin){
174
  $this->_plugin = $plugin;
175
 
176
+ $this->_gmt_offset_sec = get_option('gmt_offset') * HOUR_IN_SECONDS;
177
+
178
  parent::__construct(array(
179
  'singular' => 'log',
180
  'plural' => 'logs',
182
  'screen' => 'interval-list',
183
  ));
184
  }
185
+
186
+ protected $_gmt_offset_sec = 0;
187
 
188
  public function no_items(){
189
  _e('No events so far.', 'wp-security-audit-log');
213
 
214
  // show site alerts widget
215
  if($this->is_multisite() && $this->is_main_blog()){
 
216
  $curr = $this->get_view_site_id();
 
217
  ?><div class="wsal-ssa wsal-ssa-<?php echo $which; ?>">
218
+ <?php if($this->get_site_count() > 15){ ?>
219
+ <?php $curr = $curr ? get_blog_details($curr) : null; ?>
220
+ <?php $curr = $curr ? ($curr->blogname . ' (' . $curr->domain . ')') : 'All Sites'; ?>
221
+ <input type="text" class="wsal-ssas" value="<?php echo esc_attr($curr); ?>"/>
222
+ <?php }else{ ?>
223
+ <select class="wsal-ssas" onchange="WsalSsasChange(value);">
224
+ <option value="0"><?php _e('All Sites', 'wp-security-audit-log'); ?></option>
225
+ <?php foreach($this->get_sites() as $info){ ?>
226
+ <option value="<?php echo $info->blog_id; ?>"
227
+ <?php if($info->blog_id == $curr)echo 'selected="selected"'; ?>><?php
228
+ echo esc_html($info->blogname) . ' (' . esc_html($info->domain) . ')';
229
+ ?></option>
230
+ <?php } ?>
231
+ </select>
232
+ <?php } ?>
233
  </div><?php
234
  }
235
  }
236
+
237
+ /**
238
+ * @param int|null $limit Maximum number of sites to return (null = no limit).
239
+ * @return object Object with keys: blog_id, blogname, domain
240
+ */
241
+ public function get_sites($limit = null){
242
+ global $wpdb;
243
+
244
+ // build query
245
+ $sql = 'SELECT blog_id, domain FROM ' . $wpdb->blogs;
246
+ if(!is_null($limit))$sql .= ' LIMIT ' . $limit;
247
+
248
+ // execute query
249
+ $res = $wpdb->get_results($sql);
250
+
251
+ // modify result
252
+ foreach($res as $row){
253
+ $row->blogname = get_blog_option($row->blog_id, 'blogname');
254
+ }
255
+
256
+ // return result
257
+ return $res;
258
+ }
259
+
260
+ /**
261
+ * @return int The number of sites on the network.
262
+ */
263
+ public function get_site_count(){
264
+ global $wpdb;
265
+ $sql = 'SELECT COUNT(*) FROM ' . $wpdb->blogs;
266
+ return (int)$wpdb->get_var($sql);
267
+ }
268
 
269
  public function get_columns(){
270
  $cols = array(
319
  return '<span class="log-type log-type-' . $const->value
320
  . '" title="' . esc_html($const->name . ': ' . $const->description) . '"></span>';
321
  case 'crtd':
322
+ return $item->created_on ? (
323
+ str_replace(
324
+ '$$$',
325
+ substr(number_format(fmod($item->created_on + $this->_gmt_offset_sec, 1), 3), 2),
326
+ date('Y-m-d<\b\r>h:i:s.$$$&\n\b\s\p;A', $item->created_on + $this->_gmt_offset_sec)
327
+ )
328
+ ) : '<i>unknown</i>';
329
  case 'user':
330
  $username = $item->GetUsername();
331
+ if($username && ($user = get_user_by('login', $username))){
332
  $image = get_avatar($user->ID, 32);
333
  $uhtml = '<a href="' . admin_url('user-edit.php?user_id=' . $user->ID)
334
  . '" target="_blank">' . esc_html($user->display_name) . '</a>';
classes/Views/Sandbox.php CHANGED
@@ -34,6 +34,49 @@ class WSAL_Views_Sandbox extends WSAL_AbstractView {
34
  protected $snippets = array(
35
  '' => '',
36
  'Current WP User' => 'return wp_get_current_user();',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  );
38
 
39
  public function HandleError($code, $message, $filename = 'unknown', $lineno = 0){
@@ -99,11 +142,11 @@ class WSAL_Views_Sandbox extends WSAL_AbstractView {
99
  echo '</style>';
100
  echo '</head><body>';
101
 
102
- if(($e = error_get_last()) && !isset($this->exec_data['Errors']) && !count($this->exec_data['Errors']))
103
  $this->HandleError($e['type'], $e['message'], $e['file'], $e['line']);
104
 
105
  if(count($this->exec_data)){
106
- $result = new WSAL_Nicer($this->exec_data);
107
  $result->render();
108
  }else echo '<div class="faerror">FATAL ERROR</div>';
109
 
34
  protected $snippets = array(
35
  '' => '',
36
  'Current WP User' => 'return wp_get_current_user();',
37
+
38
+ 'Clean PHP Error Events' => '
39
+ class OccurrenceCleanupTask extends WSAL_AbstractSandboxTask {
40
+
41
+ protected $event_ids = array(0000, 0001, 0002, 0003, 0004, 0005);
42
+
43
+ protected function Execute(){
44
+ global $wpdb;
45
+ $occs = WSAL_DB_Occurrence::LoadMulti(\'alert_id IN (\'.implode(\',\', $this->event_ids).\')\');
46
+ $c = count($occs);
47
+ $this->Message($c ? (\'Removing \' . $c . \' events...\') : \'No events to remove!\');
48
+ foreach($occs as $i => $occ){
49
+ $this->Message(\'Removing Event \' . $occ->id . \'...\', true);
50
+ $occ->Delete();
51
+ $this->Progress(($i + 1) / $c * 100);
52
+ }
53
+ }
54
+ }
55
+ new OccurrenceCleanupTask();',
56
+
57
+ 'Multisite Site Creator' => '
58
+ class DummySiteCreatorTask extends WSAL_AbstractSandboxTask {
59
+
60
+ protected $sites_to_create = 100;
61
+ protected $site_host = \'localhost\';
62
+ protected $site_path = \'/wordpress-3.8/test$i/\';
63
+ protected $site_name = \'Test $i\';
64
+
65
+ protected function Execute(){
66
+ global $wpdb;
67
+ $l = $wpdb->get_var("SELECT blog_id FROM $wpdb->blogs ORDER BY blog_id DESC LIMIT 1") + 1;
68
+ $this->Message(\'Creating \' . $this->sites_to_create . \' new sites...\');
69
+ for($i = $l; $i <= $this->sites_to_create + $l; $i++){
70
+ $this->Progress(($i - $l) / $this->sites_to_create * 100);
71
+ wpmu_create_blog(
72
+ str_replace(\'$i\', $i, $this->site_host),
73
+ str_replace(\'$i\', $i, $this->site_path),
74
+ str_replace(\'$i\', $i, $this->site_name),
75
+ 1);
76
+ }
77
+ }
78
+ }
79
+ new DummySiteCreatorTask();',
80
  );
81
 
82
  public function HandleError($code, $message, $filename = 'unknown', $lineno = 0){
142
  echo '</style>';
143
  echo '</head><body>';
144
 
145
+ if(($e = error_get_last()) && (!isset($this->exec_data['Errors']) || !count($this->exec_data['Errors'])))
146
  $this->HandleError($e['type'], $e['message'], $e['file'], $e['line']);
147
 
148
  if(count($this->exec_data)){
149
+ $result = new WSAL_Nicer($this->exec_data, true);
150
  $result->render();
151
  }else echo '<div class="faerror">FATAL ERROR</div>';
152
 
classes/Views/Settings.php CHANGED
@@ -39,12 +39,15 @@ class WSAL_Views_Settings extends WSAL_AbstractView {
39
  }
40
 
41
  protected function Save(){
 
42
  $this->_plugin->settings->SetPruningDate($_REQUEST['PruningDate']);
 
43
  $this->_plugin->settings->SetPruningLimit($_REQUEST['PruningLimit']);
44
  $this->_plugin->settings->SetWidgetsEnabled($_REQUEST['EnableDashboardWidgets']);
45
  $this->_plugin->settings->SetAllowedPluginViewers(isset($_REQUEST['Viewers']) ? $_REQUEST['Viewers'] : array());
46
  $this->_plugin->settings->SetAllowedPluginEditors(isset($_REQUEST['Editors']) ? $_REQUEST['Editors'] : array());
47
  $this->_plugin->settings->SetRefreshAlertsEnabled($_REQUEST['EnableAuditViewRefresh']);
 
48
  $this->_plugin->settings->ClearDevOptions();
49
  if(isset($_REQUEST['DevOptions']))
50
  foreach($_REQUEST['DevOptions'] as $opt)
@@ -82,9 +85,13 @@ class WSAL_Views_Settings extends WSAL_AbstractView {
82
  <td>
83
  <fieldset>
84
  <?php $text = __('(eg: 1 month)', 'wp-security-audit-log'); ?>
85
- <!--<input type="radio" id="delete1" style="margin-top: 2px;"/>-->
86
- <label for="delete1"><?php echo __('Delete alerts older than', 'wp-security-audit-log'); ?></label>
87
- <input type="text" name="PruningDate" placeholder="<?php echo $text; ?>"
 
 
 
 
88
  value="<?php echo esc_attr($this->_plugin->settings->GetPruningDate()); ?>"/>
89
  <span> <?php echo $text; ?></span>
90
  </fieldset>
@@ -94,16 +101,17 @@ class WSAL_Views_Settings extends WSAL_AbstractView {
94
  <th></th>
95
  <td>
96
  <fieldset>
97
- <?php $max = $this->_plugin->settings->GetMaxAllowedAlerts(); ?>
98
- <?php $text = sprintf(__('(1 to %d alerts)', 'wp-security-audit-log'), $max); ?>
99
- <!--<input type="radio" id="delete2" style="margin-top: 2px;"/>-->
100
- <label for="delete2"><?php echo __('Keep up to', 'wp-security-audit-log'); ?></label>
101
- <input type="text" name="PruningLimit" placeholder="<?php echo $text;?>"
 
 
 
102
  value="<?php echo esc_attr($this->_plugin->settings->GetPruningLimit()); ?>"/>
 
103
  <span><?php echo $text; ?></span>
104
- <p class="description"><?php
105
- echo sprintf(__('By default we keep up to %d WordPress Security Events.', 'wp-security-audit-log'), $max);
106
- ?></p>
107
  </fieldset>
108
  </td>
109
  </tr>
@@ -217,6 +225,19 @@ class WSAL_Views_Settings extends WSAL_AbstractView {
217
  ?></fieldset>
218
  </td>
219
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  </tbody>
221
  </table>
222
 
39
  }
40
 
41
  protected function Save(){
42
+ $this->_plugin->settings->SetPruningDateEnabled(isset($_REQUEST['PruneByDate']));
43
  $this->_plugin->settings->SetPruningDate($_REQUEST['PruningDate']);
44
+ $this->_plugin->settings->SetPruningLimitEnabled(isset($_REQUEST['PruneByLimit']));
45
  $this->_plugin->settings->SetPruningLimit($_REQUEST['PruningLimit']);
46
  $this->_plugin->settings->SetWidgetsEnabled($_REQUEST['EnableDashboardWidgets']);
47
  $this->_plugin->settings->SetAllowedPluginViewers(isset($_REQUEST['Viewers']) ? $_REQUEST['Viewers'] : array());
48
  $this->_plugin->settings->SetAllowedPluginEditors(isset($_REQUEST['Editors']) ? $_REQUEST['Editors'] : array());
49
  $this->_plugin->settings->SetRefreshAlertsEnabled($_REQUEST['EnableAuditViewRefresh']);
50
+ $this->_plugin->settings->SetIncognito(isset($_REQUEST['Incognito']));
51
  $this->_plugin->settings->ClearDevOptions();
52
  if(isset($_REQUEST['DevOptions']))
53
  foreach($_REQUEST['DevOptions'] as $opt)
85
  <td>
86
  <fieldset>
87
  <?php $text = __('(eg: 1 month)', 'wp-security-audit-log'); ?>
88
+ <?php $nbld = $this->_plugin->settings->IsPruningDateEnabled(); ?>
89
+ <label for="delete1">
90
+ <input type="checkbox" id="delete1" name="PruneByDate" value="1" <?php if($nbld)echo 'checked="checked"'; ?>
91
+ onchange="jQuery('#PruningDate').attr('readonly', !checked);"/>
92
+ <?php echo __('Delete alerts older than', 'wp-security-audit-log'); ?>
93
+ </label>
94
+ <input type="text" id="PruningDate" name="PruningDate" placeholder="<?php echo $text; ?>" <?php if(!$nbld)echo 'readonly="readonly"'; ?>
95
  value="<?php echo esc_attr($this->_plugin->settings->GetPruningDate()); ?>"/>
96
  <span> <?php echo $text; ?></span>
97
  </fieldset>
101
  <th></th>
102
  <td>
103
  <fieldset>
104
+ <?php $text = __('(eg: 80)', 'wp-security-audit-log'); ?>
105
+ <?php $nbld = $this->_plugin->settings->IsPruningLimitEnabled(); ?>
106
+ <label for="delete2">
107
+ <input type="checkbox" id="delete2" name="PruneByLimit" value="1" <?php if($nbld)echo 'checked="checked"'; ?>
108
+ onchange="jQuery('#PruningLimit').attr('readonly', !checked);"/>
109
+ <?php echo __('Keep up to', 'wp-security-audit-log'); ?>
110
+ </label>
111
+ <input type="text" id="PruningLimit" name="PruningLimit" placeholder="<?php echo $text;?>" <?php if(!$nbld)echo 'readonly="readonly"'; ?>
112
  value="<?php echo esc_attr($this->_plugin->settings->GetPruningLimit()); ?>"/>
113
+ <?php echo __('alerts', 'wp-security-audit-log'); ?>
114
  <span><?php echo $text; ?></span>
 
 
 
115
  </fieldset>
116
  </td>
117
  </tr>
225
  ?></fieldset>
226
  </td>
227
  </tr>
228
+
229
+ <tr>
230
+ <th><label for="Incognito"><?php _e('Hide Plugin from Plugins Page', 'wp-security-audit-log'); ?></label></th>
231
+ <td>
232
+ <fieldset>
233
+ <label for="Incognito">
234
+ <input type="checkbox" name="Incognito" value="1" id="Incognito"<?php
235
+ if($this->_plugin->settings->IsIncognito())echo ' checked="checked"'; ?>/>
236
+ <?php _e('Hide', 'wp-security-audit-log'); ?>
237
+ </label>
238
+ </fieldset>
239
+ </td>
240
+ </tr>
241
  </tbody>
242
  </table>
243
 
css/auditlog.css CHANGED
@@ -11,6 +11,7 @@
11
  width: 56px;
12
  }
13
 
 
14
  .wsal-ssa select {
15
  margin-bottom: 6px;
16
  width: 120px;
@@ -25,7 +26,7 @@
25
  }
26
 
27
  .column-crtd {
28
- width: 96px;
29
  }
30
 
31
  .column-user {
@@ -131,4 +132,44 @@ td.column-user {
131
  .log-type-8192:after,
132
  .log-type-16384:after,
133
  .log-type-E_DEBUG:after
134
- { background: #09E; content: "i"; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  width: 56px;
12
  }
13
 
14
+ .wsal-ssa input,
15
  .wsal-ssa select {
16
  margin-bottom: 6px;
17
  width: 120px;
26
  }
27
 
28
  .column-crtd {
29
+ width: 120px;
30
  }
31
 
32
  .column-user {
132
  .log-type-8192:after,
133
  .log-type-16384:after,
134
  .log-type-E_DEBUG:after
135
+ { background: #09E; content: "i"; }
136
+
137
+ /* search box */
138
+ .wsal-ssas-dd {
139
+ position: absolute;
140
+ border: 1px solid windowframe;
141
+ background: #FFF;
142
+ padding: 0;
143
+ margin: -8px 16px;
144
+ background: window;
145
+ color: windowtext;
146
+ min-width: 140px;
147
+ }
148
+ .wsal-ssas-dd * {
149
+ cursor: default;
150
+ }
151
+ .wsal-ssas-dd a {
152
+ padding: 2px 8px;
153
+ display: block;
154
+ line-height: normal;
155
+ text-decoration: none;
156
+ color: windowtext;
157
+ }
158
+ .wsal-ssas-dd a:hover,
159
+ .wsal-ssas-dd .active {
160
+ background: highlight;
161
+ color: highlighttext;
162
+ }
163
+ .wsal-ssas-dd a u {
164
+ text-decoration: underline;
165
+ }
166
+ .wsal-ssas-dd span {
167
+ color: #555;
168
+ font-style: italic;
169
+ padding: 2px 8px;
170
+ display: block;
171
+ line-height: normal;
172
+ }
173
+ .wsal-ssas-dd .allsites {
174
+ border-bottom: 1px solid windowframe;
175
+ }
css/nice_r.css CHANGED
@@ -1,74 +1,92 @@
1
- .nice_r {
2
- font: 12px Consolas, "Lucida Console", monospace;
3
- }
4
-
5
- .nice_r a {
6
- display: block;
7
- text-decoration: none;
8
- color: #222;
9
- padding: 2px;
10
- white-space: nowrap;
11
- overflow: hidden;
12
- text-overflow: ellipsis;
13
- }
14
-
15
- .nice_r a:hover .nice_r_k,
16
- .nice_r a:hover .nice_r_d,
17
- .nice_r a:hover .nice_r_d span,
18
- .nice_r a:hover .nice_r_p,
19
- .nice_r a:hover .nice_r_a,
20
- .nice_r a:hover {
21
- background-color: Highlight;
22
- color: HighlightText;
23
- }
24
-
25
- .nice_r_a {
26
- color: #000;
27
- }
28
-
29
- .nice_r_ad {
30
- opacity: 0.5;
31
- }
32
-
33
- .nice_r_k {
34
- color: #060;
35
- font-weight: bold;
36
- }
37
-
38
- .nice_r_d {
39
- font-size: 11px;
40
- color: #777;
41
- }
42
-
43
- .nice_r_d span {
44
- color: #333;
45
- }
46
-
47
- .nice_r_p {
48
- color: #000;
49
- font-weight: bold;
50
- }
51
-
52
- .nice_r_v {
53
- margin-left: 6px;
54
- padding-left: 6px;
55
- border-left: 1px dotted #CCC;
56
- display: none;
57
- }
58
-
59
- .nice_r_ir {
60
- font-style: italic;
61
- }
62
-
63
- .nice_r_p.nice_r_t_integer,
64
- .nice_r_p.nice_r_t_double {
65
- color: #F0E;
66
- }
67
-
68
- .nice_r_p.nice_r_t_string {
69
- color: #E00;
70
- }
71
-
72
- .nice_r_p.nice_r_t_boolean {
73
- color: #00E;
74
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .nice_r {
2
+ font: 12px Consolas, "Lucida Console", monospace;
3
+ cursor: default;
4
+ }
5
+
6
+ .nice_r a {
7
+ display: block;
8
+ text-decoration: none;
9
+ color: #222;
10
+ padding: 2px;
11
+ white-space: nowrap;
12
+ overflow: hidden;
13
+ text-overflow: ellipsis;
14
+ }
15
+
16
+ .nice_r a:hover .nice_r_k,
17
+ .nice_r a:hover .nice_r_d,
18
+ .nice_r a:hover .nice_r_d span,
19
+ .nice_r a:hover .nice_r_p,
20
+ .nice_r a:hover .nice_r_a,
21
+ .nice_r a:hover {
22
+ background-color: Highlight;
23
+ color: HighlightText;
24
+ }
25
+
26
+ .nice_r_a {
27
+ color: #000;
28
+ }
29
+
30
+ .nice_r_ad {
31
+ opacity: 0.5;
32
+ }
33
+
34
+ .nice_r_k {
35
+ color: #060;
36
+ font-weight: bold;
37
+ }
38
+
39
+ .nice_r_d {
40
+ font-size: 11px;
41
+ color: #777;
42
+ }
43
+
44
+ .nice_r_d span {
45
+ color: #333;
46
+ }
47
+
48
+ .nice_r_p {
49
+ color: #000;
50
+ font-weight: bold;
51
+ }
52
+
53
+ .nice_r_v {
54
+ margin-left: 6px;
55
+ padding-left: 6px;
56
+ border-left: 1px dotted #CCC;
57
+ display: none;
58
+ }
59
+
60
+ .nice_r_ir {
61
+ font-style: italic;
62
+ }
63
+
64
+ .nice_r_p.nice_r_t_integer,
65
+ .nice_r_p.nice_r_t_double {
66
+ color: #F0E;
67
+ }
68
+
69
+ .nice_r_p.nice_r_t_string {
70
+ color: #E00;
71
+ }
72
+
73
+ .nice_r_p.nice_r_t_boolean {
74
+ color: #00E;
75
+ }
76
+
77
+ .nice_r_t_comment {
78
+ color: #080;
79
+ }
80
+
81
+ .nice_r a.nice_r_c .nice_r_k {
82
+ color: #909;
83
+ }
84
+
85
+ .nice_r a.nice_r_c .nice_r_ma {
86
+ font-weight: normal;
87
+ color: #777;
88
+ }
89
+
90
+ .nice_r a.nice_r_c .nice_r_mv {
91
+ color: #00E;
92
+ }
defaults.php CHANGED
@@ -111,13 +111,14 @@ WpSecurityAuditLog::GetInstance()
111
  array(4007, E_CRITICAL, __('A user was deleted by another user', 'wp-security-audit-log'), __('Deleted User %TargetUserData->Username% with the role of %TargetUserData->Roles%', 'wp-security-audit-log')),
112
  ),
113
  'Plugins & Themes' => array(
114
- array(5000, E_CRITICAL, __('User installed a plugin', 'wp-security-audit-log'), __('Installed the plugin %NewPlugin->Name% in %NewPlugin->plugin_dir_path%', 'wp-security-audit-log')),
115
  array(5001, E_CRITICAL, __('User activated a WordPress plugin', 'wp-security-audit-log'), __('Activated the plugin %PluginData->Name% installed in %PluginFile%', 'wp-security-audit-log')),
116
  array(5002, E_CRITICAL, __('User deactivated a WordPress plugin', 'wp-security-audit-log'), __('Deactivated the plugin %PluginData->Name% installed in %PluginFile%', 'wp-security-audit-log')),
117
  array(5003, E_CRITICAL, __('User uninstalled a plugin', 'wp-security-audit-log'), __('Uninstalled the plugin %PluginData->Name% which was installed in %PluginFile%', 'wp-security-audit-log')),
118
  array(5004, E_WARNING, __('User upgraded a plugin', 'wp-security-audit-log'), __('Upgraded the plugin %PluginData->Name% installed in %PluginFile%', 'wp-security-audit-log')),
119
- array(5005, E_CRITICAL, __('User installed a theme', 'wp-security-audit-log'), __('Installed theme "%NewTheme->Name%" in %NewTheme->get_template_directory%', 'wp-security-audit-log')),
120
- array(5006, E_CRITICAL, __('User activated a theme', 'wp-security-audit-log'), __('Activated theme "%NewTheme->Name%", installed in %NewTheme->get_template_directory%', 'wp-security-audit-log')),
 
121
  ),
122
  'System Activity' => array(
123
  array(0000, E_CRITICAL, __('Unknown Error', 'wp-security-audit-log'), __('An unexpected error has occurred', 'wp-security-audit-log')),
@@ -145,5 +146,7 @@ WpSecurityAuditLog::GetInstance()
145
  array(7003, E_CRITICAL, __('Deactivated site has been activated', 'wp-security-audit-log'), __('Activated site %SiteName%', 'wp-security-audit-log')),
146
  array(7004, E_CRITICAL, __('Site has been deactivated', 'wp-security-audit-log'), __('Deactivated site %SiteName%', 'wp-security-audit-log')),
147
  array(7005, E_CRITICAL, __('Existing site deleted from network', 'wp-security-audit-log'), __('Deleted site %SiteName%', 'wp-security-audit-log')),
 
 
148
  ),
149
  ));
111
  array(4007, E_CRITICAL, __('A user was deleted by another user', 'wp-security-audit-log'), __('Deleted User %TargetUserData->Username% with the role of %TargetUserData->Roles%', 'wp-security-audit-log')),
112
  ),
113
  'Plugins & Themes' => array(
114
+ array(5000, E_CRITICAL, __('User installed a plugin', 'wp-security-audit-log'), __('Installed the plugin %Plugin->Name% in %Plugin->plugin_dir_path%', 'wp-security-audit-log')),
115
  array(5001, E_CRITICAL, __('User activated a WordPress plugin', 'wp-security-audit-log'), __('Activated the plugin %PluginData->Name% installed in %PluginFile%', 'wp-security-audit-log')),
116
  array(5002, E_CRITICAL, __('User deactivated a WordPress plugin', 'wp-security-audit-log'), __('Deactivated the plugin %PluginData->Name% installed in %PluginFile%', 'wp-security-audit-log')),
117
  array(5003, E_CRITICAL, __('User uninstalled a plugin', 'wp-security-audit-log'), __('Uninstalled the plugin %PluginData->Name% which was installed in %PluginFile%', 'wp-security-audit-log')),
118
  array(5004, E_WARNING, __('User upgraded a plugin', 'wp-security-audit-log'), __('Upgraded the plugin %PluginData->Name% installed in %PluginFile%', 'wp-security-audit-log')),
119
+ array(5005, E_CRITICAL, __('User installed a theme', 'wp-security-audit-log'), __('Installed theme "%Theme->Name%" in %Theme->get_template_directory%', 'wp-security-audit-log')),
120
+ array(5006, E_CRITICAL, __('User activated a theme', 'wp-security-audit-log'), __('Activated theme "%Theme->Name%", installed in %Theme->get_template_directory%', 'wp-security-audit-log')),
121
+ array(5007, E_CRITICAL, __('User uninstalled a theme', 'wp-security-audit-log'), __('Deleted theme "%Theme->Name%" installed in %Theme->get_template_directory%', 'wp-security-audit-log')),
122
  ),
123
  'System Activity' => array(
124
  array(0000, E_CRITICAL, __('Unknown Error', 'wp-security-audit-log'), __('An unexpected error has occurred', 'wp-security-audit-log')),
146
  array(7003, E_CRITICAL, __('Deactivated site has been activated', 'wp-security-audit-log'), __('Activated site %SiteName%', 'wp-security-audit-log')),
147
  array(7004, E_CRITICAL, __('Site has been deactivated', 'wp-security-audit-log'), __('Deactivated site %SiteName%', 'wp-security-audit-log')),
148
  array(7005, E_CRITICAL, __('Existing site deleted from network', 'wp-security-audit-log'), __('Deleted site %SiteName%', 'wp-security-audit-log')),
149
+ array(5008, E_CRITICAL, __('Activated theme on network', 'wp-security-audit-log'), __('Network activated %Theme->Name% theme installed in %Theme->get_template_directory%', 'wp-security-audit-log')),
150
+ array(5009, E_CRITICAL, __('Deactivated theme from network', 'wp-security-audit-log'), __('Network deactivated %Theme->Name% theme installed in %Theme->get_template_directory%', 'wp-security-audit-log')),
151
  ),
152
  ));
js/auditlog.js CHANGED
@@ -24,6 +24,8 @@ function WsalAuditLogInit(_WsalData){
24
  setInterval(WsalChk, 40000);
25
  WsalChk();
26
  }
 
 
27
  }
28
 
29
  var WsalIppsPrev;
@@ -46,8 +48,63 @@ function WsalIppsChange(value){
46
  });
47
  }
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  function WsalSsasChange(value){
50
- jQuery('select.wsal-ssas').attr('disabled', true);
 
51
  jQuery('#wsal-cbid').val(value);
52
  jQuery('#audit-log-viewer').submit();
53
  }
24
  setInterval(WsalChk, 40000);
25
  WsalChk();
26
  }
27
+
28
+ WsalSsasInit();
29
  }
30
 
31
  var WsalIppsPrev;
48
  });
49
  }
50
 
51
+ function WsalSsasInit(){
52
+ var SsasAjx = null;
53
+ var SsasInps = jQuery("input.wsal-ssas");
54
+ SsasInps.after('<div class="wsal-ssas-dd" style="display: none;"/>');
55
+ SsasInps.click(function(){
56
+ jQuery(this).select();
57
+ });
58
+ SsasInps.keyup(function(){
59
+ var SsasInp = jQuery(this);
60
+ var SsasDiv = SsasInp.next();
61
+ var SsasVal = SsasInp.val();
62
+ if(SsasAjx)SsasAjx.abort();
63
+ SsasInp.removeClass('loading');
64
+
65
+ // do a new search
66
+ if(SsasInp.attr('data-oldvalue') !== SsasVal && SsasVal.length > 2){
67
+ SsasInp.addClass('loading');
68
+ SsasAjx = jQuery.post(WsalData.ajaxurl, {
69
+ action: 'AjaxSearchSite',
70
+ search: SsasVal
71
+ }, function(data){
72
+ if(SsasAjx)SsasAjx = null;
73
+ SsasInp.removeClass('loading');
74
+ SsasDiv.hide();
75
+ SsasDiv.html('');
76
+ if(data && data.length){
77
+ var SsasReg = new RegExp(SsasVal.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'), 'gi');
78
+ for (var i = 0; i < data.length; i++){
79
+ var link = jQuery('<a href="javascript:;" onclick="WsalSsasChange(' + data[i].blog_id + ')"/>')
80
+ .text(data[i].blogname + ' (' + data[i].domain + ')');
81
+ link.html(link.text().replace(SsasReg, '<u>$&</u>'));
82
+ SsasDiv.append(link);
83
+ }
84
+ }else{
85
+ SsasDiv.append(jQuery('<span/>').text(WsalData.tr8n.searchnone));
86
+ }
87
+ SsasDiv.prepend(jQuery('<a href="javascript:;" onclick="WsalSsasChange(0)" class="allsites"/>').text(WsalData.tr8n.searchback));
88
+ SsasDiv.show();
89
+ }, 'json');
90
+ SsasInp.attr('data-oldvalue', SsasVal);
91
+ }
92
+
93
+ // handle keys
94
+ });
95
+ SsasInps.blur(function(){
96
+ setTimeout(function(){
97
+ var SsasInp = jQuery(this);
98
+ var SsasDiv = SsasInp.next();
99
+ SsasInp.attr('data-oldvalue', '');
100
+ SsasDiv.hide();
101
+ }, 200);
102
+ });
103
+ }
104
+
105
  function WsalSsasChange(value){
106
+ jQuery('div.wsal-ssas-dd').hide();
107
+ jQuery('input.wsal-ssas').attr('disabled', true);
108
  jQuery('#wsal-cbid').val(value);
109
  jQuery('#audit-log-viewer').submit();
110
  }
readme.txt CHANGED
@@ -1,13 +1,13 @@
1
  === WP Security Audit Log ===
2
- Contributors: WPWhiteSecurity, uuf6429
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=payments%40wpwhitesecurity%2ecom&lc=US&item_name=WP%20Security%20Audit%20Log%20WordPress%20Plugin&currency_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted
4
  Plugin URI: http://www.wpwhitesecurity.com/wordpress-security-plugins/wp-security-audit-log/
5
  License: GPLv3
6
  License URI: http://www.gnu.org/licenses/gpl.html
7
- Tags: wordpress security plugin, wordpress security audit log, audit log, wordpress log, event log wordpress, wordpress user tracking, wordpress activity log, wordpress audit, security event log, audit trail, security audit trail, wordpress security alerts, wordpress monitor, wordpress security monitor, wordpress admin, wordpress admin monitoring, analytics, activity, admin, multisite, wordpress multisite
8
  Requires at least: 3.6
9
  Tested up to: 3.9.1
10
- Stable tag: 1.1.0
11
 
12
  Identify WordPress issues before they become a security problem by keeping an audit log of users and all of the under the hood WordPress activity.
13
 
@@ -84,6 +84,13 @@ WP Security Audit Log plugin also has a number of features that make WordPress a
84
  * Uses who upload or delete any sort of files
85
  * and much more...
86
 
 
 
 
 
 
 
 
87
  = WordPress Security Tips & Tricks =
88
  Even if WordPress security is not your cup of tea, the security of your WordPress is your responsibility. Keep yourself up to date with the latest WordPress Security Tips & Tricks. WP White Security frequently publishes WordPress security tips & tricks on the [WordPress Security section](http://www.wpwhitesecurity.com/wordpress-security/) of their blog.
89
 
@@ -125,9 +132,24 @@ Yes, WP Security Audit Log works on WordPress Multisite networks, i.e. it can mo
125
  2. The WP Security Audit Log plugin options from where WordPress administrator can configure the auto pruning of security alerts and specific user access.
126
  3. The Enable/Disable Alerts settings node from where Administrators can disable or enable WordPress security alerts.
127
  4. The Audit Log Viewer of a Super Admin in a WordPress multisite network installation with the Site selection drop down menu.
 
128
 
129
  == Changelog ==
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  = 1.1.0 (2014-05-27) =
132
  * New Features
133
  * User avatar is shown in the alert to allow administrators to easily recognize users and their activity
1
  === WP Security Audit Log ===
2
+ Contributors: WPWhiteSecurity, uuf6429, robert681
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=payments%40wpwhitesecurity%2ecom&lc=US&item_name=WP%20Security%20Audit%20Log%20WordPress%20Plugin&currency_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted
4
  Plugin URI: http://www.wpwhitesecurity.com/wordpress-security-plugins/wp-security-audit-log/
5
  License: GPLv3
6
  License URI: http://www.gnu.org/licenses/gpl.html
7
+ Tags: wordpress security plugin, wordpress security audit log, audit log, wordpress log, event log wordpress, wordpress user tracking, wordpress activity log, wordpress audit, security event log, audit trail, security audit trail, wordpress security alerts, wordpress monitor, wordpress security monitor, wordpress admin, wordpress admin monitoring, analytics, activity, admin, multisite, wordpress multisite, syslog, log, changelog, trail, history, notification, dashboard, analytics, actions,
8
  Requires at least: 3.6
9
  Tested up to: 3.9.1
10
+ Stable tag: 1.2.0
11
 
12
  Identify WordPress issues before they become a security problem by keeping an audit log of users and all of the under the hood WordPress activity.
13
 
84
  * Uses who upload or delete any sort of files
85
  * and much more...
86
 
87
+ = As Featured On: =
88
+
89
+ * [WP Mayor](http://www.wpmayor.com/wp-security-audit-log-plugin-review-user-activity-logging-wordpress/)
90
+ * [ManageWP](https://managewp.com/free-wordpress-plugins-june-2014)
91
+ * [Design Wall](http://www.designwall.com/blog/10-wordpress-multisite-plugins-you-shouldnt-live-without/)
92
+ * [WPLift](http://wplift.com/wordpress-event-tracking)
93
+
94
  = WordPress Security Tips & Tricks =
95
  Even if WordPress security is not your cup of tea, the security of your WordPress is your responsibility. Keep yourself up to date with the latest WordPress Security Tips & Tricks. WP White Security frequently publishes WordPress security tips & tricks on the [WordPress Security section](http://www.wpwhitesecurity.com/wordpress-security/) of their blog.
96
 
132
  2. The WP Security Audit Log plugin options from where WordPress administrator can configure the auto pruning of security alerts and specific user access.
133
  3. The Enable/Disable Alerts settings node from where Administrators can disable or enable WordPress security alerts.
134
  4. The Audit Log Viewer of a Super Admin in a WordPress multisite network installation with the Site selection drop down menu.
135
+ 5. If there are more than 15 sites in a multisite, an auto complete site search shows up instead of the drop down menu (see [screenshots](https://wordpress.org/plugins/wp-security-audit-log/screenshots/) for reference)
136
 
137
  == Changelog ==
138
 
139
+ = 1.2.0 (2014-07-2) =
140
+ * New Features
141
+ * Unlimited Alerts can be stored (removed the 5000 alerts limit)
142
+ * Alert time now includes milliseconds for more precision (ideal for auditing and compliance)
143
+ * Reported alert time is now relative to user's configured timezone
144
+ * Alerts automatic pruning procedures can now be enabled / disabled
145
+ * Option to hide WP Security Audit Log from plugins page in WordPress
146
+ * If there are more than 15 websites in a multisite installation, an auto complete site search box is shown instead of the drop down menu
147
+
148
+ * New WordPress Security Alerts
149
+ * Alert 5007: User has uninstalled / deleted a theme
150
+ * Alert 5008: Super administrator network activated a theme on multisite
151
+ * Alert 5009: Super administrator network deactivated a theme on multisite
152
+
153
  = 1.1.0 (2014-05-27) =
154
  * New Features
155
  * User avatar is shown in the alert to allow administrators to easily recognize users and their activity
wp-security-audit-log.php CHANGED
@@ -4,7 +4,7 @@ Plugin Name: WP Security Audit Log
4
  Plugin URI: http://www.wpwhitesecurity.com/wordpress-security-plugins/wp-security-audit-log/
5
  Description: Identify WordPress security issues before they become a problem and keep track of everything happening on your WordPress, including WordPress users activity. Similar to Windows Event Log and Linux Syslog, WP Security Audit Log will generate a security alert for everything that happens on your WordPress blog or website. Use the Audit Log Viewer included in the plugin to see all the security alerts.
6
  Author: WP White Security
7
- Version: 1.1.0
8
  Text Domain: wp-security-audit-log
9
  Author URI: http://www.wpwhitesecurity.com/
10
  License: GPL2
@@ -99,30 +99,21 @@ class WpSecurityAuditLog {
99
  $this->constants = new WSAL_ConstantManager($this);
100
  $this->widgets = new WSAL_WidgetManager($this);
101
 
102
- // listen to general events
103
- $this->sensors->HookEvents();
104
-
105
  // listen for installation event
106
  register_activation_hook(__FILE__, array($this, 'Install'));
107
 
108
- // makes sure everything is ready
109
- add_action('init', array($this, 'CheckInstall'));
110
-
111
  // listen for cleanup event
112
  add_action('wsal_cleanup', array($this, 'CleanUp'));
113
 
114
  // internationalize plugin
115
  add_action('plugins_loaded', array($this, 'LoadPluginTextdomain'));
116
- }
117
-
118
- public function CheckInstall(){
119
- // upgrade/update as necesary
120
- if(!$this->IsInstalled()){
121
- WSAL_DB_ActiveRecord::InstallAll();
122
- if ($this->CanUpgrade()) $this->Upgrade();
123
- }else{
124
- $this->Update();
125
- }
126
  }
127
 
128
  public function Install(){
@@ -142,7 +133,9 @@ class WpSecurityAuditLog {
142
  die(1);
143
  }
144
 
145
- $this->CheckInstall();
 
 
146
 
147
  wp_schedule_event(0, 'hourly', 'wsal_cleanup');
148
  }
@@ -152,10 +145,6 @@ class WpSecurityAuditLog {
152
  wp_unschedule_event(0, 'wsal_cleanup');
153
  }
154
 
155
- public function Update(){
156
-
157
- }
158
-
159
  public function Upgrade(){
160
  global $wpdb;
161
  static $migTypes = array(
@@ -216,6 +205,10 @@ class WpSecurityAuditLog {
216
 
217
  // <editor-fold desc="Utility Methods">
218
 
 
 
 
 
219
  /**
220
  * This is the class autoloader. You should not call this directly.
221
  * @param string $class Class name.
@@ -252,7 +245,7 @@ class WpSecurityAuditLog {
252
  * @return boolean Whether we are running on multisite or not.
253
  */
254
  public function IsMultisite(){
255
- return funciton_exists('is_multisite') && is_multisite();
256
  }
257
 
258
  public function CleanUp(){
@@ -303,5 +296,8 @@ class WpSecurityAuditLog {
303
  // Load extra files
304
  require_once('defaults.php');
305
 
 
 
 
306
  // Create & Run the plugin
307
  return WpSecurityAuditLog::GetInstance();
4
  Plugin URI: http://www.wpwhitesecurity.com/wordpress-security-plugins/wp-security-audit-log/
5
  Description: Identify WordPress security issues before they become a problem and keep track of everything happening on your WordPress, including WordPress users activity. Similar to Windows Event Log and Linux Syslog, WP Security Audit Log will generate a security alert for everything that happens on your WordPress blog or website. Use the Audit Log Viewer included in the plugin to see all the security alerts.
6
  Author: WP White Security
7
+ Version: 1.2.0
8
  Text Domain: wp-security-audit-log
9
  Author URI: http://www.wpwhitesecurity.com/
10
  License: GPL2
99
  $this->constants = new WSAL_ConstantManager($this);
100
  $this->widgets = new WSAL_WidgetManager($this);
101
 
 
 
 
102
  // listen for installation event
103
  register_activation_hook(__FILE__, array($this, 'Install'));
104
 
 
 
 
105
  // listen for cleanup event
106
  add_action('wsal_cleanup', array($this, 'CleanUp'));
107
 
108
  // internationalize plugin
109
  add_action('plugins_loaded', array($this, 'LoadPluginTextdomain'));
110
+
111
+ // hide plugin
112
+ if($this->settings->IsIncognito())
113
+ add_action('admin_head', array($this, 'HidePlugin'));
114
+
115
+ // clean up if need be
116
+ $this->CleanUp();
 
 
 
117
  }
118
 
119
  public function Install(){
133
  die(1);
134
  }
135
 
136
+ $PreInstalled = $this->IsInstalled();
137
+ WSAL_DB_ActiveRecord::InstallAll();
138
+ if (!$PreInstalled && $this->CanUpgrade()) $this->Upgrade();
139
 
140
  wp_schedule_event(0, 'hourly', 'wsal_cleanup');
141
  }
145
  wp_unschedule_event(0, 'wsal_cleanup');
146
  }
147
 
 
 
 
 
148
  public function Upgrade(){
149
  global $wpdb;
150
  static $migTypes = array(
205
 
206
  // <editor-fold desc="Utility Methods">
207
 
208
+ public function HidePlugin(){
209
+ ?><style type="text/css">.wp-list-table.plugins #wp-security-audit-log { display: none; }</style><?php
210
+ }
211
+
212
  /**
213
  * This is the class autoloader. You should not call this directly.
214
  * @param string $class Class name.
245
  * @return boolean Whether we are running on multisite or not.
246
  */
247
  public function IsMultisite(){
248
+ return function_exists('is_multisite') && is_multisite();
249
  }
250
 
251
  public function CleanUp(){
296
  // Load extra files
297
  require_once('defaults.php');
298
 
299
+ // Start listening to events
300
+ WpSecurityAuditLog::GetInstance()->sensors->HookEvents();
301
+
302
  // Create & Run the plugin
303
  return WpSecurityAuditLog::GetInstance();