Version Description
- Initial beta release of WP Security Audit Log.
Download this release
Release Info
Developer | WPProHelp |
Plugin | WP Security Audit Log |
Version | 0.1 |
Comparing to | |
See all releases |
Version 0.1
- inc/WPPH.php +107 -0
- inc/WPPHAdminNotices.php +82 -0
- inc/WPPHDatabase.php +296 -0
- inc/WPPHEvent.php +884 -0
- inc/WPPHLogger.php +28 -0
- inc/WPPHUtil.php +113 -0
- pages/about.php +12 -0
- pages/dashboard.php +117 -0
- pages/settings.php +259 -0
- pages/support.php +12 -0
- readme.txt +73 -0
- res/css/styles.base.css +97 -0
- res/img/ajax-loader.gif +0 -0
- res/img/error-icon.png +0 -0
- res/img/logo-main-menu.png +0 -0
- res/img/page-about-logo.png +0 -0
- res/img/page-settings-logo.png +0 -0
- res/img/page-support-logo.png +0 -0
- res/img/page-viewer-logo.png +0 -0
- res/img/success-icon.png +0 -0
- res/js/AuditLogViewModel.js +145 -0
- res/js/knockout-2.2.1.min.js +85 -0
- uninstall.php +4 -0
- wp-security-audit-log.php +58 -0
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>></span></button>
|
28 |
+
<button class="pageButton buttonPrevious" title="Previous" href="#" data-bind="disable: loading, click: prevPage, css: {wpphButtonDisabled: offset() <= 0}"><span><</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>></span></button>
|
91 |
+
<button class="pageButton buttonPrevious" title="<?php echo __('Previous');?>" href="#" data-bind="disable: loading, click: prevPage, css: {wpphButtonDisabled: offset() <= 0}"><span><</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
|