Version Description
(November 2015) =
- Added: Now logs when a user changes their password using the "reset password" link.
- Added: Now logs when a user uses the password reset form.
- Added: New method
register_dropin
that can be used to add dropins. - Added: New action
simple_history/add_custom_dropin
. - Added: Example on how to add an external dropin: example-dropin.php.
- Added: "Last day" added to filter, because a brute force attack can add so many logs that it's not possible to fetch a whole week.
- Changed: Filter
simple_history/log/do_log
now pass 5 arguments instead of 3. Before this update in was not possible for multiple add_action()-calls to use this filter, because you would not now if any other code had canceled it and so on. If you have been using this filter you need to modify your code. - Changed: When hovering the time of an event in the log, the date of the event displays in both local time and GMT time. Hopefully makes it easier for admins in different timezones that work together on a site to understand when each event happened. Fixes https://github.com/bonny/WordPress-Simple-History/issues/84.
- Fixed: Line height was a bit tight on the dashboard. Also: the margin was a tad to small for the first logged event on the dashboard.
- Fixed: Username was not added correctly to failed login attempts when using plugin Captcha on Login + it would still show that a user logged out sometimes when a bot/script brute force attacked a site by only sending login and password and not the captcha field.
Download this release
Release Info
Developer | eskapism |
Plugin | Simple History |
Version | 2.4 |
Comparing to | |
See all releases |
Code changes from version 2.3.1 to 2.4
- css/styles.css +2 -2
- dropins/SimpleHistoryFilterDropin.php +17 -5
- dropins/SimpleHistoryPluginPatchesDropin.php +37 -24
- dropins/SimpleHistorySidebarDropin.php +13 -4
- examples/example-dropin.php +68 -0
- inc/SimpleHistory.php +36 -4
- index.php +6 -6
- loggers/SimpleLogger.php +34 -6
- loggers/SimpleUserLogger.php +94 -0
- readme.txt +22 -5
css/styles.css
CHANGED
@@ -502,7 +502,7 @@ i.e. the log is inside a .postbox element
|
|
502 |
}
|
503 |
|
504 |
.postbox .SimpleHistoryLogitem:first-child {
|
505 |
-
padding-top: 0
|
506 |
}
|
507 |
|
508 |
.postbox .SimpleHistoryLogitem::before {
|
@@ -536,7 +536,7 @@ i.e. the log is inside a .postbox element
|
|
536 |
.postbox .SimpleHistoryLogitem__text,
|
537 |
.postbox .SimpleHistoryLogitem__details,
|
538 |
.postbox .SimpleHistoryLogitem__details p {
|
539 |
-
line-height: 1.
|
540 |
}
|
541 |
|
542 |
.postbox .SimpleHistoryPaginationLink,
|
502 |
}
|
503 |
|
504 |
.postbox .SimpleHistoryLogitem:first-child {
|
505 |
+
/*padding-top: 0;*/
|
506 |
}
|
507 |
|
508 |
.postbox .SimpleHistoryLogitem::before {
|
536 |
.postbox .SimpleHistoryLogitem__text,
|
537 |
.postbox .SimpleHistoryLogitem__details,
|
538 |
.postbox .SimpleHistoryLogitem__details p {
|
539 |
+
line-height: 1.5;
|
540 |
}
|
541 |
|
542 |
.postbox .SimpleHistoryPaginationLink,
|
dropins/SimpleHistoryFilterDropin.php
CHANGED
@@ -127,6 +127,10 @@ class SimpleHistoryFilterDropin {
|
|
127 |
$numEvents = $this->get_unique_events_for_days($daysToShow);
|
128 |
$numPages = $numEvents / $this->sh->get_pager_size();
|
129 |
|
|
|
|
|
|
|
|
|
130 |
if ( $numPages < 20 ) {
|
131 |
|
132 |
// Not that many things the last 7 days. Let's try to expand to 14 daysinstead.
|
@@ -155,33 +159,41 @@ class SimpleHistoryFilterDropin {
|
|
155 |
placeholder="<?php echo _e("All dates", "simple-history") ?>" multiple>
|
156 |
<?php
|
157 |
|
158 |
-
// Last week + two weeks back + 30 days back
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
159 |
printf(
|
160 |
'<option value="%1$s" %3$s>%2$s</option>',
|
161 |
"lastdays:7", // 1 - value
|
162 |
_x("Last 7 days", "Filter dropin: filter week", "simple-history"), // 2 text
|
163 |
-
selected($daysToShow, 7, 0)
|
164 |
);
|
165 |
|
166 |
printf(
|
167 |
'<option value="%1$s" %3$s>%2$s</option>',
|
168 |
"lastdays:14", // 1 - value
|
169 |
_x("Last 14 days", "Filter dropin: filter week", "simple-history"), // 2 text
|
170 |
-
selected($daysToShow, 14, 0)
|
171 |
);
|
172 |
|
173 |
printf(
|
174 |
'<option value="%1$s" %3$s>%2$s</option>',
|
175 |
"lastdays:30", // 1 - value
|
176 |
_x("Last 30 days", "Filter dropin: filter week", "simple-history"), // 2 text
|
177 |
-
selected($daysToShow, 30, 0)
|
178 |
);
|
179 |
|
180 |
printf(
|
181 |
'<option value="%1$s" %3$s>%2$s</option>',
|
182 |
"lastdays:60", // 1 - value
|
183 |
_x("Last 60 days", "Filter dropin: filter week", "simple-history"), // 2 text
|
184 |
-
selected($daysToShow, 60, 0)
|
185 |
);
|
186 |
|
187 |
// Months
|
127 |
$numEvents = $this->get_unique_events_for_days($daysToShow);
|
128 |
$numPages = $numEvents / $this->sh->get_pager_size();
|
129 |
|
130 |
+
// Example on my server with lots of brute force attacks
|
131 |
+
// 166434 / 15 = 11 000 pages for last 7 days
|
132 |
+
// 1 day = 3051 / 15 = 203 pages = still much but better than 11000 pages!
|
133 |
+
|
134 |
if ( $numPages < 20 ) {
|
135 |
|
136 |
// Not that many things the last 7 days. Let's try to expand to 14 daysinstead.
|
159 |
placeholder="<?php echo _e("All dates", "simple-history") ?>" multiple>
|
160 |
<?php
|
161 |
|
162 |
+
// One day+ Last week + two weeks back + 30 days back
|
163 |
+
|
164 |
+
printf(
|
165 |
+
'<option value="%1$s" %3$s>%2$s</option>',
|
166 |
+
"lastdays:1", // 1 - value
|
167 |
+
_x("Last day", "Filter dropin: filter week", "simple-history"), // 2 text
|
168 |
+
selected( $daysToShow, 1, 0 )
|
169 |
+
);
|
170 |
+
|
171 |
printf(
|
172 |
'<option value="%1$s" %3$s>%2$s</option>',
|
173 |
"lastdays:7", // 1 - value
|
174 |
_x("Last 7 days", "Filter dropin: filter week", "simple-history"), // 2 text
|
175 |
+
selected( $daysToShow, 7, 0 )
|
176 |
);
|
177 |
|
178 |
printf(
|
179 |
'<option value="%1$s" %3$s>%2$s</option>',
|
180 |
"lastdays:14", // 1 - value
|
181 |
_x("Last 14 days", "Filter dropin: filter week", "simple-history"), // 2 text
|
182 |
+
selected( $daysToShow, 14, 0 )
|
183 |
);
|
184 |
|
185 |
printf(
|
186 |
'<option value="%1$s" %3$s>%2$s</option>',
|
187 |
"lastdays:30", // 1 - value
|
188 |
_x("Last 30 days", "Filter dropin: filter week", "simple-history"), // 2 text
|
189 |
+
selected( $daysToShow, 30, 0 )
|
190 |
);
|
191 |
|
192 |
printf(
|
193 |
'<option value="%1$s" %3$s>%2$s</option>',
|
194 |
"lastdays:60", // 1 - value
|
195 |
_x("Last 60 days", "Filter dropin: filter week", "simple-history"), // 2 text
|
196 |
+
selected( $daysToShow, 60, 0 )
|
197 |
);
|
198 |
|
199 |
// Months
|
dropins/SimpleHistoryPluginPatchesDropin.php
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
<?php
|
2 |
-
|
3 |
defined( 'ABSPATH' ) or die();
|
4 |
|
5 |
/*
|
@@ -39,37 +38,55 @@ class SimpleHistoryPluginPatchesDropin {
|
|
39 |
*/
|
40 |
function patch_captcha_on_login() {
|
41 |
|
42 |
-
add_action( "simple_history/log/do_log", array( $this, "patch_captcha_on_login_on_log" ), 10,
|
43 |
|
44 |
}
|
45 |
|
46 |
// Detect that this log message is being called from Captha on login
|
47 |
-
|
|
|
48 |
|
49 |
if ( empty( $context ) || ! isset( $context["_message_key"] ) || "user_logged_out" != $context["_message_key"] ) {
|
50 |
// Message key did not exist or was not "user_logged_out"
|
51 |
-
return;
|
52 |
}
|
53 |
-
|
|
|
|
|
54 |
// codiga is the input with the captcha
|
|
|
55 |
if ( ! isset( $_POST["log"], $_POST["pwd"], $_POST["wp-submit"], $_POST["codigo"] ) ) {
|
56 |
// All needed post variables was not set
|
57 |
-
return;
|
58 |
}
|
|
|
59 |
|
60 |
// The Captcha on login uses a class called 'Anderson_Makiyama_Captcha_On_Login'
|
61 |
-
// and also a
|
62 |
global $anderson_makiyama;
|
63 |
if ( ! class_exists("Anderson_Makiyama_Captcha_On_Login") || ! isset( $anderson_makiyama ) ) {
|
64 |
-
return;
|
65 |
}
|
66 |
-
|
67 |
// We must come from wp-login
|
|
|
|
|
68 |
$wp_referer = wp_get_referer();
|
69 |
if ( ! $wp_referer || ! "wp-login.php" == basename( $wp_referer ) ) {
|
70 |
-
return;
|
71 |
}
|
72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
$anderson_makiyama_indice = Anderson_Makiyama_Captcha_On_Login::PLUGIN_ID;
|
74 |
$capcha_on_login_class_name = $anderson_makiyama[$anderson_makiyama_indice]::CLASS_NAME;
|
75 |
|
@@ -90,8 +107,9 @@ class SimpleHistoryPluginPatchesDropin {
|
|
90 |
|
91 |
// Get the user logger
|
92 |
$userLogger = $this->sh->getInstantiatedLoggerBySlug( "SimpleUserLogger" );
|
|
|
93 |
if ( ! $userLogger ) {
|
94 |
-
return;
|
95 |
}
|
96 |
|
97 |
// $userLogger->warningMessage("user_unknown_login_failed", $context);
|
@@ -119,7 +137,10 @@ class SimpleHistoryPluginPatchesDropin {
|
|
119 |
// Get user id and email and login
|
120 |
// Not passed to filter, but we have it in $_POST
|
121 |
$login_username = isset( $_POST["log"] ) ? $_POST["log"] : null;
|
122 |
-
|
|
|
|
|
|
|
123 |
|
124 |
$user = get_user_by( "login", $login_username );
|
125 |
|
@@ -127,7 +148,6 @@ class SimpleHistoryPluginPatchesDropin {
|
|
127 |
|
128 |
$context["login_user_id"] = $user->ID;
|
129 |
$context["login_user_email"] = $user->user_email;
|
130 |
-
$context["login_user_login"] = $user->user_login;
|
131 |
|
132 |
}
|
133 |
|
@@ -136,17 +156,10 @@ class SimpleHistoryPluginPatchesDropin {
|
|
136 |
$userLogger->warningMessage("user_login_failed", $context);
|
137 |
|
138 |
// Cancel original log event
|
139 |
-
|
140 |
-
|
141 |
-
/*$this->system_debug_log(
|
142 |
-
__FUNCTION__,
|
143 |
-
$level,
|
144 |
-
$message,
|
145 |
-
$context,
|
146 |
-
$last_login_status
|
147 |
-
);
|
148 |
-
*/
|
149 |
|
|
|
|
|
150 |
}
|
151 |
|
152 |
/**
|
1 |
<?php
|
|
|
2 |
defined( 'ABSPATH' ) or die();
|
3 |
|
4 |
/*
|
38 |
*/
|
39 |
function patch_captcha_on_login() {
|
40 |
|
41 |
+
add_action( "simple_history/log/do_log", array( $this, "patch_captcha_on_login_on_log" ), 10, 5 );
|
42 |
|
43 |
}
|
44 |
|
45 |
// Detect that this log message is being called from Captha on login
|
46 |
+
// and that the message is "user_logged_out"
|
47 |
+
function patch_captcha_on_login_on_log( $doLog, $level = null, $message = null, $context = null, $loggerInstance = null ) {
|
48 |
|
49 |
if ( empty( $context ) || ! isset( $context["_message_key"] ) || "user_logged_out" != $context["_message_key"] ) {
|
50 |
// Message key did not exist or was not "user_logged_out"
|
51 |
+
return $doLog;
|
52 |
}
|
53 |
+
|
54 |
+
// 22 nov 2015: disabled this check beacuse for example robots/scripts don't pass all args
|
55 |
+
// instead they only post "log" and "pwd"
|
56 |
// codiga is the input with the captcha
|
57 |
+
/*
|
58 |
if ( ! isset( $_POST["log"], $_POST["pwd"], $_POST["wp-submit"], $_POST["codigo"] ) ) {
|
59 |
// All needed post variables was not set
|
60 |
+
return $doLog;
|
61 |
}
|
62 |
+
*/
|
63 |
|
64 |
// The Captcha on login uses a class called 'Anderson_Makiyama_Captcha_On_Login'
|
65 |
+
// and also a global variable called $global $anderson_makiyama
|
66 |
global $anderson_makiyama;
|
67 |
if ( ! class_exists("Anderson_Makiyama_Captcha_On_Login") || ! isset( $anderson_makiyama ) ) {
|
68 |
+
return $doLog;
|
69 |
}
|
70 |
+
|
71 |
// We must come from wp-login
|
72 |
+
// Disabled 22 nov 2015 because robots/scripts dont send referer
|
73 |
+
/*
|
74 |
$wp_referer = wp_get_referer();
|
75 |
if ( ! $wp_referer || ! "wp-login.php" == basename( $wp_referer ) ) {
|
76 |
+
return $doLog;
|
77 |
}
|
78 |
+
*/
|
79 |
+
|
80 |
+
if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
|
81 |
+
return $doLog;
|
82 |
+
}
|
83 |
+
|
84 |
+
// File must be wp-login.php (can it even be another?)
|
85 |
+
$request_uri = basename( wp_unslash( $_SERVER['REQUEST_URI'] ) );
|
86 |
+
if ( "wp-login.php" !== $request_uri ) {
|
87 |
+
return $doLog;
|
88 |
+
}
|
89 |
+
|
90 |
$anderson_makiyama_indice = Anderson_Makiyama_Captcha_On_Login::PLUGIN_ID;
|
91 |
$capcha_on_login_class_name = $anderson_makiyama[$anderson_makiyama_indice]::CLASS_NAME;
|
92 |
|
107 |
|
108 |
// Get the user logger
|
109 |
$userLogger = $this->sh->getInstantiatedLoggerBySlug( "SimpleUserLogger" );
|
110 |
+
|
111 |
if ( ! $userLogger ) {
|
112 |
+
return $doLog;
|
113 |
}
|
114 |
|
115 |
// $userLogger->warningMessage("user_unknown_login_failed", $context);
|
137 |
// Get user id and email and login
|
138 |
// Not passed to filter, but we have it in $_POST
|
139 |
$login_username = isset( $_POST["log"] ) ? $_POST["log"] : null;
|
140 |
+
|
141 |
+
if ( $login_username ) {
|
142 |
+
|
143 |
+
$context["login_user_login"] = $login_username;
|
144 |
|
145 |
$user = get_user_by( "login", $login_username );
|
146 |
|
148 |
|
149 |
$context["login_user_id"] = $user->ID;
|
150 |
$context["login_user_email"] = $user->user_email;
|
|
|
151 |
|
152 |
}
|
153 |
|
156 |
$userLogger->warningMessage("user_login_failed", $context);
|
157 |
|
158 |
// Cancel original log event
|
159 |
+
$doLog = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
160 |
|
161 |
+
return $doLog;
|
162 |
+
|
163 |
}
|
164 |
|
165 |
/**
|
dropins/SimpleHistorySidebarDropin.php
CHANGED
@@ -97,18 +97,27 @@ class SimpleHistorySidebarDropin {
|
|
97 |
*/
|
98 |
|
99 |
// Box about possible events missing
|
100 |
-
$boxMissingEvents = '
|
101 |
<div class="postbox">
|
102 |
-
<h3 class="hndle"
|
103 |
<div class="inside">
|
104 |
-
<p
|
|
|
105 |
</div>
|
106 |
</div>
|
107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
//echo $boxMissingEvents;
|
109 |
|
110 |
$arrBoxes = array(
|
111 |
"boxReview" => $boxReview,
|
|
|
112 |
"boxDonate" => $boxDonate,
|
113 |
// "boxGithub" => $boxGithub,
|
114 |
);
|
97 |
*/
|
98 |
|
99 |
// Box about possible events missing
|
100 |
+
$boxMissingEvents = sprintf( '
|
101 |
<div class="postbox">
|
102 |
+
<h3 class="hndle">%1$s</h3>
|
103 |
<div class="inside">
|
104 |
+
<p>%2$s</p>
|
105 |
+
<p><a href="hello@simple-history.com">hello@simple-history.com</a></p>
|
106 |
</div>
|
107 |
</div>
|
108 |
+
',
|
109 |
+
_x("Add more to the log", "Sidebar box", "simple-history"), // 1
|
110 |
+
_x("Are there things you miss in the history log?", "Sidebar box", "simple-history") // 2
|
111 |
+
|
112 |
+
// Add events yourself using the Logger API
|
113 |
+
// Tell the developer
|
114 |
+
|
115 |
+
);
|
116 |
//echo $boxMissingEvents;
|
117 |
|
118 |
$arrBoxes = array(
|
119 |
"boxReview" => $boxReview,
|
120 |
+
"boxMissingEvents" => $boxMissingEvents,
|
121 |
"boxDonate" => $boxDonate,
|
122 |
// "boxGithub" => $boxGithub,
|
123 |
);
|
examples/example-dropin.php
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
// No external calls allowed to test file
|
4 |
+
exit;
|
5 |
+
|
6 |
+
|
7 |
+
/**
|
8 |
+
* This example shows how to create a simple dropin
|
9 |
+
* that will add a tab to the simple history settings page
|
10 |
+
*/
|
11 |
+
|
12 |
+
// We use the function "register_logger" to tell tell SimpleHistory that our custom logger exists.
|
13 |
+
// We call it from inside the filter "simple_history/add_custom_logger".
|
14 |
+
add_action("simple_history/add_custom_dropin", function( $simpleHistory ) {
|
15 |
+
|
16 |
+
$simpleHistory->register_dropin("AddSettingsPageTab");
|
17 |
+
|
18 |
+
});
|
19 |
+
|
20 |
+
|
21 |
+
/**
|
22 |
+
* This is the class that does the main work!
|
23 |
+
*/
|
24 |
+
class AddSettingsPageTab {
|
25 |
+
|
26 |
+
// This will hold a reference to the simple history instance
|
27 |
+
private $sh;
|
28 |
+
|
29 |
+
// simple history will pass itself to the constructor
|
30 |
+
function __construct( $sh ) {
|
31 |
+
|
32 |
+
$this->sh = $sh;
|
33 |
+
|
34 |
+
$this->init();
|
35 |
+
|
36 |
+
}
|
37 |
+
|
38 |
+
function init() {
|
39 |
+
|
40 |
+
add_action( "init", array( $this, "add_settings_tab" ) );
|
41 |
+
|
42 |
+
}
|
43 |
+
|
44 |
+
function add_settings_tab() {
|
45 |
+
|
46 |
+
$this->sh->registerSettingsTab( array(
|
47 |
+
"slug" => "my_unique_settings_tab_slug",
|
48 |
+
"name" => __( "Example tab", "simple-history" ),
|
49 |
+
"function" => array( $this, "settings_tab_output" ),
|
50 |
+
) );
|
51 |
+
|
52 |
+
}
|
53 |
+
|
54 |
+
function settings_tab_output() {
|
55 |
+
|
56 |
+
?>
|
57 |
+
|
58 |
+
<h3>Hi there!</h3>
|
59 |
+
|
60 |
+
<p>I'm the output from on settings tab.</p>
|
61 |
+
|
62 |
+
<?php
|
63 |
+
|
64 |
+
}
|
65 |
+
|
66 |
+
} // end class
|
67 |
+
|
68 |
+
|
inc/SimpleHistory.php
CHANGED
@@ -19,6 +19,11 @@ class SimpleHistory {
|
|
19 |
*/
|
20 |
private $externalLoggers;
|
21 |
|
|
|
|
|
|
|
|
|
|
|
22 |
/**
|
23 |
* Array with all instantiated loggers
|
24 |
*/
|
@@ -530,6 +535,7 @@ class SimpleHistory {
|
|
530 |
public function setup_variables() {
|
531 |
|
532 |
$this->externalLoggers = array();
|
|
|
533 |
$this->instantiatedLoggers = array();
|
534 |
$this->instantiatedDropins = array();
|
535 |
|
@@ -609,7 +615,7 @@ class SimpleHistory {
|
|
609 |
|
610 |
/**
|
611 |
* Register an external logger so Simple History knows about it.
|
612 |
-
* Does not load the logger, so file with logger must be loaded already.
|
613 |
*
|
614 |
* See example-logger.php for an example on how to use this.
|
615 |
*
|
@@ -621,6 +627,20 @@ class SimpleHistory {
|
|
621 |
|
622 |
}
|
623 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
624 |
/**
|
625 |
* Load built in loggers from all files in /loggers
|
626 |
* and instantiates them
|
@@ -696,14 +716,13 @@ class SimpleHistory {
|
|
696 |
}
|
697 |
|
698 |
/**
|
699 |
-
* Action that plugins
|
700 |
* See register_logger() for more info.
|
701 |
*
|
702 |
* @since 2.1
|
703 |
*
|
704 |
-
* @param
|
705 |
*/
|
706 |
-
|
707 |
do_action( "simple_history/add_custom_logger", $this );
|
708 |
|
709 |
$arrLoggersToInstantiate = array_merge( $arrLoggersToInstantiate, $this->externalLoggers );
|
@@ -890,6 +909,16 @@ class SimpleHistory {
|
|
890 |
|
891 |
}
|
892 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
893 |
/**
|
894 |
* Filter the array with names of dropin to instantiate.
|
895 |
*
|
@@ -899,6 +928,8 @@ class SimpleHistory {
|
|
899 |
*/
|
900 |
$arrDropinsToInstantiate = apply_filters( "simple_history/dropins_to_instantiate", $arrDropinsToInstantiate );
|
901 |
|
|
|
|
|
902 |
// Instantiate each dropin
|
903 |
foreach ( $arrDropinsToInstantiate as $oneDropinName ) {
|
904 |
|
@@ -3013,3 +3044,4 @@ function simple_history_text_diff( $left_string, $right_string, $args = null ) {
|
|
3013 |
|
3014 |
return $r;
|
3015 |
}
|
|
19 |
*/
|
20 |
private $externalLoggers;
|
21 |
|
22 |
+
/**
|
23 |
+
* Array with external dropins to load
|
24 |
+
*/
|
25 |
+
private $externalDropins;
|
26 |
+
|
27 |
/**
|
28 |
* Array with all instantiated loggers
|
29 |
*/
|
535 |
public function setup_variables() {
|
536 |
|
537 |
$this->externalLoggers = array();
|
538 |
+
$this->externalDropins = array();
|
539 |
$this->instantiatedLoggers = array();
|
540 |
$this->instantiatedDropins = array();
|
541 |
|
615 |
|
616 |
/**
|
617 |
* Register an external logger so Simple History knows about it.
|
618 |
+
* Does not load the logger, so file with logger class must be loaded already.
|
619 |
*
|
620 |
* See example-logger.php for an example on how to use this.
|
621 |
*
|
627 |
|
628 |
}
|
629 |
|
630 |
+
/**
|
631 |
+
* Register an external dropin so Simple History knows about it.
|
632 |
+
* Does not load the dropin, so file with dropin class must be loaded already.
|
633 |
+
*
|
634 |
+
* See example-dropin.php for an example on how to use this.
|
635 |
+
*
|
636 |
+
* @since 2.1
|
637 |
+
*/
|
638 |
+
function register_dropin( $dropinClassName ) {
|
639 |
+
|
640 |
+
$this->externalDropins[] = $dropinClassName;
|
641 |
+
|
642 |
+
}
|
643 |
+
|
644 |
/**
|
645 |
* Load built in loggers from all files in /loggers
|
646 |
* and instantiates them
|
716 |
}
|
717 |
|
718 |
/**
|
719 |
+
* Action that plugins can use to add their custom loggers.
|
720 |
* See register_logger() for more info.
|
721 |
*
|
722 |
* @since 2.1
|
723 |
*
|
724 |
+
* @param SimpleHistory instance
|
725 |
*/
|
|
|
726 |
do_action( "simple_history/add_custom_logger", $this );
|
727 |
|
728 |
$arrLoggersToInstantiate = array_merge( $arrLoggersToInstantiate, $this->externalLoggers );
|
909 |
|
910 |
}
|
911 |
|
912 |
+
/**
|
913 |
+
* Action that dropins can use to add their custom loggers.
|
914 |
+
* See register_dropin() for more info.
|
915 |
+
*
|
916 |
+
* @since 2.3.2
|
917 |
+
*
|
918 |
+
* @param array $arrDropinsToInstantiate Array with class names
|
919 |
+
*/
|
920 |
+
do_action( "simple_history/add_custom_dropin", $this );
|
921 |
+
|
922 |
/**
|
923 |
* Filter the array with names of dropin to instantiate.
|
924 |
*
|
928 |
*/
|
929 |
$arrDropinsToInstantiate = apply_filters( "simple_history/dropins_to_instantiate", $arrDropinsToInstantiate );
|
930 |
|
931 |
+
$arrDropinsToInstantiate = array_merge( $arrDropinsToInstantiate, $this->externalDropins );
|
932 |
+
|
933 |
// Instantiate each dropin
|
934 |
foreach ( $arrDropinsToInstantiate as $oneDropinName ) {
|
935 |
|
3044 |
|
3045 |
return $r;
|
3046 |
}
|
3047 |
+
|
index.php
CHANGED
@@ -5,7 +5,7 @@ Plugin URI: http://simple-history.com
|
|
5 |
Text Domain: simple-history
|
6 |
Domain Path: /languages
|
7 |
Description: Plugin that logs various things that occur in WordPress and then presents those events in a very nice GUI.
|
8 |
-
Version: 2.
|
9 |
Author: Pär Thernström
|
10 |
Author URI: http://simple-history.com/
|
11 |
License: GPL2
|
@@ -34,10 +34,6 @@ if ( ! defined( 'WPINC' ) ) {
|
|
34 |
|
35 |
if ( version_compare( phpversion(), "5.3", ">=") ) {
|
36 |
|
37 |
-
/** Load required files */
|
38 |
-
require_once(__DIR__ . "/inc/SimpleHistory.php");
|
39 |
-
require_once(__DIR__ . "/inc/SimpleHistoryLogQuery.php");
|
40 |
-
|
41 |
/**
|
42 |
* Register function that is called when plugin is installed
|
43 |
*
|
@@ -46,7 +42,7 @@ if ( version_compare( phpversion(), "5.3", ">=") ) {
|
|
46 |
// register_activation_hook( trailingslashit(WP_PLUGIN_DIR) . trailingslashit( plugin_basename(__DIR__) ) . "index.php" , array("SimpleHistory", "on_plugin_activate" ) );
|
47 |
|
48 |
if ( ! defined( 'SIMPLE_HISTORY_VERSION' ) ) {
|
49 |
-
define( 'SIMPLE_HISTORY_VERSION', '2.
|
50 |
}
|
51 |
|
52 |
if ( ! defined( 'SIMPLE_HISTORY_PATH' ) ) {
|
@@ -65,6 +61,10 @@ if ( version_compare( phpversion(), "5.3", ">=") ) {
|
|
65 |
define( 'SIMPLE_HISTORY_FILE', __FILE__ );
|
66 |
}
|
67 |
|
|
|
|
|
|
|
|
|
68 |
// Prev behavior:
|
69 |
/*
|
70 |
define( 'SIMPLE_HISTORY_FILE', __FILE__ );
|
5 |
Text Domain: simple-history
|
6 |
Domain Path: /languages
|
7 |
Description: Plugin that logs various things that occur in WordPress and then presents those events in a very nice GUI.
|
8 |
+
Version: 2.4
|
9 |
Author: Pär Thernström
|
10 |
Author URI: http://simple-history.com/
|
11 |
License: GPL2
|
34 |
|
35 |
if ( version_compare( phpversion(), "5.3", ">=") ) {
|
36 |
|
|
|
|
|
|
|
|
|
37 |
/**
|
38 |
* Register function that is called when plugin is installed
|
39 |
*
|
42 |
// register_activation_hook( trailingslashit(WP_PLUGIN_DIR) . trailingslashit( plugin_basename(__DIR__) ) . "index.php" , array("SimpleHistory", "on_plugin_activate" ) );
|
43 |
|
44 |
if ( ! defined( 'SIMPLE_HISTORY_VERSION' ) ) {
|
45 |
+
define( 'SIMPLE_HISTORY_VERSION', '2.4' );
|
46 |
}
|
47 |
|
48 |
if ( ! defined( 'SIMPLE_HISTORY_PATH' ) ) {
|
61 |
define( 'SIMPLE_HISTORY_FILE', __FILE__ );
|
62 |
}
|
63 |
|
64 |
+
/** Load required files */
|
65 |
+
require_once(__DIR__ . "/inc/SimpleHistory.php");
|
66 |
+
require_once(__DIR__ . "/inc/SimpleHistoryLogQuery.php");
|
67 |
+
|
68 |
// Prev behavior:
|
69 |
/*
|
70 |
define( 'SIMPLE_HISTORY_FILE', __FILE__ );
|
loggers/SimpleLogger.php
CHANGED
@@ -119,7 +119,7 @@ class SimpleLogger {
|
|
119 |
* @param array $context
|
120 |
* @param array $row Currently not always passed, because loggers need to be updated to support this...
|
121 |
*/
|
122 |
-
function interpolate($message, $context = array(), $row = null) {
|
123 |
|
124 |
if ( ! is_array( $context ) ) {
|
125 |
return $message;
|
@@ -135,10 +135,28 @@ class SimpleLogger {
|
|
135 |
// Build a replacement array with braces around the context keys
|
136 |
$replace = array();
|
137 |
foreach ( $context as $key => $val ) {
|
|
|
|
|
|
|
|
|
|
|
|
|
138 |
$replace['{' . $key . '}'] = $val;
|
|
|
139 |
}
|
140 |
|
141 |
// Interpolate replacement values into the message and return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
return strtr($message, $replace);
|
143 |
|
144 |
}
|
@@ -395,16 +413,25 @@ class SimpleLogger {
|
|
395 |
$str_when = sprintf(__('%1$s ago', 'simple-history'), $date_human_time_diff);
|
396 |
|
397 |
}
|
398 |
-
|
399 |
$item_permalink = admin_url("index.php?page=simple_history_page");
|
400 |
$item_permalink .= "#item/{$row->id}";
|
401 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
402 |
$date_html = "<span class='SimpleHistoryLogitem__permalink SimpleHistoryLogitem__when SimpleHistoryLogitem__inlineDivided'>";
|
403 |
$date_html .= "<a class='' href='{$item_permalink}'>";
|
404 |
$date_html .= sprintf(
|
405 |
-
'<time datetime="%
|
406 |
-
$
|
407 |
-
$str_when
|
|
|
408 |
);
|
409 |
$date_html .= "</a>";
|
410 |
$date_html .= "</span>";
|
@@ -872,7 +899,8 @@ class SimpleLogger {
|
|
872 |
*
|
873 |
* @since 2.3.1
|
874 |
*/
|
875 |
-
$do_log = apply_filters( "simple_history/log/do_log", $level, $message, $context );
|
|
|
876 |
if ( $do_log === false ) {
|
877 |
return $this;
|
878 |
}
|
119 |
* @param array $context
|
120 |
* @param array $row Currently not always passed, because loggers need to be updated to support this...
|
121 |
*/
|
122 |
+
function interpolate( $message, $context = array(), $row = null ) {
|
123 |
|
124 |
if ( ! is_array( $context ) ) {
|
125 |
return $message;
|
135 |
// Build a replacement array with braces around the context keys
|
136 |
$replace = array();
|
137 |
foreach ( $context as $key => $val ) {
|
138 |
+
|
139 |
+
// Both key and val must be strings
|
140 |
+
if ( ! is_string( $key ) || ! is_string( $val ) ) {
|
141 |
+
continue;
|
142 |
+
}
|
143 |
+
|
144 |
$replace['{' . $key . '}'] = $val;
|
145 |
+
|
146 |
}
|
147 |
|
148 |
// Interpolate replacement values into the message and return
|
149 |
+
/*
|
150 |
+
if ( ! is_string( $message )) {
|
151 |
+
echo "message:";
|
152 |
+
var_dump($message);exit;
|
153 |
+
}
|
154 |
+
if ( ! is_string( $replace )) {
|
155 |
+
echo "replace";
|
156 |
+
var_dump($replace);exit;
|
157 |
+
}
|
158 |
+
// */
|
159 |
+
|
160 |
return strtr($message, $replace);
|
161 |
|
162 |
}
|
413 |
$str_when = sprintf(__('%1$s ago', 'simple-history'), $date_human_time_diff);
|
414 |
|
415 |
}
|
416 |
+
|
417 |
$item_permalink = admin_url("index.php?page=simple_history_page");
|
418 |
$item_permalink .= "#item/{$row->id}";
|
419 |
|
420 |
+
$date_format = get_option('date_format') . ' - '. get_option('time_format');
|
421 |
+
$str_datetime_title = sprintf(
|
422 |
+
__('%1$s local time %3$s (%2$s GMT time)', "simple-history"),
|
423 |
+
get_date_from_gmt( $date_datetime->format('Y-m-d H:i:s'), $date_format ), // 1 local time
|
424 |
+
$date_datetime->format( $date_format ), // GMT time
|
425 |
+
PHP_EOL // 3, new line
|
426 |
+
);
|
427 |
+
|
428 |
$date_html = "<span class='SimpleHistoryLogitem__permalink SimpleHistoryLogitem__when SimpleHistoryLogitem__inlineDivided'>";
|
429 |
$date_html .= "<a class='' href='{$item_permalink}'>";
|
430 |
$date_html .= sprintf(
|
431 |
+
'<time datetime="%3$s" title="%1$s" class="">%2$s</time>',
|
432 |
+
esc_attr( $str_datetime_title ), // 1 datetime attribute
|
433 |
+
esc_html( $str_when ), // 2 date text, visible in log
|
434 |
+
$date_datetime->format( DateTime::RFC3339 ) // 3
|
435 |
);
|
436 |
$date_html .= "</a>";
|
437 |
$date_html .= "</span>";
|
899 |
*
|
900 |
* @since 2.3.1
|
901 |
*/
|
902 |
+
$do_log = apply_filters( "simple_history/log/do_log", true, $level, $message, $context, $this );
|
903 |
+
|
904 |
if ( $do_log === false ) {
|
905 |
return $this;
|
906 |
}
|
loggers/SimpleUserLogger.php
CHANGED
@@ -29,6 +29,8 @@ class SimpleUserLogger extends SimpleLogger {
|
|
29 |
'user_updated_profile' => __("Edited the profile for user {edited_user_login} ({edited_user_email})", "simple-history"),
|
30 |
'user_created' => __("Created user {created_user_login} ({created_user_email}) with role {created_user_role}", "simple-history"),
|
31 |
'user_deleted' => __("Deleted user {deleted_user_login} ({deleted_user_email})", "simple-history"),
|
|
|
|
|
32 |
|
33 |
/*
|
34 |
Text used in admin:
|
@@ -115,6 +117,98 @@ class SimpleUserLogger extends SimpleLogger {
|
|
115 |
// User sessions is destroyed. AJAX call that we hook onto early.
|
116 |
add_action("wp_ajax_destroy-sessions", array($this, "on_destroy_user_session"), 0);
|
117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
}
|
119 |
|
120 |
/**
|
29 |
'user_updated_profile' => __("Edited the profile for user {edited_user_login} ({edited_user_email})", "simple-history"),
|
30 |
'user_created' => __("Created user {created_user_login} ({created_user_email}) with role {created_user_role}", "simple-history"),
|
31 |
'user_deleted' => __("Deleted user {deleted_user_login} ({deleted_user_email})", "simple-history"),
|
32 |
+
"user_password_reseted" => __("Reseted their password", "simple-history"),
|
33 |
+
"user_requested_password_reset_link" => __("Requested a password reset link for user with login '{user_login}' and email '{user_email}'", "simple-history"),
|
34 |
|
35 |
/*
|
36 |
Text used in admin:
|
117 |
// User sessions is destroyed. AJAX call that we hook onto early.
|
118 |
add_action("wp_ajax_destroy-sessions", array($this, "on_destroy_user_session"), 0);
|
119 |
|
120 |
+
// User reaches reset password (from link or only from user created link)
|
121 |
+
add_action( 'validate_password_reset', array( $this, "on_validate_password_reset" ), 10, 2 );
|
122 |
+
|
123 |
+
add_action( 'retrieve_password_message', array( $this, "on_retrieve_password_message" ), 10, 4 );
|
124 |
+
|
125 |
+
}
|
126 |
+
|
127 |
+
/*
|
128 |
+
|
129 |
+
user requests a reset password link
|
130 |
+
|
131 |
+
$errors = apply_filters( 'wp_login_errors', $errors, $redirect_to );
|
132 |
+
|
133 |
+
elseif ( isset($_GET['checkemail']) && 'confirm' == $_GET['checkemail'] )
|
134 |
+
$errors->add('confirm', __('Check your e-mail for the confirmation link.'), 'message');
|
135 |
+
|
136 |
+
*/
|
137 |
+
function on_retrieve_password_message( $message, $key, $user_login, $user_data ) {
|
138 |
+
|
139 |
+
if ( isset( $_GET["action"] ) && ( "lostpassword" == $_GET["action"] ) ) {
|
140 |
+
|
141 |
+
$context = array(
|
142 |
+
"_initiator" => SimpleLoggerLogInitiators::WEB_USER,
|
143 |
+
"message" => $message,
|
144 |
+
"key" => $key,
|
145 |
+
"user_login" => $user_login,
|
146 |
+
"user_data" => $user_data,
|
147 |
+
"GET" => $_GET,
|
148 |
+
"POST" => $_POST
|
149 |
+
);
|
150 |
+
|
151 |
+
if ( is_a($user_data, "WP_User") ) {
|
152 |
+
|
153 |
+
$context["user_email"] = $user_data->user_email;
|
154 |
+
|
155 |
+
}
|
156 |
+
|
157 |
+
$this->noticeMessage( "user_requested_password_reset_link", $context );
|
158 |
+
|
159 |
+
}
|
160 |
+
|
161 |
+
return $message;
|
162 |
+
|
163 |
+
}
|
164 |
+
|
165 |
+
/**
|
166 |
+
* Fired before the password reset procedure is validated.
|
167 |
+
*
|
168 |
+
* @param object $errors WP Error object.
|
169 |
+
* @param WP_User|WP_Error $user WP_User object if the login and reset key match. WP_Error object otherwise.
|
170 |
+
*/
|
171 |
+
function on_validate_password_reset( $errors, $user ) {
|
172 |
+
|
173 |
+
/*
|
174 |
+
User visits the forgot password screen
|
175 |
+
$errors object are empty
|
176 |
+
$user contains a user
|
177 |
+
$_post is empty
|
178 |
+
|
179 |
+
User resets password
|
180 |
+
$errors empty
|
181 |
+
$user user object
|
182 |
+
$_post
|
183 |
+
|
184 |
+
*/
|
185 |
+
|
186 |
+
$context = array();
|
187 |
+
|
188 |
+
if ( is_a( $user, "WP_User") ) {
|
189 |
+
$context["_initiator"] = SimpleLoggerLogInitiators::WP_USER;
|
190 |
+
$context["_user_id"] = $user->ID;
|
191 |
+
$context["_user_login"] = $user->user_login;
|
192 |
+
$context["_user_email"] = $user->user_email;
|
193 |
+
}
|
194 |
+
|
195 |
+
if ( isset($_POST['pass1']) && $_POST['pass1'] != $_POST['pass2'] ) {
|
196 |
+
|
197 |
+
// $errors->add( 'password_reset_mismatch', __( 'The passwords do not match.' ) );
|
198 |
+
// user failed to reset password
|
199 |
+
|
200 |
+
}
|
201 |
+
|
202 |
+
|
203 |
+
if ( ( ! $errors->get_error_code() ) && isset( $_POST['pass1'] ) && !empty( $_POST['pass1'] ) ) {
|
204 |
+
|
205 |
+
// login_header( __( 'Password Reset' ), '<p class="message reset-pass">' . __( 'Your password has been reset.' ) . ' <a href="' . esc_url(
|
206 |
+
$this->infoMessage( "user_password_reseted", $context );
|
207 |
+
|
208 |
+
|
209 |
+
}
|
210 |
+
|
211 |
+
|
212 |
}
|
213 |
|
214 |
/**
|
readme.txt
CHANGED
@@ -4,7 +4,7 @@ Donate link: http://eskapism.se/sida/donate/
|
|
4 |
Tags: history, log, changes, changelog, audit, trail, pages, attachments, users, cms, dashboard, admin, syslog, feed, activity, stream, audit trail, brute-force
|
5 |
Requires at least: 3.6.0
|
6 |
Tested up to: 4.3
|
7 |
-
Stable tag: 2.
|
8 |
|
9 |
View changes made by users within WordPress. See who created a page, uploaded an attachment or approved an comment, and more.
|
10 |
|
@@ -125,22 +125,39 @@ https://github.com/bonny/WordPress-Simple-History
|
|
125 |
are of type post and pages and media (i.e. images & other uploads), and only events
|
126 |
initiated by a specific user.
|
127 |
|
128 |
-
2.
|
129 |
|
130 |
-
3. Events
|
131 |
|
132 |
-
4.
|
|
|
|
|
|
|
|
|
133 |
|
134 |
|
135 |
== Changelog ==
|
136 |
|
137 |
## Changelog
|
138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
139 |
= 2.3.1 (October 2015) =
|
140 |
|
141 |
- Fixed: Hopefully fixed the wrong relative time, as reported here: https://wordpress.org/support/topic/wrong-reporting-time.
|
142 |
- Changed: The RSS-feed with updates is now disabled by default for new installs. It is password protected, but some users felt that is should be optional to activate it. And now it is! Thanks to https://github.com/guillaumemolter for adding this feature.
|
143 |
-
- Fixed: Failed login entries when using plugin
|
144 |
- Added: Filter `simple_history/log/do_log` that can be used to shortcut the log()-method.
|
145 |
- Updated: German translation updated.
|
146 |
|
4 |
Tags: history, log, changes, changelog, audit, trail, pages, attachments, users, cms, dashboard, admin, syslog, feed, activity, stream, audit trail, brute-force
|
5 |
Requires at least: 3.6.0
|
6 |
Tested up to: 4.3
|
7 |
+
Stable tag: 2.4
|
8 |
|
9 |
View changes made by users within WordPress. See who created a page, uploaded an attachment or approved an comment, and more.
|
10 |
|
125 |
are of type post and pages and media (i.e. images & other uploads), and only events
|
126 |
initiated by a specific user.
|
127 |
|
128 |
+
2. The __Post Quick Diff__ feature will make it quick and easy for a user of a site to see what updates other users are have done to posts and pages.
|
129 |
|
130 |
+
3. Events with different severity – Simple History uses the log levels specified in the PHP PSR-3 standard.
|
131 |
|
132 |
+
4. Events have context with extra details - Each logged event can include useful rich formatted extra information. For example: a plugin install can contain author info and a the url to the plugin, and an uploaded image can contain a thumbnail of the image.
|
133 |
+
|
134 |
+
5. Click on the IP address of an entry to view the location of for example a failed login attempt.
|
135 |
+
|
136 |
+
6. See even more details about a logged event (by clicking on the date and time of the event).
|
137 |
|
138 |
|
139 |
== Changelog ==
|
140 |
|
141 |
## Changelog
|
142 |
|
143 |
+
= 2.4 (November 2015) =
|
144 |
+
|
145 |
+
- Added: Now logs when a user changes their password using the "reset password" link.
|
146 |
+
- Added: Now logs when a user uses the password reset form.
|
147 |
+
- Added: New method `register_dropin` that can be used to add dropins.
|
148 |
+
- Added: New action `simple_history/add_custom_dropin`.
|
149 |
+
- Added: Example on how to add an external dropin: [example-dropin.php](https://github.com/bonny/WordPress-Simple-History/blob/master/examples/example-dropin.php).
|
150 |
+
- Added: "Last day" added to filter, because a brute force attack can add so many logs that it's not possible to fetch a whole week.
|
151 |
+
- Changed: Filter `simple_history/log/do_log` now pass 5 arguments instead of 3. Before this update in was not possible for multiple add_action()-calls to use this filter, because you would not now if any other code had canceled it and so on. If you have been using this filter you need to modify your code.
|
152 |
+
- Changed: When hovering the time of an event in the log, the date of the event displays in both local time and GMT time. Hopefully makes it easier for admins in different timezones that work together on a site to understand when each event happened. Fixes https://github.com/bonny/WordPress-Simple-History/issues/84.
|
153 |
+
- Fixed: Line height was a bit tight on the dashboard. Also: the margin was a tad to small for the first logged event on the dashboard.
|
154 |
+
- Fixed: Username was not added correctly to failed login attempts when using plugin Captcha on Login + it would still show that a user logged out sometimes when a bot/script brute force attacked a site by only sending login and password and not the captcha field.
|
155 |
+
|
156 |
= 2.3.1 (October 2015) =
|
157 |
|
158 |
- Fixed: Hopefully fixed the wrong relative time, as reported here: https://wordpress.org/support/topic/wrong-reporting-time.
|
159 |
- Changed: The RSS-feed with updates is now disabled by default for new installs. It is password protected, but some users felt that is should be optional to activate it. And now it is! Thanks to https://github.com/guillaumemolter for adding this feature.
|
160 |
+
- Fixed: Failed login entries when using plugin [Captcha on Login](https://wordpress.org/plugins/captcha-on-login/) was reported as "Logged out" when they really meant "Failed to log in". Please note that this was nothing that Simple History did wrong, it was rather Captcha on Login that manually called `wp_logout()` each time a user failed to login. Should fix all those mystery "Logged out"-entried some of you users had.
|
161 |
- Added: Filter `simple_history/log/do_log` that can be used to shortcut the log()-method.
|
162 |
- Updated: German translation updated.
|
163 |
|