WP Security Audit Log - Version 0.1

Version Description

  • Initial beta release of WP Security Audit Log.
Download this release

Release Info

Developer WPProHelp
Plugin Icon 128x128 WP Security Audit Log
Version 0.1
Comparing to
See all releases

Version 0.1

inc/WPPH.php ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @kyos
4
+ * Base class
5
+ */
6
+ class WPPH
7
+ {
8
+ /**
9
+ * @return bool
10
+ * Convenient method to check whether or not the plugin can safely run
11
+ */
12
+ public static function canRun() { return WPPHDatabase::canRun(); }
13
+ /**
14
+ * @return bool
15
+ * Convenient method to check whether or not the plugin's resources can be loaded
16
+ */
17
+ public static function canLoad() {
18
+ if(false === ($pos = stripos($_SERVER['REQUEST_URI'], WPPH_PLUGIN_PREFIX))){ return false; }
19
+ return true;
20
+ }
21
+
22
+ public static function loadBaseResources()
23
+ {
24
+ if(self::canLoad())
25
+ {
26
+ wp_enqueue_style('wpph_styles_base', WPPH_PLUGIN_URL . 'res/css/styles.base.css');
27
+ wp_enqueue_script('wpph-ko-js', WPPH_PLUGIN_URL . 'res/js/knockout-2.2.1.min.js' );
28
+ wp_enqueue_script('wpph-alvm-js', WPPH_PLUGIN_URL . 'res/js/AuditLogViewModel.js' );
29
+ }
30
+ }
31
+
32
+ public static function createPluginWpSidebar()
33
+ {
34
+ if (!current_user_can('administrator')){return false;}
35
+ if (function_exists('add_menu_page'))
36
+ {
37
+ $baseMenuSlug = 'wpph_';
38
+ $reqCap = 'activate_plugins';
39
+
40
+ add_menu_page('WP Security Audit Log', 'WP Security Audit Log', $reqCap, $baseMenuSlug, 'WPPH::pageMain', WPPH_PLUGIN_URL.'res/img/logo-main-menu.png');
41
+ add_submenu_page($baseMenuSlug, 'Audit Log Viewer', 'Audit Log Viewer', $reqCap, $baseMenuSlug, 'WPPH::pageMain');
42
+ add_submenu_page($baseMenuSlug, 'Settings', __('Settings'), $reqCap, $baseMenuSlug.'settings', 'WPPH::pageSettings');
43
+ add_submenu_page($baseMenuSlug, 'About', __('About'), $reqCap, $baseMenuSlug.'about', 'WPPH::pageAbout');
44
+ add_submenu_page($baseMenuSlug, 'Support', __('Support'), $reqCap, $baseMenuSlug.'support', 'WPPH::pageSupport');
45
+ }
46
+ }
47
+
48
+ public static function pageMain() { include(WPPH_PLUGIN_DIR.'pages/dashboard.php'); }
49
+ public static function pageSettings() { include(WPPH_PLUGIN_DIR.'pages/settings.php'); }
50
+ public static function pageAbout() { include(WPPH_PLUGIN_DIR.'pages/about.php'); }
51
+ public static function pageSupport() { include(WPPH_PLUGIN_DIR.'pages/support.php'); }
52
+
53
+ public static function createPluginDefaultSettings()
54
+ {
55
+ $settings = new stdClass();
56
+ $settings->daysToKeep = 0;
57
+ $settings->eventsToKeep = 10000;
58
+ $settings->showEventsViewList = 50; // how many items to show in the event viewer by default
59
+ $settings->lastCleanup = time();
60
+ $settings->cleanupRan = 0;
61
+ add_option(WPPH_PLUGIN_SETTING_NAME, $settings);
62
+ wpphLog('Settings added.');
63
+ }
64
+ public static function getPluginSettings()
65
+ {
66
+ $settings = get_option(WPPH_PLUGIN_SETTING_NAME);
67
+ if(false === $settings){
68
+ self::createPluginDefaultSettings();
69
+ $settings = get_option(WPPH_PLUGIN_SETTING_NAME);
70
+ }
71
+ return $settings;
72
+ }
73
+
74
+ /**
75
+ * @param object $settings If this param is null, $settingName & $settingValue must be set
76
+ * @param string $settingName Optional. Required if $settings is null
77
+ * @param string $settingValue Optional. Required if $settings is null
78
+ * @param bool $overrideCleanupRan Whether or not to override the cleanupRan option. Defaults to false
79
+ */
80
+ public static function updatePluginSettings($settings = null, $settingName = null, $settingValue=null, $overrideCleanupRan = false)
81
+ {
82
+ if(! is_null($settings)){
83
+ if($overrideCleanupRan){
84
+ $settings->lastCleanup = 0;
85
+ $settings->cleanupRan = 0;
86
+ }
87
+ update_option(WPPH_PLUGIN_SETTING_NAME, $settings);
88
+ return;
89
+ }
90
+
91
+ // name and value must be set!
92
+ if(is_null($settingName) || is_null($settingValue)){
93
+ return;
94
+ }
95
+
96
+ $settings = self::getPluginSettings();
97
+ $settings->$settingName = $settingValue;
98
+ if($overrideCleanupRan){
99
+ $settings->lastCleanup = 0;
100
+ $settings->cleanupRan = 0;
101
+ }
102
+ update_option(WPPH_PLUGIN_SETTING_NAME, $settings);
103
+ wpphLog('Settings saved.', $settings);
104
+ }
105
+ }
106
+
107
+
inc/WPPHAdminNotices.php ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class WPPHAdminNotices
4
+ */
5
+ class WPPHAdminNotices
6
+ {
7
+ public static function show($errorCode)
8
+ {
9
+ $f = "e".$errorCode;
10
+ if(is_callable(array(__CLASS__,$f))){ add_action('admin_notices',array(__CLASS__,$f)); }
11
+ }
12
+
13
+ public static function e100(){
14
+ $m = __('Plugin cannot create tables in the WordPress database to store security audit logs. Allow write access to the WordPress database user temporarily to activate this plugin. For more information contact us on support@wpprohelp.com.');
15
+ echo '<div class="error"><p><strong>'.WPPH_PLUGIN_NAME.' '.__('Error').':</strong> '.$m.'</p></div>';
16
+ }
17
+
18
+
19
+ public static function e0()
20
+ {
21
+ $tableName = WPPHDatabase::getFullTableName('events');
22
+ $query = '<pre><code>'.WPPHDatabase::getCreateQueryEventsDetailsTable().'</code></pre>';
23
+ $m = sprintf(
24
+ __('The table <strong>%s</strong> was not found nor it could be created.<br/>Please run this query manually then reload this page: %s')
25
+ , $tableName, $query);
26
+ echo '<div class="error"><p><strong>'.WPPH_PLUGIN_NAME.' '.__('Error').':</strong> '.$m.'</p></div>';
27
+ }
28
+
29
+ public static function e1()
30
+ {
31
+ $tableName = WPPHDatabase::getFullTableName('events');
32
+ $queryUpdate = '<pre><code>';
33
+ foreach(WPPHDatabase::getUpdateQueryEventsDetailsTable() as $query){
34
+ $queryUpdate .= $query.'<br/>';
35
+ }
36
+ $queryUpdate .= '</code></pre>';
37
+ $m = sprintf(__('We have encountered an error while trying to update the table: <strong>%s</strong>
38
+ <br/>Please run the following queries manually then reload this page: %s')
39
+ ,$tableName, $queryUpdate);
40
+ echo '<div class="error"><p><strong>'.WPPH_PLUGIN_NAME.' '.__('Error').':</strong> '.$m.'</p></div>';
41
+ }
42
+
43
+ public static function e2()
44
+ {
45
+ $tableName = WPPHDatabase::getFullTableName('events');
46
+ $query = '<pre><code>'.WPPHDatabase::getUpgradeQueryEventsDetailsTable().'</code></pre>';
47
+ $m = sprintf(
48
+ __('The table <strong>%s</strong> could not be updated.<br/>Please run this query manually then reload this page: %s')
49
+ , $tableName, $query);
50
+ echo '<div class="error"><p><strong>'.WPPH_PLUGIN_NAME.' '.__('Error').':</strong> '.$m.'</p></div>';
51
+ }
52
+
53
+ public static function e3()
54
+ {
55
+ $tableName = WPPHDatabase::getFullTableName('main');
56
+ $query = '<pre><code>'.WPPHDatabase::getCreateQueryLogsTable().'</code></pre>';
57
+ $m = sprintf(
58
+ __('The table <strong>%s</strong> was not found nor it could be created.<br/>Please run this query manually then reload this page: %s')
59
+ , $tableName, $query);
60
+ echo '<div class="error"><p><strong>'.WPPH_PLUGIN_NAME.' '.__('Error').':</strong> '.$m.'</p></div>';
61
+ }
62
+
63
+ public static function e4()
64
+ {
65
+ $tableName = WPPHDatabase::getFullTableName('main');
66
+ $query = '<pre><code>'.WPPHDatabase::getUpdateQueryLogsTable().'</code></pre>';
67
+ $m = sprintf(
68
+ __('The table <strong>%s</strong> could not be updated.<br/>Please run this query manually then reload this page: %s')
69
+ , $tableName, $query);
70
+ echo '<div class="error"><p><strong>'.WPPH_PLUGIN_NAME.' '.__('Error').':</strong> '.$m.'</p></div>';
71
+ }
72
+
73
+ public static function e5()
74
+ {
75
+ $tableName = WPPHDatabase::getFullTableName('main');
76
+ $query = '<pre><code>'.WPPHDatabase::getUpgradeQueryLogsTable().'</code></pre>';
77
+ $m = sprintf(
78
+ __('The table <strong>%s</strong> could not be updated.<br/>Please run this query manually then reload this page: %s')
79
+ , $tableName, $query);
80
+ echo '<div class="error"><p><strong>'.WPPH_PLUGIN_NAME.' '.__('Error').':</strong> '.$m.'</p></div>';
81
+ }
82
+ }
inc/WPPHDatabase.php ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class WPPHDatabase
4
+ * Internal class handling the creation, update and upgrade of the db tables
5
+ */
6
+ class WPPHDatabase
7
+ {
8
+ /**
9
+ * @var bool
10
+ * Whether or not we can safely use the plugin
11
+ */
12
+ private static $_canRun = true;
13
+
14
+ /**
15
+ * @var string
16
+ * @private
17
+ * Holds the name of the event logs table WITHOUT the db prefix!
18
+ */
19
+ private static $_eventsLogTableBaseName = '_wordpress_eventlog';
20
+ /**
21
+ * @var string
22
+ * @private
23
+ * Holds the name of the events details table WITHOUT the db prefix!
24
+ */
25
+ private static $_eventsDetailsTableBaseName = '_wordpress_eventlog_details';
26
+
27
+
28
+ public static function checkTables()
29
+ {
30
+ //#! EVENT DETAILS TABLE
31
+ if(! self::_createEventDetailsTable()){
32
+ WPPHAdminNotices::show(0);
33
+ return false;
34
+ }
35
+ if(! self::_updateEventsDetailsTable()){
36
+ WPPHAdminNotices::show(1);
37
+ return false;
38
+ }
39
+ if(! self::_upgradeEventDetailsTable()){
40
+ WPPHAdminNotices::show(2);
41
+ return false;
42
+ }
43
+
44
+ //#! EVENT LOGS MAIN TABLE
45
+ if(! self::_createEventLogsTable()){
46
+ WPPHAdminNotices::show(3);
47
+ return false;
48
+ }
49
+ if(! self::_updateEventLogsTable()){
50
+ WPPHAdminNotices::show(4);
51
+ return false;
52
+ }
53
+ if(! self::_upgradeEventLogsTable()){
54
+ WPPHAdminNotices::show(5);
55
+ return false;
56
+ }
57
+
58
+ self::$_canRun = true;
59
+ return true;
60
+ }
61
+
62
+ public static function canRun() { return self::$_canRun; }
63
+
64
+ /**
65
+ * Returns the full table name db_prefix + base_table_name for the requested table
66
+ * @param string $what the table identifier. Possible values:
67
+ * main -> to retrieve : db_prefix + self::$_eventsLogTableBaseName
68
+ * events -> to retrieve: db_prefix + self::$_eventsDetailsTableBaseName
69
+ * @return string
70
+ */
71
+ public static function getFullTableName($what = 'main')
72
+ {
73
+ global $wpdb;
74
+ if(strcasecmp($what, 'MAIN') == 0){
75
+ return $wpdb->prefix.self::$_eventsLogTableBaseName;
76
+ }
77
+ elseif(strcasecmp($what, 'EVENTS') == 0){
78
+ return $wpdb->prefix.self::$_eventsDetailsTableBaseName;
79
+ }
80
+ return '';
81
+ }
82
+
83
+
84
+
85
+ public static function getCreateQueryEventsDetailsTable()
86
+ {
87
+ global $wpdb;
88
+ $tableName = $wpdb->prefix.self::$_eventsDetailsTableBaseName;
89
+ return "CREATE TABLE IF NOT EXISTS `$tableName` (
90
+ `EventID` int(8) NOT NULL,
91
+ `EventType` varchar(10) DEFAULT 'NOTICE',
92
+ `EventDescription` text NOT NULL,
93
+ PRIMARY KEY (`EventID`),
94
+ UNIQUE KEY `EventID` (`EventID`)
95
+ );";
96
+ }
97
+
98
+ public static function getUpdateQueryEventsDetailsTable()
99
+ {
100
+ global $wpdb;
101
+ $tableName = $wpdb->prefix.self::$_eventsDetailsTableBaseName;
102
+
103
+ $out = array();
104
+ $entries = WPPHEvent::listEvents();
105
+ if(empty($entries)){ return $out; }
106
+
107
+ foreach($entries as $entry)
108
+ {
109
+ $q = sprintf("INSERT INTO `%s` (`EventID`,`EventType`,`EventDescription`) VALUES(%d,'%s','%s')", $tableName, $entry['id'], $entry['category'], $entry['text']);
110
+ $out["{$entry['id']}"] = $q;
111
+ }
112
+ return $out;
113
+ }
114
+
115
+ //@todo: UPDATE AS NECESSARY
116
+ public static function getUpgradeQueryEventsDetailsTable()
117
+ {
118
+ return '';
119
+ }
120
+
121
+
122
+ public static function getCreateQueryLogsTable()
123
+ {
124
+ global $wpdb;
125
+ $t1 = $wpdb->prefix.self::$_eventsLogTableBaseName;
126
+ return "CREATE TABLE IF NOT EXISTS `$t1` (
127
+ `EventNumber` bigint(40) NOT NULL AUTO_INCREMENT,
128
+ `EventID` int(8) NOT NULL,
129
+ `EventDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
130
+ `UserID` int(8) NOT NULL DEFAULT '0',
131
+ `UserIP` varchar(24) NOT NULL DEFAULT '0.0.0.0',
132
+ `EventData` TEXT NOT NULL,
133
+ PRIMARY KEY (`EventNumber`),
134
+ UNIQUE KEY `EventNumber` (`EventNumber`)
135
+ );";
136
+ }
137
+
138
+ public static function getUpdateQueryLogsTable()
139
+ {
140
+ return '';
141
+ }
142
+ public static function getUpgradeQueryLogsTable()
143
+ {
144
+ return '';
145
+ }
146
+
147
+
148
+ private static function _createEventDetailsTable()
149
+ {
150
+ if(self::_eventDetailsTableExists()) { return true; }
151
+ global $wpdb;
152
+ $query = self::getCreateQueryEventsDetailsTable();
153
+ if (false === $wpdb->query($wpdb->prepare($query))){ return false; }
154
+ return true;
155
+ }
156
+
157
+ /**
158
+ * This function will insert the default rows in the events details table
159
+ */
160
+ private static function _updateEventsDetailsTable()
161
+ {
162
+ global $wpdb;
163
+ $queries = self::getUpdateQueryEventsDetailsTable();
164
+ foreach($queries as $id => $query){
165
+ if(! empty($query)){
166
+ $var = $wpdb->get_var("SELECT EventID FROM ".self::getFullTableName('events')." WHERE EventID = $id");
167
+ if(empty($var)){
168
+ if(false === $wpdb->query($query)){
169
+ return false;
170
+ }
171
+ }
172
+ }
173
+ }
174
+ return true;
175
+ }
176
+
177
+ //TODO: UPDATE AS NECESSARY
178
+ private static function _upgradeEventDetailsTable()
179
+ {
180
+ //EXECUTE THE QUERY FROM self::getUpgradeQueryEventsDetailsTable();
181
+ return true;
182
+ }
183
+
184
+
185
+ private static function _createEventLogsTable()
186
+ {
187
+ if(self::_eventLogsTableExists()){ return true;}
188
+ global $wpdb;
189
+ $query = self::getCreateQueryLogsTable();
190
+ if(false === $wpdb->query($wpdb->prepare($query))){return false;}
191
+ return true;
192
+ }
193
+
194
+ private static function _updateEventLogsTable()
195
+ {
196
+ return true;
197
+ }
198
+
199
+ //TODO: UPDATE AS NECESSARY
200
+ private static function _upgradeEventLogsTable()
201
+ {
202
+ //EXECUTE THE QUERY FROM self::getUpgradeQueryLogsTable();
203
+ return true;
204
+ }
205
+
206
+ private static function _eventLogsTableExists()
207
+ {
208
+ global $wpdb;
209
+ $tableName = $wpdb->prefix.self::$_eventsLogTableBaseName;
210
+ $result = $wpdb->get_var($wpdb->prepare('SELECT EventNumber FROM '.$tableName));
211
+ return (is_null($result) ? false : true);
212
+
213
+ }
214
+ private static function _eventDetailsTableExists()
215
+ {
216
+ global $wpdb;
217
+ $tableName = $wpdb->prefix.self::$_eventsDetailsTableBaseName;
218
+ $result = $wpdb->get_var($wpdb->prepare('SELECT EventID FROM '.$tableName));
219
+ return (is_null($result) ? false : true);
220
+ }
221
+
222
+ //!! TODO: CHECK
223
+ public static function userHasAccessRights()
224
+ {
225
+ global $wpdb;
226
+
227
+ $rights = $wpdb->get_results("SHOW GRANTS FOR CURRENT_USER()", ARRAY_N);
228
+
229
+ if(empty($rights)) return false;
230
+
231
+ foreach($rights as $right){
232
+ if(!empty($right[0])){
233
+ $r = strtoupper($right[0]);
234
+ if (preg_match("/GRANT ALL PRIVILEGES/i", $r)) { return true; }
235
+ else{
236
+ if (preg_match_all("/CREATE|DELETE|ALTER|INSERT|UPDATE|SELECT|DELETE/i", $r, $matches)){
237
+ if (! empty($matches[0])){
238
+ $m = $matches[0];
239
+ $m = array_unique($m);
240
+ if (count($m) >= 5){ return true; }
241
+ }
242
+ }
243
+ }
244
+ }
245
+ }
246
+ return false;
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Class WPPHDB
252
+ * Contains utility methods to communicate with the database
253
+ */
254
+ class WPPHDB extends WPPHDatabase
255
+ {
256
+ /**
257
+ * @return string The current logged in user's role
258
+ */
259
+ public static function getCurrentUserRole()
260
+ {
261
+ global $current_user;
262
+ get_currentuserinfo();
263
+ $user_roles = $current_user->roles;
264
+ $user_role = array_shift($user_roles);
265
+ return $user_role;
266
+ }
267
+ // returns array(userName, userRole)
268
+ public static function getUserInfo($userID)
269
+ {
270
+ global $wpdb;
271
+
272
+ $t = $wpdb->prefix.'users';
273
+
274
+ $username = $wpdb->get_var("SELECT user_login FROM $t WHERE ID=$userID");
275
+ $user = new WP_User( $userID );
276
+ $userRole = (empty($user->roles[0]) ? '' : $user->roles[0]);
277
+
278
+ return array(
279
+ 'userName' => $username,
280
+ 'userRole' => $userRole
281
+ );
282
+ }
283
+
284
+ /**
285
+ * Retrieve the total number of events from db
286
+ * @return int
287
+ */
288
+ public static function getEventsCount()
289
+ {
290
+ global $wpdb;
291
+ $result = $wpdb->get_var("SELECT COUNT(EventNumber) FROM ".self::getFullTableName('main'));
292
+ return intval($result);
293
+ }
294
+
295
+ }
296
+
inc/WPPHEvent.php ADDED
@@ -0,0 +1,884 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class that will handle the events management
4
+ * @kyos
5
+ */
6
+ class WPPHEvent
7
+ {
8
+ public static function bindHooks(array $hooks = array())
9
+ {
10
+ if(empty($hooks)){ return; }
11
+ foreach($hooks as $hook){
12
+ if(is_callable(array(self,$hook),true)){
13
+ call_user_func(array(self,$hook));
14
+ }
15
+ }
16
+ }
17
+ /**
18
+ * Retrieve the list of events
19
+ * @return array
20
+ */
21
+ public static function listEvents()
22
+ {
23
+ return array(
24
+ // 1xxx - Login/Logout events
25
+ array( 'id' => 1000, 'category' => 'NOTICE', 'text' => __('Successfully logged in.') ),
26
+ array( 'id' => 1001, 'category' => 'NOTICE', 'text' => __('Successfully logged out.') ),
27
+ array( 'id' => 1002, 'category' => 'WARNING', 'text' => __('Failed Login detected using <strong>%s</strong> as username.') ),
28
+
29
+ // 2xxx - User activity events
30
+ // Created a new blog post called %Post Title%. Blog post ID is %ID%
31
+ array( 'id' => 2000, 'category' => 'NOTICE', 'text' => __('Created a new draft blog post called <strong>%s</strong>. Blog post ID is <strong>%d</strong>.') ),
32
+ // Published a blog post called %Post_Title%. Blog post URL is %Post_URL%
33
+ array( 'id' => 2001, 'category' => 'NOTICE', 'text' => __('Published a blog post called <strong>%s</strong>. Blog post URL is <strong>%s</strong>.') ),
34
+ // Modified the published blog post %post_title%. Blog post URL is %post_URL%
35
+ array( 'id' => 2002, 'category' => 'NOTICE', 'text' => __('Modified the published blog post <strong>%s</strong>. Blog post URL is <strong>%s</strong>.') ),
36
+ // Modified the draft blog post %post_title%. Blog post ID is %ID%
37
+ array( 'id' => 2003, 'category' => 'NOTICE', 'text' => __('Modified the draft blog post <strong>%s</strong>. Blog post ID is <strong>%d</strong>.') ),
38
+
39
+ // Created a new page called %page_title%. Page ID is %ID%
40
+ array( 'id' => 2004, 'category' => 'NOTICE', 'text' => __('Created a new draft page called <strong>%s</strong>. Page ID is <strong>%d</strong>.') ),
41
+ // Published a page called %page_title%. Page URL is %URL%
42
+ array( 'id' => 2005, 'category' => 'NOTICE', 'text' => __('Published a page called <strong>%s</strong>. Page URL is <strong>%s</strong>.') ),
43
+ // Modified the published page %page_title%. Page URL is %URL%
44
+ array( 'id' => 2006, 'category' => 'NOTICE', 'text' => __('Modified the published page <strong>%s</strong>. Page URL is <strong>%s</strong>.') ),
45
+ // Modified the draft page %page_title%. Page ID is %ID%
46
+ array( 'id' => 2007, 'category' => 'NOTICE', 'text' => __('Modified the draft page <strong>%s</strong>. Page ID is <strong>%d</strong>.') ),
47
+ // Deleted the post %Title%. Blog post ID is %ID%
48
+ array( 'id' => 2008, 'category' => 'HIGH', 'text' => __('Deleted the post <strong>%s</strong>. Blog post ID is <strong>%d</strong>.') ),
49
+ // Deleted the page %Title%. Page ID is %ID%
50
+ array( 'id' => 2009, 'category' => 'HIGH', 'text' => __('Deleted the page <strong>%s</strong>. Page ID is <strong>%d</strong>.') ),
51
+
52
+ // Uploaded the file %file name$ in %file location%
53
+ array( 'id' => 2010, 'category' => 'NOTICE', 'text' => __('Uploaded the file <strong>%s</strong> in <strong>%s</strong>/.') ),
54
+ // Deleted file %file name$ from %file_location%
55
+ array( 'id' => 2011, 'category' => 'HIGH', 'text' => __('Deleted the file <strong>%s</strong> from <strong>%s</strong>/.') ),
56
+ // 2012 - trashed draft post
57
+ array( 'id' => 2012, 'category' => 'HIGH', 'text' => __('Moved the post <strong>%s</strong> to trash.') ),
58
+ // 2013 - trashed published post
59
+ array( 'id' => 2013, 'category' => 'HIGH', 'text' => __('Moved the page <strong>%s</strong> to trash.') ),
60
+ // 2014 - untrashed post
61
+ array( 'id' => 2014, 'category' => 'HIGH', 'text' => __('Post <strong>%s</strong> has been restored from trash.') ),
62
+ // 2015 - untrashed page
63
+ array( 'id' => 2015, 'category' => 'HIGH', 'text' => __('Page <strong>%s</strong> has been restored from trash.') ),
64
+
65
+ // 3xxx - Themes management
66
+ // Activated the theme %themeName%
67
+ array( 'id' => 3000, 'category' => 'NOTICE', 'text' => __('Activated the theme <strong>%s</strong>.') ),
68
+
69
+ // 4xxx - User profile events
70
+ array( 'id' => 4000, 'category' => 'HIGH', 'text' => __('A new user with the username <strong>%s</strong> has registered with the role of <strong>%s</strong>.') ),
71
+ array( 'id' => 4001, 'category' => 'HIGH', 'text' => __('<strong>%s</strong> created a new user <strong>%s</strong> with the role of <strong>%s</strong>.') ),
72
+ array( 'id' => 4002, 'category' => 'HIGH', 'text' => __('The role of user <strong>%s</strong> was changed from <strong>%s</strong> to <strong>%s</strong> by <strong>%s</strong>.') ),
73
+ array( 'id' => 4003, 'category' => 'HIGH', 'text' => __('Changed the account password.') ),
74
+ array( 'id' => 4004, 'category' => 'HIGH', 'text' => __('<strong>%s</strong> changed the password for user <strong>%s</strong> with the role of <strong>%s</strong>.') ),
75
+ // Changed the email address from %old_email% to %new_email%
76
+ array( 'id' => 4005, 'category' => 'NOTICE', 'text' => __('Changed the email address from <strong>%s</strong> to <strong>%s</strong>.') ),
77
+ // %user_making_change% changed the email address of user %user% from %old_email% to %new_email%
78
+ array( 'id' => 4006, 'category' => 'NOTICE', 'text' => __('<strong>%s</strong> changed the email address of user <strong>%s</strong> from <strong>%s</strong> to <strong>%s</strong>.') ),
79
+ // User %user% with the role of %role% was deleted by %user_deleting%
80
+ array( 'id' => 4007, 'category' => 'HIGH', 'text' => __('User <strong>%s</strong> with the role of <strong>%s</strong> was deleted by <strong>%s</strong>.') ),
81
+
82
+ // 5xxx - Plugin management
83
+ // Activated the plugin %plugin_name% installed in %plugin_directory%
84
+ array( 'id' => 5000, 'category' => 'HIGH', 'text' => __('Activated the plugin <strong>%s</strong> installed in /<strong>%s</strong>.') ),
85
+ // Deactivated the plugin %plugin_name% installed in %plugin_directory%
86
+ array( 'id' => 5001, 'category' => 'HIGH', 'text' => __('Deactivated the plugin <strong>%s</strong> installed in /<strong>%s</strong>.') ),
87
+
88
+ // 6xxx - System events
89
+ //
90
+ array( 'id' => 6000, 'category' => 'NOTICE', 'text' => __('Events automatically deleted by system.') ),
91
+ );
92
+ }
93
+
94
+
95
+ // 1xxx - Login/Logout events
96
+
97
+ // 1000
98
+ public static function hookLoginEvent() { add_action('wp_login', array('WPPHEventWatcher', 'watchEventLogin'), 10, 2); }
99
+ // 1001
100
+ public static function hookLogoutEvent() { add_action('wp_logout', array('WPPHEventWatcher', 'watchEventLogout')); }
101
+ // 1002
102
+ public static function hookLoginFailure() { add_action('wp_login_failed', array('WPPHEventWatcher', 'watchLoginFailure')); }
103
+
104
+
105
+ // 2xxx - User activity events
106
+
107
+ // 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
108
+ public static function hookWatchBlogActivity() { add_action('transition_post_status', array('WPPHEventWatcher', 'WatchBlogActivity'), 10, 3); }
109
+ // 2008, 2009
110
+ public static function hookFileDeletion() { add_action('delete_post', array('WPPHEventWatcher', 'watchTrash'), 10, 1); }
111
+ // 2010
112
+ public static function hookFileUploaded() { add_action('add_attachment', array('WPPHEventWatcher', 'watchFileUploaded')); }
113
+ // 2011
114
+ public static function hookFileUploadedDeleted() { add_action('delete_attachment', array('WPPHEventWatcher', 'watchFileUploadedDeleted')); }
115
+ // 2012
116
+ public static function hookTrashPost() {
117
+ if(defined('EMPTY_TRASH_DAYS') && (EMPTY_TRASH_DAYS == 0)){
118
+ add_action('delete_post', array('WPPHEventWatcher', 'watchTrash'), 10, 1);
119
+ }
120
+ else { add_action('trash_post', array('WPPHEventWatcher', 'watchFileDeletion')); }
121
+ }
122
+ // 2013
123
+ public static function hookTrashPage() {
124
+ if(defined('EMPTY_TRASH_DAYS') && (EMPTY_TRASH_DAYS == 0)){
125
+ add_action('delete_post', array('WPPHEventWatcher', 'watchTrash'), 10, 1);
126
+ }
127
+ else { add_action('trash_page', array('WPPHEventWatcher', 'watchFileDeletion')); }
128
+ }
129
+ //2014
130
+ public static function hookUntrashedPosts() { add_action('untrash_post', array('WPPHEventWatcher', 'watchTrashUndo')); }
131
+ // 2015
132
+ public static function hookUntrashedPages() { add_action('untrash_page', array('WPPHEventWatcher', 'watchTrashUndo')); }
133
+
134
+ // 3xxx - Themes management
135
+
136
+ // 3000
137
+ public static function hookThemeChange() { add_action('switch_theme', array('WPPHEventWatcher', 'watchThemeChange'));}
138
+
139
+
140
+ // 4xxx - User profile events
141
+
142
+ // 4000, 4001
143
+ public static function hookUserRegisterEvent() { add_action('user_register', array('WPPHEventWatcher', 'watchEventUserRegister')); }
144
+ // 4002
145
+ public static function hookUserRoleUpdated() {
146
+ add_action('edit_user_profile_update', array('WPPHEventWatcher', 'watchUserRoleUpdated'));
147
+ add_action('personal_options_update', array('WPPHEventWatcher', 'watchUserRoleUpdated'));
148
+ }
149
+ // 4003, 4004
150
+ public static function hookUserPasswordUpdated() {
151
+ add_action('edit_user_profile_update', array('WPPHEventWatcher', 'watchUserPasswordUpdated'));
152
+ add_action('personal_options_update', array('WPPHEventWatcher', 'watchUserPasswordUpdated'));
153
+ }
154
+ // 4005, 4006
155
+ public static function hookUserEmailUpdated() {
156
+ add_action('edit_user_profile_update', array('WPPHEventWatcher', 'watchUserEmailUpdated'));
157
+ add_action('personal_options_update', array('WPPHEventWatcher', 'watchUserEmailUpdated'));
158
+ }
159
+ // 4007
160
+ public static function hookUserDeletion() { add_action( 'delete_user', array('WPPHEventWatcher', 'watchUserDeletion') ); }
161
+
162
+
163
+ // 5xxx - Plugin management
164
+
165
+ // 5000, 5001
166
+ public static function hookWatchPluginActivity() {
167
+ @require_once(ABSPATH.'wp-admin/includes/plugin.php');
168
+ $current = get_plugins();
169
+ foreach($current as $plugin_file => $plugin_data) {
170
+ if ($plugin_file == WPPH_PLUGIN_BASE_NAME) {
171
+ continue;
172
+ }
173
+ add_action('activate_' . $plugin_file, array('WPPHEventWatcher', 'watchPluginActivate'));
174
+ add_action('deactivate_' . $plugin_file, array('WPPHEventWatcher', 'watchPluginDeactivate'));
175
+ }
176
+ }
177
+
178
+
179
+ // 6xxx - System events
180
+
181
+ // 6000
182
+ public static function hookEventsDeletion() { add_action('init', array('WPPHEventWatcher', 'watchDeleteEvents')); }
183
+
184
+
185
+ /**
186
+ * Add log event. Internal function. Don not use outside class scope.
187
+ * @internal
188
+ * @static
189
+ * @param int $eventID
190
+ * @param int $userID . A value of 0 means user "System". This is the ID of the user triggering the alert.
191
+ * @param string $userIP
192
+ * @param string $eventData Optional. If provided should be as a serialized array.
193
+ * @return bool
194
+ */
195
+ public static function _addLogEvent($eventID = 1000, $userID = 0, $userIP = '', $eventData = '')
196
+ {
197
+ if(empty($userIP)){ $userIP = WPPHUtil::getIP(); }
198
+
199
+ global $wpdb;
200
+ $query = sprintf("INSERT INTO %s (EventID, UserID, UserIP, EventData) VALUES(%d, %d, '%s', '%s')"
201
+ ,WPPHDatabase::getFullTableName('MAIN'), $eventID, $userID, $userIP, $eventData);
202
+ if(false === $wpdb->query($wpdb->prepare($query)))
203
+ {
204
+ // mysql error
205
+ return false;
206
+ }
207
+ return true;
208
+ }
209
+
210
+
211
+ /*
212
+ * PUBLIC METHODS
213
+ * ============================================
214
+ */
215
+
216
+ public static function getEventDetailsData($eventID)
217
+ {
218
+ global $wpdb;
219
+ $table = WPPHDatabase::getFullTableName('events');
220
+ return $wpdb->get_row($wpdb->prepare("SELECT EventType, EventDescription FROM $table WHERE EventID = $eventID"));
221
+ }
222
+
223
+ /**
224
+ * Retrieve the events from db.
225
+ * @param array $limit
226
+ * @param string $orderBy. Must be a valid column name. Defaults to EventNumber
227
+ * @param string $sort ASC or DESC
228
+ * @return mixed
229
+ */
230
+ public static function getEvents($orderBy='EventNumber', $sort = 'DESC', $limit = array(0,0))
231
+ {
232
+ $validArgsSort = array('ASC', 'DESC');
233
+ $validCnTableLogDetails = array('EventID', 'EventType');
234
+ $validCnTableLog = array('EventNumber', 'EventDate', 'UserID', 'UserIP');
235
+
236
+ $l0 = 0;
237
+ $l1 = 1;
238
+ if(isset($limit[0]) && ($limit[0] >= 0)){ $l0 = intval($limit[0]); }
239
+ if(isset($limit[1]) && ($limit[1] >= 1)){ $l1 = intval($limit[1]); }
240
+ $limit = "$l0,$l1";
241
+
242
+ $sort = strtoupper($sort);
243
+ if(empty($sort) || !in_array($sort, $validArgsSort)) { $sort = $validArgsSort[1]; }
244
+
245
+ if(! empty($orderBy)){
246
+ if(in_array($orderBy, $validCnTableLog)){
247
+ $orderBy = 'le.'.$orderBy;
248
+ }
249
+ elseif(in_array($orderBy, $validCnTableLogDetails)){
250
+ $orderBy = 'led.'.$orderBy;
251
+ }
252
+ }
253
+ else { $orderBy = 'le.EventNumber'; }
254
+
255
+ $t1 = WPPHDatabase::getFullTableName('main');
256
+ $t2 = WPPHDatabase::getFullTableName('events');
257
+ $querySelect = "SELECT le.EventNumber, le.EventID, le.EventDate, le.UserID, le.UserIP, le.EventData,
258
+ led.EventType, led.EventDescription
259
+ FROM `$t1` as le
260
+ INNER JOIN `$t2` as led
261
+ ON le.EventID = led.EventID
262
+ ORDER BY $orderBy
263
+ $sort
264
+ LIMIT $limit;";
265
+ global $wpdb;
266
+ return $wpdb->get_results($wpdb->prepare($querySelect), ARRAY_A);
267
+ }
268
+
269
+
270
+ }
271
+
272
+ /**
273
+ * Class WPPHEventWatcher
274
+ * This class provides callable methods that are called inside the hooks registered
275
+ * in the WPPHEvent class. All methods are internal and should not be used outside
276
+ * scope.
277
+ * @static
278
+ * @internal
279
+ */
280
+ class WPPHEventWatcher extends WPPHEvent
281
+ {
282
+ /**
283
+ * @internal
284
+ * Hooks to the login event
285
+ * @param $user_login
286
+ * @param WP_User $user
287
+ */
288
+ public static function watchEventLogin($user_login, $user)
289
+ {
290
+ wpphLog(__METHOD__.'() triggered by hook.');
291
+ self::_addLogEvent(1000, $user->ID);
292
+ }
293
+ /**
294
+ * @internal
295
+ * Hooks to the logout event
296
+ */
297
+ public static function watchEventLogout()
298
+ {
299
+ wpphLog(__METHOD__.'() triggered by hook.');
300
+ self::_addLogEvent(1001, wp_get_current_user()->ID);
301
+ }
302
+
303
+ /**
304
+ * @internal
305
+ * Hooks to the user register event
306
+ */
307
+ public static function watchEventUserRegister($user_id)
308
+ {
309
+ wpphLog(__METHOD__.'() triggered by hook.');
310
+
311
+ global $current_user;
312
+ get_currentuserinfo();
313
+
314
+ $un = (empty($current_user->user_login) ? 'System' : $current_user->user_login);
315
+ $uInfo = WPPHDB::getUserInfo($user_id);
316
+ $nu = $uInfo['userName'];
317
+ $nur = ucfirst($uInfo['userRole']);
318
+
319
+ // %s created new user %s with role %s
320
+ $eventData = serialize(array($un, $nu, $nur));
321
+
322
+ if($un == 'System')
323
+ {
324
+ // A new user with the username %username% has registered with the role of %user_role%
325
+ $eventData = serialize(array($nu, $nur));
326
+ self::_addLogEvent(4000, 0, WPPHUtil::getIP(), $eventData);
327
+ }
328
+ else {
329
+ // %s created new user %s with role %s
330
+ $eventData = serialize(array($un, $nu, $nur));
331
+ self::_addLogEvent(4001, $current_user->ID, WPPHUtil::getIP(), $eventData);
332
+ }
333
+ }
334
+
335
+ /**
336
+ * @internal
337
+ * Hooks to the events deletion event
338
+ */
339
+ public static function watchDeleteEvents()
340
+ {
341
+ wpphLog(__METHOD__.'() triggered by hook.');
342
+
343
+ // check settings and delete the events (if any)
344
+ $settings = WPPH::getPluginSettings();
345
+ if($settings->cleanupRan == 1){
346
+ wpphLog(__METHOD__.'() Ignored. Cleanup already ran today.');
347
+ return;
348
+ }
349
+ // check to see how we should do the cleanup (by days or by number)
350
+ $cleanupType = 1; // by number by default
351
+
352
+ if(!empty($settings->daysToKeep)){
353
+ $cleanupType = 0;
354
+ }
355
+
356
+ // by days
357
+ if($cleanupType == 0)
358
+ {
359
+ if(self::_deleteEventsOlderThan($settings->daysToKeep)){
360
+ $settings->cleanupRan = 1;
361
+ $settings->lastCleanup = time();
362
+ }
363
+ }
364
+ // by number
365
+ else
366
+ {
367
+ if(self::_deleteEventsGreaterThan($settings->eventsToKeep)){
368
+ $settings->cleanupRan = 1;
369
+ $settings->lastCleanup = time();
370
+ }
371
+ }
372
+ WPPH::updatePluginSettings($settings);
373
+ if($settings->cleanupRan == 1){
374
+ //#! add event success
375
+ wpphLog(__METHOD__.'() Cleanup complete.');
376
+ self::_addLogEvent(6000, 0);
377
+ }
378
+ }
379
+ //@internal
380
+ // delete by days
381
+ private static function _deleteEventsOlderThan($days = 1)
382
+ {
383
+ $query = "DELETE FROM ".WPPHDatabase::getFullTableName('main')." WHERE EventDate > (NOW() - INTERVAL ".$days." DAY)";
384
+ global $wpdb;
385
+ $result = $wpdb->query($query);
386
+ if($result === false){ $status = 'Error executing query'; }
387
+ else { $status = 'Query executed'; }
388
+ wpphLog(__METHOD__.'('.$days.') called.', array('query'=>$query, 'status'=>$status, 'rowsDeleted'=> (int)$result));
389
+ return ($result !== false);
390
+ }
391
+ //@internal
392
+ // delete by number
393
+ private static function _deleteEventsGreaterThan($number = 10000)
394
+ {
395
+ if($number > 10000){ $number = 10000; }
396
+ global $wpdb;
397
+ $tableName = WPPHDatabase::getFullTableName('main');
398
+ $count = $wpdb->get_var("SELECT COUNT(0) FROM $tableName");
399
+ if(empty($count)){
400
+ wpphLog(__METHOD__.'('.$number.') called. Ignored, there are no events in the database');
401
+ return;
402
+ }
403
+ $keep = $number;
404
+ if($count > $keep)
405
+ {
406
+ $limit = $count - $keep;
407
+ $query = "DELETE FROM $tableName ORDER BY EventDate LIMIT $limit";
408
+ }
409
+ else {
410
+ wpphLog(__METHOD__.'('.$number.') called. Ignored, there are not enough events to trigger this action.');
411
+ return;
412
+ }
413
+
414
+ $result = $wpdb->query($query);
415
+ if($result === false){ $status = 'Error executing query'; }
416
+ else { $status = 'Query executed'; }
417
+ wpphLog(__METHOD__.'('.$number.') called.', array('query'=>$query, 'status'=>$status, 'rowsAffected'=> (int)$result));
418
+ return ($result !== false);
419
+ }
420
+
421
+ /**
422
+ * @internal
423
+ * Fired on login failure
424
+ */
425
+ public static function watchLoginFailure($username)
426
+ {
427
+ wpphLog(__METHOD__.'() triggered by hook.', array('username'=>$username));
428
+ self::_addLogEvent(1002,0,WPPHUtil::getIP(),serialize(array($username)));
429
+ }
430
+
431
+ /**
432
+ * @internal
433
+ * @param $userID the id of the user being updated
434
+ * Triggered when a user's role is updated
435
+ */
436
+ public static function watchUserRoleUpdated($userID)
437
+ {
438
+ wpphLog(__METHOD__.'() triggered by hook.');
439
+
440
+ // get info for the currently logged in user
441
+ $current_user = wp_get_current_user();
442
+ $cid = $current_user->ID;
443
+ $cName = $current_user->user_login;
444
+
445
+ // get info for the currently updated user
446
+ $editedUserInfo = WPPHDB::getUserInfo($userID);
447
+ $editedUserName = $editedUserInfo['userName'];
448
+ $initialUserRole = $editedUserInfo['userRole'];
449
+
450
+ if(empty($_POST['role'])){
451
+ wpphLog(__METHOD__.'() Ignored. Role did not change.');
452
+ return;
453
+ }
454
+
455
+ $updatedRole = trim($_POST['role']);
456
+ if(strcasecmp($initialUserRole, $updatedRole)==0){
457
+ wpphLog(__METHOD__.'() Ignored. Role did not change.');
458
+ return;
459
+ }
460
+
461
+ // The role of user <strong>%s</strong> was changed from <strong>%s</strong> to <strong>%s</strong> by <strong>%s</strong>
462
+ $eData = serialize(array($editedUserName, ucfirst($initialUserRole), ucfirst($updatedRole), $cName));
463
+
464
+ self::_addLogEvent(4002, $cid, WPPHUtil::getIP(), $eData);
465
+ }
466
+
467
+ /**
468
+ * @internal
469
+ * @param $userID the id of the user being updated
470
+ * Triggered when a user's role is updated
471
+ */
472
+ public static function watchUserPasswordUpdated($userID)
473
+ {
474
+ if(empty($_POST['pass1'])){
475
+ wpphLog(__METHOD__.'() triggered by hook. Ignored. Password did not change.');
476
+ return;
477
+ }
478
+ wpphLog(__METHOD__.'() triggered by hook.');
479
+
480
+ // get info for the currently logged in user
481
+ $current_user = wp_get_current_user();
482
+ $cid = $current_user->ID;
483
+ $cName = $current_user->user_login;
484
+
485
+ // check to see who's who here
486
+ if($userID == $cid)
487
+ {
488
+ self::_addLogEvent(4003, $cid);
489
+ return;
490
+ }
491
+
492
+ // get info for the currently updated user
493
+ $editedUserInfo = WPPHDB::getUserInfo($userID);
494
+ $editedUserName = $editedUserInfo['userName'];
495
+ $editedUserRole = $editedUserInfo['userRole'];
496
+
497
+ // <strong>%s</strong> changed the password for <strong>%s</strong> with the role of <strong>%s</strong>
498
+ $eData = serialize(array($cName, $editedUserName, ucfirst($editedUserRole)));
499
+
500
+ self::_addLogEvent(4004, $cid, WPPHUtil::getIP(), $eData);
501
+ }
502
+
503
+ /**
504
+ * @internal
505
+ * @param $userID the id of the user being updated
506
+ * Triggered when a user's email is updated
507
+ */
508
+ public static function watchUserEmailUpdated($userID)
509
+ {
510
+ if(empty($_POST['email'])){
511
+ wpphLog(__METHOD__.'() triggered by hook. Ignored. Email did not change.');
512
+ return;
513
+ }
514
+ wpphLog(__METHOD__.'() triggered by hook.');
515
+
516
+ global $wpdb;
517
+
518
+ // get info for the currently logged in user
519
+ $current_user = wp_get_current_user();
520
+ $cid = $current_user->ID;
521
+ $cName = $current_user->user_login;
522
+
523
+ // get current user's email
524
+ $oldEmail = $wpdb->get_var("SELECT user_email FROM ".$wpdb->users." WHERE ID = $userID");
525
+ // new email
526
+ $newEmail = mysql_real_escape_string($_POST['email']);
527
+
528
+ // check to see who's who here
529
+ if($userID == $cid)
530
+ {
531
+
532
+ self::_addLogEvent(4005, $cid, WPPHUtil::getIP(), serialize(array($oldEmail, $newEmail)));
533
+ return;
534
+ }
535
+
536
+ // check if email updated
537
+ if($_POST['email'] == $oldEmail){
538
+ wpphLog(__METHOD__.'() Ignored. Email did not change.');
539
+ return;
540
+ }
541
+
542
+ // get info for the currently updated user
543
+ $editedUserInfo = WPPHDB::getUserInfo($userID);
544
+ $editedUserName = $editedUserInfo['userName'];
545
+
546
+ // %user_making_change% changed the email address of user account %user% from %old_email% to %new_email%
547
+ $eData = serialize(array($cName, $editedUserName, $oldEmail, $newEmail));
548
+
549
+ self::_addLogEvent(4006, $cid, WPPHUtil::getIP(), $eData);
550
+ }
551
+
552
+ /**
553
+ * @internal
554
+ * @param $userID the id of the user being deleted
555
+ * Triggered when a user is deleted
556
+ */
557
+ public static function watchUserDeletion($userID)
558
+ {
559
+ wpphLog(__METHOD__.'() triggered by hook.');
560
+
561
+ global $current_user;
562
+ get_currentuserinfo();
563
+
564
+ $un = (empty($current_user->user_login) ? 'System' : $current_user->user_login);
565
+ if($un == 'System'){
566
+ $currentUserID = 0;
567
+ }
568
+ else { $currentUserID = $current_user->ID; }
569
+
570
+ // get info for the currently deleted user
571
+ $_userInfo = WPPHDB::getUserInfo($userID);
572
+ $_userName = $_userInfo['userName'];
573
+ $_userRole = ucfirst($_userInfo['userRole']);
574
+
575
+ self::_addLogEvent(4007, $currentUserID, WPPHUtil::getIP(), serialize(array($_userName, $_userRole, $un)));
576
+ // delete transient as well
577
+ delete_transient(sha1($userID));
578
+ }
579
+
580
+ public static function watchPluginActivate()
581
+ {
582
+ wpphLog(__METHOD__.'() triggered by hook.');
583
+
584
+ // activate
585
+ $a = (empty($_GET['plugin']) ? '' : $_GET['plugin']);
586
+
587
+ // if active plugin edited (Using the plugin editor)
588
+ if(empty($a)){
589
+ $a = (empty($_GET['file']) ? '' : $_GET['file']);
590
+ }
591
+
592
+ $b = '';
593
+ if(!empty($a)){ $b = dirname($a); }
594
+
595
+ // get info for the currently logged in user
596
+ $current_user = wp_get_current_user();
597
+
598
+ self::_addLogEvent(5000,$current_user->ID, WPPHUtil::getIP(), serialize(array($b,$a)));
599
+ wpphLog('Plugin activated.', array('plugin file'=>$a));
600
+ }
601
+ public static function watchPluginDeactivate()
602
+ {
603
+ wpphLog(__METHOD__.'() triggered by hook.');
604
+
605
+ // deactivate
606
+ $a = (empty($_GET['plugin']) ? '' : $_GET['plugin']);
607
+ $b = '';
608
+ if(!empty($a)){ $b = dirname($a); }
609
+
610
+ // get info for the currently logged in user
611
+ $current_user = wp_get_current_user();
612
+
613
+ self::_addLogEvent(5001,$current_user->ID, WPPHUtil::getIP(), serialize(array($b,$a)));
614
+ wpphLog('Plugin deactivated.', array('plugin file'=>$a));
615
+
616
+ }
617
+
618
+
619
+ public static function WatchBlogActivity($newStatus, $oldStatus, $post)
620
+ {
621
+ $a = func_get_args();
622
+ wpphLog(__METHOD__.'() triggered by hook.', $a);
623
+
624
+ $postID = $post->ID;
625
+ $hPid = md5($postID);
626
+ $currentUserID = wp_get_current_user()->ID;
627
+ $userID = $postAuthorID = $post->post_author;
628
+ if($currentUserID != $postAuthorID){
629
+ // someone else is doing this
630
+ $userID = $currentUserID;
631
+ }
632
+
633
+ // We're dealing with posts
634
+ if($post->post_type == 'post')
635
+ {
636
+ // if transient exists then this is an update, otherwise this is a new post
637
+ $value = get_transient($hPid);
638
+
639
+ if($newStatus == 'inherit' && $oldStatus == 'new')
640
+ {
641
+ if(empty($value))
642
+ {
643
+ // Created a new blog post called %Post Title%. Blog post ID is %ID%
644
+ self::_addLogEvent(2000, $userID, WPPHUtil::getIP(), serialize(array($post->post_title,$postID)));
645
+ wpphLog('New blog post saved as draft.', array('title'=>$post->post_title));
646
+ set_transient($hPid, $postID, 0);
647
+ return;
648
+ }
649
+ }
650
+
651
+ // #2000 , #2003
652
+ if((($oldStatus == $newStatus) == 'draft') && $post->post_status == 'draft')
653
+ {
654
+ // so we skip generating multiple events
655
+ if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; }
656
+
657
+ // #2000 : new blog post [as draft]
658
+ if(empty($value))
659
+ {
660
+ // Created a new blog post called %Post Title%. Blog post ID is %ID%
661
+ self::_addLogEvent(2000, $userID, WPPHUtil::getIP(), serialize(array($post->post_title,$postID)));
662
+ wpphLog('New blog post saved as draft.', array('title'=>$post->post_title));
663
+ set_transient($hPid, $postID, 0);
664
+ return;
665
+
666
+ }
667
+ // #2003 : Updated draft post
668
+ else
669
+ {
670
+ // invalid
671
+ if($value != $postID) { return; }
672
+
673
+ // Modified the draft blog post %post_title%. Blog post ID is %ID%
674
+ self::_addLogEvent(2003, $userID, WPPHUtil::getIP(), serialize(array($post->post_title,$postID)));
675
+ wpphLog('Draft blog post updated.', array('title'=>$post->post_title));
676
+ return;
677
+
678
+ }
679
+ }
680
+ // #2001 : Published a blog post
681
+ elseif(($oldStatus == 'draft' && $newStatus == 'publish') && $post->post_status == 'publish')
682
+ {
683
+ // Published a blog post called %Post_Title%. Blog post URL is %Post_URL%
684
+ self::_addLogEvent(2001, $userID, WPPHUtil::getIP(), serialize(array($post->post_title,$post->guid)));
685
+ wpphLog('Blog post published.', array('title'=>$post->post_title));
686
+ return;
687
+ }
688
+ // #2002 : Updated published post
689
+ elseif(($oldStatus == 'publish' && $newStatus == 'publish') && $post->post_status == 'publish')
690
+ {
691
+ // Modified the published blog post %post_title%. Blog post URL is %post_URL%
692
+ self::_addLogEvent(2002, $userID, WPPHUtil::getIP(), serialize(array($post->post_title,$post->guid)));
693
+ wpphLog('Published blog post updated.', array('title'=>$post->post_title));
694
+ return;
695
+ }
696
+ }
697
+
698
+ // We're dealing with pages
699
+ elseif($post->post_type == 'page')
700
+ {
701
+ // if transient exists then this is an update, otherwise this is a new page
702
+ $value = get_transient($hPid);
703
+
704
+ if($newStatus == 'inherit' && $oldStatus == 'new')
705
+ {
706
+ if(empty($value))
707
+ {
708
+ // Created a new page called %page_title%. Page ID is %ID%
709
+ self::_addLogEvent(2004, $userID, WPPHUtil::getIP(), serialize(array($post->post_title,$postID)));
710
+ set_transient($hPid, $postID, 0);
711
+ wpphLog('New page saved as draft.', array('title'=>$post->post_title));
712
+ return;
713
+ }
714
+ }
715
+
716
+ // #2004 , #2007
717
+ if((($oldStatus == $newStatus) == 'draft') && $post->post_status == 'draft')
718
+ {
719
+ // so we skip generating multiple events
720
+ if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; }
721
+
722
+ // #2004 : new page [as draft]
723
+ if(empty($value))
724
+ {
725
+ // Created a new page called %page_title%. Page ID is %ID%
726
+ self::_addLogEvent(2004, $userID, WPPHUtil::getIP(), serialize(array($post->post_title,$postID)));
727
+ set_transient($hPid, $postID, 0);
728
+ wpphLog('New page saved as draft.', array('title'=>$post->post_title));
729
+ return;
730
+
731
+ }
732
+ // #2007 : Updated draft page
733
+ else
734
+ {
735
+ // invalid
736
+ if($value != $postID) { return; }
737
+ // Modified the draft page %page_title%. Page ID is %ID%
738
+ self::_addLogEvent(2007, $userID, WPPHUtil::getIP(), serialize(array($post->post_title,$postID)));
739
+ wpphLog('Draft page updated.', array('title'=>$post->post_title));
740
+ return;
741
+
742
+ }
743
+ }
744
+ // #2005 : Published a page
745
+ elseif(($oldStatus == 'draft' && $newStatus == 'publish') && $post->post_status == 'publish')
746
+ {
747
+ // Published a page called %page_title%. Page URL is %URL%
748
+ self::_addLogEvent(2005, $userID, WPPHUtil::getIP(), serialize(array($post->post_title,$post->guid)));
749
+ wpphLog('Page published.', array('title'=>$post->post_title));
750
+ return;
751
+ }
752
+ // #2006 : Updated published page
753
+ elseif(($oldStatus == 'publish' && $newStatus == 'publish') && $post->post_status == 'publish')
754
+ {
755
+ // Modified the published page %page_title%. Page URL is %URL%
756
+ self::_addLogEvent(2006, $userID, WPPHUtil::getIP(), serialize(array($post->post_title,$post->guid)));
757
+ wpphLog('Published page updated.', array('title'=>$post->post_title));
758
+ return;
759
+ }
760
+ }
761
+ }
762
+
763
+ public static function watchTrash($postID)
764
+ {
765
+ wpphLog(__METHOD__.'() triggered by hook.');
766
+
767
+ $hPid = md5($postID);
768
+
769
+ // get info for the currently logged in user
770
+ $current_user = wp_get_current_user();
771
+
772
+ global $wpdb;
773
+
774
+ $postInfo = $wpdb->get_row("SELECT post_title, post_type FROM ".$wpdb->posts." WHERE ID = ".$postID);
775
+ $postTitle = $postInfo->post_title;
776
+ $postType = $postInfo->post_type;
777
+
778
+ if($postType == 'post')
779
+ {
780
+ // Deleted the blog post %Title%. Blog post ID is %ID%
781
+ self::_addLogEvent(2008, $current_user->ID, WPPHUtil::getIP(), serialize(array($postTitle,$postID)));
782
+ delete_transient($hPid);
783
+ wpphLog('Blog post deleted.', array('title'=>$postTitle, 'id'=>$postID));
784
+ }
785
+ elseif($postType == 'page')
786
+ {
787
+ // Deleted the page %Title%. Page ID is %ID%
788
+ self::_addLogEvent(2009, $current_user->ID, WPPHUtil::getIP(), serialize(array($postTitle,$postID)));
789
+ delete_transient($hPid);
790
+ wpphLog('Page deleted.', array('title'=>$postTitle, 'id'=>$postID));
791
+ }
792
+ }
793
+
794
+ // 2010
795
+ public static function watchFileUploaded($attachmentID)
796
+ {
797
+ global $wpdb;
798
+
799
+ // get info for the currently logged in user
800
+ $current_user = wp_get_current_user();
801
+
802
+ $rowData = $wpdb->get_row("SELECT guid FROM ".$wpdb->posts." WHERE ID = ".$attachmentID);
803
+ $fileName = basename($rowData->guid);
804
+ $dirName = dirname($rowData->guid);
805
+
806
+ // Uploaded the file %file name$ in %file location%
807
+ self::_addLogEvent(2010, $current_user->ID, WPPHUtil::getIP(), serialize(array($fileName, $dirName)));
808
+ wpphLog('File uploaded.', array('title'=>$fileName, 'url'=>$dirName));
809
+ }
810
+ // 2011
811
+ public static function watchFileUploadedDeleted($attachmentID)
812
+ {
813
+ global $wpdb;
814
+
815
+ // get info for the currently logged in user
816
+ $current_user = wp_get_current_user();
817
+
818
+ $rowData = $wpdb->get_row("SELECT post_title, guid FROM ".$wpdb->posts." WHERE ID = ".$attachmentID);
819
+
820
+ // Deleted file %file name$ from %file_location%
821
+ self::_addLogEvent(2011, $current_user->ID, WPPHUtil::getIP(), serialize(array($rowData->post_title,dirname($rowData->guid))));
822
+ wpphLog('File deleted.', array('title'=>$rowData->post_title, 'url'=>dirname($rowData->guid)));
823
+ }
824
+
825
+ // 2012, 2013
826
+ public static function watchFileDeletion($postID)
827
+ {
828
+ global $wpdb;
829
+
830
+ $postInfo = $wpdb->get_row("SELECT post_title, post_type FROM ".$wpdb->posts." WHERE ID = ".$postID);
831
+ $postTitle = $postInfo->post_title;
832
+ $postType = $postInfo->post_type;
833
+
834
+ // get info for the currently logged in user
835
+ $userID = wp_get_current_user()->ID;
836
+
837
+ if('post' == $postType)
838
+ {
839
+ self::_addLogEvent(2012, $userID, WPPHUtil::getIP(), serialize(array($postTitle)));
840
+ wpphLog('Post trashed.', array('name'=>$postTitle));
841
+ }
842
+ elseif ('page' == $postType)
843
+ {
844
+ self::_addLogEvent(2013, $userID, WPPHUtil::getIP(), serialize(array($postTitle)));
845
+ wpphLog('Page trashed.', array('name'=>$postTitle));
846
+ }
847
+ }
848
+
849
+ // 2014, 2015
850
+ public static function watchTrashUndo($postID)
851
+ {
852
+ global $wpdb;
853
+
854
+ $postInfo = $wpdb->get_row("SELECT post_title, post_type FROM ".$wpdb->posts." WHERE ID = ".$postID);
855
+ $postTitle = $postInfo->post_title;
856
+ $postType = $postInfo->post_type;
857
+
858
+ // get info for the currently logged in user
859
+ $userID = wp_get_current_user()->ID;
860
+
861
+ if('post' == $postType)
862
+ {
863
+ self::_addLogEvent(2014, $userID, WPPHUtil::getIP(), serialize(array($postTitle)));
864
+ wpphLog('Post restored from trash.', array('name'=>$postTitle));
865
+ }
866
+ elseif ('page' == $postType)
867
+ {
868
+ self::_addLogEvent(2015, $userID, WPPHUtil::getIP(), serialize(array($postTitle)));
869
+ wpphLog('Page restored from trash.', array('name'=>$postTitle));
870
+ }
871
+ }
872
+
873
+
874
+ // 3000 - Theme activated
875
+ public static function watchThemeChange($themeName)
876
+ {
877
+ // get info for the currently logged in user
878
+ $current_user = wp_get_current_user();
879
+
880
+ // // Activated the theme %themeName%
881
+ self::_addLogEvent(3000, $current_user->ID, WPPHUtil::getIP(), serialize(array($themeName)));
882
+ wpphLog('Theme activated.', array('name'=>$themeName));
883
+ }
884
+ }
inc/WPPHLogger.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // custom function
3
+ function wpphLog($message, array $data = array()){ WPPHLogger::write($message,$data); }
4
+ /*
5
+ * @internal
6
+ * Debug class
7
+ * DO NOT enable this on live website
8
+ */
9
+ class WPPHLogger
10
+ {
11
+ private static $_debugLoggingEnabled = false;
12
+
13
+ private static $_fileName = 'debug.log';
14
+
15
+ public static function enableDebugLogging(){ self::$_debugLoggingEnabled = true; }
16
+ public static function enableErrorLogging(){ ini_set('error_log', WPPH_PLUGIN_DIR.'error.log'); }
17
+
18
+ public static function write($message, array $data = array())
19
+ {
20
+ if(!self::$_debugLoggingEnabled) { return; }
21
+ $m = '['.@date("D, M d, Y @H:i:s").'] Debug: '.$message;
22
+ if(! empty($data)) {
23
+ $m .= ' Data: '.var_export($data, true);
24
+ }
25
+ $m .= PHP_EOL;
26
+ @file_put_contents(WPPH_PLUGIN_DIR.self::$_fileName,$m,FILE_APPEND);
27
+ }
28
+ }
inc/WPPHUtil.php ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @kyos
4
+ * Class WPPHUtil
5
+ * Contains utility methods
6
+ */
7
+ class WPPHUtil
8
+ {
9
+ public static function getIP()
10
+ {
11
+ if ($_SERVER['HTTP_CLIENT_IP']) $ip = $_SERVER['HTTP_CLIENT_IP'];
12
+ else if($_SERVER['HTTP_X_FORWARDED_FOR']) $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
13
+ else if($_SERVER['HTTP_X_FORWARDED']) $ip = $_SERVER['HTTP_X_FORWARDED'];
14
+ else if($_SERVER['HTTP_FORWARDED_FOR']) $ip = $_SERVER['HTTP_FORWARDED_FOR'];
15
+ else if($_SERVER['HTTP_FORWARDED']) $ip = $_SERVER['HTTP_FORWARDED'];
16
+ else if($_SERVER['REMOTE_ADDR']) $ip = $_SERVER['REMOTE_ADDR'];
17
+ else $ip = '0.0.0.0';
18
+ return $ip;
19
+ }
20
+
21
+ /*
22
+ * Will respond to the ajax requests getting the events
23
+ */
24
+ public static function get_events_html()
25
+ {
26
+ //#! VALIDATE REQUEST
27
+ $rm = strtoupper($_SERVER['REQUEST_METHOD']);
28
+ if($rm != 'POST'){
29
+ exit('<tr><td colspan="7"><span>'.__('Error: Invalid request').'</span></td></tr>');
30
+ }
31
+
32
+ // set defaults
33
+ $orderBy = 'EventNumber';
34
+ $sort = 'desc';
35
+ $limit = array(0, 50);
36
+ $orderDescending = true;
37
+ $pageNumber = 0;
38
+
39
+ if(!empty($_POST['orderBy'])) { $orderBy = $_POST['orderBy']; }
40
+ if(!empty($_POST['sort'])) {
41
+ if(0 == strcasecmp($_POST['sort'],'asc')){
42
+ $sort = 'asc';
43
+ $orderDescending = false;
44
+ }
45
+ }
46
+ if(isset($_POST['offset'])) { $limit[0] = intval($_POST['offset']); }
47
+ if(isset($_POST['count'])) { $limit[1] = intval($_POST['count']); }
48
+ if(isset($_POST['pageNumber'])) { $pageNumber = intval($_POST['pageNumber']); }
49
+
50
+ function __formatJsonOutput(array $sourceData=array(), $error=''){
51
+ return json_encode(array(
52
+ 'dataSource' => $sourceData,
53
+ 'error' => $error
54
+ ));
55
+ };
56
+
57
+ // get events
58
+ $events = WPPHEvent::getEvents($orderBy, $sort, $limit);
59
+ $eventsNum = count($events);
60
+ $allEventsCount = WPPHDB::getEventsCount();
61
+
62
+ if($eventsNum == 0){
63
+ exit(__formatJsonOutput(array(),__('There are no events to display.')));
64
+ }
65
+
66
+ $out = array();
67
+ $out['events'] = array();
68
+
69
+ //#! prepare output
70
+ foreach($events as $entry)
71
+ {
72
+ $entry = (object)$entry;
73
+ $eventNumber = $entry->EventNumber;
74
+ $EventID = $entry->EventID;
75
+ $EventDate = $entry->EventDate;
76
+ $userIP = $entry->UserIP;
77
+ $UserID = $entry->UserID;
78
+ $eventData = unserialize($entry->EventData); //<< values to use for event description
79
+
80
+ // get User Info
81
+ if($UserID == 0){ $username = 'System'; }
82
+ else {
83
+ $user_info = get_userdata($UserID);
84
+ $username = $user_info->user_login;
85
+ $first_name = $user_info-> user_firstname;
86
+ $last_name = $user_info-> user_lastname;
87
+ $username = "$username ($first_name $last_name)";
88
+ }
89
+
90
+ // get event details
91
+ $eventDetails = WPPHEvent::getEventDetailsData($EventID);
92
+
93
+ // format event description message
94
+ if(empty($eventData)) { $evm = $eventDetails->EventDescription; }
95
+ else { $evm = vsprintf($eventDetails->EventDescription, $eventData); }
96
+
97
+ $e = array(
98
+ 'eventNumber' => $eventNumber,
99
+ 'eventId' => $EventID,
100
+ 'EventType' => $eventDetails->EventType,
101
+ 'eventDate' => $EventDate,
102
+ 'ip' => $userIP,
103
+ 'user' => $username,
104
+ 'description' => $evm
105
+ );
106
+ array_push($out['events'], $e);
107
+ }
108
+ $out['eventsCount'] = $allEventsCount;
109
+
110
+ exit(__formatJsonOutput($out,''));
111
+ }
112
+
113
+ }
pages/about.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php /**
2
+ * @kyos
3
+ * About us page
4
+ */ if(! WPPH::canRun()){ return; } ?>
5
+ <div id="wpph-pageWrapper" class="wrap">
6
+ <h2 class="pageTitle pageTitle-about"><?php echo __('About us');?></h2>
7
+ <div>
8
+ <p><?php echo sprintf(
9
+ __('WP Security Audit Log is a WordPress security plugin developed by %s.'),
10
+ '<a href="http://www.wpprohelp.com">WPProHelp.com</a>');?></p>
11
+ </div>
12
+ </div>
pages/dashboard.php ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php /**
2
+ * @kyos
3
+ * Dashboard page
4
+ */ if(! WPPH::canRun()){ return; }?>
5
+ <div id="wpph-pageWrapper" class="wrap">
6
+ <h2 class="pageTitle pageTitle-eventViewer"><?php echo __('Audit Log Viewer');?></h2>
7
+ <div id="EventViewerWrapper">
8
+ <div style="overflow: hidden; display: block; clear: both;">
9
+ <div class="tablenav top" style="overflow: hidden; padding: 4px 0;">
10
+ <div class="alignleft">
11
+ <div style="overflow: hidden;">
12
+ <input type="button" class="buttonRefreshEventsList button" value="<?php echo __('Refresh Events List');?>"
13
+ style="float: left; display: block;" data-bind="disable: loading, click: cleanRefresh"/>
14
+ <span class="ajaxLoaderWrapper" style="float: left; display: block; width: 20px; height: 20px; padding: 7px 7px;"><img/></span>
15
+ </div>
16
+ </div>
17
+ <div class="alignleft actions" style="overflow: hidden;">
18
+ <label class="alignleft" style="margin: 5px 5px 0 0;"><?php echo __('Number of events per page:');?></label>
19
+ <select name="actionLimit1" class="actionLimit" data-bind="options: availablePageSize, value: selectedPageSize"></select>
20
+ <input type="button" value="Apply" class="button action" data-bind="disable: loading, click: applyPageSize">
21
+ </div>
22
+ <div class="paginationWrapper" data-bind="visible: totalEventsCount">
23
+ <span class="showPages"><span class="span1" data-bind="text: totalEventsCount() > 0 ? 1 + offset() : 0"></span>-
24
+ <span class="span2" data-bind="text: offset() + events().length"></span> <?php echo __('of');?>
25
+ <span class="span3" data-bind="text: totalEventsCount"></span></span>
26
+ <div class="buttonsWrapper">
27
+ <button class="pageButton buttonNext" title="Next" href="#" data-bind="disable: loading, click: nextPage, css: {wpphButtonDisabled: offset() + events().length >= totalEventsCount() - 1}"><span>&gt;</span></button>
28
+ <button class="pageButton buttonPrevious" title="Previous" href="#" data-bind="disable: loading, click: prevPage, css: {wpphButtonDisabled: offset() <= 0}"><span>&lt;</span></button>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ <table class="wp-list-table widefat fixed" cellspacing="0" cellpadding="0">
34
+ <thead>
35
+ <tr data-bind="foreach: columns">
36
+ <th class="manage-column column-left-align" scope="col"
37
+ data-bind="style: {width: columnWidth}, css: { sortable: sortable, sorted: sorted, desc: sortable && sortedDescending(), asc: sortable && !sortedDescending()}">
38
+ <a href="#" data-bind="disable: $root.loading, click: $data.sortable ? $root.applySorting.bind($data.columnName, $root) : function() { return false; }">
39
+ <span data-bind="text: columnHeader"></span>
40
+ <span class="sorting-indicator"></span>
41
+ </a>
42
+ </th>
43
+ </tr>
44
+ </thead>
45
+ <tfoot>
46
+ <tr data-bind="foreach: columns">
47
+ <th class="manage-column column-left-align" scope="col"
48
+ data-bind="style: {width: columnWidth}, css: { sortable: sortable, sorted: sorted, desc: sortable && sortedDescending(), asc: sortable && !sortedDescending()}">
49
+ <a href="#" data-bind="disable: $root.loading, click: $data.sortable ? $root.applySorting.bind($data.columnName, $root) : function() { return false; }">
50
+ <span data-bind="text: columnHeader"></span>
51
+ <span class="sorting-indicator"></span>
52
+ </a>
53
+ </th>
54
+ </tr>
55
+ </tfoot>
56
+ <tbody id="the-list">
57
+ <tr data-bind="if: events().length == 0"><td style="padding: 4px !important;" colspan="7"><?php echo __('No events');?></td></tr>
58
+ <!-- ko foreach: events -->
59
+ <tr data-bind="css: {'row-0': ($index() % 2) == 0, 'row-1': ($index() % 2) != 0}">
60
+ <td class="column-event_number"><span data-bind="text: eventNumber"></span></td>
61
+ <td class="column-event_id"><span data-bind="text: eventId"></span></td>
62
+ <td class="column-event_date"><span data-bind="text: eventDate"></span></td>
63
+ <td class="column-event_category"><span data-bind="text: EventType"></span></td>
64
+ <td class="column-ip"><span data-bind="text: ip"></span></td>
65
+ <td class="column-user"><span data-bind="text: user"></span></td>
66
+ <td class="column-description"><span data-bind="html: description"></span></td>
67
+ </tr>
68
+ <!-- /ko -->
69
+ </tbody>
70
+ </table>
71
+ <div style="overflow: hidden; display: block; clear: both;">
72
+ <div class="tablenav top" style="overflow: hidden; padding: 4px 0;">
73
+ <div class="alignleft">
74
+ <div style="overflow: hidden;">
75
+ <input type="button" class="buttonRefreshEventsList button" value="<?php echo __('Refresh Events List');?>"
76
+ style="float: left; display: block;" data-bind="disable: loading, click: cleanRefresh"/>
77
+ <span class="ajaxLoaderWrapper" style="float: left; display: block; width: 20px; height: 20px; padding: 7px 7px;"><img/></span>
78
+ </div>
79
+ </div>
80
+ <div class="alignleft actions" style="overflow: hidden;">
81
+ <label class="alignleft" style="margin: 5px 5px 0 0;"><?php echo __('Number of events per page:');?></label>
82
+ <select name="actionLimit1" class="actionLimit" data-bind="options: availablePageSize, value: selectedPageSize"></select>
83
+ <input type="button" value="Apply" class="button action" data-bind="disable: loading, click: applyPageSize">
84
+ </div>
85
+ <div class="paginationWrapper" data-bind="visible: totalEventsCount">
86
+ <span class="showPages"><span class="span1" data-bind="text: totalEventsCount() > 0 ? 1 + offset() : 0"></span>-
87
+ <span class="span2" data-bind="text: offset() + events().length"></span> <?php echo __('of');?>
88
+ <span class="span3" data-bind="text: totalEventsCount"></span></span>
89
+ <div class="buttonsWrapper">
90
+ <button class="pageButton buttonNext" title="<?php echo __('Next');?>" href="#" data-bind="disable: loading, click: nextPage, css: {wpphButtonDisabled: offset() >= totalEventsCount() - 1}"><span>&gt;</span></button>
91
+ <button class="pageButton buttonPrevious" title="<?php echo __('Previous');?>" href="#" data-bind="disable: loading, click: prevPage, css: {wpphButtonDisabled: offset() <= 0}"><span>&lt;</span></button>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </div>
96
+
97
+ </div>
98
+ </div>
99
+
100
+ <script type="text/javascript">
101
+ // configure ajax loader
102
+ var __ajaxLoaderTargetElement__ = jQuery('.ajaxLoaderWrapper img');
103
+ var AjaxLoaderCreate = function(e){
104
+ var imgPath = "<?php echo WPPH_PLUGIN_URL.'res/img/ajax-loader.gif';?>";
105
+ e.attr('src',imgPath);
106
+ }(__ajaxLoaderTargetElement__);
107
+ var AjaxLoaderShow = function(e){ e.show(); };
108
+ var AjaxLoaderHide = function(e){ e.hide(); };
109
+
110
+ jQuery(document).ready(function($) {
111
+ var myViewModel = new AuditLogViewModel();
112
+ ko.applyBindings(myViewModel, $('#wpph-pageWrapper').get(0));
113
+ myViewModel.orderBy('EventNumber');
114
+ myViewModel.orderByDescending(true);
115
+ myViewModel.cleanRefresh(myViewModel);
116
+ });
117
+ </script>
pages/settings.php ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php /**
2
+ * @kyos
3
+ * Options page
4
+ */if(! WPPH::canRun()){ return; }
5
+
6
+ //#! defaults
7
+ $opt = WPPH::getPluginSettings();
8
+ $daysInput = 0;
9
+ $eventsNumber = 0;
10
+ $showEventsViewList = 50;
11
+
12
+ if(!empty($opt->daysToKeep)){
13
+ $daysInput = $opt->daysToKeep;
14
+ }
15
+ if(! empty($opt->eventsToKeep)){
16
+ $eventsNumber = $opt->eventsToKeep;
17
+ }
18
+ if(! empty($opt->showEventsViewList)){
19
+ $showEventsViewList = $opt->showEventsViewList;
20
+ }
21
+
22
+ //#! end defaults
23
+
24
+ $validationMessage = array();
25
+ //#! If post : section #1
26
+ if ( !empty($_POST['wpph_update_settings_field']) )
27
+ {
28
+ if(isset($_POST['wpph_update_settings_field'])){
29
+ if(!wp_verify_nonce($_POST['wpph_update_settings_field'],'wpph_update_settings')){
30
+ wp_die(__('Invalid request.'));
31
+ }
32
+ }
33
+ else {wp_die(__('Invalid request.'));}
34
+
35
+ // validate fields
36
+ $section = intval($_POST['sectionInputField']);
37
+ if(! in_array($section, array(1,2))){
38
+ $validationMessage['error'] = __('Error: Invalid form. Please try again.');
39
+ }
40
+
41
+ //#! get settings
42
+ $daysInput = $eventsNumber = 0;
43
+ $eventsNumber = 10000; // default
44
+ $opt = WPPH::getPluginSettings();
45
+ if($section == 1)
46
+ {
47
+ if(empty($_POST['daysInput'])){
48
+ $validationMessage['error'] = __('Error: Invalid form. Please try again.');
49
+ $hasErrors = true;
50
+ }
51
+ else
52
+ {
53
+ $daysInput = intval($_POST['daysInput']);
54
+
55
+ if($daysInput == 0){
56
+ $validationMessage['error'] = __('Please input the number of days.');
57
+ $hasErrors = true;
58
+ }
59
+ elseif($daysInput > 365){
60
+ $validationMessage['error'] = __('Incorrect number of days. Please specify a value between 1 and 365.');
61
+ $hasErrors = true;
62
+ }
63
+
64
+ if(! $hasErrors)
65
+ {
66
+ // reset events number
67
+ if(isset($opt->eventsToKeep)){
68
+ $opt->eventsToKeep = 0;
69
+ }
70
+ $opt->daysToKeep = $daysInput;
71
+ }
72
+ }
73
+ }
74
+ elseif($section == 2)
75
+ {
76
+ if(empty($_POST['eventsNumberInput'])){
77
+ $validationMessage['error'] = __('Error: Invalid form. Please try again.');
78
+ $hasErrors = true;
79
+ }
80
+ else
81
+ {
82
+ $eventsNumber = intval($_POST['eventsNumberInput']);
83
+
84
+ if($eventsNumber == 0){
85
+ $validationMessage['error'] = __('Please input the number of events to keep.');
86
+ $hasErrors = true;
87
+ }
88
+ elseif($eventsNumber > 10000){
89
+ $validationMessage['error'] = __('Incorrect number of events. Please specify a value between 1 and 10,000.');
90
+ $hasErrors = true;
91
+ }
92
+
93
+ if(! $hasErrors)
94
+ {
95
+ // reset days
96
+ if(isset($opt->daysToKeep)){
97
+ $opt->daysToKeep = 0;
98
+ }
99
+ $opt->eventsToKeep = $eventsNumber;
100
+ }
101
+ }
102
+ }
103
+ else { $validationMessage['error'] = __('Error: Invalid form. Please try again.'); }
104
+
105
+
106
+ if(! $hasErrors)
107
+ {
108
+ $opt->cleanupRan = 0;
109
+ WPPH::updatePluginSettings($opt,null,null,true);
110
+ $validationMessage['success'] = __('Your settings have been saved.');
111
+
112
+ //#! get updated settings
113
+ $opt = WPPH::getPluginSettings();
114
+ $daysInput = $opt->daysToKeep;
115
+ $eventsNumber = $opt->eventsToKeep;
116
+ }
117
+ }
118
+ //#! end $post
119
+ ?>
120
+ <div id="wpph-pageWrapper" class="wrap">
121
+ <h2 class="pageTitle pageTitle-settings"><?php echo __('Settings');?></h2>
122
+
123
+ <div style="width:48%; margin: 30px 0 0 0; float: left;" class="inner-sidebar1 postbox">
124
+ <h3 class="hndle" style="padding: 5px 5px; font-size: 15px;"><span><strong><?php echo __('Events Auto Deletion');?></strong></span></h3>
125
+ <div class="inside">
126
+ <?php if(! empty($validationMessage)) : ?>
127
+ <?php
128
+ if(!empty($validationMessage['error'])){
129
+ echo '<div id="errMessage" class="error-info-icon" style="display: block;">'.$validationMessage['error'].'</div>';
130
+ }
131
+ else { echo '<div id="errMessage" class="success-info-icon" style="display: block;">'.$validationMessage['success'].'</div>'; }
132
+ ?>
133
+ <?php else : ?>
134
+ <div id="errMessage" class="error-info-icon" style="display: none;"></div>
135
+ <?php endif;?>
136
+ <div style="margin: 5px 10px 0 10px; background: #fafafa; padding: 1px 10px;">
137
+ <p><?php echo __('From this section you can configure the retention of the WordPress event logs. If no option is configured, all the event logs will be kept.');?></p>
138
+ </div>
139
+ <div style="padding: 10px 10px">
140
+ <form id="updateOptionsForm" method="post">
141
+ <?php wp_nonce_field('wpph_update_settings','wpph_update_settings_field'); ?>
142
+ <div id="section1" class="form-section">
143
+ <input type="radio" id="option1" class="radioInput" name="options[]" value="e1" style="margin-top: 0;" checked="checked"/>
144
+ <label for="option1"><?php echo __('Delete events older than');?></label>
145
+ <input type="text" id="daysInput" name="daysInput" maxlength="3"
146
+ placeholder="<?php echo __('(1 to 365)');?>"
147
+ value="<?php if(! empty($daysInput)) { echo $daysInput; } ;?>"/>
148
+ <span> <?php echo __('(1 to 365 days)');?></span>
149
+ </div>
150
+ <div id="section2" class="form-section">
151
+ <input type="radio" id="option2" class="radioInput" name="options[]" value="e2" style="margin-top: 0;"/>
152
+ <label for="option2"><?php echo __('Keep up to');?></label>
153
+ <input type="text" id="eventsNumberInput" name="eventsNumberInput" maxlength="6"
154
+ placeholder="<?php echo __('1 to 10,000');?>"
155
+ value="<?php if(! empty($eventsNumber)) { echo $eventsNumber; } ;?>"/>
156
+ <span> <?php echo __('(1 to 10,000 events)');?></span>
157
+ </div>
158
+ <div class="form-section"><input type="submit" id="submitButton" class="button" value="<?php echo __('Save settings');?>"/></div>
159
+ <input type="hidden" id="sectionInputField1" name="sectionInputField"/>
160
+ </form>
161
+ </div>
162
+ </div>
163
+ <script type="text/javascript">
164
+ jQuery(document).ready(function($){
165
+ var showErrorMessage = function(msg){
166
+ var errWrapper = $('#errMessage');
167
+ errWrapper.html("Error: "+msg).show();
168
+ };
169
+ var hideErrorMessage = function(){ $('#errMessage').hide(); };
170
+ var setFocusOn = function($e){
171
+ $e.focus();
172
+ $e.select();
173
+ };
174
+
175
+ $('#updateOptionsForm :input').click(function(){ hideErrorMessage(); });
176
+
177
+ //#! select the radio input to check
178
+ <?php if(! empty($daysInput)){ ?>
179
+ $('#option1').attr('checked', 'checked');
180
+ <?php } elseif(! empty($eventsNumber)){ ?>
181
+ $('#option2').attr('checked', 'checked');
182
+ <?php };?>
183
+
184
+ // select radio on input click
185
+ $('#daysInput').click(function(){ $('#option1').attr('checked', 'checked'); });
186
+ $('#eventsNumberInput').click(function(){ $('#option2').attr('checked', 'checked'); });
187
+
188
+ $('#updateOptionsForm').submit(function()
189
+ {
190
+ var section = 0;
191
+ if ($('#option1').attr('checked') == 'checked'){section = 1;}
192
+ else { section = 2; }
193
+
194
+ // validate fields
195
+ if(section == 1)
196
+ {
197
+ var $daysInput = $('#daysInput'),
198
+ daysInputVal = $daysInput.val();
199
+
200
+ if(daysInputVal.length == 0){
201
+ showErrorMessage("<?php echo __('Please input the number of days.');?>");
202
+ setFocusOn($daysInput);
203
+ return false;
204
+ }
205
+ if(daysInputVal == 0){
206
+ showErrorMessage("<?php echo __('Please input a number greater than 0.');?>");
207
+ setFocusOn($daysInput);
208
+ return false;
209
+ }
210
+ if(!/^\d+$/.test(daysInputVal)){
211
+ showErrorMessage("<?php echo __('Only numbers greater than 0 allowed.');?>");
212
+ setFocusOn($daysInput);
213
+ return false;
214
+ }
215
+ if(daysInputVal > 365){
216
+ showErrorMessage("<?php echo __('Incorrect number of days. Please specify a value between 1 and 365.');?>");
217
+ setFocusOn($daysInput);
218
+ return false;
219
+ }
220
+ }
221
+ else if(section == 2)
222
+ {
223
+ var $eventsNumberInput = $('#eventsNumberInput'),
224
+ eniVal = $eventsNumberInput.val();
225
+
226
+ if(eniVal.length == 0){
227
+ showErrorMessage("<?php echo __('Please input the number of events.');?>");
228
+ setFocusOn($eventsNumberInput);
229
+ return false;
230
+ }
231
+ if(eniVal == 0){
232
+ showErrorMessage("<?php echo __('Please input a number greater than 0.');?>");
233
+ setFocusOn($eventsNumberInput);
234
+ return false;
235
+ }
236
+ if(!/^\d+$/.test(eniVal)){
237
+ showErrorMessage("<?php echo __('Only numbers greater than 0 allowed.');?>");
238
+ setFocusOn($eventsNumberInput);
239
+ return false;
240
+ }
241
+ if(eniVal > 500000){
242
+ showErrorMessage("<?php echo __('Incorrect number of events. Please specify a value between 1 and 10,000.');?>");
243
+ setFocusOn($eventsNumberInput);
244
+ return false;
245
+ }
246
+ }
247
+ $('#sectionInputField1').val(section);
248
+
249
+ //#! clear the other section
250
+ if(section == 1){ $('#eventsNumberInput').val(''); }
251
+ else if(section == 2){ $('#daysInput').val(''); }
252
+
253
+ return true;
254
+ });
255
+ });
256
+ </script>
257
+ </div>
258
+ <br class="clear"/>
259
+ </div>
pages/support.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php /**
2
+ * @kyos
3
+ * Support page
4
+ */ if(! WPPH::canRun()){ return; } ?>
5
+ <div id="wpph-pageWrapper" class="wrap">
6
+ <h2 class="pageTitle pageTitle-support"><?php echo __('Support');?></h2>
7
+ <div>
8
+ <p><?php echo
9
+ sprintf(__('If you encounter any issues running this plugin, or have suggestions, please get in touch with us on %s.'),
10
+ '<a href="mailto:plugins@wpprohelp.com">plugins@wpprohelp.com</a>');?></p>
11
+ </div>
12
+ </div>
readme.txt ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === WP Security Audit Log ===
2
+ Contributors: WPProHelp, WPWhiteSecurity
3
+ License: GPLv3
4
+ License URI: http://www.gnu.org/licenses/gpl.html
5
+ Tags: wordpress security plugin, wordpress security audit log, audit log, event log wordpress, wordpress user tracking, wordpress activity log, wordpress audit, security event log
6
+ Requires at least: 3.0
7
+ Tested up to: 3.6
8
+ Stable tag: 0.1
9
+
10
+ Identify WordPress security issues before they become a problem. Keep an audit log of everything that happens on WordPress
11
+
12
+ == Description ==
13
+ Identify WordPress security issues before they become a problem by keeping an audit log of what is happening under the hood of your WordPress blog or website. This plugin is developed by WordPress Security Consultants and Specialists [WP White Security](http://www.wpwhitesecurity.com/wordpress-security-services/).
14
+
15
+ = Keep A WordPress Security Audit Log & Identify WordPress Security Issues =
16
+ WP Security Audit Log keeps track of everything that is happening on your WordPress blog or website. By using this WordPress security plugin it is very easy to track suspicious user activity before it becomes a problem. A security event is generated in each of the below cases:
17
+
18
+ * New user is created via registration
19
+ * New user is created by another user
20
+ * Existing user changes the role of another user
21
+ * Existing user changes the password of another user
22
+ * User uploads a file
23
+ * User changes the password
24
+ * A user changes the password of another user
25
+ * Failed login attempt
26
+ * and much more...
27
+
28
+ = Monitor WordPress Users Activity & Productivity =
29
+ If you own a multi user WordPress blog or website you can use the WP Security Audit Log plugin to monitor your users' activity and productivity. With this WordPress security plugin you can monitor:
30
+
31
+ * When users logged in or out
32
+ * From where users are logging in
33
+ * Users who created a blog post or a page
34
+ * Users who published a blog post or a page
35
+ * Users who modified published WordPress content such as a page or a blog post
36
+ * and much more...
37
+
38
+ = WordPress Audit Log in your Language! =
39
+ We need help translating the plugin and the WordPress Security Events. If you're good at translating, please drop us an email on plugins@wpwhitesecurity.com.
40
+
41
+ = WordPress Security Tips & Tricks =
42
+ 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. WPWhiteSecurity.com frequently publishes WordPress security tips & tricks on the [WordPress Security section](http://www.wpwhitesecurity.com/wordpress-security/) of their blog.
43
+
44
+
45
+ = Further Reading =
46
+ For more information and to get started with WordPress Security, check out the following:
47
+
48
+ * [Official WP Security Audit Log Page](http://www.wpwhitesecurity.com/wordpress-security-plugins/wp-security-audit-log/)
49
+ * [List of all WP Security Audit Log Events](http://www.wpwhitesecurity.com/wordpress-security-plugins/wp-security-audit-log/security-audit-event-logs/)
50
+ * [Recipe for ultimate WordPress Security](http://www.wpwhitesecurity.com/wordpress-security/recipe-ultimate-diy-wordpress-security/)
51
+
52
+ == Installation ==
53
+
54
+ 1. Upload the `wordress-security-audit-log` folder to the `/wp-content/plugins/` directory
55
+ 2. Activate the WP Security Audit Log plugin from the 'Plugins' menu in the WordPress Administration Screens
56
+ 3. Access the Security audit logs and the plugin settings from the "Security Audit Log" menu that appears in your admin menu
57
+
58
+ == Frequently Asked Questions ==
59
+
60
+ = What can I do to prune security events? =
61
+
62
+ By default the plugin will keep up to 10,000 events. When this limit is reached, older events are deleted to make place for the new ones. You can configure the plugin to keep more events from the settings page. You can also configure the plugin to delete events which are older than a number of days.
63
+
64
+ == Screenshots ==
65
+
66
+ 1. The Audit Log Viewer from where the WordPress administrator can see all the security events generated by WP Security Audit Log WordPress plugin.
67
+ 2. The Auto Prune Security Events settings which the WordPress administrator can configure the auto deletion of security events.
68
+
69
+ == Changelog ==
70
+
71
+ = 0.1 =
72
+
73
+ * Initial beta release of WP Security Audit Log.
res/css/styles.base.css ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #wpph-pageWrapper{ padding: 0 0; clear: both; }
2
+
3
+ h2.pageTitle { padding-left: 40px; }
4
+ h2.pageTitle-eventViewer { background: url("../img/page-viewer-logo.png") no-repeat left center; }
5
+ h2.pageTitle-settings { background: url("../img/page-settings-logo.png") no-repeat left center; }
6
+ h2.pageTitle-support { background: url("../img/page-support-logo.png") no-repeat left center; }
7
+ h2.pageTitle-about { background: url("../img/page-about-logo.png") no-repeat left center; }
8
+
9
+ #wpph-pageWrapper .button { margin-top: 1px;}
10
+ #wpph-pageWrapper .buttonRefreshEventsList { margin-top: 3px;}
11
+
12
+
13
+
14
+ /*
15
+ * #! Page: Event Viewer
16
+ */
17
+ .column-left-align { text-align: left; }
18
+ .column-center-align { text-align: center; }
19
+
20
+ #the-list .row-0 { background-color: #FCFCFC; }
21
+ .widefat tbody th.check-column { padding: 8px 0 0 0 !important; }
22
+ .widefat .check-column { padding: 6px 0 0 0; }
23
+
24
+ #the-list th, #the-list tr, #the-list td { padding: 0 0 0 0 !important; line-height: normal !important;}
25
+ #the-list span { padding: 5px 5px; line-height: normal !important; display: block; }
26
+
27
+ /*
28
+ * #! Page: Settings
29
+ */
30
+ .error-info-icon {
31
+ padding: 5px 7px 5px 20px;
32
+ margin-left: 8px;
33
+ color: #ff0000;
34
+ background: url("../img/error-icon.png") no-repeat left center;
35
+ }
36
+ .success-info-icon {
37
+ padding: 5px 7px 5px 20px;
38
+ margin-left: 8px;
39
+ color: #000000;
40
+ background: url("../img/success-icon.png") no-repeat left center;
41
+ }
42
+
43
+ .form-section { margin: 7px 0; }
44
+
45
+
46
+ /*
47
+ * #! Page: Events :: pagination
48
+ */
49
+ .paginationWrapper{
50
+ overflow: hidden;
51
+ float: right;
52
+ display: block;
53
+ margin-right: 0;
54
+ }
55
+ .paginationWrapper .showPages {
56
+ float: left;
57
+ display: block;
58
+ margin-top:10px;
59
+ margin-right: 7px;
60
+ }
61
+ .paginationWrapper .showPages .span1,
62
+ .paginationWrapper .showPages .span2,
63
+ .paginationWrapper .showPages .span3 { font-weight: 800; }
64
+
65
+ .paginationWrapper .pageButton {
66
+ color: #B8B8B8;
67
+ background-color: transparent;
68
+ background-image: -moz-linear-gradient(center top , #F5F5F5, #F1F1F1);
69
+ border: 1px solid rgba(0, 0, 0, 0.1);
70
+ cursor: default;
71
+ outline: 0;
72
+ text-align: center;
73
+ white-space: nowrap;
74
+ display: inline-block;
75
+ padding: 0 0;
76
+ margin: 0 0;
77
+ }
78
+ .pageButton span{
79
+ display: block;
80
+ font-family: Verdana, "Arial Unicode MS", Arial;
81
+ font-weight: 800;
82
+ font-size: 18px;
83
+ color: #000;
84
+ margin-right: 0;
85
+ margin-top: -1px;
86
+ padding: 2px 7px 4px;
87
+ }
88
+ .paginationWrapper .buttonsWrapper{
89
+ overflow: hidden;
90
+ float: right;
91
+ display: block;
92
+ width: 70px;
93
+ }
94
+ .buttonsWrapper .buttonPrevious,
95
+ .buttonsWrapper .buttonNext {float: right;}
96
+ .wpphButtonDisabled { background: #ccc !important; }
97
+ .wpphButtonDisabled span { color: #808080 !important; }
res/img/ajax-loader.gif ADDED
Binary file
res/img/error-icon.png ADDED
Binary file
res/img/logo-main-menu.png ADDED
Binary file
res/img/page-about-logo.png ADDED
Binary file
res/img/page-settings-logo.png ADDED
Binary file
res/img/page-support-logo.png ADDED
Binary file
res/img/page-viewer-logo.png ADDED
Binary file
res/img/success-icon.png ADDED
Binary file
res/js/AuditLogViewModel.js ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var AuditLogViewModel = (function($) {
2
+
3
+ function loadRemoteData(viewModel, offset) {
4
+ var data = {
5
+ 'action': 'wpph_get_events',
6
+
7
+ 'orderBy': viewModel.orderBy(),
8
+ 'sort': viewModel.orderByDescending() ? 'desc' : 'asc',
9
+
10
+ 'offset': offset,
11
+ 'count': viewModel.pageSize()
12
+ };
13
+
14
+ AjaxLoaderShow(__ajaxLoaderTargetElement__);
15
+
16
+ $.ajax({
17
+ url: ajaxurl,
18
+ cache: false,
19
+ type: 'POST',
20
+ data: data,
21
+ beforeSend: function() {
22
+ viewModel.loading(true)
23
+ },
24
+ success: function(response) {
25
+ viewModel.loading(false);
26
+ var json = $.parseJSON(response);
27
+
28
+ if (json.error.length > 0) {
29
+ AjaxLoaderHide(__ajaxLoaderTargetElement__);
30
+ return;
31
+ }
32
+
33
+ AjaxLoaderHide(__ajaxLoaderTargetElement__);
34
+
35
+ viewModel.events(json.dataSource.events);
36
+ viewModel.totalEventsCount(json.dataSource.eventsCount);
37
+ viewModel.offset(offset);
38
+
39
+ if (viewModel.totalEventsCount() < viewModel.offset()) {
40
+ viewModel.offset(0);
41
+ }
42
+ },
43
+ error: function() {
44
+ viewModel.loading(false);
45
+ }
46
+ });
47
+ }
48
+
49
+ function AuditLogViewModel()
50
+ {
51
+ this.columns = ko.observableArray([
52
+ {columnHeader: 'Event', columnName: 'EventNumber', sortable: true, columnWidth: '80px', sorted: ko.observable(false), sortedDescending: ko.observable(false)},
53
+ {columnHeader: 'ID', columnName: 'EventID', sortable: true, columnWidth: '80px', sorted: ko.observable(false), sortedDescending: ko.observable(false)},
54
+ {columnHeader: 'Date', columnName: 'EventDate', sortable: true, columnWidth: '170px', sorted: ko.observable(false), sortedDescending: ko.observable(false)},
55
+ {columnHeader: 'Type', columnName: 'EventType', sortable: true, columnWidth: '100px', sorted: ko.observable(false), sortedDescending: ko.observable(false)},
56
+ {columnHeader: 'IP Address', columnName: 'UserIP', sortable: true, columnWidth: '100px', sorted: ko.observable(false), sortedDescending: ko.observable(false)},
57
+ {columnHeader: 'User', columnName: 'UserID', sortable: true, columnWidth: '240px', sorted: ko.observable(false), sortedDescending: ko.observable(false)},
58
+ {columnHeader: 'Description', columnName: 'EventDescription', sortable: false, columnWidth: 'auto', sorted: ko.observable(false), sortedDescending: ko.observable(false)}]);
59
+
60
+ this.loading = ko.observable(false);
61
+ this.events = ko.observableArray([]);
62
+ this.totalEventsCount = ko.observable(0);
63
+ this.offset = ko.observable(0);
64
+
65
+ this.pageSize = ko.observable(50);
66
+ this.selectedPageSize = ko.observable(50);
67
+
68
+ this.availablePageSize = ko.observableArray([25, 50, 100]);
69
+ this.orderBy = ko.computed({
70
+ read: function() {
71
+ var columnInfo = ko.utils.arrayFirst(this.columns(), function(item) { return item.sorted(); })
72
+ return columnInfo && columnInfo.columnName || '';
73
+ },
74
+ write: function(value) {
75
+ var columnInfo = ko.utils.arrayFirst(this.columns(), function(item) {
76
+ return item.columnName === value;
77
+ });
78
+ if (columnInfo) {
79
+ ko.utils.arrayForEach(this.columns(), function(item) {
80
+ item.sorted(false);
81
+ item.sortedDescending(false);
82
+ });
83
+ columnInfo.sorted(value)
84
+ }
85
+ }
86
+ }, this);
87
+
88
+ this.orderByDescending = ko.computed({
89
+ read: function() {
90
+ var columnInfo = ko.utils.arrayFirst(this.columns(), function(item) { return item.sorted(); })
91
+ return columnInfo && columnInfo.sortedDescending();
92
+ },
93
+ write: function(value) {
94
+ var columnInfo = ko.utils.arrayFirst(this.columns(), function(item) { return item.sorted(); })
95
+ columnInfo && columnInfo.sortedDescending(value);
96
+ }
97
+ }, this);
98
+ }
99
+
100
+ AuditLogViewModel.prototype.applyPageSize = function(viewModel){
101
+ viewModel.pageSize(parseInt(viewModel.selectedPageSize()));
102
+ viewModel.refreshEvents(viewModel, 0);
103
+ };
104
+
105
+ AuditLogViewModel.prototype.applySorting = function(viewModel, columnInfo) {
106
+ if (viewModel.orderBy() == columnInfo.columnName) {
107
+ viewModel.orderByDescending(! viewModel.orderByDescending());
108
+ }
109
+ else {
110
+ viewModel.orderBy(columnInfo.columnName);
111
+ viewModel.orderByDescending(false);
112
+ }
113
+ viewModel.refreshEvents(viewModel, 0);
114
+ };
115
+
116
+ AuditLogViewModel.prototype.nextPage = function(viewModel) {
117
+ var currentOffset = viewModel.offset();
118
+ var newOffset = currentOffset + viewModel.pageSize();
119
+
120
+ if (newOffset < viewModel.totalEventsCount()) {
121
+ viewModel.refreshEvents(viewModel, newOffset);
122
+ }
123
+ };
124
+
125
+ AuditLogViewModel.prototype.prevPage = function(viewModel) {
126
+ var currentOffset = viewModel.offset();
127
+ var newOffset = currentOffset - viewModel.pageSize();
128
+
129
+ if (newOffset >= 0) {
130
+ viewModel.refreshEvents(viewModel, newOffset);
131
+ }
132
+ };
133
+
134
+ AuditLogViewModel.prototype.refreshEvents = function(viewModel, offset) {
135
+ loadRemoteData(viewModel, offset);
136
+ };
137
+
138
+ AuditLogViewModel.prototype.cleanRefresh = function(viewModel) {
139
+ loadRemoteData(viewModel, 0);
140
+ };
141
+
142
+ return AuditLogViewModel;
143
+
144
+ })(jQuery);
145
+
res/js/knockout-2.2.1.min.js ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Knockout JavaScript library v2.2.1
2
+ // (c) Steven Sanderson - http://knockoutjs.com/
3
+ // License: MIT (http://www.opensource.org/licenses/mit-license.php)
4
+
5
+ (function() {function j(w){throw w;}var m=!0,p=null,r=!1;function u(w){return function(){return w}};var x=window,y=document,ga=navigator,F=window.jQuery,I=void 0;
6
+ function L(w){function ha(a,d,c,e,f){var g=[];a=b.j(function(){var a=d(c,f)||[];0<g.length&&(b.a.Ya(M(g),a),e&&b.r.K(e,p,[c,a,f]));g.splice(0,g.length);b.a.P(g,a)},p,{W:a,Ka:function(){return 0==g.length||!b.a.X(g[0])}});return{M:g,j:a.pa()?a:I}}function M(a){for(;a.length&&!b.a.X(a[0]);)a.splice(0,1);if(1<a.length){for(var d=a[0],c=a[a.length-1],e=[d];d!==c;){d=d.nextSibling;if(!d)return;e.push(d)}Array.prototype.splice.apply(a,[0,a.length].concat(e))}return a}function S(a,b,c,e,f){var g=Math.min,
7
+ h=Math.max,k=[],l,n=a.length,q,s=b.length,v=s-n||1,G=n+s+1,J,A,z;for(l=0;l<=n;l++){A=J;k.push(J=[]);z=g(s,l+v);for(q=h(0,l-1);q<=z;q++)J[q]=q?l?a[l-1]===b[q-1]?A[q-1]:g(A[q]||G,J[q-1]||G)+1:q+1:l+1}g=[];h=[];v=[];l=n;for(q=s;l||q;)s=k[l][q]-1,q&&s===k[l][q-1]?h.push(g[g.length]={status:c,value:b[--q],index:q}):l&&s===k[l-1][q]?v.push(g[g.length]={status:e,value:a[--l],index:l}):(g.push({status:"retained",value:b[--q]}),--l);if(h.length&&v.length){a=10*n;var t;for(b=c=0;(f||b<a)&&(t=h[c]);c++){for(e=
8
+ 0;k=v[e];e++)if(t.value===k.value){t.moved=k.index;k.moved=t.index;v.splice(e,1);b=e=0;break}b+=e}}return g.reverse()}function T(a,d,c,e,f){f=f||{};var g=a&&N(a),g=g&&g.ownerDocument,h=f.templateEngine||O;b.za.vb(c,h,g);c=h.renderTemplate(c,e,f,g);("number"!=typeof c.length||0<c.length&&"number"!=typeof c[0].nodeType)&&j(Error("Template engine must return an array of DOM nodes"));g=r;switch(d){case "replaceChildren":b.e.N(a,c);g=m;break;case "replaceNode":b.a.Ya(a,c);g=m;break;case "ignoreTargetNode":break;
9
+ default:j(Error("Unknown renderMode: "+d))}g&&(U(c,e),f.afterRender&&b.r.K(f.afterRender,p,[c,e.$data]));return c}function N(a){return a.nodeType?a:0<a.length?a[0]:p}function U(a,d){if(a.length){var c=a[0],e=a[a.length-1];V(c,e,function(a){b.Da(d,a)});V(c,e,function(a){b.s.ib(a,[d])})}}function V(a,d,c){var e;for(d=b.e.nextSibling(d);a&&(e=a)!==d;)a=b.e.nextSibling(e),(1===e.nodeType||8===e.nodeType)&&c(e)}function W(a,d,c){a=b.g.aa(a);for(var e=b.g.Q,f=0;f<a.length;f++){var g=a[f].key;if(e.hasOwnProperty(g)){var h=
10
+ e[g];"function"===typeof h?(g=h(a[f].value))&&j(Error(g)):h||j(Error("This template engine does not support the '"+g+"' binding within its templates"))}}a="ko.__tr_ambtns(function($context,$element){return(function(){return{ "+b.g.ba(a)+" } })()})";return c.createJavaScriptEvaluatorBlock(a)+d}function X(a,d,c,e){function f(a){return function(){return k[a]}}function g(){return k}var h=0,k,l;b.j(function(){var n=c&&c instanceof b.z?c:new b.z(b.a.d(c)),q=n.$data;e&&b.eb(a,n);if(k=("function"==typeof d?
11
+ d(n,a):d)||b.J.instance.getBindings(a,n)){if(0===h){h=1;for(var s in k){var v=b.c[s];v&&8===a.nodeType&&!b.e.I[s]&&j(Error("The binding '"+s+"' cannot be used with virtual elements"));if(v&&"function"==typeof v.init&&(v=(0,v.init)(a,f(s),g,q,n))&&v.controlsDescendantBindings)l!==I&&j(Error("Multiple bindings ("+l+" and "+s+") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.")),l=s}h=2}if(2===h)for(s in k)(v=b.c[s])&&"function"==
12
+ typeof v.update&&(0,v.update)(a,f(s),g,q,n)}},p,{W:a});return{Nb:l===I}}function Y(a,d,c){var e=m,f=1===d.nodeType;f&&b.e.Ta(d);if(f&&c||b.J.instance.nodeHasBindings(d))e=X(d,p,a,c).Nb;e&&Z(a,d,!f)}function Z(a,d,c){for(var e=b.e.firstChild(d);d=e;)e=b.e.nextSibling(d),Y(a,d,c)}function $(a,b){var c=aa(a,b);return c?0<c.length?c[c.length-1].nextSibling:a.nextSibling:p}function aa(a,b){for(var c=a,e=1,f=[];c=c.nextSibling;){if(H(c)&&(e--,0===e))return f;f.push(c);B(c)&&e++}b||j(Error("Cannot find closing comment tag to match: "+
13
+ a.nodeValue));return p}function H(a){return 8==a.nodeType&&(K?a.text:a.nodeValue).match(ia)}function B(a){return 8==a.nodeType&&(K?a.text:a.nodeValue).match(ja)}function P(a,b){for(var c=p;a!=c;)c=a,a=a.replace(ka,function(a,c){return b[c]});return a}function la(){var a=[],d=[];this.save=function(c,e){var f=b.a.i(a,c);0<=f?d[f]=e:(a.push(c),d.push(e))};this.get=function(c){c=b.a.i(a,c);return 0<=c?d[c]:I}}function ba(a,b,c){function e(e){var g=b(a[e]);switch(typeof g){case "boolean":case "number":case "string":case "function":f[e]=
14
+ g;break;case "object":case "undefined":var h=c.get(g);f[e]=h!==I?h:ba(g,b,c)}}c=c||new la;a=b(a);if(!("object"==typeof a&&a!==p&&a!==I&&!(a instanceof Date)))return a;var f=a instanceof Array?[]:{};c.save(a,f);var g=a;if(g instanceof Array){for(var h=0;h<g.length;h++)e(h);"function"==typeof g.toJSON&&e("toJSON")}else for(h in g)e(h);return f}function ca(a,d){if(a)if(8==a.nodeType){var c=b.s.Ua(a.nodeValue);c!=p&&d.push({sb:a,Fb:c})}else if(1==a.nodeType)for(var c=0,e=a.childNodes,f=e.length;c<f;c++)ca(e[c],
15
+ d)}function Q(a,d,c,e){b.c[a]={init:function(a){b.a.f.set(a,da,{});return{controlsDescendantBindings:m}},update:function(a,g,h,k,l){h=b.a.f.get(a,da);g=b.a.d(g());k=!c!==!g;var n=!h.Za;if(n||d||k!==h.qb)n&&(h.Za=b.a.Ia(b.e.childNodes(a),m)),k?(n||b.e.N(a,b.a.Ia(h.Za)),b.Ea(e?e(l,g):l,a)):b.e.Y(a),h.qb=k}};b.g.Q[a]=r;b.e.I[a]=m}function ea(a,d,c){c&&d!==b.k.q(a)&&b.k.T(a,d);d!==b.k.q(a)&&b.r.K(b.a.Ba,p,[a,"change"])}var b="undefined"!==typeof w?w:{};b.b=function(a,d){for(var c=a.split("."),e=b,f=0;f<
16
+ c.length-1;f++)e=e[c[f]];e[c[c.length-1]]=d};b.p=function(a,b,c){a[b]=c};b.version="2.2.1";b.b("version",b.version);b.a=new function(){function a(a,d){if("input"!==b.a.u(a)||!a.type||"click"!=d.toLowerCase())return r;var c=a.type;return"checkbox"==c||"radio"==c}var d=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,c={},e={};c[/Firefox\/2/i.test(ga.userAgent)?"KeyboardEvent":"UIEvents"]=["keyup","keydown","keypress"];c.MouseEvents="click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave".split(" ");
17
+ for(var f in c){var g=c[f];if(g.length)for(var h=0,k=g.length;h<k;h++)e[g[h]]=f}var l={propertychange:m},n,c=3;f=y.createElement("div");for(g=f.getElementsByTagName("i");f.innerHTML="\x3c!--[if gt IE "+ ++c+"]><i></i><![endif]--\x3e",g[0];);n=4<c?c:I;return{Na:["authenticity_token",/^__RequestVerificationToken(_.*)?$/],o:function(a,b){for(var d=0,c=a.length;d<c;d++)b(a[d])},i:function(a,b){if("function"==typeof Array.prototype.indexOf)return Array.prototype.indexOf.call(a,b);for(var d=0,c=a.length;d<
18
+ c;d++)if(a[d]===b)return d;return-1},lb:function(a,b,d){for(var c=0,e=a.length;c<e;c++)if(b.call(d,a[c]))return a[c];return p},ga:function(a,d){var c=b.a.i(a,d);0<=c&&a.splice(c,1)},Ga:function(a){a=a||[];for(var d=[],c=0,e=a.length;c<e;c++)0>b.a.i(d,a[c])&&d.push(a[c]);return d},V:function(a,b){a=a||[];for(var d=[],c=0,e=a.length;c<e;c++)d.push(b(a[c]));return d},fa:function(a,b){a=a||[];for(var d=[],c=0,e=a.length;c<e;c++)b(a[c])&&d.push(a[c]);return d},P:function(a,b){if(b instanceof Array)a.push.apply(a,
19
+ b);else for(var d=0,c=b.length;d<c;d++)a.push(b[d]);return a},extend:function(a,b){if(b)for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);return a},ka:function(a){for(;a.firstChild;)b.removeNode(a.firstChild)},Hb:function(a){a=b.a.L(a);for(var d=y.createElement("div"),c=0,e=a.length;c<e;c++)d.appendChild(b.A(a[c]));return d},Ia:function(a,d){for(var c=0,e=a.length,g=[];c<e;c++){var f=a[c].cloneNode(m);g.push(d?b.A(f):f)}return g},N:function(a,d){b.a.ka(a);if(d)for(var c=0,e=d.length;c<e;c++)a.appendChild(d[c])},
20
+ Ya:function(a,d){var c=a.nodeType?[a]:a;if(0<c.length){for(var e=c[0],g=e.parentNode,f=0,h=d.length;f<h;f++)g.insertBefore(d[f],e);f=0;for(h=c.length;f<h;f++)b.removeNode(c[f])}},bb:function(a,b){7>n?a.setAttribute("selected",b):a.selected=b},D:function(a){return(a||"").replace(d,"")},Rb:function(a,d){for(var c=[],e=(a||"").split(d),f=0,g=e.length;f<g;f++){var h=b.a.D(e[f]);""!==h&&c.push(h)}return c},Ob:function(a,b){a=a||"";return b.length>a.length?r:a.substring(0,b.length)===b},tb:function(a,b){if(b.compareDocumentPosition)return 16==
21
+ (b.compareDocumentPosition(a)&16);for(;a!=p;){if(a==b)return m;a=a.parentNode}return r},X:function(a){return b.a.tb(a,a.ownerDocument)},u:function(a){return a&&a.tagName&&a.tagName.toLowerCase()},n:function(b,d,c){var e=n&&l[d];if(!e&&"undefined"!=typeof F){if(a(b,d)){var f=c;c=function(a,b){var d=this.checked;b&&(this.checked=b.nb!==m);f.call(this,a);this.checked=d}}F(b).bind(d,c)}else!e&&"function"==typeof b.addEventListener?b.addEventListener(d,c,r):"undefined"!=typeof b.attachEvent?b.attachEvent("on"+
22
+ d,function(a){c.call(b,a)}):j(Error("Browser doesn't support addEventListener or attachEvent"))},Ba:function(b,d){(!b||!b.nodeType)&&j(Error("element must be a DOM node when calling triggerEvent"));if("undefined"!=typeof F){var c=[];a(b,d)&&c.push({nb:b.checked});F(b).trigger(d,c)}else"function"==typeof y.createEvent?"function"==typeof b.dispatchEvent?(c=y.createEvent(e[d]||"HTMLEvents"),c.initEvent(d,m,m,x,0,0,0,0,0,r,r,r,r,0,b),b.dispatchEvent(c)):j(Error("The supplied element doesn't support dispatchEvent")):
23
+ "undefined"!=typeof b.fireEvent?(a(b,d)&&(b.checked=b.checked!==m),b.fireEvent("on"+d)):j(Error("Browser doesn't support triggering events"))},d:function(a){return b.$(a)?a():a},ua:function(a){return b.$(a)?a.t():a},da:function(a,d,c){if(d){var e=/[\w-]+/g,f=a.className.match(e)||[];b.a.o(d.match(e),function(a){var d=b.a.i(f,a);0<=d?c||f.splice(d,1):c&&f.push(a)});a.className=f.join(" ")}},cb:function(a,d){var c=b.a.d(d);if(c===p||c===I)c="";if(3===a.nodeType)a.data=c;else{var e=b.e.firstChild(a);
24
+ !e||3!=e.nodeType||b.e.nextSibling(e)?b.e.N(a,[y.createTextNode(c)]):e.data=c;b.a.wb(a)}},ab:function(a,b){a.name=b;if(7>=n)try{a.mergeAttributes(y.createElement("<input name='"+a.name+"'/>"),r)}catch(d){}},wb:function(a){9<=n&&(a=1==a.nodeType?a:a.parentNode,a.style&&(a.style.zoom=a.style.zoom))},ub:function(a){if(9<=n){var b=a.style.width;a.style.width=0;a.style.width=b}},Lb:function(a,d){a=b.a.d(a);d=b.a.d(d);for(var c=[],e=a;e<=d;e++)c.push(e);return c},L:function(a){for(var b=[],d=0,c=a.length;d<
25
+ c;d++)b.push(a[d]);return b},Pb:6===n,Qb:7===n,Z:n,Oa:function(a,d){for(var c=b.a.L(a.getElementsByTagName("input")).concat(b.a.L(a.getElementsByTagName("textarea"))),e="string"==typeof d?function(a){return a.name===d}:function(a){return d.test(a.name)},f=[],g=c.length-1;0<=g;g--)e(c[g])&&f.push(c[g]);return f},Ib:function(a){return"string"==typeof a&&(a=b.a.D(a))?x.JSON&&x.JSON.parse?x.JSON.parse(a):(new Function("return "+a))():p},xa:function(a,d,c){("undefined"==typeof JSON||"undefined"==typeof JSON.stringify)&&
26
+ j(Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js"));return JSON.stringify(b.a.d(a),d,c)},Jb:function(a,d,c){c=c||{};var e=c.params||{},f=c.includeFields||this.Na,g=a;if("object"==typeof a&&"form"===b.a.u(a))for(var g=a.action,h=f.length-1;0<=h;h--)for(var k=b.a.Oa(a,f[h]),l=k.length-1;0<=l;l--)e[k[l].name]=k[l].value;d=b.a.d(d);var n=y.createElement("form");
27
+ n.style.display="none";n.action=g;n.method="post";for(var w in d)a=y.createElement("input"),a.name=w,a.value=b.a.xa(b.a.d(d[w])),n.appendChild(a);for(w in e)a=y.createElement("input"),a.name=w,a.value=e[w],n.appendChild(a);y.body.appendChild(n);c.submitter?c.submitter(n):n.submit();setTimeout(function(){n.parentNode.removeChild(n)},0)}}};b.b("utils",b.a);b.b("utils.arrayForEach",b.a.o);b.b("utils.arrayFirst",b.a.lb);b.b("utils.arrayFilter",b.a.fa);b.b("utils.arrayGetDistinctValues",b.a.Ga);b.b("utils.arrayIndexOf",
28
+ b.a.i);b.b("utils.arrayMap",b.a.V);b.b("utils.arrayPushAll",b.a.P);b.b("utils.arrayRemoveItem",b.a.ga);b.b("utils.extend",b.a.extend);b.b("utils.fieldsIncludedWithJsonPost",b.a.Na);b.b("utils.getFormFields",b.a.Oa);b.b("utils.peekObservable",b.a.ua);b.b("utils.postJson",b.a.Jb);b.b("utils.parseJson",b.a.Ib);b.b("utils.registerEventHandler",b.a.n);b.b("utils.stringifyJson",b.a.xa);b.b("utils.range",b.a.Lb);b.b("utils.toggleDomNodeCssClass",b.a.da);b.b("utils.triggerEvent",b.a.Ba);b.b("utils.unwrapObservable",
29
+ b.a.d);Function.prototype.bind||(Function.prototype.bind=function(a){var b=this,c=Array.prototype.slice.call(arguments);a=c.shift();return function(){return b.apply(a,c.concat(Array.prototype.slice.call(arguments)))}});b.a.f=new function(){var a=0,d="__ko__"+(new Date).getTime(),c={};return{get:function(a,d){var c=b.a.f.la(a,r);return c===I?I:c[d]},set:function(a,d,c){c===I&&b.a.f.la(a,r)===I||(b.a.f.la(a,m)[d]=c)},la:function(b,f){var g=b[d];if(!g||!("null"!==g&&c[g])){if(!f)return I;g=b[d]="ko"+
30
+ a++;c[g]={}}return c[g]},clear:function(a){var b=a[d];return b?(delete c[b],a[d]=p,m):r}}};b.b("utils.domData",b.a.f);b.b("utils.domData.clear",b.a.f.clear);b.a.F=new function(){function a(a,d){var e=b.a.f.get(a,c);e===I&&d&&(e=[],b.a.f.set(a,c,e));return e}function d(c){var e=a(c,r);if(e)for(var e=e.slice(0),k=0;k<e.length;k++)e[k](c);b.a.f.clear(c);"function"==typeof F&&"function"==typeof F.cleanData&&F.cleanData([c]);if(f[c.nodeType])for(e=c.firstChild;c=e;)e=c.nextSibling,8===c.nodeType&&d(c)}
31
+ var c="__ko_domNodeDisposal__"+(new Date).getTime(),e={1:m,8:m,9:m},f={1:m,9:m};return{Ca:function(b,d){"function"!=typeof d&&j(Error("Callback must be a function"));a(b,m).push(d)},Xa:function(d,e){var f=a(d,r);f&&(b.a.ga(f,e),0==f.length&&b.a.f.set(d,c,I))},A:function(a){if(e[a.nodeType]&&(d(a),f[a.nodeType])){var c=[];b.a.P(c,a.getElementsByTagName("*"));for(var k=0,l=c.length;k<l;k++)d(c[k])}return a},removeNode:function(a){b.A(a);a.parentNode&&a.parentNode.removeChild(a)}}};b.A=b.a.F.A;b.removeNode=
32
+ b.a.F.removeNode;b.b("cleanNode",b.A);b.b("removeNode",b.removeNode);b.b("utils.domNodeDisposal",b.a.F);b.b("utils.domNodeDisposal.addDisposeCallback",b.a.F.Ca);b.b("utils.domNodeDisposal.removeDisposeCallback",b.a.F.Xa);b.a.ta=function(a){var d;if("undefined"!=typeof F)if(F.parseHTML)d=F.parseHTML(a);else{if((d=F.clean([a]))&&d[0]){for(a=d[0];a.parentNode&&11!==a.parentNode.nodeType;)a=a.parentNode;a.parentNode&&a.parentNode.removeChild(a)}}else{var c=b.a.D(a).toLowerCase();d=y.createElement("div");
33
+ c=c.match(/^<(thead|tbody|tfoot)/)&&[1,"<table>","</table>"]||!c.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!c.indexOf("<td")||!c.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||[0,"",""];a="ignored<div>"+c[1]+a+c[2]+"</div>";for("function"==typeof x.innerShiv?d.appendChild(x.innerShiv(a)):d.innerHTML=a;c[0]--;)d=d.lastChild;d=b.a.L(d.lastChild.childNodes)}return d};b.a.ca=function(a,d){b.a.ka(a);d=b.a.d(d);if(d!==p&&d!==I)if("string"!=typeof d&&(d=d.toString()),
34
+ "undefined"!=typeof F)F(a).html(d);else for(var c=b.a.ta(d),e=0;e<c.length;e++)a.appendChild(c[e])};b.b("utils.parseHtmlFragment",b.a.ta);b.b("utils.setHtml",b.a.ca);var R={};b.s={ra:function(a){"function"!=typeof a&&j(Error("You can only pass a function to ko.memoization.memoize()"));var b=(4294967296*(1+Math.random())|0).toString(16).substring(1)+(4294967296*(1+Math.random())|0).toString(16).substring(1);R[b]=a;return"\x3c!--[ko_memo:"+b+"]--\x3e"},hb:function(a,b){var c=R[a];c===I&&j(Error("Couldn't find any memo with ID "+
35
+ a+". Perhaps it's already been unmemoized."));try{return c.apply(p,b||[]),m}finally{delete R[a]}},ib:function(a,d){var c=[];ca(a,c);for(var e=0,f=c.length;e<f;e++){var g=c[e].sb,h=[g];d&&b.a.P(h,d);b.s.hb(c[e].Fb,h);g.nodeValue="";g.parentNode&&g.parentNode.removeChild(g)}},Ua:function(a){return(a=a.match(/^\[ko_memo\:(.*?)\]$/))?a[1]:p}};b.b("memoization",b.s);b.b("memoization.memoize",b.s.ra);b.b("memoization.unmemoize",b.s.hb);b.b("memoization.parseMemoText",b.s.Ua);b.b("memoization.unmemoizeDomNodeAndDescendants",
36
+ b.s.ib);b.Ma={throttle:function(a,d){a.throttleEvaluation=d;var c=p;return b.j({read:a,write:function(b){clearTimeout(c);c=setTimeout(function(){a(b)},d)}})},notify:function(a,d){a.equalityComparer="always"==d?u(r):b.m.fn.equalityComparer;return a}};b.b("extenders",b.Ma);b.fb=function(a,d,c){this.target=a;this.ha=d;this.rb=c;b.p(this,"dispose",this.B)};b.fb.prototype.B=function(){this.Cb=m;this.rb()};b.S=function(){this.w={};b.a.extend(this,b.S.fn);b.p(this,"subscribe",this.ya);b.p(this,"extend",
37
+ this.extend);b.p(this,"getSubscriptionsCount",this.yb)};b.S.fn={ya:function(a,d,c){c=c||"change";var e=new b.fb(this,d?a.bind(d):a,function(){b.a.ga(this.w[c],e)}.bind(this));this.w[c]||(this.w[c]=[]);this.w[c].push(e);return e},notifySubscribers:function(a,d){d=d||"change";this.w[d]&&b.r.K(function(){b.a.o(this.w[d].slice(0),function(b){b&&b.Cb!==m&&b.ha(a)})},this)},yb:function(){var a=0,b;for(b in this.w)this.w.hasOwnProperty(b)&&(a+=this.w[b].length);return a},extend:function(a){var d=this;if(a)for(var c in a){var e=
38
+ b.Ma[c];"function"==typeof e&&(d=e(d,a[c]))}return d}};b.Qa=function(a){return"function"==typeof a.ya&&"function"==typeof a.notifySubscribers};b.b("subscribable",b.S);b.b("isSubscribable",b.Qa);var C=[];b.r={mb:function(a){C.push({ha:a,La:[]})},end:function(){C.pop()},Wa:function(a){b.Qa(a)||j(Error("Only subscribable things can act as dependencies"));if(0<C.length){var d=C[C.length-1];d&&!(0<=b.a.i(d.La,a))&&(d.La.push(a),d.ha(a))}},K:function(a,b,c){try{return C.push(p),a.apply(b,c||[])}finally{C.pop()}}};
39
+ var ma={undefined:m,"boolean":m,number:m,string:m};b.m=function(a){function d(){if(0<arguments.length){if(!d.equalityComparer||!d.equalityComparer(c,arguments[0]))d.H(),c=arguments[0],d.G();return this}b.r.Wa(d);return c}var c=a;b.S.call(d);d.t=function(){return c};d.G=function(){d.notifySubscribers(c)};d.H=function(){d.notifySubscribers(c,"beforeChange")};b.a.extend(d,b.m.fn);b.p(d,"peek",d.t);b.p(d,"valueHasMutated",d.G);b.p(d,"valueWillMutate",d.H);return d};b.m.fn={equalityComparer:function(a,
40
+ b){return a===p||typeof a in ma?a===b:r}};var E=b.m.Kb="__ko_proto__";b.m.fn[E]=b.m;b.ma=function(a,d){return a===p||a===I||a[E]===I?r:a[E]===d?m:b.ma(a[E],d)};b.$=function(a){return b.ma(a,b.m)};b.Ra=function(a){return"function"==typeof a&&a[E]===b.m||"function"==typeof a&&a[E]===b.j&&a.zb?m:r};b.b("observable",b.m);b.b("isObservable",b.$);b.b("isWriteableObservable",b.Ra);b.R=function(a){0==arguments.length&&(a=[]);a!==p&&(a!==I&&!("length"in a))&&j(Error("The argument passed when initializing an observable array must be an array, or null, or undefined."));
41
+ var d=b.m(a);b.a.extend(d,b.R.fn);return d};b.R.fn={remove:function(a){for(var b=this.t(),c=[],e="function"==typeof a?a:function(b){return b===a},f=0;f<b.length;f++){var g=b[f];e(g)&&(0===c.length&&this.H(),c.push(g),b.splice(f,1),f--)}c.length&&this.G();return c},removeAll:function(a){if(a===I){var d=this.t(),c=d.slice(0);this.H();d.splice(0,d.length);this.G();return c}return!a?[]:this.remove(function(d){return 0<=b.a.i(a,d)})},destroy:function(a){var b=this.t(),c="function"==typeof a?a:function(b){return b===
42
+ a};this.H();for(var e=b.length-1;0<=e;e--)c(b[e])&&(b[e]._destroy=m);this.G()},destroyAll:function(a){return a===I?this.destroy(u(m)):!a?[]:this.destroy(function(d){return 0<=b.a.i(a,d)})},indexOf:function(a){var d=this();return b.a.i(d,a)},replace:function(a,b){var c=this.indexOf(a);0<=c&&(this.H(),this.t()[c]=b,this.G())}};b.a.o("pop push reverse shift sort splice unshift".split(" "),function(a){b.R.fn[a]=function(){var b=this.t();this.H();b=b[a].apply(b,arguments);this.G();return b}});b.a.o(["slice"],
43
+ function(a){b.R.fn[a]=function(){var b=this();return b[a].apply(b,arguments)}});b.b("observableArray",b.R);b.j=function(a,d,c){function e(){b.a.o(z,function(a){a.B()});z=[]}function f(){var a=h.throttleEvaluation;a&&0<=a?(clearTimeout(t),t=setTimeout(g,a)):g()}function g(){if(!q)if(n&&w())A();else{q=m;try{var a=b.a.V(z,function(a){return a.target});b.r.mb(function(c){var d;0<=(d=b.a.i(a,c))?a[d]=I:z.push(c.ya(f))});for(var c=s.call(d),e=a.length-1;0<=e;e--)a[e]&&z.splice(e,1)[0].B();n=m;h.notifySubscribers(l,
44
+ "beforeChange");l=c}finally{b.r.end()}h.notifySubscribers(l);q=r;z.length||A()}}function h(){if(0<arguments.length)return"function"===typeof v?v.apply(d,arguments):j(Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.")),this;n||g();b.r.Wa(h);return l}function k(){return!n||0<z.length}var l,n=r,q=r,s=a;s&&"object"==typeof s?(c=s,s=c.read):(c=c||{},s||(s=c.read));"function"!=typeof s&&j(Error("Pass a function that returns the value of the ko.computed"));
45
+ var v=c.write,G=c.disposeWhenNodeIsRemoved||c.W||p,w=c.disposeWhen||c.Ka||u(r),A=e,z=[],t=p;d||(d=c.owner);h.t=function(){n||g();return l};h.xb=function(){return z.length};h.zb="function"===typeof c.write;h.B=function(){A()};h.pa=k;b.S.call(h);b.a.extend(h,b.j.fn);b.p(h,"peek",h.t);b.p(h,"dispose",h.B);b.p(h,"isActive",h.pa);b.p(h,"getDependenciesCount",h.xb);c.deferEvaluation!==m&&g();if(G&&k()){A=function(){b.a.F.Xa(G,arguments.callee);e()};b.a.F.Ca(G,A);var D=w,w=function(){return!b.a.X(G)||D()}}return h};
46
+ b.Bb=function(a){return b.ma(a,b.j)};w=b.m.Kb;b.j[w]=b.m;b.j.fn={};b.j.fn[w]=b.j;b.b("dependentObservable",b.j);b.b("computed",b.j);b.b("isComputed",b.Bb);b.gb=function(a){0==arguments.length&&j(Error("When calling ko.toJS, pass the object you want to convert."));return ba(a,function(a){for(var c=0;b.$(a)&&10>c;c++)a=a();return a})};b.toJSON=function(a,d,c){a=b.gb(a);return b.a.xa(a,d,c)};b.b("toJS",b.gb);b.b("toJSON",b.toJSON);b.k={q:function(a){switch(b.a.u(a)){case "option":return a.__ko__hasDomDataOptionValue__===
47
+ m?b.a.f.get(a,b.c.options.sa):7>=b.a.Z?a.getAttributeNode("value").specified?a.value:a.text:a.value;case "select":return 0<=a.selectedIndex?b.k.q(a.options[a.selectedIndex]):I;default:return a.value}},T:function(a,d){switch(b.a.u(a)){case "option":switch(typeof d){case "string":b.a.f.set(a,b.c.options.sa,I);"__ko__hasDomDataOptionValue__"in a&&delete a.__ko__hasDomDataOptionValue__;a.value=d;break;default:b.a.f.set(a,b.c.options.sa,d),a.__ko__hasDomDataOptionValue__=m,a.value="number"===typeof d?
48
+ d:""}break;case "select":for(var c=a.options.length-1;0<=c;c--)if(b.k.q(a.options[c])==d){a.selectedIndex=c;break}break;default:if(d===p||d===I)d="";a.value=d}}};b.b("selectExtensions",b.k);b.b("selectExtensions.readValue",b.k.q);b.b("selectExtensions.writeValue",b.k.T);var ka=/\@ko_token_(\d+)\@/g,na=["true","false"],oa=/^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i;b.g={Q:[],aa:function(a){var d=b.a.D(a);if(3>d.length)return[];"{"===d.charAt(0)&&(d=d.substring(1,d.length-1));a=[];for(var c=
49
+ p,e,f=0;f<d.length;f++){var g=d.charAt(f);if(c===p)switch(g){case '"':case "'":case "/":c=f,e=g}else if(g==e&&"\\"!==d.charAt(f-1)){g=d.substring(c,f+1);a.push(g);var h="@ko_token_"+(a.length-1)+"@",d=d.substring(0,c)+h+d.substring(f+1),f=f-(g.length-h.length),c=p}}e=c=p;for(var k=0,l=p,f=0;f<d.length;f++){g=d.charAt(f);if(c===p)switch(g){case "{":c=f;l=g;e="}";break;case "(":c=f;l=g;e=")";break;case "[":c=f,l=g,e="]"}g===l?k++:g===e&&(k--,0===k&&(g=d.substring(c,f+1),a.push(g),h="@ko_token_"+(a.length-
50
+ 1)+"@",d=d.substring(0,c)+h+d.substring(f+1),f-=g.length-h.length,c=p))}e=[];d=d.split(",");c=0;for(f=d.length;c<f;c++)k=d[c],l=k.indexOf(":"),0<l&&l<k.length-1?(g=k.substring(l+1),e.push({key:P(k.substring(0,l),a),value:P(g,a)})):e.push({unknown:P(k,a)});return e},ba:function(a){var d="string"===typeof a?b.g.aa(a):a,c=[];a=[];for(var e,f=0;e=d[f];f++)if(0<c.length&&c.push(","),e.key){var g;a:{g=e.key;var h=b.a.D(g);switch(h.length&&h.charAt(0)){case "'":case '"':break a;default:g="'"+h+"'"}}e=e.value;
51
+ c.push(g);c.push(":");c.push(e);e=b.a.D(e);0<=b.a.i(na,b.a.D(e).toLowerCase())?e=r:(h=e.match(oa),e=h===p?r:h[1]?"Object("+h[1]+")"+h[2]:e);e&&(0<a.length&&a.push(", "),a.push(g+" : function(__ko_value) { "+e+" = __ko_value; }"))}else e.unknown&&c.push(e.unknown);d=c.join("");0<a.length&&(d=d+", '_ko_property_writers' : { "+a.join("")+" } ");return d},Eb:function(a,d){for(var c=0;c<a.length;c++)if(b.a.D(a[c].key)==d)return m;return r},ea:function(a,d,c,e,f){if(!a||!b.Ra(a)){if((a=d()._ko_property_writers)&&
52
+ a[c])a[c](e)}else(!f||a.t()!==e)&&a(e)}};b.b("expressionRewriting",b.g);b.b("expressionRewriting.bindingRewriteValidators",b.g.Q);b.b("expressionRewriting.parseObjectLiteral",b.g.aa);b.b("expressionRewriting.preProcessBindings",b.g.ba);b.b("jsonExpressionRewriting",b.g);b.b("jsonExpressionRewriting.insertPropertyAccessorsIntoJson",b.g.ba);var K="\x3c!--test--\x3e"===y.createComment("test").text,ja=K?/^\x3c!--\s*ko(?:\s+(.+\s*\:[\s\S]*))?\s*--\x3e$/:/^\s*ko(?:\s+(.+\s*\:[\s\S]*))?\s*$/,ia=K?/^\x3c!--\s*\/ko\s*--\x3e$/:
53
+ /^\s*\/ko\s*$/,pa={ul:m,ol:m};b.e={I:{},childNodes:function(a){return B(a)?aa(a):a.childNodes},Y:function(a){if(B(a)){a=b.e.childNodes(a);for(var d=0,c=a.length;d<c;d++)b.removeNode(a[d])}else b.a.ka(a)},N:function(a,d){if(B(a)){b.e.Y(a);for(var c=a.nextSibling,e=0,f=d.length;e<f;e++)c.parentNode.insertBefore(d[e],c)}else b.a.N(a,d)},Va:function(a,b){B(a)?a.parentNode.insertBefore(b,a.nextSibling):a.firstChild?a.insertBefore(b,a.firstChild):a.appendChild(b)},Pa:function(a,d,c){c?B(a)?a.parentNode.insertBefore(d,
54
+ c.nextSibling):c.nextSibling?a.insertBefore(d,c.nextSibling):a.appendChild(d):b.e.Va(a,d)},firstChild:function(a){return!B(a)?a.firstChild:!a.nextSibling||H(a.nextSibling)?p:a.nextSibling},nextSibling:function(a){B(a)&&(a=$(a));return a.nextSibling&&H(a.nextSibling)?p:a.nextSibling},jb:function(a){return(a=B(a))?a[1]:p},Ta:function(a){if(pa[b.a.u(a)]){var d=a.firstChild;if(d){do if(1===d.nodeType){var c;c=d.firstChild;var e=p;if(c){do if(e)e.push(c);else if(B(c)){var f=$(c,m);f?c=f:e=[c]}else H(c)&&
55
+ (e=[c]);while(c=c.nextSibling)}if(c=e){e=d.nextSibling;for(f=0;f<c.length;f++)e?a.insertBefore(c[f],e):a.appendChild(c[f])}}while(d=d.nextSibling)}}}};b.b("virtualElements",b.e);b.b("virtualElements.allowedBindings",b.e.I);b.b("virtualElements.emptyNode",b.e.Y);b.b("virtualElements.insertAfter",b.e.Pa);b.b("virtualElements.prepend",b.e.Va);b.b("virtualElements.setDomNodeChildren",b.e.N);b.J=function(){this.Ha={}};b.a.extend(b.J.prototype,{nodeHasBindings:function(a){switch(a.nodeType){case 1:return a.getAttribute("data-bind")!=
56
+ p;case 8:return b.e.jb(a)!=p;default:return r}},getBindings:function(a,b){var c=this.getBindingsString(a,b);return c?this.parseBindingsString(c,b,a):p},getBindingsString:function(a){switch(a.nodeType){case 1:return a.getAttribute("data-bind");case 8:return b.e.jb(a);default:return p}},parseBindingsString:function(a,d,c){try{var e;if(!(e=this.Ha[a])){var f=this.Ha,g,h="with($context){with($data||{}){return{"+b.g.ba(a)+"}}}";g=new Function("$context","$element",h);e=f[a]=g}return e(d,c)}catch(k){j(Error("Unable to parse bindings.\nMessage: "+
57
+ k+";\nBindings value: "+a))}}});b.J.instance=new b.J;b.b("bindingProvider",b.J);b.c={};b.z=function(a,d,c){d?(b.a.extend(this,d),this.$parentContext=d,this.$parent=d.$data,this.$parents=(d.$parents||[]).slice(0),this.$parents.unshift(this.$parent)):(this.$parents=[],this.$root=a,this.ko=b);this.$data=a;c&&(this[c]=a)};b.z.prototype.createChildContext=function(a,d){return new b.z(a,this,d)};b.z.prototype.extend=function(a){var d=b.a.extend(new b.z,this);return b.a.extend(d,a)};b.eb=function(a,d){if(2==
58
+ arguments.length)b.a.f.set(a,"__ko_bindingContext__",d);else return b.a.f.get(a,"__ko_bindingContext__")};b.Fa=function(a,d,c){1===a.nodeType&&b.e.Ta(a);return X(a,d,c,m)};b.Ea=function(a,b){(1===b.nodeType||8===b.nodeType)&&Z(a,b,m)};b.Da=function(a,b){b&&(1!==b.nodeType&&8!==b.nodeType)&&j(Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node"));b=b||x.document.body;Y(a,b,m)};b.ja=function(a){switch(a.nodeType){case 1:case 8:var d=b.eb(a);if(d)return d;
59
+ if(a.parentNode)return b.ja(a.parentNode)}return I};b.pb=function(a){return(a=b.ja(a))?a.$data:I};b.b("bindingHandlers",b.c);b.b("applyBindings",b.Da);b.b("applyBindingsToDescendants",b.Ea);b.b("applyBindingsToNode",b.Fa);b.b("contextFor",b.ja);b.b("dataFor",b.pb);var fa={"class":"className","for":"htmlFor"};b.c.attr={update:function(a,d){var c=b.a.d(d())||{},e;for(e in c)if("string"==typeof e){var f=b.a.d(c[e]),g=f===r||f===p||f===I;g&&a.removeAttribute(e);8>=b.a.Z&&e in fa?(e=fa[e],g?a.removeAttribute(e):
60
+ a[e]=f):g||a.setAttribute(e,f.toString());"name"===e&&b.a.ab(a,g?"":f.toString())}}};b.c.checked={init:function(a,d,c){b.a.n(a,"click",function(){var e;if("checkbox"==a.type)e=a.checked;else if("radio"==a.type&&a.checked)e=a.value;else return;var f=d(),g=b.a.d(f);"checkbox"==a.type&&g instanceof Array?(e=b.a.i(g,a.value),a.checked&&0>e?f.push(a.value):!a.checked&&0<=e&&f.splice(e,1)):b.g.ea(f,c,"checked",e,m)});"radio"==a.type&&!a.name&&b.c.uniqueName.init(a,u(m))},update:function(a,d){var c=b.a.d(d());
61
+ "checkbox"==a.type?a.checked=c instanceof Array?0<=b.a.i(c,a.value):c:"radio"==a.type&&(a.checked=a.value==c)}};b.c.css={update:function(a,d){var c=b.a.d(d());if("object"==typeof c)for(var e in c){var f=b.a.d(c[e]);b.a.da(a,e,f)}else c=String(c||""),b.a.da(a,a.__ko__cssValue,r),a.__ko__cssValue=c,b.a.da(a,c,m)}};b.c.enable={update:function(a,d){var c=b.a.d(d());c&&a.disabled?a.removeAttribute("disabled"):!c&&!a.disabled&&(a.disabled=m)}};b.c.disable={update:function(a,d){b.c.enable.update(a,function(){return!b.a.d(d())})}};
62
+ b.c.event={init:function(a,d,c,e){var f=d()||{},g;for(g in f)(function(){var f=g;"string"==typeof f&&b.a.n(a,f,function(a){var g,n=d()[f];if(n){var q=c();try{var s=b.a.L(arguments);s.unshift(e);g=n.apply(e,s)}finally{g!==m&&(a.preventDefault?a.preventDefault():a.returnValue=r)}q[f+"Bubble"]===r&&(a.cancelBubble=m,a.stopPropagation&&a.stopPropagation())}})})()}};b.c.foreach={Sa:function(a){return function(){var d=a(),c=b.a.ua(d);if(!c||"number"==typeof c.length)return{foreach:d,templateEngine:b.C.oa};
63
+ b.a.d(d);return{foreach:c.data,as:c.as,includeDestroyed:c.includeDestroyed,afterAdd:c.afterAdd,beforeRemove:c.beforeRemove,afterRender:c.afterRender,beforeMove:c.beforeMove,afterMove:c.afterMove,templateEngine:b.C.oa}}},init:function(a,d){return b.c.template.init(a,b.c.foreach.Sa(d))},update:function(a,d,c,e,f){return b.c.template.update(a,b.c.foreach.Sa(d),c,e,f)}};b.g.Q.foreach=r;b.e.I.foreach=m;b.c.hasfocus={init:function(a,d,c){function e(e){a.__ko_hasfocusUpdating=m;var f=a.ownerDocument;"activeElement"in
64
+ f&&(e=f.activeElement===a);f=d();b.g.ea(f,c,"hasfocus",e,m);a.__ko_hasfocusUpdating=r}var f=e.bind(p,m),g=e.bind(p,r);b.a.n(a,"focus",f);b.a.n(a,"focusin",f);b.a.n(a,"blur",g);b.a.n(a,"focusout",g)},update:function(a,d){var c=b.a.d(d());a.__ko_hasfocusUpdating||(c?a.focus():a.blur(),b.r.K(b.a.Ba,p,[a,c?"focusin":"focusout"]))}};b.c.html={init:function(){return{controlsDescendantBindings:m}},update:function(a,d){b.a.ca(a,d())}};var da="__ko_withIfBindingData";Q("if");Q("ifnot",r,m);Q("with",m,r,function(a,
65
+ b){return a.createChildContext(b)});b.c.options={update:function(a,d,c){"select"!==b.a.u(a)&&j(Error("options binding applies only to SELECT elements"));for(var e=0==a.length,f=b.a.V(b.a.fa(a.childNodes,function(a){return a.tagName&&"option"===b.a.u(a)&&a.selected}),function(a){return b.k.q(a)||a.innerText||a.textContent}),g=a.scrollTop,h=b.a.d(d());0<a.length;)b.A(a.options[0]),a.remove(0);if(h){c=c();var k=c.optionsIncludeDestroyed;"number"!=typeof h.length&&(h=[h]);if(c.optionsCaption){var l=y.createElement("option");
66
+ b.a.ca(l,c.optionsCaption);b.k.T(l,I);a.appendChild(l)}d=0;for(var n=h.length;d<n;d++){var q=h[d];if(!q||!q._destroy||k){var l=y.createElement("option"),s=function(a,b,c){var d=typeof b;return"function"==d?b(a):"string"==d?a[b]:c},v=s(q,c.optionsValue,q);b.k.T(l,b.a.d(v));q=s(q,c.optionsText,v);b.a.cb(l,q);a.appendChild(l)}}h=a.getElementsByTagName("option");d=k=0;for(n=h.length;d<n;d++)0<=b.a.i(f,b.k.q(h[d]))&&(b.a.bb(h[d],m),k++);a.scrollTop=g;e&&"value"in c&&ea(a,b.a.ua(c.value),m);b.a.ub(a)}}};
67
+ b.c.options.sa="__ko.optionValueDomData__";b.c.selectedOptions={init:function(a,d,c){b.a.n(a,"change",function(){var e=d(),f=[];b.a.o(a.getElementsByTagName("option"),function(a){a.selected&&f.push(b.k.q(a))});b.g.ea(e,c,"value",f)})},update:function(a,d){"select"!=b.a.u(a)&&j(Error("values binding applies only to SELECT elements"));var c=b.a.d(d());c&&"number"==typeof c.length&&b.a.o(a.getElementsByTagName("option"),function(a){var d=0<=b.a.i(c,b.k.q(a));b.a.bb(a,d)})}};b.c.style={update:function(a,
68
+ d){var c=b.a.d(d()||{}),e;for(e in c)if("string"==typeof e){var f=b.a.d(c[e]);a.style[e]=f||""}}};b.c.submit={init:function(a,d,c,e){"function"!=typeof d()&&j(Error("The value for a submit binding must be a function"));b.a.n(a,"submit",function(b){var c,h=d();try{c=h.call(e,a)}finally{c!==m&&(b.preventDefault?b.preventDefault():b.returnValue=r)}})}};b.c.text={update:function(a,d){b.a.cb(a,d())}};b.e.I.text=m;b.c.uniqueName={init:function(a,d){if(d()){var c="ko_unique_"+ ++b.c.uniqueName.ob;b.a.ab(a,
69
+ c)}}};b.c.uniqueName.ob=0;b.c.value={init:function(a,d,c){function e(){h=r;var e=d(),f=b.k.q(a);b.g.ea(e,c,"value",f)}var f=["change"],g=c().valueUpdate,h=r;g&&("string"==typeof g&&(g=[g]),b.a.P(f,g),f=b.a.Ga(f));if(b.a.Z&&("input"==a.tagName.toLowerCase()&&"text"==a.type&&"off"!=a.autocomplete&&(!a.form||"off"!=a.form.autocomplete))&&-1==b.a.i(f,"propertychange"))b.a.n(a,"propertychange",function(){h=m}),b.a.n(a,"blur",function(){h&&e()});b.a.o(f,function(c){var d=e;b.a.Ob(c,"after")&&(d=function(){setTimeout(e,
70
+ 0)},c=c.substring(5));b.a.n(a,c,d)})},update:function(a,d){var c="select"===b.a.u(a),e=b.a.d(d()),f=b.k.q(a),g=e!=f;0===e&&(0!==f&&"0"!==f)&&(g=m);g&&(f=function(){b.k.T(a,e)},f(),c&&setTimeout(f,0));c&&0<a.length&&ea(a,e,r)}};b.c.visible={update:function(a,d){var c=b.a.d(d()),e="none"!=a.style.display;c&&!e?a.style.display="":!c&&e&&(a.style.display="none")}};b.c.click={init:function(a,d,c,e){return b.c.event.init.call(this,a,function(){var a={};a.click=d();return a},c,e)}};b.v=function(){};b.v.prototype.renderTemplateSource=
71
+ function(){j(Error("Override renderTemplateSource"))};b.v.prototype.createJavaScriptEvaluatorBlock=function(){j(Error("Override createJavaScriptEvaluatorBlock"))};b.v.prototype.makeTemplateSource=function(a,d){if("string"==typeof a){d=d||y;var c=d.getElementById(a);c||j(Error("Cannot find template with ID "+a));return new b.l.h(c)}if(1==a.nodeType||8==a.nodeType)return new b.l.O(a);j(Error("Unknown template type: "+a))};b.v.prototype.renderTemplate=function(a,b,c,e){a=this.makeTemplateSource(a,e);
72
+ return this.renderTemplateSource(a,b,c)};b.v.prototype.isTemplateRewritten=function(a,b){return this.allowTemplateRewriting===r?m:this.makeTemplateSource(a,b).data("isRewritten")};b.v.prototype.rewriteTemplate=function(a,b,c){a=this.makeTemplateSource(a,c);b=b(a.text());a.text(b);a.data("isRewritten",m)};b.b("templateEngine",b.v);var qa=/(<[a-z]+\d*(\s+(?!data-bind=)[a-z0-9\-]+(=(\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind=(["'])([\s\S]*?)\5/gi,ra=/\x3c!--\s*ko\b\s*([\s\S]*?)\s*--\x3e/g;b.za={vb:function(a,
73
+ d,c){d.isTemplateRewritten(a,c)||d.rewriteTemplate(a,function(a){return b.za.Gb(a,d)},c)},Gb:function(a,b){return a.replace(qa,function(a,e,f,g,h,k,l){return W(l,e,b)}).replace(ra,function(a,e){return W(e,"\x3c!-- ko --\x3e",b)})},kb:function(a){return b.s.ra(function(d,c){d.nextSibling&&b.Fa(d.nextSibling,a,c)})}};b.b("__tr_ambtns",b.za.kb);b.l={};b.l.h=function(a){this.h=a};b.l.h.prototype.text=function(){var a=b.a.u(this.h),a="script"===a?"text":"textarea"===a?"value":"innerHTML";if(0==arguments.length)return this.h[a];
74
+ var d=arguments[0];"innerHTML"===a?b.a.ca(this.h,d):this.h[a]=d};b.l.h.prototype.data=function(a){if(1===arguments.length)return b.a.f.get(this.h,"templateSourceData_"+a);b.a.f.set(this.h,"templateSourceData_"+a,arguments[1])};b.l.O=function(a){this.h=a};b.l.O.prototype=new b.l.h;b.l.O.prototype.text=function(){if(0==arguments.length){var a=b.a.f.get(this.h,"__ko_anon_template__")||{};a.Aa===I&&a.ia&&(a.Aa=a.ia.innerHTML);return a.Aa}b.a.f.set(this.h,"__ko_anon_template__",{Aa:arguments[0]})};b.l.h.prototype.nodes=
75
+ function(){if(0==arguments.length)return(b.a.f.get(this.h,"__ko_anon_template__")||{}).ia;b.a.f.set(this.h,"__ko_anon_template__",{ia:arguments[0]})};b.b("templateSources",b.l);b.b("templateSources.domElement",b.l.h);b.b("templateSources.anonymousTemplate",b.l.O);var O;b.wa=function(a){a!=I&&!(a instanceof b.v)&&j(Error("templateEngine must inherit from ko.templateEngine"));O=a};b.va=function(a,d,c,e,f){c=c||{};(c.templateEngine||O)==I&&j(Error("Set a template engine before calling renderTemplate"));
76
+ f=f||"replaceChildren";if(e){var g=N(e);return b.j(function(){var h=d&&d instanceof b.z?d:new b.z(b.a.d(d)),k="function"==typeof a?a(h.$data,h):a,h=T(e,f,k,h,c);"replaceNode"==f&&(e=h,g=N(e))},p,{Ka:function(){return!g||!b.a.X(g)},W:g&&"replaceNode"==f?g.parentNode:g})}return b.s.ra(function(e){b.va(a,d,c,e,"replaceNode")})};b.Mb=function(a,d,c,e,f){function g(a,b){U(b,k);c.afterRender&&c.afterRender(b,a)}function h(d,e){k=f.createChildContext(b.a.d(d),c.as);k.$index=e;var g="function"==typeof a?
77
+ a(d,k):a;return T(p,"ignoreTargetNode",g,k,c)}var k;return b.j(function(){var a=b.a.d(d)||[];"undefined"==typeof a.length&&(a=[a]);a=b.a.fa(a,function(a){return c.includeDestroyed||a===I||a===p||!b.a.d(a._destroy)});b.r.K(b.a.$a,p,[e,a,h,c,g])},p,{W:e})};b.c.template={init:function(a,d){var c=b.a.d(d());if("string"!=typeof c&&!c.name&&(1==a.nodeType||8==a.nodeType))c=1==a.nodeType?a.childNodes:b.e.childNodes(a),c=b.a.Hb(c),(new b.l.O(a)).nodes(c);return{controlsDescendantBindings:m}},update:function(a,
78
+ d,c,e,f){d=b.a.d(d());c={};e=m;var g,h=p;"string"!=typeof d&&(c=d,d=c.name,"if"in c&&(e=b.a.d(c["if"])),e&&"ifnot"in c&&(e=!b.a.d(c.ifnot)),g=b.a.d(c.data));"foreach"in c?h=b.Mb(d||a,e&&c.foreach||[],c,a,f):e?(f="data"in c?f.createChildContext(g,c.as):f,h=b.va(d||a,f,c,a)):b.e.Y(a);f=h;(g=b.a.f.get(a,"__ko__templateComputedDomDataKey__"))&&"function"==typeof g.B&&g.B();b.a.f.set(a,"__ko__templateComputedDomDataKey__",f&&f.pa()?f:I)}};b.g.Q.template=function(a){a=b.g.aa(a);return 1==a.length&&a[0].unknown||
79
+ b.g.Eb(a,"name")?p:"This template engine does not support anonymous templates nested within its templates"};b.e.I.template=m;b.b("setTemplateEngine",b.wa);b.b("renderTemplate",b.va);b.a.Ja=function(a,b,c){a=a||[];b=b||[];return a.length<=b.length?S(a,b,"added","deleted",c):S(b,a,"deleted","added",c)};b.b("utils.compareArrays",b.a.Ja);b.a.$a=function(a,d,c,e,f){function g(a,b){t=l[b];w!==b&&(z[a]=t);t.na(w++);M(t.M);s.push(t);A.push(t)}function h(a,c){if(a)for(var d=0,e=c.length;d<e;d++)c[d]&&b.a.o(c[d].M,
80
+ function(b){a(b,d,c[d].U)})}d=d||[];e=e||{};var k=b.a.f.get(a,"setDomNodeChildrenFromArrayMapping_lastMappingResult")===I,l=b.a.f.get(a,"setDomNodeChildrenFromArrayMapping_lastMappingResult")||[],n=b.a.V(l,function(a){return a.U}),q=b.a.Ja(n,d),s=[],v=0,w=0,B=[],A=[];d=[];for(var z=[],n=[],t,D=0,C,E;C=q[D];D++)switch(E=C.moved,C.status){case "deleted":E===I&&(t=l[v],t.j&&t.j.B(),B.push.apply(B,M(t.M)),e.beforeRemove&&(d[D]=t,A.push(t)));v++;break;case "retained":g(D,v++);break;case "added":E!==I?
81
+ g(D,E):(t={U:C.value,na:b.m(w++)},s.push(t),A.push(t),k||(n[D]=t))}h(e.beforeMove,z);b.a.o(B,e.beforeRemove?b.A:b.removeNode);for(var D=0,k=b.e.firstChild(a),H;t=A[D];D++){t.M||b.a.extend(t,ha(a,c,t.U,f,t.na));for(v=0;q=t.M[v];k=q.nextSibling,H=q,v++)q!==k&&b.e.Pa(a,q,H);!t.Ab&&f&&(f(t.U,t.M,t.na),t.Ab=m)}h(e.beforeRemove,d);h(e.afterMove,z);h(e.afterAdd,n);b.a.f.set(a,"setDomNodeChildrenFromArrayMapping_lastMappingResult",s)};b.b("utils.setDomNodeChildrenFromArrayMapping",b.a.$a);b.C=function(){this.allowTemplateRewriting=
82
+ r};b.C.prototype=new b.v;b.C.prototype.renderTemplateSource=function(a){var d=!(9>b.a.Z)&&a.nodes?a.nodes():p;if(d)return b.a.L(d.cloneNode(m).childNodes);a=a.text();return b.a.ta(a)};b.C.oa=new b.C;b.wa(b.C.oa);b.b("nativeTemplateEngine",b.C);b.qa=function(){var a=this.Db=function(){if("undefined"==typeof F||!F.tmpl)return 0;try{if(0<=F.tmpl.tag.tmpl.open.toString().indexOf("__"))return 2}catch(a){}return 1}();this.renderTemplateSource=function(b,c,e){e=e||{};2>a&&j(Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later."));
83
+ var f=b.data("precompiled");f||(f=b.text()||"",f=F.template(p,"{{ko_with $item.koBindingContext}}"+f+"{{/ko_with}}"),b.data("precompiled",f));b=[c.$data];c=F.extend({koBindingContext:c},e.templateOptions);c=F.tmpl(f,b,c);c.appendTo(y.createElement("div"));F.fragments={};return c};this.createJavaScriptEvaluatorBlock=function(a){return"{{ko_code ((function() { return "+a+" })()) }}"};this.addTemplate=function(a,b){y.write("<script type='text/html' id='"+a+"'>"+b+"\x3c/script>")};0<a&&(F.tmpl.tag.ko_code=
84
+ {open:"__.push($1 || '');"},F.tmpl.tag.ko_with={open:"with($1) {",close:"} "})};b.qa.prototype=new b.v;w=new b.qa;0<w.Db&&b.wa(w);b.b("jqueryTmplTemplateEngine",b.qa)}"function"===typeof require&&"object"===typeof exports&&"object"===typeof module?L(module.exports||exports):"function"===typeof define&&define.amd?define(["exports"],L):L(x.ko={});m;
85
+ })();
uninstall.php ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <?php /**
2
+ * @kyos
3
+ * Uninstall plugin
4
+ */
wp-security-audit-log.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: WP Security Audit Log
4
+ Plugin URI: http://www.wpprohelp.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 an event for every action it logs. Use the Audit Log Viewer to see all the events.
6
+ Author: WPProHelp
7
+ Contributors: kyos
8
+ Version: 0.1
9
+ Author URI: http://www.wpprohelp.com/
10
+ License: GPL2
11
+
12
+ WP Security Audit Log
13
+ Copyright(c) 2013 Robert Abela (email : robert@wpprohelp.com)
14
+
15
+ This program is free software; you can redistribute it and/or modify
16
+ it under the terms of the GNU General Public License, version 2, as
17
+ published by the Free Software Foundation.
18
+
19
+ This program is distributed in the hope that it will be useful,
20
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
21
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
+ GNU General Public License for more details.
23
+
24
+ You should have received a copy of the GNU General Public License
25
+ along with this program; if not, write to the Free Software
26
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
27
+ */
28
+ //#! Holds the plugin option name
29
+ define('WPPH_PLUGIN_SETTING_NAME', 'wpph_plugin_settings');
30
+ define('WPPH_PLUGIN_PREFIX', 'wpph_');
31
+ define('WPPH_PLUGIN_NAME', 'WP Security Audit Log');
32
+ define('WPPH_PLUGIN_URL', trailingslashit(plugins_url('', __FILE__)));
33
+ define('WPPH_PLUGIN_DIR', trailingslashit(plugin_dir_path(__FILE__)));
34
+ define('WPPH_PLUGIN_BASE_NAME', basename(__DIR__));
35
+
36
+ //#! Load required files
37
+ require('inc/WPPHLogger.php');
38
+ require('inc/WPPHUtil.php');
39
+ require('inc/WPPHAdminNotices.php');
40
+ require('inc/WPPHDatabase.php');
41
+ require('inc/WPPHEvent.php');
42
+ require('inc/WPPH.php');
43
+
44
+ //#! Create tables
45
+ register_activation_hook( __FILE__, array('WPPHDatabase', 'checkTables') );
46
+
47
+ //#! Load resources
48
+ add_action('admin_init', array('WPPH', 'loadBaseResources'));
49
+
50
+ //#! Add the sidebar menu
51
+ add_action('admin_menu', array('WPPH', 'createPluginWpSidebar'));
52
+
53
+ //#! Bind hooks
54
+ WPPHEvent::bindHooks(array('hookLoginEvent','hookLogoutEvent','hookUserRegisterEvent','hookEventsDeletion','hookUserRoleUpdated','hookUserPasswordUpdated','hookUserEmailUpdated','hookLoginFailure','hookUserDeletion','hookWatchPluginActivity','hookWatchBlogActivity','hookFileDeletion','hookFileUploaded','hookFileUploadedDeleted','hookThemeChange','hookTrashPost','hookTrashPage','hookUntrashedPosts','hookUntrashedPages'));
55
+
56
+ /* Enable ajax functionality in the settings page */
57
+ add_action('wp_ajax_wpph_get_events', array('WPPHUtil','get_events_html'));
58
+ //#! End wp-security-audit-log