Simple History - Version 2.32

Version Description

(August 2019) =

  • Fix error in Beaver Builder logger. Fixes https://wordpress.org/support/topic/conflict-with-beaver-builder-plugin-4/.
  • Add filter simple_history/admin_location that makes is possible to move the main page from the dashboard menu to any other menu page, for example the Tools menu. Fixes https://github.com/bonny/WordPress-Simple-History/issues/140. Example usage of filter:
// Move Simple History log sub page from the "Dashboard" menu to the "Tools" menu.
add_filter('simple_history/admin_location', function ($location) {
    $location
Download this release

Release Info

Developer eskapism
Plugin Icon 128x128 Simple History
Version 2.32
Comparing to
See all releases

Code changes from version 2.31.1 to 2.32

Files changed (47) hide show
  1. README.md +0 -1
  2. code.md +9 -7
  3. composer.json +1 -1
  4. css/styles.css +5 -5
  5. dropins/SimpleHistoryDebugDropin.php +61 -0
  6. dropins/SimpleHistoryDonateDropin.php +69 -70
  7. dropins/SimpleHistoryExportDropin.php +209 -231
  8. dropins/SimpleHistoryFilterDropin.php +564 -574
  9. dropins/SimpleHistoryIpInfoDropin.php +166 -165
  10. dropins/SimpleHistoryNewRowsNotifier.php +64 -66
  11. dropins/SimpleHistoryPluginPatchesDropin.php +160 -160
  12. dropins/SimpleHistoryRSSDropin.php +398 -384
  13. dropins/SimpleHistorySettingsDebugDropin.php +26 -25
  14. dropins/SimpleHistorySettingsLogtestDropin.php +267 -263
  15. dropins/SimpleHistorySettingsStatsDropin.php +75 -77
  16. dropins/SimpleHistorySidebarDropin.php +161 -160
  17. dropins/SimpleHistorySidebarSettings.php +81 -81
  18. dropins/SimpleHistorySidebarStats.php +191 -194
  19. dropins/SimpleHistoryWPCLIDropin.php +156 -155
  20. examples/example-dropin.php +31 -32
  21. examples/example-logger.php +76 -77
  22. examples/examples.php +174 -189
  23. inc/SimpleHistory.php +3239 -4169
  24. inc/SimpleHistoryLogQuery.php +654 -707
  25. inc/helpers.php +219 -0
  26. index.php +36 -33
  27. loggers/AvailableUpdatesLogger.php +294 -303
  28. loggers/FileEditsLogger.php +248 -250
  29. loggers/PluginEnableMediaReplaceLogger.php +94 -95
  30. loggers/PluginUserSwitchingLogger.php +130 -134
  31. loggers/Plugin_ACF.php +1041 -1026
  32. loggers/Plugin_BeaverBuilder.php +94 -75
  33. loggers/Plugin_DuplicatePost.php +121 -116
  34. loggers/Plugin_LimitLoginAttempts.php +224 -234
  35. loggers/Plugin_Redirection.php +387 -376
  36. loggers/Plugin_UltimateMembers_Logger.php +47 -46
  37. loggers/SimpleCategoriesLogger.php +316 -291
  38. loggers/SimpleCommentsLogger.php +744 -763
  39. loggers/SimpleCoreUpdatesLogger.php +109 -108
  40. loggers/SimpleExportLogger.php +68 -68
  41. loggers/SimpleLegacyLogger.php +77 -79
  42. loggers/SimpleLogger.php +18 -26
  43. loggers/SimpleMediaLogger.php +344 -367
  44. loggers/SimpleMenuLogger.php +370 -371
  45. loggers/SimpleOptionsLogger.php +441 -475
  46. loggers/SimplePluginLogger.php +1117 -1153
  47. loggers/SimplePostLogger.php +190 -1243
README.md CHANGED
@@ -65,7 +65,6 @@ SimpleLogger()->info("This is a message sent to the log");
65
  SimpleLogger()->info("User admin edited page 'About our company'");
66
  SimpleLogger()->warning("User 'Jessie' deleted user 'Kim'");
67
  SimpleLogger()->debug("Ok, cron job is running!");
68
-
69
  ```
70
 
71
  You will find more examples in the [examples.php](https://github.com/bonny/WordPress-Simple-History/blob/master/examples/examples.php) file.
65
  SimpleLogger()->info("User admin edited page 'About our company'");
66
  SimpleLogger()->warning("User 'Jessie' deleted user 'Kim'");
67
  SimpleLogger()->debug("Ok, cron job is running!");
 
68
  ```
69
 
70
  You will find more examples in the [examples.php](https://github.com/bonny/WordPress-Simple-History/blob/master/examples/examples.php) file.
code.md CHANGED
@@ -1,10 +1,14 @@
 
1
 
2
- Since I always forget what standards I us in different projects this file is here to remind me about the standards I use in this project:
 
3
 
4
- - Formatting:
5
- Prettier, for both JavaScript and PHP.
6
- - Not WordPress standard, I try to move towards PSR standards.
7
- - phpcs to lint while editing. Lots of code is old but working but was written before my editor had nice linting, so much of the code does not lint. This will be fixed.
 
 
8
 
9
  ## How to use php codesniffer
10
 
@@ -15,5 +19,3 @@ List errors and warnings:
15
  Fix things:
16
 
17
  $ phpcbf /path/to/code
18
-
19
-
1
+ Since I always forget what standards I use in different projects this file is here to remind me about the standards I use in this project:
2
 
3
+ - PHP coding standard: PSR2 because that's the standard that I use in other projects.
4
+ `phpcs.xml.dist` is the config used.
5
 
6
+ - Formatting:
7
+ phpcbf to fix errors and warning.
8
+ Then sometimes Prettier, but befare it's not 100 % stable yet for PHP files.
9
+
10
+ - phpcs to lint while editing. Lots of code is old but working but was written
11
+ before my editor had nice linting, so much of the code does not lint. This will be fixed.
12
 
13
  ## How to use php codesniffer
14
 
19
  Fix things:
20
 
21
  $ phpcbf /path/to/code
 
 
composer.json CHANGED
@@ -14,7 +14,7 @@
14
  "require": {
15
  "php": ">=5.3.0"
16
  },
17
- "version": "2.31.1",
18
  "authors": [
19
  {
20
  "name": "Pär Thernström",
14
  "require": {
15
  "php": ">=5.3.0"
16
  },
17
+ "version": "2.32",
18
  "authors": [
19
  {
20
  "name": "Pär Thernström",
css/styles.css CHANGED
@@ -39,7 +39,7 @@ The spinner that wp uses:;
39
 
40
  /* on its own page */
41
  /*.dashboard_page_simple_history_page .SimpleHistoryLogitemsWrap {*/
42
- .dashboard_page_simple_history_page .SimpleHistoryGuiWrap {
43
  /*float: left;*/
44
  position: relative;
45
  margin-right: 340px;
@@ -47,21 +47,21 @@ The spinner that wp uses:;
47
  background-color: rgba(255,255,255,.75);
48
  }
49
 
50
- .SimpleHistory--isLoaded.dashboard_page_simple_history_page .SimpleHistoryGuiWrap {
51
  background-color: transparent;
52
  }
53
 
54
- .dashboard_page_simple_history_page .SimpleHistoryGui {
55
  width: 100%;
56
  float: left;
57
  }
58
 
59
  @media only screen and (max-width: 850px) {
60
- .dashboard_page_simple_history_page .SimpleHistoryGuiWrap {
61
  float: none;
62
  margin-right: auto;
63
  }
64
- .dashboard_page_simple_history_page .SimpleHistoryGui {
65
  float: none;
66
  }
67
  }
39
 
40
  /* on its own page */
41
  /*.dashboard_page_simple_history_page .SimpleHistoryLogitemsWrap {*/
42
+ .SimpleHistoryGuiWrap {
43
  /*float: left;*/
44
  position: relative;
45
  margin-right: 340px;
47
  background-color: rgba(255,255,255,.75);
48
  }
49
 
50
+ .SimpleHistory--isLoaded .SimpleHistoryGuiWrap {
51
  background-color: transparent;
52
  }
53
 
54
+ .SimpleHistoryGui {
55
  width: 100%;
56
  float: left;
57
  }
58
 
59
  @media only screen and (max-width: 850px) {
60
+ .SimpleHistoryGuiWrap {
61
  float: none;
62
  margin-right: auto;
63
  }
64
+ .SimpleHistoryGui {
65
  float: none;
66
  }
67
  }
dropins/SimpleHistoryDebugDropin.php ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ defined('ABSPATH') or die();
4
+
5
+ /**
6
+ * Dropin Name: Debug
7
+ * Dropin Description: Add some extra info to each logged context when SIMPLE_HISTORY_LOG_DEBUG is set and true
8
+ * Dropin URI: http://simple-history.com/
9
+ * Author: Pär Thernström
10
+ */
11
+ class SimpleHistoryDebugDropin
12
+ {
13
+ public function __construct($sh)
14
+ {
15
+ // Bail if Simple History debug mode is not active.
16
+ if (!defined('SIMPLE_HISTORY_LOG_DEBUG') || !SIMPLE_HISTORY_LOG_DEBUG) {
17
+ return;
18
+ }
19
+
20
+ add_action('simple_history/log_argument/context', [$this, 'onLogArgumentContext'], 10, 4);
21
+ }
22
+
23
+ /**
24
+ * Modify the context to add debug information.
25
+ *
26
+ * @param array $context
27
+ * @param string $level
28
+ * @param string $message
29
+ * @param SimpleLogger $logger
30
+ */
31
+ public function onLogArgumentContext(array $context, string $level, string $message, SimpleLogger $logger)
32
+ {
33
+ $sh = SimpleHistory::get_instance();
34
+ $context['_debug_get'] = $sh->json_encode($_GET);
35
+ $context['_debug_post'] = $sh->json_encode($_POST);
36
+ $context['_debug_server'] = $sh->json_encode($_SERVER);
37
+ $context['_debug_files'] = $sh->json_encode($_FILES);
38
+ $context['_debug_php_sapi_name'] = php_sapi_name();
39
+
40
+ global $argv;
41
+ $context['_debug_argv'] = $sh->json_encode($argv);
42
+
43
+ $consts = get_defined_constants(true);
44
+ $consts = $consts['user'];
45
+ $context['_debug_user_constants'] = $sh->json_encode($consts);
46
+
47
+ $postdata = file_get_contents('php://input');
48
+ $context['_debug_http_raw_post_data'] = $sh->json_encode($postdata);
49
+
50
+ $context['_debug_wp_debug_backtrace_summary'] = wp_debug_backtrace_summary();
51
+ $context['_debug_is_admin'] = json_encode(is_admin());
52
+ $context['_debug_is_ajax'] = json_encode(defined('DOING_AJAX') && DOING_AJAX);
53
+ $context['_debug_is_doing_cron'] = json_encode(defined('DOING_CRON') && DOING_CRON);
54
+
55
+ global $wp_current_filter;
56
+ $context['_debug_current_filter_array'] = $wp_current_filter;
57
+ $context['_debug_current_filter'] = current_filter();
58
+
59
+ return $context;
60
+ }
61
+ }
dropins/SimpleHistoryDonateDropin.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /*
6
  Dropin Name: Donate things
@@ -12,73 +12,72 @@ Author: Pär Thernström
12
  * Simple History Donate dropin
13
  * Put some donate messages here and there
14
  */
15
- class SimpleHistoryDonateDropin {
16
-
17
- // Simple History instance
18
- private $sh;
19
-
20
- function __construct( $sh ) {
21
-
22
- $this->sh = $sh;
23
- add_action( 'admin_menu', array( $this, 'add_settings' ), 50 );
24
- add_action( 'plugin_row_meta', array( $this, 'action_plugin_row_meta' ), 10, 2 );
25
-
26
- }
27
-
28
- /**
29
- * Add link to the donate page in the Plugins » Installed plugins screen
30
- * Called from filter 'plugin_row_meta'
31
- */
32
- function action_plugin_row_meta( $links, $file ) {
33
-
34
- if ( $file == $this->sh->plugin_basename ) {
35
-
36
- $links = array_merge(
37
- $links,
38
- array( sprintf( '<a href="https://www.paypal.me/eskapism">%1$s</a>', __('Donate', "simple-history") ) )
39
- );
40
-
41
- }
42
-
43
- return $links;
44
-
45
- }
46
-
47
- public function add_settings() {
48
-
49
- $settings_section_id = 'simple_history_settings_section_donate';
50
-
51
- add_settings_section(
52
- $settings_section_id,
53
- _x( 'Donate', 'donate settings headline', 'simple-history' ), // No title __("General", "simple-history"),
54
- array( $this, 'settings_section_output' ),
55
- SimpleHistory::SETTINGS_MENU_SLUG // same slug as for options menu page
56
- );
57
-
58
- // Empty section to make more room below
59
- /*
60
- add_settings_field(
61
- "simple_history_settings_donate",
62
- "", // __("Donate", "simple-history"),
63
- array($this, "settings_field_donate"),
64
- SimpleHistory::SETTINGS_MENU_SLUG,
65
- $settings_section_id
66
- );
67
- */
68
-
69
- }
70
-
71
- function settings_section_output() {
72
-
73
- printf(
74
- __( 'If you find Simple History useful please <a href="%1$s">donate</a>.', "simple-history"),
75
- 'https://www.paypal.me/eskapism'
76
- );
77
-
78
- }
79
-
80
-
81
- function settings_field_donate() {
82
- }
83
-
84
  }
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: Donate things
12
  * Simple History Donate dropin
13
  * Put some donate messages here and there
14
  */
15
+ class SimpleHistoryDonateDropin
16
+ {
17
+
18
+ // Simple History instance
19
+ private $sh;
20
+
21
+ function __construct($sh)
22
+ {
23
+
24
+ $this->sh = $sh;
25
+ add_action('admin_menu', array( $this, 'add_settings' ), 50);
26
+ add_action('plugin_row_meta', array( $this, 'action_plugin_row_meta' ), 10, 2);
27
+ }
28
+
29
+ /**
30
+ * Add link to the donate page in the Plugins » Installed plugins screen
31
+ * Called from filter 'plugin_row_meta'
32
+ */
33
+ function action_plugin_row_meta($links, $file)
34
+ {
35
+
36
+ if ($file == $this->sh->plugin_basename) {
37
+ $links = array_merge(
38
+ $links,
39
+ array( sprintf('<a href="https://www.paypal.me/eskapism">%1$s</a>', __('Donate', "simple-history")) )
40
+ );
41
+ }
42
+
43
+ return $links;
44
+ }
45
+
46
+ public function add_settings()
47
+ {
48
+
49
+ $settings_section_id = 'simple_history_settings_section_donate';
50
+
51
+ add_settings_section(
52
+ $settings_section_id,
53
+ _x('Donate', 'donate settings headline', 'simple-history'), // No title __("General", "simple-history"),
54
+ array( $this, 'settings_section_output' ),
55
+ SimpleHistory::SETTINGS_MENU_SLUG // same slug as for options menu page
56
+ );
57
+
58
+ // Empty section to make more room below
59
+ /*
60
+ add_settings_field(
61
+ "simple_history_settings_donate",
62
+ "", // __("Donate", "simple-history"),
63
+ array($this, "settings_field_donate"),
64
+ SimpleHistory::SETTINGS_MENU_SLUG,
65
+ $settings_section_id
66
+ );
67
+ */
68
+ }
69
+
70
+ function settings_section_output()
71
+ {
72
+
73
+ printf(
74
+ __('If you find Simple History useful please <a href="%1$s">donate</a>.', "simple-history"),
75
+ 'https://www.paypal.me/eskapism'
76
+ );
77
+ }
78
+
79
+
80
+ function settings_field_donate()
81
+ {
82
+ }
 
83
  }
dropins/SimpleHistoryExportDropin.php CHANGED
@@ -6,269 +6,247 @@
6
  * Dropin URI: http://simple-history.com/
7
  * Author: Pär Thernström
8
  */
9
- class SimpleHistoryExportDropin {
10
-
11
- /**
12
- * Simple History instance.
13
- *
14
- * @var $sh
15
- */
16
- private $sh;
17
-
18
- /**
19
- * Constructor.
20
- *
21
- * @param instance $sh Simple History instance.
22
- */
23
- public function __construct( $sh ) {
24
-
25
- $this->sh = $sh;
26
-
27
- // Add tab to settings page.
28
- $sh->registerSettingsTab(array(
29
- 'slug' => 'export',
30
- 'name' => _x( 'Export', 'Export dropin: Tab name on settings page', 'simple-history' ),
31
- 'function' => array( $this, 'output' ),
32
- ));
33
-
34
- add_action( 'init', array( $this, 'downloadExport' ) );
35
-
36
- }
37
-
38
- public function downloadExport() {
39
-
40
- global $wpdb;
41
-
42
- $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
43
- $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
44
-
45
- if ( isset( $_POST['simple-history-action'] ) && $_POST['simple-history-action'] === 'export-history' ) {
46
-
47
- // Will die if nonce not valid
48
- check_admin_referer( __CLASS__ . '-action-export' );
49
-
50
- $export_format = isset( $_POST['format'] ) ? $_POST['format'] : 'json';
51
-
52
- // Disable relative time output in header
53
- add_filter( 'simple_history/header_time_ago_max_time', '__return_zero' );
54
- add_filter( 'simple_history/header_just_now_max_time', '__return_zero' );
55
-
56
- // Don't use "You" if event is initiated by the same user that does the export
57
- add_filter( 'simple_history/header_initiator_use_you', '__return_false' );
58
-
59
- $query = new SimpleHistoryLogQuery();
60
-
61
- $query_args = array(
62
- 'paged' => 1,
63
- 'posts_per_page' => 3000,
64
- );
65
-
66
- $events = $query->query( $query_args );
67
-
68
- // $events->total_row_count;
69
- $pages_count = $events['pages_count'];
70
- $page_current = $events['page_current'];
71
-
72
- $fp = fopen( 'php://output', 'w' );
73
-
74
- $attachment_header_template = 'Content-Disposition: attachment; filename="%1$s"';
75
-
76
- if ( 'csv' == $export_format ) {
77
-
78
- $filename = 'simple-history-export-' . time() . '.csv';
79
- header( 'Content-Type: text/plain' );
80
- header( sprintf( $attachment_header_template, $filename ) );
81
-
82
- } elseif ( 'json' == $export_format ) {
83
-
84
- $filename = 'simple-history-export-' . time() . '.json';
85
- header( 'Content-Type: application/json' );
86
- header( sprintf( $attachment_header_template, $filename ) );
87
-
88
- } elseif ( 'html' == $export_format ) {
89
-
90
- $filename = 'simple-history-export-' . time() . '.html';
91
- header( 'Content-Type: text/html' );
92
- // header("Content-Disposition: attachment; filename='{$filename}'");
93
- }
94
-
95
- // Some formats need to output some stuff before the actual loops
96
- if ( 'json' == $export_format ) {
97
-
98
- $json_row = '[';
99
- fwrite( $fp, $json_row );
100
-
101
- } elseif ( 'html' == $export_format ) {
102
-
103
- $html = sprintf(
104
- '
105
  <!doctype html>
106
  <meta charset="utf-8">
107
  <title>Simple History export</title>
108
  <ul>
109
- ');
110
- fwrite( $fp, $html );
111
-
112
- }
113
-
114
- // Paginate through all pages and all their rows.
115
- $row_loop = 0;
116
- while ( $page_current <= $pages_count + 1 ) {
117
-
118
- // if ($page_current > 1) { break; } # To debug/test
119
- foreach ( $events['log_rows'] as $one_row ) {
120
-
121
- // if ( $row_loop > 10) { break; } # To debug/test
122
- set_time_limit( 30 );
123
-
124
- if ( 'csv' == $export_format ) {
125
-
126
- $header_output = strip_tags( html_entity_decode( $this->sh->getLogRowHeaderOutput( $one_row ), ENT_QUOTES, 'UTF-8' ) );
127
- $header_output = trim( preg_replace( '/\s\s+/', ' ', $header_output ) );
128
-
129
- $message_output = strip_tags( html_entity_decode( $this->sh->getLogRowPlainTextOutput( $one_row ), ENT_QUOTES, 'UTF-8' ) );
130
-
131
- $user_email = empty( $one_row->context['_user_email'] ) ? null : $one_row->context['_user_email'];
132
- $user_login = empty( $one_row->context['_user_login'] ) ? null : $one_row->context['_user_login'];
133
-
134
- fputcsv($fp, array(
135
- $one_row->date,
136
- $one_row->logger,
137
- $one_row->level,
138
- $one_row->initiator,
139
- $one_row->context_message_key,
140
- $user_email,
141
- $user_login,
142
- $header_output,
143
- $message_output,
144
- $one_row->subsequentOccasions,
145
- ));
146
-
147
- } elseif ( 'json' == $export_format ) {
148
-
149
- // If not first loop then add a comma between all json objects.
150
- if ( $row_loop == 0 ) {
151
- $comma = "\n";
152
- } else {
153
- $comma = ",\n";
154
- }
155
-
156
- $json_row = $comma . $this->sh->json_encode( $one_row );
157
- fwrite( $fp, $json_row );
158
-
159
- } elseif ( 'html' == $export_format ) {
160
-
161
- $html = sprintf(
162
- '
163
  <li>
164
  <div>%1$s</div>
165
  <div>%2$s</div>
166
  <div>%3$s</div>
167
  </li>
168
  ',
169
- $this->sh->getLogRowHeaderOutput( $one_row ),
170
- $this->sh->getLogRowPlainTextOutput( $one_row ),
171
- $this->sh->getLogRowDetailsOutput( $one_row )
172
- );
173
-
174
- fwrite( $fp, $html );
175
-
176
- }// End if().
177
-
178
- $row_loop++;
179
-
180
- }// End foreach().
181
-
182
- // echo "<br>memory_get_usage:<br>"; print_r(memory_get_usage());
183
- // echo "<br>memory_get_peak_usage:<br>"; print_r(memory_get_peak_usage());
184
- // echo "<br>fetch next page";
185
- flush();
186
-
187
- // Fetch next page
188
- // @TODO: must take into consideration that new items can be added while we do the fetch
189
- $page_current++;
190
- $query_args['paged'] = $page_current;
191
- $events = $query->query( $query_args );
192
-
193
- // echo "<br>did fetch next page";
194
- // echo "<br>memory_get_usage:<br>"; print_r(memory_get_usage());
195
- // echo "<br>memory_get_peak_usage:<br>"; print_r(memory_get_peak_usage());
196
- }// End while().
197
-
198
- if ( 'json' == $export_format ) {
199
-
200
- $json_row = ']';
201
- fwrite( $fp, $json_row );
202
 
203
- } elseif ( 'html' == $export_format ) {
 
204
 
205
- $html = sprintf( '</ul>' );
206
- fwrite( $fp, $html );
207
 
208
- }
 
 
 
209
 
210
- fclose( $fp );
211
- flush();
 
 
 
212
 
213
- exit;
 
 
 
214
 
215
- // echo "<br>done";
216
- }// End if().
 
 
 
 
 
217
 
218
- }
 
219
 
 
220
 
221
- public function output() {
 
 
222
 
223
- ?>
224
- <!-- <h2>Export</h2> -->
225
 
226
- <p><?php _ex( 'The export function will export the full history.', 'Export dropin: introtext', 'simple-history' ) ?></p>
 
227
 
228
- <form method="post">
 
229
 
230
- <h3><?php _ex( 'Choose format to export to', 'Export dropin: format', 'simple-history' ) ?></h3>
231
 
232
- <p>
233
- <label>
234
- <input type="radio" name="format" value="json" checked>
235
- <?php _ex( 'JSON', 'Export dropin: export format', 'simple-history' ) ?>
236
- </label>
237
- </p>
238
 
239
- <p>
240
- <label>
241
- <input type="radio" name="format" value="csv">
242
- <?php _ex( 'CSV', 'Export dropin: export format', 'simple-history' ) ?>
243
- </label>
244
- </p>
245
 
246
- <!-- <br> -->
 
 
 
 
 
247
 
248
- <!--<label>
249
- <input type="radio" name="format" value="html">
250
- HTML
251
- </label>
252
- <br> -->
 
253
 
254
- <!-- <label>
255
- <input type="radio" name="format" value="xml">
256
- XML
257
- </label> -->
258
 
259
- <p>
260
- <button type="submit" class="button button-primary"><?php _ex( 'Download Export File', 'Export dropin: submit button', 'simple-history' ) ?></button>
261
- <input type="hidden" name="simple-history-action" value="export-history">
262
- </p>
 
263
 
264
- <?php
265
- wp_nonce_field( __CLASS__ . '-action-export' );
266
- ?>
 
267
 
268
- </form>
 
 
 
269
 
270
- <?php
 
 
271
 
272
- }
273
 
 
 
274
  }
6
  * Dropin URI: http://simple-history.com/
7
  * Author: Pär Thernström
8
  */
9
+ class SimpleHistoryExportDropin
10
+ {
11
+
12
+ /**
13
+ * Simple History instance.
14
+ *
15
+ * @var $sh
16
+ */
17
+ private $sh;
18
+
19
+ /**
20
+ * Constructor.
21
+ *
22
+ * @param instance $sh Simple History instance.
23
+ */
24
+ public function __construct($sh)
25
+ {
26
+
27
+ $this->sh = $sh;
28
+
29
+ // Add tab to settings page.
30
+ $sh->registerSettingsTab(array(
31
+ 'slug' => 'export',
32
+ 'name' => _x('Export', 'Export dropin: Tab name on settings page', 'simple-history'),
33
+ 'function' => array( $this, 'output' ),
34
+ ));
35
+
36
+ add_action('init', array( $this, 'downloadExport' ));
37
+ }
38
+
39
+ public function downloadExport()
40
+ {
41
+
42
+ global $wpdb;
43
+
44
+ $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
45
+ $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
46
+
47
+ if (isset($_POST['simple-history-action']) && $_POST['simple-history-action'] === 'export-history') {
48
+ // Will die if nonce not valid
49
+ check_admin_referer(__CLASS__ . '-action-export');
50
+
51
+ $export_format = isset($_POST['format']) ? $_POST['format'] : 'json';
52
+
53
+ // Disable relative time output in header
54
+ add_filter('simple_history/header_time_ago_max_time', '__return_zero');
55
+ add_filter('simple_history/header_just_now_max_time', '__return_zero');
56
+
57
+ // Don't use "You" if event is initiated by the same user that does the export
58
+ add_filter('simple_history/header_initiator_use_you', '__return_false');
59
+
60
+ $query = new SimpleHistoryLogQuery();
61
+
62
+ $query_args = array(
63
+ 'paged' => 1,
64
+ 'posts_per_page' => 3000,
65
+ );
66
+
67
+ $events = $query->query($query_args);
68
+
69
+ // $events->total_row_count;
70
+ $pages_count = $events['pages_count'];
71
+ $page_current = $events['page_current'];
72
+
73
+ $fp = fopen('php://output', 'w');
74
+
75
+ $attachment_header_template = 'Content-Disposition: attachment; filename="%1$s"';
76
+
77
+ if ('csv' == $export_format) {
78
+ $filename = 'simple-history-export-' . time() . '.csv';
79
+ header('Content-Type: text/plain');
80
+ header(sprintf($attachment_header_template, $filename));
81
+ } elseif ('json' == $export_format) {
82
+ $filename = 'simple-history-export-' . time() . '.json';
83
+ header('Content-Type: application/json');
84
+ header(sprintf($attachment_header_template, $filename));
85
+ } elseif ('html' == $export_format) {
86
+ $filename = 'simple-history-export-' . time() . '.html';
87
+ header('Content-Type: text/html');
88
+ // header("Content-Disposition: attachment; filename='{$filename}'");
89
+ }
90
+
91
+ // Some formats need to output some stuff before the actual loops
92
+ if ('json' == $export_format) {
93
+ $json_row = '[';
94
+ fwrite($fp, $json_row);
95
+ } elseif ('html' == $export_format) {
96
+ $html = sprintf(
97
+ '
 
 
 
 
 
 
 
98
  <!doctype html>
99
  <meta charset="utf-8">
100
  <title>Simple History export</title>
101
  <ul>
102
+ '
103
+ );
104
+ fwrite($fp, $html);
105
+ }
106
+
107
+ // Paginate through all pages and all their rows.
108
+ $row_loop = 0;
109
+ while ($page_current <= $pages_count + 1) {
110
+ // if ($page_current > 1) { break; } # To debug/test
111
+ foreach ($events['log_rows'] as $one_row) {
112
+ // if ( $row_loop > 10) { break; } # To debug/test
113
+ set_time_limit(30);
114
+
115
+ if ('csv' == $export_format) {
116
+ $header_output = strip_tags(html_entity_decode($this->sh->getLogRowHeaderOutput($one_row), ENT_QUOTES, 'UTF-8'));
117
+ $header_output = trim(preg_replace('/\s\s+/', ' ', $header_output));
118
+
119
+ $message_output = strip_tags(html_entity_decode($this->sh->getLogRowPlainTextOutput($one_row), ENT_QUOTES, 'UTF-8'));
120
+
121
+ $user_email = empty($one_row->context['_user_email']) ? null : $one_row->context['_user_email'];
122
+ $user_login = empty($one_row->context['_user_login']) ? null : $one_row->context['_user_login'];
123
+
124
+ fputcsv($fp, array(
125
+ $one_row->date,
126
+ $one_row->logger,
127
+ $one_row->level,
128
+ $one_row->initiator,
129
+ $one_row->context_message_key,
130
+ $user_email,
131
+ $user_login,
132
+ $header_output,
133
+ $message_output,
134
+ $one_row->subsequentOccasions,
135
+ ));
136
+ } elseif ('json' == $export_format) {
137
+ // If not first loop then add a comma between all json objects.
138
+ if ($row_loop == 0) {
139
+ $comma = "\n";
140
+ } else {
141
+ $comma = ",\n";
142
+ }
143
+
144
+ $json_row = $comma . $this->sh->json_encode($one_row);
145
+ fwrite($fp, $json_row);
146
+ } elseif ('html' == $export_format) {
147
+ $html = sprintf(
148
+ '
 
 
 
 
 
 
 
149
  <li>
150
  <div>%1$s</div>
151
  <div>%2$s</div>
152
  <div>%3$s</div>
153
  </li>
154
  ',
155
+ $this->sh->getLogRowHeaderOutput($one_row),
156
+ $this->sh->getLogRowPlainTextOutput($one_row),
157
+ $this->sh->getLogRowDetailsOutput($one_row)
158
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
+ fwrite($fp, $html);
161
+ }// End if().
162
 
163
+ $row_loop++;
164
+ }// End foreach().
165
 
166
+ // echo "<br>memory_get_usage:<br>"; print_r(memory_get_usage());
167
+ // echo "<br>memory_get_peak_usage:<br>"; print_r(memory_get_peak_usage());
168
+ // echo "<br>fetch next page";
169
+ flush();
170
 
171
+ // Fetch next page
172
+ // @TODO: must take into consideration that new items can be added while we do the fetch
173
+ $page_current++;
174
+ $query_args['paged'] = $page_current;
175
+ $events = $query->query($query_args);
176
 
177
+ // echo "<br>did fetch next page";
178
+ // echo "<br>memory_get_usage:<br>"; print_r(memory_get_usage());
179
+ // echo "<br>memory_get_peak_usage:<br>"; print_r(memory_get_peak_usage());
180
+ }// End while().
181
 
182
+ if ('json' == $export_format) {
183
+ $json_row = ']';
184
+ fwrite($fp, $json_row);
185
+ } elseif ('html' == $export_format) {
186
+ $html = sprintf('</ul>');
187
+ fwrite($fp, $html);
188
+ }
189
 
190
+ fclose($fp);
191
+ flush();
192
 
193
+ exit;
194
 
195
+ // echo "<br>done";
196
+ }// End if().
197
+ }
198
 
 
 
199
 
200
+ public function output()
201
+ {
202
 
203
+ ?>
204
+ <!-- <h2>Export</h2> -->
205
 
206
+ <p><?php _ex('The export function will export the full history.', 'Export dropin: introtext', 'simple-history') ?></p>
207
 
208
+ <form method="post">
 
 
 
 
 
209
 
210
+ <h3><?php _ex('Choose format to export to', 'Export dropin: format', 'simple-history') ?></h3>
 
 
 
 
 
211
 
212
+ <p>
213
+ <label>
214
+ <input type="radio" name="format" value="json" checked>
215
+ <?php _ex('JSON', 'Export dropin: export format', 'simple-history') ?>
216
+ </label>
217
+ </p>
218
 
219
+ <p>
220
+ <label>
221
+ <input type="radio" name="format" value="csv">
222
+ <?php _ex('CSV', 'Export dropin: export format', 'simple-history') ?>
223
+ </label>
224
+ </p>
225
 
226
+ <!-- <br> -->
 
 
 
227
 
228
+ <!--<label>
229
+ <input type="radio" name="format" value="html">
230
+ HTML
231
+ </label>
232
+ <br> -->
233
 
234
+ <!-- <label>
235
+ <input type="radio" name="format" value="xml">
236
+ XML
237
+ </label> -->
238
 
239
+ <p>
240
+ <button type="submit" class="button button-primary"><?php _ex('Download Export File', 'Export dropin: submit button', 'simple-history') ?></button>
241
+ <input type="hidden" name="simple-history-action" value="export-history">
242
+ </p>
243
 
244
+ <?php
245
+ wp_nonce_field(__CLASS__ . '-action-export');
246
+ ?>
247
 
248
+ </form>
249
 
250
+ <?php
251
+ }
252
  }
dropins/SimpleHistoryFilterDropin.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /*
6
  Dropin Name: Filter GUI
@@ -8,600 +8,590 @@ Dropin URI: http://simple-history.com/
8
  Author: Pär Thernström
9
  */
10
 
11
- class SimpleHistoryFilterDropin {
 
12
 
13
- // Simple History instance
14
- private $sh;
15
 
16
- function __construct( $sh ) {
 
17
 
18
- $this->sh = $sh;
19
 
20
- add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'enqueue_admin_scripts' ) );
21
- add_action( 'simple_history/history_page/before_gui', array( $this, 'gui_page_filters' ) );
22
- add_action( 'simple_history/dashboard/before_gui', array( $this, 'gui_page_filters' ) );
23
- add_action( 'wp_ajax_simple_history_filters_search_user', array( $this, 'ajax_simple_history_filters_search_user' ) );
 
24
 
25
- }
 
26
 
27
- public function enqueue_admin_scripts() {
28
 
29
- $file_url = plugin_dir_url( __FILE__ );
30
 
31
- wp_enqueue_script( 'simple_history_FilterDropin', $file_url . 'SimpleHistoryFilterDropin.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION, true );
 
32
 
33
- wp_enqueue_style( 'simple_history_FilterDropin', $file_url . 'SimpleHistoryFilterDropin.css', null, SIMPLE_HISTORY_VERSION );
34
 
35
- }
 
36
 
 
37
 
38
- public function gui_page_filters() {
 
 
 
 
 
 
 
 
 
 
 
39
 
40
- $loggers_user_can_read = $this->sh->getLoggersThatUserCanRead();
 
41
 
42
- /**
43
- * Filter that determines if search filters should be visible directly on page load
44
- *
45
- * Set to true to show the filters when page is loaded
46
- * If false then user must click "Show options"
47
- *
48
- * @since 2.1.2
49
- *
50
- * @param bool $show_more_filters_on_load Default false
51
- */
52
- $show_more_filters_on_load = false;
53
- $show_more_filters_on_load = apply_filters( 'SimpleHistoryFilterDropin/show_more_filters_on_load' , $show_more_filters_on_load );
54
 
55
- ?>
56
- <div class="SimpleHistory__filters <?php echo $show_more_filters_on_load ? 'is-showingMoreFilters' : '' ?>">
57
 
58
- <form class="SimpleHistory__filters__form js-SimpleHistory__filters__form">
59
 
60
- <!-- <h3><?php _e( 'Filter history', 'simple-history' ) ?></h3> -->
 
 
 
61
 
62
- <?php
 
 
63
 
64
- // Start months filter
65
- global $wpdb;
66
- $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
67
- $loggers_user_can_read_sql_in = $this->sh->getLoggersThatUserCanRead( null, 'sql' );
68
-
69
- // Get unique months
70
- $cache_key = 'sh_filter_unique_months';
71
- $result_months = get_transient( $cache_key );
72
-
73
- if ( false === $result_months ) {
74
-
75
- $sql_dates = sprintf('
76
  SELECT DISTINCT ( date_format(DATE, "%%Y-%%m") ) AS yearMonth
77
  FROM %s
78
  WHERE logger IN %s
79
  ORDER BY yearMonth DESC
80
- ', $table_name, // 1
81
- $loggers_user_can_read_sql_in // 2
82
- );
83
-
84
- $result_months = $wpdb->get_results( $sql_dates );
85
-
86
- set_transient( $cache_key, $result_months, HOUR_IN_SECONDS );
87
-
88
- }
89
-
90
- $arr_days_and_pages = array();
91
-
92
- // Default month = current month
93
- // Mainly for performance reasons, since often
94
- // it's not the users intention to view all events,
95
- // but just the latest
96
- $this_month = date( 'Y-m' );
97
-
98
- // Determine if we limit the date range by default
99
- $daysToShow = 1;
100
-
101
- // Start with the latest day
102
- $numEvents = $this->sh->get_unique_events_for_days( $daysToShow );
103
- $numPages = $numEvents / $this->sh->get_pager_size();
104
-
105
- $arr_days_and_pages[] = array(
106
- 'daysToShow' => $daysToShow,
107
- 'numPages' => $numPages,
108
- );
109
-
110
- // Example on my server with lots of brute force attacks (causing log to not load)
111
- // 166434 / 15 = 11 000 pages for last 7 days
112
- // 1 day = 3051 / 15 = 203 pages = still much but better than 11000 pages!
113
- if ( $numPages < 20 ) {
114
-
115
- // Not that many things the last day. Let's try to expand to 7 days instead.
116
- $daysToShow = 7;
117
- $numEvents = $this->sh->get_unique_events_for_days( $daysToShow );
118
- $numPages = $numEvents / $this->sh->get_pager_size();
119
-
120
- $arr_days_and_pages[] = array(
121
- 'daysToShow' => $daysToShow,
122
- 'numPages' => $numPages,
123
- );
124
-
125
- if ( $numPages < 20 ) {
126
-
127
- // Not that many things the last 7 days. Let's try to expand to 14 days instead.
128
- $daysToShow = 14;
129
- $numEvents = $this->sh->get_unique_events_for_days( $daysToShow );
130
- $numPages = $numEvents / $this->sh->get_pager_size();
131
-
132
- $arr_days_and_pages[] = array(
133
- 'daysToShow' => $daysToShow,
134
- 'numPages' => $numPages,
135
- );
136
-
137
- if ( $numPages < 20 ) {
138
-
139
- // Not many things the last 14 days either. Let try with 30 days.
140
- $daysToShow = 30;
141
- $numEvents = $this->sh->get_unique_events_for_days( $daysToShow );
142
- $numPages = $numEvents / $this->sh->get_pager_size();
143
-
144
- $arr_days_and_pages[] = array(
145
- 'daysToShow' => $daysToShow,
146
- 'numPages' => $numPages,
147
- );
148
-
149
- // If 30 days gives a big amount of pages, go back to 14 days
150
- if ( $numPages > 1000 ) {
151
- $daysToShow = 14;
152
- }
153
- }
154
- }
155
- }// End if().
156
-
157
- ?>
158
-
159
- <p class="SimpleHistory__filters__filterRow SimpleHistory__filters__filterRow--date" data-debug-daysAndPages='<?php echo json_encode( $arr_days_and_pages ) ?>'>
160
-
161
- <label class="SimpleHistory__filters__filterLabel"><?php _ex( 'Dates:', 'Filter label', 'simple-history' ) ?></label>
162
-
163
- <select class="SimpleHistory__filters__filter SimpleHistory__filters__filter--date"
164
- name="dates"
165
- placeholder="<?php echo _e( 'All dates', 'simple-history' ) ?>"
166
- NOTmultiple
167
- >
168
- <?php
169
-
170
- // custom date range
171
- // since 2.8.1
172
- printf(
173
- '<option value="%1$s" %3$s>%2$s</option>',
174
- 'customRange', // 1 - value
175
- _x( 'Custom date range...', 'Filter dropin: filter custom range', 'simple-history' ), // 2 text
176
- selected( $daysToShow, 'customRange', 0 )
177
- );
178
-
179
- // One day+ Last week + two weeks back + 30 days back
180
- printf(
181
- '<option value="%1$s" %3$s>%2$s</option>',
182
- 'lastdays:1', // 1 - value
183
- _x( 'Last day', 'Filter dropin: filter week', 'simple-history' ), // 2 text
184
- selected( $daysToShow, 1, 0 )
185
- );
186
-
187
- printf(
188
- '<option value="%1$s" %3$s>%2$s</option>',
189
- 'lastdays:7', // 1 - value
190
- _x( 'Last 7 days', 'Filter dropin: filter week', 'simple-history' ), // 2 text
191
- selected( $daysToShow, 7, 0 )
192
- );
193
-
194
- printf(
195
- '<option value="%1$s" %3$s>%2$s</option>',
196
- 'lastdays:14', // 1 - value
197
- _x( 'Last 14 days', 'Filter dropin: filter week', 'simple-history' ), // 2 text
198
- selected( $daysToShow, 14, 0 )
199
- );
200
-
201
- printf(
202
- '<option value="%1$s" %3$s>%2$s</option>',
203
- 'lastdays:30', // 1 - value
204
- _x( 'Last 30 days', 'Filter dropin: filter week', 'simple-history' ), // 2 text
205
- selected( $daysToShow, 30, 0 )
206
- );
207
-
208
- printf(
209
- '<option value="%1$s" %3$s>%2$s</option>',
210
- 'lastdays:60', // 1 - value
211
- _x( 'Last 60 days', 'Filter dropin: filter week', 'simple-history' ), // 2 text
212
- selected( $daysToShow, 60, 0 )
213
- );
214
-
215
- // Months
216
- foreach ( $result_months as $row ) {
217
-
218
- printf(
219
- '<option value="%1$s" %3$s>%2$s</option>',
220
- 'month:' . $row->yearMonth,
221
- date_i18n( 'F Y', strtotime( $row->yearMonth ) ),
222
- '' // selected( $this_month, $row->yearMonth, false )
223
- );
224
-
225
- }
226
-
227
- ?>
228
- </select>
229
-
230
- <!-- <p> -->
231
- <!-- <label class="SimpleHistory__filters__filterLabel"><?php _ex( 'Between dates:', 'Filter label', 'simple-history' ) ?></label> -->
232
- <span class="SimpleHistory__filters__filter--dayValuesWrap">
233
- <?php
234
- $this->touch_time( 'from' );
235
- $this->touch_time( 'to' );
236
- ?>
237
- </span>
238
- <!-- </p> -->
239
-
240
- </p><!-- end months -->
241
-
242
- <?php
243
- /**
244
- * Filter to control what the default search string is. Default to empty string.
245
- *
246
- * @since 2.1.2
247
- *
248
- * @param string Default search string
249
- */
250
- $default_search_string = apply_filters( 'SimpleHistoryFilterDropin/filter_default_search_string' , '' );
251
- ?>
252
-
253
- <p>
254
-
255
- <label class="SimpleHistory__filters__filterLabel"><?php _ex( 'Containing words:', 'Filter label', 'simple-history' ) ?></label>
256
-
257
- <input
258
- type="search"
259
- class="SimpleHistoryFilterDropin-searchInput"
260
- placeholder="<?php /* _e("Containing words", "simple-history"); */ ?>"
261
- name="search"
262
- value="<?php echo esc_attr( $default_search_string ); ?>"
263
- >
264
-
265
- </p>
266
-
267
- <p class="SimpleHistory__filters__filterSubmitWrap">
268
- <button class="button SimpleHistoryFilterDropin-doFilterButton SimpleHistoryFilterDropin-doFilterButton--first js-SimpleHistoryFilterDropin-doFilter"><?php _e( 'Search events', 'simple-history' ) ?></button>
269
- <button type="button" class="SimpleHistoryFilterDropin-showMoreFilters SimpleHistoryFilterDropin-showMoreFilters--first js-SimpleHistoryFilterDropin-showMoreFilters"><?php _ex( 'Show search options', 'Filter dropin: button to show more search options', 'simple-history' ) ?></button>
270
- </p>
271
-
272
- <?php
273
- /**
274
- * Filter to control what the default loglevels are.
275
- *
276
- * @since 2.1.2
277
- *
278
- * @param array Array with loglevel sugs. Default empty = show all.
279
- */
280
- $arr_default_loglevels = apply_filters( 'SimpleHistoryFilterDropin/filter_default_loglevel', array() );
281
- ?>
282
- <div class="SimpleHistory__filters__moreFilters js-SimpleHistory__filters__moreFilters">
283
-
284
- <p>
285
-
286
- <label class="SimpleHistory__filters__filterLabel"><?php _ex( 'Log levels:', 'Filter label', 'simple-history' ) ?></label>
287
-
288
- <select
289
- name="loglevels"
290
- class="SimpleHistory__filters__filter SimpleHistory__filters__filter--loglevel"
291
- placeholder="<?php _e( 'All log levels', 'simple-history' ) ?>"
292
- multiple
293
- >
294
- <option <?php selected( in_array( 'debug', $arr_default_loglevels ) ) ?> value="debug" data-color="#CEF6D8"><?php echo $this->sh->getLogLevelTranslated( 'Debug' ) ?></option>
295
- <option <?php selected( in_array( 'info', $arr_default_loglevels ) ) ?> value="info" data-color="white"><?php echo $this->sh->getLogLevelTranslated( 'Info' ) ?></option>
296
- <option <?php selected( in_array( 'notice', $arr_default_loglevels ) ) ?> value="notice" data-color="rgb(219, 219, 183)"><?php echo $this->sh->getLogLevelTranslated( 'Notice' ) ?></option>
297
- <option <?php selected( in_array( 'warning', $arr_default_loglevels ) ) ?> value="warning" data-color="#F7D358"><?php echo $this->sh->getLogLevelTranslated( 'Warning' ) ?></option>
298
- <option <?php selected( in_array( 'error', $arr_default_loglevels ) ) ?> value="error" data-color="#F79F81"><?php echo $this->sh->getLogLevelTranslated( 'Error' ) ?></option>
299
- <option <?php selected( in_array( 'critical', $arr_default_loglevels ) ) ?> value="critical" data-color="#FA5858"><?php echo $this->sh->getLogLevelTranslated( 'Critical' ) ?></option>
300
- <option <?php selected( in_array( 'alert', $arr_default_loglevels ) ) ?> value="alert" data-color="rgb(199, 69, 69)"><?php echo $this->sh->getLogLevelTranslated( 'Alert' ) ?></option>
301
- <option <?php selected( in_array( 'emergency', $arr_default_loglevels ) ) ?> value="emergency" data-color="#DF0101"><?php echo $this->sh->getLogLevelTranslated( 'Emergency' ) ?></option>
302
- </select>
303
-
304
- </p>
305
-
306
- <?php
307
-
308
- /**
309
- * Todo: Filter to control what the default messages to filter/search.
310
- * Message in in format: LoggerSlug:MessageKey
311
- * For example:
312
- * - SimplePluginLogger:plugin_activated
313
- * - SimpleCommentsLogger:user_comment_added
314
- *
315
- * @since 2.1.2
316
- *
317
- * @param array Array with loglevel sugs. Default empty = show all.
318
- */
319
- // $arr_default_messages = apply_filters("SimpleHistoryFilterDropin/filter_default_messages", array());
320
- ?>
321
- <p>
322
-
323
- <label class="SimpleHistory__filters__filterLabel"><?php _ex( 'Message types:', 'Filter label', 'simple-history' ) ?></label>
324
-
325
- <select
326
- name="messages"
327
- class="SimpleHistory__filters__filter SimpleHistory__filters__filter--logger"
328
- placeholder="<?php _e( 'All messages', 'simple-history' ) ?>"
329
- multiple
330
- >
331
- <?php
332
- foreach ( $loggers_user_can_read as $logger ) {
333
-
334
- $logger_info = $logger['instance']->getInfo();
335
- $logger_slug = $logger['instance']->slug;
336
-
337
- // Get labels for logger
338
- if ( isset( $logger_info['labels']['search'] ) ) {
339
-
340
- printf( '<optgroup label="%1$s">', esc_attr( $logger_info['labels']['search']['label'] ) );
341
-
342
- // If all activity
343
- if ( ! empty( $logger_info['labels']['search']['label_all'] ) ) {
344
-
345
- $arr_all_search_messages = array();
346
- foreach ( $logger_info['labels']['search']['options'] as $option_key => $option_messages ) {
347
- $arr_all_search_messages = array_merge( $arr_all_search_messages, $option_messages );
348
- }
349
-
350
- foreach ( $arr_all_search_messages as $key => $val ) {
351
- $arr_all_search_messages[ $key ] = $logger_slug . ':' . $val;
352
- }
353
-
354
- printf( '<option value="%2$s">%1$s</option>', esc_attr( $logger_info['labels']['search']['label_all'] ), esc_attr( implode( ',', $arr_all_search_messages ) ) );
355
-
356
- }
357
-
358
- // For each specific search option
359
- foreach ( $logger_info['labels']['search']['options'] as $option_key => $option_messages ) {
360
-
361
- foreach ( $option_messages as $key => $val ) {
362
- $option_messages[ $key ] = $logger_slug . ':' . $val;
363
- }
364
-
365
- $str_option_messages = implode( ',', $option_messages );
366
- printf(
367
- '<option value="%2$s">%1$s</option>',
368
- esc_attr( $option_key ), // 1
369
- esc_attr( $str_option_messages ) // 2
370
- );
371
-
372
- }
373
-
374
- printf( '</optgroup>' );
375
-
376
- }// End if().
377
- }// End foreach().
378
- ?>
379
- </select>
380
- </p>
381
-
382
- <?php
383
-
384
- /**
385
- * Filter what users to search for by default
386
- *
387
- * Example:
388
- *
389
- * add_filter("SimpleHistoryFilterDropin/filter_default_user_ids", function() { return array(1,4); });
390
- *
391
- * @since 2.1.2
392
- *
393
- * @param array Array with user ids. Default is an empty array = show all users.
394
- */
395
-
396
- /*
397
- add_filter("SimpleHistoryFilterDropin/filter_default_user_ids", function($arr) {
398
- $arr = array(
399
- 1,
400
- 4
401
- );
402
- return $arr;
403
- });
404
- //*/
405
-
406
- $default_user_ids = apply_filters( 'SimpleHistoryFilterDropin/filter_default_user_ids', array() );
407
- $arr_default_user_data = array();
408
-
409
- foreach ( $default_user_ids as $user_id ) {
410
- $arr_default_user_data[] = $this->get_data_for_user( $user_id );
411
- }
412
-
413
- if ( current_user_can( 'list_users' ) ) {
414
- ?>
415
- <p>
416
- <label class="SimpleHistory__filters__filterLabel"><?php _ex( 'Users:', 'Filter label', 'simple-history' ) ?></label>
417
- <select
418
- name="users"
419
- class="SimpleHistory__filters__filter SimpleHistory__filters__filter--user"
420
- placeholder="<?php _e( 'All users', 'simple-history' ) ?>"
421
- value="<?php echo esc_attr( implode( ',', $default_user_ids ) ) ?>"
422
- data-default-user-data="<?php echo esc_attr( json_encode( $arr_default_user_data ) ) ?>"
423
- >
424
- </select>
425
- </p>
426
- <?php
427
- }
428
-
429
- ?>
430
-
431
- <p class="SimpleHistory__filters__filterSubmitWrap">
432
- <button class="button SimpleHistoryFilterDropin-doFilterButton SimpleHistoryFilterDropin-doFilterButton--second js-SimpleHistoryFilterDropin-doFilter"><?php _e( 'Search events', 'simple-history' ) ?></button>
433
- <button type="button" class="SimpleHistoryFilterDropin-showMoreFilters SimpleHistoryFilterDropin-showMoreFilters--second js-SimpleHistoryFilterDropin-showMoreFilters"><?php _ex( 'Hide search options', 'Filter dropin: button to hide more search options', 'simple-history' ) ?></button>
434
- </p>
435
-
436
- </div><!-- // more filters -->
437
-
438
- <!--
439
- <p>
440
- <button class="button js-SimpleHistoryFilterDropin-doFilter"><?php _e( 'Search', 'simple-history' ) ?></button>
441
- </p>
442
- -->
443
-
444
- </form>
445
-
446
- </div>
447
- <?php
448
-
449
- } // function
450
-
451
-
452
- /**
453
- * Return format used for select2 for a single user id
454
- *
455
- * @param int $userID
456
- * @return array Array with each user as an object
457
- */
458
- public function get_data_for_user( $userID ) {
459
- if ( ! $userID || ! is_numeric( $userID ) ) {
460
- return false;
461
- }
462
-
463
- $user = get_user_by( 'id', $userID );
464
-
465
- if ( false == $user ) {
466
- return false;
467
- }
468
-
469
- $userdata = (object) array(
470
- 'id' => $user->ID,
471
- 'user_email' => $user->user_email,
472
- 'user_login' => $user->user_login,
473
- 'user_nicename' => $user->user_nicename,
474
- );
475
-
476
- $this->add_gravatar_to_user_array( $userdata );
477
-
478
- return $userdata;
479
- }
480
-
481
- /**
482
- * Return users
483
- */
484
- public function ajax_simple_history_filters_search_user() {
485
-
486
- $q = isset( $_GET['q'] ) ? $_GET['q'] : '';
487
- $page_limit = isset( $_GET['page_limit'] ) ? (int) $_GET['page_limit'] : '';
488
-
489
- // query and page limit must be set
490
- if ( ! $q || ! $page_limit ) {
491
- wp_send_json_error();
492
- }
493
-
494
- // user must have list_users capability (default super admin + administrators have this)
495
- if ( ! current_user_can( 'list_users' ) ) {
496
- wp_send_json_error();
497
- }
498
-
499
- // Search both current users and all logged rows,
500
- // because a user can change email
501
- // search in context: user_id, user_email, user_login
502
- // search in wp_users: login, nicename, user_email
503
- // search and get users. make sure to use "fields" and "number" or we can get timeout/use lots of memory if we have a large amount of users
504
- $results_user = get_users( array(
505
- 'search' => "*{$q}*",
506
- 'fields' => array( 'ID', 'user_login', 'user_nicename', 'user_email', 'display_name' ),
507
- 'number' => 20,
508
- ) );
509
-
510
- // add lower case id to user array
511
- array_walk( $results_user, function( $val ) {
512
- $val->id = $val->ID;
513
- } );
514
-
515
- // add gravatars to user array
516
- array_walk( $results_user, array( $this, 'add_gravatar_to_user_array' ) );
517
-
518
- $data = array(
519
- 'results' => array(),
520
- 'more' => false,
521
- 'context' => array(),
522
- 'count' => sizeof( $results_user ),
523
- );
524
-
525
- $data['results'] = array_merge( $data['results'], $results_user );
526
-
527
- wp_send_json_success( $data );
528
-
529
- } // function
530
-
531
- function add_gravatar_to_user_array( & $val, $index = null ) {
532
- $val->text = sprintf(
533
- '%1$s - %2$s',
534
- $val->user_login,
535
- $val->user_email
536
- );
537
-
538
- $val->gravatar = $this->sh->get_avatar( $val->user_email, '18', 'mm' );
539
-
540
- }
541
-
542
-
543
- /**
544
- * Print out HTML form date elements for editing post or comment publish date.
545
- *
546
- * Based on the wordpress function touch_time();
547
- *
548
- * @global WP_Locale $wp_locale
549
- *
550
- * @param int|bool $edit Accepts 1|true for editing the date, 0|false for adding the date.
551
- * @param int|bool $for_post Accepts 1|true for applying the date to a post, 0|false for a comment.
552
- * @param int $tab_index The tabindex attribute to add. Default 0.
553
- * @param int|bool $multi Optional. Whether the additional fields and buttons should be added.
554
- * Default 0|false.
555
- */
556
- function touch_time( $from_or_to, $edit = 1 ) {
557
-
558
- global $wp_locale;
559
-
560
- // Prefix = text before the inputs
561
- $prefix = '';
562
- $input_prefix = '';
563
- if ( 'from' == $from_or_to ) {
564
- $prefix = _x( 'From', 'Filter dropin, custom date range', 'simple-history' );
565
- $input_prefix = 'from_';
566
- } elseif ( 'to' == $from_or_to ) {
567
- $prefix = _x( 'To', 'Filter dropin, custom date range', 'simple-history' );
568
- $input_prefix = 'to_';
569
- }
570
-
571
- // The default date to show in the inputs
572
- $date = date( 'Y-m-d' );
573
-
574
- $jj = mysql2date( 'd', $date, false );
575
- $mm = mysql2date( 'm', $date, false );
576
- $aa = mysql2date( 'Y', $date, false );
577
-
578
- $month = "<select name='{$input_prefix}mm'>";
579
-
580
- for ( $i = 1; $i < 13; $i = $i + 1 ) {
581
- $monthnum = zeroise( $i, 2 );
582
- $monthtext = $wp_locale->get_month_abbrev( $wp_locale->get_month( $i ) );
583
- $month .= "\t\t\t" . '<option value="' . $monthnum . '" data-text="' . $monthtext . '" ' . selected( $monthnum, $mm, false ) . '>';
584
- /* translators: 1: month number (01, 02, etc.), 2: month abbreviation */
585
- $month .= sprintf( __( '%1$s-%2$s' ), $monthnum, $monthtext ) . "</option>\n";
586
- }
587
- $month .= '</select>';
588
- $month .= '</label>';
589
-
590
- $day = '<label><span class="screen-reader-text">' . __( 'Day' ) . '</span><input type="text" name="' . $input_prefix . 'jj" value="' . $jj . '" size="2" maxlength="2" autocomplete="off" /></label>';
591
- $year = '<label><span class="screen-reader-text">' . __( 'Year' ) . '</span><input type="text" name="' . $input_prefix . 'aa" value="' . $aa . '" size="4" maxlength="4" autocomplete="off" /></label>';
592
-
593
- echo '<span class="SimpleHistory__filters__filter SimpleHistory__filters__filter--day">';
594
-
595
- echo $prefix . '<br>';
596
-
597
- /* translators: 1: month, 2: day, 3: year */
598
- printf( __( '%1$s %2$s, %3$s' ), $month, $day, $year );
599
-
600
- echo '</span>';
601
-
602
- ?>
603
-
604
- <?php
605
- } // func
606
-
607
  } // end class
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: Filter GUI
8
  Author: Pär Thernström
9
  */
10
 
11
+ class SimpleHistoryFilterDropin
12
+ {
13
 
14
+ // Simple History instance
15
+ private $sh;
16
 
17
+ function __construct($sh)
18
+ {
19
 
20
+ $this->sh = $sh;
21
 
22
+ add_action('simple_history/enqueue_admin_scripts', array( $this, 'enqueue_admin_scripts' ));
23
+ add_action('simple_history/history_page/before_gui', array( $this, 'gui_page_filters' ));
24
+ add_action('simple_history/dashboard/before_gui', array( $this, 'gui_page_filters' ));
25
+ add_action('wp_ajax_simple_history_filters_search_user', array( $this, 'ajax_simple_history_filters_search_user' ));
26
+ }
27
 
28
+ public function enqueue_admin_scripts()
29
+ {
30
 
31
+ $file_url = plugin_dir_url(__FILE__);
32
 
33
+ wp_enqueue_script('simple_history_FilterDropin', $file_url . 'SimpleHistoryFilterDropin.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION, true);
34
 
35
+ wp_enqueue_style('simple_history_FilterDropin', $file_url . 'SimpleHistoryFilterDropin.css', null, SIMPLE_HISTORY_VERSION);
36
+ }
37
 
 
38
 
39
+ public function gui_page_filters()
40
+ {
41
 
42
+ $loggers_user_can_read = $this->sh->getLoggersThatUserCanRead();
43
 
44
+ /**
45
+ * Filter that determines if search filters should be visible directly on page load
46
+ *
47
+ * Set to true to show the filters when page is loaded
48
+ * If false then user must click "Show options"
49
+ *
50
+ * @since 2.1.2
51
+ *
52
+ * @param bool $show_more_filters_on_load Default false
53
+ */
54
+ $show_more_filters_on_load = false;
55
+ $show_more_filters_on_load = apply_filters('SimpleHistoryFilterDropin/show_more_filters_on_load', $show_more_filters_on_load);
56
 
57
+ ?>
58
+ <div class="SimpleHistory__filters <?php echo $show_more_filters_on_load ? 'is-showingMoreFilters' : '' ?>">
59
 
60
+ <form class="SimpleHistory__filters__form js-SimpleHistory__filters__form">
 
 
 
 
 
 
 
 
 
 
 
61
 
62
+ <!-- <h3><?php _e('Filter history', 'simple-history') ?></h3> -->
 
63
 
64
+ <?php
65
 
66
+ // Start months filter
67
+ global $wpdb;
68
+ $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
69
+ $loggers_user_can_read_sql_in = $this->sh->getLoggersThatUserCanRead(null, 'sql');
70
 
71
+ // Get unique months
72
+ $cache_key = 'sh_filter_unique_months';
73
+ $result_months = get_transient($cache_key);
74
 
75
+ if (false === $result_months) {
76
+ $sql_dates = sprintf(
77
+ '
 
 
 
 
 
 
 
 
 
78
  SELECT DISTINCT ( date_format(DATE, "%%Y-%%m") ) AS yearMonth
79
  FROM %s
80
  WHERE logger IN %s
81
  ORDER BY yearMonth DESC
82
+ ',
83
+ $table_name, // 1
84
+ $loggers_user_can_read_sql_in // 2
85
+ );
86
+
87
+ $result_months = $wpdb->get_results($sql_dates);
88
+
89
+ set_transient($cache_key, $result_months, HOUR_IN_SECONDS);
90
+ }
91
+
92
+ $arr_days_and_pages = array();
93
+
94
+ // Default month = current month
95
+ // Mainly for performance reasons, since often
96
+ // it's not the users intention to view all events,
97
+ // but just the latest
98
+ $this_month = date('Y-m');
99
+
100
+ // Determine if we limit the date range by default
101
+ $daysToShow = 1;
102
+
103
+ // Start with the latest day
104
+ $numEvents = $this->sh->get_unique_events_for_days($daysToShow);
105
+ $numPages = $numEvents / $this->sh->get_pager_size();
106
+
107
+ $arr_days_and_pages[] = array(
108
+ 'daysToShow' => $daysToShow,
109
+ 'numPages' => $numPages,
110
+ );
111
+
112
+ // Example on my server with lots of brute force attacks (causing log to not load)
113
+ // 166434 / 15 = 11 000 pages for last 7 days
114
+ // 1 day = 3051 / 15 = 203 pages = still much but better than 11000 pages!
115
+ if ($numPages < 20) {
116
+ // Not that many things the last day. Let's try to expand to 7 days instead.
117
+ $daysToShow = 7;
118
+ $numEvents = $this->sh->get_unique_events_for_days($daysToShow);
119
+ $numPages = $numEvents / $this->sh->get_pager_size();
120
+
121
+ $arr_days_and_pages[] = array(
122
+ 'daysToShow' => $daysToShow,
123
+ 'numPages' => $numPages,
124
+ );
125
+
126
+ if ($numPages < 20) {
127
+ // Not that many things the last 7 days. Let's try to expand to 14 days instead.
128
+ $daysToShow = 14;
129
+ $numEvents = $this->sh->get_unique_events_for_days($daysToShow);
130
+ $numPages = $numEvents / $this->sh->get_pager_size();
131
+
132
+ $arr_days_and_pages[] = array(
133
+ 'daysToShow' => $daysToShow,
134
+ 'numPages' => $numPages,
135
+ );
136
+
137
+ if ($numPages < 20) {
138
+ // Not many things the last 14 days either. Let try with 30 days.
139
+ $daysToShow = 30;
140
+ $numEvents = $this->sh->get_unique_events_for_days($daysToShow);
141
+ $numPages = $numEvents / $this->sh->get_pager_size();
142
+
143
+ $arr_days_and_pages[] = array(
144
+ 'daysToShow' => $daysToShow,
145
+ 'numPages' => $numPages,
146
+ );
147
+
148
+ // If 30 days gives a big amount of pages, go back to 14 days
149
+ if ($numPages > 1000) {
150
+ $daysToShow = 14;
151
+ }
152
+ }
153
+ }
154
+ }// End if().
155
+
156
+ ?>
157
+
158
+ <p class="SimpleHistory__filters__filterRow SimpleHistory__filters__filterRow--date" data-debug-daysAndPages='<?php echo json_encode($arr_days_and_pages) ?>'>
159
+
160
+ <label class="SimpleHistory__filters__filterLabel"><?php _ex('Dates:', 'Filter label', 'simple-history') ?></label>
161
+
162
+ <select class="SimpleHistory__filters__filter SimpleHistory__filters__filter--date"
163
+ name="dates"
164
+ placeholder="<?php echo _e('All dates', 'simple-history') ?>"
165
+ NOTmultiple
166
+ >
167
+ <?php
168
+
169
+ // custom date range
170
+ // since 2.8.1
171
+ printf(
172
+ '<option value="%1$s" %3$s>%2$s</option>',
173
+ 'customRange', // 1 - value
174
+ _x('Custom date range...', 'Filter dropin: filter custom range', 'simple-history'), // 2 text
175
+ selected($daysToShow, 'customRange', 0)
176
+ );
177
+
178
+ // One day+ Last week + two weeks back + 30 days back
179
+ printf(
180
+ '<option value="%1$s" %3$s>%2$s</option>',
181
+ 'lastdays:1', // 1 - value
182
+ _x('Last day', 'Filter dropin: filter week', 'simple-history'), // 2 text
183
+ selected($daysToShow, 1, 0)
184
+ );
185
+
186
+ printf(
187
+ '<option value="%1$s" %3$s>%2$s</option>',
188
+ 'lastdays:7', // 1 - value
189
+ _x('Last 7 days', 'Filter dropin: filter week', 'simple-history'), // 2 text
190
+ selected($daysToShow, 7, 0)
191
+ );
192
+
193
+ printf(
194
+ '<option value="%1$s" %3$s>%2$s</option>',
195
+ 'lastdays:14', // 1 - value
196
+ _x('Last 14 days', 'Filter dropin: filter week', 'simple-history'), // 2 text
197
+ selected($daysToShow, 14, 0)
198
+ );
199
+
200
+ printf(
201
+ '<option value="%1$s" %3$s>%2$s</option>',
202
+ 'lastdays:30', // 1 - value
203
+ _x('Last 30 days', 'Filter dropin: filter week', 'simple-history'), // 2 text
204
+ selected($daysToShow, 30, 0)
205
+ );
206
+
207
+ printf(
208
+ '<option value="%1$s" %3$s>%2$s</option>',
209
+ 'lastdays:60', // 1 - value
210
+ _x('Last 60 days', 'Filter dropin: filter week', 'simple-history'), // 2 text
211
+ selected($daysToShow, 60, 0)
212
+ );
213
+
214
+ // Months
215
+ foreach ($result_months as $row) {
216
+ printf(
217
+ '<option value="%1$s" %3$s>%2$s</option>',
218
+ 'month:' . $row->yearMonth,
219
+ date_i18n('F Y', strtotime($row->yearMonth)),
220
+ '' // selected( $this_month, $row->yearMonth, false )
221
+ );
222
+ }
223
+
224
+ ?>
225
+ </select>
226
+
227
+ <!-- <p> -->
228
+ <!-- <label class="SimpleHistory__filters__filterLabel"><?php _ex('Between dates:', 'Filter label', 'simple-history') ?></label> -->
229
+ <span class="SimpleHistory__filters__filter--dayValuesWrap">
230
+ <?php
231
+ $this->touch_time('from');
232
+ $this->touch_time('to');
233
+ ?>
234
+ </span>
235
+ <!-- </p> -->
236
+
237
+ </p><!-- end months -->
238
+
239
+ <?php
240
+ /**
241
+ * Filter to control what the default search string is. Default to empty string.
242
+ *
243
+ * @since 2.1.2
244
+ *
245
+ * @param string Default search string
246
+ */
247
+ $default_search_string = apply_filters('SimpleHistoryFilterDropin/filter_default_search_string', '');
248
+ ?>
249
+
250
+ <p>
251
+
252
+ <label class="SimpleHistory__filters__filterLabel"><?php _ex('Containing words:', 'Filter label', 'simple-history') ?></label>
253
+
254
+ <input
255
+ type="search"
256
+ class="SimpleHistoryFilterDropin-searchInput"
257
+ placeholder="<?php /* _e("Containing words", "simple-history"); */ ?>"
258
+ name="search"
259
+ value="<?php echo esc_attr($default_search_string); ?>"
260
+ >
261
+
262
+ </p>
263
+
264
+ <p class="SimpleHistory__filters__filterSubmitWrap">
265
+ <button class="button SimpleHistoryFilterDropin-doFilterButton SimpleHistoryFilterDropin-doFilterButton--first js-SimpleHistoryFilterDropin-doFilter"><?php _e('Search events', 'simple-history') ?></button>
266
+ <button type="button" class="SimpleHistoryFilterDropin-showMoreFilters SimpleHistoryFilterDropin-showMoreFilters--first js-SimpleHistoryFilterDropin-showMoreFilters"><?php _ex('Show search options', 'Filter dropin: button to show more search options', 'simple-history') ?></button>
267
+ </p>
268
+
269
+ <?php
270
+ /**
271
+ * Filter to control what the default loglevels are.
272
+ *
273
+ * @since 2.1.2
274
+ *
275
+ * @param array Array with loglevel sugs. Default empty = show all.
276
+ */
277
+ $arr_default_loglevels = apply_filters('SimpleHistoryFilterDropin/filter_default_loglevel', array());
278
+ ?>
279
+ <div class="SimpleHistory__filters__moreFilters js-SimpleHistory__filters__moreFilters">
280
+
281
+ <p>
282
+
283
+ <label class="SimpleHistory__filters__filterLabel"><?php _ex('Log levels:', 'Filter label', 'simple-history') ?></label>
284
+
285
+ <select
286
+ name="loglevels"
287
+ class="SimpleHistory__filters__filter SimpleHistory__filters__filter--loglevel"
288
+ placeholder="<?php _e('All log levels', 'simple-history') ?>"
289
+ multiple
290
+ >
291
+ <option <?php selected(in_array('debug', $arr_default_loglevels)) ?> value="debug" data-color="#CEF6D8"><?php echo $this->sh->getLogLevelTranslated('Debug') ?></option>
292
+ <option <?php selected(in_array('info', $arr_default_loglevels)) ?> value="info" data-color="white"><?php echo $this->sh->getLogLevelTranslated('Info') ?></option>
293
+ <option <?php selected(in_array('notice', $arr_default_loglevels)) ?> value="notice" data-color="rgb(219, 219, 183)"><?php echo $this->sh->getLogLevelTranslated('Notice') ?></option>
294
+ <option <?php selected(in_array('warning', $arr_default_loglevels)) ?> value="warning" data-color="#F7D358"><?php echo $this->sh->getLogLevelTranslated('Warning') ?></option>
295
+ <option <?php selected(in_array('error', $arr_default_loglevels)) ?> value="error" data-color="#F79F81"><?php echo $this->sh->getLogLevelTranslated('Error') ?></option>
296
+ <option <?php selected(in_array('critical', $arr_default_loglevels)) ?> value="critical" data-color="#FA5858"><?php echo $this->sh->getLogLevelTranslated('Critical') ?></option>
297
+ <option <?php selected(in_array('alert', $arr_default_loglevels)) ?> value="alert" data-color="rgb(199, 69, 69)"><?php echo $this->sh->getLogLevelTranslated('Alert') ?></option>
298
+ <option <?php selected(in_array('emergency', $arr_default_loglevels)) ?> value="emergency" data-color="#DF0101"><?php echo $this->sh->getLogLevelTranslated('Emergency') ?></option>
299
+ </select>
300
+
301
+ </p>
302
+
303
+ <?php
304
+
305
+ /**
306
+ * Todo: Filter to control what the default messages to filter/search.
307
+ * Message in in format: LoggerSlug:MessageKey
308
+ * For example:
309
+ * - SimplePluginLogger:plugin_activated
310
+ * - SimpleCommentsLogger:user_comment_added
311
+ *
312
+ * @since 2.1.2
313
+ *
314
+ * @param array Array with loglevel sugs. Default empty = show all.
315
+ */
316
+ // $arr_default_messages = apply_filters("SimpleHistoryFilterDropin/filter_default_messages", array());
317
+ ?>
318
+ <p>
319
+
320
+ <label class="SimpleHistory__filters__filterLabel"><?php _ex('Message types:', 'Filter label', 'simple-history') ?></label>
321
+
322
+ <select
323
+ name="messages"
324
+ class="SimpleHistory__filters__filter SimpleHistory__filters__filter--logger"
325
+ placeholder="<?php _e('All messages', 'simple-history') ?>"
326
+ multiple
327
+ >
328
+ <?php
329
+ foreach ($loggers_user_can_read as $logger) {
330
+ $logger_info = $logger['instance']->getInfo();
331
+ $logger_slug = $logger['instance']->slug;
332
+
333
+ // Get labels for logger
334
+ if (isset($logger_info['labels']['search'])) {
335
+ printf('<optgroup label="%1$s">', esc_attr($logger_info['labels']['search']['label']));
336
+
337
+ // If all activity
338
+ if (! empty($logger_info['labels']['search']['label_all'])) {
339
+ $arr_all_search_messages = array();
340
+ foreach ($logger_info['labels']['search']['options'] as $option_key => $option_messages) {
341
+ $arr_all_search_messages = array_merge($arr_all_search_messages, $option_messages);
342
+ }
343
+
344
+ foreach ($arr_all_search_messages as $key => $val) {
345
+ $arr_all_search_messages[ $key ] = $logger_slug . ':' . $val;
346
+ }
347
+
348
+ printf('<option value="%2$s">%1$s</option>', esc_attr($logger_info['labels']['search']['label_all']), esc_attr(implode(',', $arr_all_search_messages)));
349
+ }
350
+
351
+ // For each specific search option
352
+ foreach ($logger_info['labels']['search']['options'] as $option_key => $option_messages) {
353
+ foreach ($option_messages as $key => $val) {
354
+ $option_messages[ $key ] = $logger_slug . ':' . $val;
355
+ }
356
+
357
+ $str_option_messages = implode(',', $option_messages);
358
+ printf(
359
+ '<option value="%2$s">%1$s</option>',
360
+ esc_attr($option_key), // 1
361
+ esc_attr($str_option_messages) // 2
362
+ );
363
+ }
364
+
365
+ printf('</optgroup>');
366
+ }// End if().
367
+ }// End foreach().
368
+ ?>
369
+ </select>
370
+ </p>
371
+
372
+ <?php
373
+
374
+ /**
375
+ * Filter what users to search for by default
376
+ *
377
+ * Example:
378
+ *
379
+ * add_filter("SimpleHistoryFilterDropin/filter_default_user_ids", function() { return array(1,4); });
380
+ *
381
+ * @since 2.1.2
382
+ *
383
+ * @param array Array with user ids. Default is an empty array = show all users.
384
+ */
385
+
386
+ /*
387
+ add_filter("SimpleHistoryFilterDropin/filter_default_user_ids", function($arr) {
388
+ $arr = array(
389
+ 1,
390
+ 4
391
+ );
392
+ return $arr;
393
+ });
394
+ //*/
395
+
396
+ $default_user_ids = apply_filters('SimpleHistoryFilterDropin/filter_default_user_ids', array());
397
+ $arr_default_user_data = array();
398
+
399
+ foreach ($default_user_ids as $user_id) {
400
+ $arr_default_user_data[] = $this->get_data_for_user($user_id);
401
+ }
402
+
403
+ if (current_user_can('list_users')) {
404
+ ?>
405
+ <p>
406
+ <label class="SimpleHistory__filters__filterLabel"><?php _ex('Users:', 'Filter label', 'simple-history') ?></label>
407
+ <select
408
+ name="users"
409
+ class="SimpleHistory__filters__filter SimpleHistory__filters__filter--user"
410
+ placeholder="<?php _e('All users', 'simple-history') ?>"
411
+ value="<?php echo esc_attr(implode(',', $default_user_ids)) ?>"
412
+ data-default-user-data="<?php echo esc_attr(json_encode($arr_default_user_data)) ?>"
413
+ >
414
+ </select>
415
+ </p>
416
+ <?php
417
+ }
418
+
419
+ ?>
420
+
421
+ <p class="SimpleHistory__filters__filterSubmitWrap">
422
+ <button class="button SimpleHistoryFilterDropin-doFilterButton SimpleHistoryFilterDropin-doFilterButton--second js-SimpleHistoryFilterDropin-doFilter"><?php _e('Search events', 'simple-history') ?></button>
423
+ <button type="button" class="SimpleHistoryFilterDropin-showMoreFilters SimpleHistoryFilterDropin-showMoreFilters--second js-SimpleHistoryFilterDropin-showMoreFilters"><?php _ex('Hide search options', 'Filter dropin: button to hide more search options', 'simple-history') ?></button>
424
+ </p>
425
+
426
+ </div><!-- // more filters -->
427
+
428
+ <!--
429
+ <p>
430
+ <button class="button js-SimpleHistoryFilterDropin-doFilter"><?php _e('Search', 'simple-history') ?></button>
431
+ </p>
432
+ -->
433
+
434
+ </form>
435
+
436
+ </div>
437
+ <?php
438
+ } // function
439
+
440
+
441
+ /**
442
+ * Return format used for select2 for a single user id
443
+ *
444
+ * @param int $userID
445
+ * @return array Array with each user as an object
446
+ */
447
+ public function get_data_for_user($userID)
448
+ {
449
+ if (! $userID || ! is_numeric($userID)) {
450
+ return false;
451
+ }
452
+
453
+ $user = get_user_by('id', $userID);
454
+
455
+ if (false == $user) {
456
+ return false;
457
+ }
458
+
459
+ $userdata = (object) array(
460
+ 'id' => $user->ID,
461
+ 'user_email' => $user->user_email,
462
+ 'user_login' => $user->user_login,
463
+ 'user_nicename' => $user->user_nicename,
464
+ );
465
+
466
+ $this->add_gravatar_to_user_array($userdata);
467
+
468
+ return $userdata;
469
+ }
470
+
471
+ /**
472
+ * Return users
473
+ */
474
+ public function ajax_simple_history_filters_search_user()
475
+ {
476
+
477
+ $q = isset($_GET['q']) ? $_GET['q'] : '';
478
+ $page_limit = isset($_GET['page_limit']) ? (int) $_GET['page_limit'] : '';
479
+
480
+ // query and page limit must be set
481
+ if (! $q || ! $page_limit) {
482
+ wp_send_json_error();
483
+ }
484
+
485
+ // user must have list_users capability (default super admin + administrators have this)
486
+ if (! current_user_can('list_users')) {
487
+ wp_send_json_error();
488
+ }
489
+
490
+ // Search both current users and all logged rows,
491
+ // because a user can change email
492
+ // search in context: user_id, user_email, user_login
493
+ // search in wp_users: login, nicename, user_email
494
+ // search and get users. make sure to use "fields" and "number" or we can get timeout/use lots of memory if we have a large amount of users
495
+ $results_user = get_users(array(
496
+ 'search' => "*{$q}*",
497
+ 'fields' => array( 'ID', 'user_login', 'user_nicename', 'user_email', 'display_name' ),
498
+ 'number' => 20,
499
+ ));
500
+
501
+ // add lower case id to user array
502
+ array_walk($results_user, function ($val) {
503
+ $val->id = $val->ID;
504
+ });
505
+
506
+ // add gravatars to user array
507
+ array_walk($results_user, array( $this, 'add_gravatar_to_user_array' ));
508
+
509
+ $data = array(
510
+ 'results' => array(),
511
+ 'more' => false,
512
+ 'context' => array(),
513
+ 'count' => sizeof($results_user),
514
+ );
515
+
516
+ $data['results'] = array_merge($data['results'], $results_user);
517
+
518
+ wp_send_json_success($data);
519
+ } // function
520
+
521
+ function add_gravatar_to_user_array(& $val, $index = null)
522
+ {
523
+ $val->text = sprintf(
524
+ '%1$s - %2$s',
525
+ $val->user_login,
526
+ $val->user_email
527
+ );
528
+
529
+ $val->gravatar = $this->sh->get_avatar($val->user_email, '18', 'mm');
530
+ }
531
+
532
+
533
+ /**
534
+ * Print out HTML form date elements for editing post or comment publish date.
535
+ *
536
+ * Based on the wordpress function touch_time();
537
+ *
538
+ * @global WP_Locale $wp_locale
539
+ *
540
+ * @param int|bool $edit Accepts 1|true for editing the date, 0|false for adding the date.
541
+ * @param int|bool $for_post Accepts 1|true for applying the date to a post, 0|false for a comment.
542
+ * @param int $tab_index The tabindex attribute to add. Default 0.
543
+ * @param int|bool $multi Optional. Whether the additional fields and buttons should be added.
544
+ * Default 0|false.
545
+ */
546
+ function touch_time($from_or_to, $edit = 1)
547
+ {
548
+
549
+ global $wp_locale;
550
+
551
+ // Prefix = text before the inputs
552
+ $prefix = '';
553
+ $input_prefix = '';
554
+ if ('from' == $from_or_to) {
555
+ $prefix = _x('From', 'Filter dropin, custom date range', 'simple-history');
556
+ $input_prefix = 'from_';
557
+ } elseif ('to' == $from_or_to) {
558
+ $prefix = _x('To', 'Filter dropin, custom date range', 'simple-history');
559
+ $input_prefix = 'to_';
560
+ }
561
+
562
+ // The default date to show in the inputs
563
+ $date = date('Y-m-d');
564
+
565
+ $jj = mysql2date('d', $date, false);
566
+ $mm = mysql2date('m', $date, false);
567
+ $aa = mysql2date('Y', $date, false);
568
+
569
+ $month = "<select name='{$input_prefix}mm'>";
570
+
571
+ for ($i = 1; $i < 13; $i = $i + 1) {
572
+ $monthnum = zeroise($i, 2);
573
+ $monthtext = $wp_locale->get_month_abbrev($wp_locale->get_month($i));
574
+ $month .= "\t\t\t" . '<option value="' . $monthnum . '" data-text="' . $monthtext . '" ' . selected($monthnum, $mm, false) . '>';
575
+ /* translators: 1: month number (01, 02, etc.), 2: month abbreviation */
576
+ $month .= sprintf(__('%1$s-%2$s'), $monthnum, $monthtext) . "</option>\n";
577
+ }
578
+ $month .= '</select>';
579
+ $month .= '</label>';
580
+
581
+ $day = '<label><span class="screen-reader-text">' . __('Day') . '</span><input type="text" name="' . $input_prefix . 'jj" value="' . $jj . '" size="2" maxlength="2" autocomplete="off" /></label>';
582
+ $year = '<label><span class="screen-reader-text">' . __('Year') . '</span><input type="text" name="' . $input_prefix . 'aa" value="' . $aa . '" size="4" maxlength="4" autocomplete="off" /></label>';
583
+
584
+ echo '<span class="SimpleHistory__filters__filter SimpleHistory__filters__filter--day">';
585
+
586
+ echo $prefix . '<br>';
587
+
588
+ /* translators: 1: month, 2: day, 3: year */
589
+ printf(__('%1$s %2$s, %3$s'), $month, $day, $year);
590
+
591
+ echo '</span>';
592
+
593
+ ?>
594
+
595
+ <?php
596
+ } // func
 
 
 
 
 
 
 
 
 
 
 
 
597
  } // end class
dropins/SimpleHistoryIpInfoDropin.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /*
6
  Dropin Name: IP Info
@@ -8,169 +8,170 @@ Dropin URI: http://simple-history.com/
8
  Author: Pär Thernström
9
  */
10
 
11
- class SimpleHistoryIpInfoDropin {
12
-
13
- private $sh;
14
-
15
- function __construct( $sh ) {
16
-
17
- $this->sh = $sh;
18
-
19
- // Since it's not quite done yet, it's for da devs only for now
20
- /*
21
- if ( ! defined("SIMPLE_HISTORY_DEV") || ! SIMPLE_HISTORY_DEV ) {
22
- return;
23
- }*/
24
-
25
- add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'enqueue_admin_scripts' ) );
26
- add_action( 'simple_history/admin_footer', array( $this, 'add_js_template' ) );
27
-
28
- }
29
-
30
- public function enqueue_admin_scripts() {
31
-
32
- $file_url = plugin_dir_url( __FILE__ );
33
-
34
- wp_enqueue_script( 'simple_history_IpInfoDropin', $file_url . 'SimpleHistoryIpInfoDropin.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION, true );
35
-
36
- wp_enqueue_style( 'simple_history_IpInfoDropin', $file_url . 'SimpleHistoryIpInfoDropin.css', null, SIMPLE_HISTORY_VERSION );
37
-
38
- }
39
-
40
- public function add_js_template() {
41
- ?>
42
-
43
- <div class="SimpleHistoryIpInfoDropin__popup">
44
- <div class="SimpleHistoryIpInfoDropin__popupArrow"></div>
45
- <div class="SimpleHistoryIpInfoDropin__popupClose"><button class="SimpleHistoryIpInfoDropin__popupCloseButton">×</button></div>
46
- <div class="SimpleHistoryIpInfoDropin__popupContent"></div>
47
- </div>
48
-
49
- <script type="text/html" id="tmpl-simple-history-ipinfodropin-popup-loading">
50
- <!-- <p>Getting IP info ...</p> -->
51
- </script>
52
-
53
- <script type="text/html" id="tmpl-simple-history-ipinfodropin-popup-loaded">
54
- <!--
55
- {
56
- "ip": "8.8.8.8",
57
- "hostname": "google-public-dns-a.google.com",
58
- "city": "Mountain View",
59
- "region": "California",
60
- "country": "US",
61
- "loc": "37.3860,-122.0838",
62
- "org": "AS15169 Google Inc.",
63
- "postal": "94035"
64
- }
65
- -->
66
- <# if ( typeof(data.bogon) != "undefined" ) { #>
67
-
68
- <p><?php _ex( 'That IP address does not seem like a public one.', 'IP Info Dropin', 'simple-history' ); ?></p>
69
-
70
- <# } else { #>
71
-
72
- <table class="SimpleHistoryIpInfoDropin__ipInfoTable">
73
-
74
- <tr class="SimpleHistoryIpInfoDropin__ipInfoTable__mapRow">
75
- <td colspan="2">
76
- <!--
77
- <# if ( typeof(data.loc) != "undefined" && data.loc ) { #>
78
- <a href="https://www.google.com/maps/place/{{ data.loc }}/@{{ data.loc }},6z" target="_blank">
79
- <img src="https://maps.googleapis.com/maps/api/staticmap?center={{ data.loc }}&zoom=7&size=350x100&sensor=false" width="350" height="100" alt="Google Map">
80
- </a>
81
- <# } #>
82
- -->
83
- </td>
84
- </tr>
85
-
86
- <# if ( typeof(data.ip) != "undefined" && data.ip ) { #>
87
- <tr>
88
- <td>
89
- <?php _ex( 'IP address', 'IP Info Dropin', 'simple-history' ); ?>
90
- </td>
91
- <td>
92
- {{ data.ip }}
93
- </td>
94
- </tr>
95
- <# } #>
96
-
97
- <# if ( typeof(data.hostname) != "undefined" && data.hostname ) { #>
98
- <tr>
99
- <td>
100
- <?php _ex( 'Hostname', 'IP Info Dropin', 'simple-history' ); ?>
101
- </td>
102
- <td>
103
- {{ data.hostname }}
104
- </td>
105
- </tr>
106
- <# } #>
107
-
108
- <# if ( typeof(data.org) != "undefined" && data.org ) { #>
109
- <tr>
110
- <td>
111
- <?php _ex( 'Network', 'IP Info Dropin', 'simple-history' ); ?>
112
- </td>
113
- <td>
114
- {{ data.org }}
115
- </td>
116
- </tr>
117
- <# } #>
118
-
119
- <# if ( typeof(data.network) != "undefined" && data.network ) { #>
120
- <tr>
121
- <td>
122
- <?php _ex( 'Network', 'IP Info Dropin', 'simple-history' ); ?>
123
- </td>
124
- <td>
125
- {{ data.network }}
126
- </td>
127
- </tr>
128
- <# } #>
129
-
130
- <# if ( typeof(data.city) != "undefined" && data.city ) { #>
131
- <tr>
132
- <td>
133
- <?php _ex( 'City', 'IP Info Dropin', 'simple-history' ); ?>
134
- </td>
135
- <td>
136
- {{ data.city }}
137
- </td>
138
- </tr>
139
- <# } #>
140
-
141
- <# if ( typeof(data.region) != "undefined" && data.region ) { #>
142
- <tr>
143
- <td>
144
- <?php _ex( 'Region', 'IP Info Dropin', 'simple-history' ); ?>
145
- </td>
146
- <td>
147
- {{ data.region }}
148
- </td>
149
- </tr>
150
- <# } #>
151
-
152
- <# if ( typeof(data.country) != "undefined" && data.country ) { #>
153
- <tr>
154
- <td>
155
- <?php _ex( 'Country', 'IP Info Dropin', 'simple-history' ); ?>
156
- </td>
157
- <td>
158
- {{ data.country }}
159
- </td>
160
- </tr>
161
- <# } #>
162
-
163
- </table>
164
-
165
- <p class="SimpleHistoryIpInfoDropin__provider">
166
- <?php printf( _x( 'IP info provided by %1$s ipinfo.io %2$s', 'IP Info Dropin', 'simple-history' ), "<a href='https://ipinfo.io/{{ data.ip }}' target='_blank'>", '</a>' ); ?>
167
- </p>
168
-
169
- <# } #>
170
-
171
- </script>
172
- <?php
173
- }
174
-
 
175
  } // end class
176
 
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: IP Info
8
  Author: Pär Thernström
9
  */
10
 
11
+ class SimpleHistoryIpInfoDropin
12
+ {
13
+
14
+ private $sh;
15
+
16
+ function __construct($sh)
17
+ {
18
+
19
+ $this->sh = $sh;
20
+
21
+ // Since it's not quite done yet, it's for da devs only for now
22
+ /*
23
+ if ( ! defined("SIMPLE_HISTORY_DEV") || ! SIMPLE_HISTORY_DEV ) {
24
+ return;
25
+ }*/
26
+
27
+ add_action('simple_history/enqueue_admin_scripts', array( $this, 'enqueue_admin_scripts' ));
28
+ add_action('simple_history/admin_footer', array( $this, 'add_js_template' ));
29
+ }
30
+
31
+ public function enqueue_admin_scripts()
32
+ {
33
+
34
+ $file_url = plugin_dir_url(__FILE__);
35
+
36
+ wp_enqueue_script('simple_history_IpInfoDropin', $file_url . 'SimpleHistoryIpInfoDropin.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION, true);
37
+
38
+ wp_enqueue_style('simple_history_IpInfoDropin', $file_url . 'SimpleHistoryIpInfoDropin.css', null, SIMPLE_HISTORY_VERSION);
39
+ }
40
+
41
+ public function add_js_template()
42
+ {
43
+ ?>
44
+
45
+ <div class="SimpleHistoryIpInfoDropin__popup">
46
+ <div class="SimpleHistoryIpInfoDropin__popupArrow"></div>
47
+ <div class="SimpleHistoryIpInfoDropin__popupClose"><button class="SimpleHistoryIpInfoDropin__popupCloseButton">×</button></div>
48
+ <div class="SimpleHistoryIpInfoDropin__popupContent"></div>
49
+ </div>
50
+
51
+ <script type="text/html" id="tmpl-simple-history-ipinfodropin-popup-loading">
52
+ <!-- <p>Getting IP info ...</p> -->
53
+ </script>
54
+
55
+ <script type="text/html" id="tmpl-simple-history-ipinfodropin-popup-loaded">
56
+ <!--
57
+ {
58
+ "ip": "8.8.8.8",
59
+ "hostname": "google-public-dns-a.google.com",
60
+ "city": "Mountain View",
61
+ "region": "California",
62
+ "country": "US",
63
+ "loc": "37.3860,-122.0838",
64
+ "org": "AS15169 Google Inc.",
65
+ "postal": "94035"
66
+ }
67
+ -->
68
+ <# if ( typeof(data.bogon) != "undefined" ) { #>
69
+
70
+ <p><?php _ex('That IP address does not seem like a public one.', 'IP Info Dropin', 'simple-history'); ?></p>
71
+
72
+ <# } else { #>
73
+
74
+ <table class="SimpleHistoryIpInfoDropin__ipInfoTable">
75
+
76
+ <tr class="SimpleHistoryIpInfoDropin__ipInfoTable__mapRow">
77
+ <td colspan="2">
78
+ <!--
79
+ <# if ( typeof(data.loc) != "undefined" && data.loc ) { #>
80
+ <a href="https://www.google.com/maps/place/{{ data.loc }}/@{{ data.loc }},6z" target="_blank">
81
+ <img src="https://maps.googleapis.com/maps/api/staticmap?center={{ data.loc }}&zoom=7&size=350x100&sensor=false" width="350" height="100" alt="Google Map">
82
+ </a>
83
+ <# } #>
84
+ -->
85
+ </td>
86
+ </tr>
87
+
88
+ <# if ( typeof(data.ip) != "undefined" && data.ip ) { #>
89
+ <tr>
90
+ <td>
91
+ <?php _ex('IP address', 'IP Info Dropin', 'simple-history'); ?>
92
+ </td>
93
+ <td>
94
+ {{ data.ip }}
95
+ </td>
96
+ </tr>
97
+ <# } #>
98
+
99
+ <# if ( typeof(data.hostname) != "undefined" && data.hostname ) { #>
100
+ <tr>
101
+ <td>
102
+ <?php _ex('Hostname', 'IP Info Dropin', 'simple-history'); ?>
103
+ </td>
104
+ <td>
105
+ {{ data.hostname }}
106
+ </td>
107
+ </tr>
108
+ <# } #>
109
+
110
+ <# if ( typeof(data.org) != "undefined" && data.org ) { #>
111
+ <tr>
112
+ <td>
113
+ <?php _ex('Network', 'IP Info Dropin', 'simple-history'); ?>
114
+ </td>
115
+ <td>
116
+ {{ data.org }}
117
+ </td>
118
+ </tr>
119
+ <# } #>
120
+
121
+ <# if ( typeof(data.network) != "undefined" && data.network ) { #>
122
+ <tr>
123
+ <td>
124
+ <?php _ex('Network', 'IP Info Dropin', 'simple-history'); ?>
125
+ </td>
126
+ <td>
127
+ {{ data.network }}
128
+ </td>
129
+ </tr>
130
+ <# } #>
131
+
132
+ <# if ( typeof(data.city) != "undefined" && data.city ) { #>
133
+ <tr>
134
+ <td>
135
+ <?php _ex('City', 'IP Info Dropin', 'simple-history'); ?>
136
+ </td>
137
+ <td>
138
+ {{ data.city }}
139
+ </td>
140
+ </tr>
141
+ <# } #>
142
+
143
+ <# if ( typeof(data.region) != "undefined" && data.region ) { #>
144
+ <tr>
145
+ <td>
146
+ <?php _ex('Region', 'IP Info Dropin', 'simple-history'); ?>
147
+ </td>
148
+ <td>
149
+ {{ data.region }}
150
+ </td>
151
+ </tr>
152
+ <# } #>
153
+
154
+ <# if ( typeof(data.country) != "undefined" && data.country ) { #>
155
+ <tr>
156
+ <td>
157
+ <?php _ex('Country', 'IP Info Dropin', 'simple-history'); ?>
158
+ </td>
159
+ <td>
160
+ {{ data.country }}
161
+ </td>
162
+ </tr>
163
+ <# } #>
164
+
165
+ </table>
166
+
167
+ <p class="SimpleHistoryIpInfoDropin__provider">
168
+ <?php printf(_x('IP info provided by %1$s ipinfo.io %2$s', 'IP Info Dropin', 'simple-history'), "<a href='https://ipinfo.io/{{ data.ip }}' target='_blank'>", '</a>'); ?>
169
+ </p>
170
+
171
+ <# } #>
172
+
173
+ </script>
174
+ <?php
175
+ }
176
  } // end class
177
 
dropins/SimpleHistoryNewRowsNotifier.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /**
6
  * Dropin Name: New Items Notifier
@@ -9,92 +9,90 @@ defined( 'ABSPATH' ) or die();
9
  * Author: Pär Thernström
10
  */
11
 
12
- class SimpleHistoryNewRowsNotifier {
 
13
 
14
- // Simple History instance
15
- private $sh;
16
 
17
- // How often we should check for new rows, in ms
18
- private $interval = 10000;
19
 
20
- function __construct( $sh ) {
 
21
 
22
- $this->sh = $sh;
23
 
24
- // How often the script checks for new rows
25
- $this->interval = (int) apply_filters( 'SimpleHistoryNewRowsNotifier/interval', $this->interval );
26
 
27
- add_action( 'wp_ajax_SimpleHistoryNewRowsNotifier', array( $this, 'ajax' ) );
28
- add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'enqueue_admin_scripts' ) );
 
29
 
30
- }
 
31
 
32
- public function enqueue_admin_scripts() {
33
 
34
- $file_url = plugin_dir_url( __FILE__ );
35
 
36
- wp_enqueue_script( 'simple_history_NewRowsNotifierDropin', $file_url . 'SimpleHistoryNewRowsNotifierDropin.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION, true );
 
 
 
37
 
38
- $arr_localize_data = array(
39
- 'interval' => $this->interval,
40
- 'errorCheck' => _x( 'An error occured while checking for new events', 'New rows notifier: error while checking for new rows', 'simple-history' ),
41
- );
42
 
43
- wp_localize_script( 'simple_history_NewRowsNotifierDropin', 'simple_history_NewRowsNotifierDropin', $arr_localize_data );
 
44
 
45
- wp_enqueue_style( 'simple_history_NewRowsNotifierDropin', $file_url . 'SimpleHistoryNewRowsNotifierDropin.css', null, SIMPLE_HISTORY_VERSION );
 
46
 
47
- }
48
 
49
- public function ajax() {
 
 
 
 
50
 
51
- $apiArgs = isset( $_GET['apiArgs'] ) ? $_GET['apiArgs'] : array();
 
 
 
 
52
 
53
- if ( ! $apiArgs ) {
54
- wp_send_json_error( array(
55
- 'error' => 'MISSING_APIARGS',
56
- ) );
57
- }
 
58
 
59
- if ( empty( $apiArgs['since_id'] ) || ! is_numeric( $apiArgs['since_id'] ) ) {
60
- wp_send_json_error( array(
61
- 'error' => 'MISSING_SINCE_ID',
62
- ) );
63
- }
64
 
65
- // User must have capability to view the history page
66
- if ( ! current_user_can( $this->sh->get_view_history_capability() ) ) {
67
- wp_send_json_error( array(
68
- 'error' => 'CAPABILITY_ERROR',
69
- ) );
70
- }
71
 
72
- // $since_id = isset( $_GET["since_id"] ) ? absint($_GET["since_id"]) : null;
73
- $logQueryArgs = $apiArgs;
74
 
75
- $logQuery = new SimpleHistoryLogQuery();
76
- $answer = $logQuery->query( $logQueryArgs );
 
77
 
78
- // Use our own response array instead of $answer to keep size down
79
- $json_data = array();
80
-
81
- $numNewRows = isset( $answer['total_row_count'] ) ? $answer['total_row_count'] : 0;
82
- $json_data['num_new_rows'] = $numNewRows;
83
- $json_data['num_mysql_queries'] = get_num_queries();
84
-
85
- if ( $numNewRows ) {
86
-
87
- // We have new rows
88
- // Append strings
89
- $textRowsFound = sprintf( _n( '1 new event', '%d new events', $numNewRows, 'simple-history' ), $numNewRows );
90
- $json_data['strings'] = array(
91
- 'newRowsFound' => $textRowsFound,
92
- );
93
-
94
- }
95
-
96
- wp_send_json_success( $json_data );
97
-
98
- }
99
 
 
 
100
  } // class
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /**
6
  * Dropin Name: New Items Notifier
9
  * Author: Pär Thernström
10
  */
11
 
12
+ class SimpleHistoryNewRowsNotifier
13
+ {
14
 
15
+ // Simple History instance
16
+ private $sh;
17
 
18
+ // How often we should check for new rows, in ms
19
+ private $interval = 10000;
20
 
21
+ function __construct($sh)
22
+ {
23
 
24
+ $this->sh = $sh;
25
 
26
+ // How often the script checks for new rows
27
+ $this->interval = (int) apply_filters('SimpleHistoryNewRowsNotifier/interval', $this->interval);
28
 
29
+ add_action('wp_ajax_SimpleHistoryNewRowsNotifier', array( $this, 'ajax' ));
30
+ add_action('simple_history/enqueue_admin_scripts', array( $this, 'enqueue_admin_scripts' ));
31
+ }
32
 
33
+ public function enqueue_admin_scripts()
34
+ {
35
 
36
+ $file_url = plugin_dir_url(__FILE__);
37
 
38
+ wp_enqueue_script('simple_history_NewRowsNotifierDropin', $file_url . 'SimpleHistoryNewRowsNotifierDropin.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION, true);
39
 
40
+ $arr_localize_data = array(
41
+ 'interval' => $this->interval,
42
+ 'errorCheck' => _x('An error occured while checking for new events', 'New rows notifier: error while checking for new rows', 'simple-history'),
43
+ );
44
 
45
+ wp_localize_script('simple_history_NewRowsNotifierDropin', 'simple_history_NewRowsNotifierDropin', $arr_localize_data);
 
 
 
46
 
47
+ wp_enqueue_style('simple_history_NewRowsNotifierDropin', $file_url . 'SimpleHistoryNewRowsNotifierDropin.css', null, SIMPLE_HISTORY_VERSION);
48
+ }
49
 
50
+ public function ajax()
51
+ {
52
 
53
+ $apiArgs = isset($_GET['apiArgs']) ? $_GET['apiArgs'] : array();
54
 
55
+ if (! $apiArgs) {
56
+ wp_send_json_error(array(
57
+ 'error' => 'MISSING_APIARGS',
58
+ ));
59
+ }
60
 
61
+ if (empty($apiArgs['since_id']) || ! is_numeric($apiArgs['since_id'])) {
62
+ wp_send_json_error(array(
63
+ 'error' => 'MISSING_SINCE_ID',
64
+ ));
65
+ }
66
 
67
+ // User must have capability to view the history page
68
+ if (! current_user_can($this->sh->get_view_history_capability())) {
69
+ wp_send_json_error(array(
70
+ 'error' => 'CAPABILITY_ERROR',
71
+ ));
72
+ }
73
 
74
+ // $since_id = isset( $_GET["since_id"] ) ? absint($_GET["since_id"]) : null;
75
+ $logQueryArgs = $apiArgs;
 
 
 
76
 
77
+ $logQuery = new SimpleHistoryLogQuery();
78
+ $answer = $logQuery->query($logQueryArgs);
 
 
 
 
79
 
80
+ // Use our own response array instead of $answer to keep size down
81
+ $json_data = array();
82
 
83
+ $numNewRows = isset($answer['total_row_count']) ? $answer['total_row_count'] : 0;
84
+ $json_data['num_new_rows'] = $numNewRows;
85
+ $json_data['num_mysql_queries'] = get_num_queries();
86
 
87
+ if ($numNewRows) {
88
+ // We have new rows
89
+ // Append strings
90
+ $textRowsFound = sprintf(_n('1 new event', '%d new events', $numNewRows, 'simple-history'), $numNewRows);
91
+ $json_data['strings'] = array(
92
+ 'newRowsFound' => $textRowsFound,
93
+ );
94
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
+ wp_send_json_success($json_data);
97
+ }
98
  } // class
dropins/SimpleHistoryPluginPatchesDropin.php CHANGED
@@ -1,5 +1,5 @@
1
  <?php
2
- defined( 'ABSPATH' ) or die();
3
 
4
  /*
5
  Dropin Name: Plugin Patches
@@ -8,163 +8,163 @@ Dropin URI: http://simple-history.com/
8
  Author: Pär Thernström
9
  */
10
 
11
- class SimpleHistoryPluginPatchesDropin {
12
-
13
- private $sh;
14
-
15
- function __construct( $sh ) {
16
-
17
- $this->sh = $sh;
18
-
19
- $this->patch_captcha_on_login();
20
-
21
- add_filter(
22
- 'simple_history/post_logger/skip_posttypes',
23
- array( $this, 'woocommerce_skip_scheduled_actions_posttype' )
24
- );
25
- }
26
-
27
- /**
28
- * Skip logging of WooCommerce scheduled actions/cron related things,
29
- * stored in the scheduled-action"post type. If not disabled the log can be filled with
30
- * a large amount of actions for this postype.
31
- *
32
- * @since 2.3
33
- */
34
- public function woocommerce_skip_scheduled_actions_posttype( $skip_posttypes ) {
35
- $skip_posttypes[] = 'scheduled-action';
36
- return $skip_posttypes;
37
- }
38
-
39
- /**
40
- * Captcha on Login
41
- *
42
- * Calls wp_logut() wrongly when
43
- * - a user IP is blocked
44
- * - when max num of tries is reached
45
- * - or when the capcha is not correct
46
- *
47
- * So the event logged will be logged_out but should be user_login_failed or user_unknown_login_failed.
48
- * Wrong events logged reported here:
49
- * https://wordpress.org/support/topic/many-unknown-logged-out-entries
50
- *
51
- * Plugin also gives lots of errors, reported by me here:
52
- * https://wordpress.org/support/topic/errors-has_cap-deprecated-strict-standards-warning
53
- */
54
- function patch_captcha_on_login() {
55
-
56
- add_action( 'simple_history/log/do_log', array( $this, 'patch_captcha_on_login_on_log' ), 10, 5 );
57
-
58
- }
59
-
60
- // Detect that this log message is being called from Captha on login
61
- // and that the message is "user_logged_out"
62
- function patch_captcha_on_login_on_log( $doLog, $level = null, $message = null, $context = null, $loggerInstance = null ) {
63
-
64
- if ( empty( $context ) || ! isset( $context['_message_key'] ) || 'user_logged_out' != $context['_message_key'] ) {
65
- // Message key did not exist or was not "user_logged_out"
66
- return $doLog;
67
- }
68
-
69
- // 22 nov 2015: disabled this check beacuse for example robots/scripts don't pass all args
70
- // instead they only post "log" and "pwd"
71
- // codiga is the input with the captcha
72
- /*
73
- if ( ! isset( $_POST["log"], $_POST["pwd"], $_POST["wp-submit"], $_POST["codigo"] ) ) {
74
- // All needed post variables was not set
75
- return $doLog;
76
- }
77
- */
78
-
79
- // The Captcha on login uses a class called 'Anderson_Makiyama_Captcha_On_Login'
80
- // and also a global variable called $global $anderson_makiyama
81
- global $anderson_makiyama;
82
- if ( ! class_exists( 'Anderson_Makiyama_Captcha_On_Login' ) || ! isset( $anderson_makiyama ) ) {
83
- return $doLog;
84
- }
85
-
86
- // We must come from wp-login
87
- // Disabled 22 nov 2015 because robots/scripts dont send referer
88
- /*
89
- $wp_referer = wp_get_referer();
90
- if ( ! $wp_referer || ! "wp-login.php" == basename( $wp_referer ) ) {
91
- return $doLog;
92
- }
93
- */
94
-
95
- if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
96
- return $doLog;
97
- }
98
-
99
- // File must be wp-login.php (can it even be another?)
100
- $request_uri = basename( wp_unslash( $_SERVER['REQUEST_URI'] ) );
101
- if ( 'wp-login.php' !== $request_uri ) {
102
- return $doLog;
103
- }
104
-
105
- $anderson_makiyama_indice = Anderson_Makiyama_Captcha_On_Login::PLUGIN_ID;
106
- $capcha_on_login_class_name = $anderson_makiyama[ $anderson_makiyama_indice ]::CLASS_NAME;
107
-
108
- $capcha_on_login_options = (array) get_option( $capcha_on_login_class_name . '_options', array() );
109
- $last_100_logins = isset( $capcha_on_login_options['last_100_logins'] ) ? (array) $capcha_on_login_options['last_100_logins'] : array();
110
- $last_100_logins = array_reverse( $last_100_logins );
111
-
112
- // Possible messages
113
- // - Failed: IP already blocked
114
- // - Failed: exceeded max number of tries
115
- // - Failed: image code did not match
116
- // - Failed: Login or Password did not match
117
- // - Success
118
- $last_login_status = isset( $last_100_logins[0][2] ) ? $last_100_logins[0][2] : '';
119
-
120
- // If we get here we're pretty sure we come from Captcha on login
121
- // and that we should cancel the wp_logout message and log an failed login instead
122
- // Get the user logger
123
- $userLogger = $this->sh->getInstantiatedLoggerBySlug( 'SimpleUserLogger' );
124
-
125
- if ( ! $userLogger ) {
126
- return $doLog;
127
- }
128
-
129
- // $userLogger->warningMessage("user_unknown_login_failed", $context);
130
- // Same context as in SimpleUserLogger
131
- $context = array(
132
- '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
133
- 'server_http_user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : null,
134
- '_occasionsID' => 'SimpleUserLogger' . '/failed_user_login',
135
- 'patch_using_patch' => true,
136
- 'patch_name' => 'captcha_on_login',
137
- );
138
-
139
- // Append capcha message
140
- if ( $last_login_status ) {
141
- $context['patch_last_login_status'] = $last_login_status;
142
- }
143
-
144
- // Get user id and email and login
145
- // Not passed to filter, but we have it in $_POST
146
- $login_username = isset( $_POST['log'] ) ? $_POST['log'] : null;
147
-
148
- if ( $login_username ) {
149
-
150
- $context['login_user_login'] = $login_username;
151
-
152
- $user = get_user_by( 'login', $login_username );
153
-
154
- if ( is_a( $user, 'WP_User' ) ) {
155
-
156
- $context['login_user_id'] = $user->ID;
157
- $context['login_user_email'] = $user->user_email;
158
-
159
- }
160
- }
161
-
162
- $userLogger->warningMessage( 'user_login_failed', $context );
163
-
164
- // Cancel original log event
165
- $doLog = false;
166
-
167
- return $doLog;
168
-
169
- }
170
  } // end class
1
  <?php
2
+ defined('ABSPATH') or die();
3
 
4
  /*
5
  Dropin Name: Plugin Patches
8
  Author: Pär Thernström
9
  */
10
 
11
+ class SimpleHistoryPluginPatchesDropin
12
+ {
13
+
14
+ private $sh;
15
+
16
+ function __construct($sh)
17
+ {
18
+
19
+ $this->sh = $sh;
20
+
21
+ $this->patch_captcha_on_login();
22
+
23
+ add_filter(
24
+ 'simple_history/post_logger/skip_posttypes',
25
+ array( $this, 'woocommerce_skip_scheduled_actions_posttype' )
26
+ );
27
+ }
28
+
29
+ /**
30
+ * Skip logging of WooCommerce scheduled actions/cron related things,
31
+ * stored in the scheduled-action"post type. If not disabled the log can be filled with
32
+ * a large amount of actions for this postype.
33
+ *
34
+ * @since 2.3
35
+ */
36
+ public function woocommerce_skip_scheduled_actions_posttype($skip_posttypes)
37
+ {
38
+ $skip_posttypes[] = 'scheduled-action';
39
+ return $skip_posttypes;
40
+ }
41
+
42
+ /**
43
+ * Captcha on Login
44
+ *
45
+ * Calls wp_logut() wrongly when
46
+ * - a user IP is blocked
47
+ * - when max num of tries is reached
48
+ * - or when the capcha is not correct
49
+ *
50
+ * So the event logged will be logged_out but should be user_login_failed or user_unknown_login_failed.
51
+ * Wrong events logged reported here:
52
+ * https://wordpress.org/support/topic/many-unknown-logged-out-entries
53
+ *
54
+ * Plugin also gives lots of errors, reported by me here:
55
+ * https://wordpress.org/support/topic/errors-has_cap-deprecated-strict-standards-warning
56
+ */
57
+ function patch_captcha_on_login()
58
+ {
59
+
60
+ add_action('simple_history/log/do_log', array( $this, 'patch_captcha_on_login_on_log' ), 10, 5);
61
+ }
62
+
63
+ // Detect that this log message is being called from Captha on login
64
+ // and that the message is "user_logged_out"
65
+ function patch_captcha_on_login_on_log($doLog, $level = null, $message = null, $context = null, $loggerInstance = null)
66
+ {
67
+
68
+ if (empty($context) || ! isset($context['_message_key']) || 'user_logged_out' != $context['_message_key']) {
69
+ // Message key did not exist or was not "user_logged_out"
70
+ return $doLog;
71
+ }
72
+
73
+ // 22 nov 2015: disabled this check beacuse for example robots/scripts don't pass all args
74
+ // instead they only post "log" and "pwd"
75
+ // codiga is the input with the captcha
76
+ /*
77
+ if ( ! isset( $_POST["log"], $_POST["pwd"], $_POST["wp-submit"], $_POST["codigo"] ) ) {
78
+ // All needed post variables was not set
79
+ return $doLog;
80
+ }
81
+ */
82
+
83
+ // The Captcha on login uses a class called 'Anderson_Makiyama_Captcha_On_Login'
84
+ // and also a global variable called $global $anderson_makiyama
85
+ global $anderson_makiyama;
86
+ if (! class_exists('Anderson_Makiyama_Captcha_On_Login') || ! isset($anderson_makiyama)) {
87
+ return $doLog;
88
+ }
89
+
90
+ // We must come from wp-login
91
+ // Disabled 22 nov 2015 because robots/scripts dont send referer
92
+ /*
93
+ $wp_referer = wp_get_referer();
94
+ if ( ! $wp_referer || ! "wp-login.php" == basename( $wp_referer ) ) {
95
+ return $doLog;
96
+ }
97
+ */
98
+
99
+ if (! isset($_SERVER['REQUEST_URI'])) {
100
+ return $doLog;
101
+ }
102
+
103
+ // File must be wp-login.php (can it even be another?)
104
+ $request_uri = basename(wp_unslash($_SERVER['REQUEST_URI']));
105
+ if ('wp-login.php' !== $request_uri) {
106
+ return $doLog;
107
+ }
108
+
109
+ $anderson_makiyama_indice = Anderson_Makiyama_Captcha_On_Login::PLUGIN_ID;
110
+ $capcha_on_login_class_name = $anderson_makiyama[ $anderson_makiyama_indice ]::CLASS_NAME;
111
+
112
+ $capcha_on_login_options = (array) get_option($capcha_on_login_class_name . '_options', array());
113
+ $last_100_logins = isset($capcha_on_login_options['last_100_logins']) ? (array) $capcha_on_login_options['last_100_logins'] : array();
114
+ $last_100_logins = array_reverse($last_100_logins);
115
+
116
+ // Possible messages
117
+ // - Failed: IP already blocked
118
+ // - Failed: exceeded max number of tries
119
+ // - Failed: image code did not match
120
+ // - Failed: Login or Password did not match
121
+ // - Success
122
+ $last_login_status = isset($last_100_logins[0][2]) ? $last_100_logins[0][2] : '';
123
+
124
+ // If we get here we're pretty sure we come from Captcha on login
125
+ // and that we should cancel the wp_logout message and log an failed login instead
126
+ // Get the user logger
127
+ $userLogger = $this->sh->getInstantiatedLoggerBySlug('SimpleUserLogger');
128
+
129
+ if (! $userLogger) {
130
+ return $doLog;
131
+ }
132
+
133
+ // $userLogger->warningMessage("user_unknown_login_failed", $context);
134
+ // Same context as in SimpleUserLogger
135
+ $context = array(
136
+ '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
137
+ 'server_http_user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null,
138
+ '_occasionsID' => 'SimpleUserLogger' . '/failed_user_login',
139
+ 'patch_using_patch' => true,
140
+ 'patch_name' => 'captcha_on_login',
141
+ );
142
+
143
+ // Append capcha message
144
+ if ($last_login_status) {
145
+ $context['patch_last_login_status'] = $last_login_status;
146
+ }
147
+
148
+ // Get user id and email and login
149
+ // Not passed to filter, but we have it in $_POST
150
+ $login_username = isset($_POST['log']) ? $_POST['log'] : null;
151
+
152
+ if ($login_username) {
153
+ $context['login_user_login'] = $login_username;
154
+
155
+ $user = get_user_by('login', $login_username);
156
+
157
+ if (is_a($user, 'WP_User')) {
158
+ $context['login_user_id'] = $user->ID;
159
+ $context['login_user_email'] = $user->user_email;
160
+ }
161
+ }
162
+
163
+ $userLogger->warningMessage('user_login_failed', $context);
164
+
165
+ // Cancel original log event
166
+ $doLog = false;
167
+
168
+ return $doLog;
169
+ }
170
  } // end class
dropins/SimpleHistoryRSSDropin.php CHANGED
@@ -10,388 +10,402 @@ Author: Pär Thernström
10
  /**
11
  * Simple History RSS Feed drop-in
12
  */
13
- class SimpleHistoryRSSDropin {
14
-
15
-
16
- public function __construct( $sh ) {
17
-
18
- $this->sh = $sh;
19
-
20
- if ( ! function_exists( 'get_editable_roles' ) ) {
21
- require_once( ABSPATH . '/wp-admin/includes/user.php' );
22
- }
23
-
24
- // Check the status of the RSS feed
25
- $this->isRssEnabled();
26
-
27
- // Generate a rss secret, if it does not exist
28
- if ( ! get_option( 'simple_history_rss_secret' ) ) {
29
- $this->updateRssSecret();
30
- }
31
-
32
- add_action( 'init', array( $this, 'checkForRssFeedRequest' ) );
33
-
34
- // Add settings with prio 11 so it' added after the main Simple History settings
35
- add_action( 'admin_menu', array( $this, 'addSettings' ), 11 );
36
- }
37
-
38
- /**
39
- * Add settings for the RSS feed
40
- * + also regenerates the secret if requested
41
- */
42
- public function addSettings() {
43
-
44
- // we register a setting to keep track of the RSS feed status (enabled/disabled)
45
- register_setting(
46
- SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP,
47
- 'simple_history_enable_rss_feed',
48
- array(
49
- $this,
50
- 'public updateRssStatus',
51
- )
52
- );
53
- /**
54
- * Start new section for RSS feed
55
- */
56
- $settings_section_rss_id = 'simple_history_settings_section_rss';
57
-
58
- add_settings_section(
59
- $settings_section_rss_id,
60
- _x( 'RSS feed', 'rss settings headline', 'simple-history' ), // No title __("General", "simple-history"),
61
- array( $this, 'settingsSectionOutput' ),
62
- SimpleHistory::SETTINGS_MENU_SLUG // same slug as for options menu page
63
- );
64
-
65
- // Enable/Disabled RSS feed
66
- add_settings_field(
67
- 'simple_history_enable_rss_feed',
68
- __( 'Enable', 'simple-history' ),
69
- array( $this, 'settingsFieldRssEnable' ),
70
- SimpleHistory::SETTINGS_MENU_SLUG,
71
- $settings_section_rss_id
72
- );
73
-
74
- // if RSS is activated we display other fields
75
- if ( $this->isRssEnabled() ) {
76
- // RSS address
77
- add_settings_field(
78
- 'simple_history_rss_feed',
79
- __( 'Address', 'simple-history' ),
80
- array( $this, 'settingsFieldRss' ),
81
- SimpleHistory::SETTINGS_MENU_SLUG,
82
- $settings_section_rss_id
83
- );
84
-
85
- // Regnerate address
86
- add_settings_field(
87
- 'simple_history_rss_feed_regenerate_secret',
88
- __( 'Regenerate', 'simple-history' ),
89
- array( $this, 'settingsFieldRssRegenerate' ),
90
- SimpleHistory::SETTINGS_MENU_SLUG,
91
- $settings_section_rss_id
92
- );
93
- }
94
-
95
- // Create new RSS secret
96
- $create_new_secret = false;
97
- $create_secret_nonce_name = 'simple_history_rss_secret_regenerate_nonce';
98
- $createNonceOk = isset( $_GET[ $create_secret_nonce_name ] ) && wp_verify_nonce( $_GET[ $create_secret_nonce_name ], 'simple_history_rss_update_secret' );
99
-
100
- if ( $createNonceOk ) {
101
- $create_new_secret = true;
102
- $this->updateRssSecret();
103
-
104
- // Add updated-message and store in transient and then redirect
105
- // This is the way options.php does it.
106
- $msg = __( 'Created new secret RSS address', 'simple-history' );
107
- add_settings_error( 'simple_history_rss_feed_regenerate_secret', 'simple_history_rss_feed_regenerate_secret', $msg, 'updated' );
108
- set_transient( 'settings_errors', get_settings_errors(), 30 );
109
-
110
- $goback = esc_url_raw( add_query_arg( 'settings-updated', 'true', wp_get_referer() ) );
111
- wp_redirect( $goback );
112
- exit;
113
- }
114
- } // settings
115
-
116
- /**
117
- * Check if RSS feed is enabled or disabled
118
- */
119
- public function isRssEnabled() {
120
-
121
- // User has never used the plugin we disable RSS feed
122
- if ( get_option( 'simple_history_rss_secret' ) === false && get_option( 'simple_history_enable_rss_feed' ) === false ) {
123
- // We disable RSS by default, we use 0/1 to prevent fake disabled with bools from functions returning false for unset
124
- update_option( 'simple_history_enable_rss_feed', '0' );
125
- } elseif ( get_option( 'simple_history_enable_rss_feed' ) === false ) {
126
- // User was using the plugin before RSS feed became disabled by default
127
- // We activate RSS to prevent a "breaking change"
128
- update_option( 'simple_history_enable_rss_feed', '1' );
129
- return true;
130
- } elseif ( get_option( 'simple_history_enable_rss_feed' ) === '1' ) {
131
- return true;
132
- }
133
-
134
- return false;
135
- }
136
-
137
- /**
138
- * Output for settings field that show current RSS address
139
- */
140
- public function settingsFieldRssEnable() {
141
- ?>
142
- <input value="1" type="checkbox" id="simple_history_enable_rss_feed" name="simple_history_enable_rss_feed" <?php checked( $this->isRssEnabled(), 1 ); ?> />
143
- <label for="simple_history_enable_rss_feed"><?php _e( 'Enable RSS feed', 'simple-history' ) ?></label>
144
- <?php
145
- }
146
-
147
- /**
148
- * Sanitize RSS enabled/disabled status on update settings
149
- */
150
- public function updateRssStatus( $field ) {
151
-
152
- if ( $field === '1' ) {
153
- return '1';
154
- }
155
-
156
- return '0';
157
- }
158
-
159
-
160
- /**
161
- * Check if current request is a request for the RSS feed
162
- */
163
- public function checkForRssFeedRequest() {
164
- // check for RSS
165
- // don't know if this is the right way to do this, but it seems to work!
166
- if ( isset( $_GET['simple_history_get_rss'] ) ) {
167
- $this->outputRss();
168
- exit;
169
- }
170
- }
171
-
172
- /**
173
- * Modify capability check so all users reading rss feed (logged in or not) can read all loggers
174
- */
175
- public function onCanReadSingleLogger( $user_can_read_logger, $logger_instance, $user_id ) {
176
- $user_can_read_logger = true;
177
-
178
- return $user_can_read_logger;
179
- }
180
-
181
- /**
182
- * Output RSS
183
- */
184
- public function outputRss() {
185
-
186
- $rss_secret_option = get_option( 'simple_history_rss_secret' );
187
- $rss_secret_get = isset( $_GET['rss_secret'] ) ? $_GET['rss_secret'] : '';
188
-
189
- if ( empty( $rss_secret_option ) || empty( $rss_secret_get ) ) {
190
- die();
191
- }
192
-
193
- $rss_show = true;
194
- $rss_show = apply_filters( 'simple_history/rss_feed_show', $rss_show );
195
- if ( ! $rss_show || ! $this->isRssEnabled() ) {
196
- wp_die( 'Nothing here.' );
197
- }
198
-
199
- header( 'Content-Type: text/xml; charset=utf-8' );
200
- echo '<?xml version="1.0" encoding="UTF-8"?>';
201
- $self_link = $this->getRssAddress();
202
-
203
- if ( $rss_secret_option === $rss_secret_get ) {
204
- ?>
205
- <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
206
- <channel>
207
- <title><![CDATA[<?php printf( __( 'History for %s', 'simple-history' ), get_bloginfo( 'name' ) ) ?>]]></title>
208
- <description><![CDATA[<?php printf( __( 'WordPress History for %s', 'simple-history' ), get_bloginfo( 'name' ) ) ?>]]></description>
209
- <link><?php echo get_bloginfo( 'url' ) ?></link>
210
- <atom:link href="<?php echo $self_link; ?>" rel="self" type="application/atom+xml" />
211
- <?php
212
-
213
- // Override capability check: if you have a valid rss_secret_key you can read it all
214
- $action_tag = 'simple_history/loggers_user_can_read/can_read_single_logger';
215
- add_action( $action_tag, array( $this, 'onCanReadSingleLogger' ), 10, 3 );
216
-
217
- // Modify header time output so it does not show relative date or time ago-format
218
- // Because we don't know when a user reads the RSS feed, time ago format may be very inaccurate
219
- add_action( 'simple_history/header_just_now_max_time', '__return_zero' );
220
- add_action( 'simple_history/header_time_ago_max_time', '__return_zero' );
221
-
222
- // Get log rows
223
- $args = array(
224
- 'posts_per_page' => 10,
225
- );
226
-
227
- $args = apply_filters( 'simple_history/rss_feed_args', $args );
228
-
229
- $logQuery = new SimpleHistoryLogQuery();
230
- $queryResults = $logQuery->query( $args );
231
-
232
- // Remove capability override after query is done
233
- // remove_action( $action_tag, array($this, "onCanReadSingleLogger") );
234
- foreach ( $queryResults['log_rows'] as $row ) {
235
- $header_output = $this->sh->getLogRowHeaderOutput( $row );
236
- $text_output = $this->sh->getLogRowPlainTextOutput( $row );
237
- $details_output = $this->sh->getLogRowDetailsOutput( $row );
238
-
239
- // http://cyber.law.harvard.edu/rss/rss.html#ltguidgtSubelementOfLtitemgt
240
- // $item_guid = home_url() . "?SimpleHistoryGuid=" . $row->id;
241
- $item_guid = esc_url( add_query_arg( 'SimpleHistoryGuid', $row->id, home_url() ) );
242
- $item_link = esc_url( add_query_arg( 'SimpleHistoryGuid', $row->id, home_url() ) );
243
-
244
- /**
245
- * Filter the guid/link URL used in RSS feed.
246
- * Link will be esc_url'ed by simple history, so no need to do that in your filter
247
- *
248
- * @since 2.0.23
249
- *
250
- * @param string $item_guid link.
251
- * @param array $row
252
- */
253
- $item_link = apply_filters( 'simple_history/rss_item_link', $item_link, $row );
254
- $item_link = esc_url( $item_link );
255
-
256
- $item_title = sprintf(
257
- '%2$s',
258
- $this->sh->getLogLevelTranslated( $row->level ),
259
- wp_kses( $text_output, array() )
260
- );
261
-
262
- $level_output = sprintf( __( 'Severity level: %1$s' ), $this->sh->getLogLevelTranslated( $row->level ) );
263
-
264
- ?>
265
- <item>
266
- <title><![CDATA[<?php echo $item_title; ?>]]></title>
267
- <description><![CDATA[
268
- <p><?php echo $header_output ?></p>
269
- <p><?php echo $text_output ?></p>
270
- <div><?php echo $details_output ?></div>
271
- <p><?php echo $level_output ?></p>
272
- <?php
273
- $occasions = $row->subsequentOccasions - 1;
274
- if ( $occasions ) {
275
- printf(
276
- _n( '+%1$s occasion', '+%1$s occasions', $occasions, 'simple-history' ),
277
- $occasions
278
- );
279
- }
280
- ?>
281
- ]]></description>
282
- <?php
283
- // author must be email to validate, but the field is optional, so we skip it
284
- /* <author><?php echo $row->initiator ?></author> */
285
- ?>
286
- <pubDate><?php echo date( 'D, d M Y H:i:s', strtotime( $row->date ) ) ?> GMT</pubDate>
287
- <guid isPermaLink="false"><![CDATA[<?php echo $item_guid ?>]]></guid>
288
- <link><![CDATA[<?php echo $item_link ?>]]></link>
289
- </item>
290
- <?php
291
- } // End foreach().
292
-
293
- ?>
294
- </channel>
295
- </rss>
296
- <?php
297
- } else {
298
- // RSS secret was not ok
299
- ?>
300
- <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
301
- <channel>
302
- <title><?php printf( __( 'History for %s', 'simple-history' ), get_bloginfo( 'name' ) ) ?></title>
303
- <description><?php printf( __( 'WordPress History for %s', 'simple-history' ), get_bloginfo( 'name' ) ) ?></description>
304
- <link><?php echo home_url() ?></link>
305
- <item>
306
- <title><?php _e( 'Wrong RSS secret', 'simple-history' )?></title>
307
- <description><?php _e( 'Your RSS secret for Simple History RSS feed is wrong. Please see WordPress settings for current link to the RSS feed.', 'simple-history' )?></description>
308
- <pubDate><?php echo date( 'D, d M Y H:i:s', time() ) ?> GMT</pubDate>
309
- <guid><?php echo home_url() . '?SimpleHistoryGuid=wrong-secret' ?></guid>
310
- </item>
311
- </channel>
312
- </rss>
313
- <?php
314
- }// End if().
315
- } // rss
316
-
317
- /**
318
- * Create a new RSS secret
319
- *
320
- * @return string new secret
321
- */
322
- public function updateRssSecret() {
323
-
324
- $rss_secret = '';
325
-
326
- for ( $i = 0; $i < 20; $i++ ) {
327
- $rss_secret .= chr( rand( 97, 122 ) );
328
- }
329
-
330
- update_option( 'simple_history_rss_secret', $rss_secret );
331
-
332
- return $rss_secret;
333
- }
334
-
335
- /**
336
- * Output for settings field that show current RSS address
337
- */
338
- public function settingsFieldRss() {
339
-
340
- $rss_address = $this->getRssAddress();
341
-
342
- echo "<p><code><a href='$rss_address'>$rss_address</a></code></p>";
343
- }
344
-
345
- /**
346
- * Output for settings field that regenerates the RSS adress/secret
347
- */
348
- public function settingsFieldRssRegenerate() {
349
-
350
- $update_link = esc_url( add_query_arg( '', '' ) );
351
- $update_link = wp_nonce_url( $update_link, 'simple_history_rss_update_secret', 'simple_history_rss_secret_regenerate_nonce' );
352
-
353
- echo '<p>';
354
- _e( 'You can generate a new address for the RSS feed. This is useful if you think that the address has fallen into the wrong hands.', 'simple-history' );
355
- echo '</p>';
356
-
357
- echo '<p>';
358
- printf(
359
- '<a class="button" href="%1$s">%2$s</a>',
360
- $update_link, // 1
361
- __( 'Generate new address', 'simple-history' ) // 2
362
- );
363
-
364
- echo '</p>';
365
- }
366
-
367
- /**
368
- * Get the URL to the RSS feed
369
- *
370
- * @return string URL
371
- */
372
- public function getRssAddress() {
373
-
374
- $rss_secret = get_option( 'simple_history_rss_secret' );
375
- $rss_address = add_query_arg(
376
- array(
377
- 'simple_history_get_rss' => '1',
378
- 'rss_secret' => $rss_secret,
379
- ),
380
- get_bloginfo( 'url' ) . '/'
381
- );
382
- $rss_address = esc_url( $rss_address );
383
- // $rss_address = htmlspecialchars($rss_address, ENT_COMPAT, "UTF-8");
384
- return $rss_address;
385
- }
386
-
387
- /**
388
- * Content for section intro. Leave it be, even if empty.
389
- * Called from add_sections_setting.
390
- */
391
- public function settingsSectionOutput() {
392
-
393
- echo '<p>';
394
- _e( 'Simple History has a RSS feed which you can subscribe to and receive log updates. Make sure you only share the feed with people you trust, since it can contain sensitive or confidential information.', 'simple-history' );
395
- echo '</p>';
396
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  } // end rss class
10
  /**
11
  * Simple History RSS Feed drop-in
12
  */
13
+ class SimpleHistoryRSSDropin
14
+ {
15
+
16
+
17
+ public function __construct($sh)
18
+ {
19
+
20
+ $this->sh = $sh;
21
+
22
+ if (! function_exists('get_editable_roles')) {
23
+ require_once(ABSPATH . '/wp-admin/includes/user.php');
24
+ }
25
+
26
+ // Check the status of the RSS feed
27
+ $this->isRssEnabled();
28
+
29
+ // Generate a rss secret, if it does not exist
30
+ if (! get_option('simple_history_rss_secret')) {
31
+ $this->updateRssSecret();
32
+ }
33
+
34
+ add_action('init', array( $this, 'checkForRssFeedRequest' ));
35
+
36
+ // Add settings with prio 11 so it' added after the main Simple History settings
37
+ add_action('admin_menu', array( $this, 'addSettings' ), 11);
38
+ }
39
+
40
+ /**
41
+ * Add settings for the RSS feed
42
+ * + also regenerates the secret if requested
43
+ */
44
+ public function addSettings()
45
+ {
46
+
47
+ // we register a setting to keep track of the RSS feed status (enabled/disabled)
48
+ register_setting(
49
+ SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP,
50
+ 'simple_history_enable_rss_feed',
51
+ array(
52
+ $this,
53
+ 'public updateRssStatus',
54
+ )
55
+ );
56
+ /**
57
+ * Start new section for RSS feed
58
+ */
59
+ $settings_section_rss_id = 'simple_history_settings_section_rss';
60
+
61
+ add_settings_section(
62
+ $settings_section_rss_id,
63
+ _x('RSS feed', 'rss settings headline', 'simple-history'), // No title __("General", "simple-history"),
64
+ array( $this, 'settingsSectionOutput' ),
65
+ SimpleHistory::SETTINGS_MENU_SLUG // same slug as for options menu page
66
+ );
67
+
68
+ // Enable/Disabled RSS feed
69
+ add_settings_field(
70
+ 'simple_history_enable_rss_feed',
71
+ __('Enable', 'simple-history'),
72
+ array( $this, 'settingsFieldRssEnable' ),
73
+ SimpleHistory::SETTINGS_MENU_SLUG,
74
+ $settings_section_rss_id
75
+ );
76
+
77
+ // if RSS is activated we display other fields
78
+ if ($this->isRssEnabled()) {
79
+ // RSS address
80
+ add_settings_field(
81
+ 'simple_history_rss_feed',
82
+ __('Address', 'simple-history'),
83
+ array( $this, 'settingsFieldRss' ),
84
+ SimpleHistory::SETTINGS_MENU_SLUG,
85
+ $settings_section_rss_id
86
+ );
87
+
88
+ // Regnerate address
89
+ add_settings_field(
90
+ 'simple_history_rss_feed_regenerate_secret',
91
+ __('Regenerate', 'simple-history'),
92
+ array( $this, 'settingsFieldRssRegenerate' ),
93
+ SimpleHistory::SETTINGS_MENU_SLUG,
94
+ $settings_section_rss_id
95
+ );
96
+ }
97
+
98
+ // Create new RSS secret
99
+ $create_new_secret = false;
100
+ $create_secret_nonce_name = 'simple_history_rss_secret_regenerate_nonce';
101
+ $createNonceOk = isset($_GET[ $create_secret_nonce_name ]) && wp_verify_nonce($_GET[ $create_secret_nonce_name ], 'simple_history_rss_update_secret');
102
+
103
+ if ($createNonceOk) {
104
+ $create_new_secret = true;
105
+ $this->updateRssSecret();
106
+
107
+ // Add updated-message and store in transient and then redirect
108
+ // This is the way options.php does it.
109
+ $msg = __('Created new secret RSS address', 'simple-history');
110
+ add_settings_error('simple_history_rss_feed_regenerate_secret', 'simple_history_rss_feed_regenerate_secret', $msg, 'updated');
111
+ set_transient('settings_errors', get_settings_errors(), 30);
112
+
113
+ $goback = esc_url_raw(add_query_arg('settings-updated', 'true', wp_get_referer()));
114
+ wp_redirect($goback);
115
+ exit;
116
+ }
117
+ } // settings
118
+
119
+ /**
120
+ * Check if RSS feed is enabled or disabled
121
+ */
122
+ public function isRssEnabled()
123
+ {
124
+
125
+ // User has never used the plugin we disable RSS feed
126
+ if (get_option('simple_history_rss_secret') === false && get_option('simple_history_enable_rss_feed') === false) {
127
+ // We disable RSS by default, we use 0/1 to prevent fake disabled with bools from functions returning false for unset
128
+ update_option('simple_history_enable_rss_feed', '0');
129
+ } elseif (get_option('simple_history_enable_rss_feed') === false) {
130
+ // User was using the plugin before RSS feed became disabled by default
131
+ // We activate RSS to prevent a "breaking change"
132
+ update_option('simple_history_enable_rss_feed', '1');
133
+ return true;
134
+ } elseif (get_option('simple_history_enable_rss_feed') === '1') {
135
+ return true;
136
+ }
137
+
138
+ return false;
139
+ }
140
+
141
+ /**
142
+ * Output for settings field that show current RSS address
143
+ */
144
+ public function settingsFieldRssEnable()
145
+ {
146
+ ?>
147
+ <input value="1" type="checkbox" id="simple_history_enable_rss_feed" name="simple_history_enable_rss_feed" <?php checked($this->isRssEnabled(), 1); ?> />
148
+ <label for="simple_history_enable_rss_feed"><?php _e('Enable RSS feed', 'simple-history') ?></label>
149
+ <?php
150
+ }
151
+
152
+ /**
153
+ * Sanitize RSS enabled/disabled status on update settings
154
+ */
155
+ public function updateRssStatus($field)
156
+ {
157
+
158
+ if ($field === '1') {
159
+ return '1';
160
+ }
161
+
162
+ return '0';
163
+ }
164
+
165
+
166
+ /**
167
+ * Check if current request is a request for the RSS feed
168
+ */
169
+ public function checkForRssFeedRequest()
170
+ {
171
+ // check for RSS
172
+ // don't know if this is the right way to do this, but it seems to work!
173
+ if (isset($_GET['simple_history_get_rss'])) {
174
+ $this->outputRss();
175
+ exit;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Modify capability check so all users reading rss feed (logged in or not) can read all loggers
181
+ */
182
+ public function onCanReadSingleLogger($user_can_read_logger, $logger_instance, $user_id)
183
+ {
184
+ $user_can_read_logger = true;
185
+
186
+ return $user_can_read_logger;
187
+ }
188
+
189
+ /**
190
+ * Output RSS
191
+ */
192
+ public function outputRss()
193
+ {
194
+
195
+ $rss_secret_option = get_option('simple_history_rss_secret');
196
+ $rss_secret_get = isset($_GET['rss_secret']) ? $_GET['rss_secret'] : '';
197
+
198
+ if (empty($rss_secret_option) || empty($rss_secret_get)) {
199
+ die();
200
+ }
201
+
202
+ $rss_show = true;
203
+ $rss_show = apply_filters('simple_history/rss_feed_show', $rss_show);
204
+ if (! $rss_show || ! $this->isRssEnabled()) {
205
+ wp_die('Nothing here.');
206
+ }
207
+
208
+ header('Content-Type: text/xml; charset=utf-8');
209
+ echo '<?xml version="1.0" encoding="UTF-8"?>';
210
+ $self_link = $this->getRssAddress();
211
+
212
+ if ($rss_secret_option === $rss_secret_get) {
213
+ ?>
214
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
215
+ <channel>
216
+ <title><![CDATA[<?php printf(__('History for %s', 'simple-history'), get_bloginfo('name')) ?>]]></title>
217
+ <description><![CDATA[<?php printf(__('WordPress History for %s', 'simple-history'), get_bloginfo('name')) ?>]]></description>
218
+ <link><?php echo get_bloginfo('url') ?></link>
219
+ <atom:link href="<?php echo $self_link; ?>" rel="self" type="application/atom+xml" />
220
+ <?php
221
+
222
+ // Override capability check: if you have a valid rss_secret_key you can read it all
223
+ $action_tag = 'simple_history/loggers_user_can_read/can_read_single_logger';
224
+ add_action($action_tag, array( $this, 'onCanReadSingleLogger' ), 10, 3);
225
+
226
+ // Modify header time output so it does not show relative date or time ago-format
227
+ // Because we don't know when a user reads the RSS feed, time ago format may be very inaccurate
228
+ add_action('simple_history/header_just_now_max_time', '__return_zero');
229
+ add_action('simple_history/header_time_ago_max_time', '__return_zero');
230
+
231
+ // Get log rows
232
+ $args = array(
233
+ 'posts_per_page' => 10,
234
+ );
235
+
236
+ $args = apply_filters('simple_history/rss_feed_args', $args);
237
+
238
+ $logQuery = new SimpleHistoryLogQuery();
239
+ $queryResults = $logQuery->query($args);
240
+
241
+ // Remove capability override after query is done
242
+ // remove_action( $action_tag, array($this, "onCanReadSingleLogger") );
243
+ foreach ($queryResults['log_rows'] as $row) {
244
+ $header_output = $this->sh->getLogRowHeaderOutput($row);
245
+ $text_output = $this->sh->getLogRowPlainTextOutput($row);
246
+ $details_output = $this->sh->getLogRowDetailsOutput($row);
247
+
248
+ // http://cyber.law.harvard.edu/rss/rss.html#ltguidgtSubelementOfLtitemgt
249
+ // $item_guid = home_url() . "?SimpleHistoryGuid=" . $row->id;
250
+ $item_guid = esc_url(add_query_arg('SimpleHistoryGuid', $row->id, home_url()));
251
+ $item_link = esc_url(add_query_arg('SimpleHistoryGuid', $row->id, home_url()));
252
+
253
+ /**
254
+ * Filter the guid/link URL used in RSS feed.
255
+ * Link will be esc_url'ed by simple history, so no need to do that in your filter
256
+ *
257
+ * @since 2.0.23
258
+ *
259
+ * @param string $item_guid link.
260
+ * @param array $row
261
+ */
262
+ $item_link = apply_filters('simple_history/rss_item_link', $item_link, $row);
263
+ $item_link = esc_url($item_link);
264
+
265
+ $item_title = sprintf(
266
+ '%2$s',
267
+ $this->sh->getLogLevelTranslated($row->level),
268
+ wp_kses($text_output, array())
269
+ );
270
+
271
+ $level_output = sprintf(__('Severity level: %1$s'), $this->sh->getLogLevelTranslated($row->level));
272
+
273
+ ?>
274
+ <item>
275
+ <title><![CDATA[<?php echo $item_title; ?>]]></title>
276
+ <description><![CDATA[
277
+ <p><?php echo $header_output ?></p>
278
+ <p><?php echo $text_output ?></p>
279
+ <div><?php echo $details_output ?></div>
280
+ <p><?php echo $level_output ?></p>
281
+ <?php
282
+ $occasions = $row->subsequentOccasions - 1;
283
+ if ($occasions) {
284
+ printf(
285
+ _n('+%1$s occasion', '+%1$s occasions', $occasions, 'simple-history'),
286
+ $occasions
287
+ );
288
+ }
289
+ ?>
290
+ ]]></description>
291
+ <?php
292
+ // author must be email to validate, but the field is optional, so we skip it
293
+ /* <author><?php echo $row->initiator ?></author> */
294
+ ?>
295
+ <pubDate><?php echo date('D, d M Y H:i:s', strtotime($row->date)) ?> GMT</pubDate>
296
+ <guid isPermaLink="false"><![CDATA[<?php echo $item_guid ?>]]></guid>
297
+ <link><![CDATA[<?php echo $item_link ?>]]></link>
298
+ </item>
299
+ <?php
300
+ } // End foreach().
301
+
302
+ ?>
303
+ </channel>
304
+ </rss>
305
+ <?php
306
+ } else {
307
+ // RSS secret was not ok
308
+ ?>
309
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
310
+ <channel>
311
+ <title><?php printf(__('History for %s', 'simple-history'), get_bloginfo('name')) ?></title>
312
+ <description><?php printf(__('WordPress History for %s', 'simple-history'), get_bloginfo('name')) ?></description>
313
+ <link><?php echo home_url() ?></link>
314
+ <item>
315
+ <title><?php _e('Wrong RSS secret', 'simple-history')?></title>
316
+ <description><?php _e('Your RSS secret for Simple History RSS feed is wrong. Please see WordPress settings for current link to the RSS feed.', 'simple-history')?></description>
317
+ <pubDate><?php echo date('D, d M Y H:i:s', time()) ?> GMT</pubDate>
318
+ <guid><?php echo home_url() . '?SimpleHistoryGuid=wrong-secret' ?></guid>
319
+ </item>
320
+ </channel>
321
+ </rss>
322
+ <?php
323
+ }// End if().
324
+ } // rss
325
+
326
+ /**
327
+ * Create a new RSS secret
328
+ *
329
+ * @return string new secret
330
+ */
331
+ public function updateRssSecret()
332
+ {
333
+
334
+ $rss_secret = '';
335
+
336
+ for ($i = 0; $i < 20; $i++) {
337
+ $rss_secret .= chr(rand(97, 122));
338
+ }
339
+
340
+ update_option('simple_history_rss_secret', $rss_secret);
341
+
342
+ return $rss_secret;
343
+ }
344
+
345
+ /**
346
+ * Output for settings field that show current RSS address
347
+ */
348
+ public function settingsFieldRss()
349
+ {
350
+
351
+ $rss_address = $this->getRssAddress();
352
+
353
+ echo "<p><code><a href='$rss_address'>$rss_address</a></code></p>";
354
+ }
355
+
356
+ /**
357
+ * Output for settings field that regenerates the RSS adress/secret
358
+ */
359
+ public function settingsFieldRssRegenerate()
360
+ {
361
+
362
+ $update_link = esc_url(add_query_arg('', ''));
363
+ $update_link = wp_nonce_url($update_link, 'simple_history_rss_update_secret', 'simple_history_rss_secret_regenerate_nonce');
364
+
365
+ echo '<p>';
366
+ _e('You can generate a new address for the RSS feed. This is useful if you think that the address has fallen into the wrong hands.', 'simple-history');
367
+ echo '</p>';
368
+
369
+ echo '<p>';
370
+ printf(
371
+ '<a class="button" href="%1$s">%2$s</a>',
372
+ $update_link, // 1
373
+ __('Generate new address', 'simple-history') // 2
374
+ );
375
+
376
+ echo '</p>';
377
+ }
378
+
379
+ /**
380
+ * Get the URL to the RSS feed
381
+ *
382
+ * @return string URL
383
+ */
384
+ public function getRssAddress()
385
+ {
386
+
387
+ $rss_secret = get_option('simple_history_rss_secret');
388
+ $rss_address = add_query_arg(
389
+ array(
390
+ 'simple_history_get_rss' => '1',
391
+ 'rss_secret' => $rss_secret,
392
+ ),
393
+ get_bloginfo('url') . '/'
394
+ );
395
+ $rss_address = esc_url($rss_address);
396
+ // $rss_address = htmlspecialchars($rss_address, ENT_COMPAT, "UTF-8");
397
+ return $rss_address;
398
+ }
399
+
400
+ /**
401
+ * Content for section intro. Leave it be, even if empty.
402
+ * Called from add_sections_setting.
403
+ */
404
+ public function settingsSectionOutput()
405
+ {
406
+
407
+ echo '<p>';
408
+ _e('Simple History has a RSS feed which you can subscribe to and receive log updates. Make sure you only share the feed with people you trust, since it can contain sensitive or confidential information.', 'simple-history');
409
+ echo '</p>';
410
+ }
411
  } // end rss class
dropins/SimpleHistorySettingsDebugDropin.php CHANGED
@@ -7,41 +7,42 @@ Dropin URI: http://simple-history.com/
7
  Author: Pär Thernström
8
  */
9
 
10
- defined( 'ABSPATH' ) or die();
11
 
12
- class SimpleHistorySettingsDebugDropin {
 
13
 
14
- private $sh;
15
 
16
- public function __construct( $sh ) {
 
17
 
18
- $this->sh = $sh;
19
 
20
- // How do we register this to the settings array?
21
- $sh->registerSettingsTab( array(
22
- 'slug' => 'debug',
23
- 'name' => __( 'Debug', 'simple-history' ),
24
- 'function' => array( $this, 'output' ),
25
- ) );
26
 
27
- // add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'on_admin_enqueue_scripts' ) );
28
- }
29
 
30
- /*
31
- public function on_admin_enqueue_scripts() {
32
 
33
- $file_url = plugin_dir_url( __FILE__ );
34
 
35
- wp_enqueue_script( "google-ajax-api", "https://www.google.com/jsapi" );
36
- wp_enqueue_style( "simple_history_SettingsStatsDropin", $file_url . "SimpleHistorySettingsStatsDropin.css", null, SIMPLE_HISTORY_VERSION );
37
 
38
- }
39
- */
40
 
41
- public function output() {
42
-
43
- include SIMPLE_HISTORY_PATH . 'templates/template-settings-tab-debug.php';
44
-
45
- }
46
 
 
 
47
  }
7
  Author: Pär Thernström
8
  */
9
 
10
+ defined('ABSPATH') or die();
11
 
12
+ class SimpleHistorySettingsDebugDropin
13
+ {
14
 
15
+ private $sh;
16
 
17
+ public function __construct($sh)
18
+ {
19
 
20
+ $this->sh = $sh;
21
 
22
+ // How do we register this to the settings array?
23
+ $sh->registerSettingsTab(array(
24
+ 'slug' => 'debug',
25
+ 'name' => __('Debug', 'simple-history'),
26
+ 'function' => array( $this, 'output' ),
27
+ ));
28
 
29
+ // add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'on_admin_enqueue_scripts' ) );
30
+ }
31
 
32
+ /*
33
+ public function on_admin_enqueue_scripts() {
34
 
35
+ $file_url = plugin_dir_url( __FILE__ );
36
 
37
+ wp_enqueue_script( "google-ajax-api", "https://www.google.com/jsapi" );
38
+ wp_enqueue_style( "simple_history_SettingsStatsDropin", $file_url . "SimpleHistorySettingsStatsDropin.css", null, SIMPLE_HISTORY_VERSION );
39
 
40
+ }
41
+ */
42
 
43
+ public function output()
44
+ {
 
 
 
45
 
46
+ include SIMPLE_HISTORY_PATH . 'templates/template-settings-tab-debug.php';
47
+ }
48
  }
dropins/SimpleHistorySettingsLogtestDropin.php CHANGED
@@ -1,289 +1,293 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
- class SimpleHistorySettingsLogtestDropin {
 
6
 
7
- // Simple History instance
8
- private $sh;
9
 
10
- public function __construct( $sh ) {
 
11
 
12
- // Since it's not quite done yet, it's for da devs only for now
13
- if ( ! defined( 'SIMPLE_HISTORY_DEV' ) || ! SIMPLE_HISTORY_DEV ) {
14
- return;
15
- }
16
 
17
- $this->sh = $sh;
18
 
19
- // How do we register this to the settings array?
20
- $sh->registerSettingsTab(array(
21
- 'slug' => 'testLog',
22
- 'name' => __( 'Test data (debug)', 'simple-history' ),
23
- 'function' => array( $this, 'output' ),
24
- ));
25
 
26
- // add_action( 'admin_enqueue_scripts', array( $this, 'on_admin_enqueue_scripts') );
27
- add_action( 'admin_head', array( $this, 'on_admin_head' ) );
28
- add_action( 'wp_ajax_SimpleHistoryAddLogTest', array( $this, 'on_ajax_add_logtests' ) );
 
29
 
30
- }
 
31
 
32
- public function on_ajax_add_logtests() {
33
 
34
- $this->doLogTestThings();
 
 
35
 
36
- $arr = array(
37
- 'message' => 'did it!',
38
- );
39
 
40
- wp_send_json_success( $arr );
 
41
 
42
- }
 
43
 
44
- public function on_admin_head() {
45
-
46
- ?>
47
- <script>
48
-
49
- jQuery(function($) {
50
-
51
- var button = $(".js-SimpleHistorySettingsLogtestDropin-addStuff");
52
- var messageDone = $(".js-SimpleHistorySettingsLogtestDropin-addStuffDone");
53
- var messageWorking = $(".js-SimpleHistorySettingsLogtestDropin-addStuffWorking");
54
-
55
- button.on("click", function(e) {
56
-
57
- messageWorking.show();
58
- messageDone.hide();
59
-
60
- $.post(ajaxurl, {
61
- action: "SimpleHistoryAddLogTest"
62
- }).done(function(r) {
63
-
64
- messageWorking.hide();
65
- messageDone.show();
66
-
67
- });
68
-
69
- });
70
-
71
- });
72
-
73
-
74
- </script>
75
- <?php
76
-
77
- }
78
-
79
- public function output() {
80
-
81
- ?>
82
- <h1>Test data</h1>
83
-
84
- <p>Add lots of test data to the log database.</p>
85
-
86
- <p>
87
- <button class="button js-SimpleHistorySettingsLogtestDropin-addStuff">Ok, add lots of stuff to the log!</button>
88
- </p>
89
-
90
- <div class="updated hidden js-SimpleHistorySettingsLogtestDropin-addStuffDone">
91
- <p>Done! Added lots of test rows</p>
92
- </div>
93
-
94
- <div class="updated hidden js-SimpleHistorySettingsLogtestDropin-addStuffWorking">
95
- <p>Adding...</p>
96
- </div>
97
-
98
- <?php
99
-
100
- }
101
-
102
- public function doLogTestThings() {
103
-
104
- // Add some data random back in time, to fill up the log to test much data
105
- for ( $j = 0; $j < 50; $j++ ) {
106
- // between yesteday and a month back in time
107
- for ( $i = 0; $i < rand( 1,30 ); $i++ ) {
108
- $str_date = date( 'Y-m-d H:i:s', strtotime( "now -{$i}days" ) );
109
- SimpleLogger()->info(
110
- 'Entry with date in the past', array(
111
- '_date' => $str_date,
112
- '_occasionsID' => "past_date:{$str_date}",
113
- ));
114
- }
115
- }
116
-
117
- SimpleLogger()->info( 'This is a message sent to the log' );
118
-
119
- // Second log entry with same info will make these two become an occasionGroup,
120
- // collapsing their entries into one expandable log item
121
- SimpleLogger()->info( 'This is a message sent to the log' );
122
-
123
- // Log entries can be of different severity
124
- SimpleLogger()->info( "User admin edited page 'About our company'" );
125
- SimpleLogger()->warning( "User 'Jessie' deleted user 'Kim'" );
126
- SimpleLogger()->debug( 'Ok, cron job is running!' );
127
-
128
- // Log entries can have placeholders and context
129
- // This makes log entried translatable and filterable
130
- for ( $i = 0; $i < rand( 1, 50 ); $i++ ) {
131
- SimpleLogger()->notice(
132
- 'User {username} edited page {pagename}',
133
- array(
134
- 'username' => 'bonnyerden',
135
- 'pagename' => 'My test page',
136
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
137
- '_user_id' => rand( 1,20 ),
138
- '_user_login' => 'loginname' . rand( 1,20 ),
139
- '_user_email' => 'user' . rand( 1,20 ) . '@example.com',
140
- )
141
- );
142
- }
143
- // return;
144
- // Log entried can have custom occasionsID
145
- // This will group items together and a log entry will only be shown once
146
- // in the log overview
147
- for ( $i = 0; $i < rand( 1, 50 ); $i++ ) {
148
- SimpleLogger()->notice('User {username} edited page {pagename}', array(
149
- 'username' => 'admin',
150
- 'pagename' => 'My test page',
151
- '_occasionsID' => 'username:1,postID:24884,action:edited',
152
- ));
153
- }
154
-
155
- SimpleLogger()->info(
156
- 'WordPress updated itself from version {from_version} to {to_version}',
157
- array(
158
- 'from_version' => '3.8',
159
- 'to_version' => '3.8.1',
160
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
161
- )
162
- );
163
-
164
- SimpleLogger()->info(
165
- 'Plugin {plugin_name} was updated from version {plugin_from_version} to version {plugin_to_version}',
166
- array(
167
- 'plugin_name' => 'CMS Tree Page View',
168
- 'plugin_from_version' => '4.0',
169
- 'plugin_to_version' => '4.2',
170
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
171
- )
172
- );
173
-
174
- SimpleLogger()->info(
175
- 'Updated plugin {plugin_name} from version {plugin_from_version} to version {plugin_to_version}',
176
- array(
177
- 'plugin_name' => 'Ninja Forms',
178
- 'plugin_from_version' => '1.1',
179
- 'plugin_to_version' => '1.1.2',
180
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
181
- )
182
- );
183
-
184
- SimpleLogger()->warning("An attempt to login as user 'administrator' failed to login because the wrong password was entered", array(
185
- '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
186
- ));
187
-
188
- SimpleLogger()->info(
189
- 'Updated plugin {plugin_name} from version {plugin_from_version} to version {plugin_to_version}',
190
- array(
191
- 'plugin_name' => 'Simple Fields',
192
- 'plugin_from_version' => '1.3.7',
193
- 'plugin_to_version' => '1.3.8',
194
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
195
- )
196
- );
197
-
198
- SimpleLogger()->error("A JavaScript error was detected on page 'About us'", array(
199
- '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
200
- ));
201
-
202
- SimpleLogger()->debug("WP Cron 'my_test_cron_job' finished in 0.012 seconds", array(
203
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
204
- ));
205
-
206
- for ( $i = 0; $i < rand( 50,1000 ); $i++ ) {
207
- SimpleLogger()->warning(
208
- 'An attempt to login as user "{user_login}" failed to login because the wrong password was entered', array(
209
- 'user_login' => 'admin',
210
- '_userID' => null,
211
- '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
212
- ));
213
- }
214
-
215
- // Add more data to context array. Data can be used later on to show detailed info about a log entry.
216
- SimpleLogger()->info("Edited product '{pagename}'", array(
217
- 'pagename' => 'We are hiring!',
218
- '_postType' => 'product',
219
- '_userID' => 1,
220
- '_userLogin' => 'jessie',
221
- '_userEmail' => 'jessie@example.com',
222
- '_occasionsID' => 'username:1,postID:24885,action:edited',
223
- ));
224
-
225
- SimpleLogger()->debug( 'This is a message with no translation' );
226
- SimpleLogger()->debug( __( 'Plugin' ), array(
227
- 'comment' => "This message is 'Plugin' and should contain text domain 'default' since it's a translation that comes with WordPress",
228
- ) );
229
- SimpleLogger()->debug( __( 'Enter title of new page', 'cms-tree-page-view' ), array(
230
- 'comment' => 'A translation used in CMS Tree Page View',
231
- ) );
232
-
233
- }
234
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  }
236
 
237
 
238
  /*
239
  add_action("init", function() {
240
 
241
- register_post_type("texts", array(
242
- "show_ui" => true
243
- ));
244
-
245
- register_post_type("products", array(
246
- "labels" => array(
247
- "name" => "Products",
248
- "singular_name" => "Product"
249
- ),
250
- "public" => true
251
- ));
252
-
253
- // Example from the codex
254
- $labels = array(
255
- 'name' => _x( 'Books', 'post type general name', 'your-plugin-textdomain' ),
256
- 'singular_name' => _x( 'Book', 'post type singular name', 'your-plugin-textdomain' ),
257
- 'menu_name' => _x( 'Books', 'admin menu', 'your-plugin-textdomain' ),
258
- 'name_admin_bar' => _x( 'Book', 'add new on admin bar', 'your-plugin-textdomain' ),
259
- 'add_new' => _x( 'Add New', 'book', 'your-plugin-textdomain' ),
260
- 'add_new_item' => __( 'Add New Book', 'your-plugin-textdomain' ),
261
- 'new_item' => __( 'New Book', 'your-plugin-textdomain' ),
262
- 'edit_item' => __( 'Edit Book', 'your-plugin-textdomain' ),
263
- 'view_item' => __( 'View Book', 'your-plugin-textdomain' ),
264
- 'all_items' => __( 'All Books', 'your-plugin-textdomain' ),
265
- 'search_items' => __( 'Search Books', 'your-plugin-textdomain' ),
266
- 'parent_item_colon' => __( 'Parent Books:', 'your-plugin-textdomain' ),
267
- 'not_found' => __( 'No books found.', 'your-plugin-textdomain' ),
268
- 'not_found_in_trash' => __( 'No books found in Trash.', 'your-plugin-textdomain' ),
269
- );
270
-
271
- $args = array(
272
- 'labels' => $labels,
273
- 'public' => true,
274
- 'publicly_queryable' => true,
275
- 'show_ui' => true,
276
- 'show_in_menu' => true,
277
- 'query_var' => true,
278
- 'rewrite' => array( 'slug' => 'book' ),
279
- 'capability_type' => 'post',
280
- 'has_archive' => true,
281
- 'hierarchical' => false,
282
- 'menu_position' => null,
283
- 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments' )
284
- );
285
-
286
- register_post_type( 'book', $args );
287
 
288
  });
289
  */
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
+ class SimpleHistorySettingsLogtestDropin
6
+ {
7
 
8
+ // Simple History instance
9
+ private $sh;
10
 
11
+ public function __construct($sh)
12
+ {
13
 
14
+ // Since it's not quite done yet, it's for da devs only for now
15
+ if (! defined('SIMPLE_HISTORY_DEV') || ! SIMPLE_HISTORY_DEV) {
16
+ return;
17
+ }
18
 
19
+ $this->sh = $sh;
20
 
21
+ // How do we register this to the settings array?
22
+ $sh->registerSettingsTab(array(
23
+ 'slug' => 'testLog',
24
+ 'name' => __('Test data (debug)', 'simple-history'),
25
+ 'function' => array( $this, 'output' ),
26
+ ));
27
 
28
+ // add_action( 'admin_enqueue_scripts', array( $this, 'on_admin_enqueue_scripts') );
29
+ add_action('admin_head', array( $this, 'on_admin_head' ));
30
+ add_action('wp_ajax_SimpleHistoryAddLogTest', array( $this, 'on_ajax_add_logtests' ));
31
+ }
32
 
33
+ public function on_ajax_add_logtests()
34
+ {
35
 
36
+ $this->doLogTestThings();
37
 
38
+ $arr = array(
39
+ 'message' => 'did it!',
40
+ );
41
 
42
+ wp_send_json_success($arr);
43
+ }
 
44
 
45
+ public function on_admin_head()
46
+ {
47
 
48
+ ?>
49
+ <script>
50
 
51
+ jQuery(function($) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
+ var button = $(".js-SimpleHistorySettingsLogtestDropin-addStuff");
54
+ var messageDone = $(".js-SimpleHistorySettingsLogtestDropin-addStuffDone");
55
+ var messageWorking = $(".js-SimpleHistorySettingsLogtestDropin-addStuffWorking");
56
+
57
+ button.on("click", function(e) {
58
+
59
+ messageWorking.show();
60
+ messageDone.hide();
61
+
62
+ $.post(ajaxurl, {
63
+ action: "SimpleHistoryAddLogTest"
64
+ }).done(function(r) {
65
+
66
+ messageWorking.hide();
67
+ messageDone.show();
68
+
69
+ });
70
+
71
+ });
72
+
73
+ });
74
+
75
+
76
+ </script>
77
+ <?php
78
+ }
79
+
80
+ public function output()
81
+ {
82
+
83
+ ?>
84
+ <h1>Test data</h1>
85
+
86
+ <p>Add lots of test data to the log database.</p>
87
+
88
+ <p>
89
+ <button class="button js-SimpleHistorySettingsLogtestDropin-addStuff">Ok, add lots of stuff to the log!</button>
90
+ </p>
91
+
92
+ <div class="updated hidden js-SimpleHistorySettingsLogtestDropin-addStuffDone">
93
+ <p>Done! Added lots of test rows</p>
94
+ </div>
95
+
96
+ <div class="updated hidden js-SimpleHistorySettingsLogtestDropin-addStuffWorking">
97
+ <p>Adding...</p>
98
+ </div>
99
+
100
+ <?php
101
+ }
102
+
103
+ public function doLogTestThings()
104
+ {
105
+
106
+ // Add some data random back in time, to fill up the log to test much data
107
+ for ($j = 0; $j < 50; $j++) {
108
+ // between yesteday and a month back in time
109
+ for ($i = 0; $i < rand(1, 30); $i++) {
110
+ $str_date = date('Y-m-d H:i:s', strtotime("now -{$i}days"));
111
+ SimpleLogger()->info(
112
+ 'Entry with date in the past',
113
+ array(
114
+ '_date' => $str_date,
115
+ '_occasionsID' => "past_date:{$str_date}",
116
+ )
117
+ );
118
+ }
119
+ }
120
+
121
+ SimpleLogger()->info('This is a message sent to the log');
122
+
123
+ // Second log entry with same info will make these two become an occasionGroup,
124
+ // collapsing their entries into one expandable log item
125
+ SimpleLogger()->info('This is a message sent to the log');
126
+
127
+ // Log entries can be of different severity
128
+ SimpleLogger()->info("User admin edited page 'About our company'");
129
+ SimpleLogger()->warning("User 'Jessie' deleted user 'Kim'");
130
+ SimpleLogger()->debug('Ok, cron job is running!');
131
+
132
+ // Log entries can have placeholders and context
133
+ // This makes log entried translatable and filterable
134
+ for ($i = 0; $i < rand(1, 50); $i++) {
135
+ SimpleLogger()->notice(
136
+ 'User {username} edited page {pagename}',
137
+ array(
138
+ 'username' => 'bonnyerden',
139
+ 'pagename' => 'My test page',
140
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
141
+ '_user_id' => rand(1, 20),
142
+ '_user_login' => 'loginname' . rand(1, 20),
143
+ '_user_email' => 'user' . rand(1, 20) . '@example.com',
144
+ )
145
+ );
146
+ }
147
+ // return;
148
+ // Log entried can have custom occasionsID
149
+ // This will group items together and a log entry will only be shown once
150
+ // in the log overview
151
+ for ($i = 0; $i < rand(1, 50); $i++) {
152
+ SimpleLogger()->notice('User {username} edited page {pagename}', array(
153
+ 'username' => 'admin',
154
+ 'pagename' => 'My test page',
155
+ '_occasionsID' => 'username:1,postID:24884,action:edited',
156
+ ));
157
+ }
158
+
159
+ SimpleLogger()->info(
160
+ 'WordPress updated itself from version {from_version} to {to_version}',
161
+ array(
162
+ 'from_version' => '3.8',
163
+ 'to_version' => '3.8.1',
164
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
165
+ )
166
+ );
167
+
168
+ SimpleLogger()->info(
169
+ 'Plugin {plugin_name} was updated from version {plugin_from_version} to version {plugin_to_version}',
170
+ array(
171
+ 'plugin_name' => 'CMS Tree Page View',
172
+ 'plugin_from_version' => '4.0',
173
+ 'plugin_to_version' => '4.2',
174
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
175
+ )
176
+ );
177
+
178
+ SimpleLogger()->info(
179
+ 'Updated plugin {plugin_name} from version {plugin_from_version} to version {plugin_to_version}',
180
+ array(
181
+ 'plugin_name' => 'Ninja Forms',
182
+ 'plugin_from_version' => '1.1',
183
+ 'plugin_to_version' => '1.1.2',
184
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
185
+ )
186
+ );
187
+
188
+ SimpleLogger()->warning("An attempt to login as user 'administrator' failed to login because the wrong password was entered", array(
189
+ '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
190
+ ));
191
+
192
+ SimpleLogger()->info(
193
+ 'Updated plugin {plugin_name} from version {plugin_from_version} to version {plugin_to_version}',
194
+ array(
195
+ 'plugin_name' => 'Simple Fields',
196
+ 'plugin_from_version' => '1.3.7',
197
+ 'plugin_to_version' => '1.3.8',
198
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
199
+ )
200
+ );
201
+
202
+ SimpleLogger()->error("A JavaScript error was detected on page 'About us'", array(
203
+ '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
204
+ ));
205
+
206
+ SimpleLogger()->debug("WP Cron 'my_test_cron_job' finished in 0.012 seconds", array(
207
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
208
+ ));
209
+
210
+ for ($i = 0; $i < rand(50, 1000); $i++) {
211
+ SimpleLogger()->warning(
212
+ 'An attempt to login as user "{user_login}" failed to login because the wrong password was entered',
213
+ array(
214
+ 'user_login' => 'admin',
215
+ '_userID' => null,
216
+ '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
217
+ )
218
+ );
219
+ }
220
+
221
+ // Add more data to context array. Data can be used later on to show detailed info about a log entry.
222
+ SimpleLogger()->info("Edited product '{pagename}'", array(
223
+ 'pagename' => 'We are hiring!',
224
+ '_postType' => 'product',
225
+ '_userID' => 1,
226
+ '_userLogin' => 'jessie',
227
+ '_userEmail' => 'jessie@example.com',
228
+ '_occasionsID' => 'username:1,postID:24885,action:edited',
229
+ ));
230
+
231
+ SimpleLogger()->debug('This is a message with no translation');
232
+ SimpleLogger()->debug(__('Plugin'), array(
233
+ 'comment' => "This message is 'Plugin' and should contain text domain 'default' since it's a translation that comes with WordPress",
234
+ ));
235
+ SimpleLogger()->debug(__('Enter title of new page', 'cms-tree-page-view'), array(
236
+ 'comment' => 'A translation used in CMS Tree Page View',
237
+ ));
238
+ }
239
  }
240
 
241
 
242
  /*
243
  add_action("init", function() {
244
 
245
+ register_post_type("texts", array(
246
+ "show_ui" => true
247
+ ));
248
+
249
+ register_post_type("products", array(
250
+ "labels" => array(
251
+ "name" => "Products",
252
+ "singular_name" => "Product"
253
+ ),
254
+ "public" => true
255
+ ));
256
+
257
+ // Example from the codex
258
+ $labels = array(
259
+ 'name' => _x( 'Books', 'post type general name', 'your-plugin-textdomain' ),
260
+ 'singular_name' => _x( 'Book', 'post type singular name', 'your-plugin-textdomain' ),
261
+ 'menu_name' => _x( 'Books', 'admin menu', 'your-plugin-textdomain' ),
262
+ 'name_admin_bar' => _x( 'Book', 'add new on admin bar', 'your-plugin-textdomain' ),
263
+ 'add_new' => _x( 'Add New', 'book', 'your-plugin-textdomain' ),
264
+ 'add_new_item' => __( 'Add New Book', 'your-plugin-textdomain' ),
265
+ 'new_item' => __( 'New Book', 'your-plugin-textdomain' ),
266
+ 'edit_item' => __( 'Edit Book', 'your-plugin-textdomain' ),
267
+ 'view_item' => __( 'View Book', 'your-plugin-textdomain' ),
268
+ 'all_items' => __( 'All Books', 'your-plugin-textdomain' ),
269
+ 'search_items' => __( 'Search Books', 'your-plugin-textdomain' ),
270
+ 'parent_item_colon' => __( 'Parent Books:', 'your-plugin-textdomain' ),
271
+ 'not_found' => __( 'No books found.', 'your-plugin-textdomain' ),
272
+ 'not_found_in_trash' => __( 'No books found in Trash.', 'your-plugin-textdomain' ),
273
+ );
274
+
275
+ $args = array(
276
+ 'labels' => $labels,
277
+ 'public' => true,
278
+ 'publicly_queryable' => true,
279
+ 'show_ui' => true,
280
+ 'show_in_menu' => true,
281
+ 'query_var' => true,
282
+ 'rewrite' => array( 'slug' => 'book' ),
283
+ 'capability_type' => 'post',
284
+ 'has_archive' => true,
285
+ 'hierarchical' => false,
286
+ 'menu_position' => null,
287
+ 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments' )
288
+ );
289
+
290
+ register_post_type( 'book', $args );
291
 
292
  });
293
  */
dropins/SimpleHistorySettingsStatsDropin.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /*
6
  Dropin Name: Settings stats
@@ -9,107 +9,105 @@ Dropin URI: http://simple-history.com/
9
  Author: Pär Thernström
10
  */
11
 
12
- class SimpleHistorySettingsStatsDropin {
 
13
 
14
- // Simple History instance
15
- private $sh;
16
 
17
- public function __construct( $sh ) {
 
18
 
19
- // Since it's not quite done yet, it's for da devs only for now
20
- if ( ! defined( 'SIMPLE_HISTORY_DEV' ) || ! SIMPLE_HISTORY_DEV ) {
21
- return;
22
- }
23
 
24
- $this->sh = $sh;
25
 
26
- // How do we register this to the settings array?
27
- $sh->registerSettingsTab(array(
28
- 'slug' => 'stats',
29
- 'name' => __( 'Stats', 'simple-history' ),
30
- 'function' => array( $this, 'output' ),
31
- ));
32
 
33
- add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'on_admin_enqueue_scripts' ) );
 
34
 
35
- }
 
36
 
37
- public function on_admin_enqueue_scripts() {
38
 
39
- $file_url = plugin_dir_url( __FILE__ );
 
 
40
 
41
- wp_enqueue_script( 'google-ajax-api', 'https://www.google.com/jsapi' );
42
- wp_enqueue_style( 'simple_history_SettingsStatsDropin', $file_url . 'SimpleHistorySettingsStatsDropin.css', null, SIMPLE_HISTORY_VERSION );
43
 
44
- }
 
 
45
 
46
- public function output() {
 
 
 
47
 
48
- global $wpdb;
49
- $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
50
- $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
51
 
52
- // $period_days = (int) 28;
53
- $period_days = (int) 14;
54
- $period_start_date = DateTime::createFromFormat( 'U', strtotime( "-$period_days days" ) );
55
- $period_end_date = DateTime::createFromFormat( 'U', time() );
 
 
56
 
57
- // Colors taken from the gogole chart example that was found in this Stack Overflow thread:
58
- // http://stackoverflow.com/questions/236936/how-pick-colors-for-a-pie-chart
59
- $arr_colors = explode( ',', '8a56e2,cf56e2,e256ae,e25668,e28956,e2cf56,aee256,68e256,56e289,56e2cf,56aee2,5668e2' );
 
 
 
 
60
 
61
- // Load google charts libraries
62
- ?>
63
- <script>
64
- google.load('visualization', '1', {'packages':['corechart']});
65
- </script>
66
- <?php
67
 
68
- ?>
69
- <!-- Overview, larger text -->
70
- <div class='SimpleHistoryStats__intro'>
71
- <?php
72
- include( SIMPLE_HISTORY_PATH . 'templates/settings-statsIntro.php' );
73
- ?>
74
- </div>
75
 
76
- <!-- Start charts wrap -->
77
- <div class='SimpleHistoryStats__graphs SimpleHistory__cf'>
 
78
 
79
- <!-- bar chart with rows per day -->
80
- <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--rowsPerDay'>
81
- <?php include( SIMPLE_HISTORY_PATH . 'templates/settings-statsRowsPerDay.php' ) ?>
82
- </div><!-- // end bar chart rows per day -->
83
 
84
- <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--loggersPie'>
85
- <?php include( SIMPLE_HISTORY_PATH . 'templates/settings-statsLoggers.php' ) ?>
86
- </div>
87
 
88
- <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--logLevels'>
89
- <?php include( SIMPLE_HISTORY_PATH . 'templates/settings-statsLogLevels.php' ) ?>
90
- </div>
 
 
91
 
92
- <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--users'>
93
- <?php include( SIMPLE_HISTORY_PATH . 'templates/settings-statsUsers.php' ) ?>
94
- </div>
95
-
96
- <!--
97
- <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--initiators'>
98
- <?php include( SIMPLE_HISTORY_PATH . 'templates/settings-statsInitiators.php' ) ?>
99
- </div>
100
- -->
101
-
102
-
103
- </div><!-- // end charts wrapper -->
104
-
105
- <?php
106
-
107
- include( SIMPLE_HISTORY_PATH . 'templates/settings-statsForGeeks.php' );
108
-
109
- }
110
 
 
111
 
 
112
 
 
 
113
  }
114
 
115
 
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: Settings stats
9
  Author: Pär Thernström
10
  */
11
 
12
+ class SimpleHistorySettingsStatsDropin
13
+ {
14
 
15
+ // Simple History instance
16
+ private $sh;
17
 
18
+ public function __construct($sh)
19
+ {
20
 
21
+ // Since it's not quite done yet, it's for da devs only for now
22
+ if (! defined('SIMPLE_HISTORY_DEV') || ! SIMPLE_HISTORY_DEV) {
23
+ return;
24
+ }
25
 
26
+ $this->sh = $sh;
27
 
28
+ // How do we register this to the settings array?
29
+ $sh->registerSettingsTab(array(
30
+ 'slug' => 'stats',
31
+ 'name' => __('Stats', 'simple-history'),
32
+ 'function' => array( $this, 'output' ),
33
+ ));
34
 
35
+ add_action('simple_history/enqueue_admin_scripts', array( $this, 'on_admin_enqueue_scripts' ));
36
+ }
37
 
38
+ public function on_admin_enqueue_scripts()
39
+ {
40
 
41
+ $file_url = plugin_dir_url(__FILE__);
42
 
43
+ wp_enqueue_script('google-ajax-api', 'https://www.google.com/jsapi');
44
+ wp_enqueue_style('simple_history_SettingsStatsDropin', $file_url . 'SimpleHistorySettingsStatsDropin.css', null, SIMPLE_HISTORY_VERSION);
45
+ }
46
 
47
+ public function output()
48
+ {
49
 
50
+ global $wpdb;
51
+ $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
52
+ $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
53
 
54
+ // $period_days = (int) 28;
55
+ $period_days = (int) 14;
56
+ $period_start_date = DateTime::createFromFormat('U', strtotime("-$period_days days"));
57
+ $period_end_date = DateTime::createFromFormat('U', time());
58
 
59
+ // Colors taken from the gogole chart example that was found in this Stack Overflow thread:
60
+ // http://stackoverflow.com/questions/236936/how-pick-colors-for-a-pie-chart
61
+ $arr_colors = explode(',', '8a56e2,cf56e2,e256ae,e25668,e28956,e2cf56,aee256,68e256,56e289,56e2cf,56aee2,5668e2');
62
 
63
+ // Load google charts libraries
64
+ ?>
65
+ <script>
66
+ google.load('visualization', '1', {'packages':['corechart']});
67
+ </script>
68
+ <?php
69
 
70
+ ?>
71
+ <!-- Overview, larger text -->
72
+ <div class='SimpleHistoryStats__intro'>
73
+ <?php
74
+ include(SIMPLE_HISTORY_PATH . 'templates/settings-statsIntro.php');
75
+ ?>
76
+ </div>
77
 
78
+ <!-- Start charts wrap -->
79
+ <div class='SimpleHistoryStats__graphs SimpleHistory__cf'>
 
 
 
 
80
 
81
+ <!-- bar chart with rows per day -->
82
+ <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--rowsPerDay'>
83
+ <?php include(SIMPLE_HISTORY_PATH . 'templates/settings-statsRowsPerDay.php') ?>
84
+ </div><!-- // end bar chart rows per day -->
 
 
 
85
 
86
+ <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--loggersPie'>
87
+ <?php include(SIMPLE_HISTORY_PATH . 'templates/settings-statsLoggers.php') ?>
88
+ </div>
89
 
90
+ <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--logLevels'>
91
+ <?php include(SIMPLE_HISTORY_PATH . 'templates/settings-statsLogLevels.php') ?>
92
+ </div>
 
93
 
94
+ <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--users'>
95
+ <?php include(SIMPLE_HISTORY_PATH . 'templates/settings-statsUsers.php') ?>
96
+ </div>
97
 
98
+ <!--
99
+ <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--initiators'>
100
+ <?php include(SIMPLE_HISTORY_PATH . 'templates/settings-statsInitiators.php') ?>
101
+ </div>
102
+ -->
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
+ </div><!-- // end charts wrapper -->
106
 
107
+ <?php
108
 
109
+ include(SIMPLE_HISTORY_PATH . 'templates/settings-statsForGeeks.php');
110
+ }
111
  }
112
 
113
 
dropins/SimpleHistorySidebarDropin.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /*
6
  Dropin Name: Sidebar
@@ -9,34 +9,36 @@ Dropin URI: http://simple-history.com/
9
  Author: Pär Thernström
10
  */
11
 
12
- class SimpleHistorySidebarDropin {
 
13
 
14
- private $sh;
15
 
16
- function __construct( $sh ) {
 
17
 
18
- $this->sh = $sh;
19
 
20
- add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'enqueue_admin_scripts' ) );
21
- add_action( 'simple_history/history_page/after_gui', array( $this, 'output_sidebar_html' ) );
22
 
23
- // add_action("simple_history/dropin/sidebar/sidebar_html", array($this, "example_output"));
24
- add_action( 'simple_history/dropin/sidebar/sidebar_html', array( $this, 'default_sidebar_contents' ) );
 
25
 
26
- }
 
27
 
28
- public function default_sidebar_contents() {
 
 
29
 
30
- // Boxes that will appear randomly
31
- // Box about GitHub
32
- $headline = _x( 'Simple History is on GitHub', 'Sidebar box', 'simple-history' );
 
33
 
34
- $body = sprintf(
35
- _x( 'You can star, fork, or report issues with this plugin over at the <a href="%1$s">GitHub page</a>.', 'Sidebar box', 'simple-history' ),
36
- 'https://github.com/bonny/WordPress-Simple-History'
37
- );
38
-
39
- $boxGithub = '
40
  <div class="postbox">
41
  <h3 class="hndle">' . $headline . '</h3>
42
  <div class="inside">
@@ -45,15 +47,15 @@ class SimpleHistorySidebarDropin {
45
  </div>
46
  ';
47
 
48
- // Box about donation
49
- $headline = _x( 'Donate to support development', 'Sidebar box', 'simple-history' );
50
 
51
- $body = sprintf(
52
- _x( 'If you like and use Simple History you should <a href="%1$s">donate to keep this plugin free</a>.', 'Sidebar box', 'simple-history' ),
53
- 'http://eskapism.se/sida/donate/'
54
- );
55
 
56
- $boxDonate = '
57
  <div class="postbox">
58
  <h3 class="hndle">' . $headline . '</h3>
59
  <div class="inside">
@@ -62,17 +64,17 @@ class SimpleHistorySidebarDropin {
62
  </div>
63
  ';
64
 
65
- // Box about review
66
- $headline = _x( 'Review this plugin if you like it', 'Sidebar box', 'simple-history' );
67
 
68
- $body1 = sprintf(
69
- _x( 'If you like Simple History then please <a href="%1$s">give it a nice review over at wordpress.org</a>.', 'Sidebar box', 'simple-history' ),
70
- 'https://wordpress.org/support/view/plugin-reviews/simple-history'
71
- );
72
 
73
- $body2 = _x( 'A good review will help new users find this plugin. And it will make the plugin author very happy :)', 'Sidebar box', 'simple-history' );
74
 
75
- $boxReview = '
76
  <div class="postbox">
77
  <h3 class="hndle">' . $headline . '</h3>
78
  <div class="inside">
@@ -82,20 +84,21 @@ class SimpleHistorySidebarDropin {
82
  </div>
83
  ';
84
 
85
- // Box about tweeting and blogging
86
- /*
87
- $boxSocial = '
88
- <div class="postbox">
89
- <h3 class="hndle">Blog or tweet</h3>
90
- <div class="inside">
91
- <p>Yeah, how about that yo.</p>
92
- </div>
93
- </div>
94
- ';
95
- */
96
-
97
- // Box about possible events missing
98
- $boxMissingEvents = sprintf( '
 
99
  <div class="postbox">
100
  <h3 class="hndle">%1$s</h3>
101
  <div class="inside">
@@ -104,12 +107,13 @@ class SimpleHistorySidebarDropin {
104
  </div>
105
  </div>
106
  ',
107
- _x( 'Add more to the log', 'Sidebar box', 'simple-history' ), // 1
108
- _x( 'Are there things you miss in the history log?', 'Sidebar box', 'simple-history' ) // 2
109
- );
110
 
111
- // Box about support
112
- $boxSupport = sprintf( '
 
113
  <div class="postbox">
114
  <h3 class="hndle">%1$s</h3>
115
  <div class="inside">
@@ -117,45 +121,44 @@ class SimpleHistorySidebarDropin {
117
  </div>
118
  </div>
119
  ',
120
- _x( 'Support', 'Sidebar box', 'simple-history' ), // 1
121
- sprintf( _x( '<a href="%1$s">Visit the support forum</a> if you need help or have questions.', 'Sidebar box', 'simple-history' ), 'https://wordpress.org/support/plugin/simple-history' ) // 2
122
- );
123
-
124
- $arrBoxes = array(
125
- 'boxReview' => $boxReview,
126
- 'boxSupport' => $boxSupport,
127
- // "boxMissingEvents" => $boxMissingEvents,
128
- 'boxDonate' => $boxDonate,
129
- // "boxGithub" => $boxGithub,
130
- );
131
-
132
- /**
133
- * Filter the default boxes to output in the sidebar
134
- *
135
- * @since 2.0.17
136
- *
137
- * @param array $arrBoxes array with boxes to output. Check the key to determine which box is which.
138
- */
139
- $arrBoxes = apply_filters( 'simple_history/SidebarDropin/default_sidebar_boxes', $arrBoxes );
140
-
141
- // echo $arrBoxes[array_rand($arrBoxes)];
142
- echo implode( '', $arrBoxes ); // show all
143
-
144
- // Box to encourage people translate plugin
145
- $current_locale = get_locale();
146
-
147
- /** WordPress Translation Install API. This file exists only since 4.0. */
148
- $translation_install_file = ABSPATH . 'wp-admin/includes/translation-install.php';
149
-
150
- // Show only the translation box if current language is not an english language
151
- if ( in_array( $current_locale, array( 'en_US', 'en_GB', 'en_CA', 'en_NZ', 'en_AU' ) ) != $current_locale && file_exists( $translation_install_file ) ) {
152
-
153
- require_once $translation_install_file;
154
-
155
- $translations = wp_get_available_translations();
156
-
157
- // This text does not need translation since is's only shown in English
158
- $boxTranslationTmpl = '
159
  <div class="postbox">
160
  <h3 class="hndle">Translate Simple History to %1$s</h3>
161
  <div class="inside">
@@ -176,71 +179,69 @@ class SimpleHistorySidebarDropin {
176
  </div>
177
  ';
178
 
179
- if ( isset( $translations[ $current_locale ] ) ) {
180
-
181
- // Check if an existing text string returns something else, and that current lang is not en
182
- $teststring_translated = __( 'Just now', 'simple-history' );
183
- $teststring_untranslated = 'Just now';
184
- if ( $teststring_untranslated == $teststring_translated ) {
185
- // strings are the same, so plugin probably not translated
186
- printf( $boxTranslationTmpl, $translations[ $current_locale ]['english_name'] );
187
- }
188
- }
189
- } // End if().
190
-
191
- }
192
-
193
- public function example_output() {
194
- ?>
195
- <div class="postbox">
196
- <h3 class="hndle">Example title</h3>
197
- <div class="inside">
198
- <p>Example content. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Inquit, dasne adolescenti veniam? Non laboro, inquit, de nomine. In quibus doctissimi illi veteres inesse quiddam caeleste et divinum putaverunt. Duo Reges: constructio interrete. Indicant pueri, in quibus ut in speculis natura cernitur. Quod ea non occurrentia fingunt, vincunt Aristonem; Quod quidem iam fit etiam in Academia. Aliter enim nosmet ipsos nosse non possumus.</p>
199
- </div>
200
- </div>
201
- <?php
202
- }
203
-
204
- public function enqueue_admin_scripts() {
205
-
206
- $file_url = plugin_dir_url( __FILE__ );
207
-
208
- wp_enqueue_style( 'simple_history_SidebarDropin', $file_url . 'SimpleHistorySidebarDropin.css', null, SIMPLE_HISTORY_VERSION );
209
-
210
- }
211
-
212
- /**
213
- * Output the outline for the sidebar
214
- * Plugins and dropins simple use the filters to output contents to the sidebar
215
- * Example HTML code to generate meta box:
216
- *
217
- * <div class="postbox">
218
- * <h3 class="hndle">Title</h3>
219
- * <div class="inside">
220
- * <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Inquit, dasne adolescenti veniam? Non laboro, inquit, de nomine. In quibus doctissimi illi veteres inesse quiddam caeleste et divinum putaverunt. Duo Reges: constructio interrete. Indicant pueri, in quibus ut in speculis natura cernitur. Quod ea non occurrentia fingunt, vincunt Aristonem; Quod quidem iam fit etiam in Academia. Aliter enim nosmet ipsos nosse non possumus.</p>
221
- * </div>
222
- * </div>
223
- */
224
- public function output_sidebar_html() {
225
-
226
- ?>
227
- <div class="SimpleHistory__pageSidebar">
228
-
229
- <div class="metabox-holder">
230
-
231
- <?php
232
- /**
233
- * Allows to output HTML in sidebar
234
- *
235
- * @since 2.0.16
236
- */
237
- do_action( 'simple_history/dropin/sidebar/sidebar_html' );
238
- ?>
239
- </div>
240
-
241
- </div>
242
- <?php
243
-
244
- }
245
-
246
  }//end class
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: Sidebar
9
  Author: Pär Thernström
10
  */
11
 
12
+ class SimpleHistorySidebarDropin
13
+ {
14
 
15
+ private $sh;
16
 
17
+ function __construct($sh)
18
+ {
19
 
20
+ $this->sh = $sh;
21
 
22
+ add_action('simple_history/enqueue_admin_scripts', array( $this, 'enqueue_admin_scripts' ));
23
+ add_action('simple_history/history_page/after_gui', array( $this, 'output_sidebar_html' ));
24
 
25
+ // add_action("simple_history/dropin/sidebar/sidebar_html", array($this, "example_output"));
26
+ add_action('simple_history/dropin/sidebar/sidebar_html', array( $this, 'default_sidebar_contents' ));
27
+ }
28
 
29
+ public function default_sidebar_contents()
30
+ {
31
 
32
+ // Boxes that will appear randomly
33
+ // Box about GitHub
34
+ $headline = _x('Simple History is on GitHub', 'Sidebar box', 'simple-history');
35
 
36
+ $body = sprintf(
37
+ _x('You can star, fork, or report issues with this plugin over at the <a href="%1$s">GitHub page</a>.', 'Sidebar box', 'simple-history'),
38
+ 'https://github.com/bonny/WordPress-Simple-History'
39
+ );
40
 
41
+ $boxGithub = '
 
 
 
 
 
42
  <div class="postbox">
43
  <h3 class="hndle">' . $headline . '</h3>
44
  <div class="inside">
47
  </div>
48
  ';
49
 
50
+ // Box about donation
51
+ $headline = _x('Donate to support development', 'Sidebar box', 'simple-history');
52
 
53
+ $body = sprintf(
54
+ _x('If you like and use Simple History you should <a href="%1$s">donate to keep this plugin free</a>.', 'Sidebar box', 'simple-history'),
55
+ 'http://eskapism.se/sida/donate/'
56
+ );
57
 
58
+ $boxDonate = '
59
  <div class="postbox">
60
  <h3 class="hndle">' . $headline . '</h3>
61
  <div class="inside">
64
  </div>
65
  ';
66
 
67
+ // Box about review
68
+ $headline = _x('Review this plugin if you like it', 'Sidebar box', 'simple-history');
69
 
70
+ $body1 = sprintf(
71
+ _x('If you like Simple History then please <a href="%1$s">give it a nice review over at wordpress.org</a>.', 'Sidebar box', 'simple-history'),
72
+ 'https://wordpress.org/support/view/plugin-reviews/simple-history'
73
+ );
74
 
75
+ $body2 = _x('A good review will help new users find this plugin. And it will make the plugin author very happy :)', 'Sidebar box', 'simple-history');
76
 
77
+ $boxReview = '
78
  <div class="postbox">
79
  <h3 class="hndle">' . $headline . '</h3>
80
  <div class="inside">
84
  </div>
85
  ';
86
 
87
+ // Box about tweeting and blogging
88
+ /*
89
+ $boxSocial = '
90
+ <div class="postbox">
91
+ <h3 class="hndle">Blog or tweet</h3>
92
+ <div class="inside">
93
+ <p>Yeah, how about that yo.</p>
94
+ </div>
95
+ </div>
96
+ ';
97
+ */
98
+
99
+ // Box about possible events missing
100
+ $boxMissingEvents = sprintf(
101
+ '
102
  <div class="postbox">
103
  <h3 class="hndle">%1$s</h3>
104
  <div class="inside">
107
  </div>
108
  </div>
109
  ',
110
+ _x('Add more to the log', 'Sidebar box', 'simple-history'), // 1
111
+ _x('Are there things you miss in the history log?', 'Sidebar box', 'simple-history') // 2
112
+ );
113
 
114
+ // Box about support
115
+ $boxSupport = sprintf(
116
+ '
117
  <div class="postbox">
118
  <h3 class="hndle">%1$s</h3>
119
  <div class="inside">
121
  </div>
122
  </div>
123
  ',
124
+ _x('Support', 'Sidebar box', 'simple-history'), // 1
125
+ sprintf(_x('<a href="%1$s">Visit the support forum</a> if you need help or have questions.', 'Sidebar box', 'simple-history'), 'https://wordpress.org/support/plugin/simple-history') // 2
126
+ );
127
+
128
+ $arrBoxes = array(
129
+ 'boxReview' => $boxReview,
130
+ 'boxSupport' => $boxSupport,
131
+ // "boxMissingEvents" => $boxMissingEvents,
132
+ 'boxDonate' => $boxDonate,
133
+ // "boxGithub" => $boxGithub,
134
+ );
135
+
136
+ /**
137
+ * Filter the default boxes to output in the sidebar
138
+ *
139
+ * @since 2.0.17
140
+ *
141
+ * @param array $arrBoxes array with boxes to output. Check the key to determine which box is which.
142
+ */
143
+ $arrBoxes = apply_filters('simple_history/SidebarDropin/default_sidebar_boxes', $arrBoxes);
144
+
145
+ // echo $arrBoxes[array_rand($arrBoxes)];
146
+ echo implode('', $arrBoxes); // show all
147
+
148
+ // Box to encourage people translate plugin
149
+ $current_locale = get_locale();
150
+
151
+ /** WordPress Translation Install API. This file exists only since 4.0. */
152
+ $translation_install_file = ABSPATH . 'wp-admin/includes/translation-install.php';
153
+
154
+ // Show only the translation box if current language is not an english language
155
+ if (in_array($current_locale, array( 'en_US', 'en_GB', 'en_CA', 'en_NZ', 'en_AU' )) != $current_locale && file_exists($translation_install_file)) {
156
+ require_once $translation_install_file;
157
+
158
+ $translations = wp_get_available_translations();
159
+
160
+ // This text does not need translation since is's only shown in English
161
+ $boxTranslationTmpl = '
 
162
  <div class="postbox">
163
  <h3 class="hndle">Translate Simple History to %1$s</h3>
164
  <div class="inside">
179
  </div>
180
  ';
181
 
182
+ if (isset($translations[ $current_locale ])) {
183
+ // Check if an existing text string returns something else, and that current lang is not en
184
+ $teststring_translated = __('Just now', 'simple-history');
185
+ $teststring_untranslated = 'Just now';
186
+ if ($teststring_untranslated == $teststring_translated) {
187
+ // strings are the same, so plugin probably not translated
188
+ printf($boxTranslationTmpl, $translations[ $current_locale ]['english_name']);
189
+ }
190
+ }
191
+ } // End if().
192
+ }
193
+
194
+ public function example_output()
195
+ {
196
+ ?>
197
+ <div class="postbox">
198
+ <h3 class="hndle">Example title</h3>
199
+ <div class="inside">
200
+ <p>Example content. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Inquit, dasne adolescenti veniam? Non laboro, inquit, de nomine. In quibus doctissimi illi veteres inesse quiddam caeleste et divinum putaverunt. Duo Reges: constructio interrete. Indicant pueri, in quibus ut in speculis natura cernitur. Quod ea non occurrentia fingunt, vincunt Aristonem; Quod quidem iam fit etiam in Academia. Aliter enim nosmet ipsos nosse non possumus.</p>
201
+ </div>
202
+ </div>
203
+ <?php
204
+ }
205
+
206
+ public function enqueue_admin_scripts()
207
+ {
208
+
209
+ $file_url = plugin_dir_url(__FILE__);
210
+
211
+ wp_enqueue_style('simple_history_SidebarDropin', $file_url . 'SimpleHistorySidebarDropin.css', null, SIMPLE_HISTORY_VERSION);
212
+ }
213
+
214
+ /**
215
+ * Output the outline for the sidebar
216
+ * Plugins and dropins simple use the filters to output contents to the sidebar
217
+ * Example HTML code to generate meta box:
218
+ *
219
+ * <div class="postbox">
220
+ * <h3 class="hndle">Title</h3>
221
+ * <div class="inside">
222
+ * <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Inquit, dasne adolescenti veniam? Non laboro, inquit, de nomine. In quibus doctissimi illi veteres inesse quiddam caeleste et divinum putaverunt. Duo Reges: constructio interrete. Indicant pueri, in quibus ut in speculis natura cernitur. Quod ea non occurrentia fingunt, vincunt Aristonem; Quod quidem iam fit etiam in Academia. Aliter enim nosmet ipsos nosse non possumus.</p>
223
+ * </div>
224
+ * </div>
225
+ */
226
+ public function output_sidebar_html()
227
+ {
228
+
229
+ ?>
230
+ <div class="SimpleHistory__pageSidebar">
231
+
232
+ <div class="metabox-holder">
233
+
234
+ <?php
235
+ /**
236
+ * Allows to output HTML in sidebar
237
+ *
238
+ * @since 2.0.16
239
+ */
240
+ do_action('simple_history/dropin/sidebar/sidebar_html');
241
+ ?>
242
+ </div>
243
+
244
+ </div>
245
+ <?php
246
+ }
 
 
247
  }//end class
dropins/SimpleHistorySidebarSettings.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /*
6
  Dropin Name: Sidebar with link to settings
@@ -8,84 +8,84 @@ Dropin URI: http://simple-history.com/
8
  Author: Pär Thernström
9
  */
10
 
11
- class SimpleHistorySidebarSettings {
12
-
13
- /**
14
- * Simple History isntance
15
- *
16
- * @var object $sh Simple History instance.
17
- */
18
- private $sh;
19
-
20
- /**
21
- * Constructor.
22
- *
23
- * @param object $sh Simple History instance.
24
- */
25
- function __construct( $sh ) {
26
-
27
- $this->init( $sh );
28
-
29
- }
30
-
31
- /**
32
- * Init
33
- *
34
- * @param object $sh Simple History instance.
35
- */
36
- function init( $sh ) {
37
-
38
- $this->sh = $sh;
39
-
40
- add_action( 'simple_history/dropin/sidebar/sidebar_html', array( $this, 'on_sidebar_html' ), 5 );
41
-
42
- }
43
-
44
- /**
45
- * Output HTML
46
- */
47
- function on_sidebar_html() {
48
-
49
- ?>
50
-
51
- <div class="postbox">
52
-
53
- <h3 class="hndle"><?php esc_html_e( 'Settings', 'simple-history' ) ?></h3>
54
-
55
- <div class="inside">
56
-
57
- <p>
58
- <?php
59
-
60
- /*
61
- Visit the settings page to change the number of items to show and
62
- where to show
63
- rss feed
64
- clear log
65
-
66
- - Visit the settings page to change the number of events to show, to get
67
- - Visit the settings page
68
- */
69
- printf(
70
- wp_kses(
71
- /* translators: 1: URL to settings page */
72
- __( '<a href="%1$s">Visit the settings page</a> to change things like the number of events to show and to get access to the RSS feed with all events, and more.', 'simple-history' ),
73
- array(
74
- 'a' => array(
75
- 'href' => array(),
76
- ),
77
- )
78
- ),
79
- esc_url( menu_page_url( SimpleHistory::SETTINGS_MENU_SLUG, false ) )
80
- );
81
- ?>
82
- </p>
83
-
84
- </div>
85
- </div>
86
-
87
- <?php
88
-
89
- }
90
-
91
  }
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: Sidebar with link to settings
8
  Author: Pär Thernström
9
  */
10
 
11
+ class SimpleHistorySidebarSettings
12
+ {
13
+
14
+ /**
15
+ * Simple History isntance
16
+ *
17
+ * @var object $sh Simple History instance.
18
+ */
19
+ private $sh;
20
+
21
+ /**
22
+ * Constructor.
23
+ *
24
+ * @param object $sh Simple History instance.
25
+ */
26
+ function __construct($sh)
27
+ {
28
+
29
+ $this->init($sh);
30
+ }
31
+
32
+ /**
33
+ * Init
34
+ *
35
+ * @param object $sh Simple History instance.
36
+ */
37
+ function init($sh)
38
+ {
39
+
40
+ $this->sh = $sh;
41
+
42
+ add_action('simple_history/dropin/sidebar/sidebar_html', array( $this, 'on_sidebar_html' ), 5);
43
+ }
44
+
45
+ /**
46
+ * Output HTML
47
+ */
48
+ function on_sidebar_html()
49
+ {
50
+
51
+ ?>
52
+
53
+ <div class="postbox">
54
+
55
+ <h3 class="hndle"><?php esc_html_e('Settings', 'simple-history') ?></h3>
56
+
57
+ <div class="inside">
58
+
59
+ <p>
60
+ <?php
61
+
62
+ /*
63
+ Visit the settings page to change the number of items to show and
64
+ where to show
65
+ rss feed
66
+ clear log
67
+
68
+ - Visit the settings page to change the number of events to show, to get
69
+ - Visit the settings page
70
+ */
71
+ printf(
72
+ wp_kses(
73
+ /* translators: 1: URL to settings page */
74
+ __('<a href="%1$s">Visit the settings page</a> to change things like the number of events to show and to get access to the RSS feed with all events, and more.', 'simple-history'),
75
+ array(
76
+ 'a' => array(
77
+ 'href' => array(),
78
+ ),
79
+ )
80
+ ),
81
+ esc_url(menu_page_url(SimpleHistory::SETTINGS_MENU_SLUG, false))
82
+ );
83
+ ?>
84
+ </p>
85
+
86
+ </div>
87
+ </div>
88
+
89
+ <?php
90
+ }
91
  }
dropins/SimpleHistorySidebarStats.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /*
6
  Dropin Name: Sidebar with short stats
@@ -8,240 +8,237 @@ Dropin URI: http://simple-history.com/
8
  Author: Pär Thernström
9
  */
10
 
11
- class SimpleHistorySidebarStats {
 
 
 
12
 
13
- private $sh;
 
 
 
 
 
 
 
14
 
15
- function __construct( $sh ) {
16
 
17
- $this->init( $sh );
18
 
19
- }
20
 
21
- function init( $sh ) {
 
22
 
23
- $this->sh = $sh;
 
24
 
25
- add_action( 'simple_history/dropin/sidebar/sidebar_html', array( $this, 'on_sidebar_html' ), 5 );
 
26
 
27
- add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'on_admin_enqueue_scripts' ) );
 
28
 
29
- add_action( 'simple_history/admin_footer', array( $this, 'on_admin_footer' ) );
 
30
 
31
- }
 
 
 
32
 
33
- public function on_admin_enqueue_scripts() {
34
 
35
- wp_enqueue_script( 'simple_history_chart.js', SIMPLE_HISTORY_DIR_URL . 'js/Chart.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION, true );
36
 
37
- }
 
 
38
 
39
- function on_admin_footer() {
 
 
40
 
41
- ?>
42
- <script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
- /**
45
- * JavaScript for SimpleHistory_SidebarChart
46
- */
47
- (function($) {
48
 
49
- $(function() {
 
50
 
51
- var ctx = $(".SimpleHistory_SidebarChart_ChartCanvas");
52
 
53
- if ( ! ctx.length ) {
54
- return;
55
- }
 
56
 
57
- var chartLabels = JSON.parse( $(".SimpleHistory_SidebarChart_ChartLabels").val() );
58
- var chartLabelsToDates = JSON.parse( $(".SimpleHistory_SidebarChart_ChartLabelsToDates").val() );
59
- var chartDatasetData = JSON.parse( $(".SimpleHistory_SidebarChart_ChartDatasetData").val() );
60
 
61
- var myChart = new Simple_History_Chart(ctx, {
62
- type: 'bar',
63
- data: {
64
- labels: chartLabels,
65
- datasets: [{
66
- data: chartDatasetData,
67
- backgroundColor: "rgb(210,210,210)",
68
- hoverBackgroundColor: "rgb(175,175,175)",
69
- }]
70
- },
71
- options: {
72
- legend: {
73
- display: false
74
- },
75
- scales: {
76
- yAxes: [{
77
- ticks: {
78
- beginAtZero:true
79
- },
80
- }],
81
- xAxes: [{
82
- display: false
83
- }]
84
- },
85
- onClick: clickChart
86
- },
87
- });
88
 
 
 
 
89
 
90
- // when chart is clicked determine what value/day was clicked
91
- function clickChart(e) {
92
 
93
- var chartElmClicked = this.getElementAtEvent(e)[0];
 
94
 
95
- if (!chartElmClicked || !chartElmClicked._index) {
96
- console.log("No value found for click");
97
- return;
98
- }
99
 
100
- var label = this.data.labels[chartElmClicked._index];
101
- // var value = this.data.datasets[chartElmClicked._datasetIndex].data[chartElmClicked._index];
102
 
103
- // now we have the label which is like "July 23" or "23 juli" depending on language
104
- // look for that label value in chartLabelsToDates and there we get the date in format Y-m-d
105
- //console.log("chartLabelsToDates", chartLabelsToDates);
106
- var labelDate;
107
- for (idx in chartLabelsToDates) {
108
- if (label == chartLabelsToDates[idx].label) {
109
- //console.log(chartLabelsToDates[idx]);
110
- labelDate = chartLabelsToDates[idx];
111
- }
112
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
- if (!labelDate) {
115
- return;
116
- }
117
 
118
- // got a date, now reload the history/post search filter form again
119
- var labelDateParts = labelDate.date.split("-"); ["2016", "07", "18"]
 
 
120
 
121
- // show custom date range
122
- $(".SimpleHistory__filters__filter--date").val("customRange").trigger("change");
 
123
 
124
- // set values, same for both from and to because we only want to show one day
125
- SimpleHistoryFilterDropin.$elms.filter_form.find("[name='from_aa'], [name='to_aa']").val(labelDateParts[0]);
126
- SimpleHistoryFilterDropin.$elms.filter_form.find("[name='from_jj'], [name='to_jj']").val(labelDateParts[2]);
127
- SimpleHistoryFilterDropin.$elms.filter_form.find("[name='from_mm'], [name='to_mm']").val(labelDateParts[1]);
128
 
129
- SimpleHistoryFilterDropin.$elms.filter_form.trigger("submit");
 
 
130
 
131
- }
 
 
 
132
 
133
- });
 
 
 
 
 
134
 
135
- })(jQuery);
136
 
137
- </script>
 
 
 
138
 
139
- <?php
 
 
 
 
 
 
140
 
141
- }
142
 
143
- function on_sidebar_html() {
 
 
 
 
144
 
145
- $num_days = 28;
 
 
 
 
146
 
147
- $num_events_per_day_for_period = $this->sh->get_num_events_per_day_last_n_days( $num_days );
 
 
 
 
148
 
149
- // Period = all dates, so empty ones don't get lost
150
- $period_start_date = DateTime::createFromFormat( 'U', strtotime( "-$num_days days" ) );
151
- $period_end_date = DateTime::createFromFormat( 'U', time() );
152
- $interval = DateInterval::createFromDateString( '1 day' );
153
- $period = new DatePeriod( $period_start_date, $interval, $period_end_date->add( date_interval_create_from_date_string( '1 days' ) ) );
154
-
155
- ?>
156
-
157
- <div class="postbox">
158
-
159
- <h3 class="hndle"><?php _e( 'Stats', 'simple-history' ) ?></h3>
160
-
161
- <div class="inside">
162
-
163
- <p>
164
- <?php
165
-
166
- printf(
167
- __( '<b>%1$s events</b> have been logged the last <b>%2$s days</b>.', 'simple-history' ),
168
- $this->sh->get_num_events_last_n_days( $num_days ),
169
- number_format_i18n( $num_days )
170
- );
171
-
172
- ?>
173
- </p>
174
-
175
- <!-- wrapper div so sidebar does not "jump" when loading. so annoying. -->
176
- <div style="position: relative; height: 0; overflow: hidden; padding-bottom: 40%;">
177
- <canvas style="position: absolute; left: 0; right: 0;" class="SimpleHistory_SidebarChart_ChartCanvas" width="100" height="40"></canvas>
178
- </div>
179
-
180
- <p class="SimpleHistory_SidebarChart_ChartDescription" style="font-style: italic; color: #777; text-align: center;">
181
- <?php _e( 'Number of events per day.', 'simple-history' ) ?>
182
- </p>
183
-
184
- <?php
185
-
186
- $arr_labels = array();
187
- $arr_labels_to_datetime = array();
188
- $arr_dataset_data = array();
189
-
190
- foreach ( $period as $dt ) {
191
-
192
- $datef = _x( 'M j', 'stats: date in rows per day chart', 'simple-history' );
193
- $str_date = date_i18n( $datef, $dt->getTimestamp() );
194
- $str_date_ymd = date( 'Y-m-d', $dt->getTimestamp() );
195
-
196
- // Get data for this day, if exist
197
- // Day in object is in format '2014-09-07'
198
- $yearDate = $dt->format( 'Y-m-d' );
199
- $day_data = wp_filter_object_list( $num_events_per_day_for_period, array(
200
- 'yearDate' => $yearDate,
201
- ) );
202
-
203
- $arr_labels[] = $str_date;
204
-
205
- $arr_labels_to_datetime[] = array(
206
- 'label' => $str_date,
207
- 'date' => $str_date_ymd,
208
- );
209
-
210
- if ( $day_data ) {
211
-
212
- $day_data = reset( $day_data );
213
- $arr_dataset_data[] = $day_data->count;
214
-
215
- } else {
216
- $arr_dataset_data[] = 0;
217
- }
218
- }
219
-
220
- ?>
221
-
222
- <input
223
- type="hidden"
224
- class="SimpleHistory_SidebarChart_ChartLabels"
225
- value="<?php esc_attr_e( json_encode( $arr_labels ) ) ?>"
226
- />
227
-
228
- <input
229
- type="hidden"
230
- class="SimpleHistory_SidebarChart_ChartLabelsToDates"
231
- value="<?php esc_attr_e( json_encode( $arr_labels_to_datetime ) ) ?>"
232
- />
233
-
234
- <input
235
- type="hidden"
236
- class="SimpleHistory_SidebarChart_ChartDatasetData"
237
- value="<?php esc_attr_e( json_encode( $arr_dataset_data ) ) ?>"
238
- />
239
-
240
- </div>
241
- </div>
242
-
243
- <?php
244
-
245
- }
246
 
 
 
247
  }
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: Sidebar with short stats
8
  Author: Pär Thernström
9
  */
10
 
11
+ class SimpleHistorySidebarStats
12
+ {
13
+
14
+ private $sh;
15
 
16
+ function __construct($sh)
17
+ {
18
+
19
+ $this->init($sh);
20
+ }
21
+
22
+ function init($sh)
23
+ {
24
 
25
+ $this->sh = $sh;
26
 
27
+ add_action('simple_history/dropin/sidebar/sidebar_html', array( $this, 'on_sidebar_html' ), 5);
28
 
29
+ add_action('simple_history/enqueue_admin_scripts', array( $this, 'on_admin_enqueue_scripts' ));
30
 
31
+ add_action('simple_history/admin_footer', array( $this, 'on_admin_footer' ));
32
+ }
33
 
34
+ public function on_admin_enqueue_scripts()
35
+ {
36
 
37
+ wp_enqueue_script('simple_history_chart.js', SIMPLE_HISTORY_DIR_URL . 'js/Chart.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION, true);
38
+ }
39
 
40
+ function on_admin_footer()
41
+ {
42
 
43
+ ?>
44
+ <script>
45
 
46
+ /**
47
+ * JavaScript for SimpleHistory_SidebarChart
48
+ */
49
+ (function($) {
50
 
51
+ $(function() {
52
 
53
+ var ctx = $(".SimpleHistory_SidebarChart_ChartCanvas");
54
 
55
+ if ( ! ctx.length ) {
56
+ return;
57
+ }
58
 
59
+ var chartLabels = JSON.parse( $(".SimpleHistory_SidebarChart_ChartLabels").val() );
60
+ var chartLabelsToDates = JSON.parse( $(".SimpleHistory_SidebarChart_ChartLabelsToDates").val() );
61
+ var chartDatasetData = JSON.parse( $(".SimpleHistory_SidebarChart_ChartDatasetData").val() );
62
 
63
+ var myChart = new Simple_History_Chart(ctx, {
64
+ type: 'bar',
65
+ data: {
66
+ labels: chartLabels,
67
+ datasets: [{
68
+ data: chartDatasetData,
69
+ backgroundColor: "rgb(210,210,210)",
70
+ hoverBackgroundColor: "rgb(175,175,175)",
71
+ }]
72
+ },
73
+ options: {
74
+ legend: {
75
+ display: false
76
+ },
77
+ scales: {
78
+ yAxes: [{
79
+ ticks: {
80
+ beginAtZero:true
81
+ },
82
+ }],
83
+ xAxes: [{
84
+ display: false
85
+ }]
86
+ },
87
+ onClick: clickChart
88
+ },
89
+ });
90
 
 
 
 
 
91
 
92
+ // when chart is clicked determine what value/day was clicked
93
+ function clickChart(e) {
94
 
95
+ var chartElmClicked = this.getElementAtEvent(e)[0];
96
 
97
+ if (!chartElmClicked || !chartElmClicked._index) {
98
+ console.log("No value found for click");
99
+ return;
100
+ }
101
 
102
+ var label = this.data.labels[chartElmClicked._index];
103
+ // var value = this.data.datasets[chartElmClicked._datasetIndex].data[chartElmClicked._index];
 
104
 
105
+ // now we have the label which is like "July 23" or "23 juli" depending on language
106
+ // look for that label value in chartLabelsToDates and there we get the date in format Y-m-d
107
+ //console.log("chartLabelsToDates", chartLabelsToDates);
108
+ var labelDate;
109
+ for (idx in chartLabelsToDates) {
110
+ if (label == chartLabelsToDates[idx].label) {
111
+ //console.log(chartLabelsToDates[idx]);
112
+ labelDate = chartLabelsToDates[idx];
113
+ }
114
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
+ if (!labelDate) {
117
+ return;
118
+ }
119
 
120
+ // got a date, now reload the history/post search filter form again
121
+ var labelDateParts = labelDate.date.split("-"); ["2016", "07", "18"]
122
 
123
+ // show custom date range
124
+ $(".SimpleHistory__filters__filter--date").val("customRange").trigger("change");
125
 
126
+ // set values, same for both from and to because we only want to show one day
127
+ SimpleHistoryFilterDropin.$elms.filter_form.find("[name='from_aa'], [name='to_aa']").val(labelDateParts[0]);
128
+ SimpleHistoryFilterDropin.$elms.filter_form.find("[name='from_jj'], [name='to_jj']").val(labelDateParts[2]);
129
+ SimpleHistoryFilterDropin.$elms.filter_form.find("[name='from_mm'], [name='to_mm']").val(labelDateParts[1]);
130
 
131
+ SimpleHistoryFilterDropin.$elms.filter_form.trigger("submit");
 
132
 
133
+ }
134
+
135
+ });
136
+
137
+ })(jQuery);
138
+
139
+ </script>
140
+
141
+ <?php
142
+ }
143
+
144
+ function on_sidebar_html()
145
+ {
146
+
147
+ $num_days = 28;
148
+
149
+ $num_events_per_day_for_period = $this->sh->get_num_events_per_day_last_n_days($num_days);
150
+
151
+ // Period = all dates, so empty ones don't get lost
152
+ $period_start_date = DateTime::createFromFormat('U', strtotime("-$num_days days"));
153
+ $period_end_date = DateTime::createFromFormat('U', time());
154
+ $interval = DateInterval::createFromDateString('1 day');
155
+ $period = new DatePeriod($period_start_date, $interval, $period_end_date->add(date_interval_create_from_date_string('1 days')));
156
+
157
+ ?>
158
+
159
+ <div class="postbox">
160
+
161
+ <h3 class="hndle"><?php _e('Stats', 'simple-history') ?></h3>
162
+
163
+ <div class="inside">
164
+
165
+ <p>
166
+ <?php
167
+
168
+ printf(
169
+ __('<b>%1$s events</b> have been logged the last <b>%2$s days</b>.', 'simple-history'),
170
+ $this->sh->get_num_events_last_n_days($num_days),
171
+ number_format_i18n($num_days)
172
+ );
173
 
174
+ ?>
175
+ </p>
 
176
 
177
+ <!-- wrapper div so sidebar does not "jump" when loading. so annoying. -->
178
+ <div style="position: relative; height: 0; overflow: hidden; padding-bottom: 40%;">
179
+ <canvas style="position: absolute; left: 0; right: 0;" class="SimpleHistory_SidebarChart_ChartCanvas" width="100" height="40"></canvas>
180
+ </div>
181
 
182
+ <p class="SimpleHistory_SidebarChart_ChartDescription" style="font-style: italic; color: #777; text-align: center;">
183
+ <?php _e('Number of events per day.', 'simple-history') ?>
184
+ </p>
185
 
186
+ <?php
 
 
 
187
 
188
+ $arr_labels = array();
189
+ $arr_labels_to_datetime = array();
190
+ $arr_dataset_data = array();
191
 
192
+ foreach ($period as $dt) {
193
+ $datef = _x('M j', 'stats: date in rows per day chart', 'simple-history');
194
+ $str_date = date_i18n($datef, $dt->getTimestamp());
195
+ $str_date_ymd = date('Y-m-d', $dt->getTimestamp());
196
 
197
+ // Get data for this day, if exist
198
+ // Day in object is in format '2014-09-07'
199
+ $yearDate = $dt->format('Y-m-d');
200
+ $day_data = wp_filter_object_list($num_events_per_day_for_period, array(
201
+ 'yearDate' => $yearDate,
202
+ ));
203
 
204
+ $arr_labels[] = $str_date;
205
 
206
+ $arr_labels_to_datetime[] = array(
207
+ 'label' => $str_date,
208
+ 'date' => $str_date_ymd,
209
+ );
210
 
211
+ if ($day_data) {
212
+ $day_data = reset($day_data);
213
+ $arr_dataset_data[] = $day_data->count;
214
+ } else {
215
+ $arr_dataset_data[] = 0;
216
+ }
217
+ }
218
 
219
+ ?>
220
 
221
+ <input
222
+ type="hidden"
223
+ class="SimpleHistory_SidebarChart_ChartLabels"
224
+ value="<?php esc_attr_e(json_encode($arr_labels)) ?>"
225
+ />
226
 
227
+ <input
228
+ type="hidden"
229
+ class="SimpleHistory_SidebarChart_ChartLabelsToDates"
230
+ value="<?php esc_attr_e(json_encode($arr_labels_to_datetime)) ?>"
231
+ />
232
 
233
+ <input
234
+ type="hidden"
235
+ class="SimpleHistory_SidebarChart_ChartDatasetData"
236
+ value="<?php esc_attr_e(json_encode($arr_dataset_data)) ?>"
237
+ />
238
 
239
+ </div>
240
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
242
+ <?php
243
+ }
244
  }
dropins/SimpleHistoryWPCLIDropin.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /*
6
  Dropin Name: WP CLI
@@ -8,158 +8,159 @@ Dropin URI: http://simple-history.com/
8
  Author: Pär Thernström
9
  */
10
 
11
- class SimpleHistoryWPCLIDropin {
12
-
13
- // Simple History instance
14
- private $sh;
15
-
16
- function __construct( $sh ) {
17
-
18
- $this->sh = $sh;
19
- // add_action( 'admin_menu', array($this, 'add_settings'), 50 );
20
- // add_action( 'plugin_row_meta', array($this, 'action_plugin_row_meta'), 10, 2);
21
- if ( defined( 'WP_CLI' ) && WP_CLI ) {
22
- $this->register_commands();
23
- }
24
-
25
- }
26
-
27
- private function register_commands() {
28
- $commandConfigurationOptions = array(
29
- 'shortdesc' => 'Lists the history log',
30
- 'synopsis' => array(
31
- array(
32
- 'type' => 'assoc',
33
- 'name' => 'format',
34
- 'optional' => true,
35
- 'default' => 'table',
36
- 'options' => array( 'table', 'json', 'csv', 'yaml' ),
37
- ),
38
- array(
39
- 'type' => 'assoc',
40
- 'name' => 'count',
41
- 'optional' => true,
42
- 'default' => '10',
43
- ),
44
- ),
45
- 'when' => 'after_wp_load',
46
- );
47
-
48
- WP_CLI::add_command( 'simple-history list', array( $this, 'commandList' ), $commandConfigurationOptions );
49
- }
50
-
51
- private function getInitiatorTextFromRow( $row ) {
52
- if ( ! isset( $row->initiator ) ) {
53
- return false;
54
- }
55
-
56
- $initiator = $row->initiator;
57
- $initiatorText = '';
58
-
59
- switch ( $initiator ) {
60
- case 'wp':
61
- $initiatorText = 'WordPress';
62
- break;
63
- case 'wp_cli':
64
- $initiatorText = 'WP-CLI';
65
- break;
66
- case 'wp_user':
67
- $user_id = isset( $row->context['_user_id'] ) ? $row->context['_user_id'] : null;
68
-
69
- if ( $user_id > 0 && $user = get_user_by( 'id', $user_id ) ) {
70
- // User still exists
71
- // Get user role, as done in user-edit.php
72
- $wp_roles = $GLOBALS['wp_roles'];
73
- $all_roles = (array) $wp_roles->roles;
74
- $user_roles = array_intersect( array_values( (array) $user->roles ), array_keys( (array) $wp_roles->roles ) );
75
- $user_role = array_shift( $user_roles );
76
-
77
- $initiatorText = sprintf(
78
- '%1$s (%2$s)' ,
79
- $user->user_login, // 1
80
- $user->user_email // 2
81
- );
82
-
83
- } elseif ( $user_id > 0 ) {
84
- // Sender was a user, but user is deleted now
85
- $initiatorText = sprintf(
86
- __( 'Deleted user (had id %1$s, email %2$s, login %3$s)', 'simple-history' ),
87
- $context['_user_id'], // 1
88
- $context['_user_email'], // 2
89
- $context['_user_login'] // 3
90
- );
91
- } // End if().
92
- break;
93
- case 'web_user':
94
- $initiatorText = __( 'Anonymous web user', 'simple-history' );
95
- break;
96
- case 'other':
97
- $initiatorText = _x( 'Other', 'Event header output, when initiator is unknown', 'simple-history' );
98
- break;
99
- default:
100
- $initiatorText = $initiator;
101
- }// End switch().
102
-
103
- return $initiatorText;
104
- }
105
-
106
- /**
107
- * The function for the command "list"
108
- */
109
- public function commandList( $args, $assoc_args ) {
110
-
111
- if ( ! is_numeric( $assoc_args['count'] ) ) {
112
- WP_CLI::error( __( 'Error: parameter "count" must be a number', 'simple-history' ) );
113
- }
114
-
115
- // Override capability check: if you can run wp cli commands you can read all loggers
116
- add_action( 'simple_history/loggers_user_can_read/can_read_single_logger', '__return_true', 10, 3 );
117
-
118
- // WP_CLI::log( sprintf( 'Showing %1$d events from Simple History', $assoc_args["count"] ) );
119
- $query = new SimpleHistoryLogQuery();
120
-
121
- $query_args = array(
122
- 'paged' => 1,
123
- 'posts_per_page' => $assoc_args['count'],
124
- );
125
-
126
- $events = $query->query( $query_args );
127
-
128
- // A cleaned version of the events, formatted for wp cli table output
129
- $eventsCleaned = array();
130
-
131
- foreach ( $events['log_rows'] as $row ) {
132
- $header_output = $this->sh->getLogRowHeaderOutput( $row );
133
- $text_output = $this->sh->getLogRowPlainTextOutput( $row );
134
- // $details_output = $this->sh->getLogRowDetailsOutput($row);
135
- $header_output = strip_tags( html_entity_decode( $header_output, ENT_QUOTES, 'UTF-8' ) );
136
- $header_output = trim( preg_replace( '/\s\s+/', ' ', $header_output ) );
137
-
138
- $text_output = strip_tags( html_entity_decode( $text_output, ENT_QUOTES, 'UTF-8' ) );
139
-
140
- $eventsCleaned[] = array(
141
- 'date' => get_date_from_gmt( $row->date ),
142
- // "initiator" => $row->initiator,
143
- 'initiator' => $this->getInitiatorTextFromRow( $row ),
144
- 'logger' => $row->logger,
145
- 'level' => $row->level,
146
- 'who_when' => $header_output,
147
- 'description' => $text_output,
148
- 'count' => $row->subsequentOccasions,
149
- // "details" => $details_output
150
- );
151
- }
152
-
153
- $fields = array(
154
- 'date',
155
- 'initiator',
156
- 'description',
157
- 'level',
158
- 'count',
159
- );
160
-
161
- WP_CLI\Utils\format_items( $assoc_args['format'], $eventsCleaned, $fields );
162
-
163
- }
164
-
 
165
  }
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: WP CLI
8
  Author: Pär Thernström
9
  */
10
 
11
+ class SimpleHistoryWPCLIDropin
12
+ {
13
+
14
+ // Simple History instance
15
+ private $sh;
16
+
17
+ function __construct($sh)
18
+ {
19
+
20
+ $this->sh = $sh;
21
+ // add_action( 'admin_menu', array($this, 'add_settings'), 50 );
22
+ // add_action( 'plugin_row_meta', array($this, 'action_plugin_row_meta'), 10, 2);
23
+ if (defined('WP_CLI') && WP_CLI) {
24
+ $this->register_commands();
25
+ }
26
+ }
27
+
28
+ private function register_commands()
29
+ {
30
+ $commandConfigurationOptions = array(
31
+ 'shortdesc' => 'Lists the history log',
32
+ 'synopsis' => array(
33
+ array(
34
+ 'type' => 'assoc',
35
+ 'name' => 'format',
36
+ 'optional' => true,
37
+ 'default' => 'table',
38
+ 'options' => array( 'table', 'json', 'csv', 'yaml' ),
39
+ ),
40
+ array(
41
+ 'type' => 'assoc',
42
+ 'name' => 'count',
43
+ 'optional' => true,
44
+ 'default' => '10',
45
+ ),
46
+ ),
47
+ 'when' => 'after_wp_load',
48
+ );
49
+
50
+ WP_CLI::add_command('simple-history list', array( $this, 'commandList' ), $commandConfigurationOptions);
51
+ }
52
+
53
+ private function getInitiatorTextFromRow($row)
54
+ {
55
+ if (! isset($row->initiator)) {
56
+ return false;
57
+ }
58
+
59
+ $initiator = $row->initiator;
60
+ $initiatorText = '';
61
+
62
+ switch ($initiator) {
63
+ case 'wp':
64
+ $initiatorText = 'WordPress';
65
+ break;
66
+ case 'wp_cli':
67
+ $initiatorText = 'WP-CLI';
68
+ break;
69
+ case 'wp_user':
70
+ $user_id = isset($row->context['_user_id']) ? $row->context['_user_id'] : null;
71
+
72
+ if ($user_id > 0 && $user = get_user_by('id', $user_id)) {
73
+ // User still exists
74
+ // Get user role, as done in user-edit.php
75
+ $wp_roles = $GLOBALS['wp_roles'];
76
+ $all_roles = (array) $wp_roles->roles;
77
+ $user_roles = array_intersect(array_values((array) $user->roles), array_keys((array) $wp_roles->roles));
78
+ $user_role = array_shift($user_roles);
79
+
80
+ $initiatorText = sprintf(
81
+ '%1$s (%2$s)',
82
+ $user->user_login, // 1
83
+ $user->user_email // 2
84
+ );
85
+ } elseif ($user_id > 0) {
86
+ // Sender was a user, but user is deleted now
87
+ $initiatorText = sprintf(
88
+ __('Deleted user (had id %1$s, email %2$s, login %3$s)', 'simple-history'),
89
+ $context['_user_id'], // 1
90
+ $context['_user_email'], // 2
91
+ $context['_user_login'] // 3
92
+ );
93
+ } // End if().
94
+ break;
95
+ case 'web_user':
96
+ $initiatorText = __('Anonymous web user', 'simple-history');
97
+ break;
98
+ case 'other':
99
+ $initiatorText = _x('Other', 'Event header output, when initiator is unknown', 'simple-history');
100
+ break;
101
+ default:
102
+ $initiatorText = $initiator;
103
+ }// End switch().
104
+
105
+ return $initiatorText;
106
+ }
107
+
108
+ /**
109
+ * The function for the command "list"
110
+ */
111
+ public function commandList($args, $assoc_args)
112
+ {
113
+
114
+ if (! is_numeric($assoc_args['count'])) {
115
+ WP_CLI::error(__('Error: parameter "count" must be a number', 'simple-history'));
116
+ }
117
+
118
+ // Override capability check: if you can run wp cli commands you can read all loggers
119
+ add_action('simple_history/loggers_user_can_read/can_read_single_logger', '__return_true', 10, 3);
120
+
121
+ // WP_CLI::log( sprintf( 'Showing %1$d events from Simple History', $assoc_args["count"] ) );
122
+ $query = new SimpleHistoryLogQuery();
123
+
124
+ $query_args = array(
125
+ 'paged' => 1,
126
+ 'posts_per_page' => $assoc_args['count'],
127
+ );
128
+
129
+ $events = $query->query($query_args);
130
+
131
+ // A cleaned version of the events, formatted for wp cli table output
132
+ $eventsCleaned = array();
133
+
134
+ foreach ($events['log_rows'] as $row) {
135
+ $header_output = $this->sh->getLogRowHeaderOutput($row);
136
+ $text_output = $this->sh->getLogRowPlainTextOutput($row);
137
+ // $details_output = $this->sh->getLogRowDetailsOutput($row);
138
+ $header_output = strip_tags(html_entity_decode($header_output, ENT_QUOTES, 'UTF-8'));
139
+ $header_output = trim(preg_replace('/\s\s+/', ' ', $header_output));
140
+
141
+ $text_output = strip_tags(html_entity_decode($text_output, ENT_QUOTES, 'UTF-8'));
142
+
143
+ $eventsCleaned[] = array(
144
+ 'date' => get_date_from_gmt($row->date),
145
+ // "initiator" => $row->initiator,
146
+ 'initiator' => $this->getInitiatorTextFromRow($row),
147
+ 'logger' => $row->logger,
148
+ 'level' => $row->level,
149
+ 'who_when' => $header_output,
150
+ 'description' => $text_output,
151
+ 'count' => $row->subsequentOccasions,
152
+ // "details" => $details_output
153
+ );
154
+ }
155
+
156
+ $fields = array(
157
+ 'date',
158
+ 'initiator',
159
+ 'description',
160
+ 'level',
161
+ 'count',
162
+ );
163
+
164
+ WP_CLI\Utils\format_items($assoc_args['format'], $eventsCleaned, $fields);
165
+ }
166
  }
examples/example-dropin.php CHANGED
@@ -11,58 +11,57 @@ exit;
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
 
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
  * This is the class that does the main work!
22
  */
23
+ class AddSettingsPageTab
24
+ {
 
 
 
 
 
 
 
 
 
 
 
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
 
33
+ $this->sh = $sh;
34
 
35
+ $this->init();
36
+ }
37
 
38
+ function init()
39
+ {
 
 
 
40
 
41
+ add_action('init', array( $this, 'add_settings_tab' ));
42
+ }
43
 
44
+ function add_settings_tab()
45
+ {
46
 
47
+ $this->sh->registerSettingsTab(array(
48
+ 'slug' => 'my_unique_settings_tab_slug',
49
+ 'name' => __('Example tab', 'simple-history'),
50
+ 'function' => array( $this, 'settings_tab_output' ),
51
+ ));
52
+ }
53
 
54
+ function settings_tab_output()
55
+ {
56
 
57
+ ?>
58
 
59
+ <h3>Hi there!</h3>
60
 
61
+ <p>I'm the output from on settings tab.</p>
62
 
63
+ <?php
64
+ }
65
  } // end class
66
 
67
 
examples/example-logger.php CHANGED
@@ -11,86 +11,85 @@ exit;
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_logger', function( $simpleHistory ) {
15
-
16
- $simpleHistory->register_logger( 'FourOhFourLogger' );
17
 
 
18
  });
19
 
20
  // We make sure that the SimpleLogger class exists before trying to extend it.
21
  // This prevents error if the Simple History plugin gets inactivated.
22
- if ( class_exists( 'SimpleLogger' ) ) {
23
-
24
- /**
25
- * This is the class that does the main work!
26
- */
27
- class FourOhFourLogger extends SimpleLogger {
28
-
29
- /**
30
- * The slug is ised to identify this logger in various places.
31
- * We use the name of the class too keep it simple.
32
- */
33
- public $slug = __CLASS__;
34
-
35
- /**
36
- * Return information about this logger.
37
- * Used to show info about the logger at various places.
38
- */
39
- function getInfo() {
40
-
41
- $arr_info = array(
42
- 'name' => '404 Logger',
43
- 'description' => 'Logs access to pages that result in page not found errors (error code 404)',
44
- 'capability' => 'edit_pages',
45
- 'messages' => array(
46
- 'page_not_found' => __( 'Got a 404-page when trying to visit "{request_uri}"', 'simple-history' ),
47
- ),
48
- 'labels' => array(
49
- 'search' => array(
50
- 'label' => _x( 'Pages not found (404 errors)', 'User logger: 404', 'simple-history' ),
51
- 'options' => array(
52
- _x( 'Pages not found', 'User logger: 404', 'simple-history' ) => array(
53
- 'page_not_found',
54
- ),
55
- ),
56
- ), // end search
57
- ), // end labels
58
- );
59
-
60
- return $arr_info;
61
-
62
- }
63
-
64
- /**
65
- * When Simple History has loaded this logger it automagically
66
- * calls a loaded() function. This is where you add your actions
67
- * and other logger functionality.
68
- */
69
- function loaded() {
70
-
71
- // Call a function when WordPress finds a 404 page
72
- add_action( '404_template', array( $this, 'on_404_template' ), 10, 1 );
73
-
74
- }
75
-
76
- /**
77
- * Function that is called when WordPress finds a 404 page.
78
- * It collects some info and then it logs a warning message
79
- * to the log.
80
- */
81
- function on_404_template( $template ) {
82
-
83
- $context = array(
84
- '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
85
- 'request_uri' => isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '',
86
- 'http_referer' => isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : '',
87
- );
88
-
89
- $this->warningMessage( 'page_not_found', $context );
90
-
91
- return $template;
92
-
93
- }
94
-
95
- }
96
  }// End if().
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_logger', function ($simpleHistory) {
 
 
15
 
16
+ $simpleHistory->register_logger('FourOhFourLogger');
17
  });
18
 
19
  // We make sure that the SimpleLogger class exists before trying to extend it.
20
  // This prevents error if the Simple History plugin gets inactivated.
21
+ if (class_exists('SimpleLogger')) {
22
+
23
+ /**
24
+ * This is the class that does the main work!
25
+ */
26
+ class FourOhFourLogger extends SimpleLogger
27
+ {
28
+
29
+ /**
30
+ * The slug is ised to identify this logger in various places.
31
+ * We use the name of the class too keep it simple.
32
+ */
33
+ public $slug = __CLASS__;
34
+
35
+ /**
36
+ * Return information about this logger.
37
+ * Used to show info about the logger at various places.
38
+ */
39
+ function getInfo()
40
+ {
41
+
42
+ $arr_info = array(
43
+ 'name' => '404 Logger',
44
+ 'description' => 'Logs access to pages that result in page not found errors (error code 404)',
45
+ 'capability' => 'edit_pages',
46
+ 'messages' => array(
47
+ 'page_not_found' => __('Got a 404-page when trying to visit "{request_uri}"', 'simple-history'),
48
+ ),
49
+ 'labels' => array(
50
+ 'search' => array(
51
+ 'label' => _x('Pages not found (404 errors)', 'User logger: 404', 'simple-history'),
52
+ 'options' => array(
53
+ _x('Pages not found', 'User logger: 404', 'simple-history') => array(
54
+ 'page_not_found',
55
+ ),
56
+ ),
57
+ ), // end search
58
+ ), // end labels
59
+ );
60
+
61
+ return $arr_info;
62
+ }
63
+
64
+ /**
65
+ * When Simple History has loaded this logger it automagically
66
+ * calls a loaded() function. This is where you add your actions
67
+ * and other logger functionality.
68
+ */
69
+ function loaded()
70
+ {
71
+
72
+ // Call a function when WordPress finds a 404 page
73
+ add_action('404_template', array( $this, 'on_404_template' ), 10, 1);
74
+ }
75
+
76
+ /**
77
+ * Function that is called when WordPress finds a 404 page.
78
+ * It collects some info and then it logs a warning message
79
+ * to the log.
80
+ */
81
+ function on_404_template($template)
82
+ {
83
+
84
+ $context = array(
85
+ '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
86
+ 'request_uri' => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '',
87
+ 'http_referer' => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '',
88
+ );
89
+
90
+ $this->warningMessage('page_not_found', $context);
91
+
92
+ return $template;
93
+ }
94
+ }
95
  }// End if().
examples/examples.php CHANGED
@@ -13,7 +13,7 @@ exit;
13
  */
14
 
15
  // Add $_GET, $_POST, and more info to each logged even.
16
- define( 'SIMPLE_HISTORY_LOG_DEBUG', true );
17
 
18
 
19
  /**
@@ -24,65 +24,60 @@ define( 'SIMPLE_HISTORY_LOG_DEBUG', true );
24
  * Remove the "Clear log"-button, so a user with admin access can not clear the log
25
  * and wipe their mischievous behavior from the log.
26
  */
27
- add_filter( 'simple_history/user_can_clear_log', function ( $user_can_clear_log ) {
28
- $user_can_clear_log = false;
29
- return $user_can_clear_log;
30
  });
31
 
32
  // Modify who can read a logger.
33
  // Modify the if part to give users access or no access to a logger.
34
- add_filter( 'simple_history/loggers_user_can_read/can_read_single_logger', function( $user_can_read_logger, $logger_instance, $user_id ) {
35
 
36
- // in this example user with id 3 gets access to the post logger
37
- // while user with id 8 does not get any access to it
38
- if ( $logger_instance->slug == 'SimplePostLogger' && $user_id === 3 ) {
39
- $user_can_read_logger = true;
40
- } elseif ( $logger_instance->slug == 'SimplePostLogger' && $user_id === 9 ) {
41
- $user_can_read_logger = false;
42
- }
43
 
44
- return $user_can_read_logger;
45
-
46
- }, 10, 3 );
47
 
48
 
49
  // Do not log some post types, for example pages and attachments in this case
50
- add_filter( 'simple_history/log/do_log', function( $do_log = null, $level = null, $message = null, $context = null, $logger = null ) {
51
-
52
- $post_types_to_not_log = array(
53
- 'page',
54
- 'attachment',
55
- );
56
-
57
- if ( ( isset( $logger->slug ) && ($logger->slug === 'SimplePostLogger' || $logger->slug === 'SimpleMediaLogger') ) && ( isset( $context['post_type'] ) && in_array( $context['post_type'], $post_types_to_not_log ) ) ) {
58
-
59
- $do_log = false;
60
 
61
- }
 
 
 
62
 
63
- return $do_log;
 
 
64
 
 
65
  }, 10, 5);
66
 
67
  // Disable all logging
68
- add_filter( 'simple_history/log/do_log', '__return_false' );
69
 
70
  /**
71
  * Example that modifies the parameters sent to the message template
72
  * This example will change the post type from "post" or "page" or similar to "my own page type"
73
  */
74
- add_filter( 'simple_history/logger/interpolate/context', function( $context, $message, $row ) {
75
 
76
- if ( empty( $row ) ) {
77
- return $context;
78
- }
79
 
80
- if ( $row->logger == 'SimplePostLogger' && $row->context_message_key == 'post_updated' ) {
81
- $context['post_type'] = 'my own page type';
82
- }
83
-
84
- return $context;
85
 
 
86
  }, 10, 3);
87
 
88
 
@@ -91,11 +86,10 @@ add_filter( 'simple_history/logger/interpolate/context', function( $context, $me
91
  * Change capability required to manage the options page of simple history.
92
  * Default capability is "manage_options"
93
  */
94
- add_filter('simple_history/view_settings_capability', function( $capability ) {
95
-
96
- $capability = 'manage_options';
97
- return $capability;
98
 
 
 
99
  });
100
 
101
 
@@ -104,244 +98,235 @@ add_filter('simple_history/view_settings_capability', function( $capability ) {
104
  * Default capability is "edit_pages". Change to for example "manage options"
105
  * to only allow admins to view the history log.
106
  */
107
- add_filter('simple_history/view_history_capability', function( $capability ) {
108
-
109
- $capability = 'manage_options';
110
- return $capability;
111
 
 
 
112
  });
113
 
114
 
115
  // Skip adding things to the context table during logging.
116
  // Useful if you don't want to add cool and possible super useful info to your logged events.
117
  // Also nice to have if you want to make sure your database does not grow.
118
- add_filter('simple_history/log_insert_context', function( $context, $data ) {
119
 
120
- unset( $context['_user_id'] );
121
- unset( $context['_user_login'] );
122
- unset( $context['_user_email'] );
123
- unset( $context['server_http_user_agent'] );
124
-
125
- return $context;
126
 
 
127
  }, 10, 2);
128
 
129
  // Hide some columns from the detailed context view popup window
130
- add_filter('simple_history/log_html_output_details_table/row_keys_to_show', function( $logRowKeysToShow, $oneLogRow ) {
131
-
132
- $logRowKeysToShow['id'] = false;
133
- $logRowKeysToShow['logger'] = false;
134
- $logRowKeysToShow['level'] = false;
135
- $logRowKeysToShow['message'] = false;
136
 
137
- return $logRowKeysToShow;
 
 
 
138
 
 
139
  }, 10, 2);
140
 
141
 
142
  // Hide some more columns from the detailed context view popup window
143
- add_filter('simple_history/log_html_output_details_table/context_keys_to_show', function( $logRowContextKeysToShow, $oneLogRow ) {
144
 
145
- $logRowContextKeysToShow['plugin_slug'] = false;
146
- $logRowContextKeysToShow['plugin_name'] = false;
147
- $logRowContextKeysToShow['plugin_title'] = false;
148
- $logRowContextKeysToShow['plugin_description'] = false;
149
-
150
- return $logRowContextKeysToShow;
151
 
 
152
  }, 10, 2);
153
 
154
 
155
 
156
  // Allow only the users specified in $allowed_users to show the history page, the history widget on the dashboard, or the history settings page
157
- add_filter( 'simple_history/show_dashboard_page', 'function_show_history_dashboard_or_page' );
158
- add_filter( 'simple_history/show_dashboard_widget', 'function_show_history_dashboard_or_page' );
159
- add_filter( 'simple_history/show_settings_page', 'function_show_history_dashboard_or_page' );
160
- function function_show_history_dashboard_or_page( $show ) {
161
-
162
- $allowed_users = array(
163
- 'user1@example.com',
164
- 'anotheruser@example.com',
165
- );
166
 
167
- $user = wp_get_current_user();
 
 
 
168
 
169
- if ( ! in_array( $user->user_email, $allowed_users ) ) {
170
- $show = false;
171
- }
172
 
173
- return $show;
 
 
174
 
 
175
  }
176
 
177
 
178
  // Skip loading of loggers
179
- add_filter('simple_history/logger/load_logger', function( $load_logger, $oneLoggerFile ) {
180
-
181
- // Don't load loggers for comments or menus, i.e. don't log changes to comments or to menus
182
- if ( in_array( $oneLoggerFile, array( 'SimpleCommentsLogger', 'SimpleMenuLogger' ) ) ) {
183
- $load_logger = false;
184
- }
185
 
186
- return $load_logger;
 
 
 
187
 
 
188
  }, 10, 2);
189
 
190
  /**
191
  * Load only the loggers that are specified in the $do_log_us array
192
  */
193
- add_filter('simple_history/logger/load_logger', function( $load_logger, $logger_basename ) {
194
 
195
- $load_logger = false;
196
- $do_log_us = array( 'SimplePostLogger', 'SimplePluginLogger', 'SimpleLogger' );
197
 
198
- if ( in_array( $logger_basename, $do_log_us ) ) {
199
- $load_logger = true;
200
- }
201
 
202
- return $load_logger;
203
-
204
- }, 10, 2 );
205
 
206
 
207
  // Skip the loading of dropins
208
- add_filter('simple_history/dropin/load_dropin', function( $load_dropin, $dropinFileBasename ) {
209
 
210
- // Don't load the RSS feed dropin
211
- if ( $dropinFileBasename == 'SimpleHistoryRSSDropin' ) {
212
- $load_dropin = false;
213
- }
214
 
215
- // Don't load the dropin that polls for changes
216
- if ( $dropinFileBasename == 'SimpleHistoryNewRowsNotifier' ) {
217
- $load_dropin = false;
218
- }
219
-
220
- return $load_dropin;
221
 
 
222
  }, 10, 2);
223
 
224
 
225
  // Don't log failed logins
226
- add_filter('simple_history/simple_logger/log_message_key', function( $doLog, $loggerSlug, $messageKey, $SimpleLoggerLogLevelsLevel, $context ) {
227
-
228
- // Don't log login attempts to non existing users
229
- if ( 'SimpleUserLogger' == $loggerSlug && 'user_unknown_login_failed' == $messageKey ) {
230
- $doLog = false;
231
- }
232
 
233
- // Don't log failed logins to existing users
234
- if ( 'SimpleUserLogger' == $loggerSlug && 'user_login_failed' == $messageKey ) {
235
- $doLog = false;
236
- }
237
 
238
- return $doLog;
 
 
 
239
 
 
240
  }, 10, 5);
241
 
242
  // Never clear the log (default is 60 days)
243
- add_filter( 'simple_history/db_purge_days_interval', '__return_zero' );
244
 
245
  // Clear items that are older than a 7 days (i.e. keep only the most recent 7 days in the log)
246
- add_filter( 'simple_history/db_purge_days_interval', function( $days ) {
247
-
248
- $days = 7;
249
 
250
- return $days;
251
 
252
- } );
 
253
 
254
  // Don't let anyone - even with the correct secret - view the RSS feed
255
- add_filter( 'simple_history/rss_feed_show', '__return_false' );
256
 
257
  // Skip loading of a dropin completely (in this case the RSS dropin)
258
- add_filter( 'simple_history/dropin/load_dropin_SimpleHistoryRSSDropin', '__return_false' );
259
 
260
  /**
261
  * Example of logging
262
  */
263
 
264
  // This is the easiest and safest way to add messages to the log:
265
- apply_filters( 'simple_history_log', 'This is a logged message' );
266
- apply_filters( 'simple_history_log', 'This is a message with some context added', array(
267
- 'isATestMessage' => 'yup',
268
- 'debugRequestData' => $_REQUEST,
269
- ) );
270
- apply_filters( 'simple_history_log', 'This is another logged message, with another severity level', null, 'debug' );
271
 
272
  // Below is the function way of adding things to the log
273
  // Remember to check that the SimpleLogger function exists before trying to log anything,
274
  // or else your site will break if you disable the Simple History plugin
275
  // (Use the apply_filters method above if you want to stay safer!)
276
- if ( function_exists( 'SimpleLogger' ) ) {
277
- SimpleLogger()->info( 'This is a message added to the log' );
278
  }
279
 
280
  // Add a message to the history log
281
- SimpleLogger()->info( 'This is a message sent to the log' );
282
 
283
  // Add log entries with different severities
284
- SimpleLogger()->warning( "User 'Jessie' deleted user 'Kim'" );
285
- SimpleLogger()->debug( 'Ok, cron job is running!' );
286
 
287
  // Add a message to the history log
288
  // and then add a second log entry with same info and Simple History
289
  // will make these two become an "occasionGroup",
290
  // i.e. collapsing their entries into one expandable log item
291
- SimpleLogger()->info( 'This is a message sent to the log' );
292
- SimpleLogger()->info( 'This is a message sent to the log' );
293
 
294
  // Log entries can have placeholders and context
295
  // This makes log entried translatable and filterable
296
  SimpleLogger()->notice(
297
- 'User {username} edited page {pagename}',
298
- array(
299
- 'username' => 'jessie',
300
- 'pagename' => 'My test page',
301
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
302
- '_user_id' => 5,
303
- '_user_login' => 'jess',
304
- '_user_email' => 'jessie@example.com',
305
- )
306
  );
307
 
308
  // Log entried can have custom occasionsID
309
  // This will group items together and a log entry will only be shown once
310
  // in the log overview, even if the logged messages are different
311
- for ( $i = 0; $i < rand( 1, 50 ); $i++ ) {
312
- SimpleLogger()->notice('User {username} edited page {pagename}', array(
313
- 'username' => "example_user_{$i}",
314
- 'pagename' => 'My test page',
315
- '_occasionsID' => 'postID:24884,action:edited',
316
- ));
317
  }
318
 
319
  // Events can have different "initiators",
320
  // i.e. who was responsible for the logged event
321
  // Initiator "WORDPRESS" means that WordPress did something on it's own
322
  SimpleLogger()->info(
323
- 'WordPress updated itself from version {from_version} to {to_version}',
324
- array(
325
- 'from_version' => '3.8',
326
- 'to_version' => '3.8.1',
327
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
328
- )
329
  );
330
 
331
  // Initiator "WP_USER" means that a logged in user did someting
332
  SimpleLogger()->info(
333
- 'Updated plugin {plugin_name} from version {plugin_from_version} to version {plugin_to_version}',
334
- array(
335
- 'plugin_name' => 'Ninja Forms',
336
- 'plugin_from_version' => '1.1',
337
- 'plugin_to_version' => '1.1.2',
338
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
339
- )
340
  );
341
 
342
  // // Initiator "WEB_USER" means that an unknown internet user did something
343
  SimpleLogger()->warning("An attempt to login as user 'administrator' failed to login because the wrong password was entered", array(
344
- '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
345
  ));
346
 
347
 
@@ -349,12 +334,12 @@ SimpleLogger()->warning("An attempt to login as user 'administrator' failed to l
349
  // Data can be used later on to show detailed info about a log entry
350
  // and does not need to be shown on the overview screen
351
  SimpleLogger()->info("Edited product '{pagename}'", array(
352
- 'pagename' => 'We are hiring!',
353
- '_postType' => 'product',
354
- '_userID' => 1,
355
- '_userLogin' => 'jessie',
356
- '_userEmail' => 'jessie@example.com',
357
- '_occasionsID' => 'username:1,postID:24885,action:edited',
358
  ));
359
 
360
 
@@ -366,31 +351,31 @@ wp_schedule_event( time(), "hourly", "simple_history_cron_testhook");
366
  wp_clear_scheduled_hook("simple_history_cron_testhook");
367
  add_action( 'simple_history_cron_testhook', 'simple_history_cron_testhook_function' );
368
  function simple_history_cron_testhook_function() {
369
- SimpleLogger()->info("This is a message inside a cron function");
370
  }
371
  */
372
 
373
  /*
374
  add_action("init", function() {
375
 
376
- global $wp_current_filter;
377
 
378
- $doing_cron = get_transient( 'doing_cron' );
379
- $const_doing_cron = defined('DOING_CRON') && DOING_CRON;
380
 
381
- if ($const_doing_cron) {
382
 
383
- $current_filter = current_filter();
384
 
385
- SimpleLogger()->info("This is a message inside init, trying to log crons", array(
386
- "doing_cron" => simpleHistory::json_encode($doing_cron),
387
- "current_filter" => $current_filter,
388
- "wp_current_filter" => $wp_current_filter,
389
- "wp_current_filter" => simpleHistory::json_encode( $wp_current_filter ),
390
- "const_doing_cron" => simpleHistory::json_encode($const_doing_cron)
391
- ));
392
 
393
- }
394
 
395
  }, 100);
396
  */
@@ -399,7 +384,7 @@ add_action("init", function() {
399
  /*
400
  add_action("init", function() {
401
 
402
- #SimpleLogger()->info("This is a regular info message" . time());
403
 
404
  }, 100);
405
  // */
13
  */
14
 
15
  // Add $_GET, $_POST, and more info to each logged even.
16
+ define('SIMPLE_HISTORY_LOG_DEBUG', true);
17
 
18
 
19
  /**
24
  * Remove the "Clear log"-button, so a user with admin access can not clear the log
25
  * and wipe their mischievous behavior from the log.
26
  */
27
+ add_filter('simple_history/user_can_clear_log', function ($user_can_clear_log) {
28
+ $user_can_clear_log = false;
29
+ return $user_can_clear_log;
30
  });
31
 
32
  // Modify who can read a logger.
33
  // Modify the if part to give users access or no access to a logger.
34
+ add_filter('simple_history/loggers_user_can_read/can_read_single_logger', function ($user_can_read_logger, $logger_instance, $user_id) {
35
 
36
+ // in this example user with id 3 gets access to the post logger
37
+ // while user with id 8 does not get any access to it
38
+ if ($logger_instance->slug == 'SimplePostLogger' && $user_id === 3) {
39
+ $user_can_read_logger = true;
40
+ } elseif ($logger_instance->slug == 'SimplePostLogger' && $user_id === 9) {
41
+ $user_can_read_logger = false;
42
+ }
43
 
44
+ return $user_can_read_logger;
45
+ }, 10, 3);
 
46
 
47
 
48
  // Do not log some post types, for example pages and attachments in this case
49
+ add_filter('simple_history/log/do_log', function ($do_log = null, $level = null, $message = null, $context = null, $logger = null) {
 
 
 
 
 
 
 
 
 
50
 
51
+ $post_types_to_not_log = array(
52
+ 'page',
53
+ 'attachment',
54
+ );
55
 
56
+ if (( isset($logger->slug) && ($logger->slug === 'SimplePostLogger' || $logger->slug === 'SimpleMediaLogger') ) && ( isset($context['post_type']) && in_array($context['post_type'], $post_types_to_not_log) )) {
57
+ $do_log = false;
58
+ }
59
 
60
+ return $do_log;
61
  }, 10, 5);
62
 
63
  // Disable all logging
64
+ add_filter('simple_history/log/do_log', '__return_false');
65
 
66
  /**
67
  * Example that modifies the parameters sent to the message template
68
  * This example will change the post type from "post" or "page" or similar to "my own page type"
69
  */
70
+ add_filter('simple_history/logger/interpolate/context', function ($context, $message, $row) {
71
 
72
+ if (empty($row)) {
73
+ return $context;
74
+ }
75
 
76
+ if ($row->logger == 'SimplePostLogger' && $row->context_message_key == 'post_updated') {
77
+ $context['post_type'] = 'my own page type';
78
+ }
 
 
79
 
80
+ return $context;
81
  }, 10, 3);
82
 
83
 
86
  * Change capability required to manage the options page of simple history.
87
  * Default capability is "manage_options"
88
  */
89
+ add_filter('simple_history/view_settings_capability', function ($capability) {
 
 
 
90
 
91
+ $capability = 'manage_options';
92
+ return $capability;
93
  });
94
 
95
 
98
  * Default capability is "edit_pages". Change to for example "manage options"
99
  * to only allow admins to view the history log.
100
  */
101
+ add_filter('simple_history/view_history_capability', function ($capability) {
 
 
 
102
 
103
+ $capability = 'manage_options';
104
+ return $capability;
105
  });
106
 
107
 
108
  // Skip adding things to the context table during logging.
109
  // Useful if you don't want to add cool and possible super useful info to your logged events.
110
  // Also nice to have if you want to make sure your database does not grow.
111
+ add_filter('simple_history/log_insert_context', function ($context, $data) {
112
 
113
+ unset($context['_user_id']);
114
+ unset($context['_user_login']);
115
+ unset($context['_user_email']);
116
+ unset($context['server_http_user_agent']);
 
 
117
 
118
+ return $context;
119
  }, 10, 2);
120
 
121
  // Hide some columns from the detailed context view popup window
122
+ add_filter('simple_history/log_html_output_details_table/row_keys_to_show', function ($logRowKeysToShow, $oneLogRow) {
 
 
 
 
 
123
 
124
+ $logRowKeysToShow['id'] = false;
125
+ $logRowKeysToShow['logger'] = false;
126
+ $logRowKeysToShow['level'] = false;
127
+ $logRowKeysToShow['message'] = false;
128
 
129
+ return $logRowKeysToShow;
130
  }, 10, 2);
131
 
132
 
133
  // Hide some more columns from the detailed context view popup window
134
+ add_filter('simple_history/log_html_output_details_table/context_keys_to_show', function ($logRowContextKeysToShow, $oneLogRow) {
135
 
136
+ $logRowContextKeysToShow['plugin_slug'] = false;
137
+ $logRowContextKeysToShow['plugin_name'] = false;
138
+ $logRowContextKeysToShow['plugin_title'] = false;
139
+ $logRowContextKeysToShow['plugin_description'] = false;
 
 
140
 
141
+ return $logRowContextKeysToShow;
142
  }, 10, 2);
143
 
144
 
145
 
146
  // Allow only the users specified in $allowed_users to show the history page, the history widget on the dashboard, or the history settings page
147
+ add_filter('simple_history/show_dashboard_page', 'function_show_history_dashboard_or_page');
148
+ add_filter('simple_history/show_dashboard_widget', 'function_show_history_dashboard_or_page');
149
+ add_filter('simple_history/show_settings_page', 'function_show_history_dashboard_or_page');
150
+ function function_show_history_dashboard_or_page($show)
151
+ {
 
 
 
 
152
 
153
+ $allowed_users = array(
154
+ 'user1@example.com',
155
+ 'anotheruser@example.com',
156
+ );
157
 
158
+ $user = wp_get_current_user();
 
 
159
 
160
+ if (! in_array($user->user_email, $allowed_users)) {
161
+ $show = false;
162
+ }
163
 
164
+ return $show;
165
  }
166
 
167
 
168
  // Skip loading of loggers
169
+ add_filter('simple_history/logger/load_logger', function ($load_logger, $oneLoggerFile) {
 
 
 
 
 
170
 
171
+ // Don't load loggers for comments or menus, i.e. don't log changes to comments or to menus
172
+ if (in_array($oneLoggerFile, array( 'SimpleCommentsLogger', 'SimpleMenuLogger' ))) {
173
+ $load_logger = false;
174
+ }
175
 
176
+ return $load_logger;
177
  }, 10, 2);
178
 
179
  /**
180
  * Load only the loggers that are specified in the $do_log_us array
181
  */
182
+ add_filter('simple_history/logger/load_logger', function ($load_logger, $logger_basename) {
183
 
184
+ $load_logger = false;
185
+ $do_log_us = array( 'SimplePostLogger', 'SimplePluginLogger', 'SimpleLogger' );
186
 
187
+ if (in_array($logger_basename, $do_log_us)) {
188
+ $load_logger = true;
189
+ }
190
 
191
+ return $load_logger;
192
+ }, 10, 2);
 
193
 
194
 
195
  // Skip the loading of dropins
196
+ add_filter('simple_history/dropin/load_dropin', function ($load_dropin, $dropinFileBasename) {
197
 
198
+ // Don't load the RSS feed dropin
199
+ if ($dropinFileBasename == 'SimpleHistoryRSSDropin') {
200
+ $load_dropin = false;
201
+ }
202
 
203
+ // Don't load the dropin that polls for changes
204
+ if ($dropinFileBasename == 'SimpleHistoryNewRowsNotifier') {
205
+ $load_dropin = false;
206
+ }
 
 
207
 
208
+ return $load_dropin;
209
  }, 10, 2);
210
 
211
 
212
  // Don't log failed logins
213
+ add_filter('simple_history/simple_logger/log_message_key', function ($doLog, $loggerSlug, $messageKey, $SimpleLoggerLogLevelsLevel, $context) {
 
 
 
 
 
214
 
215
+ // Don't log login attempts to non existing users
216
+ if ('SimpleUserLogger' == $loggerSlug && 'user_unknown_login_failed' == $messageKey) {
217
+ $doLog = false;
218
+ }
219
 
220
+ // Don't log failed logins to existing users
221
+ if ('SimpleUserLogger' == $loggerSlug && 'user_login_failed' == $messageKey) {
222
+ $doLog = false;
223
+ }
224
 
225
+ return $doLog;
226
  }, 10, 5);
227
 
228
  // Never clear the log (default is 60 days)
229
+ add_filter('simple_history/db_purge_days_interval', '__return_zero');
230
 
231
  // Clear items that are older than a 7 days (i.e. keep only the most recent 7 days in the log)
232
+ add_filter('simple_history/db_purge_days_interval', function ($days) {
 
 
233
 
234
+ $days = 7;
235
 
236
+ return $days;
237
+ });
238
 
239
  // Don't let anyone - even with the correct secret - view the RSS feed
240
+ add_filter('simple_history/rss_feed_show', '__return_false');
241
 
242
  // Skip loading of a dropin completely (in this case the RSS dropin)
243
+ add_filter('simple_history/dropin/load_dropin_SimpleHistoryRSSDropin', '__return_false');
244
 
245
  /**
246
  * Example of logging
247
  */
248
 
249
  // This is the easiest and safest way to add messages to the log:
250
+ apply_filters('simple_history_log', 'This is a logged message');
251
+ apply_filters('simple_history_log', 'This is a message with some context added', array(
252
+ 'isATestMessage' => 'yup',
253
+ 'debugRequestData' => $_REQUEST,
254
+ ));
255
+ apply_filters('simple_history_log', 'This is another logged message, with another severity level', null, 'debug');
256
 
257
  // Below is the function way of adding things to the log
258
  // Remember to check that the SimpleLogger function exists before trying to log anything,
259
  // or else your site will break if you disable the Simple History plugin
260
  // (Use the apply_filters method above if you want to stay safer!)
261
+ if (function_exists('SimpleLogger')) {
262
+ SimpleLogger()->info('This is a message added to the log');
263
  }
264
 
265
  // Add a message to the history log
266
+ SimpleLogger()->info('This is a message sent to the log');
267
 
268
  // Add log entries with different severities
269
+ SimpleLogger()->warning("User 'Jessie' deleted user 'Kim'");
270
+ SimpleLogger()->debug('Ok, cron job is running!');
271
 
272
  // Add a message to the history log
273
  // and then add a second log entry with same info and Simple History
274
  // will make these two become an "occasionGroup",
275
  // i.e. collapsing their entries into one expandable log item
276
+ SimpleLogger()->info('This is a message sent to the log');
277
+ SimpleLogger()->info('This is a message sent to the log');
278
 
279
  // Log entries can have placeholders and context
280
  // This makes log entried translatable and filterable
281
  SimpleLogger()->notice(
282
+ 'User {username} edited page {pagename}',
283
+ array(
284
+ 'username' => 'jessie',
285
+ 'pagename' => 'My test page',
286
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
287
+ '_user_id' => 5,
288
+ '_user_login' => 'jess',
289
+ '_user_email' => 'jessie@example.com',
290
+ )
291
  );
292
 
293
  // Log entried can have custom occasionsID
294
  // This will group items together and a log entry will only be shown once
295
  // in the log overview, even if the logged messages are different
296
+ for ($i = 0; $i < rand(1, 50); $i++) {
297
+ SimpleLogger()->notice('User {username} edited page {pagename}', array(
298
+ 'username' => "example_user_{$i}",
299
+ 'pagename' => 'My test page',
300
+ '_occasionsID' => 'postID:24884,action:edited',
301
+ ));
302
  }
303
 
304
  // Events can have different "initiators",
305
  // i.e. who was responsible for the logged event
306
  // Initiator "WORDPRESS" means that WordPress did something on it's own
307
  SimpleLogger()->info(
308
+ 'WordPress updated itself from version {from_version} to {to_version}',
309
+ array(
310
+ 'from_version' => '3.8',
311
+ 'to_version' => '3.8.1',
312
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
313
+ )
314
  );
315
 
316
  // Initiator "WP_USER" means that a logged in user did someting
317
  SimpleLogger()->info(
318
+ 'Updated plugin {plugin_name} from version {plugin_from_version} to version {plugin_to_version}',
319
+ array(
320
+ 'plugin_name' => 'Ninja Forms',
321
+ 'plugin_from_version' => '1.1',
322
+ 'plugin_to_version' => '1.1.2',
323
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
324
+ )
325
  );
326
 
327
  // // Initiator "WEB_USER" means that an unknown internet user did something
328
  SimpleLogger()->warning("An attempt to login as user 'administrator' failed to login because the wrong password was entered", array(
329
+ '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
330
  ));
331
 
332
 
334
  // Data can be used later on to show detailed info about a log entry
335
  // and does not need to be shown on the overview screen
336
  SimpleLogger()->info("Edited product '{pagename}'", array(
337
+ 'pagename' => 'We are hiring!',
338
+ '_postType' => 'product',
339
+ '_userID' => 1,
340
+ '_userLogin' => 'jessie',
341
+ '_userEmail' => 'jessie@example.com',
342
+ '_occasionsID' => 'username:1,postID:24885,action:edited',
343
  ));
344
 
345
 
351
  wp_clear_scheduled_hook("simple_history_cron_testhook");
352
  add_action( 'simple_history_cron_testhook', 'simple_history_cron_testhook_function' );
353
  function simple_history_cron_testhook_function() {
354
+ SimpleLogger()->info("This is a message inside a cron function");
355
  }
356
  */
357
 
358
  /*
359
  add_action("init", function() {
360
 
361
+ global $wp_current_filter;
362
 
363
+ $doing_cron = get_transient( 'doing_cron' );
364
+ $const_doing_cron = defined('DOING_CRON') && DOING_CRON;
365
 
366
+ if ($const_doing_cron) {
367
 
368
+ $current_filter = current_filter();
369
 
370
+ SimpleLogger()->info("This is a message inside init, trying to log crons", array(
371
+ "doing_cron" => simpleHistory::json_encode($doing_cron),
372
+ "current_filter" => $current_filter,
373
+ "wp_current_filter" => $wp_current_filter,
374
+ "wp_current_filter" => simpleHistory::json_encode( $wp_current_filter ),
375
+ "const_doing_cron" => simpleHistory::json_encode($const_doing_cron)
376
+ ));
377
 
378
+ }
379
 
380
  }, 100);
381
  */
384
  /*
385
  add_action("init", function() {
386
 
387
+ #SimpleLogger()->info("This is a regular info message" . time());
388
 
389
  }, 100);
390
  // */
inc/SimpleHistory.php CHANGED
@@ -7,4191 +7,3261 @@ defined('ABSPATH') or die();
7
  */
8
  class SimpleHistory
9
  {
10
- const NAME = 'Simple History';
11
-
12
- /**
13
- * For singleton
14
- */
15
- private static $instance;
16
-
17
- /**
18
- * Array with external loggers to load
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
- */
30
- private $instantiatedLoggers;
31
-
32
- /**
33
- * Array with all instantiated dropins
34
- */
35
- private $instantiatedDropins;
36
-
37
- /**
38
- * Bool if gettext filter function should be active
39
- * Should only be active during the load of a logger
40
- */
41
- private $doFilterGettext = false;
42
-
43
- /**
44
- * Used by gettext filter to temporarily store current logger
45
- */
46
- private $doFilterGettext_currentLogger = null;
47
-
48
- /**
49
- * Used to store latest translations used by __()
50
- * Required to automagically determine orginal text and text domain
51
- * for calls like this `SimpleLogger()->log( __("My translated message") );`
52
- */
53
- public $gettextLatestTranslations = array();
54
-
55
- /**
56
- * All registered settings tabs
57
- */
58
- private $arr_settings_tabs = array();
59
-
60
- const DBTABLE = 'simple_history';
61
- const DBTABLE_CONTEXTS = 'simple_history_contexts';
62
-
63
- /** Slug for the settings menu */
64
- const SETTINGS_MENU_SLUG = 'simple_history_settings_menu_slug';
65
-
66
- /** Slug for the settings menu */
67
- const SETTINGS_GENERAL_OPTION_GROUP = 'simple_history_settings_group';
68
-
69
- /** ID for the general settings section */
70
- const SETTINGS_SECTION_GENERAL_ID = 'simple_history_settings_section_general';
71
-
72
- function __construct()
73
- {
74
- $this->init();
75
- } // construct
76
-
77
- /**
78
- * @since 2.5.2
79
- */
80
- public function init()
81
- {
82
- /**
83
- * Fires before Simple History does it's init stuff
84
- *
85
- * @since 2.0
86
- *
87
- * @param SimpleHistory $SimpleHistory This class.
88
- */
89
- do_action('simple_history/before_init', $this);
90
-
91
- $this->setup_variables();
92
-
93
- // Actions and filters, ordered by order specified in codex: http://codex.wordpress.org/Plugin_API/Action_Reference
94
- add_action('after_setup_theme', array($this, 'load_plugin_textdomain'));
95
- add_action('after_setup_theme', array(
96
- $this,
97
- 'add_default_settings_tabs'
98
- ));
99
-
100
- // Plugins and dropins are loaded using the "after_setup_theme" filter so
101
- // themes can use filters to modify the loading of them.
102
- // The drawback with this is that for example logouts done when plugins like
103
- // iThemes Security is installed is not logged, because those plugins fire wp_logout()
104
- // using filter "plugins_loaded", i.e. before simple history has loaded its filters.
105
- add_action('after_setup_theme', array($this, 'load_loggers'));
106
- add_action('after_setup_theme', array($this, 'load_dropins'));
107
-
108
- // Run before loading of loggers and before menu items are added.
109
- add_action('after_setup_theme', array($this, 'check_for_upgrade'), 5);
110
-
111
- add_action('after_setup_theme', array($this, 'setup_cron'));
112
-
113
- // Filters and actions not called during regular boot.
114
- add_filter('gettext', array($this, 'filter_gettext'), 20, 3);
115
- add_filter(
116
- 'gettext_with_context',
117
- array($this, 'filter_gettext_with_context'),
118
- 20,
119
- 4
120
- );
121
-
122
- add_filter(
123
- 'gettext',
124
- array($this, 'filter_gettext_storeLatestTranslations'),
125
- 10,
126
- 3
127
- );
128
-
129
- add_action(
130
- 'admin_bar_menu',
131
- array($this, 'add_admin_bar_network_menu_item'),
132
- 40
133
- );
134
- add_action(
135
- 'admin_bar_menu',
136
- array($this, 'add_admin_bar_menu_item'),
137
- 40
138
- );
139
-
140
- /**
141
- * Filter that is used to log things, without the need to check that simple history is available
142
- * i.e. you can have simple history acivated and log things and then you can disable the plugin
143
- * and no errors will occur
144
- *
145
- * Usage:
146
- * apply_filters("simple_history_log", "This is the log message");
147
- * apply_filters("simple_history_log", "This is the log message with some extra data/info", ["extraThing1" => $variableWIihThing]);
148
- * apply_filters("simple_history_log", "This is the log message with severity debug", null, "debug");
149
- * apply_filters("simple_history_log", "This is the log message with severity debug and with some extra info/data logged", ["userData" => $userData, "shoppingCartDebugData" => $shopDebugData], "debug",);
150
- *
151
- * @since 2.13
152
- */
153
- add_filter(
154
- 'simple_history_log',
155
- array($this, 'on_filter_simple_history_log'),
156
- 10,
157
- 3
158
- );
159
-
160
- /**
161
- * Filter to log with specific log level, for example:
162
- * apply_filters('simple_history_log_debug', 'My debug message');
163
- * apply_filters('simple_history_log_warning', 'My warning message');
164
- *
165
- * @since 2.17
166
- */
167
- add_filter(
168
- 'simple_history_log_emergency',
169
- array($this, 'on_filter_simple_history_log_emergency'),
170
- 10,
171
- 3
172
- );
173
- add_filter(
174
- 'simple_history_log_alert',
175
- array($this, 'on_filter_simple_history_log_alert'),
176
- 10,
177
- 2
178
- );
179
- add_filter(
180
- 'simple_history_log_critical',
181
- array($this, 'on_filter_simple_history_log_critical'),
182
- 10,
183
- 2
184
- );
185
- add_filter(
186
- 'simple_history_log_error',
187
- array($this, 'on_filter_simple_history_log_error'),
188
- 10,
189
- 2
190
- );
191
- add_filter(
192
- 'simple_history_log_warning',
193
- array($this, 'on_filter_simple_history_log_warning'),
194
- 10,
195
- 2
196
- );
197
- add_filter(
198
- 'simple_history_log_notice',
199
- array($this, 'on_filter_simple_history_log_notice'),
200
- 10,
201
- 2
202
- );
203
- add_filter(
204
- 'simple_history_log_info',
205
- array($this, 'on_filter_simple_history_log_info'),
206
- 10,
207
- 2
208
- );
209
- add_filter(
210
- 'simple_history_log_debug',
211
- array($this, 'on_filter_simple_history_log_debug'),
212
- 10,
213
- 2
214
- );
215
-
216
- if (is_admin()) {
217
- $this->add_admin_actions();
218
- }
219
-
220
- /**
221
- * Fires after Simple History has done it's init stuff
222
- *
223
- * @since 2.0
224
- *
225
- * @param SimpleHistory $SimpleHistory This class.
226
- */
227
- do_action('simple_history/after_init', $this);
228
-
229
- // Add some extra info to each logged context when SIMPLE_HISTORY_LOG_DEBUG is set and true
230
- if (defined('SIMPLE_HISTORY_LOG_DEBUG') && SIMPLE_HISTORY_LOG_DEBUG) {
231
- add_filter(
232
- 'simple_history/log_argument/context',
233
- function ($context, $level, $message, $logger) {
234
- $sh = SimpleHistory::get_instance();
235
- $context['_debug_get'] = $sh->json_encode($_GET);
236
- $context['_debug_post'] = $sh->json_encode($_POST);
237
- $context['_debug_server'] = $sh->json_encode($_SERVER);
238
- $context['_debug_files'] = $sh->json_encode($_FILES);
239
- $context['_debug_php_sapi_name'] = php_sapi_name();
240
-
241
- global $argv;
242
- $context['_debug_argv'] = $sh->json_encode($argv);
243
-
244
- $consts = get_defined_constants(true);
245
- $consts = $consts['user'];
246
- $context['_debug_user_constants'] = $sh->json_encode(
247
- $consts
248
- );
249
-
250
- $postdata = file_get_contents('php://input');
251
- $context['_debug_http_raw_post_data'] = $sh->json_encode(
252
- $postdata
253
- );
254
-
255
- $context[
256
- '_debug_wp_debug_backtrace_summary'
257
- ] = wp_debug_backtrace_summary();
258
- $context['_debug_is_admin'] = json_encode(is_admin());
259
- $context['_debug_is_ajax'] = json_encode(
260
- defined('DOING_AJAX') && DOING_AJAX
261
- );
262
- $context['_debug_is_doing_cron'] = json_encode(
263
- defined('DOING_CRON') && DOING_CRON
264
- );
265
-
266
- global $wp_current_filter;
267
- $context[
268
- '_debug_current_filter_array'
269
- ] = $wp_current_filter;
270
- $context['_debug_current_filter'] = current_filter();
271
-
272
- return $context;
273
- },
274
- 10,
275
- 4
276
- );
277
- }
278
- }
279
-
280
- /**
281
- * Log a message
282
- *
283
- * Function called when running filter "simple_history_log"
284
- *
285
- * @since 2.13
286
- * @param string $message The message to log.
287
- * @param array $context Optional context to add to the logged data.
288
- * @param string $level The loglevel. Must be one of the existing ones. Defaults to "info".
289
- */
290
- public function on_filter_simple_history_log(
291
- $message = null,
292
- $context = null,
293
- $level = 'info'
294
- ) {
295
- SimpleLogger()->log($level, $message, $context);
296
- }
297
-
298
- /**
299
- * Log a message, triggered by filter 'on_filter_simple_history_log_emergency'.
300
- *
301
- * @param string $message The message to log.
302
- * @param array $context The context (optional).
303
- */
304
- public function on_filter_simple_history_log_emergency(
305
- $message = null,
306
- $context = null
307
- ) {
308
- SimpleLogger()->log('emergency', $message, $context);
309
- }
310
-
311
- /**
312
- * Log a message, triggered by filter 'on_filter_simple_history_log_alert'.
313
- *
314
- * @param string $message The message to log.
315
- * @param array $context The context (optional).
316
- */
317
- public function on_filter_simple_history_log_alert(
318
- $message = null,
319
- $context = null
320
- ) {
321
- SimpleLogger()->log('alert', $message, $context);
322
- }
323
-
324
- /**
325
- * Log a message, triggered by filter 'on_filter_simple_history_log_critical'.
326
- *
327
- * @param string $message The message to log.
328
- * @param array $context The context (optional).
329
- */
330
- public function on_filter_simple_history_log_critical(
331
- $message = null,
332
- $context = null
333
- ) {
334
- SimpleLogger()->log('critical', $message, $context);
335
- }
336
-
337
- /**
338
- * Log a message, triggered by filter 'on_filter_simple_history_log_error'.
339
- *
340
- * @param string $message The message to log.
341
- * @param array $context The context (optional).
342
- */
343
- public function on_filter_simple_history_log_error(
344
- $message = null,
345
- $context = null
346
- ) {
347
- SimpleLogger()->log('error', $message, $context);
348
- }
349
-
350
- /**
351
- * Log a message, triggered by filter 'on_filter_simple_history_log_warning'.
352
- *
353
- * @param string $message The message to log.
354
- * @param array $context The context (optional).
355
- */
356
- public function on_filter_simple_history_log_warning(
357
- $message = null,
358
- $context = null
359
- ) {
360
- SimpleLogger()->log('warning', $message, $context);
361
- }
362
-
363
- /**
364
- * Log a message, triggered by filter 'on_filter_simple_history_log_notice'.
365
- *
366
- * @param string $message The message to log.
367
- * @param array $context The context (optional).
368
- */
369
- public function on_filter_simple_history_log_notice(
370
- $message = null,
371
- $context = null
372
- ) {
373
- SimpleLogger()->log('notice', $message, $context);
374
- }
375
-
376
- /**
377
- * Log a message, triggered by filter 'on_filter_simple_history_log_info'.
378
- *
379
- * @param string $message The message to log.
380
- * @param array $context The context (optional).
381
- */
382
- public function on_filter_simple_history_log_info(
383
- $message = null,
384
- $context = null
385
- ) {
386
- SimpleLogger()->log('info', $message, $context);
387
- }
388
-
389
- /**
390
- * Log a message, triggered by filter 'on_filter_simple_history_log_debug'.
391
- *
392
- * @param string $message The message to log.
393
- * @param array $context The context (optional).
394
- */
395
- public function on_filter_simple_history_log_debug(
396
- $message = null,
397
- $context = null
398
- ) {
399
- SimpleLogger()->log('debug', $message, $context);
400
- }
401
-
402
- /**
403
- * @since 2.5.2
404
- */
405
- private function add_admin_actions()
406
- {
407
- add_action('admin_menu', array($this, 'add_admin_pages'));
408
- add_action('admin_menu', array($this, 'add_settings'));
409
-
410
- add_action('admin_footer', array($this, 'add_js_templates'));
411
-
412
- add_action('wp_dashboard_setup', array($this, 'add_dashboard_widget'));
413
-
414
- add_action('admin_enqueue_scripts', array(
415
- $this,
416
- 'enqueue_admin_scripts'
417
- ));
418
-
419
- add_action('admin_head', array($this, 'onAdminHead'));
420
- add_action('admin_footer', array($this, 'onAdminFooter'));
421
-
422
- add_action('simple_history/history_page/before_gui', array(
423
- $this,
424
- 'output_quick_stats'
425
- ));
426
- add_action('simple_history/dashboard/before_gui', array(
427
- $this,
428
- 'output_quick_stats'
429
- ));
430
-
431
- add_action('wp_ajax_simple_history_api', array($this, 'api'));
432
-
433
- add_filter(
434
- 'plugin_action_links_simple-history/index.php',
435
- array($this, 'plugin_action_links'),
436
- 10,
437
- 4
438
- );
439
- }
440
-
441
- /**
442
- * Adds a "View history" item/shortcut to the network admin, on blogs where Simple History is installed
443
- *
444
- * Useful because Simple History is something at least the author of this plugin often use on a site :)
445
- *
446
- * @since 2.7.1
447
- */
448
- function add_admin_bar_network_menu_item($wp_admin_bar)
449
- {
450
- /**
451
- * Filter to control if admin bar shortcut should be added
452
- *
453
- * @since 2.7.1
454
- *
455
- * @param bool Add item
456
- */
457
- $add_items = apply_filters(
458
- 'simple_history/add_admin_bar_network_menu_item',
459
- true
460
- );
461
-
462
- if (!$add_items) {
463
- return;
464
- }
465
-
466
- // Don't show for logged out users or single site mode.
467
- if (!is_user_logged_in() || !is_multisite()) {
468
- return;
469
- }
470
-
471
- // Show only when the user has at least one site, or they're a super admin.
472
- if (count($wp_admin_bar->user->blogs) < 1 && !is_super_admin()) {
473
- return;
474
- }
475
-
476
- // Setting to show as page must be true
477
- if (!$this->setting_show_as_page()) {
478
- return;
479
- }
480
-
481
- // User must have capability to view the history page
482
- if (!current_user_can($this->get_view_history_capability())) {
483
- return;
484
- }
485
-
486
- /*
487
- menu_page_url() is defined in the WordPress Plugin Administration API, which is not loaded here by default */
488
- /* dito for is_plugin_active() */
489
- require_once ABSPATH . 'wp-admin/includes/plugin.php';
490
-
491
- foreach ((array) $wp_admin_bar->user->blogs as $blog) {
492
- switch_to_blog($blog->userblog_id);
493
-
494
- if (is_plugin_active(SIMPLE_HISTORY_BASENAME)) {
495
- $menu_id = 'simple-history-blog-' . $blog->userblog_id;
496
- $parent_menu_id = 'blog-' . $blog->userblog_id;
497
- $url = admin_url('index.php?page=simple_history_page');
498
-
499
- // Each network site is added by WP core with id "blog-1", "blog-2" ... "blog-n"
500
- // https://codex.wordpress.org/Function_Reference/add_node
501
- $args = array(
502
- 'id' => $menu_id,
503
- 'parent' => $parent_menu_id,
504
- 'title' => _x(
505
- 'View History',
506
- 'Admin bar network name',
507
- 'simple-history'
508
- ),
509
- 'href' => $url,
510
- 'meta' => array(
511
- 'class' => 'ab-item--simplehistory'
512
- )
513
- );
514
-
515
- $wp_admin_bar->add_node($args);
516
- } // End if().
517
-
518
- restore_current_blog();
519
- } // End foreach().
520
- } // func
521
-
522
- /**
523
- * Adds a "View history" item/shortcut to the admin bar
524
- *
525
- * Useful because Simple History is something at least the author of this plugin often use on a site :)
526
- *
527
- * @since 2.7.1
528
- */
529
- function add_admin_bar_menu_item($wp_admin_bar)
530
- {
531
- /**
532
- * Filter to control if admin bar shortcut should be added
533
- *
534
- * @since 2.7.1
535
- *
536
- * @param bool Add item
537
- */
538
- $add_item = apply_filters(
539
- 'simple_history/add_admin_bar_menu_item',
540
- true
541
- );
542
-
543
- if (!$add_item) {
544
- return;
545
- }
546
-
547
- // Don't show for logged out users
548
- if (!is_user_logged_in()) {
549
- return;
550
- }
551
-
552
- // Setting to show as page must be true
553
- if (!$this->setting_show_as_page()) {
554
- return;
555
- }
556
-
557
- // User must have capability to view the history page
558
- if (!current_user_can($this->get_view_history_capability())) {
559
- return;
560
- }
561
-
562
- /*
563
- menu_page_url() is defined in the WordPress Plugin Administration API, which is not loaded here by default */
564
- /* dito for is_plugin_active() */
565
- require_once ABSPATH . 'wp-admin/includes/plugin.php';
566
-
567
- $menu_id = 'simple-history-view-history';
568
- $parent_menu_id = 'site-name';
569
- $url = admin_url('index.php?page=simple_history_page');
570
-
571
- $args = array(
572
- 'id' => $menu_id,
573
- 'parent' => $parent_menu_id,
574
- 'title' => _x('Simple History', 'Admin bar name', 'simple-history'),
575
- 'href' => $url,
576
- 'meta' => array(
577
- 'class' => 'ab-item--simplehistory'
578
- )
579
- );
580
-
581
- $wp_admin_bar->add_node($args);
582
- } // func
583
-
584
- /**
585
- * Get singleton intance
586
- *
587
- * @return SimpleHistory instance
588
- */
589
- public static function get_instance()
590
- {
591
- if (!isset(self::$instance)) {
592
- self::$instance = new SimpleHistory();
593
- }
594
-
595
- return self::$instance;
596
- }
597
-
598
- function filter_gettext_storeLatestTranslations(
599
- $translation,
600
- $text,
601
- $domain
602
- ) {
603
- // Check that translation is a string or integer, i.ex. the valid values for an array key
604
- if (!is_string($translation) || !is_integer($translation)) {
605
- return $translation;
606
- }
607
-
608
- $array_max_size = 5;
609
-
610
- // Keep a listing of the n latest translation
611
- // when SimpleLogger->log() is called from anywhere we can then search for the
612
- // translated string among our n latest things and find it there, if it's translated
613
- // global $sh_latest_translations;
614
- $sh_latest_translations = $this->gettextLatestTranslations;
615
-
616
- $sh_latest_translations[$translation] = array(
617
- 'translation' => $translation,
618
- 'text' => $text,
619
- 'domain' => $domain
620
- );
621
-
622
- $arr_length = sizeof($sh_latest_translations);
623
- if ($arr_length > $array_max_size) {
624
- $sh_latest_translations = array_slice(
625
- $sh_latest_translations,
626
- $arr_length - $array_max_size
627
- );
628
- }
629
-
630
- $this->gettextLatestTranslations = $sh_latest_translations;
631
-
632
- return $translation;
633
- }
634
-
635
- function setup_cron()
636
- {
637
- add_filter('simple_history/maybe_purge_db', array(
638
- $this,
639
- 'maybe_purge_db'
640
- ));
641
-
642
- if (!wp_next_scheduled('simple_history/maybe_purge_db')) {
643
- wp_schedule_event(time(), 'daily', 'simple_history/maybe_purge_db');
644
- } else {
645
- }
646
-
647
- // Remove old schedule (only author dev sites should have it)
648
- $old_next_scheduled = wp_next_scheduled('simple_history/purge_db');
649
- if ($old_next_scheduled) {
650
- wp_unschedule_event($old_next_scheduled, 'simple_history/purge_db');
651
- }
652
- }
653
-
654
- public function testlog_old()
655
- {
656
- // Log that an email has been sent
657
- simple_history_add(array(
658
- 'object_type' => 'Email',
659
- 'object_name' => 'Hi there',
660
- 'action' => 'was sent'
661
- ));
662
-
663
- // Will show “Plugin your_plugin_name Edited” in the history log
664
- simple_history_add(
665
- 'action=edited&object_type=plugin&object_name=your_plugin_name'
666
- );
667
-
668
- // Will show the history item "Starship USS Enterprise repaired"
669
- simple_history_add(
670
- 'action=repaired&object_type=Starship&object_name=USS Enterprise'
671
- );
672
-
673
- // Log with some extra details about the email
674
- simple_history_add(array(
675
- 'object_type' => 'Email',
676
- 'object_name' => 'Hi there',
677
- 'action' => 'was sent',
678
- 'description' =>
679
- 'The database query to generate the email took .3 seconds. This is email number 4 that is sent to this user'
680
- ));
681
- }
682
-
683
- public function onAdminHead()
684
- {
685
- if ($this->is_on_our_own_pages()) {
686
- do_action('simple_history/admin_head', $this);
687
- }
688
- }
689
-
690
- public function onAdminFooter()
691
- {
692
- if ($this->is_on_our_own_pages()) {
693
- do_action('simple_history/admin_footer', $this);
694
- }
695
- }
696
-
697
- /**
698
- * Output JS templated into footer
699
- */
700
- public function add_js_templates($hook)
701
- {
702
- if ($this->is_on_our_own_pages()) { ?>
703
- <script type="text/html" id="tmpl-simple-history-base">
704
-
705
- <div class="SimpleHistory__waitingForFirstLoad">
706
- <img src="<?php echo admin_url(
707
- '/images/spinner.gif'
708
- ); ?>" alt="" width="20" height="20">
709
- <?php echo _x(
710
- 'Loading history...',
711
- 'Message visible while waiting for log to load from server the first time',
712
- 'simple-history'
713
- ); ?>
714
- </div>
715
-
716
- <div class="SimpleHistoryLogitemsWrap">
717
- <div class="SimpleHistoryLogitems__beforeTopPagination"></div>
718
- <div class="SimpleHistoryLogitems__above"></div>
719
- <ul class="SimpleHistoryLogitems"></ul>
720
- <div class="SimpleHistoryLogitems__below"></div>
721
- <div class="SimpleHistoryLogitems__pagination"></div>
722
- <div class="SimpleHistoryLogitems__afterBottomPagination"></div>
723
- </div>
724
-
725
- <div class="SimpleHistoryLogitems__debug"></div>
726
-
727
- </script>
728
-
729
- <script type="text/html" id="tmpl-simple-history-logitems-pagination">
730
-
731
- <!-- this uses the (almost) the same html as WP does -->
732
- <div class="SimpleHistoryPaginationPages">
733
- <!--
734
- {{ data.page_rows_from }}–{{ data.page_rows_to }}
735
- <span class="SimpleHistoryPaginationDisplayNum"> of {{ data.total_row_count }} </span>
736
- -->
737
- <span class="SimpleHistoryPaginationLinks">
738
- <a
739
- data-direction="first"
740
- class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--firstPage <# if ( data.api_args.paged <= 1 ) { #> disabled <# } #>"
741
- title="{{ data.strings.goToTheFirstPage }}"
742
- href="#">«</a>
743
- <a
744
- data-direction="prev"
745
- class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--prevPage <# if ( data.api_args.paged <= 1 ) { #> disabled <# } #>"
746
- title="{{ data.strings.goToThePrevPage }}"
747
- href="#">‹</a>
748
- <span class="SimpleHistoryPaginationInput">
749
- <input class="SimpleHistoryPaginationCurrentPage" title="{{ data.strings.currentPage }}" type="text" name="paged" value="{{ data.api_args.paged }}" size="4">
750
- <?php _x('of', 'page n of n', 'simple-history'); ?>
751
- <span class="total-pages">{{ data.pages_count }}</span>
752
- </span>
753
- <a
754
- data-direction="next"
755
- class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--nextPage <# if ( data.api_args.paged >= data.pages_count ) { #> disabled <# } #>"
756
- title="{{ data.strings.goToTheNextPage }}"
757
- href="#">›</a>
758
- <a
759
- data-direction="last"
760
- class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--lastPage <# if ( data.api_args.paged >= data.pages_count ) { #> disabled <# } #>"
761
- title="{{ data.strings.goToTheLastPage }}"
762
- href="#">»</a>
763
- </span>
764
- </div>
765
-
766
- </script>
767
-
768
- <script type="text/html" id="tmpl-simple-history-logitems-modal">
769
-
770
- <div class="SimpleHistory-modal">
771
- <div class="SimpleHistory-modal__background"></div>
772
- <div class="SimpleHistory-modal__content">
773
- <div class="SimpleHistory-modal__contentInner">
774
- <img class="SimpleHistory-modal__contentSpinner" src="<?php echo admin_url(
775
- '/images/spinner.gif'
776
- ); ?>" alt="">
777
- </div>
778
- <div class="SimpleHistory-modal__contentClose">
779
- <button class="button">✕</button>
780
- </div>
781
- </div>
782
- </div>
783
-
784
- </script>
785
-
786
- <script type="text/html" id="tmpl-simple-history-occasions-too-many">
787
- <li
788
- class="SimpleHistoryLogitem
789
- SimpleHistoryLogitem--occasion
790
- SimpleHistoryLogitem--occasion-tooMany
791
- ">
792
- <div class="SimpleHistoryLogitem__firstcol"></div>
793
- <div class="SimpleHistoryLogitem__secondcol">
794
- <div class="SimpleHistoryLogitem__text">
795
- <?php _e(
796
- 'Sorry, but there are too many similar events to show.',
797
- 'simple-history'
798
- ); ?>
799
- <!-- <br>occasionsCount: {{ data.occasionsCount }}
800
- <br>occasionsCountMaxReturn: {{ data.occasionsCountMaxReturn }}
801
- <br>diff: {{ data.occasionsCount - data.occasionsCountMaxReturn }}
802
- Suggestions:
803
- <ul>
804
- <li>- dig into database directly
805
- <li>- Export
806
- </ul>
807
- -->
808
- </div>
809
- </div>
810
- </li>
811
- </script>
812
-
813
- <?php // Call plugins so they can add their js
814
- foreach ($this->instantiatedLoggers as $one_logger) {
815
- if (method_exists($one_logger['instance'], 'adminJS')) {
816
- $one_logger['instance']->adminJS();
817
- }
818
- }} // End if().
819
- }
820
-
821
- /**
822
- * Base url is:
823
- * /wp-admin/admin-ajax.php?action=simple_history_api
824
- *
825
- * Examples:
826
- * http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&posts_per_page=5&paged=1&format=html
827
- */
828
- public function api()
829
- {
830
- global $wpdb;
831
-
832
- // Fake slow answers
833
- // sleep(2);
834
- // sleep(rand(0,3));
835
- $args = $_GET;
836
- unset($args['action']);
837
-
838
- // Type = overview | ...
839
- $type = isset($_GET['type']) ? $_GET['type'] : null;
840
-
841
- if (empty($args) || !$type) {
842
- wp_send_json_error(array(
843
- _x(
844
- 'Not enough args specified',
845
- 'API: not enought arguments passed',
846
- 'simple-history'
847
- )
848
- ));
849
- }
850
-
851
- // User must have capability to view the history page
852
- if (!current_user_can($this->get_view_history_capability())) {
853
- wp_send_json_error(array(
854
- 'error' => 'CAPABILITY_ERROR'
855
- ));
856
- }
857
-
858
- if (isset($args['id'])) {
859
- $args['post__in'] = array($args['id']);
860
- }
861
-
862
- $data = array();
863
-
864
- switch ($type) {
865
- case 'overview':
866
- case 'occasions':
867
- case 'single':
868
- // API use SimpleHistoryLogQuery, so simply pass args on to that
869
- $logQuery = new SimpleHistoryLogQuery();
870
- $data = $logQuery->query($args);
871
-
872
- $data['api_args'] = $args;
873
-
874
- // Output can be array or HMTL
875
- if (isset($args['format']) && 'html' === $args['format']) {
876
- $data['log_rows_raw'] = array();
877
-
878
- foreach ($data['log_rows'] as $key => $oneLogRow) {
879
- $args = array();
880
- if ($type == 'single') {
881
- $args['type'] = 'single';
882
- }
883
-
884
- $data['log_rows'][$key] = $this->getLogRowHTMLOutput(
885
- $oneLogRow,
886
- $args
887
- );
888
- $data['num_queries'] = get_num_queries();
889
- }
890
- } else {
891
- // $data["logRows"] = $logRows;
892
- }
893
-
894
- break;
895
-
896
- default:
897
- $data[] = 'Nah.';
898
- } // End switch().
899
-
900
- wp_send_json_success($data);
901
- }
902
-
903
- /**
904
- * During the load of info for a logger we want to get a reference
905
- * to the untranslated text too, because that's the version we want to store
906
- * in the database.
907
- */
908
- public function filter_gettext(
909
- $translated_text,
910
- $untranslated_text,
911
- $domain
912
- ) {
913
- if (isset($this->doFilterGettext) && $this->doFilterGettext) {
914
- $this->doFilterGettext_currentLogger->messages[] = array(
915
- 'untranslated_text' => $untranslated_text,
916
- 'translated_text' => $translated_text,
917
- 'domain' => $domain,
918
- 'context' => null
919
- );
920
- }
921
-
922
- return $translated_text;
923
- }
924
-
925
- /**
926
- * Store messages with context
927
- */
928
- public function filter_gettext_with_context(
929
- $translated_text,
930
- $untranslated_text,
931
- $context,
932
- $domain
933
- ) {
934
- if (isset($this->doFilterGettext) && $this->doFilterGettext) {
935
- $this->doFilterGettext_currentLogger->messages[] = array(
936
- 'untranslated_text' => $untranslated_text,
937
- 'translated_text' => $translated_text,
938
- 'domain' => $domain,
939
- 'context' => $context
940
- );
941
- }
942
-
943
- return $translated_text;
944
- }
945
-
946
- /**
947
- * Load language files.
948
- * Uses the method described here:
949
- * http://geertdedeckere.be/article/loading-wordpress-language-files-the-right-way
950
- *
951
- * @since 2.0
952
- */
953
- public function load_plugin_textdomain()
954
- {
955
- $domain = 'simple-history';
956
-
957
- // The "plugin_locale" filter is also used in load_plugin_textdomain()
958
- $locale = apply_filters('plugin_locale', get_locale(), $domain);
959
- load_textdomain(
960
- $domain,
961
- WP_LANG_DIR . '/simple-history/' . $domain . '-' . $locale . '.mo'
962
- );
963
- load_plugin_textdomain(
964
- $domain,
965
- false,
966
- dirname($this->plugin_basename) . '/languages/'
967
- );
968
- }
969
-
970
- /**
971
- * Setup variables and things
972
- */
973
- public function setup_variables()
974
- {
975
- $this->externalLoggers = array();
976
- $this->externalDropins = array();
977
- $this->instantiatedLoggers = array();
978
- $this->instantiatedDropins = array();
979
-
980
- $this->plugin_basename = SIMPLE_HISTORY_BASENAME;
981
- }
982
-
983
- /**
984
- * Return capability required to view history = for who will the History page be added
985
- *
986
- * @since 2.1.5
987
- * @return string capability
988
- */
989
- public function get_view_history_capability()
990
- {
991
- $view_history_capability = 'edit_pages';
992
- $view_history_capability = apply_filters(
993
- 'simple_history_view_history_capability',
994
- $view_history_capability
995
- );
996
- $view_history_capability = apply_filters(
997
- 'simple_history/view_history_capability',
998
- $view_history_capability
999
- );
1000
-
1001
- return $view_history_capability;
1002
- }
1003
-
1004
- /**
1005
- * Return capability required to view settings
1006
- *
1007
- * @since 2.1.5
1008
- * @return string capability
1009
- */
1010
- public function get_view_settings_capability()
1011
- {
1012
- $view_settings_capability = 'manage_options';
1013
- $view_settings_capability = apply_filters(
1014
- 'simple_history_view_settings_capability',
1015
- $view_settings_capability
1016
- );
1017
- $view_settings_capability = apply_filters(
1018
- 'simple_history/view_settings_capability',
1019
- $view_settings_capability
1020
- );
1021
-
1022
- return $view_settings_capability;
1023
- }
1024
-
1025
- /**
1026
- * Check if the current user can clear the log
1027
- *
1028
- * @since 2.19
1029
- * @return bool
1030
- */
1031
- public function user_can_clear_log()
1032
- {
1033
- $user_can_clear_log = apply_filters(
1034
- 'simple_history/user_can_clear_log',
1035
- true
1036
- );
1037
-
1038
- return $user_can_clear_log;
1039
- }
1040
-
1041
- /**
1042
- * Adds default tabs to settings
1043
- */
1044
- public function add_default_settings_tabs()
1045
- {
1046
- // Add default settings tabs
1047
- $this->arr_settings_tabs = array(
1048
- array(
1049
- 'slug' => 'settings',
1050
- 'name' => __('Settings', 'simple-history'),
1051
- 'function' => array($this, 'settings_output_general')
1052
- )
1053
- );
1054
-
1055
- if (defined('SIMPLE_HISTORY_DEV') && SIMPLE_HISTORY_DEV) {
1056
- $arr_dev_tabs = array(
1057
- array(
1058
- 'slug' => 'log',
1059
- 'name' => __('Log (debug)', 'simple-history'),
1060
- 'function' => array($this, 'settings_output_log')
1061
- ),
1062
- array(
1063
- 'slug' => 'styles-example',
1064
- 'name' => __('Styles example (debug)', 'simple-history'),
1065
- 'function' => array($this, 'settings_output_styles_example')
1066
- )
1067
- );
1068
-
1069
- $this->arr_settings_tabs = array_merge(
1070
- $this->arr_settings_tabs,
1071
- $arr_dev_tabs
1072
- );
1073
- }
1074
- }
1075
-
1076
- /**
1077
- * Register an external logger so Simple History knows about it.
1078
- * Does not load the logger, so file with logger class must be loaded already.
1079
- *
1080
- * See example-logger.php for an example on how to use this.
1081
- *
1082
- * @since 2.1
1083
- */
1084
- function register_logger($loggerClassName)
1085
- {
1086
- $this->externalLoggers[] = $loggerClassName;
1087
- }
1088
-
1089
- /**
1090
- * Register an external dropin so Simple History knows about it.
1091
- * Does not load the dropin, so file with dropin class must be loaded already.
1092
- *
1093
- * See example-dropin.php for an example on how to use this.
1094
- *
1095
- * @since 2.1
1096
- */
1097
- function register_dropin($dropinClassName)
1098
- {
1099
- $this->externalDropins[] = $dropinClassName;
1100
- }
1101
-
1102
- /**
1103
- * Load built in loggers from all files in /loggers
1104
- * and instantiates them
1105
- */
1106
- public function load_loggers()
1107
- {
1108
- $loggersDir = SIMPLE_HISTORY_PATH . 'loggers/';
1109
-
1110
- $loggersFiles = array(
1111
- // Main loggers.
1112
- $loggersDir . 'SimpleCommentsLogger.php',
1113
- $loggersDir . 'SimpleCoreUpdatesLogger.php',
1114
- $loggersDir . 'SimpleExportLogger.php',
1115
- $loggersDir . 'SimpleLegacyLogger.php',
1116
- $loggersDir . 'SimpleLogger.php',
1117
- $loggersDir . 'SimpleMediaLogger.php',
1118
- $loggersDir . 'SimpleMenuLogger.php',
1119
- $loggersDir . 'SimpleOptionsLogger.php',
1120
- $loggersDir . 'SimplePluginLogger.php',
1121
- $loggersDir . 'SimplePostLogger.php',
1122
- $loggersDir . 'SimpleThemeLogger.php',
1123
- $loggersDir . 'SimpleUserLogger.php',
1124
- $loggersDir . 'SimpleCategoriesLogger.php',
1125
- $loggersDir . 'AvailableUpdatesLogger.php',
1126
- $loggersDir . 'FileEditsLogger.php',
1127
- $loggersDir . 'class-sh-privacy-logger.php',
1128
- $loggersDir . 'class-sh-translations-logger.php',
1129
- $loggersDir . 'class-sh-jetpack-logger.php',
1130
-
1131
- // Loggers for third party plugins.
1132
- $loggersDir . 'PluginUserSwitchingLogger.php',
1133
- $loggersDir . 'PluginEnableMediaReplaceLogger.php',
1134
- $loggersDir . 'Plugin_UltimateMembers_Logger.php',
1135
- $loggersDir . 'Plugin_LimitLoginAttempts.php',
1136
- $loggersDir . 'Plugin_Redirection.php',
1137
- $loggersDir . 'Plugin_DuplicatePost.php',
1138
- $loggersDir . 'Plugin_ACF.php',
1139
- $loggersDir . 'Plugin_BeaverBuilder.php'
1140
- );
1141
-
1142
- // SimpleLogger.php must be loaded first and always since the other loggers extend it.
1143
- // Include it manually so no risk of anyone using filters or similar disables it.
1144
- include_once $loggersDir . 'SimpleLogger.php';
1145
-
1146
- /**
1147
- * Filter the array with absolute paths to logger files to be loaded.
1148
- *
1149
- * Each file will be loaded and will be assumed to be a logger with a classname
1150
- * the same as the filename.
1151
- *
1152
- * @since 2.0
1153
- *
1154
- * @param array $loggersFiles Array with filenames
1155
- */
1156
- $loggersFiles = apply_filters(
1157
- 'simple_history/loggers_files',
1158
- $loggersFiles
1159
- );
1160
-
1161
- // Array with slug of loggers to instantiate.
1162
- // Slug of logger must also be the name of the logger class.
1163
- $arr_loggers_to_instantiate = array();
1164
-
1165
- // $one_logger_file = "SimpleCommentsLogger.php", "class-privacy-logger.php", and so on.
1166
- foreach ($loggersFiles as $one_logger_file) {
1167
- $load_logger = true;
1168
-
1169
- // SimpleCommentsLogger.php -> SimpleCommentsLogger.
1170
- // class-privacy-logger.php -> class-privacy-logger.
1171
- $basename_no_suffix = basename($one_logger_file, '.php');
1172
-
1173
- /**
1174
- * Filter to completely skip loading of a logger
1175
- *
1176
- * @since 2.0.22
1177
- *
1178
- * @param bool if to load the logger. return false to not load it.
1179
- * @param string basename of logger, i.e. "SimpleCommentsLogger" or "class-privacy-logger"
1180
- */
1181
- $load_logger = apply_filters(
1182
- 'simple_history/logger/load_logger',
1183
- $load_logger,
1184
- $basename_no_suffix
1185
- );
1186
-
1187
- // If logger was SimpleLogger then force it to be loaded because for example
1188
- // custom extended plugins added later probably depends on it.
1189
- if ('SimpleLogger' === $basename_no_suffix) {
1190
- $load_logger = true;
1191
- }
1192
-
1193
- if (!$load_logger) {
1194
- continue;
1195
- }
1196
-
1197
- include_once $one_logger_file;
1198
-
1199
- $arr_loggers_to_instantiate[] = $basename_no_suffix;
1200
- }
1201
-
1202
- /**
1203
- * Action that plugins can use to add their custom loggers.
1204
- * See register_logger() for more info.
1205
- *
1206
- * @since 2.1
1207
- *
1208
- * @param SimpleHistory instance
1209
- */
1210
- do_action('simple_history/add_custom_logger', $this);
1211
-
1212
- $arr_loggers_to_instantiate = array_merge(
1213
- $arr_loggers_to_instantiate,
1214
- $this->externalLoggers
1215
- );
1216
-
1217
- /**
1218
- * Filter the array with names of loggers to instantiate.
1219
- *
1220
- * Array
1221
- * (
1222
- * [0] => SimpleCommentsLogger
1223
- * [1] => SimpleCoreUpdatesLogger
1224
- * ...
1225
- * )
1226
- *
1227
- * @since 2.0
1228
- *
1229
- * @param array $arr_loggers_to_instantiate Array with class names
1230
- */
1231
- $arr_loggers_to_instantiate = apply_filters(
1232
- 'simple_history/loggers_to_instantiate',
1233
- $arr_loggers_to_instantiate
1234
- );
1235
-
1236
- // Instantiate each logger.
1237
- foreach ($arr_loggers_to_instantiate as $one_logger_name) {
1238
- // Detect logger class name.
1239
- $logger_class_name = null;
1240
-
1241
- if (class_exists($one_logger_name)) {
1242
- // Logger name is "SimpleCommentsLogger".
1243
- $logger_class_name = $one_logger_name;
1244
- } else {
1245
- // Check if class is "class-privacy-logger".
1246
- $logger_snaked_name = substr($one_logger_name, 6);
1247
- // "privacy-logger" -> "privacy_logger" -> Privacy_Logger
1248
- $logger_snaked_name = str_replace(
1249
- '-',
1250
- '_',
1251
- $logger_snaked_name
1252
- );
1253
- $logger_snaked_name = sh_ucwords($logger_snaked_name, '_');
1254
-
1255
- if (class_exists($logger_snaked_name)) {
1256
- $logger_class_name = $logger_snaked_name;
1257
- }
1258
- }
1259
-
1260
- // Continue to load next logger if no valid logger class found.
1261
- if (!$logger_class_name) {
1262
- continue;
1263
- }
1264
-
1265
- // Init found logger class.
1266
- $logger_instance = new $logger_class_name($this);
1267
-
1268
- if (
1269
- !is_subclass_of($logger_instance, 'SimpleLogger') &&
1270
- !is_a($logger_instance, 'SimpleLogger')
1271
- ) {
1272
- continue;
1273
- }
1274
-
1275
- $logger_instance->loaded();
1276
-
1277
- // Tell gettext-filter to add untranslated messages.
1278
- $this->doFilterGettext = true;
1279
- $this->doFilterGettext_currentLogger = $logger_instance;
1280
-
1281
- $logger_info = $logger_instance->getInfo();
1282
-
1283
- // Check so no logger has a logger slug with more than 30 chars,
1284
- // because db column is only 30 chars.
1285
- if (strlen($logger_instance->slug) > 30) {
1286
- add_action('admin_notices', array(
1287
- $this,
1288
- 'admin_notice_logger_slug_to_long'
1289
- ));
1290
- }
1291
-
1292
- // Un-tell gettext filter.
1293
- $this->doFilterGettext = false;
1294
- $this->doFilterGettext_currentLogger = null;
1295
-
1296
- // LoggerInfo contains all messages, both translated an not, by key.
1297
- // Add messages to the loggerInstance.
1298
- $loopNum = 0;
1299
-
1300
- $arr_messages_by_message_key = array();
1301
-
1302
- if (
1303
- isset($logger_info['messages']) &&
1304
- is_array($logger_info['messages'])
1305
- ) {
1306
- foreach (
1307
- (array) $logger_info['messages']
1308
- as $message_key => $message_translated
1309
- ) {
1310
- // Find message in array with both translated and non translated strings.
1311
- foreach (
1312
- $logger_instance->messages
1313
- as $one_message_with_translation_info
1314
- ) {
1315
- if (
1316
- $message_translated ==
1317
- $one_message_with_translation_info[
1318
- 'translated_text'
1319
- ]
1320
- ) {
1321
- $arr_messages_by_message_key[
1322
- $message_key
1323
- ] = $one_message_with_translation_info;
1324
- continue;
1325
- }
1326
- }
1327
- }
1328
- }
1329
-
1330
- $logger_instance->messages = $arr_messages_by_message_key;
1331
-
1332
- // Add logger to array of loggers.
1333
- $this->instantiatedLoggers[$logger_instance->slug] = array(
1334
- 'name' => $logger_info['name'],
1335
- 'instance' => $logger_instance
1336
- );
1337
- } // End foreach().
1338
-
1339
- do_action('simple_history/loggers_loaded');
1340
- }
1341
-
1342
- /**
1343
- * Load built in dropins from all files in /dropins
1344
- * and instantiates them
1345
- */
1346
- public function load_dropins()
1347
- {
1348
- $dropinsDir = SIMPLE_HISTORY_PATH . 'dropins/';
1349
-
1350
- $dropinsFiles = array(
1351
- $dropinsDir . 'SimpleHistoryPluginPatchesDropin.php',
1352
- $dropinsDir . 'SimpleHistoryDonateDropin.php',
1353
- $dropinsDir . 'SimpleHistoryExportDropin.php',
1354
- $dropinsDir . 'SimpleHistoryFilterDropin.php',
1355
- $dropinsDir . 'SimpleHistoryIpInfoDropin.php',
1356
- $dropinsDir . 'SimpleHistoryNewRowsNotifier.php',
1357
- $dropinsDir . 'SimpleHistoryRSSDropin.php',
1358
- $dropinsDir . 'SimpleHistorySettingsLogtestDropin.php',
1359
- $dropinsDir . 'SimpleHistorySettingsStatsDropin.php',
1360
- $dropinsDir . 'SimpleHistorySettingsDebugDropin.php',
1361
- $dropinsDir . 'SimpleHistorySidebarDropin.php',
1362
- $dropinsDir . 'SimpleHistorySidebarStats.php',
1363
- $dropinsDir . 'SimpleHistorySidebarSettings.php',
1364
- $dropinsDir . 'SimpleHistoryWPCLIDropin.php'
1365
- );
1366
-
1367
- /**
1368
- * Filter the array with absolute paths to files as returned by glob function.
1369
- * Each file will be loaded and will be assumed to be a dropin with a classname
1370
- * the same as the filename.
1371
- *
1372
- * @since 2.0
1373
- *
1374
- * @param array $dropinsFiles Array with filenames
1375
- */
1376
- $dropinsFiles = apply_filters(
1377
- 'simple_history/dropins_files',
1378
- $dropinsFiles
1379
- );
1380
-
1381
- $arrDropinsToInstantiate = array();
1382
-
1383
- foreach ($dropinsFiles as $oneDropinFile) {
1384
- // path/path/simplehistory/dropins/SimpleHistoryDonateDropin.php => SimpleHistoryDonateDropin
1385
- $oneDropinFileBasename = basename($oneDropinFile, '.php');
1386
-
1387
- $load_dropin = true;
1388
-
1389
- /**
1390
- * Filter to completely skip loading of dropin
1391
- * complete filer name will be like:
1392
- * simple_history/dropin/load_dropin_SimpleHistoryRSSDropin
1393
- *
1394
- * @since 2.0.6
1395
- *
1396
- * @param bool if to load the dropin. return false to not load it.
1397
- */
1398
- $load_dropin = apply_filters(
1399
- "simple_history/dropin/load_dropin_{$oneDropinFileBasename}",
1400
- $load_dropin
1401
- );
1402
-
1403
- /**
1404
- * Filter to completely skip loading of a dropin
1405
- *
1406
- * @since 2.0.22
1407
- *
1408
- * @param bool if to load the dropin. return false to not load it.
1409
- * @param string slug of dropin
1410
- */
1411
- $load_dropin = apply_filters(
1412
- 'simple_history/dropin/load_dropin',
1413
- $load_dropin,
1414
- $oneDropinFileBasename
1415
- );
1416
-
1417
- if (!$load_dropin) {
1418
- continue;
1419
- }
1420
-
1421
- include_once $oneDropinFile;
1422
-
1423
- $arrDropinsToInstantiate[] = $oneDropinFileBasename;
1424
- } // End foreach().
1425
-
1426
- /**
1427
- * Action that dropins can use to add their custom loggers.
1428
- * See register_dropin() for more info.
1429
- *
1430
- * @since 2.3.2
1431
- *
1432
- * @param array $arrDropinsToInstantiate Array with class names
1433
- */
1434
- do_action('simple_history/add_custom_dropin', $this);
1435
-
1436
- /**
1437
- * Filter the array with names of dropin to instantiate.
1438
- *
1439
- * @since 2.0
1440
- *
1441
- * @param array $arrDropinsToInstantiate Array with class names
1442
- */
1443
- $arrDropinsToInstantiate = apply_filters(
1444
- 'simple_history/dropins_to_instantiate',
1445
- $arrDropinsToInstantiate
1446
- );
1447
-
1448
- $arrDropinsToInstantiate = array_merge(
1449
- $arrDropinsToInstantiate,
1450
- $this->externalDropins
1451
- );
1452
-
1453
- // Instantiate each dropin
1454
- foreach ($arrDropinsToInstantiate as $oneDropinName) {
1455
- if (!class_exists($oneDropinName)) {
1456
- continue;
1457
- }
1458
-
1459
- $this->instantiatedDropins[$oneDropinName] = array(
1460
- 'name' => $oneDropinName,
1461
- 'instance' => new $oneDropinName($this)
1462
- );
1463
- }
1464
- }
1465
-
1466
- /**
1467
- * Gets the pager size,
1468
- * i.e. the number of items to show on each page in the history
1469
- *
1470
- * @return int
1471
- */
1472
- function get_pager_size()
1473
- {
1474
- $pager_size = get_option('simple_history_pager_size', 20);
1475
-
1476
- /**
1477
- * Filter the pager size setting
1478
- *
1479
- * @since 2.0
1480
- *
1481
- * @param int $pager_size
1482
- */
1483
- $pager_size = apply_filters('simple_history/pager_size', $pager_size);
1484
-
1485
- return $pager_size;
1486
- }
1487
-
1488
- /**
1489
- * Gets the pager size,
1490
- * i.e. the number of items to show on each page in the history
1491
- *
1492
- * @since 2.12
1493
- * @return int
1494
- */
1495
- function get_pager_size_dashboard()
1496
- {
1497
- $pager_size = get_option('simple_history_pager_size_dashboard', 5);
1498
-
1499
- /**
1500
- * Filter the pager size setting
1501
- *
1502
- * @since 2.12
1503
- *
1504
- * @param int $pager_size
1505
- */
1506
- $pager_size = apply_filters(
1507
- 'simple_history/pager_size_dashboard',
1508
- $pager_size
1509
- );
1510
-
1511
- return $pager_size;
1512
- }
1513
-
1514
- /**
1515
- * Show a link to our settings page on the Plugins -> Installed Plugins screen
1516
- */
1517
- function plugin_action_links($actions, $b, $c, $d)
1518
- {
1519
- // Only add link if user has the right to view the settings page
1520
- if (!current_user_can($this->get_view_settings_capability())) {
1521
- return $actions;
1522
- }
1523
-
1524
- $settings_page_url = menu_page_url(
1525
- SimpleHistory::SETTINGS_MENU_SLUG,
1526
- 0
1527
- );
1528
-
1529
- if (empty($actions)) {
1530
- // Create array if actions is empty (and therefore is assumed to be a string by PHP & results in PHP 7.1+ fatal error due to trying to make array modifications on what's assumed to be a string)
1531
- $actions = array();
1532
- } elseif (is_string($actions)) {
1533
- // Convert the string (which it might've been retrieved as) to an array for future use as an array
1534
- $actions = array($actions);
1535
- }
1536
- $actions[] =
1537
- "<a href='$settings_page_url'>" .
1538
- __('Settings', 'simple-history') .
1539
- '</a>';
1540
-
1541
- return $actions;
1542
- }
1543
-
1544
- /**
1545
- * Maybe add a dashboard widget,
1546
- * requires current user to have view history capability
1547
- * and a setting to show dashboard to be set
1548
- */
1549
- function add_dashboard_widget()
1550
- {
1551
- if (
1552
- $this->setting_show_on_dashboard() &&
1553
- current_user_can($this->get_view_history_capability())
1554
- ) {
1555
- /**
1556
- * Filter to determine if history page should be added to page below dashboard or not
1557
- *
1558
- * @since 2.0.23
1559
- *
1560
- * @param bool Show the page or not
1561
- */
1562
- $show_dashboard_widget = apply_filters(
1563
- 'simple_history/show_dashboard_widget',
1564
- true
1565
- );
1566
-
1567
- if ($show_dashboard_widget) {
1568
- wp_add_dashboard_widget(
1569
- 'simple_history_dashboard_widget',
1570
- __('Simple History', 'simple-history'),
1571
- array($this, 'dashboard_widget_output')
1572
- );
1573
- }
1574
- }
1575
- }
1576
-
1577
- /**
1578
- * Output html for the dashboard widget
1579
- */
1580
- function dashboard_widget_output()
1581
- {
1582
- $pager_size = $this->get_pager_size_dashboard();
1583
-
1584
- /**
1585
- * Filter the pager size setting for the dashboard
1586
- *
1587
- * @since 2.0
1588
- *
1589
- * @param int $pager_size
1590
- */
1591
- $pager_size = apply_filters(
1592
- 'simple_history/dashboard_pager_size',
1593
- $pager_size
1594
- );
1595
-
1596
- do_action('simple_history/dashboard/before_gui', $this);
1597
- ?>
1598
- <div class="SimpleHistoryGui"
1599
- data-pager-size='<?php echo $pager_size; ?>'
1600
- ></div>
1601
- <?php
1602
- }
1603
-
1604
- function is_on_our_own_pages($hook = '')
1605
- {
1606
- $current_screen = get_current_screen();
1607
-
1608
- if (
1609
- $current_screen &&
1610
- $current_screen->base ==
1611
- 'settings_page_' . SimpleHistory::SETTINGS_MENU_SLUG
1612
- ) {
1613
- return true;
1614
- } elseif (
1615
- $current_screen &&
1616
- $current_screen->base == 'dashboard_page_simple_history_page'
1617
- ) {
1618
- return true;
1619
- } elseif (
1620
- $hook == 'settings_page_' . SimpleHistory::SETTINGS_MENU_SLUG ||
1621
- ($this->setting_show_on_dashboard() && $hook == 'index.php') ||
1622
- ($this->setting_show_as_page() &&
1623
- $hook == 'dashboard_page_simple_history_page')
1624
- ) {
1625
- return true;
1626
- } elseif (
1627
- $current_screen &&
1628
- $current_screen->base == 'dashboard' &&
1629
- $this->setting_show_on_dashboard()
1630
- ) {
1631
- return true;
1632
- }
1633
-
1634
- return false;
1635
- }
1636
-
1637
- /**
1638
- * Enqueue styles and scripts for Simple History but only to our own pages.
1639
- *
1640
- * Only adds scripts to pages where the log is shown or the settings page.
1641
- */
1642
- function enqueue_admin_scripts($hook)
1643
- {
1644
- if ($this->is_on_our_own_pages()) {
1645
- add_thickbox();
1646
-
1647
- wp_enqueue_style(
1648
- 'simple_history_styles',
1649
- SIMPLE_HISTORY_DIR_URL . 'css/styles.css',
1650
- false,
1651
- SIMPLE_HISTORY_VERSION
1652
- );
1653
- wp_enqueue_script(
1654
- 'simple_history_script',
1655
- SIMPLE_HISTORY_DIR_URL . 'js/scripts.js',
1656
- array('jquery', 'backbone', 'wp-util'),
1657
- SIMPLE_HISTORY_VERSION,
1658
- true
1659
- );
1660
-
1661
- wp_enqueue_script(
1662
- 'select2',
1663
- SIMPLE_HISTORY_DIR_URL . 'js/select2/select2.full.min.js',
1664
- array('jquery')
1665
- );
1666
- wp_enqueue_style(
1667
- 'select2',
1668
- SIMPLE_HISTORY_DIR_URL . 'js/select2/select2.min.css'
1669
- );
1670
-
1671
- // Translations that we use in JavaScript
1672
- wp_localize_script(
1673
- 'simple_history_script',
1674
- 'simple_history_script_vars',
1675
- array(
1676
- 'settingsConfirmClearLog' => __(
1677
- 'Remove all log items?',
1678
- 'simple-history'
1679
- ),
1680
- 'pagination' => array(
1681
- 'goToTheFirstPage' => __(
1682
- 'Go to the first page',
1683
- 'simple-history'
1684
- ),
1685
- 'goToThePrevPage' => __(
1686
- 'Go to the previous page',
1687
- 'simple-history'
1688
- ),
1689
- 'goToTheNextPage' => __(
1690
- 'Go to the next page',
1691
- 'simple-history'
1692
- ),
1693
- 'goToTheLastPage' => __(
1694
- 'Go to the last page',
1695
- 'simple-history'
1696
- ),
1697
- 'currentPage' => __('Current page', 'simple-history')
1698
- ),
1699
- 'loadLogAPIError' => __(
1700
- 'Oups, the log could not be loaded right now.',
1701
- 'simple-history'
1702
- ),
1703
- 'ajaxLoadError' => __(
1704
- 'Hm, the log could not be loaded right now. Perhaps another plugin is giving some errors. Anyway, below is the output I got from the server.',
1705
- 'simple-history'
1706
- ),
1707
- 'logNoHits' => __(
1708
- 'Your search did not match any history events.',
1709
- 'simple-history'
1710
- )
1711
- )
1712
- );
1713
-
1714
- // Call plugins adminCSS-method, so they can add their CSS
1715
- foreach ($this->instantiatedLoggers as $one_logger) {
1716
- if (method_exists($one_logger['instance'], 'adminCSS')) {
1717
- $one_logger['instance']->adminCSS();
1718
- }
1719
- }
1720
-
1721
- // Add timeago.js
1722
- wp_enqueue_script(
1723
- 'timeago',
1724
- SIMPLE_HISTORY_DIR_URL . 'js/timeago/jquery.timeago.js',
1725
- array('jquery'),
1726
- '1.5.2',
1727
- true
1728
- );
1729
-
1730
- // Determine current locale to load timeago locale
1731
- $locale = strtolower(substr(get_locale(), 0, 2));
1732
- $locale_url_path =
1733
- SIMPLE_HISTORY_DIR_URL .
1734
- 'js/timeago/locales/jquery.timeago.%s.js';
1735
- $locale_dir_path =
1736
- SIMPLE_HISTORY_PATH . 'js/timeago/locales/jquery.timeago.%s.js';
1737
-
1738
- // Only enqueue if locale-file exists on file system
1739
- if (file_exists(sprintf($locale_dir_path, $locale))) {
1740
- wp_enqueue_script(
1741
- 'timeago-locale',
1742
- sprintf($locale_url_path, $locale),
1743
- array('jquery'),
1744
- '1.5.2',
1745
- true
1746
- );
1747
- } else {
1748
- wp_enqueue_script(
1749
- 'timeago-locale',
1750
- sprintf($locale_url_path, 'en'),
1751
- array('jquery'),
1752
- '1.5.2',
1753
- true
1754
- );
1755
- }
1756
- // end add timeago
1757
- // Load Select2 locale
1758
- $locale_url_path = SIMPLE_HISTORY_DIR_URL . 'js/select2/i18n/%s.js';
1759
- $locale_dir_path = SIMPLE_HISTORY_PATH . 'js/select2/i18n/%s.js';
1760
-
1761
- if (file_exists(sprintf($locale_dir_path, $locale))) {
1762
- wp_enqueue_script(
1763
- 'select2-locale',
1764
- sprintf($locale_url_path, $locale),
1765
- array('jquery'),
1766
- '3.5.1',
1767
- true
1768
- );
1769
- }
1770
-
1771
- /**
1772
- * Fires when the admin scripts have been enqueued.
1773
- * Only fires on any of the pages where Simple History is used
1774
- *
1775
- * @since 2.0
1776
- *
1777
- * @param SimpleHistory $SimpleHistory This class.
1778
- */
1779
- do_action('simple_history/enqueue_admin_scripts', $this);
1780
- } // End if().
1781
- }
1782
-
1783
- function filter_option_page_capability($capability)
1784
- {
1785
- return $capability;
1786
- }
1787
-
1788
- /**
1789
- * Check if plugin version have changed, i.e. has been upgraded
1790
- * If upgrade is detected then maybe modify database and so on for that version
1791
- */
1792
- function check_for_upgrade()
1793
- {
1794
- global $wpdb;
1795
-
1796
- $db_version = get_option('simple_history_db_version');
1797
- $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
1798
- $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
1799
- $first_install = false;
1800
-
1801
- // If no db_version is set then this
1802
- // is a version of Simple History < 0.4
1803
- // or it's a first install
1804
- // Fix database not using UTF-8
1805
- if (false === $db_version || intval($db_version) == 0) {
1806
- require_once ABSPATH . 'wp-admin/includes/upgrade.php';
1807
-
1808
- // Table creation, used to be in register_activation_hook
1809
- // We change the varchar size to add one num just to force update of encoding. dbdelta didn't see it otherwise.
1810
- $sql =
1811
- 'CREATE TABLE ' .
1812
- $table_name .
1813
- ' (
1814
- id bigint(20) NOT NULL AUTO_INCREMENT,
1815
- date datetime NOT NULL,
1816
- PRIMARY KEY (id)
1817
- ) CHARACTER SET=utf8;';
1818
-
1819
- // Upgrade db / fix utf for varchars
1820
- dbDelta($sql);
1821
-
1822
- // Fix UTF-8 for table
1823
- $sql = sprintf('alter table %1$s charset=utf8;', $table_name);
1824
- $wpdb->query($sql);
1825
-
1826
- $db_version_prev = $db_version;
1827
- $db_version = 1;
1828
-
1829
- update_option('simple_history_db_version', $db_version);
1830
-
1831
- // We are not 100% sure that this is a first install,
1832
- // but it is at least a very old version that is being updated
1833
- $first_install = true;
1834
- } // End if().
1835
-
1836
- // If db version is 1 then upgrade to 2
1837
- // Version 2 added the action_description column
1838
- if (1 == intval($db_version)) {
1839
- // V2 used to add column "action_description"
1840
- // but it's not used any more so don't do i
1841
- $db_version_prev = $db_version;
1842
- $db_version = 2;
1843
-
1844
- update_option('simple_history_db_version', $db_version);
1845
- }
1846
-
1847
- // Check that all options we use are set to their defaults, if they miss value
1848
- // Each option that is missing a value will make a sql call otherwise = unnecessary
1849
- $arr_options = array(
1850
- array(
1851
- 'name' => 'simple_history_show_as_page',
1852
- 'default_value' => 1
1853
- ),
1854
- array(
1855
- 'name' => 'simple_history_show_on_dashboard',
1856
- 'default_value' => 1
1857
- )
1858
- );
1859
-
1860
- foreach ($arr_options as $one_option) {
1861
- if (false === ($option_value = get_option($one_option['name']))) {
1862
- // Value is not set in db, so set it to a default
1863
- update_option(
1864
- $one_option['name'],
1865
- $one_option['default_value']
1866
- );
1867
- }
1868
- }
1869
-
1870
- /**
1871
- * If db_version is 2 then upgrade to 3:
1872
- * - Add some fields to existing table wp_simple_history_contexts
1873
- * - Add all new table wp_simple_history_contexts
1874
- *
1875
- * @since 2.0
1876
- */
1877
- if (2 == intval($db_version)) {
1878
- require_once ABSPATH . 'wp-admin/includes/upgrade.php';
1879
-
1880
- // Update old table
1881
- $sql = "
1882
- CREATE TABLE {$table_name} (
1883
- id bigint(20) NOT NULL AUTO_INCREMENT,
1884
- date datetime NOT NULL,
1885
- logger varchar(30) DEFAULT NULL,
1886
- level varchar(20) DEFAULT NULL,
1887
- message varchar(255) DEFAULT NULL,
1888
- occasionsID varchar(32) DEFAULT NULL,
1889
- initiator varchar(16) DEFAULT NULL,
1890
- PRIMARY KEY (id),
1891
- KEY date (date),
1892
- KEY loggerdate (logger,date)
1893
- ) CHARSET=utf8;";
1894
-
1895
- dbDelta($sql);
1896
-
1897
- // Add context table
1898
- $sql = "
1899
- CREATE TABLE IF NOT EXISTS {$table_name_contexts} (
1900
- context_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
1901
- history_id bigint(20) unsigned NOT NULL,
1902
- `key` varchar(255) DEFAULT NULL,
1903
- value longtext,
1904
- PRIMARY KEY (context_id),
1905
- KEY history_id (history_id),
1906
- KEY `key` (`key`)
1907
- ) CHARSET=utf8;
1908
- ";
1909
-
1910
- $wpdb->query($sql);
1911
-
1912
- $db_version_prev = $db_version;
1913
- $db_version = 3;
1914
- update_option('simple_history_db_version', $db_version);
1915
-
1916
- // Update possible old items to use SimpleLegacyLogger
1917
- $sql = sprintf(
1918
- '
1919
- UPDATE %1$s
1920
- SET
1921
- logger = "SimpleLegacyLogger",
1922
- level = "info"
1923
- WHERE logger IS NULL
1924
- ',
1925
- $table_name
1926
- );
1927
-
1928
- $wpdb->query($sql);
1929
-
1930
- // Say welcome, however loggers are not added this early so we need to
1931
- // use a filter to load it later
1932
- add_action('simple_history/loggers_loaded', array(
1933
- $this,
1934
- 'addWelcomeLogMessage'
1935
- ));
1936
- } // End if().
1937
-
1938
- /**
1939
- * If db version = 3
1940
- * then we need to update database to allow null values for some old columns
1941
- * that used to work in pre wp 4.1 beta, but since 4.1 wp uses STRICT_ALL_TABLES
1942
- * WordPress Commit: https://github.com/WordPress/WordPress/commit/f17d168a0f72211a9bfd9d3fa680713069871bb6
1943
- *
1944
- * @since 2.0
1945
- */
1946
- if (3 == intval($db_version)) {
1947
- require_once ABSPATH . 'wp-admin/includes/upgrade.php';
1948
-
1949
- // If old columns exist = this is an old install, then modify the columns so we still can keep them
1950
- // we want to keep them because user may have logged items that they want to keep
1951
- $db_cools = $wpdb->get_col("DESCRIBE $table_name");
1952
-
1953
- if (in_array('action', $db_cools)) {
1954
- $sql = sprintf(
1955
- '
1956
- ALTER TABLE %1$s
1957
- MODIFY `action` varchar(255) NULL,
1958
- MODIFY `object_type` varchar(255) NULL,
1959
- MODIFY `object_subtype` varchar(255) NULL,
1960
- MODIFY `user_id` int(10) NULL,
1961
- MODIFY `object_id` int(10) NULL,
1962
- MODIFY `object_name` varchar(255) NULL
1963
- ',
1964
- $table_name
1965
- );
1966
- $wpdb->query($sql);
1967
- }
1968
-
1969
- $db_version_prev = $db_version;
1970
- $db_version = 4;
1971
-
1972
- update_option('simple_history_db_version', $db_version);
1973
- } // End if().
1974
-
1975
- // Some installs on 2.2.2 got failed installs
1976
- // We detect these by checking for db_version and then running the install stuff again
1977
- if (4 == intval($db_version)) {
1978
- if (!$this->does_database_have_data()) {
1979
- // not ok, decrease db number so installs will run again and hopefully fix things
1980
- $db_version = 0;
1981
- } else {
1982
- // all looks ok, upgrade to db version 5, so this part is not done again
1983
- $db_version = 5;
1984
- }
1985
-
1986
- update_option('simple_history_db_version', $db_version);
1987
- }
1988
- } // end check_for_upgrade
1989
-
1990
- /**
1991
- * Check if the database has data/rows
1992
- *
1993
- * @since 2.1.6
1994
- * @return bool True if database is not empty, false if database is empty = contains no data
1995
- */
1996
- function does_database_have_data()
1997
- {
1998
- global $wpdb;
1999
-
2000
- $tableprefix = $wpdb->prefix;
2001
-
2002
- $simple_history_table = SimpleHistory::DBTABLE;
2003
- $simple_history_context_table = SimpleHistory::DBTABLE_CONTEXTS;
2004
-
2005
- $sql_data_exists = "SELECT id AS id_exists FROM {$tableprefix}{$simple_history_table} LIMIT 1";
2006
- $data_exists = (bool) $wpdb->get_var($sql_data_exists, 0);
2007
-
2008
- return $data_exists;
2009
- }
2010
-
2011
- /**
2012
- * Greet users to version 2!
2013
- * Is only called after database has been upgraded, so only on first install (or upgrade).
2014
- * Not called after only plugin activation.
2015
- */
2016
- public function addWelcomeLogMessage()
2017
- {
2018
- $db_data_exists = $this->does_database_have_data();
2019
- // $db_data_exists = false;
2020
- $pluginLogger = $this->getInstantiatedLoggerBySlug(
2021
- 'SimplePluginLogger'
2022
- );
2023
- if ($pluginLogger) {
2024
- // Add plugin installed message
2025
- $context = array(
2026
- 'plugin_name' => 'Simple History',
2027
- 'plugin_description' =>
2028
- 'Plugin that logs various things that occur in WordPress and then presents those events in a very nice GUI.',
2029
- 'plugin_url' => 'http://simple-history.com',
2030
- 'plugin_version' => SIMPLE_HISTORY_VERSION,
2031
- 'plugin_author' => 'Pär Thernström'
2032
- );
2033
-
2034
- $pluginLogger->infoMessage('plugin_installed', $context);
2035
-
2036
- // Add plugin activated message
2037
- $context['plugin_slug'] = 'simple-history';
2038
- $context['plugin_title'] =
2039
- '<a href="http://simple-history.com/">Simple History</a>';
2040
-
2041
- $pluginLogger->infoMessage('plugin_activated', $context);
2042
- }
2043
-
2044
- if (!$db_data_exists) {
2045
- $welcome_message_1 = __(
2046
- '
2047
  Welcome to Simple History!
2048
 
2049
  This is the main history feed. It will contain events that this plugin has logged.
2050
  ',
2051
- 'simple-history'
2052
- );
2053
 
2054
- $welcome_message_2 = __(
2055
- '
2056
  Because Simple History was just recently installed, this feed does not contain much events yet. But keep the plugin activated and soon you will see detailed information about page edits, plugin updates, user logins, and much more.
2057
  ',
2058
- 'simple-history'
2059
- );
2060
-
2061
- SimpleLogger()->info($welcome_message_2, array(
2062
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS
2063
- ));
2064
-
2065
- SimpleLogger()->info($welcome_message_1, array(
2066
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS
2067
- ));
2068
- }
2069
- }
2070
-
2071
- public function registerSettingsTab($arr_tab_settings)
2072
- {
2073
- $this->arr_settings_tabs[] = $arr_tab_settings;
2074
- }
2075
-
2076
- public function getSettingsTabs()
2077
- {
2078
- return $this->arr_settings_tabs;
2079
- }
2080
-
2081
- /**
2082
- * Output HTML for the settings page
2083
- * Called from add_options_page
2084
- */
2085
- function settings_page_output()
2086
- {
2087
- $arr_settings_tabs = $this->getSettingsTabs(); ?>
2088
- <div class="wrap">
2089
-
2090
- <h1 class="SimpleHistoryPageHeadline">
2091
- <div class="dashicons dashicons-backup SimpleHistoryPageHeadline__icon"></div>
2092
- <?php _e('Simple History Settings', 'simple-history'); ?>
2093
- </h1>
2094
-
2095
- <?php
2096
- $active_tab = isset($_GET['selected-tab'])
2097
- ? $_GET['selected-tab']
2098
- : 'settings';
2099
- $settings_base_url = menu_page_url(SimpleHistory::SETTINGS_MENU_SLUG, 0);
2100
- ?>
2101
-
2102
- <h2 class="nav-tab-wrapper">
2103
- <?php foreach ($arr_settings_tabs as $one_tab) {
2104
- $tab_slug = $one_tab['slug'];
2105
-
2106
- printf(
2107
- '<a href="%3$s" class="nav-tab %4$s">%1$s</a>',
2108
- $one_tab['name'], // 1
2109
- $tab_slug, // 2
2110
- esc_url(
2111
- add_query_arg('selected-tab', $tab_slug, $settings_base_url)
2112
- ), // 3
2113
- $active_tab == $tab_slug ? 'nav-tab-active' : '' // 4
2114
- );
2115
- } ?>
2116
- </h2>
2117
-
2118
- <?php
2119
- // Output contents for selected tab
2120
- $arr_active_tab = wp_filter_object_list($arr_settings_tabs, array(
2121
- 'slug' => $active_tab
2122
- ));
2123
- $arr_active_tab = current($arr_active_tab);
2124
-
2125
- // We must have found an active tab and it must have a callable function
2126
- if (!$arr_active_tab || !is_callable($arr_active_tab['function'])) {
2127
- wp_die(__('No valid callback found', 'simple-history'));
2128
- }
2129
-
2130
- $args = array(
2131
- 'arr_active_tab' => $arr_active_tab
2132
- );
2133
-
2134
- call_user_func_array($arr_active_tab['function'], $args);?>
2135
-
2136
- </div>
2137
- <?php
2138
- }
2139
-
2140
- public function settings_output_log()
2141
- {
2142
- include SIMPLE_HISTORY_PATH . 'templates/settings-log.php';
2143
- }
2144
-
2145
- public function settings_output_general()
2146
- {
2147
- include SIMPLE_HISTORY_PATH . 'templates/settings-general.php';
2148
- }
2149
-
2150
- public function settings_output_styles_example()
2151
- {
2152
- include SIMPLE_HISTORY_PATH . 'templates/settings-style-example.php';
2153
- }
2154
-
2155
- /**
2156
- * Content for section intro. Leave it be, even if empty.
2157
- * Called from add_sections_setting.
2158
- */
2159
- function settings_section_output()
2160
- {
2161
- }
2162
-
2163
- /**
2164
- * Add pages (history page and settings page)
2165
- */
2166
- function add_admin_pages()
2167
- {
2168
- // Add a history page as a sub-page below the Dashboard menu item
2169
- if ($this->setting_show_as_page()) {
2170
- /**
2171
- * Filter to determine if history page should be added to page below dashboard or not
2172
- *
2173
- * @since 2.0.23
2174
- *
2175
- * @param bool Show the page or not
2176
- */
2177
- $show_dashboard_page = apply_filters(
2178
- 'simple_history/show_dashboard_page',
2179
- true
2180
- );
2181
-
2182
- if ($show_dashboard_page) {
2183
- add_dashboard_page(
2184
- _x(
2185
- 'Simple History',
2186
- 'dashboard title name',
2187
- 'simple-history'
2188
- ),
2189
- _x(
2190
- 'Simple History',
2191
- 'dashboard menu name',
2192
- 'simple-history'
2193
- ),
2194
- $this->get_view_history_capability(),
2195
- 'simple_history_page',
2196
- array($this, 'history_page_output')
2197
- );
2198
- }
2199
- }
2200
-
2201
- // Add a settings page
2202
- $show_settings_page = true;
2203
- $show_settings_page = apply_filters(
2204
- 'simple_history_show_settings_page',
2205
- $show_settings_page
2206
- );
2207
- $show_settings_page = apply_filters(
2208
- 'simple_history/show_settings_page',
2209
- $show_settings_page
2210
- );
2211
-
2212
- if ($show_settings_page) {
2213
- add_options_page(
2214
- __('Simple History Settings', 'simple-history'),
2215
- _x(
2216
- 'Simple History',
2217
- 'Options page menu title',
2218
- 'simple-history'
2219
- ),
2220
- $this->get_view_settings_capability(),
2221
- SimpleHistory::SETTINGS_MENU_SLUG,
2222
- array($this, 'settings_page_output')
2223
- );
2224
- }
2225
- }
2226
-
2227
- /**
2228
- * Add setting sections and settings for the settings page
2229
- * Also maybe save some settings before outputing them
2230
- */
2231
- function add_settings()
2232
- {
2233
- // Clear the log if clear button was clicked in settings.
2234
- if (
2235
- isset($_GET['simple_history_clear_log_nonce']) &&
2236
- wp_verify_nonce(
2237
- $_GET['simple_history_clear_log_nonce'],
2238
- 'simple_history_clear_log'
2239
- )
2240
- ) {
2241
- if ($this->user_can_clear_log()) {
2242
- $this->clear_log();
2243
- }
2244
-
2245
- $msg = __('Cleared database', 'simple-history');
2246
-
2247
- add_settings_error(
2248
- 'simple_history_rss_feed_regenerate_secret',
2249
- 'simple_history_rss_feed_regenerate_secret',
2250
- $msg,
2251
- 'updated'
2252
- );
2253
-
2254
- set_transient('settings_errors', get_settings_errors(), 30);
2255
-
2256
- $goback = esc_url_raw(
2257
- add_query_arg('settings-updated', 'true', wp_get_referer())
2258
- );
2259
- wp_redirect($goback);
2260
- exit();
2261
- }
2262
-
2263
- // Section for general options.
2264
- // Will contain settings like where to show simple history and number of items.
2265
- $settings_section_general_id = self::SETTINGS_SECTION_GENERAL_ID;
2266
- add_settings_section(
2267
- $settings_section_general_id,
2268
- '',
2269
- array($this, 'settings_section_output'),
2270
- SimpleHistory::SETTINGS_MENU_SLUG // Same slug as for options menu page.
2271
- );
2272
-
2273
- // Settings for the general settings section
2274
- // Each setting = one row in the settings section
2275
- // add_settings_field( $id, $title, $callback, $page, $section, $args );
2276
- // Checkboxes for where to show simple history
2277
- add_settings_field(
2278
- 'simple_history_show_where',
2279
- __('Show history', 'simple-history'),
2280
- array($this, 'settings_field_where_to_show'),
2281
- SimpleHistory::SETTINGS_MENU_SLUG,
2282
- $settings_section_general_id
2283
- );
2284
-
2285
- // Nonces for show where inputs.
2286
- register_setting(
2287
- SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP,
2288
- 'simple_history_show_on_dashboard'
2289
- );
2290
- register_setting(
2291
- SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP,
2292
- 'simple_history_show_as_page'
2293
- );
2294
-
2295
- // Number if items to show on the history page.
2296
- add_settings_field(
2297
- 'simple_history_number_of_items',
2298
- __('Number of items per page on the log page', 'simple-history'),
2299
- array($this, 'settings_field_number_of_items'),
2300
- SimpleHistory::SETTINGS_MENU_SLUG,
2301
- $settings_section_general_id
2302
- );
2303
-
2304
- // Nonces for number of items inputs.
2305
- register_setting(
2306
- SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP,
2307
- 'simple_history_pager_size'
2308
- );
2309
-
2310
- // Number if items to show on dashboard.
2311
- add_settings_field(
2312
- 'simple_history_number_of_items_dashboard',
2313
- __('Number of items per page on the dashboard', 'simple-history'),
2314
- array($this, 'settings_field_number_of_items_dashboard'),
2315
- SimpleHistory::SETTINGS_MENU_SLUG,
2316
- $settings_section_general_id
2317
- );
2318
-
2319
- // Nonces for number of items inputs.
2320
- register_setting(
2321
- SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP,
2322
- 'simple_history_pager_size_dashboard'
2323
- );
2324
-
2325
- // Link/button to clear log.
2326
- if ($this->user_can_clear_log()) {
2327
- add_settings_field(
2328
- 'simple_history_clear_log',
2329
- __('Clear log', 'simple-history'),
2330
- array($this, 'settings_field_clear_log'),
2331
- SimpleHistory::SETTINGS_MENU_SLUG,
2332
- $settings_section_general_id
2333
- );
2334
- }
2335
- }
2336
-
2337
- /**
2338
- * Output for page with the history
2339
- */
2340
- function history_page_output()
2341
- {
2342
- // global $simple_history;
2343
- // $this->purge_db();
2344
- global $wpdb;
2345
-
2346
- $pager_size = $this->get_pager_size();
2347
-
2348
- /**
2349
- * Filter the pager size setting for the history page
2350
- *
2351
- * @since 2.0
2352
- *
2353
- * @param int $pager_size
2354
- */
2355
- $pager_size = apply_filters(
2356
- 'simple_history/page_pager_size',
2357
- $pager_size
2358
- );
2359
- ?>
2360
-
2361
- <div class="wrap SimpleHistoryWrap">
2362
-
2363
- <h1 class="SimpleHistoryPageHeadline">
2364
- <div class="dashicons dashicons-backup SimpleHistoryPageHeadline__icon"></div>
2365
- <?php echo _x('Simple History', 'history page headline', 'simple-history'); ?>
2366
- </h1>
2367
-
2368
- <?php /**
2369
- * Fires before the gui div
2370
- *
2371
- * @since 2.0
2372
- *
2373
- * @param SimpleHistory $SimpleHistory This class.
2374
- */
2375
- do_action('simple_history/history_page/before_gui', $this); ?>
2376
-
2377
- <div class="SimpleHistoryGuiWrap">
2378
-
2379
- <div class="SimpleHistoryGui"
2380
- data-pager-size='<?php echo $pager_size; ?>'
2381
- ></div>
2382
-
2383
- <?php /**
2384
- * Fires after the gui div
2385
  *
2386
- * @since 2.0
 
 
 
 
 
 
 
 
 
 
 
2387
  *
2388
- * @param SimpleHistory $SimpleHistory This class.
2389
  */
2390
- do_action('simple_history/history_page/after_gui', $this); ?>
2391
-
2392
- </div>
2393
-
2394
- </div>
2395
-
2396
- <?php
2397
- }
2398
-
2399
- /**
2400
- * Get setting if plugin should be visible on dasboard.
2401
- * Defaults to true
2402
- *
2403
- * @return bool
2404
- */
2405
- function setting_show_on_dashboard()
2406
- {
2407
- $show_on_dashboard = get_option('simple_history_show_on_dashboard', 1);
2408
- $show_on_dashboard = apply_filters(
2409
- 'simple_history_show_on_dashboard',
2410
- $show_on_dashboard
2411
- );
2412
- return (bool) $show_on_dashboard;
2413
- }
2414
-
2415
- /**
2416
- * Should simple history be shown as a page
2417
- * Defaults to true
2418
- *
2419
- * @return bool
2420
- */
2421
- function setting_show_as_page()
2422
- {
2423
- $setting = get_option('simple_history_show_as_page', 1);
2424
- $setting = apply_filters('simple_history_show_as_page', $setting);
2425
-
2426
- return (bool) $setting;
2427
- }
2428
-
2429
- /**
2430
- * Settings field for how many rows/items to show in log on the log page
2431
- */
2432
- function settings_field_number_of_items()
2433
- {
2434
- $current_pager_size = $this->get_pager_size(); ?>
2435
- <select name="simple_history_pager_size">
2436
- <option <?php echo $current_pager_size == 5
2437
- ? 'selected'
2438
- : ''; ?> value="5">5</option>
2439
- <option <?php echo $current_pager_size == 10
2440
- ? 'selected'
2441
- : ''; ?> value="10">10</option>
2442
- <option <?php echo $current_pager_size == 15
2443
- ? 'selected'
2444
- : ''; ?> value="15">15</option>
2445
- <option <?php echo $current_pager_size == 20
2446
- ? 'selected'
2447
- : ''; ?> value="20">20</option>
2448
- <option <?php echo $current_pager_size == 25
2449
- ? 'selected'
2450
- : ''; ?> value="25">25</option>
2451
- <option <?php echo $current_pager_size == 30
2452
- ? 'selected'
2453
- : ''; ?> value="30">30</option>
2454
- <option <?php echo $current_pager_size == 40
2455
- ? 'selected'
2456
- : ''; ?> value="40">40</option>
2457
- <option <?php echo $current_pager_size == 50
2458
- ? 'selected'
2459
- : ''; ?> value="50">50</option>
2460
- <option <?php echo $current_pager_size == 75
2461
- ? 'selected'
2462
- : ''; ?> value="75">75</option>
2463
- <option <?php echo $current_pager_size == 100
2464
- ? 'selected'
2465
- : ''; ?> value="100">100</option>
2466
- </select>
2467
- <?php
2468
- }
2469
-
2470
- /**
2471
- * Settings field for how many rows/items to show in log on the dashboard
2472
- */
2473
- function settings_field_number_of_items_dashboard()
2474
- {
2475
- $current_pager_size = $this->get_pager_size_dashboard(); ?>
2476
- <select name="simple_history_pager_size_dashboard">
2477
- <option <?php echo $current_pager_size == 5
2478
- ? 'selected'
2479
- : ''; ?> value="5">5</option>
2480
- <option <?php echo $current_pager_size == 10
2481
- ? 'selected'
2482
- : ''; ?> value="10">10</option>
2483
- <option <?php echo $current_pager_size == 15
2484
- ? 'selected'
2485
- : ''; ?> value="15">15</option>
2486
- <option <?php echo $current_pager_size == 20
2487
- ? 'selected'
2488
- : ''; ?> value="20">20</option>
2489
- <option <?php echo $current_pager_size == 25
2490
- ? 'selected'
2491
- : ''; ?> value="25">25</option>
2492
- <option <?php echo $current_pager_size == 30
2493
- ? 'selected'
2494
- : ''; ?> value="30">30</option>
2495
- <option <?php echo $current_pager_size == 40
2496
- ? 'selected'
2497
- : ''; ?> value="40">40</option>
2498
- <option <?php echo $current_pager_size == 50
2499
- ? 'selected'
2500
- : ''; ?> value="50">50</option>
2501
- <option <?php echo $current_pager_size == 75
2502
- ? 'selected'
2503
- : ''; ?> value="75">75</option>
2504
- <option <?php echo $current_pager_size == 100
2505
- ? 'selected'
2506
- : ''; ?> value="100">100</option>
2507
- </select>
2508
- <?php
2509
- }
2510
-
2511
- /**
2512
- * Settings field for where to show the log, page or dashboard
2513
- */
2514
- function settings_field_where_to_show()
2515
- {
2516
- $show_on_dashboard = $this->setting_show_on_dashboard();
2517
- $show_as_page = $this->setting_show_as_page();
2518
- ?>
2519
-
2520
- <input <?php echo $show_on_dashboard
2521
- ? "checked='checked'"
2522
- : ''; ?> type="checkbox" value="1" name="simple_history_show_on_dashboard" id="simple_history_show_on_dashboard" class="simple_history_show_on_dashboard" />
2523
- <label for="simple_history_show_on_dashboard"><?php _e(
2524
- 'on the dashboard',
2525
- 'simple-history'
2526
- ); ?></label>
2527
-
2528
- <br />
2529
-
2530
- <input <?php echo $show_as_page
2531
- ? "checked='checked'"
2532
- : ''; ?> type="checkbox" value="1" name="simple_history_show_as_page" id="simple_history_show_as_page" class="simple_history_show_as_page" />
2533
- <label for="simple_history_show_as_page"><?php _e(
2534
- 'as a page under the dashboard menu',
2535
- 'simple-history'
2536
- ); ?></label>
2537
-
2538
- <?php
2539
- }
2540
-
2541
- /**
2542
- * Settings section to clear database
2543
- */
2544
- function settings_field_clear_log()
2545
- {
2546
- $clear_link = esc_url(add_query_arg('', ''));
2547
- $clear_link = wp_nonce_url(
2548
- $clear_link,
2549
- 'simple_history_clear_log',
2550
- 'simple_history_clear_log_nonce'
2551
- );
2552
- $clear_days = $this->get_clear_history_interval();
2553
-
2554
- echo '<p>';
2555
-
2556
- if ($clear_days > 0) {
2557
- echo sprintf(
2558
- __(
2559
- 'Items in the database are automatically removed after %1$s days.',
2560
- 'simple-history'
2561
- ),
2562
- $clear_days
2563
- );
2564
- } else {
2565
- _e('Items in the database are kept forever.', 'simple-history');
2566
- }
2567
-
2568
- echo '</p>';
2569
-
2570
- printf(
2571
- '<p><a class="button js-SimpleHistory-Settings-ClearLog" href="%2$s">%1$s</a></p>',
2572
- __('Clear log now', 'simple-history'),
2573
- $clear_link
2574
- );
2575
- }
2576
-
2577
- /**
2578
- * How old log entried are allowed to be.
2579
- * 0 = don't delete old entries.
2580
- *
2581
- * @return int Number of days.
2582
- */
2583
- function get_clear_history_interval()
2584
- {
2585
- $days = 60;
2586
-
2587
- /**
2588
- * Filter to modify number of days of history to keep.
2589
- * Default is 60 days.
2590
- *
2591
- * @param $days Number of days of history to keep
2592
- */
2593
- $days = (int) apply_filters(
2594
- 'simple_history_db_purge_days_interval',
2595
- $days
2596
- );
2597
- $days = (int) apply_filters(
2598
- 'simple_history/db_purge_days_interval',
2599
- $days
2600
- );
2601
-
2602
- return $days;
2603
- }
2604
-
2605
- /**
2606
- * Removes all items from the log
2607
- */
2608
- function clear_log()
2609
- {
2610
- global $wpdb;
2611
-
2612
- $tableprefix = $wpdb->prefix;
2613
-
2614
- $simple_history_table = SimpleHistory::DBTABLE;
2615
- $simple_history_context_table = SimpleHistory::DBTABLE_CONTEXTS;
2616
-
2617
- // Get number of rows before delete.
2618
- $sql_num_rows = "SELECT count(id) AS num_rows FROM {$tableprefix}{$simple_history_table}";
2619
- $num_rows = $wpdb->get_var($sql_num_rows, 0);
2620
-
2621
- // Use truncate instead of delete because it's much faster (I think, writing this much later).
2622
- $sql = "TRUNCATE {$tableprefix}{$simple_history_table}";
2623
- $wpdb->query($sql);
2624
-
2625
- $sql = "TRUNCATE {$tableprefix}{$simple_history_context_table}";
2626
- $wpdb->query($sql);
2627
-
2628
- // Zero state sucks
2629
- SimpleLogger()->info(
2630
- __(
2631
- 'The log for Simple History was cleared ({num_rows} rows were removed).',
2632
- 'simple-history'
2633
- ),
2634
- array(
2635
- 'num_rows' => $num_rows
2636
- )
2637
- );
2638
-
2639
- $this->get_cache_incrementor(true);
2640
- }
2641
-
2642
- /**
2643
- * Runs the purge_db() method sometimes
2644
- * We don't want to call it each time because it performs SQL queries
2645
- *
2646
- * @since 2.0.17
2647
- */
2648
- function maybe_purge_db()
2649
- {
2650
- // How often should we try to do this?
2651
- // Once a day = a bit tiresome.
2652
- // Let's go with sundays; purge the log on sundays.
2653
- // Day of week, 1 = mon, 7 = sun.
2654
- $day_of_week = date('N');
2655
- if (7 === (int) $day_of_week) {
2656
- $this->purge_db();
2657
- }
2658
- }
2659
-
2660
- /**
2661
- * Removes old entries from the db
2662
- */
2663
- function purge_db()
2664
- {
2665
- $do_purge_history = true;
2666
-
2667
- $do_purge_history = apply_filters(
2668
- 'simple_history_allow_db_purge',
2669
- $do_purge_history
2670
- );
2671
- $do_purge_history = apply_filters(
2672
- 'simple_history/allow_db_purge',
2673
- $do_purge_history
2674
- );
2675
-
2676
- if (!$do_purge_history) {
2677
- return;
2678
- }
2679
-
2680
- $days = $this->get_clear_history_interval();
2681
-
2682
- // Never clear log if days = 0.
2683
- if (0 == $days) {
2684
- return;
2685
- }
2686
-
2687
- global $wpdb;
2688
-
2689
- $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
2690
- $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
2691
-
2692
- while (1 > 0) {
2693
- // Get id of rows to delete.
2694
- $sql = $wpdb->prepare(
2695
- "SELECT id FROM $table_name WHERE DATE_ADD(date, INTERVAL %d DAY) < now() LIMIT 100000",
2696
- $days
2697
- );
2698
-
2699
- $ids_to_delete = $wpdb->get_col($sql);
2700
-
2701
- if (empty($ids_to_delete)) {
2702
- // Nothing to delete.
2703
- return;
2704
- }
2705
-
2706
- $sql_ids_in = implode(',', $ids_to_delete);
2707
-
2708
- // Add number of deleted rows to total_rows option.
2709
- $prev_total_rows = (int) get_option('simple_history_total_rows', 0);
2710
- $total_rows = $prev_total_rows + sizeof($ids_to_delete);
2711
- update_option('simple_history_total_rows', $total_rows);
2712
-
2713
- // Remove rows + contexts.
2714
- $sql_delete_history = "DELETE FROM {$table_name} WHERE id IN ($sql_ids_in)";
2715
- $sql_delete_history_context = "DELETE FROM {$table_name_contexts} WHERE history_id IN ($sql_ids_in)";
2716
-
2717
- $wpdb->query($sql_delete_history);
2718
- $wpdb->query($sql_delete_history_context);
2719
-
2720
- $message = _nx(
2721
- 'Simple History removed one event that were older than {days} days',
2722
- 'Simple History removed {num_rows} events that were older than {days} days',
2723
- count($ids_to_delete),
2724
- 'Database is being cleared automagically',
2725
- 'simple-history'
2726
- );
2727
-
2728
- SimpleLogger()->info($message, array(
2729
- 'days' => $days,
2730
- 'num_rows' => count($ids_to_delete)
2731
- ));
2732
-
2733
- $this->get_cache_incrementor(true);
2734
- }
2735
- }
2736
-
2737
- /**
2738
- * Return plain text output for a log row
2739
- * Uses the getLogRowPlainTextOutput of the logger that logged the row
2740
- * with fallback to SimpleLogger if logger is not available.
2741
- *
2742
- * @param array $row
2743
- * @return string
2744
- */
2745
- public function getLogRowPlainTextOutput($row)
2746
- {
2747
- $row_logger = $row->logger;
2748
- $logger = null;
2749
- $row->context =
2750
- isset($row->context) && is_array($row->context)
2751
- ? $row->context
2752
- : array();
2753
-
2754
- if (!isset($row->context['_message_key'])) {
2755
- $row->context['_message_key'] = null;
2756
- }
2757
-
2758
- // Fallback to SimpleLogger if no logger exists for row
2759
- if (!isset($this->instantiatedLoggers[$row_logger])) {
2760
- $row_logger = 'SimpleLogger';
2761
- }
2762
-
2763
- $logger = $this->instantiatedLoggers[$row_logger]['instance'];
2764
-
2765
- return $logger->getLogRowPlainTextOutput($row);
2766
- }
2767
-
2768
- /**
2769
- * Return header output for a log row
2770
- * Uses the getLogRowHeaderOutput of the logger that logged the row
2771
- * with fallback to SimpleLogger if logger is not available
2772
- *
2773
- * Loggers are discouraged to override this in the loggers,
2774
- * because the output should be the same for all items in the gui
2775
- *
2776
- * @param array $row
2777
- * @return string
2778
- */
2779
- public function getLogRowHeaderOutput($row)
2780
- {
2781
- $row_logger = $row->logger;
2782
- $logger = null;
2783
- $row->context =
2784
- isset($row->context) && is_array($row->context)
2785
- ? $row->context
2786
- : array();
2787
-
2788
- // Fallback to SimpleLogger if no logger exists for row
2789
- if (!isset($this->instantiatedLoggers[$row_logger])) {
2790
- $row_logger = 'SimpleLogger';
2791
- }
2792
-
2793
- $logger = $this->instantiatedLoggers[$row_logger]['instance'];
2794
-
2795
- return $logger->getLogRowHeaderOutput($row);
2796
- }
2797
-
2798
- /**
2799
- *
2800
- *
2801
- * @param array $row
2802
- * @return string
2803
- */
2804
- private function getLogRowSenderImageOutput($row)
2805
- {
2806
- $row_logger = $row->logger;
2807
- $logger = null;
2808
- $row->context =
2809
- isset($row->context) && is_array($row->context)
2810
- ? $row->context
2811
- : array();
2812
-
2813
- // Fallback to SimpleLogger if no logger exists for row
2814
- if (!isset($this->instantiatedLoggers[$row_logger])) {
2815
- $row_logger = 'SimpleLogger';
2816
- }
2817
-
2818
- $logger = $this->instantiatedLoggers[$row_logger]['instance'];
2819
-
2820
- return $logger->getLogRowSenderImageOutput($row);
2821
- }
2822
-
2823
- public function getLogRowDetailsOutput($row)
2824
- {
2825
- $row_logger = $row->logger;
2826
- $logger = null;
2827
- $row->context =
2828
- isset($row->context) && is_array($row->context)
2829
- ? $row->context
2830
- : array();
2831
-
2832
- // Fallback to SimpleLogger if no logger exists for row
2833
- if (!isset($this->instantiatedLoggers[$row_logger])) {
2834
- $row_logger = 'SimpleLogger';
2835
- }
2836
-
2837
- $logger = $this->instantiatedLoggers[$row_logger]['instance'];
2838
-
2839
- return $logger->getLogRowDetailsOutput($row);
2840
- }
2841
-
2842
- /**
2843
- * Works like json_encode, but adds JSON_PRETTY_PRINT if the current php version supports it
2844
- * i.e. PHP is 5.4.0 or greated
2845
- *
2846
- * @param mixed $value array|object|string|whatever that is json_encode'able.
2847
- */
2848
- public static function json_encode($value)
2849
- {
2850
- return version_compare(PHP_VERSION, '5.4.0') >= 0
2851
- ? json_encode($value, JSON_PRETTY_PRINT)
2852
- : json_encode($value);
2853
- }
2854
-
2855
- /**
2856
- * Returns true if $haystack ends with $needle
2857
- *
2858
- * @param string $haystack
2859
- * @param string $needle
2860
- */
2861
- public static function ends_with($haystack, $needle)
2862
- {
2863
- return $needle === substr($haystack, -strlen($needle));
2864
- }
2865
-
2866
- /**
2867
- * Returns the HTML output for a log row, to be used in the GUI/Activity Feed
2868
- *
2869
- * @param array $oneLogRow SimpleHistoryLogQuery array with data from SimpleHistoryLogQuery
2870
- * @return string
2871
- */
2872
- public function getLogRowHTMLOutput($oneLogRow, $args)
2873
- {
2874
- $defaults = array(
2875
- 'type' => 'overview' // or "single" to include more stuff
2876
- );
2877
-
2878
- $args = wp_parse_args($args, $defaults);
2879
-
2880
- $header_html = $this->getLogRowHeaderOutput($oneLogRow);
2881
- $plain_text_html = $this->getLogRowPlainTextOutput($oneLogRow);
2882
- $sender_image_html = $this->getLogRowSenderImageOutput($oneLogRow);
2883
-
2884
- // Details = for example thumbnail of media
2885
- $details_html = trim($this->getLogRowDetailsOutput($oneLogRow));
2886
- if ($details_html) {
2887
- $details_html = sprintf(
2888
- '<div class="SimpleHistoryLogitem__details">%1$s</div>',
2889
- $details_html
2890
- );
2891
- }
2892
-
2893
- // subsequentOccasions = including the current one
2894
- $occasions_count = $oneLogRow->subsequentOccasions - 1;
2895
- $occasions_html = '';
2896
-
2897
- if ($occasions_count > 0) {
2898
- $occasions_html = '<div class="SimpleHistoryLogitem__occasions">';
2899
-
2900
- $occasions_html .=
2901
- '<a href="#" class="SimpleHistoryLogitem__occasionsLink">';
2902
- $occasions_html .= sprintf(
2903
- _n(
2904
- '+%1$s similar event',
2905
- '+%1$s similar events',
2906
- $occasions_count,
2907
- 'simple-history'
2908
- ),
2909
- $occasions_count
2910
- );
2911
- $occasions_html .= '</a>';
2912
-
2913
- $occasions_html .=
2914
- '<span class="SimpleHistoryLogitem__occasionsLoading">';
2915
- $occasions_html .= sprintf(
2916
- __('Loading…', 'simple-history'),
2917
- $occasions_count
2918
- );
2919
- $occasions_html .= '</span>';
2920
-
2921
- $occasions_html .=
2922
- '<span class="SimpleHistoryLogitem__occasionsLoaded">';
2923
- $occasions_html .= sprintf(
2924
- __('Showing %1$s more', 'simple-history'),
2925
- $occasions_count
2926
- );
2927
- $occasions_html .= '</span>';
2928
-
2929
- $occasions_html .= '</div>';
2930
- }
2931
-
2932
- // Add data atributes to log row, so plugins can do stuff
2933
- $data_attrs = '';
2934
- $data_attrs .= sprintf(' data-row-id="%1$d" ', $oneLogRow->id);
2935
- $data_attrs .= sprintf(
2936
- ' data-occasions-count="%1$d" ',
2937
- $occasions_count
2938
- );
2939
- $data_attrs .= sprintf(
2940
- ' data-occasions-id="%1$s" ',
2941
- esc_attr($oneLogRow->occasionsID)
2942
- );
2943
-
2944
- if (isset($oneLogRow->context['_server_remote_addr'])) {
2945
- $data_attrs .= sprintf(
2946
- ' data-ip-address="%1$s" ',
2947
- esc_attr($oneLogRow->context['_server_remote_addr'])
2948
- );
2949
- }
2950
-
2951
- $arr_found_additional_ip_headers = $this->instantiatedLoggers[
2952
- 'SimpleLogger'
2953
- ]['instance']->get_event_ip_number_headers($oneLogRow);
2954
- if ($arr_found_additional_ip_headers) {
2955
- $data_attrs .= sprintf(' data-ip-address-multiple="1" ');
2956
- }
2957
-
2958
- $data_attrs .= sprintf(
2959
- ' data-logger="%1$s" ',
2960
- esc_attr($oneLogRow->logger)
2961
- );
2962
- $data_attrs .= sprintf(
2963
- ' data-level="%1$s" ',
2964
- esc_attr($oneLogRow->level)
2965
- );
2966
- $data_attrs .= sprintf(
2967
- ' data-date="%1$s" ',
2968
- esc_attr($oneLogRow->date)
2969
- );
2970
- $data_attrs .= sprintf(
2971
- ' data-initiator="%1$s" ',
2972
- esc_attr($oneLogRow->initiator)
2973
- );
2974
-
2975
- if (isset($oneLogRow->context['_user_id'])) {
2976
- $data_attrs .= sprintf(
2977
- ' data-initiator-user-id="%1$d" ',
2978
- $oneLogRow->context['_user_id']
2979
- );
2980
- }
2981
-
2982
- // If type is single then include more details
2983
- $more_details_html = '';
2984
- if ($args['type'] == 'single') {
2985
- $more_details_html = apply_filters(
2986
- 'simple_history/log_html_output_details_single/html_before_context_table',
2987
- $more_details_html,
2988
- $oneLogRow
2989
- );
2990
-
2991
- $more_details_html .= sprintf(
2992
- '<h2 class="SimpleHistoryLogitem__moreDetailsHeadline">%1$s</h2>',
2993
- __('Context data', 'simple-history')
2994
- );
2995
- $more_details_html .=
2996
- '<p>' .
2997
- __(
2998
- 'This is potentially useful meta data that a logger has saved.',
2999
- 'simple-history'
3000
- ) .
3001
- '</p>';
3002
- $more_details_html .=
3003
- "<table class='SimpleHistoryLogitem__moreDetailsContext'>";
3004
- $more_details_html .= sprintf(
3005
- '<tr>
3006
- <th>%1$s</th>
3007
- <th>%2$s</th>
3008
- </tr>',
3009
- 'Key',
3010
- 'Value'
3011
- );
3012
-
3013
- $logRowKeysToShow = array_fill_keys(
3014
- array_keys((array) $oneLogRow),
3015
- true
3016
- );
3017
-
3018
- /**
3019
- * Filter what keys to show from oneLogRow
3020
- *
3021
- * Array is in format
3022
- *
3023
- * Array
3024
- * (
3025
- * [id] => 1
3026
- * [logger] => 1
3027
- * [level] => 1
3028
- * ...
3029
- * )
3030
- *
3031
- * @since 2.0.29
3032
- *
3033
- * @param array with keys to show. key to show = key. value = boolean to show or not.
3034
- * @param object log row to show details from
3035
- */
3036
- $logRowKeysToShow = apply_filters(
3037
- 'simple_history/log_html_output_details_table/row_keys_to_show',
3038
- $logRowKeysToShow,
3039
- $oneLogRow
3040
- );
3041
-
3042
- // Hide some keys by default
3043
- unset(
3044
- $logRowKeysToShow['occasionsID'],
3045
- $logRowKeysToShow['subsequentOccasions'],
3046
- $logRowKeysToShow['rep'],
3047
- $logRowKeysToShow['repeated'],
3048
- $logRowKeysToShow['occasionsIDType'],
3049
- $logRowKeysToShow['context'],
3050
- $logRowKeysToShow['type']
3051
- );
3052
-
3053
- foreach ($oneLogRow as $rowKey => $rowVal) {
3054
- // Only columns from oneLogRow that exist in logRowKeysToShow will be outputed
3055
- if (
3056
- !array_key_exists($rowKey, $logRowKeysToShow) ||
3057
- !$logRowKeysToShow[$rowKey]
3058
- ) {
3059
- continue;
3060
- }
3061
-
3062
- // skip arrays and objects and such
3063
- if (is_array($rowVal) || is_object($rowVal)) {
3064
- continue;
3065
- }
3066
-
3067
- $more_details_html .= sprintf(
3068
- '<tr>
3069
- <td>%1$s</td>
3070
- <td>%2$s</td>
3071
- </tr>',
3072
- esc_html($rowKey),
3073
- esc_html($rowVal)
3074
- );
3075
- }
3076
-
3077
- $logRowContextKeysToShow = array_fill_keys(
3078
- array_keys((array) $oneLogRow->context),
3079
- true
3080
- );
3081
-
3082
- /**
3083
- * Filter what keys to show from the row context
3084
- *
3085
- * Array is in format
3086
- *
3087
- * Array
3088
- * (
3089
- * [plugin_slug] => 1
3090
- * [plugin_name] => 1
3091
- * [plugin_title] => 1
3092
- * [plugin_description] => 1
3093
- * [plugin_author] => 1
3094
- * [plugin_version] => 1
3095
- * ...
3096
- * )
3097
- *
3098
- * @since 2.0.29
3099
- *
3100
- * @param array with keys to show. key to show = key. value = boolean to show or not.
3101
- * @param object log row to show details from
3102
- */
3103
- $logRowContextKeysToShow = apply_filters(
3104
- 'simple_history/log_html_output_details_table/context_keys_to_show',
3105
- $logRowContextKeysToShow,
3106
- $oneLogRow
3107
- );
3108
-
3109
- foreach ($oneLogRow->context as $contextKey => $contextVal) {
3110
- // Only columns from context that exist in logRowContextKeysToShow will be outputed
3111
- if (
3112
- !array_key_exists($contextKey, $logRowContextKeysToShow) ||
3113
- !$logRowContextKeysToShow[$contextKey]
3114
- ) {
3115
- continue;
3116
- }
3117
-
3118
- $more_details_html .= sprintf(
3119
- '<tr>
3120
- <td>%1$s</td>
3121
- <td>%2$s</td>
3122
- </tr>',
3123
- esc_html($contextKey),
3124
- esc_html($contextVal)
3125
- );
3126
- }
3127
-
3128
- $more_details_html .= '</table>';
3129
-
3130
- $more_details_html = apply_filters(
3131
- 'simple_history/log_html_output_details_single/html_after_context_table',
3132
- $more_details_html,
3133
- $oneLogRow
3134
- );
3135
-
3136
- $more_details_html = sprintf(
3137
- '<div class="SimpleHistoryLogitem__moreDetails">%1$s</div>',
3138
- $more_details_html
3139
- );
3140
- } // End if().
3141
-
3142
- // Classes to add to log item li element
3143
- $classes = array(
3144
- 'SimpleHistoryLogitem',
3145
- "SimpleHistoryLogitem--loglevel-{$oneLogRow->level}",
3146
- "SimpleHistoryLogitem--logger-{$oneLogRow->logger}"
3147
- );
3148
-
3149
- if (isset($oneLogRow->initiator) && !empty($oneLogRow->initiator)) {
3150
- $classes[] =
3151
- 'SimpleHistoryLogitem--initiator-' . $oneLogRow->initiator;
3152
- }
3153
-
3154
- if ($arr_found_additional_ip_headers) {
3155
- $classes[] = 'SimpleHistoryLogitem--IPAddress-multiple';
3156
- }
3157
-
3158
- // Always append the log level tag
3159
- $log_level_tag_html = sprintf(
3160
- ' <span class="SimpleHistoryLogitem--logleveltag SimpleHistoryLogitem--logleveltag-%1$s">%2$s</span>',
3161
- $oneLogRow->level,
3162
- $this->getLogLevelTranslated($oneLogRow->level)
3163
- );
3164
-
3165
- $plain_text_html .= $log_level_tag_html;
3166
-
3167
- /**
3168
- * Filter to modify classes added to item li element
3169
- *
3170
- * @since 2.0.7
3171
- *
3172
- * @param $classes Array with classes
3173
- */
3174
- $classes = apply_filters(
3175
- 'simple_history/logrowhtmloutput/classes',
3176
- $classes
3177
- );
3178
-
3179
- // Generate the HTML output for a row
3180
- $output = sprintf(
3181
- '
3182
- <li %8$s class="%10$s">
3183
- <div class="SimpleHistoryLogitem__firstcol">
3184
- <div class="SimpleHistoryLogitem__senderImage">%3$s</div>
3185
- </div>
3186
- <div class="SimpleHistoryLogitem__secondcol">
3187
- <div class="SimpleHistoryLogitem__header">%1$s</div>
3188
- <div class="SimpleHistoryLogitem__text">%2$s</div>
3189
- %6$s <!-- details_html -->
3190
- %9$s <!-- more details html -->
3191
- %4$s <!-- occasions -->
3192
- </div>
3193
- </li>
3194
- ',
3195
- $header_html, // 1
3196
- $plain_text_html, // 2
3197
- $sender_image_html, // 3
3198
- $occasions_html, // 4
3199
- $oneLogRow->level, // 5
3200
- $details_html, // 6
3201
- $oneLogRow->logger, // 7
3202
- $data_attrs, // 8 data attributes
3203
- $more_details_html, // 9
3204
- esc_attr(join(' ', $classes)) // 10
3205
- );
3206
-
3207
- // Get the main message row.
3208
- // Should be as plain as possible, like plain text
3209
- // but with links to for example users and posts
3210
- // SimpleLoggerFormatter::getRowTextOutput($oneLogRow);
3211
- // Get detailed HTML-based output
3212
- // May include images, lists, any cool stuff needed to view
3213
- // SimpleLoggerFormatter::getRowHTMLOutput($oneLogRow);
3214
- return trim($output);
3215
- }
3216
-
3217
- /**
3218
- * Return translated loglevel
3219
- *
3220
- * @since 2.0.14
3221
- * @param string $loglevel
3222
- * @return string translated loglevel
3223
- */
3224
- function getLogLevelTranslated($loglevel)
3225
- {
3226
- $str_translated = '';
3227
-
3228
- switch ($loglevel) {
3229
- // Lowercase
3230
- case 'emergency':
3231
- $str_translated = _x(
3232
- 'emergency',
3233
- 'Log level in gui',
3234
- 'simple-history'
3235
- );
3236
- break;
3237
-
3238
- case 'alert':
3239
- $str_translated = _x(
3240
- 'alert',
3241
- 'Log level in gui',
3242
- 'simple-history'
3243
- );
3244
- break;
3245
-
3246
- case 'critical':
3247
- $str_translated = _x(
3248
- 'critical',
3249
- 'Log level in gui',
3250
- 'simple-history'
3251
- );
3252
- break;
3253
-
3254
- case 'error':
3255
- $str_translated = _x(
3256
- 'error',
3257
- 'Log level in gui',
3258
- 'simple-history'
3259
- );
3260
- break;
3261
-
3262
- case 'warning':
3263
- $str_translated = _x(
3264
- 'warning',
3265
- 'Log level in gui',
3266
- 'simple-history'
3267
- );
3268
- break;
3269
-
3270
- case 'notice':
3271
- $str_translated = _x(
3272
- 'notice',
3273
- 'Log level in gui',
3274
- 'simple-history'
3275
- );
3276
- break;
3277
-
3278
- case 'info':
3279
- $str_translated = _x(
3280
- 'info',
3281
- 'Log level in gui',
3282
- 'simple-history'
3283
- );
3284
- break;
3285
-
3286
- case 'debug':
3287
- $str_translated = _x(
3288
- 'debug',
3289
- 'Log level in gui',
3290
- 'simple-history'
3291
- );
3292
- break;
3293
-
3294
- // Uppercase
3295
- case 'Emergency':
3296
- $str_translated = _x(
3297
- 'Emergency',
3298
- 'Log level in gui',
3299
- 'simple-history'
3300
- );
3301
- break;
3302
-
3303
- case 'Alert':
3304
- $str_translated = _x(
3305
- 'Alert',
3306
- 'Log level in gui',
3307
- 'simple-history'
3308
- );
3309
- break;
3310
-
3311
- case 'Critical':
3312
- $str_translated = _x(
3313
- 'Critical',
3314
- 'Log level in gui',
3315
- 'simple-history'
3316
- );
3317
- break;
3318
-
3319
- case 'Error':
3320
- $str_translated = _x(
3321
- 'Error',
3322
- 'Log level in gui',
3323
- 'simple-history'
3324
- );
3325
- break;
3326
-
3327
- case 'Warning':
3328
- $str_translated = _x(
3329
- 'Warning',
3330
- 'Log level in gui',
3331
- 'simple-history'
3332
- );
3333
- break;
3334
-
3335
- case 'Notice':
3336
- $str_translated = _x(
3337
- 'Notice',
3338
- 'Log level in gui',
3339
- 'simple-history'
3340
- );
3341
- break;
3342
-
3343
- case 'Info':
3344
- $str_translated = _x(
3345
- 'Info',
3346
- 'Log level in gui',
3347
- 'simple-history'
3348
- );
3349
- break;
3350
-
3351
- case 'Debug':
3352
- $str_translated = _x(
3353
- 'Debug',
3354
- 'Log level in gui',
3355
- 'simple-history'
3356
- );
3357
- break;
3358
-
3359
- default:
3360
- $str_translated = $loglevel;
3361
- } // End switch().
3362
-
3363
- return $str_translated;
3364
- }
3365
-
3366
- public function getInstantiatedLoggers()
3367
- {
3368
- return $this->instantiatedLoggers;
3369
- }
3370
-
3371
- public function getInstantiatedDropins()
3372
- {
3373
- return $this->instantiatedDropins;
3374
- }
3375
-
3376
- /**
3377
- * @param string $slug
3378
- * @return mixed logger instance if found, bool false if logger not found
3379
- */
3380
- public function getInstantiatedLoggerBySlug($slug = '')
3381
- {
3382
- if (empty($slug)) {
3383
- return false;
3384
- }
3385
-
3386
- foreach ($this->getInstantiatedLoggers() as $one_logger) {
3387
- if ($slug == $one_logger['instance']->slug) {
3388
- return $one_logger['instance'];
3389
- }
3390
- }
3391
-
3392
- return false;
3393
- }
3394
-
3395
- /**
3396
- * Check which loggers a user has the right to read and return an array
3397
- * with all loggers they are allowed to read
3398
- *
3399
- * @param int $user_id Id of user to get loggers for. Defaults to current user id.
3400
- * @param string $format format to return loggers in. Default is array. Can also be "sql"
3401
- * @return array
3402
- */
3403
- public function getLoggersThatUserCanRead($user_id = '', $format = 'array')
3404
- {
3405
- $arr_loggers_user_can_view = array();
3406
-
3407
- if (!is_numeric($user_id)) {
3408
- $user_id = get_current_user_id();
3409
- }
3410
-
3411
- $loggers = $this->getInstantiatedLoggers();
3412
- foreach ($loggers as $one_logger) {
3413
- $logger_capability = $one_logger['instance']->getCapability();
3414
-
3415
- // $arr_loggers_user_can_view = apply_filters("simple_history/loggers_user_can_read", $user_id, $arr_loggers_user_can_view);
3416
- $user_can_read_logger = user_can($user_id, $logger_capability);
3417
- $user_can_read_logger = apply_filters(
3418
- 'simple_history/loggers_user_can_read/can_read_single_logger',
3419
- $user_can_read_logger,
3420
- $one_logger['instance'],
3421
- $user_id
3422
- );
3423
-
3424
- if ($user_can_read_logger) {
3425
- $arr_loggers_user_can_view[] = $one_logger;
3426
- }
3427
- }
3428
-
3429
- /**
3430
- * Fires before Simple History does it's init stuff
3431
- *
3432
- * @since 2.0
3433
- *
3434
- * @param array $arr_loggers_user_can_view Array with loggers that user $user_id can read
3435
- * @param int user_id ID of user to check read capability for
3436
- */
3437
- $arr_loggers_user_can_view = apply_filters(
3438
- 'simple_history/loggers_user_can_read',
3439
- $arr_loggers_user_can_view,
3440
- $user_id
3441
- );
3442
-
3443
- // just return array with slugs in parenthesis suitable for sql-where
3444
- if ('sql' == $format) {
3445
- $str_return = '(';
3446
-
3447
- if (sizeof($arr_loggers_user_can_view)) {
3448
- foreach ($arr_loggers_user_can_view as $one_logger) {
3449
- $str_return .= sprintf(
3450
- '"%1$s", ',
3451
- esc_sql($one_logger['instance']->slug)
3452
- );
3453
- }
3454
-
3455
- $str_return = rtrim($str_return, ' ,');
3456
- } else {
3457
- // user was not allowed to read any loggers, return in (NULL) to return nothing
3458
- $str_return .= 'NULL';
3459
- }
3460
-
3461
- $str_return .= ')';
3462
-
3463
- return $str_return;
3464
- }
3465
-
3466
- return $arr_loggers_user_can_view;
3467
- }
3468
-
3469
- /**
3470
- * Retrieve the avatar for a user who provided a user ID or email address.
3471
- * A modified version of the function that comes with WordPress, but we
3472
- * want to allow/show gravatars even if they are disabled in discussion settings
3473
- *
3474
- * @since 2.0
3475
- *
3476
- * @param string $email email address
3477
- * @param int $size Size of the avatar image
3478
- * @param string $default URL to a default image to use if no avatar is available
3479
- * @param string $alt Alternative text to use in image tag. Defaults to blank
3480
- * @return string <img> tag for the user's avatar
3481
- */
3482
- function get_avatar($email, $size = '96', $default = '', $alt = false)
3483
- {
3484
- // WP setting for avatars is to show, so just use the built in function
3485
- if (get_option('show_avatars')) {
3486
- $avatar = get_avatar($email, $size, $default, $alt);
3487
-
3488
- return $avatar;
3489
- } else {
3490
- // WP setting for avatar was to not show, but we do it anyway, using the same code as get_avatar() would have used
3491
- if (false === $alt) {
3492
- $safe_alt = '';
3493
- } else {
3494
- $safe_alt = esc_attr($alt);
3495
- }
3496
-
3497
- if (!is_numeric($size)) {
3498
- $size = '96';
3499
- }
3500
-
3501
- if (empty($default)) {
3502
- $avatar_default = get_option('avatar_default');
3503
- if (empty($avatar_default)) {
3504
- $default = 'mystery';
3505
- } else {
3506
- $default = $avatar_default;
3507
- }
3508
- }
3509
-
3510
- if (!empty($email)) {
3511
- $email_hash = md5(strtolower(trim($email)));
3512
- }
3513
-
3514
- if (is_ssl()) {
3515
- $host = 'https://secure.gravatar.com';
3516
- } else {
3517
- if (!empty($email)) {
3518
- $host = sprintf(
3519
- 'http://%d.gravatar.com',
3520
- hexdec($email_hash[0]) % 2
3521
- );
3522
- } else {
3523
- $host = 'http://0.gravatar.com';
3524
- }
3525
- }
3526
-
3527
- if ('mystery' == $default) {
3528
- $default = "$host/avatar/ad516503a11cd5ca435acc9bb6523536?s={$size}";
3529
- }
3530
- // End if().
3531
- elseif ('blank' == $default) {
3532
- $default = $email ? 'blank' : includes_url('images/blank.gif');
3533
- } elseif (!empty($email) && 'gravatar_default' == $default) {
3534
- $default = '';
3535
- } elseif ('gravatar_default' == $default) {
3536
- $default = "$host/avatar/?s={$size}";
3537
- } elseif (empty($email)) {
3538
- $default = "$host/avatar/?d=$default&amp;s={$size}";
3539
- } elseif (strpos($default, 'http://') === 0) {
3540
- $default = add_query_arg('s', $size, $default);
3541
- }
3542
-
3543
- if (!empty($email)) {
3544
- $out = "$host/avatar/";
3545
- $out .= $email_hash;
3546
- $out .= '?s=' . $size;
3547
- $out .= '&amp;d=' . urlencode($default);
3548
-
3549
- $rating = get_option('avatar_rating');
3550
- if (!empty($rating)) {
3551
- $out .= "&amp;r={$rating}";
3552
- }
3553
-
3554
- $out = str_replace('&#038;', '&amp;', esc_url($out));
3555
- $avatar = "<img alt='{$safe_alt}' src='{$out}' class='avatar avatar-{$size} photo' height='{$size}' width='{$size}' />";
3556
- } else {
3557
- $out = esc_url($default);
3558
- $avatar = "<img alt='{$safe_alt}' src='{$out}' class='avatar avatar-{$size} photo avatar-default' height='{$size}' width='{$size}' />";
3559
- }
3560
-
3561
- /**
3562
- * Filter the avatar to retrieve.
3563
- * Same filter WordPress uses
3564
- *
3565
- * @since 2.0.19
3566
- *
3567
- * @param string $avatar Image tag for the user's avatar.
3568
- * @param int|object|string $id_or_email A user ID, email address, or comment object.
3569
- * @param int $size Square avatar width and height in pixels to retrieve.
3570
- * @param string $alt Alternative text to use in the avatar image tag.
3571
- * Default empty.
3572
- */
3573
- $avatar = apply_filters(
3574
- 'get_avatar',
3575
- $avatar,
3576
- $email,
3577
- $size,
3578
- $default,
3579
- $alt
3580
- );
3581
-
3582
- return $avatar;
3583
- } // End if().
3584
- }
3585
-
3586
- /**
3587
- * Quick stats above the log
3588
- * Uses filter "simple_history/history_page/before_gui" to output its contents
3589
- */
3590
- public function output_quick_stats()
3591
- {
3592
- global $wpdb;
3593
-
3594
- // Get number of events today
3595
- $logQuery = new SimpleHistoryLogQuery();
3596
- $logResults = $logQuery->query(array(
3597
- 'posts_per_page' => 1,
3598
- 'date_from' => strtotime('today')
3599
- ));
3600
-
3601
- $total_row_count = (int) $logResults['total_row_count'];
3602
-
3603
- // Get sql query for where to read only loggers current user is allowed to read/view
3604
- $sql_loggers_in = $this->getLoggersThatUserCanRead(
3605
- get_current_user_id(),
3606
- 'sql'
3607
- );
3608
-
3609
- // Get number of users today, i.e. events with wp_user as initiator
3610
- $sql_users_today = sprintf(
3611
- '
3612
- SELECT
3613
- DISTINCT(c.value) AS user_id
3614
- #h.id, h.logger, h.level, h.initiator, h.date
3615
- FROM %3$s AS h
3616
- INNER JOIN %4$s AS c
3617
- ON c.history_id = h.id AND c.key = "_user_id"
3618
- WHERE
3619
- initiator = "wp_user"
3620
- AND logger IN %1$s
3621
- AND date > "%2$s"
3622
- ',
3623
- $sql_loggers_in,
3624
- date('Y-m-d H:i', strtotime('today')),
3625
- $wpdb->prefix . SimpleHistory::DBTABLE,
3626
- $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS
3627
- );
3628
-
3629
- $cache_key =
3630
- 'quick_stats_users_today_' . md5(serialize($sql_loggers_in));
3631
- $cache_group = 'simple-history-' . $this->get_cache_incrementor();
3632
- $results_users_today = wp_cache_get($cache_key, $cache_group);
3633
-
3634
- if (false === $results_users_today) {
3635
- $results_users_today = $wpdb->get_results($sql_users_today);
3636
- wp_cache_set($cache_key, $results_users_today, $cache_group);
3637
- }
3638
-
3639
- $count_users_today = sizeof($results_users_today);
3640
-
3641
- // Get number of other sources (not wp_user)
3642
- $sql_other_sources_where = sprintf(
3643
- '
3644
- initiator <> "wp_user"
3645
- AND logger IN %1$s
3646
- AND date > "%2$s"
3647
- ',
3648
- $sql_loggers_in,
3649
- date('Y-m-d H:i', strtotime('today')),
3650
- $wpdb->prefix . SimpleHistory::DBTABLE,
3651
- $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS
3652
- );
3653
-
3654
- $sql_other_sources_where = apply_filters(
3655
- 'simple_history/quick_stats_where',
3656
- $sql_other_sources_where
3657
- );
3658
-
3659
- $sql_other_sources = sprintf(
3660
- '
3661
- SELECT
3662
- DISTINCT(h.initiator) AS initiator
3663
- FROM %3$s AS h
3664
- WHERE
3665
- %5$s
3666
- ',
3667
- $sql_loggers_in,
3668
- date('Y-m-d H:i', strtotime('today')),
3669
- $wpdb->prefix . SimpleHistory::DBTABLE,
3670
- $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS,
3671
- $sql_other_sources_where // 5
3672
- );
3673
- // sf_d($sql_other_sources, '$sql_other_sources');
3674
- $cache_key =
3675
- 'quick_stats_results_other_sources_today_' .
3676
- md5(serialize($sql_other_sources));
3677
- $results_other_sources_today = wp_cache_get($cache_key, $cache_group);
3678
-
3679
- if (false === $results_other_sources_today) {
3680
- $results_other_sources_today = $wpdb->get_results(
3681
- $sql_other_sources
3682
- );
3683
- wp_cache_set(
3684
- $cache_key,
3685
- $results_other_sources_today,
3686
- $cache_group
3687
- );
3688
- }
3689
-
3690
- $count_other_sources = sizeof($results_other_sources_today);
3691
-
3692
- // sf_d($logResults, '$logResults');
3693
- // sf_d($results_users_today, '$sql_users_today');
3694
- // sf_d($results_other_sources_today, '$results_other_sources_today');
3695
- ?>
3696
- <div class="SimpleHistoryQuickStats">
3697
- <p>
3698
- <?php
3699
- $msg_tmpl = '';
3700
-
3701
- // No results today at all
3702
- if ($total_row_count == 0) {
3703
- $msg_tmpl = __('No events today so far.', 'simple-history');
3704
- } else {
3705
- /*
3706
- Type of results
3707
- x1 event today from 1 user.
3708
- x1 event today from 1 source.
3709
- 3 events today from 1 user.
3710
- x2 events today from 2 users.
3711
- x2 events today from 1 user and 1 other source.
3712
- x3 events today from 2 users and 1 other source.
3713
- x3 events today from 1 user and 2 other sources.
3714
- x4 events today from 2 users and 2 other sources.
3715
- */
3716
-
3717
- // A single event existed and was from a user
3718
- // 1 event today from 1 user.
3719
- if ($total_row_count == 1 && $count_users_today == 1) {
3720
- $msg_tmpl .= __('One event today from one user.', 'simple-history');
3721
- }
3722
-
3723
- // A single event existed and was from another source
3724
- // 1 event today from 1 source.
3725
- if ($total_row_count == 1 && !$count_users_today) {
3726
- $msg_tmpl .= __(
3727
- 'One event today from one source.',
3728
- 'simple-history'
3729
- );
3730
- }
3731
-
3732
- // Multiple events from a single user
3733
- // 3 events today from one user.
3734
- if (
3735
- $total_row_count > 1 &&
3736
- $count_users_today == 1 &&
3737
- !$count_other_sources
3738
- ) {
3739
- $msg_tmpl .= __(
3740
- '%1$d events today from one user.',
3741
- 'simple-history'
3742
- );
3743
- }
3744
-
3745
- // Multiple events from only users
3746
- // 2 events today from 2 users.
3747
- if ($total_row_count > 1 && $count_users_today == $total_row_count) {
3748
- $msg_tmpl .= __(
3749
- '%1$d events today from %2$d users.',
3750
- 'simple-history'
3751
- );
3752
- }
3753
-
3754
- // Multiple events from 1 single user and 1 single other source
3755
- // 2 events today from 1 user and 1 other source.
3756
- if (
3757
- $total_row_count &&
3758
- 1 == $count_users_today &&
3759
- 1 == $count_other_sources
3760
- ) {
3761
- $msg_tmpl .= __(
3762
- '%1$d events today from one user and one other source.',
3763
- 'simple-history'
3764
- );
3765
- }
3766
-
3767
- // Multiple events from multple users but from only 1 single other source
3768
- // 3 events today from 2 users and 1 other source.
3769
- if (
3770
- $total_row_count > 1 &&
3771
- $count_users_today > 1 &&
3772
- $count_other_sources == 1
3773
- ) {
3774
- $msg_tmpl .= __(
3775
- '%1$d events today from one user and one other source.',
3776
- 'simple-history'
3777
- );
3778
- }
3779
-
3780
- // Multiple events from 1 user but from multiple other source
3781
- // 3 events today from 1 user and 2 other sources.
3782
- if (
3783
- $total_row_count > 1 &&
3784
- 1 == $count_users_today &&
3785
- $count_other_sources > 1
3786
- ) {
3787
- $msg_tmpl .= __(
3788
- '%1$d events today from one user and %3$d other sources.',
3789
- 'simple-history'
3790
- );
3791
- }
3792
-
3793
- // Multiple events from multiple user and from multiple other sources
3794
- // 4 events today from 2 users and 2 other sources.
3795
- if (
3796
- $total_row_count > 1 &&
3797
- $count_users_today > 1 &&
3798
- $count_other_sources > 1
3799
- ) {
3800
- $msg_tmpl .= __(
3801
- '%1$s events today from %2$d users and %3$d other sources.',
3802
- 'simple-history'
3803
- );
3804
- }
3805
- } // End if().
3806
-
3807
- // only show stats if we have something to output
3808
- if ($msg_tmpl) {
3809
- printf(
3810
- $msg_tmpl,
3811
- $logResults['total_row_count'], // 1
3812
- $count_users_today, // 2
3813
- $count_other_sources // 3
3814
- );
3815
-
3816
- // Space between texts
3817
- /*
3818
- echo " ";
3819
-
3820
- // http://playground-root.ep/wp-admin/options-general.php?page=simple_history_settings_menu_slug&selected-tab=stats
3821
- printf(
3822
- '<a href="%1$s">View more stats</a>.',
3823
- add_query_arg("selected-tab", "stats", menu_page_url(SimpleHistory::SETTINGS_MENU_SLUG, 0))
3824
- );
3825
- */
3826
- }?>
3827
- </p>
3828
- </div>
3829
- <?php
3830
- } // output_quick_stats
3831
-
3832
- /**
3833
- * https://www.tollmanz.com/invalidation-schemes/
3834
- *
3835
- * @param $refresh bool
3836
- * @return string
3837
- */
3838
- public static function get_cache_incrementor($refresh = false)
3839
- {
3840
- $incrementor_key = 'simple_history_incrementor';
3841
- $incrementor_value = wp_cache_get($incrementor_key);
3842
-
3843
- if (false === $incrementor_value || true === $refresh) {
3844
- $incrementor_value = time();
3845
- wp_cache_set($incrementor_key, $incrementor_value);
3846
- }
3847
-
3848
- // echo "<br>incrementor_value: $incrementor_value";
3849
- return $incrementor_value;
3850
- }
3851
-
3852
- // Number of rows the last n days
3853
- function get_num_events_last_n_days($period_days = 28)
3854
- {
3855
- $transient_key = 'sh_' . md5(__METHOD__ . $period_days . '_2');
3856
-
3857
- $count = get_transient($transient_key);
3858
-
3859
- if (false === $count) {
3860
- global $wpdb;
3861
-
3862
- $sqlStringLoggersUserCanRead = $this->getLoggersThatUserCanRead(
3863
- null,
3864
- 'sql'
3865
- );
3866
-
3867
- $sql = sprintf(
3868
- '
3869
- SELECT count(*)
3870
- FROM %1$s
3871
- WHERE UNIX_TIMESTAMP(date) >= %2$d
3872
- AND logger IN %3$s
3873
- ',
3874
- $wpdb->prefix . SimpleHistory::DBTABLE,
3875
- strtotime("-$period_days days"),
3876
- $sqlStringLoggersUserCanRead
3877
- );
3878
-
3879
- $count = $wpdb->get_var($sql);
3880
-
3881
- set_transient($transient_key, $count, HOUR_IN_SECONDS);
3882
- }
3883
-
3884
- return $count;
3885
- } // get_num_events_last_n_days
3886
-
3887
- function get_num_events_per_day_last_n_days($period_days = 28)
3888
- {
3889
- $transient_key = 'sh_' . md5(__METHOD__ . $period_days . '_2');
3890
-
3891
- $dates = get_transient($transient_key);
3892
-
3893
- if (false === $dates) {
3894
- global $wpdb;
3895
-
3896
- $sqlStringLoggersUserCanRead = $this->getLoggersThatUserCanRead(
3897
- null,
3898
- 'sql'
3899
- );
3900
-
3901
- $sql = sprintf(
3902
- '
3903
- SELECT
3904
- date_format(date, "%%Y-%%m-%%d") AS yearDate,
3905
- count(date) AS count
3906
- FROM
3907
- %1$s
3908
- WHERE
3909
- UNIX_TIMESTAMP(date) >= %2$d
3910
- AND logger IN (%3$d)
3911
- GROUP BY yearDate
3912
- ORDER BY yearDate ASC
3913
- ',
3914
- $wpdb->prefix . SimpleHistory::DBTABLE,
3915
- strtotime("-$period_days days"),
3916
- $sqlStringLoggersUserCanRead
3917
- );
3918
-
3919
- $dates = $wpdb->get_results($sql);
3920
-
3921
- set_transient($transient_key, $dates, HOUR_IN_SECONDS);
3922
- // echo "set";exit;
3923
- } else {
3924
- // echo "get";exit;
3925
- }
3926
-
3927
- return $dates;
3928
- } // get_num_events_per_day_for_period
3929
-
3930
- // Number of unique events the last n days
3931
- public function get_unique_events_for_days($days = 7)
3932
- {
3933
- global $wpdb;
3934
-
3935
- $days = (int) $days;
3936
-
3937
- $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
3938
-
3939
- $cache_key = 'sh_' . md5(__METHOD__ . $days);
3940
-
3941
- $numEvents = get_transient($cache_key);
3942
-
3943
- if (false == $numEvents) {
3944
- $sql = $wpdb->prepare(
3945
- "
3946
- SELECT count( DISTINCT occasionsID )
3947
- FROM $table_name
3948
- WHERE date >= DATE_ADD(CURDATE(), INTERVAL -%d DAY)
3949
- ",
3950
- $days
3951
- );
3952
-
3953
- $numEvents = $wpdb->get_var($sql);
3954
-
3955
- set_transient($cache_key, $numEvents, HOUR_IN_SECONDS);
3956
- }
3957
-
3958
- return $numEvents;
3959
- } // get_unique_events_for_days
3960
-
3961
- /**
3962
- * Output an admin notice about logger slug being to long
3963
- */
3964
- public function admin_notice_logger_slug_to_long()
3965
- {
3966
- ?>
3967
- <div class="error notice">
3968
- <p><?php echo esc_html__(
3969
- 'The slug for a logger in Simple History can be max 30 chars long.',
3970
- 'simple-history'
3971
- ); ?></p>
3972
- </div>
3973
- <?php
3974
- }
3975
- } // class
3976
 
3977
- /**
3978
- * Helper function with same name as the SimpleLogger-class
3979
- *
3980
- * Makes call like this possible:
3981
- * SimpleLogger()->info("This is a message sent to the log");
3982
- */
3983
- function SimpleLogger()
3984
- {
3985
- return new SimpleLogger(SimpleHistory::get_instance());
3986
- }
3987
 
3988
- /**
3989
- * Add event to history table
3990
- * This is here for backwards compatibility
3991
- * If you use this please consider using
3992
- * SimpleHistory()->info();
3993
- * instead
3994
- */
3995
- function simple_history_add($args)
3996
- {
3997
- $defaults = array(
3998
- 'action' => null,
3999
- 'object_type' => null,
4000
- 'object_subtype' => null,
4001
- 'object_id' => null,
4002
- 'object_name' => null,
4003
- 'user_id' => null,
4004
- 'description' => null
4005
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4006
 
4007
- $context = wp_parse_args($args, $defaults);
4008
 
4009
- $message = "{$context["object_type"]} {$context["object_name"]} {$context["action"]}";
 
4010
 
4011
- SimpleLogger()->info($message, $context);
4012
- } // simple_history_add
 
4013
 
4014
- /**
4015
- * Pretty much same as wp_text_diff() but with this you can set leading and trailing context lines
4016
- *
4017
- * @since 2.0.29
4018
- *
4019
- *
4020
- * Original description from wp_text_diff():
4021
- *
4022
- * Displays a human readable HTML representation of the difference between two strings.
4023
- *
4024
- * The Diff is available for getting the changes between versions. The output is
4025
- * HTML, so the primary use is for displaying the changes. If the two strings
4026
- * are equivalent, then an empty string will be returned.
4027
- *
4028
- * The arguments supported and can be changed are listed below.
4029
- *
4030
- * 'title' : Default is an empty string. Titles the diff in a manner compatible
4031
- * with the output.
4032
- * 'title_left' : Default is an empty string. Change the HTML to the left of the
4033
- * title.
4034
- * 'title_right' : Default is an empty string. Change the HTML to the right of
4035
- * the title.
4036
- *
4037
- * @see wp_parse_args() Used to change defaults to user defined settings.
4038
- * @uses Text_Diff
4039
- * @uses WP_Text_Diff_Renderer_Table
4040
- *
4041
- * @param string $left_string "old" (left) version of string
4042
- * @param string $right_string "new" (right) version of string
4043
- * @param string|array $args Optional. Change 'title', 'title_left', and 'title_right' defaults. And leading_context_lines and trailing_context_lines.
4044
- * @return string Empty string if strings are equivalent or HTML with differences.
4045
- */
4046
- function simple_history_text_diff($left_string, $right_string, $args = null)
4047
- {
4048
- $defaults = array(
4049
- 'title' => '',
4050
- 'title_left' => '',
4051
- 'title_right' => '',
4052
- 'leading_context_lines' => 1,
4053
- 'trailing_context_lines' => 1
4054
- );
4055
-
4056
- $args = wp_parse_args($args, $defaults);
4057
-
4058
- if (!class_exists('WP_Text_Diff_Renderer_Table')) {
4059
- require ABSPATH . WPINC . '/wp-diff.php';
4060
- }
4061
-
4062
- $left_string = normalize_whitespace($left_string);
4063
- $right_string = normalize_whitespace($right_string);
4064
-
4065
- $left_lines = explode("\n", $left_string);
4066
- $right_lines = explode("\n", $right_string);
4067
- $text_diff = new Text_Diff($left_lines, $right_lines);
4068
-
4069
- $renderer = new WP_Text_Diff_Renderer_Table($args);
4070
- $renderer->_leading_context_lines = $args['leading_context_lines'];
4071
- $renderer->_trailing_context_lines = $args['trailing_context_lines'];
4072
-
4073
- $diff = $renderer->render($text_diff);
4074
-
4075
- if (!$diff) {
4076
- return '';
4077
- }
4078
-
4079
- $r = '';
4080
-
4081
- $r .= "<div class='SimpleHistory__diff__contents' tabindex='0'>";
4082
- $r .= "<div class='SimpleHistory__diff__contentsInner'>";
4083
-
4084
- $r .= "<table class='diff SimpleHistory__diff'>\n";
4085
-
4086
- if (!empty($args['show_split_view'])) {
4087
- $r .=
4088
- "<col class='content diffsplit left' /><col class='content diffsplit middle' /><col class='content diffsplit right' />";
4089
- } else {
4090
- $r .= "<col class='content' />";
4091
- }
4092
-
4093
- if ($args['title'] || $args['title_left'] || $args['title_right']) {
4094
- $r .= '<thead>';
4095
- }
4096
- if ($args['title']) {
4097
- $r .= "<tr class='diff-title'><th colspan='4'>$args[title]</th></tr>\n";
4098
- }
4099
- if ($args['title_left'] || $args['title_right']) {
4100
- $r .= "<tr class='diff-sub-title'>\n";
4101
- $r .= "\t<td></td><th>$args[title_left]</th>\n";
4102
- $r .= "\t<td></td><th>$args[title_right]</th>\n";
4103
- $r .= "</tr>\n";
4104
- }
4105
- if ($args['title'] || $args['title_left'] || $args['title_right']) {
4106
- $r .= "</thead>\n";
4107
- }
4108
-
4109
- $r .= "<tbody>\n$diff</div>\n</tbody>\n";
4110
- $r .= '</table>';
4111
-
4112
- $r .= '</div>';
4113
- $r .= '</div>';
4114
-
4115
- return $r;
4116
- }
4117
 
4118
- /**
4119
- * Log variable(s) to error log.
4120
- * Any number of variables can be passed and each variable is print_r'ed to the error log.
4121
- *
4122
- * Example usage:
4123
- * sh_error_log(
4124
- * 'rest_request_after_callbacks:',
4125
- * $handler,
4126
- * $handler['callback'][0],
4127
- * $handler['callback'][1]
4128
- * );
4129
- */
4130
- function sh_error_log()
4131
- {
4132
- foreach (func_get_args() as $var) {
4133
- if (is_bool($var)) {
4134
- $bool_string = true === $var ? 'true' : 'false';
4135
- error_log("$bool_string (boolean value)");
4136
- } elseif (is_null($var)) {
4137
- error_log('null (null value)');
4138
- } else {
4139
- error_log(print_r($var, true));
4140
- }
4141
- }
4142
- }
4143
 
4144
- /**
4145
- * Return a name for a callable.
4146
- *
4147
- * Examples of return values:
4148
- * - WP_REST_Posts_Controller::get_items
4149
- * - WP_REST_Users_Controller::get_items"
4150
- * - WP_REST_Server::get_index
4151
- * - Redirection_Api_Redirect::route_bulk
4152
- * - wpcf7_rest_create_feedback
4153
- * - closure
4154
- *
4155
- * Function based on code found on stack overflow:
4156
- * https://stackoverflow.com/questions/34324576/print-name-or-definition-of-callable-in-php
4157
- *
4158
- * @param callable $callable The callable thing to check.
4159
- * @return string Name of callable.
4160
- */
4161
- function sh_get_callable_name($callable)
4162
- {
4163
- if (is_string($callable)) {
4164
- return trim($callable);
4165
- } elseif (is_array($callable)) {
4166
- if (is_object($callable[0])) {
4167
- return sprintf(
4168
- '%s::%s',
4169
- get_class($callable[0]),
4170
- trim($callable[1])
4171
- );
4172
- } else {
4173
- return sprintf('%s::%s', trim($callable[0]), trim($callable[1]));
4174
- }
4175
- } elseif ($callable instanceof Closure) {
4176
- return 'closure';
4177
- } else {
4178
- return 'unknown';
4179
- }
4180
- }
4181
 
4182
- /**
4183
- * PHP 5.3 compatible version of ucwords with second argument.
4184
- * Taken from http://php.net/manual/en/function.ucwords.php#105249.
4185
- *
4186
- * @param string $str String.
4187
- * @param string $separator String.
4188
- *
4189
- * @return string with words uppercased.
4190
- */
4191
- function sh_ucwords($str, $separator = ' ')
4192
- {
4193
- $str = str_replace($separator, ' ', $str);
4194
- $str = ucwords(strtolower($str));
4195
- $str = str_replace(' ', $separator, $str);
4196
- return $str;
4197
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  */
8
  class SimpleHistory
9
  {
10
+ const NAME = 'Simple History';
11
+
12
+ /**
13
+ * For singleton
14
+ */
15
+ private static $instance;
16
+
17
+ /**
18
+ * Array with external loggers to load
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
+ */
30
+ private $instantiatedLoggers;
31
+
32
+ /**
33
+ * Array with all instantiated dropins
34
+ */
35
+ private $instantiatedDropins;
36
+
37
+ /**
38
+ * Bool if gettext filter function should be active
39
+ * Should only be active during the load of a logger
40
+ */
41
+ private $doFilterGettext = false;
42
+
43
+ /**
44
+ * Used by gettext filter to temporarily store current logger
45
+ */
46
+ private $doFilterGettext_currentLogger = null;
47
+
48
+ /**
49
+ * Used to store latest translations used by __()
50
+ * Required to automagically determine orginal text and text domain
51
+ * for calls like this `SimpleLogger()->log( __("My translated message") );`
52
+ */
53
+ public $gettextLatestTranslations = array();
54
+
55
+ /**
56
+ * All registered settings tabs
57
+ */
58
+ private $arr_settings_tabs = array();
59
+
60
+ const DBTABLE = 'simple_history';
61
+ const DBTABLE_CONTEXTS = 'simple_history_contexts';
62
+
63
+ /** Slug for the settings menu */
64
+ const SETTINGS_MENU_SLUG = 'simple_history_settings_menu_slug';
65
+
66
+ /** Slug for the settings menu */
67
+ const SETTINGS_GENERAL_OPTION_GROUP = 'simple_history_settings_group';
68
+
69
+ /** ID for the general settings section */
70
+ const SETTINGS_SECTION_GENERAL_ID = 'simple_history_settings_section_general';
71
+
72
+ public function __construct()
73
+ {
74
+ $this->init();
75
+ }
76
+
77
+ /**
78
+ * @since 2.5.2
79
+ */
80
+ public function init()
81
+ {
82
+ /**
83
+ * Fires before Simple History does it's init stuff
84
+ *
85
+ * @since 2.0
86
+ *
87
+ * @param SimpleHistory $SimpleHistory This class.
88
+ */
89
+ do_action('simple_history/before_init', $this);
90
+
91
+ $this->setup_variables();
92
+
93
+ // Actions and filters, ordered by order specified in codex: http://codex.wordpress.org/Plugin_API/Action_Reference
94
+ add_action('after_setup_theme', array( $this, 'load_plugin_textdomain' ));
95
+ add_action('after_setup_theme', array( $this, 'add_default_settings_tabs' ));
96
+
97
+ // Plugins and dropins are loaded using the "after_setup_theme" filter so
98
+ // themes can use filters to modify the loading of them.
99
+ // The drawback with this is that for example logouts done when plugins like
100
+ // iThemes Security is installed is not logged, because those plugins fire wp_logout()
101
+ // using filter "plugins_loaded", i.e. before simple history has loaded its filters.
102
+ add_action('after_setup_theme', array( $this, 'load_loggers' ));
103
+ add_action('after_setup_theme', array( $this, 'load_dropins' ));
104
+
105
+ // Run before loading of loggers and before menu items are added.
106
+ add_action('after_setup_theme', array( $this, 'check_for_upgrade' ), 5);
107
+
108
+ add_action('after_setup_theme', array( $this, 'setup_cron' ));
109
+
110
+ // Filters and actions not called during regular boot.
111
+ add_filter('gettext', array( $this, 'filter_gettext' ), 20, 3);
112
+ add_filter('gettext_with_context', array( $this, 'filter_gettext_with_context' ), 20, 4);
113
+
114
+ add_filter('gettext', array( $this, 'filter_gettext_storeLatestTranslations' ), 10, 3);
115
+
116
+ add_action('admin_bar_menu', array( $this, 'add_admin_bar_network_menu_item' ), 40);
117
+ add_action('admin_bar_menu', array( $this, 'add_admin_bar_menu_item' ), 40);
118
+
119
+ /**
120
+ * Filter that is used to log things, without the need to check that simple history is available
121
+ * i.e. you can have simple history acivated and log things and then you can disable the plugin
122
+ * and no errors will occur
123
+ *
124
+ * Usage:
125
+ * apply_filters("simple_history_log", "This is the log message");
126
+ * apply_filters("simple_history_log", "This is the log message with some extra data/info", ["extraThing1" => $variableWIihThing]);
127
+ * apply_filters("simple_history_log", "This is the log message with severity debug", null, "debug");
128
+ * apply_filters("simple_history_log", "This is the log message with severity debug and with some extra info/data logged", ["userData" => $userData, "shoppingCartDebugData" => $shopDebugData], "debug",);
129
+ *
130
+ * @since 2.13
131
+ */
132
+ add_filter('simple_history_log', array( $this, 'on_filter_simple_history_log' ), 10, 3);
133
+
134
+ /**
135
+ * Filter to log with specific log level, for example:
136
+ * apply_filters('simple_history_log_debug', 'My debug message');
137
+ * apply_filters('simple_history_log_warning', 'My warning message');
138
+ *
139
+ * @since 2.17
140
+ */
141
+ add_filter('simple_history_log_emergency', array( $this, 'on_filter_simple_history_log_emergency' ), 10, 3);
142
+ add_filter('simple_history_log_alert', array( $this, 'on_filter_simple_history_log_alert' ), 10, 2);
143
+ add_filter('simple_history_log_critical', array( $this, 'on_filter_simple_history_log_critical' ), 10, 2);
144
+ add_filter('simple_history_log_error', array( $this, 'on_filter_simple_history_log_error' ), 10, 2);
145
+ add_filter('simple_history_log_warning', array( $this, 'on_filter_simple_history_log_warning' ), 10, 2);
146
+ add_filter('simple_history_log_notice', array( $this, 'on_filter_simple_history_log_notice' ), 10, 2);
147
+ add_filter('simple_history_log_info', array( $this, 'on_filter_simple_history_log_info' ), 10, 2);
148
+ add_filter('simple_history_log_debug', array( $this, 'on_filter_simple_history_log_debug' ), 10, 2);
149
+
150
+ if (is_admin()) {
151
+ $this->add_admin_actions();
152
+ }
153
+
154
+ /**
155
+ * Fires after Simple History has done it's init stuff
156
+ *
157
+ * @since 2.0
158
+ *
159
+ * @param SimpleHistory $SimpleHistory This class.
160
+ */
161
+ do_action('simple_history/after_init', $this);
162
+ }
163
+
164
+ /**
165
+ * Log a message
166
+ *
167
+ * Function called when running filter "simple_history_log"
168
+ *
169
+ * @since 2.13
170
+ * @param string $message The message to log.
171
+ * @param array $context Optional context to add to the logged data.
172
+ * @param string $level The loglevel. Must be one of the existing ones. Defaults to "info".
173
+ */
174
+ public function on_filter_simple_history_log($message = null, $context = null, $level = 'info')
175
+ {
176
+ SimpleLogger()->log($level, $message, $context);
177
+ }
178
+
179
+ /**
180
+ * Log a message, triggered by filter 'on_filter_simple_history_log_emergency'.
181
+ *
182
+ * @param string $message The message to log.
183
+ * @param array $context The context (optional).
184
+ */
185
+ public function on_filter_simple_history_log_emergency($message = null, $context = null)
186
+ {
187
+ SimpleLogger()->log('emergency', $message, $context);
188
+ }
189
+
190
+ /**
191
+ * Log a message, triggered by filter 'on_filter_simple_history_log_alert'.
192
+ *
193
+ * @param string $message The message to log.
194
+ * @param array $context The context (optional).
195
+ */
196
+ public function on_filter_simple_history_log_alert($message = null, $context = null)
197
+ {
198
+ SimpleLogger()->log('alert', $message, $context);
199
+ }
200
+
201
+ /**
202
+ * Log a message, triggered by filter 'on_filter_simple_history_log_critical'.
203
+ *
204
+ * @param string $message The message to log.
205
+ * @param array $context The context (optional).
206
+ */
207
+ public function on_filter_simple_history_log_critical($message = null, $context = null)
208
+ {
209
+ SimpleLogger()->log('critical', $message, $context);
210
+ }
211
+
212
+ /**
213
+ * Log a message, triggered by filter 'on_filter_simple_history_log_error'.
214
+ *
215
+ * @param string $message The message to log.
216
+ * @param array $context The context (optional).
217
+ */
218
+ public function on_filter_simple_history_log_error($message = null, $context = null)
219
+ {
220
+ SimpleLogger()->log('error', $message, $context);
221
+ }
222
+
223
+ /**
224
+ * Log a message, triggered by filter 'on_filter_simple_history_log_warning'.
225
+ *
226
+ * @param string $message The message to log.
227
+ * @param array $context The context (optional).
228
+ */
229
+ public function on_filter_simple_history_log_warning($message = null, $context = null)
230
+ {
231
+ SimpleLogger()->log('warning', $message, $context);
232
+ }
233
+
234
+ /**
235
+ * Log a message, triggered by filter 'on_filter_simple_history_log_notice'.
236
+ *
237
+ * @param string $message The message to log.
238
+ * @param array $context The context (optional).
239
+ */
240
+ public function on_filter_simple_history_log_notice($message = null, $context = null)
241
+ {
242
+ SimpleLogger()->log('notice', $message, $context);
243
+ }
244
+
245
+ /**
246
+ * Log a message, triggered by filter 'on_filter_simple_history_log_info'.
247
+ *
248
+ * @param string $message The message to log.
249
+ * @param array $context The context (optional).
250
+ */
251
+ public function on_filter_simple_history_log_info($message = null, $context = null)
252
+ {
253
+ SimpleLogger()->log('info', $message, $context);
254
+ }
255
+
256
+ /**
257
+ * Log a message, triggered by filter 'on_filter_simple_history_log_debug'.
258
+ *
259
+ * @param string $message The message to log.
260
+ * @param array $context The context (optional).
261
+ */
262
+ public function on_filter_simple_history_log_debug($message = null, $context = null)
263
+ {
264
+ SimpleLogger()->log('debug', $message, $context);
265
+ }
266
+
267
+ /**
268
+ * @since 2.5.2
269
+ */
270
+ private function add_admin_actions()
271
+ {
272
+ add_action('admin_menu', array( $this, 'add_admin_pages' ));
273
+ add_action('admin_menu', array( $this, 'add_settings' ));
274
+
275
+ add_action('admin_footer', array( $this, 'add_js_templates' ));
276
+
277
+ add_action('wp_dashboard_setup', array( $this, 'add_dashboard_widget' ));
278
+
279
+ add_action('admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ));
280
+
281
+ add_action('admin_head', array( $this, 'onAdminHead' ));
282
+ add_action('admin_footer', array( $this, 'onAdminFooter' ));
283
+
284
+ add_action('simple_history/history_page/before_gui', array( $this, 'output_quick_stats' ));
285
+ add_action('simple_history/dashboard/before_gui', array( $this, 'output_quick_stats' ));
286
+
287
+ add_action('wp_ajax_simple_history_api', array( $this, 'api' ));
288
+
289
+ add_filter('plugin_action_links_simple-history/index.php', array( $this, 'plugin_action_links' ), 10, 4);
290
+ }
291
+
292
+ /**
293
+ * Adds a "View history" item/shortcut to the network admin, on blogs where Simple History is installed
294
+ *
295
+ * Useful because Simple History is something at least the author of this plugin often use on a site :)
296
+ *
297
+ * @since 2.7.1
298
+ */
299
+ public function add_admin_bar_network_menu_item($wp_admin_bar)
300
+ {
301
+ /**
302
+ * Filter to control if admin bar shortcut should be added
303
+ *
304
+ * @since 2.7.1
305
+ *
306
+ * @param bool Add item
307
+ */
308
+ $add_items = apply_filters('simple_history/add_admin_bar_network_menu_item', true);
309
+
310
+ if (! $add_items) {
311
+ return;
312
+ }
313
+
314
+ // Don't show for logged out users or single site mode.
315
+ if (! is_user_logged_in() || ! is_multisite()) {
316
+ return;
317
+ }
318
+
319
+ // Show only when the user has at least one site, or they're a super admin.
320
+ if (count($wp_admin_bar->user->blogs) < 1 && ! is_super_admin()) {
321
+ return;
322
+ }
323
+
324
+ // Setting to show as page must be true
325
+ if (! $this->setting_show_as_page()) {
326
+ return;
327
+ }
328
+
329
+ // User must have capability to view the history page
330
+ if (! current_user_can($this->get_view_history_capability())) {
331
+ return;
332
+ }
333
+
334
+ /*
335
+ menu_page_url() is defined in the WordPress Plugin Administration API, which is not loaded here by default */
336
+ /* dito for is_plugin_active() */
337
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
338
+
339
+ foreach ((array) $wp_admin_bar->user->blogs as $blog) {
340
+ switch_to_blog($blog->userblog_id);
341
+
342
+ if (is_plugin_active(SIMPLE_HISTORY_BASENAME)) {
343
+ $menu_id = 'simple-history-blog-' . $blog->userblog_id;
344
+ $parent_menu_id = 'blog-' . $blog->userblog_id;
345
+ $url = admin_url(apply_filters('simple_history/admin_location', 'index') . '.php?page=simple_history_page');
346
+
347
+ // Each network site is added by WP core with id "blog-1", "blog-2" ... "blog-n"
348
+ // https://codex.wordpress.org/Function_Reference/add_node
349
+ $args = array(
350
+ 'id' => $menu_id,
351
+ 'parent' => $parent_menu_id,
352
+ 'title' => _x('View History', 'Admin bar network name', 'simple-history'),
353
+ 'href' => $url,
354
+ 'meta' => array(
355
+ 'class' => 'ab-item--simplehistory'
356
+ )
357
+ );
358
+
359
+ $wp_admin_bar->add_node($args);
360
+ } // End if().
361
+
362
+ restore_current_blog();
363
+ } // End foreach().
364
+ } // func
365
+
366
+ /**
367
+ * Adds a "View history" item/shortcut to the admin bar
368
+ *
369
+ * Useful because Simple History is something at least the author of this plugin often use on a site :)
370
+ *
371
+ * @since 2.7.1
372
+ */
373
+ public function add_admin_bar_menu_item($wp_admin_bar)
374
+ {
375
+ /**
376
+ * Filter to control if admin bar shortcut should be added
377
+ *
378
+ * @since 2.7.1
379
+ *
380
+ * @param bool Add item
381
+ */
382
+ $add_item = apply_filters('simple_history/add_admin_bar_menu_item', true);
383
+
384
+ if (! $add_item) {
385
+ return;
386
+ }
387
+
388
+ // Don't show for logged out users
389
+ if (! is_user_logged_in()) {
390
+ return;
391
+ }
392
+
393
+ // Setting to show as page must be true
394
+ if (! $this->setting_show_as_page()) {
395
+ return;
396
+ }
397
+
398
+ // User must have capability to view the history page
399
+ if (! current_user_can($this->get_view_history_capability())) {
400
+ return;
401
+ }
402
+
403
+ /* menu_page_url() and is_plugin_active()is defined in the WordPress Plugin Administration API, which is not loaded here by default */
404
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
405
+
406
+ $menu_id = 'simple-history-view-history';
407
+ $parent_menu_id = 'site-name';
408
+ $url = admin_url(apply_filters('simple_history/admin_location', 'index') . '.php?page=simple_history_page');
409
+
410
+ $args = array(
411
+ 'id' => $menu_id,
412
+ 'parent' => $parent_menu_id,
413
+ 'title' => _x('Simple History', 'Admin bar name', 'simple-history'),
414
+ 'href' => $url,
415
+ 'meta' => array(
416
+ 'class' => 'ab-item--simplehistory'
417
+ )
418
+ );
419
+
420
+ $wp_admin_bar->add_node($args);
421
+ } // func
422
+
423
+ /**
424
+ * Get singleton intance
425
+ *
426
+ * @return SimpleHistory instance
427
+ */
428
+ public static function get_instance()
429
+ {
430
+ if (! isset(self::$instance)) {
431
+ self::$instance = new SimpleHistory();
432
+ }
433
+
434
+ return self::$instance;
435
+ }
436
+
437
+ public function filter_gettext_storeLatestTranslations($translation, $text, $domain)
438
+ {
439
+ // Check that translation is a string or integer, i.ex. the valid values for an array key
440
+ if (! is_string($translation) || ! is_integer($translation)) {
441
+ return $translation;
442
+ }
443
+
444
+ $array_max_size = 5;
445
+
446
+ // Keep a listing of the n latest translation
447
+ // when SimpleLogger->log() is called from anywhere we can then search for the
448
+ // translated string among our n latest things and find it there, if it's translated
449
+ // global $sh_latest_translations;
450
+ $sh_latest_translations = $this->gettextLatestTranslations;
451
+
452
+ $sh_latest_translations[ $translation ] = array(
453
+ 'translation' => $translation,
454
+ 'text' => $text,
455
+ 'domain' => $domain
456
+ );
457
+
458
+ $arr_length = sizeof($sh_latest_translations);
459
+ if ($arr_length > $array_max_size) {
460
+ $sh_latest_translations = array_slice($sh_latest_translations, $arr_length - $array_max_size);
461
+ }
462
+
463
+ $this->gettextLatestTranslations = $sh_latest_translations;
464
+
465
+ return $translation;
466
+ }
467
+
468
+ public function setup_cron()
469
+ {
470
+ add_filter('simple_history/maybe_purge_db', array( $this, 'maybe_purge_db' ));
471
+
472
+ if (! wp_next_scheduled('simple_history/maybe_purge_db')) {
473
+ wp_schedule_event(time(), 'daily', 'simple_history/maybe_purge_db');
474
+ } else {
475
+ }
476
+
477
+ // Remove old schedule (only author dev sites should have it)
478
+ $old_next_scheduled = wp_next_scheduled('simple_history/purge_db');
479
+ if ($old_next_scheduled) {
480
+ wp_unschedule_event($old_next_scheduled, 'simple_history/purge_db');
481
+ }
482
+ }
483
+
484
+ public function onAdminHead()
485
+ {
486
+ if ($this->is_on_our_own_pages()) {
487
+ do_action('simple_history/admin_head', $this);
488
+ }
489
+ }
490
+
491
+ public function onAdminFooter()
492
+ {
493
+ if ($this->is_on_our_own_pages()) {
494
+ do_action('simple_history/admin_footer', $this);
495
+ }
496
+ }
497
+
498
+ /**
499
+ * Output JS templated into footer
500
+ */
501
+ public function add_js_templates($hook)
502
+ {
503
+ if ($this->is_on_our_own_pages()) { ?>
504
+ <script type="text/html" id="tmpl-simple-history-base">
505
+
506
+ <div class="SimpleHistory__waitingForFirstLoad">
507
+ <img src="<?php echo admin_url('/images/spinner.gif'); ?>" alt="" width="20" height="20">
508
+ <?php echo _x(
509
+ 'Loading history...',
510
+ 'Message visible while waiting for log to load from server the first time',
511
+ 'simple-history'
512
+ ); ?>
513
+ </div>
514
+
515
+ <div class="SimpleHistoryLogitemsWrap">
516
+ <div class="SimpleHistoryLogitems__beforeTopPagination"></div>
517
+ <div class="SimpleHistoryLogitems__above"></div>
518
+ <ul class="SimpleHistoryLogitems"></ul>
519
+ <div class="SimpleHistoryLogitems__below"></div>
520
+ <div class="SimpleHistoryLogitems__pagination"></div>
521
+ <div class="SimpleHistoryLogitems__afterBottomPagination"></div>
522
+ </div>
523
+
524
+ <div class="SimpleHistoryLogitems__debug"></div>
525
+
526
+ </script>
527
+
528
+ <script type="text/html" id="tmpl-simple-history-logitems-pagination">
529
+
530
+ <!-- this uses the (almost) the same html as WP does -->
531
+ <div class="SimpleHistoryPaginationPages">
532
+ <!--
533
+ {{ data.page_rows_from }}–{{ data.page_rows_to }}
534
+ <span class="SimpleHistoryPaginationDisplayNum"> of {{ data.total_row_count }} </span>
535
+ -->
536
+ <span class="SimpleHistoryPaginationLinks">
537
+ <a
538
+ data-direction="first"
539
+ class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--firstPage <# if ( data.api_args.paged <= 1 ) { #> disabled <# } #>"
540
+ title="{{ data.strings.goToTheFirstPage }}"
541
+ href="#">«</a>
542
+ <a
543
+ data-direction="prev"
544
+ class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--prevPage <# if ( data.api_args.paged <= 1 ) { #> disabled <# } #>"
545
+ title="{{ data.strings.goToThePrevPage }}"
546
+ href="#">‹</a>
547
+ <span class="SimpleHistoryPaginationInput">
548
+ <input class="SimpleHistoryPaginationCurrentPage" title="{{ data.strings.currentPage }}" type="text" name="paged" value="{{ data.api_args.paged }}" size="4">
549
+ <?php _x('of', 'page n of n', 'simple-history'); ?>
550
+ <span class="total-pages">{{ data.pages_count }}</span>
551
+ </span>
552
+ <a
553
+ data-direction="next"
554
+ class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--nextPage <# if ( data.api_args.paged >= data.pages_count ) { #> disabled <# } #>"
555
+ title="{{ data.strings.goToTheNextPage }}"
556
+ href="#">›</a>
557
+ <a
558
+ data-direction="last"
559
+ class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--lastPage <# if ( data.api_args.paged >= data.pages_count ) { #> disabled <# } #>"
560
+ title="{{ data.strings.goToTheLastPage }}"
561
+ href="#">»</a>
562
+ </span>
563
+ </div>
564
+
565
+ </script>
566
+
567
+ <script type="text/html" id="tmpl-simple-history-logitems-modal">
568
+
569
+ <div class="SimpleHistory-modal">
570
+ <div class="SimpleHistory-modal__background"></div>
571
+ <div class="SimpleHistory-modal__content">
572
+ <div class="SimpleHistory-modal__contentInner">
573
+ <img class="SimpleHistory-modal__contentSpinner" src="<?php echo esc_url(admin_url('/images/spinner.gif')); ?>" alt="">
574
+ </div>
575
+ <div class="SimpleHistory-modal__contentClose">
576
+ <button class="button">✕</button>
577
+ </div>
578
+ </div>
579
+ </div>
580
+
581
+ </script>
582
+
583
+ <script type="text/html" id="tmpl-simple-history-occasions-too-many">
584
+ <li
585
+ class="SimpleHistoryLogitem
586
+ SimpleHistoryLogitem--occasion
587
+ SimpleHistoryLogitem--occasion-tooMany
588
+ ">
589
+ <div class="SimpleHistoryLogitem__firstcol"></div>
590
+ <div class="SimpleHistoryLogitem__secondcol">
591
+ <div class="SimpleHistoryLogitem__text">
592
+ <?php _e('Sorry, but there are too many similar events to show.', 'simple-history'); ?>
593
+ <!-- <br>occasionsCount: {{ data.occasionsCount }}
594
+ <br>occasionsCountMaxReturn: {{ data.occasionsCountMaxReturn }}
595
+ <br>diff: {{ data.occasionsCount - data.occasionsCountMaxReturn }}
596
+ Suggestions:
597
+ <ul>
598
+ <li>- dig into database directly
599
+ <li>- Export
600
+ </ul>
601
+ -->
602
+ </div>
603
+ </div>
604
+ </li>
605
+ </script>
606
+
607
+ <?php // Call plugins so they can add their js
608
+ foreach ($this->instantiatedLoggers as $one_logger) {
609
+ if (method_exists($one_logger['instance'], 'adminJS')) {
610
+ $one_logger['instance']->adminJS();
611
+ }
612
+ }
613
+ }// End if().
614
+ }
615
+
616
+ /**
617
+ * Base url is:
618
+ * /wp-admin/admin-ajax.php?action=simple_history_api
619
+ *
620
+ * Examples:
621
+ * http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&posts_per_page=5&paged=1&format=html
622
+ */
623
+ public function api()
624
+ {
625
+ global $wpdb;
626
+
627
+ // Fake slow answers
628
+ // sleep(2);
629
+ // sleep(rand(0,3));
630
+ $args = $_GET;
631
+ unset($args['action']);
632
+
633
+ // Type = overview | ...
634
+ $type = isset($_GET['type']) ? $_GET['type'] : null;
635
+
636
+ if (empty($args) || ! $type) {
637
+ wp_send_json_error(array(
638
+ _x('Not enough args specified', 'API: not enought arguments passed', 'simple-history')
639
+ ));
640
+ }
641
+
642
+ // User must have capability to view the history page
643
+ if (! current_user_can($this->get_view_history_capability())) {
644
+ wp_send_json_error(array(
645
+ 'error' => 'CAPABILITY_ERROR'
646
+ ));
647
+ }
648
+
649
+ if (isset($args['id'])) {
650
+ $args['post__in'] = array($args['id']);
651
+ }
652
+
653
+ $data = array();
654
+
655
+ switch ($type) {
656
+ case 'overview':
657
+ case 'occasions':
658
+ case 'single':
659
+ // API use SimpleHistoryLogQuery, so simply pass args on to that
660
+ $logQuery = new SimpleHistoryLogQuery();
661
+ $data = $logQuery->query($args);
662
+
663
+ $data['api_args'] = $args;
664
+
665
+ // Output can be array or HMTL
666
+ if (isset($args['format']) && 'html' === $args['format']) {
667
+ $data['log_rows_raw'] = array();
668
+
669
+ foreach ($data['log_rows'] as $key => $oneLogRow) {
670
+ $args = array();
671
+ if ($type == 'single') {
672
+ $args['type'] = 'single';
673
+ }
674
+
675
+ $data['log_rows'][ $key ] = $this->getLogRowHTMLOutput($oneLogRow, $args);
676
+ $data['num_queries'] = get_num_queries();
677
+ }
678
+ } else {
679
+ // $data["logRows"] = $logRows;
680
+ }
681
+
682
+ break;
683
+
684
+ default:
685
+ $data[] = 'Nah.';
686
+ }// End switch().
687
+
688
+ wp_send_json_success($data);
689
+ }
690
+
691
+ /**
692
+ * During the load of info for a logger we want to get a reference
693
+ * to the untranslated text too, because that's the version we want to store
694
+ * in the database.
695
+ */
696
+ public function filter_gettext($translated_text, $untranslated_text, $domain)
697
+ {
698
+ if (isset($this->doFilterGettext) && $this->doFilterGettext) {
699
+ $this->doFilterGettext_currentLogger->messages[] = array(
700
+ 'untranslated_text' => $untranslated_text,
701
+ 'translated_text' => $translated_text,
702
+ 'domain' => $domain,
703
+ 'context' => null
704
+ );
705
+ }
706
+
707
+ return $translated_text;
708
+ }
709
+
710
+ /**
711
+ * Store messages with context
712
+ */
713
+ public function filter_gettext_with_context($translated_text, $untranslated_text, $context, $domain)
714
+ {
715
+ if (isset($this->doFilterGettext) && $this->doFilterGettext) {
716
+ $this->doFilterGettext_currentLogger->messages[] = array(
717
+ 'untranslated_text' => $untranslated_text,
718
+ 'translated_text' => $translated_text,
719
+ 'domain' => $domain,
720
+ 'context' => $context
721
+ );
722
+ }
723
+
724
+ return $translated_text;
725
+ }
726
+
727
+ /**
728
+ * Load language files.
729
+ * Uses the method described here:
730
+ * http://geertdedeckere.be/article/loading-wordpress-language-files-the-right-way
731
+ *
732
+ * @since 2.0
733
+ */
734
+ public function load_plugin_textdomain()
735
+ {
736
+ $domain = 'simple-history';
737
+
738
+ // The "plugin_locale" filter is also used in load_plugin_textdomain()
739
+ $locale = apply_filters('plugin_locale', get_locale(), $domain);
740
+ load_textdomain($domain, WP_LANG_DIR . '/simple-history/' . $domain . '-' . $locale . '.mo');
741
+ load_plugin_textdomain($domain, false, dirname($this->plugin_basename) . '/languages/');
742
+ }
743
+
744
+ /**
745
+ * Setup variables and things
746
+ */
747
+ public function setup_variables()
748
+ {
749
+ $this->externalLoggers = array();
750
+ $this->externalDropins = array();
751
+ $this->instantiatedLoggers = array();
752
+ $this->instantiatedDropins = array();
753
+
754
+ $this->plugin_basename = SIMPLE_HISTORY_BASENAME;
755
+ }
756
+
757
+ /**
758
+ * Return capability required to view history = for who will the History page be added
759
+ *
760
+ * @since 2.1.5
761
+ * @return string capability
762
+ */
763
+ public function get_view_history_capability()
764
+ {
765
+ $view_history_capability = 'edit_pages';
766
+ $view_history_capability = apply_filters('simple_history_view_history_capability', $view_history_capability);
767
+ $view_history_capability = apply_filters('simple_history/view_history_capability', $view_history_capability);
768
+
769
+ return $view_history_capability;
770
+ }
771
+
772
+ /**
773
+ * Return capability required to view settings
774
+ *
775
+ * @since 2.1.5
776
+ * @return string capability
777
+ */
778
+ public function get_view_settings_capability()
779
+ {
780
+ $view_settings_capability = 'manage_options';
781
+ $view_settings_capability = apply_filters('simple_history_view_settings_capability', $view_settings_capability);
782
+ $view_settings_capability = apply_filters('simple_history/view_settings_capability', $view_settings_capability);
783
+
784
+ return $view_settings_capability;
785
+ }
786
+
787
+ /**
788
+ * Check if the current user can clear the log
789
+ *
790
+ * @since 2.19
791
+ * @return bool
792
+ */
793
+ public function user_can_clear_log()
794
+ {
795
+ $user_can_clear_log = apply_filters('simple_history/user_can_clear_log', true);
796
+
797
+ return $user_can_clear_log;
798
+ }
799
+
800
+ /**
801
+ * Adds default tabs to settings
802
+ */
803
+ public function add_default_settings_tabs()
804
+ {
805
+ // Add default settings tabs
806
+ $this->arr_settings_tabs = array(
807
+ array(
808
+ 'slug' => 'settings',
809
+ 'name' => __('Settings', 'simple-history'),
810
+ 'function' => array($this, 'settings_output_general')
811
+ )
812
+ );
813
+
814
+ if (defined('SIMPLE_HISTORY_DEV') && SIMPLE_HISTORY_DEV) {
815
+ $arr_dev_tabs = array(
816
+ array(
817
+ 'slug' => 'log',
818
+ 'name' => __('Log (debug)', 'simple-history'),
819
+ 'function' => array($this, 'settings_output_log')
820
+ ),
821
+ array(
822
+ 'slug' => 'styles-example',
823
+ 'name' => __('Styles example (debug)', 'simple-history'),
824
+ 'function' => array($this, 'settings_output_styles_example')
825
+ )
826
+ );
827
+
828
+ $this->arr_settings_tabs = array_merge($this->arr_settings_tabs, $arr_dev_tabs);
829
+ }
830
+ }
831
+
832
+ /**
833
+ * Register an external logger so Simple History knows about it.
834
+ * Does not load the logger, so file with logger class must be loaded already.
835
+ *
836
+ * See example-logger.php for an example on how to use this.
837
+ *
838
+ * @since 2.1
839
+ */
840
+ public function register_logger($loggerClassName)
841
+ {
842
+ $this->externalLoggers[] = $loggerClassName;
843
+ }
844
+
845
+ /**
846
+ * Register an external dropin so Simple History knows about it.
847
+ * Does not load the dropin, so file with dropin class must be loaded already.
848
+ *
849
+ * See example-dropin.php for an example on how to use this.
850
+ *
851
+ * @since 2.1
852
+ */
853
+ public function register_dropin($dropinClassName)
854
+ {
855
+ $this->externalDropins[] = $dropinClassName;
856
+ }
857
+
858
+ /**
859
+ * Load built in loggers from all files in /loggers
860
+ * and instantiates them
861
+ */
862
+ public function load_loggers()
863
+ {
864
+ $loggersDir = SIMPLE_HISTORY_PATH . 'loggers/';
865
+
866
+ $loggersFiles = array(
867
+ // Main loggers.
868
+ $loggersDir . 'SimpleCommentsLogger.php',
869
+ $loggersDir . 'SimpleCoreUpdatesLogger.php',
870
+ $loggersDir . 'SimpleExportLogger.php',
871
+ $loggersDir . 'SimpleLegacyLogger.php',
872
+ $loggersDir . 'SimpleLogger.php',
873
+ $loggersDir . 'SimpleMediaLogger.php',
874
+ $loggersDir . 'SimpleMenuLogger.php',
875
+ $loggersDir . 'SimpleOptionsLogger.php',
876
+ $loggersDir . 'SimplePluginLogger.php',
877
+ $loggersDir . 'SimplePostLogger.php',
878
+ $loggersDir . 'SimpleThemeLogger.php',
879
+ $loggersDir . 'SimpleUserLogger.php',
880
+ $loggersDir . 'SimpleCategoriesLogger.php',
881
+ $loggersDir . 'AvailableUpdatesLogger.php',
882
+ $loggersDir . 'FileEditsLogger.php',
883
+ $loggersDir . 'class-sh-privacy-logger.php',
884
+ $loggersDir . 'class-sh-translations-logger.php',
885
+ $loggersDir . 'class-sh-jetpack-logger.php',
886
+
887
+ // Loggers for third party plugins.
888
+ $loggersDir . 'PluginUserSwitchingLogger.php',
889
+ $loggersDir . 'PluginEnableMediaReplaceLogger.php',
890
+ $loggersDir . 'Plugin_UltimateMembers_Logger.php',
891
+ $loggersDir . 'Plugin_LimitLoginAttempts.php',
892
+ $loggersDir . 'Plugin_Redirection.php',
893
+ $loggersDir . 'Plugin_DuplicatePost.php',
894
+ $loggersDir . 'Plugin_ACF.php',
895
+ $loggersDir . 'Plugin_BeaverBuilder.php'
896
+ );
897
+
898
+ // SimpleLogger.php must be loaded first and always since the other loggers extend it.
899
+ // Include it manually so no risk of anyone using filters or similar disables it.
900
+ include_once $loggersDir . 'SimpleLogger.php';
901
+
902
+ /**
903
+ * Filter the array with absolute paths to logger files to be loaded.
904
+ *
905
+ * Each file will be loaded and will be assumed to be a logger with a classname
906
+ * the same as the filename.
907
+ *
908
+ * @since 2.0
909
+ *
910
+ * @param array $loggersFiles Array with filenames
911
+ */
912
+ $loggersFiles = apply_filters('simple_history/loggers_files', $loggersFiles);
913
+
914
+ // Array with slug of loggers to instantiate.
915
+ // Slug of logger must also be the name of the logger class.
916
+ $arr_loggers_to_instantiate = array();
917
+
918
+ // $one_logger_file = "SimpleCommentsLogger.php", "class-privacy-logger.php", and so on.
919
+ foreach ($loggersFiles as $one_logger_file) {
920
+ $load_logger = true;
921
+
922
+ // SimpleCommentsLogger.php -> SimpleCommentsLogger.
923
+ // class-privacy-logger.php -> class-privacy-logger.
924
+ $basename_no_suffix = basename($one_logger_file, '.php');
925
+
926
+ /**
927
+ * Filter to completely skip loading of a logger
928
+ *
929
+ * @since 2.0.22
930
+ *
931
+ * @param bool if to load the logger. return false to not load it.
932
+ * @param string basename of logger, i.e. "SimpleCommentsLogger" or "class-privacy-logger"
933
+ */
934
+ $load_logger = apply_filters('simple_history/logger/load_logger', $load_logger, $basename_no_suffix);
935
+
936
+ // If logger was SimpleLogger then force it to be loaded because for example
937
+ // custom extended plugins added later probably depends on it.
938
+ if ('SimpleLogger' === $basename_no_suffix) {
939
+ $load_logger = true;
940
+ }
941
+
942
+ if (! $load_logger) {
943
+ continue;
944
+ }
945
+
946
+ include_once $one_logger_file;
947
+
948
+ $arr_loggers_to_instantiate[] = $basename_no_suffix;
949
+ }
950
+
951
+ /**
952
+ * Action that plugins can use to add their custom loggers.
953
+ * See register_logger() for more info.
954
+ *
955
+ * @since 2.1
956
+ *
957
+ * @param SimpleHistory instance
958
+ */
959
+ do_action('simple_history/add_custom_logger', $this);
960
+
961
+ $arr_loggers_to_instantiate = array_merge($arr_loggers_to_instantiate, $this->externalLoggers);
962
+
963
+ /**
964
+ * Filter the array with names of loggers to instantiate.
965
+ *
966
+ * Array
967
+ * (
968
+ * [0] => SimpleCommentsLogger
969
+ * [1] => SimpleCoreUpdatesLogger
970
+ * ...
971
+ * )
972
+ *
973
+ * @since 2.0
974
+ *
975
+ * @param array $arr_loggers_to_instantiate Array with class names
976
+ */
977
+ $arr_loggers_to_instantiate = apply_filters(
978
+ 'simple_history/loggers_to_instantiate',
979
+ $arr_loggers_to_instantiate
980
+ );
981
+
982
+ // Instantiate each logger.
983
+ foreach ($arr_loggers_to_instantiate as $one_logger_name) {
984
+ // Detect logger class name.
985
+ $logger_class_name = null;
986
+
987
+ if (class_exists($one_logger_name)) {
988
+ // Logger name is "SimpleCommentsLogger".
989
+ $logger_class_name = $one_logger_name;
990
+ } else {
991
+ // Check if class is "class-privacy-logger".
992
+ $logger_snaked_name = substr($one_logger_name, 6);
993
+ // "privacy-logger" -> "privacy_logger" -> Privacy_Logger
994
+ $logger_snaked_name = str_replace('-', '_', $logger_snaked_name);
995
+ $logger_snaked_name = sh_ucwords($logger_snaked_name, '_');
996
+
997
+ if (class_exists($logger_snaked_name)) {
998
+ $logger_class_name = $logger_snaked_name;
999
+ }
1000
+ }
1001
+
1002
+ // Continue to load next logger if no valid logger class found.
1003
+ if (! $logger_class_name) {
1004
+ continue;
1005
+ }
1006
+
1007
+ // Init found logger class.
1008
+ $logger_instance = new $logger_class_name($this);
1009
+
1010
+ if (! is_subclass_of($logger_instance, 'SimpleLogger') && ! is_a($logger_instance, 'SimpleLogger')) {
1011
+ continue;
1012
+ }
1013
+
1014
+ $logger_instance->loaded();
1015
+
1016
+ // Tell gettext-filter to add untranslated messages.
1017
+ $this->doFilterGettext = true;
1018
+ $this->doFilterGettext_currentLogger = $logger_instance;
1019
+
1020
+ $logger_info = $logger_instance->getInfo();
1021
+
1022
+ // Check so no logger has a logger slug with more than 30 chars,
1023
+ // because db column is only 30 chars.
1024
+ if (strlen($logger_instance->slug) > 30) {
1025
+ add_action('admin_notices', array( $this, 'admin_notice_logger_slug_to_long' ));
1026
+ }
1027
+
1028
+ // Un-tell gettext filter.
1029
+ $this->doFilterGettext = false;
1030
+ $this->doFilterGettext_currentLogger = null;
1031
+
1032
+ // LoggerInfo contains all messages, both translated an not, by key.
1033
+ // Add messages to the loggerInstance.
1034
+ $loopNum = 0;
1035
+
1036
+ $arr_messages_by_message_key = array();
1037
+
1038
+ if (isset($logger_info['messages']) && is_array($logger_info['messages'])) {
1039
+ foreach ((array) $logger_info['messages'] as $message_key => $message_translated) {
1040
+ // Find message in array with both translated and non translated strings.
1041
+ foreach ($logger_instance->messages as $one_message_with_translation_info) {
1042
+ if ($message_translated == $one_message_with_translation_info['translated_text']) {
1043
+ $arr_messages_by_message_key[ $message_key ] = $one_message_with_translation_info;
1044
+ continue;
1045
+ }
1046
+ }
1047
+ }
1048
+ }
1049
+
1050
+ $logger_instance->messages = $arr_messages_by_message_key;
1051
+
1052
+ // Add logger to array of loggers.
1053
+ $this->instantiatedLoggers[ $logger_instance->slug ] = array(
1054
+ 'name' => $logger_info['name'],
1055
+ 'instance' => $logger_instance
1056
+ );
1057
+ }// End foreach().
1058
+
1059
+ do_action('simple_history/loggers_loaded');
1060
+ }
1061
+
1062
+ /**
1063
+ * Load built in dropins from all files in /dropins
1064
+ * and instantiates them
1065
+ */
1066
+ public function load_dropins()
1067
+ {
1068
+ $dropinsDir = SIMPLE_HISTORY_PATH . 'dropins/';
1069
+
1070
+ $dropinsFiles = array(
1071
+ $dropinsDir . 'SimpleHistoryPluginPatchesDropin.php',
1072
+ $dropinsDir . 'SimpleHistoryDonateDropin.php',
1073
+ $dropinsDir . 'SimpleHistoryExportDropin.php',
1074
+ $dropinsDir . 'SimpleHistoryFilterDropin.php',
1075
+ $dropinsDir . 'SimpleHistoryIpInfoDropin.php',
1076
+ $dropinsDir . 'SimpleHistoryNewRowsNotifier.php',
1077
+ $dropinsDir . 'SimpleHistoryRSSDropin.php',
1078
+ $dropinsDir . 'SimpleHistorySettingsLogtestDropin.php',
1079
+ $dropinsDir . 'SimpleHistorySettingsStatsDropin.php',
1080
+ $dropinsDir . 'SimpleHistorySettingsDebugDropin.php',
1081
+ $dropinsDir . 'SimpleHistorySidebarDropin.php',
1082
+ $dropinsDir . 'SimpleHistorySidebarStats.php',
1083
+ $dropinsDir . 'SimpleHistorySidebarSettings.php',
1084
+ $dropinsDir . 'SimpleHistoryWPCLIDropin.php',
1085
+ $dropinsDir . 'SimpleHistoryDebugDropin.php',
1086
+ );
1087
+
1088
+ /**
1089
+ * Filter the array with absolute paths to files as returned by glob function.
1090
+ * Each file will be loaded and will be assumed to be a dropin with a classname
1091
+ * the same as the filename.
1092
+ *
1093
+ * @since 2.0
1094
+ *
1095
+ * @param array $dropinsFiles Array with filenames
1096
+ */
1097
+ $dropinsFiles = apply_filters('simple_history/dropins_files', $dropinsFiles);
1098
+
1099
+ $arrDropinsToInstantiate = array();
1100
+
1101
+ foreach ($dropinsFiles as $oneDropinFile) {
1102
+ // path/path/simplehistory/dropins/SimpleHistoryDonateDropin.php => SimpleHistoryDonateDropin
1103
+ $oneDropinFileBasename = basename($oneDropinFile, '.php');
1104
+
1105
+ $load_dropin = true;
1106
+
1107
+ /**
1108
+ * Filter to completely skip loading of dropin
1109
+ * complete filer name will be like:
1110
+ * simple_history/dropin/load_dropin_SimpleHistoryRSSDropin
1111
+ *
1112
+ * @since 2.0.6
1113
+ *
1114
+ * @param bool if to load the dropin. return false to not load it.
1115
+ */
1116
+ $load_dropin = apply_filters("simple_history/dropin/load_dropin_{$oneDropinFileBasename}", $load_dropin);
1117
+
1118
+ /**
1119
+ * Filter to completely skip loading of a dropin
1120
+ *
1121
+ * @since 2.0.22
1122
+ *
1123
+ * @param bool if to load the dropin. return false to not load it.
1124
+ * @param string slug of dropin
1125
+ */
1126
+ $load_dropin = apply_filters('simple_history/dropin/load_dropin', $load_dropin, $oneDropinFileBasename);
1127
+
1128
+ if (! $load_dropin) {
1129
+ continue;
1130
+ }
1131
+
1132
+ include_once $oneDropinFile;
1133
+
1134
+ $arrDropinsToInstantiate[] = $oneDropinFileBasename;
1135
+ }// End foreach().
1136
+
1137
+ /**
1138
+ * Action that dropins can use to add their custom loggers.
1139
+ * See register_dropin() for more info.
1140
+ *
1141
+ * @since 2.3.2
1142
+ *
1143
+ * @param array $arrDropinsToInstantiate Array with class names
1144
+ */
1145
+ do_action('simple_history/add_custom_dropin', $this);
1146
+
1147
+ /**
1148
+ * Filter the array with names of dropin to instantiate.
1149
+ *
1150
+ * @since 2.0
1151
+ *
1152
+ * @param array $arrDropinsToInstantiate Array with class names
1153
+ */
1154
+ $arrDropinsToInstantiate = apply_filters('simple_history/dropins_to_instantiate', $arrDropinsToInstantiate);
1155
+
1156
+ $arrDropinsToInstantiate = array_merge($arrDropinsToInstantiate, $this->externalDropins);
1157
+
1158
+ // Instantiate each dropin
1159
+ foreach ($arrDropinsToInstantiate as $oneDropinName) {
1160
+ if (! class_exists($oneDropinName)) {
1161
+ continue;
1162
+ }
1163
+
1164
+ $this->instantiatedDropins[ $oneDropinName ] = array(
1165
+ 'name' => $oneDropinName,
1166
+ 'instance' => new $oneDropinName($this)
1167
+ );
1168
+ }
1169
+ }
1170
+
1171
+ /**
1172
+ * Gets the pager size,
1173
+ * i.e. the number of items to show on each page in the history
1174
+ *
1175
+ * @return int
1176
+ */
1177
+ public function get_pager_size()
1178
+ {
1179
+ $pager_size = get_option('simple_history_pager_size', 20);
1180
+
1181
+ /**
1182
+ * Filter the pager size setting
1183
+ *
1184
+ * @since 2.0
1185
+ *
1186
+ * @param int $pager_size
1187
+ */
1188
+ $pager_size = apply_filters('simple_history/pager_size', $pager_size);
1189
+
1190
+ return $pager_size;
1191
+ }
1192
+
1193
+ /**
1194
+ * Gets the pager size,
1195
+ * i.e. the number of items to show on each page in the history
1196
+ *
1197
+ * @since 2.12
1198
+ * @return int
1199
+ */
1200
+ public function get_pager_size_dashboard()
1201
+ {
1202
+ $pager_size = get_option('simple_history_pager_size_dashboard', 5);
1203
+
1204
+ /**
1205
+ * Filter the pager size setting
1206
+ *
1207
+ * @since 2.12
1208
+ *
1209
+ * @param int $pager_size
1210
+ */
1211
+ $pager_size = apply_filters('simple_history/pager_size_dashboard', $pager_size);
1212
+
1213
+ return $pager_size;
1214
+ }
1215
+
1216
+ /**
1217
+ * Show a link to our settings page on the Plugins -> Installed Plugins screen
1218
+ */
1219
+ public function plugin_action_links($actions, $b, $c, $d)
1220
+ {
1221
+ // Only add link if user has the right to view the settings page
1222
+ if (! current_user_can($this->get_view_settings_capability())) {
1223
+ return $actions;
1224
+ }
1225
+
1226
+ $settings_page_url = menu_page_url(SimpleHistory::SETTINGS_MENU_SLUG, 0);
1227
+
1228
+ if (empty($actions)) {
1229
+ // Create array if actions is empty (and therefore is assumed to be a string by PHP & results in PHP 7.1+ fatal error due to trying to make array modifications on what's assumed to be a string)
1230
+ $actions = array();
1231
+ } elseif (is_string($actions)) {
1232
+ // Convert the string (which it might've been retrieved as) to an array for future use as an array
1233
+ $actions = array( $actions );
1234
+ }
1235
+ $actions[] = "<a href='$settings_page_url'>" . __('Settings', 'simple-history') . '</a>';
1236
+
1237
+ return $actions;
1238
+ }
1239
+
1240
+ /**
1241
+ * Maybe add a dashboard widget,
1242
+ * requires current user to have view history capability
1243
+ * and a setting to show dashboard to be set
1244
+ */
1245
+ public function add_dashboard_widget()
1246
+ {
1247
+ if ($this->setting_show_on_dashboard() && current_user_can($this->get_view_history_capability())) {
1248
+ /**
1249
+ * Filter to determine if history page should be added to page below dashboard or not
1250
+ *
1251
+ * @since 2.0.23
1252
+ *
1253
+ * @param bool Show the page or not
1254
+ */
1255
+ $show_dashboard_widget = apply_filters('simple_history/show_dashboard_widget', true);
1256
+
1257
+ if ($show_dashboard_widget) {
1258
+ wp_add_dashboard_widget(
1259
+ 'simple_history_dashboard_widget',
1260
+ __('Simple History', 'simple-history'),
1261
+ array($this, 'dashboard_widget_output')
1262
+ );
1263
+ }
1264
+ }
1265
+ }
1266
+
1267
+ /**
1268
+ * Output html for the dashboard widget
1269
+ */
1270
+ public function dashboard_widget_output()
1271
+ {
1272
+ $pager_size = $this->get_pager_size_dashboard();
1273
+
1274
+ /**
1275
+ * Filter the pager size setting for the dashboard
1276
+ *
1277
+ * @since 2.0
1278
+ *
1279
+ * @param int $pager_size
1280
+ */
1281
+ $pager_size = apply_filters('simple_history/dashboard_pager_size', $pager_size);
1282
+
1283
+ do_action('simple_history/dashboard/before_gui', $this);
1284
+ ?>
1285
+ <div class="SimpleHistoryGui"
1286
+ data-pager-size='<?php echo $pager_size; ?>'
1287
+ ></div>
1288
+ <?php
1289
+ }
1290
+
1291
+ public function is_on_our_own_pages($hook = '')
1292
+ {
1293
+ $current_screen = get_current_screen();
1294
+
1295
+ $basePrefix = apply_filters('simple_history/admin_location', 'index');
1296
+ $basePrefix = ( $basePrefix === 'index' ) ? 'dashboard' : $basePrefix;
1297
+
1298
+ if ($current_screen && $current_screen->base == 'settings_page_' . SimpleHistory::SETTINGS_MENU_SLUG) {
1299
+ return true;
1300
+ } elseif ($current_screen && $current_screen->base == $basePrefix. '_page_simple_history_page') {
1301
+ return true;
1302
+ } elseif (( $hook == 'settings_page_' . SimpleHistory::SETTINGS_MENU_SLUG ) || ( $this->setting_show_on_dashboard() && $hook == 'index.php' ) || ( $this->setting_show_as_page() && $hook == $basePrefix . '_page_simple_history_page' )) {
1303
+ return true;
1304
+ } elseif ($current_screen && $current_screen->base == 'dashboard' && $this->setting_show_on_dashboard()) {
1305
+ return true;
1306
+ }
1307
+
1308
+ return false;
1309
+ }
1310
+
1311
+ /**
1312
+ * Enqueue styles and scripts for Simple History but only to our own pages.
1313
+ *
1314
+ * Only adds scripts to pages where the log is shown or the settings page.
1315
+ */
1316
+ public function enqueue_admin_scripts($hook)
1317
+ {
1318
+ if ($this->is_on_our_own_pages()) {
1319
+ add_thickbox();
1320
+
1321
+ wp_enqueue_style(
1322
+ 'simple_history_styles',
1323
+ SIMPLE_HISTORY_DIR_URL . 'css/styles.css',
1324
+ false,
1325
+ SIMPLE_HISTORY_VERSION
1326
+ );
1327
+ wp_enqueue_script(
1328
+ 'simple_history_script',
1329
+ SIMPLE_HISTORY_DIR_URL . 'js/scripts.js',
1330
+ array('jquery', 'backbone', 'wp-util'),
1331
+ SIMPLE_HISTORY_VERSION,
1332
+ true
1333
+ );
1334
+
1335
+ wp_enqueue_script('select2', SIMPLE_HISTORY_DIR_URL . 'js/select2/select2.full.min.js', array( 'jquery' ));
1336
+ wp_enqueue_style('select2', SIMPLE_HISTORY_DIR_URL . 'js/select2/select2.min.css');
1337
+
1338
+ // Translations that we use in JavaScript
1339
+ wp_localize_script('simple_history_script', 'simple_history_script_vars', array(
1340
+ 'settingsConfirmClearLog' => __('Remove all log items?', 'simple-history'),
1341
+ 'pagination' => array(
1342
+ 'goToTheFirstPage' => __('Go to the first page', 'simple-history'),
1343
+ 'goToThePrevPage' => __('Go to the previous page', 'simple-history'),
1344
+ 'goToTheNextPage' => __('Go to the next page', 'simple-history'),
1345
+ 'goToTheLastPage' => __('Go to the last page', 'simple-history'),
1346
+ 'currentPage' => __('Current page', 'simple-history')
1347
+ ),
1348
+ 'loadLogAPIError' => __('Oups, the log could not be loaded right now.', 'simple-history'),
1349
+ 'ajaxLoadError' => __(
1350
+ 'Hm, the log could not be loaded right now. Perhaps another plugin is giving some errors. Anyway, below is the output I got from the server.',
1351
+ 'simple-history'
1352
+ ),
1353
+ 'logNoHits' => __('Your search did not match any history events.', 'simple-history')
1354
+ ));
1355
+
1356
+ // Call plugins adminCSS-method, so they can add their CSS
1357
+ foreach ($this->instantiatedLoggers as $one_logger) {
1358
+ if (method_exists($one_logger['instance'], 'adminCSS')) {
1359
+ $one_logger['instance']->adminCSS();
1360
+ }
1361
+ }
1362
+
1363
+ // Add timeago.js
1364
+ wp_enqueue_script(
1365
+ 'timeago',
1366
+ SIMPLE_HISTORY_DIR_URL . 'js/timeago/jquery.timeago.js',
1367
+ array('jquery'),
1368
+ '1.5.2',
1369
+ true
1370
+ );
1371
+
1372
+ // Determine current locale to load timeago locale
1373
+ $locale = strtolower(substr(get_locale(), 0, 2));
1374
+ $locale_url_path = SIMPLE_HISTORY_DIR_URL . 'js/timeago/locales/jquery.timeago.%s.js';
1375
+ $locale_dir_path = SIMPLE_HISTORY_PATH . 'js/timeago/locales/jquery.timeago.%s.js';
1376
+
1377
+ // Only enqueue if locale-file exists on file system
1378
+ if (file_exists(sprintf($locale_dir_path, $locale))) {
1379
+ wp_enqueue_script('timeago-locale', sprintf($locale_url_path, $locale), array( 'jquery' ), '1.5.2', true);
1380
+ } else {
1381
+ wp_enqueue_script('timeago-locale', sprintf($locale_url_path, 'en'), array( 'jquery' ), '1.5.2', true);
1382
+ }
1383
+ // end add timeago
1384
+ // Load Select2 locale
1385
+ $locale_url_path = SIMPLE_HISTORY_DIR_URL . 'js/select2/i18n/%s.js';
1386
+ $locale_dir_path = SIMPLE_HISTORY_PATH . 'js/select2/i18n/%s.js';
1387
+
1388
+ if (file_exists(sprintf($locale_dir_path, $locale))) {
1389
+ wp_enqueue_script('select2-locale', sprintf($locale_url_path, $locale), array( 'jquery' ), '3.5.1', true);
1390
+ }
1391
+
1392
+ /**
1393
+ * Fires when the admin scripts have been enqueued.
1394
+ * Only fires on any of the pages where Simple History is used
1395
+ *
1396
+ * @since 2.0
1397
+ *
1398
+ * @param SimpleHistory $SimpleHistory This class.
1399
+ */
1400
+ do_action('simple_history/enqueue_admin_scripts', $this);
1401
+ }// End if().
1402
+ }
1403
+
1404
+ public function filter_option_page_capability($capability)
1405
+ {
1406
+ return $capability;
1407
+ }
1408
+
1409
+ /**
1410
+ * Check if plugin version have changed, i.e. has been upgraded
1411
+ * If upgrade is detected then maybe modify database and so on for that version
1412
+ */
1413
+ public function check_for_upgrade()
1414
+ {
1415
+ global $wpdb;
1416
+
1417
+ $db_version = get_option('simple_history_db_version');
1418
+ $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
1419
+ $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
1420
+ $first_install = false;
1421
+
1422
+ // If no db_version is set then this
1423
+ // is a version of Simple History < 0.4
1424
+ // or it's a first install
1425
+ // Fix database not using UTF-8
1426
+ if (false === $db_version || intval($db_version) == 0) {
1427
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
1428
+
1429
+ // Table creation, used to be in register_activation_hook
1430
+ // We change the varchar size to add one num just to force update of encoding. dbdelta didn't see it otherwise.
1431
+ $sql =
1432
+ 'CREATE TABLE ' .
1433
+ $table_name .
1434
+ ' (
1435
+ id bigint(20) NOT NULL AUTO_INCREMENT,
1436
+ date datetime NOT NULL,
1437
+ PRIMARY KEY (id)
1438
+ ) CHARACTER SET=utf8;';
1439
+
1440
+ // Upgrade db / fix utf for varchars
1441
+ dbDelta($sql);
1442
+
1443
+ // Fix UTF-8 for table
1444
+ $sql = sprintf('alter table %1$s charset=utf8;', $table_name);
1445
+ $wpdb->query($sql);
1446
+
1447
+ $db_version_prev = $db_version;
1448
+ $db_version = 1;
1449
+
1450
+ update_option('simple_history_db_version', $db_version);
1451
+
1452
+ // We are not 100% sure that this is a first install,
1453
+ // but it is at least a very old version that is being updated
1454
+ $first_install = true;
1455
+ } // End if().
1456
+
1457
+ // If db version is 1 then upgrade to 2
1458
+ // Version 2 added the action_description column
1459
+ if (1 == intval($db_version)) {
1460
+ // V2 used to add column "action_description"
1461
+ // but it's not used any more so don't do i
1462
+ $db_version_prev = $db_version;
1463
+ $db_version = 2;
1464
+
1465
+ update_option('simple_history_db_version', $db_version);
1466
+ }
1467
+
1468
+ // Check that all options we use are set to their defaults, if they miss value
1469
+ // Each option that is missing a value will make a sql call otherwise = unnecessary
1470
+ $arr_options = array(
1471
+ array(
1472
+ 'name' => 'simple_history_show_as_page',
1473
+ 'default_value' => 1
1474
+ ),
1475
+ array(
1476
+ 'name' => 'simple_history_show_on_dashboard',
1477
+ 'default_value' => 1
1478
+ )
1479
+ );
1480
+
1481
+ foreach ($arr_options as $one_option) {
1482
+ if (false === ( $option_value = get_option($one_option['name']) )) {
1483
+ // Value is not set in db, so set it to a default
1484
+ update_option($one_option['name'], $one_option['default_value']);
1485
+ }
1486
+ }
1487
+
1488
+ /**
1489
+ * If db_version is 2 then upgrade to 3:
1490
+ * - Add some fields to existing table wp_simple_history_contexts
1491
+ * - Add all new table wp_simple_history_contexts
1492
+ *
1493
+ * @since 2.0
1494
+ */
1495
+ if (2 == intval($db_version)) {
1496
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
1497
+
1498
+ // Update old table
1499
+ $sql = "
1500
+ CREATE TABLE {$table_name} (
1501
+ id bigint(20) NOT NULL AUTO_INCREMENT,
1502
+ date datetime NOT NULL,
1503
+ logger varchar(30) DEFAULT NULL,
1504
+ level varchar(20) DEFAULT NULL,
1505
+ message varchar(255) DEFAULT NULL,
1506
+ occasionsID varchar(32) DEFAULT NULL,
1507
+ initiator varchar(16) DEFAULT NULL,
1508
+ PRIMARY KEY (id),
1509
+ KEY date (date),
1510
+ KEY loggerdate (logger,date)
1511
+ ) CHARSET=utf8;";
1512
+
1513
+ dbDelta($sql);
1514
+
1515
+ // Add context table
1516
+ $sql = "
1517
+ CREATE TABLE IF NOT EXISTS {$table_name_contexts} (
1518
+ context_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
1519
+ history_id bigint(20) unsigned NOT NULL,
1520
+ `key` varchar(255) DEFAULT NULL,
1521
+ value longtext,
1522
+ PRIMARY KEY (context_id),
1523
+ KEY history_id (history_id),
1524
+ KEY `key` (`key`)
1525
+ ) CHARSET=utf8;
1526
+ ";
1527
+
1528
+ $wpdb->query($sql);
1529
+
1530
+ $db_version_prev = $db_version;
1531
+ $db_version = 3;
1532
+ update_option('simple_history_db_version', $db_version);
1533
+
1534
+ // Update possible old items to use SimpleLegacyLogger
1535
+ $sql = sprintf(
1536
+ '
1537
+ UPDATE %1$s
1538
+ SET
1539
+ logger = "SimpleLegacyLogger",
1540
+ level = "info"
1541
+ WHERE logger IS NULL
1542
+ ',
1543
+ $table_name
1544
+ );
1545
+
1546
+ $wpdb->query($sql);
1547
+
1548
+ // Say welcome, however loggers are not added this early so we need to
1549
+ // use a filter to load it later
1550
+ add_action('simple_history/loggers_loaded', array( $this, 'addWelcomeLogMessage' ));
1551
+ } // End if().
1552
+
1553
+ /**
1554
+ * If db version = 3
1555
+ * then we need to update database to allow null values for some old columns
1556
+ * that used to work in pre wp 4.1 beta, but since 4.1 wp uses STRICT_ALL_TABLES
1557
+ * WordPress Commit: https://github.com/WordPress/WordPress/commit/f17d168a0f72211a9bfd9d3fa680713069871bb6
1558
+ *
1559
+ * @since 2.0
1560
+ */
1561
+ if (3 == intval($db_version)) {
1562
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
1563
+
1564
+ // If old columns exist = this is an old install, then modify the columns so we still can keep them
1565
+ // we want to keep them because user may have logged items that they want to keep
1566
+ $db_cools = $wpdb->get_col("DESCRIBE $table_name");
1567
+
1568
+ if (in_array('action', $db_cools)) {
1569
+ $sql = sprintf(
1570
+ '
1571
+ ALTER TABLE %1$s
1572
+ MODIFY `action` varchar(255) NULL,
1573
+ MODIFY `object_type` varchar(255) NULL,
1574
+ MODIFY `object_subtype` varchar(255) NULL,
1575
+ MODIFY `user_id` int(10) NULL,
1576
+ MODIFY `object_id` int(10) NULL,
1577
+ MODIFY `object_name` varchar(255) NULL
1578
+ ',
1579
+ $table_name
1580
+ );
1581
+ $wpdb->query($sql);
1582
+ }
1583
+
1584
+ $db_version_prev = $db_version;
1585
+ $db_version = 4;
1586
+
1587
+ update_option('simple_history_db_version', $db_version);
1588
+ } // End if().
1589
+
1590
+ // Some installs on 2.2.2 got failed installs
1591
+ // We detect these by checking for db_version and then running the install stuff again
1592
+ if (4 == intval($db_version)) {
1593
+ if (! $this->does_database_have_data()) {
1594
+ // not ok, decrease db number so installs will run again and hopefully fix things
1595
+ $db_version = 0;
1596
+ } else {
1597
+ // all looks ok, upgrade to db version 5, so this part is not done again
1598
+ $db_version = 5;
1599
+ }
1600
+
1601
+ update_option('simple_history_db_version', $db_version);
1602
+ }
1603
+ } // end check_for_upgrade
1604
+
1605
+ /**
1606
+ * Check if the database has data/rows
1607
+ *
1608
+ * @since 2.1.6
1609
+ * @return bool True if database is not empty, false if database is empty = contains no data
1610
+ */
1611
+ public function does_database_have_data()
1612
+ {
1613
+ global $wpdb;
1614
+
1615
+ $tableprefix = $wpdb->prefix;
1616
+
1617
+ $simple_history_table = SimpleHistory::DBTABLE;
1618
+ $simple_history_context_table = SimpleHistory::DBTABLE_CONTEXTS;
1619
+
1620
+ $sql_data_exists = "SELECT id AS id_exists FROM {$tableprefix}{$simple_history_table} LIMIT 1";
1621
+ $data_exists = (bool) $wpdb->get_var($sql_data_exists, 0);
1622
+
1623
+ return $data_exists;
1624
+ }
1625
+
1626
+ /**
1627
+ * Greet users to version 2!
1628
+ * Is only called after database has been upgraded, so only on first install (or upgrade).
1629
+ * Not called after only plugin activation.
1630
+ */
1631
+ public function addWelcomeLogMessage()
1632
+ {
1633
+ $db_data_exists = $this->does_database_have_data();
1634
+ // $db_data_exists = false;
1635
+ $pluginLogger = $this->getInstantiatedLoggerBySlug('SimplePluginLogger');
1636
+ if ($pluginLogger) {
1637
+ // Add plugin installed message
1638
+ $context = array(
1639
+ 'plugin_name' => 'Simple History',
1640
+ 'plugin_description' =>
1641
+ 'Plugin that logs various things that occur in WordPress and then presents those events in a very nice GUI.',
1642
+ 'plugin_url' => 'http://simple-history.com',
1643
+ 'plugin_version' => SIMPLE_HISTORY_VERSION,
1644
+ 'plugin_author' => 'Pär Thernström'
1645
+ );
1646
+
1647
+ $pluginLogger->infoMessage('plugin_installed', $context);
1648
+
1649
+ // Add plugin activated message
1650
+ $context['plugin_slug'] = 'simple-history';
1651
+ $context['plugin_title'] = '<a href="http://simple-history.com/">Simple History</a>';
1652
+
1653
+ $pluginLogger->infoMessage('plugin_activated', $context);
1654
+ }
1655
+
1656
+ if (! $db_data_exists) {
1657
+ $welcome_message_1 = __(
1658
+ '
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1659
  Welcome to Simple History!
1660
 
1661
  This is the main history feed. It will contain events that this plugin has logged.
1662
  ',
1663
+ 'simple-history'
1664
+ );
1665
 
1666
+ $welcome_message_2 = __(
1667
+ '
1668
  Because Simple History was just recently installed, this feed does not contain much events yet. But keep the plugin activated and soon you will see detailed information about page edits, plugin updates, user logins, and much more.
1669
  ',
1670
+ 'simple-history'
1671
+ );
1672
+
1673
+ SimpleLogger()->info($welcome_message_2, array(
1674
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS
1675
+ ));
1676
+
1677
+ SimpleLogger()->info($welcome_message_1, array(
1678
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS
1679
+ ));
1680
+ }
1681
+ }
1682
+
1683
+ public function registerSettingsTab($arr_tab_settings)
1684
+ {
1685
+ $this->arr_settings_tabs[] = $arr_tab_settings;
1686
+ }
1687
+
1688
+ public function getSettingsTabs()
1689
+ {
1690
+ return $this->arr_settings_tabs;
1691
+ }
1692
+
1693
+ /**
1694
+ * Output HTML for the settings page
1695
+ * Called from add_options_page
1696
+ */
1697
+ public function settings_page_output()
1698
+ {
1699
+ $arr_settings_tabs = $this->getSettingsTabs(); ?>
1700
+ <div class="wrap">
1701
+
1702
+ <h1 class="SimpleHistoryPageHeadline">
1703
+ <div class="dashicons dashicons-backup SimpleHistoryPageHeadline__icon"></div>
1704
+ <?php _e('Simple History Settings', 'simple-history'); ?>
1705
+ </h1>
1706
+
1707
+ <?php
1708
+ $active_tab = isset($_GET['selected-tab']) ? $_GET['selected-tab'] : 'settings';
1709
+ $settings_base_url = menu_page_url(SimpleHistory::SETTINGS_MENU_SLUG, 0);
1710
+ ?>
1711
+
1712
+ <h2 class="nav-tab-wrapper">
1713
+ <?php foreach ($arr_settings_tabs as $one_tab) {
1714
+ $tab_slug = $one_tab['slug'];
1715
+
1716
+ printf(
1717
+ '<a href="%3$s" class="nav-tab %4$s">%1$s</a>',
1718
+ $one_tab['name'], // 1
1719
+ $tab_slug, // 2
1720
+ esc_url(add_query_arg('selected-tab', $tab_slug, $settings_base_url)), // 3
1721
+ $active_tab == $tab_slug ? 'nav-tab-active' : ''// 4
1722
+ );
1723
+ } ?>
1724
+ </h2>
1725
+
1726
+ <?php
1727
+ // Output contents for selected tab
1728
+ $arr_active_tab = wp_filter_object_list($arr_settings_tabs, array(
1729
+ 'slug' => $active_tab
1730
+ ));
1731
+ $arr_active_tab = current($arr_active_tab);
1732
+
1733
+ // We must have found an active tab and it must have a callable function
1734
+ if (! $arr_active_tab || ! is_callable($arr_active_tab['function'])) {
1735
+ wp_die(__('No valid callback found', 'simple-history'));
1736
+ }
1737
+
1738
+ $args = array(
1739
+ 'arr_active_tab' => $arr_active_tab
1740
+ );
1741
+
1742
+ call_user_func_array($arr_active_tab['function'], $args);?>
1743
+
1744
+ </div>
1745
+ <?php
1746
+ }
1747
+
1748
+ public function settings_output_log()
1749
+ {
1750
+ include SIMPLE_HISTORY_PATH . 'templates/settings-log.php';
1751
+ }
1752
+
1753
+ public function settings_output_general()
1754
+ {
1755
+ include SIMPLE_HISTORY_PATH . 'templates/settings-general.php';
1756
+ }
1757
+
1758
+ public function settings_output_styles_example()
1759
+ {
1760
+ include SIMPLE_HISTORY_PATH . 'templates/settings-style-example.php';
1761
+ }
1762
+
1763
+ /**
1764
+ * Content for section intro. Leave it be, even if empty.
1765
+ * Called from add_sections_setting.
1766
+ */
1767
+ public function settings_section_output()
1768
+ {
1769
+ }
1770
+
1771
+ /**
1772
+ * Add pages (history page and settings page)
1773
+ */
1774
+ public function add_admin_pages()
1775
+ {
1776
+ // Add a history page as a sub-page below the Dashboard menu item
1777
+ if ($this->setting_show_as_page()) {
1778
+ /**
1779
+ * Filter to determine if history page should be added to page below dashboard or not
1780
+ *
1781
+ * @since 2.0.23
1782
+ *
1783
+ * @param bool Show the page or not
1784
+ */
1785
+ $show_dashboard_page = apply_filters('simple_history/show_dashboard_page', true);
1786
+
1787
+ if ($show_dashboard_page) {
1788
+ add_submenu_page(
1789
+ apply_filters('simple_history/admin_location', 'index') . '.php',
1790
+ _x('Simple History', 'dashboard title name', 'simple-history'),
1791
+ _x('Simple History', 'dashboard menu name', 'simple-history'),
1792
+ $this->get_view_history_capability(),
1793
+ 'simple_history_page',
1794
+ array( $this, 'history_page_output' )
1795
+ );
1796
+ }
1797
+ }
1798
+
1799
+ // Add a settings page
1800
+ $show_settings_page = true;
1801
+ $show_settings_page = apply_filters('simple_history_show_settings_page', $show_settings_page);
1802
+ $show_settings_page = apply_filters('simple_history/show_settings_page', $show_settings_page);
1803
+
1804
+ if ($show_settings_page) {
1805
+ add_options_page(
1806
+ __('Simple History Settings', 'simple-history'),
1807
+ _x('Simple History', 'Options page menu title', 'simple-history'),
1808
+ $this->get_view_settings_capability(),
1809
+ SimpleHistory::SETTINGS_MENU_SLUG,
1810
+ array( $this, 'settings_page_output' )
1811
+ );
1812
+ }
1813
+ }
1814
+
1815
+ /**
1816
+ * Add setting sections and settings for the settings page
1817
+ * Also maybe save some settings before outputing them
1818
+ */
1819
+ public function add_settings()
1820
+ {
1821
+ // Clear the log if clear button was clicked in settings.
1822
+ if (isset($_GET['simple_history_clear_log_nonce']) &&
1823
+ wp_verify_nonce($_GET['simple_history_clear_log_nonce'], 'simple_history_clear_log')
1824
+ ) {
1825
+ if ($this->user_can_clear_log()) {
1826
+ $this->clear_log();
1827
+ }
1828
+
1829
+ $msg = __('Cleared database', 'simple-history');
1830
+
1831
+ add_settings_error(
1832
+ 'simple_history_rss_feed_regenerate_secret',
1833
+ 'simple_history_rss_feed_regenerate_secret',
1834
+ $msg,
1835
+ 'updated'
1836
+ );
1837
+
1838
+ set_transient('settings_errors', get_settings_errors(), 30);
1839
+
1840
+ $goback = esc_url_raw(add_query_arg('settings-updated', 'true', wp_get_referer()));
1841
+ wp_redirect($goback);
1842
+ exit();
1843
+ }
1844
+
1845
+ // Section for general options.
1846
+ // Will contain settings like where to show simple history and number of items.
1847
+ $settings_section_general_id = self::SETTINGS_SECTION_GENERAL_ID;
1848
+ add_settings_section(
1849
+ $settings_section_general_id,
1850
+ '',
1851
+ array( $this, 'settings_section_output' ),
1852
+ SimpleHistory::SETTINGS_MENU_SLUG // Same slug as for options menu page.
1853
+ );
1854
+
1855
+ // Settings for the general settings section
1856
+ // Each setting = one row in the settings section
1857
+ // add_settings_field( $id, $title, $callback, $page, $section, $args );
1858
+ // Checkboxes for where to show simple history
1859
+ add_settings_field(
1860
+ 'simple_history_show_where',
1861
+ __('Show history', 'simple-history'),
1862
+ array( $this, 'settings_field_where_to_show' ),
1863
+ SimpleHistory::SETTINGS_MENU_SLUG,
1864
+ $settings_section_general_id
1865
+ );
1866
+
1867
+ // Nonces for show where inputs.
1868
+ register_setting(SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP, 'simple_history_show_on_dashboard');
1869
+ register_setting(SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP, 'simple_history_show_as_page');
1870
+
1871
+ // Number if items to show on the history page.
1872
+ add_settings_field(
1873
+ 'simple_history_number_of_items',
1874
+ __('Number of items per page on the log page', 'simple-history'),
1875
+ array( $this, 'settings_field_number_of_items' ),
1876
+ SimpleHistory::SETTINGS_MENU_SLUG,
1877
+ $settings_section_general_id
1878
+ );
1879
+
1880
+ // Nonces for number of items inputs.
1881
+ register_setting(SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP, 'simple_history_pager_size');
1882
+
1883
+ // Number if items to show on dashboard.
1884
+ add_settings_field(
1885
+ 'simple_history_number_of_items_dashboard',
1886
+ __('Number of items per page on the dashboard', 'simple-history'),
1887
+ array( $this, 'settings_field_number_of_items_dashboard' ),
1888
+ SimpleHistory::SETTINGS_MENU_SLUG,
1889
+ $settings_section_general_id
1890
+ );
1891
+
1892
+ // Nonces for number of items inputs.
1893
+ register_setting(SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP, 'simple_history_pager_size_dashboard');
1894
+
1895
+ // Link/button to clear log.
1896
+ if ($this->user_can_clear_log()) {
1897
+ add_settings_field(
1898
+ 'simple_history_clear_log',
1899
+ __('Clear log', 'simple-history'),
1900
+ array( $this, 'settings_field_clear_log' ),
1901
+ SimpleHistory::SETTINGS_MENU_SLUG,
1902
+ $settings_section_general_id
1903
+ );
1904
+ }
1905
+ }
1906
+
1907
+ /**
1908
+ * Output for page with the history
1909
+ */
1910
+ public function history_page_output()
1911
+ {
1912
+ // global $simple_history;
1913
+ // $this->purge_db();
1914
+ global $wpdb;
1915
+
1916
+ $pager_size = $this->get_pager_size();
1917
+
1918
+ /**
1919
+ * Filter the pager size setting for the history page
1920
+ *
1921
+ * @since 2.0
1922
+ *
1923
+ * @param int $pager_size
1924
+ */
1925
+ $pager_size = apply_filters('simple_history/page_pager_size', $pager_size);
1926
+ ?>
1927
+
1928
+ <div class="wrap SimpleHistoryWrap">
1929
+
1930
+ <h1 class="SimpleHistoryPageHeadline">
1931
+ <div class="dashicons dashicons-backup SimpleHistoryPageHeadline__icon"></div>
1932
+ <?php echo _x('Simple History', 'history page headline', 'simple-history'); ?>
1933
+ </h1>
1934
+
1935
+ <?php /**
1936
+ * Fires before the gui div
1937
+ *
1938
+ * @since 2.0
1939
+ *
1940
+ * @param SimpleHistory $SimpleHistory This class.
1941
+ */
1942
+ do_action('simple_history/history_page/before_gui', $this); ?>
1943
+
1944
+ <div class="SimpleHistoryGuiWrap">
1945
+
1946
+ <div class="SimpleHistoryGui"
1947
+ data-pager-size='<?php echo $pager_size; ?>'
1948
+ ></div>
1949
+
1950
+ <?php /**
1951
+ * Fires after the gui div
1952
+ *
1953
+ * @since 2.0
1954
+ *
1955
+ * @param SimpleHistory $SimpleHistory This class.
1956
+ */
1957
+ do_action('simple_history/history_page/after_gui', $this); ?>
1958
+
1959
+ </div>
1960
+
1961
+ </div>
1962
+
1963
+ <?php
1964
+ }
1965
+
1966
+ /**
1967
+ * Get setting if plugin should be visible on dasboard.
1968
+ * Defaults to true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1969
  *
1970
+ * @return bool
1971
+ */
1972
+ public function setting_show_on_dashboard()
1973
+ {
1974
+ $show_on_dashboard = get_option('simple_history_show_on_dashboard', 1);
1975
+ $show_on_dashboard = apply_filters('simple_history_show_on_dashboard', $show_on_dashboard);
1976
+ return (bool) $show_on_dashboard;
1977
+ }
1978
+
1979
+ /**
1980
+ * Should simple history be shown as a page
1981
+ * Defaults to true
1982
  *
1983
+ * @return bool
1984
  */
1985
+ public function setting_show_as_page()
1986
+ {
1987
+ $setting = get_option('simple_history_show_as_page', 1);
1988
+ $setting = apply_filters('simple_history_show_as_page', $setting);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1989
 
1990
+ return (bool) $setting;
1991
+ }
 
 
 
 
 
 
 
 
1992
 
1993
+ /**
1994
+ * Settings field for how many rows/items to show in log on the log page
1995
+ */
1996
+ public function settings_field_number_of_items()
1997
+ {
1998
+ $current_pager_size = $this->get_pager_size(); ?>
1999
+ <select name="simple_history_pager_size">
2000
+ <option <?php echo $current_pager_size == 5 ? 'selected' : ''; ?> value="5">5</option>
2001
+ <option <?php echo $current_pager_size == 10 ? 'selected' : ''; ?> value="10">10</option>
2002
+ <option <?php echo $current_pager_size == 15 ? 'selected' : ''; ?> value="15">15</option>
2003
+ <option <?php echo $current_pager_size == 20 ? 'selected' : ''; ?> value="20">20</option>
2004
+ <option <?php echo $current_pager_size == 25 ? 'selected' : ''; ?> value="25">25</option>
2005
+ <option <?php echo $current_pager_size == 30 ? 'selected' : ''; ?> value="30">30</option>
2006
+ <option <?php echo $current_pager_size == 40 ? 'selected' : ''; ?> value="40">40</option>
2007
+ <option <?php echo $current_pager_size == 50 ? 'selected' : ''; ?> value="50">50</option>
2008
+ <option <?php echo $current_pager_size == 75 ? 'selected' : ''; ?> value="75">75</option>
2009
+ <option <?php echo $current_pager_size == 100 ? 'selected' : ''; ?> value="100">100</option>
2010
+ </select>
2011
+ <?php
2012
+ }
2013
+
2014
+ /**
2015
+ * Settings field for how many rows/items to show in log on the dashboard
2016
+ */
2017
+ public function settings_field_number_of_items_dashboard()
2018
+ {
2019
+ $current_pager_size = $this->get_pager_size_dashboard(); ?>
2020
+ <select name="simple_history_pager_size_dashboard">
2021
+ <option <?php echo $current_pager_size == 5 ? 'selected' : ''; ?> value="5">5</option>
2022
+ <option <?php echo $current_pager_size == 10 ? 'selected' : ''; ?> value="10">10</option>
2023
+ <option <?php echo $current_pager_size == 15 ? 'selected' : ''; ?> value="15">15</option>
2024
+ <option <?php echo $current_pager_size == 20 ? 'selected' : ''; ?> value="20">20</option>
2025
+ <option <?php echo $current_pager_size == 25 ? 'selected' : ''; ?> value="25">25</option>
2026
+ <option <?php echo $current_pager_size == 30 ? 'selected' : ''; ?> value="30">30</option>
2027
+ <option <?php echo $current_pager_size == 40 ? 'selected' : ''; ?> value="40">40</option>
2028
+ <option <?php echo $current_pager_size == 50 ? 'selected' : ''; ?> value="50">50</option>
2029
+ <option <?php echo $current_pager_size == 75 ? 'selected' : ''; ?> value="75">75</option>
2030
+ <option <?php echo $current_pager_size == 100 ? 'selected' : ''; ?> value="100">100</option>
2031
+ </select>
2032
+ <?php
2033
+ }
2034
+
2035
+ /**
2036
+ * Settings field for where to show the log, page or dashboard
2037
+ */
2038
+ public function settings_field_where_to_show()
2039
+ {
2040
+ $show_on_dashboard = $this->setting_show_on_dashboard();
2041
+ $show_as_page = $this->setting_show_as_page();
2042
+ ?>
2043
+
2044
+ <input <?php echo $show_on_dashboard
2045
+ ? "checked='checked'"
2046
+ : ''; ?> type="checkbox" value="1" name="simple_history_show_on_dashboard" id="simple_history_show_on_dashboard" class="simple_history_show_on_dashboard" />
2047
+ <label for="simple_history_show_on_dashboard"><?php _e('on the dashboard', 'simple-history'); ?></label>
2048
+
2049
+ <br />
2050
+
2051
+ <input <?php echo $show_as_page
2052
+ ? "checked='checked'"
2053
+ : ''; ?> type="checkbox" value="1" name="simple_history_show_as_page" id="simple_history_show_as_page" class="simple_history_show_as_page" />
2054
+ <label for="simple_history_show_as_page">
2055
+ <?php
2056
+ _e(
2057
+ 'as a page under the dashboard menu',
2058
+ 'simple-history'
2059
+ );
2060
+ ?>
2061
+ </label>
2062
+
2063
+ <?php
2064
+ }
2065
+
2066
+ /**
2067
+ * Settings section to clear database
2068
+ */
2069
+ public function settings_field_clear_log()
2070
+ {
2071
+ $clear_link = esc_url(add_query_arg('', ''));
2072
+ $clear_link = wp_nonce_url($clear_link, 'simple_history_clear_log', 'simple_history_clear_log_nonce');
2073
+ $clear_days = $this->get_clear_history_interval();
2074
+
2075
+ echo '<p>';
2076
+
2077
+ if ($clear_days > 0) {
2078
+ echo sprintf(
2079
+ __('Items in the database are automatically removed after %1$s days.', 'simple-history'),
2080
+ $clear_days
2081
+ );
2082
+ } else {
2083
+ _e('Items in the database are kept forever.', 'simple-history');
2084
+ }
2085
+
2086
+ echo '</p>';
2087
+
2088
+ printf(
2089
+ '<p><a class="button js-SimpleHistory-Settings-ClearLog" href="%2$s">%1$s</a></p>',
2090
+ __('Clear log now', 'simple-history'),
2091
+ $clear_link
2092
+ );
2093
+ }
2094
+
2095
+ /**
2096
+ * How old log entried are allowed to be.
2097
+ * 0 = don't delete old entries.
2098
+ *
2099
+ * @return int Number of days.
2100
+ */
2101
+ public function get_clear_history_interval()
2102
+ {
2103
+ $days = 60;
2104
+
2105
+ /**
2106
+ * Filter to modify number of days of history to keep.
2107
+ * Default is 60 days.
2108
+ *
2109
+ * @param $days Number of days of history to keep
2110
+ */
2111
+ $days = (int) apply_filters('simple_history_db_purge_days_interval', $days);
2112
+ $days = (int) apply_filters('simple_history/db_purge_days_interval', $days);
2113
+
2114
+ return $days;
2115
+ }
2116
+
2117
+ /**
2118
+ * Removes all items from the log
2119
+ */
2120
+ public function clear_log()
2121
+ {
2122
+ global $wpdb;
2123
 
2124
+ $tableprefix = $wpdb->prefix;
2125
 
2126
+ $simple_history_table = SimpleHistory::DBTABLE;
2127
+ $simple_history_context_table = SimpleHistory::DBTABLE_CONTEXTS;
2128
 
2129
+ // Get number of rows before delete.
2130
+ $sql_num_rows = "SELECT count(id) AS num_rows FROM {$tableprefix}{$simple_history_table}";
2131
+ $num_rows = $wpdb->get_var($sql_num_rows, 0);
2132
 
2133
+ // Use truncate instead of delete because it's much faster (I think, writing this much later).
2134
+ $sql = "TRUNCATE {$tableprefix}{$simple_history_table}";
2135
+ $wpdb->query($sql);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2136
 
2137
+ $sql = "TRUNCATE {$tableprefix}{$simple_history_context_table}";
2138
+ $wpdb->query($sql);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2139
 
2140
+ // Zero state sucks
2141
+ SimpleLogger()->info(
2142
+ __('The log for Simple History was cleared ({num_rows} rows were removed).', 'simple-history'),
2143
+ array(
2144
+ 'num_rows' => $num_rows
2145
+ )
2146
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2147
 
2148
+ $this->get_cache_incrementor(true);
2149
+ }
2150
+
2151
+ /**
2152
+ * Runs the purge_db() method sometimes
2153
+ * We don't want to call it each time because it performs SQL queries
2154
+ *
2155
+ * @since 2.0.17
2156
+ */
2157
+ public function maybe_purge_db()
2158
+ {
2159
+ // How often should we try to do this?
2160
+ // Once a day = a bit tiresome.
2161
+ // Let's go with sundays; purge the log on sundays.
2162
+ // Day of week, 1 = mon, 7 = sun.
2163
+ $day_of_week = date('N');
2164
+ if (7 === (int) $day_of_week) {
2165
+ $this->purge_db();
2166
+ }
2167
+ }
2168
+
2169
+ /**
2170
+ * Removes old entries from the db
2171
+ */
2172
+ public function purge_db()
2173
+ {
2174
+ $do_purge_history = true;
2175
+
2176
+ $do_purge_history = apply_filters('simple_history_allow_db_purge', $do_purge_history);
2177
+ $do_purge_history = apply_filters('simple_history/allow_db_purge', $do_purge_history);
2178
+
2179
+ if (! $do_purge_history) {
2180
+ return;
2181
+ }
2182
+
2183
+ $days = $this->get_clear_history_interval();
2184
+
2185
+ // Never clear log if days = 0.
2186
+ if (0 == $days) {
2187
+ return;
2188
+ }
2189
+
2190
+ global $wpdb;
2191
+
2192
+ $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
2193
+ $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
2194
+
2195
+ while (1 > 0) {
2196
+ // Get id of rows to delete.
2197
+ $sql = $wpdb->prepare(
2198
+ "SELECT id FROM $table_name WHERE DATE_ADD(date, INTERVAL %d DAY) < now() LIMIT 100000",
2199
+ $days
2200
+ );
2201
+
2202
+ $ids_to_delete = $wpdb->get_col($sql);
2203
+
2204
+ if (empty($ids_to_delete)) {
2205
+ // Nothing to delete.
2206
+ return;
2207
+ }
2208
+
2209
+ $sql_ids_in = implode(',', $ids_to_delete);
2210
+
2211
+ // Add number of deleted rows to total_rows option.
2212
+ $prev_total_rows = (int) get_option('simple_history_total_rows', 0);
2213
+ $total_rows = $prev_total_rows + sizeof($ids_to_delete);
2214
+ update_option('simple_history_total_rows', $total_rows);
2215
+
2216
+ // Remove rows + contexts.
2217
+ $sql_delete_history = "DELETE FROM {$table_name} WHERE id IN ($sql_ids_in)";
2218
+ $sql_delete_history_context = "DELETE FROM {$table_name_contexts} WHERE history_id IN ($sql_ids_in)";
2219
+
2220
+ $wpdb->query($sql_delete_history);
2221
+ $wpdb->query($sql_delete_history_context);
2222
+
2223
+ $message = _nx(
2224
+ 'Simple History removed one event that were older than {days} days',
2225
+ 'Simple History removed {num_rows} events that were older than {days} days',
2226
+ count($ids_to_delete),
2227
+ 'Database is being cleared automagically',
2228
+ 'simple-history'
2229
+ );
2230
+
2231
+ SimpleLogger()->info($message, array(
2232
+ 'days' => $days,
2233
+ 'num_rows' => count($ids_to_delete)
2234
+ ));
2235
+
2236
+ $this->get_cache_incrementor(true);
2237
+ }
2238
+ }
2239
+
2240
+ /**
2241
+ * Return plain text output for a log row
2242
+ * Uses the getLogRowPlainTextOutput of the logger that logged the row
2243
+ * with fallback to SimpleLogger if logger is not available.
2244
+ *
2245
+ * @param array $row
2246
+ * @return string
2247
+ */
2248
+ public function getLogRowPlainTextOutput($row)
2249
+ {
2250
+ $row_logger = $row->logger;
2251
+ $logger = null;
2252
+ $row->context = isset($row->context) && is_array($row->context) ? $row->context : array();
2253
+
2254
+ if (! isset($row->context['_message_key'])) {
2255
+ $row->context['_message_key'] = null;
2256
+ }
2257
+
2258
+ // Fallback to SimpleLogger if no logger exists for row
2259
+ if (! isset($this->instantiatedLoggers[ $row_logger ])) {
2260
+ $row_logger = 'SimpleLogger';
2261
+ }
2262
+
2263
+ $logger = $this->instantiatedLoggers[ $row_logger ]['instance'];
2264
+
2265
+ return $logger->getLogRowPlainTextOutput($row);
2266
+ }
2267
+
2268
+ /**
2269
+ * Return header output for a log row
2270
+ * Uses the getLogRowHeaderOutput of the logger that logged the row
2271
+ * with fallback to SimpleLogger if logger is not available
2272
+ *
2273
+ * Loggers are discouraged to override this in the loggers,
2274
+ * because the output should be the same for all items in the gui
2275
+ *
2276
+ * @param array $row
2277
+ * @return string
2278
+ */
2279
+ public function getLogRowHeaderOutput($row)
2280
+ {
2281
+ $row_logger = $row->logger;
2282
+ $logger = null;
2283
+ $row->context = isset($row->context) && is_array($row->context) ? $row->context : array();
2284
+
2285
+ // Fallback to SimpleLogger if no logger exists for row
2286
+ if (! isset($this->instantiatedLoggers[ $row_logger ])) {
2287
+ $row_logger = 'SimpleLogger';
2288
+ }
2289
+
2290
+ $logger = $this->instantiatedLoggers[ $row_logger ]['instance'];
2291
+
2292
+ return $logger->getLogRowHeaderOutput($row);
2293
+ }
2294
+
2295
+ /**
2296
+ *
2297
+ *
2298
+ * @param array $row
2299
+ * @return string
2300
+ */
2301
+ private function getLogRowSenderImageOutput($row)
2302
+ {
2303
+ $row_logger = $row->logger;
2304
+ $logger = null;
2305
+ $row->context = isset($row->context) && is_array($row->context) ? $row->context : array();
2306
+
2307
+ // Fallback to SimpleLogger if no logger exists for row
2308
+ if (! isset($this->instantiatedLoggers[ $row_logger ])) {
2309
+ $row_logger = 'SimpleLogger';
2310
+ }
2311
+
2312
+ $logger = $this->instantiatedLoggers[ $row_logger ]['instance'];
2313
+
2314
+ return $logger->getLogRowSenderImageOutput($row);
2315
+ }
2316
+
2317
+ public function getLogRowDetailsOutput($row)
2318
+ {
2319
+ $row_logger = $row->logger;
2320
+ $logger = null;
2321
+ $row->context = isset($row->context) && is_array($row->context) ? $row->context : array();
2322
+
2323
+ // Fallback to SimpleLogger if no logger exists for row
2324
+ if (! isset($this->instantiatedLoggers[ $row_logger ])) {
2325
+ $row_logger = 'SimpleLogger';
2326
+ }
2327
+
2328
+ $logger = $this->instantiatedLoggers[ $row_logger ]['instance'];
2329
+
2330
+ return $logger->getLogRowDetailsOutput($row);
2331
+ }
2332
+
2333
+ /**
2334
+ * Works like json_encode, but adds JSON_PRETTY_PRINT if the current php version supports it
2335
+ * i.e. PHP is 5.4.0 or greated
2336
+ *
2337
+ * @param mixed $value array|object|string|whatever that is json_encode'able.
2338
+ */
2339
+ public static function json_encode($value)
2340
+ {
2341
+ return version_compare(PHP_VERSION, '5.4.0') >= 0
2342
+ ? json_encode($value, JSON_PRETTY_PRINT)
2343
+ : json_encode($value);
2344
+ }
2345
+
2346
+ /**
2347
+ * Returns true if $haystack ends with $needle
2348
+ *
2349
+ * @param string $haystack
2350
+ * @param string $needle
2351
+ */
2352
+ public static function ends_with($haystack, $needle)
2353
+ {
2354
+ return $needle === substr($haystack, -strlen($needle));
2355
+ }
2356
+
2357
+ /**
2358
+ * Returns the HTML output for a log row, to be used in the GUI/Activity Feed
2359
+ *
2360
+ * @param array $oneLogRow SimpleHistoryLogQuery array with data from SimpleHistoryLogQuery
2361
+ * @return string
2362
+ */
2363
+ public function getLogRowHTMLOutput($oneLogRow, $args)
2364
+ {
2365
+ $defaults = array(
2366
+ 'type' => 'overview' // or "single" to include more stuff
2367
+ );
2368
+
2369
+ $args = wp_parse_args($args, $defaults);
2370
+
2371
+ $header_html = $this->getLogRowHeaderOutput($oneLogRow);
2372
+ $plain_text_html = $this->getLogRowPlainTextOutput($oneLogRow);
2373
+ $sender_image_html = $this->getLogRowSenderImageOutput($oneLogRow);
2374
+
2375
+ // Details = for example thumbnail of media
2376
+ $details_html = trim($this->getLogRowDetailsOutput($oneLogRow));
2377
+ if ($details_html) {
2378
+ $details_html = sprintf('<div class="SimpleHistoryLogitem__details">%1$s</div>', $details_html);
2379
+ }
2380
+
2381
+ // subsequentOccasions = including the current one
2382
+ $occasions_count = $oneLogRow->subsequentOccasions - 1;
2383
+ $occasions_html = '';
2384
+
2385
+ if ($occasions_count > 0) {
2386
+ $occasions_html = '<div class="SimpleHistoryLogitem__occasions">';
2387
+
2388
+ $occasions_html .= '<a href="#" class="SimpleHistoryLogitem__occasionsLink">';
2389
+ $occasions_html .= sprintf(
2390
+ _n('+%1$s similar event', '+%1$s similar events', $occasions_count, 'simple-history'),
2391
+ $occasions_count
2392
+ );
2393
+ $occasions_html .= '</a>';
2394
+
2395
+ $occasions_html .= '<span class="SimpleHistoryLogitem__occasionsLoading">';
2396
+ $occasions_html .= sprintf(__('Loading…', 'simple-history'), $occasions_count);
2397
+ $occasions_html .= '</span>';
2398
+
2399
+ $occasions_html .= '<span class="SimpleHistoryLogitem__occasionsLoaded">';
2400
+ $occasions_html .= sprintf(__('Showing %1$s more', 'simple-history'), $occasions_count);
2401
+ $occasions_html .= '</span>';
2402
+
2403
+ $occasions_html .= '</div>';
2404
+ }
2405
+
2406
+ // Add data atributes to log row, so plugins can do stuff
2407
+ $data_attrs = '';
2408
+ $data_attrs .= sprintf(' data-row-id="%1$d" ', $oneLogRow->id);
2409
+ $data_attrs .= sprintf(' data-occasions-count="%1$d" ', $occasions_count);
2410
+ $data_attrs .= sprintf(' data-occasions-id="%1$s" ', esc_attr($oneLogRow->occasionsID));
2411
+
2412
+ if (isset($oneLogRow->context['_server_remote_addr'])) {
2413
+ $data_attrs .= sprintf(' data-ip-address="%1$s" ', esc_attr($oneLogRow->context['_server_remote_addr']));
2414
+ }
2415
+
2416
+ $arr_found_additional_ip_headers = $this->instantiatedLoggers['SimpleLogger'][
2417
+ 'instance'
2418
+ ]->get_event_ip_number_headers($oneLogRow);
2419
+ if ($arr_found_additional_ip_headers) {
2420
+ $data_attrs .= sprintf(' data-ip-address-multiple="1" ');
2421
+ }
2422
+
2423
+ $data_attrs .= sprintf(' data-logger="%1$s" ', esc_attr($oneLogRow->logger));
2424
+ $data_attrs .= sprintf(' data-level="%1$s" ', esc_attr($oneLogRow->level));
2425
+ $data_attrs .= sprintf(' data-date="%1$s" ', esc_attr($oneLogRow->date));
2426
+ $data_attrs .= sprintf(' data-initiator="%1$s" ', esc_attr($oneLogRow->initiator));
2427
+
2428
+ if (isset($oneLogRow->context['_user_id'])) {
2429
+ $data_attrs .= sprintf(' data-initiator-user-id="%1$d" ', $oneLogRow->context['_user_id']);
2430
+ }
2431
+
2432
+ // If type is single then include more details
2433
+ $more_details_html = '';
2434
+ if ($args['type'] == 'single') {
2435
+ $more_details_html = apply_filters(
2436
+ 'simple_history/log_html_output_details_single/html_before_context_table',
2437
+ $more_details_html,
2438
+ $oneLogRow
2439
+ );
2440
+
2441
+ $more_details_html .= sprintf(
2442
+ '<h2 class="SimpleHistoryLogitem__moreDetailsHeadline">%1$s</h2>',
2443
+ __('Context data', 'simple-history')
2444
+ );
2445
+ $more_details_html .=
2446
+ '<p>' . __('This is potentially useful meta data that a logger has saved.', 'simple-history') . '</p>';
2447
+ $more_details_html .= "<table class='SimpleHistoryLogitem__moreDetailsContext'>";
2448
+ $more_details_html .= sprintf(
2449
+ '<tr>
2450
+ <th>%1$s</th>
2451
+ <th>%2$s</th>
2452
+ </tr>',
2453
+ 'Key',
2454
+ 'Value'
2455
+ );
2456
+
2457
+ $logRowKeysToShow = array_fill_keys(array_keys((array) $oneLogRow), true);
2458
+
2459
+ /**
2460
+ * Filter what keys to show from oneLogRow
2461
+ *
2462
+ * Array is in format
2463
+ *
2464
+ * Array
2465
+ * (
2466
+ * [id] => 1
2467
+ * [logger] => 1
2468
+ * [level] => 1
2469
+ * ...
2470
+ * )
2471
+ *
2472
+ * @since 2.0.29
2473
+ *
2474
+ * @param array with keys to show. key to show = key. value = boolean to show or not.
2475
+ * @param object log row to show details from
2476
+ */
2477
+ $logRowKeysToShow = apply_filters(
2478
+ 'simple_history/log_html_output_details_table/row_keys_to_show',
2479
+ $logRowKeysToShow,
2480
+ $oneLogRow
2481
+ );
2482
+
2483
+ // Hide some keys by default
2484
+ unset(
2485
+ $logRowKeysToShow['occasionsID'],
2486
+ $logRowKeysToShow['subsequentOccasions'],
2487
+ $logRowKeysToShow['rep'],
2488
+ $logRowKeysToShow['repeated'],
2489
+ $logRowKeysToShow['occasionsIDType'],
2490
+ $logRowKeysToShow['context'],
2491
+ $logRowKeysToShow['type']
2492
+ );
2493
+
2494
+ foreach ($oneLogRow as $rowKey => $rowVal) {
2495
+ // Only columns from oneLogRow that exist in logRowKeysToShow will be outputed
2496
+ if (! array_key_exists($rowKey, $logRowKeysToShow) || ! $logRowKeysToShow[ $rowKey ]) {
2497
+ continue;
2498
+ }
2499
+
2500
+ // skip arrays and objects and such
2501
+ if (is_array($rowVal) || is_object($rowVal)) {
2502
+ continue;
2503
+ }
2504
+
2505
+ $more_details_html .= sprintf(
2506
+ '<tr>
2507
+ <td>%1$s</td>
2508
+ <td>%2$s</td>
2509
+ </tr>',
2510
+ esc_html($rowKey),
2511
+ esc_html($rowVal)
2512
+ );
2513
+ }
2514
+
2515
+ $logRowContextKeysToShow = array_fill_keys(array_keys((array) $oneLogRow->context), true);
2516
+
2517
+ /**
2518
+ * Filter what keys to show from the row context
2519
+ *
2520
+ * Array is in format
2521
+ *
2522
+ * Array
2523
+ * (
2524
+ * [plugin_slug] => 1
2525
+ * [plugin_name] => 1
2526
+ * [plugin_title] => 1
2527
+ * [plugin_description] => 1
2528
+ * [plugin_author] => 1
2529
+ * [plugin_version] => 1
2530
+ * ...
2531
+ * )
2532
+ *
2533
+ * @since 2.0.29
2534
+ *
2535
+ * @param array with keys to show. key to show = key. value = boolean to show or not.
2536
+ * @param object log row to show details from
2537
+ */
2538
+ $logRowContextKeysToShow = apply_filters(
2539
+ 'simple_history/log_html_output_details_table/context_keys_to_show',
2540
+ $logRowContextKeysToShow,
2541
+ $oneLogRow
2542
+ );
2543
+
2544
+ foreach ($oneLogRow->context as $contextKey => $contextVal) {
2545
+ // Only columns from context that exist in logRowContextKeysToShow will be outputed
2546
+ if (!array_key_exists($contextKey, $logRowContextKeysToShow) ||
2547
+ !$logRowContextKeysToShow[$contextKey]
2548
+ ) {
2549
+ continue;
2550
+ }
2551
+
2552
+ $more_details_html .= sprintf(
2553
+ '<tr>
2554
+ <td>%1$s</td>
2555
+ <td>%2$s</td>
2556
+ </tr>',
2557
+ esc_html($contextKey),
2558
+ esc_html($contextVal)
2559
+ );
2560
+ }
2561
+
2562
+ $more_details_html .= '</table>';
2563
+
2564
+ $more_details_html = apply_filters(
2565
+ 'simple_history/log_html_output_details_single/html_after_context_table',
2566
+ $more_details_html,
2567
+ $oneLogRow
2568
+ );
2569
+
2570
+ $more_details_html = sprintf(
2571
+ '<div class="SimpleHistoryLogitem__moreDetails">%1$s</div>',
2572
+ $more_details_html
2573
+ );
2574
+ }// End if().
2575
+
2576
+ // Classes to add to log item li element
2577
+ $classes = array(
2578
+ 'SimpleHistoryLogitem',
2579
+ "SimpleHistoryLogitem--loglevel-{$oneLogRow->level}",
2580
+ "SimpleHistoryLogitem--logger-{$oneLogRow->logger}"
2581
+ );
2582
+
2583
+ if (isset($oneLogRow->initiator) && ! empty($oneLogRow->initiator)) {
2584
+ $classes[] = 'SimpleHistoryLogitem--initiator-' . $oneLogRow->initiator;
2585
+ }
2586
+
2587
+ if ($arr_found_additional_ip_headers) {
2588
+ $classes[] = 'SimpleHistoryLogitem--IPAddress-multiple';
2589
+ }
2590
+
2591
+ // Always append the log level tag
2592
+ $log_level_tag_html = sprintf(
2593
+ ' <span class="SimpleHistoryLogitem--logleveltag SimpleHistoryLogitem--logleveltag-%1$s">%2$s</span>',
2594
+ $oneLogRow->level,
2595
+ $this->getLogLevelTranslated($oneLogRow->level)
2596
+ );
2597
+
2598
+ $plain_text_html .= $log_level_tag_html;
2599
+
2600
+ /**
2601
+ * Filter to modify classes added to item li element
2602
+ *
2603
+ * @since 2.0.7
2604
+ *
2605
+ * @param $classes Array with classes
2606
+ */
2607
+ $classes = apply_filters('simple_history/logrowhtmloutput/classes', $classes);
2608
+
2609
+ // Generate the HTML output for a row
2610
+ $output = sprintf(
2611
+ '
2612
+ <li %8$s class="%10$s">
2613
+ <div class="SimpleHistoryLogitem__firstcol">
2614
+ <div class="SimpleHistoryLogitem__senderImage">%3$s</div>
2615
+ </div>
2616
+ <div class="SimpleHistoryLogitem__secondcol">
2617
+ <div class="SimpleHistoryLogitem__header">%1$s</div>
2618
+ <div class="SimpleHistoryLogitem__text">%2$s</div>
2619
+ %6$s <!-- details_html -->
2620
+ %9$s <!-- more details html -->
2621
+ %4$s <!-- occasions -->
2622
+ </div>
2623
+ </li>
2624
+ ',
2625
+ $header_html, // 1
2626
+ $plain_text_html, // 2
2627
+ $sender_image_html, // 3
2628
+ $occasions_html, // 4
2629
+ $oneLogRow->level, // 5
2630
+ $details_html, // 6
2631
+ $oneLogRow->logger, // 7
2632
+ $data_attrs, // 8 data attributes
2633
+ $more_details_html, // 9
2634
+ esc_attr(join(' ', $classes)) // 10
2635
+ );
2636
+
2637
+ // Get the main message row.
2638
+ // Should be as plain as possible, like plain text
2639
+ // but with links to for example users and posts
2640
+ // SimpleLoggerFormatter::getRowTextOutput($oneLogRow);
2641
+ // Get detailed HTML-based output
2642
+ // May include images, lists, any cool stuff needed to view
2643
+ // SimpleLoggerFormatter::getRowHTMLOutput($oneLogRow);
2644
+ return trim($output);
2645
+ }
2646
+
2647
+ /**
2648
+ * Return translated loglevel
2649
+ *
2650
+ * @since 2.0.14
2651
+ * @param string $loglevel
2652
+ * @return string translated loglevel
2653
+ */
2654
+ public function getLogLevelTranslated($loglevel)
2655
+ {
2656
+ $str_translated = '';
2657
+
2658
+ switch ($loglevel) {
2659
+ // Lowercase
2660
+ case 'emergency':
2661
+ $str_translated = _x('emergency', 'Log level in gui', 'simple-history');
2662
+ break;
2663
+
2664
+ case 'alert':
2665
+ $str_translated = _x('alert', 'Log level in gui', 'simple-history');
2666
+ break;
2667
+
2668
+ case 'critical':
2669
+ $str_translated = _x('critical', 'Log level in gui', 'simple-history');
2670
+ break;
2671
+
2672
+ case 'error':
2673
+ $str_translated = _x('error', 'Log level in gui', 'simple-history');
2674
+ break;
2675
+
2676
+ case 'warning':
2677
+ $str_translated = _x('warning', 'Log level in gui', 'simple-history');
2678
+ break;
2679
+
2680
+ case 'notice':
2681
+ $str_translated = _x('notice', 'Log level in gui', 'simple-history');
2682
+ break;
2683
+
2684
+ case 'info':
2685
+ $str_translated = _x('info', 'Log level in gui', 'simple-history');
2686
+ break;
2687
+
2688
+ case 'debug':
2689
+ $str_translated = _x('debug', 'Log level in gui', 'simple-history');
2690
+ break;
2691
+
2692
+ // Uppercase
2693
+ case 'Emergency':
2694
+ $str_translated = _x('Emergency', 'Log level in gui', 'simple-history');
2695
+ break;
2696
+
2697
+ case 'Alert':
2698
+ $str_translated = _x('Alert', 'Log level in gui', 'simple-history');
2699
+ break;
2700
+
2701
+ case 'Critical':
2702
+ $str_translated = _x('Critical', 'Log level in gui', 'simple-history');
2703
+ break;
2704
+
2705
+ case 'Error':
2706
+ $str_translated = _x('Error', 'Log level in gui', 'simple-history');
2707
+ break;
2708
+
2709
+ case 'Warning':
2710
+ $str_translated = _x('Warning', 'Log level in gui', 'simple-history');
2711
+ break;
2712
+
2713
+ case 'Notice':
2714
+ $str_translated = _x('Notice', 'Log level in gui', 'simple-history');
2715
+ break;
2716
+
2717
+ case 'Info':
2718
+ $str_translated = _x('Info', 'Log level in gui', 'simple-history');
2719
+ break;
2720
+
2721
+ case 'Debug':
2722
+ $str_translated = _x('Debug', 'Log level in gui', 'simple-history');
2723
+ break;
2724
+
2725
+ default:
2726
+ $str_translated = $loglevel;
2727
+ }// End switch().
2728
+
2729
+ return $str_translated;
2730
+ }
2731
+
2732
+ public function getInstantiatedLoggers()
2733
+ {
2734
+ return $this->instantiatedLoggers;
2735
+ }
2736
+
2737
+ public function getInstantiatedDropins()
2738
+ {
2739
+ return $this->instantiatedDropins;
2740
+ }
2741
+
2742
+ /**
2743
+ * @param string $slug
2744
+ * @return mixed logger instance if found, bool false if logger not found
2745
+ */
2746
+ public function getInstantiatedLoggerBySlug($slug = '')
2747
+ {
2748
+ if (empty($slug)) {
2749
+ return false;
2750
+ }
2751
+
2752
+ foreach ($this->getInstantiatedLoggers() as $one_logger) {
2753
+ if ($slug == $one_logger['instance']->slug) {
2754
+ return $one_logger['instance'];
2755
+ }
2756
+ }
2757
+
2758
+ return false;
2759
+ }
2760
+
2761
+ /**
2762
+ * Check which loggers a user has the right to read and return an array
2763
+ * with all loggers they are allowed to read
2764
+ *
2765
+ * @param int $user_id Id of user to get loggers for. Defaults to current user id.
2766
+ * @param string $format format to return loggers in. Default is array. Can also be "sql"
2767
+ * @return array
2768
+ */
2769
+ public function getLoggersThatUserCanRead($user_id = '', $format = 'array')
2770
+ {
2771
+ $arr_loggers_user_can_view = array();
2772
+
2773
+ if (! is_numeric($user_id)) {
2774
+ $user_id = get_current_user_id();
2775
+ }
2776
+
2777
+ $loggers = $this->getInstantiatedLoggers();
2778
+ foreach ($loggers as $one_logger) {
2779
+ $logger_capability = $one_logger['instance']->getCapability();
2780
+
2781
+ // $arr_loggers_user_can_view = apply_filters("simple_history/loggers_user_can_read", $user_id, $arr_loggers_user_can_view);
2782
+ $user_can_read_logger = user_can($user_id, $logger_capability);
2783
+ $user_can_read_logger = apply_filters(
2784
+ 'simple_history/loggers_user_can_read/can_read_single_logger',
2785
+ $user_can_read_logger,
2786
+ $one_logger['instance'],
2787
+ $user_id
2788
+ );
2789
+
2790
+ if ($user_can_read_logger) {
2791
+ $arr_loggers_user_can_view[] = $one_logger;
2792
+ }
2793
+ }
2794
+
2795
+ /**
2796
+ * Fires before Simple History does it's init stuff
2797
+ *
2798
+ * @since 2.0
2799
+ *
2800
+ * @param array $arr_loggers_user_can_view Array with loggers that user $user_id can read
2801
+ * @param int user_id ID of user to check read capability for
2802
+ */
2803
+ $arr_loggers_user_can_view = apply_filters(
2804
+ 'simple_history/loggers_user_can_read',
2805
+ $arr_loggers_user_can_view,
2806
+ $user_id
2807
+ );
2808
+
2809
+ // just return array with slugs in parenthesis suitable for sql-where
2810
+ if ('sql' == $format) {
2811
+ $str_return = '(';
2812
+
2813
+ if (sizeof($arr_loggers_user_can_view)) {
2814
+ foreach ($arr_loggers_user_can_view as $one_logger) {
2815
+ $str_return .= sprintf('"%1$s", ', esc_sql($one_logger['instance']->slug));
2816
+ }
2817
+
2818
+ $str_return = rtrim($str_return, ' ,');
2819
+ } else {
2820
+ // user was not allowed to read any loggers, return in (NULL) to return nothing
2821
+ $str_return .= 'NULL';
2822
+ }
2823
+
2824
+ $str_return .= ')';
2825
+
2826
+ return $str_return;
2827
+ }
2828
+
2829
+ return $arr_loggers_user_can_view;
2830
+ }
2831
+
2832
+ /**
2833
+ * Retrieve the avatar for a user who provided a user ID or email address.
2834
+ * A modified version of the function that comes with WordPress, but we
2835
+ * want to allow/show gravatars even if they are disabled in discussion settings
2836
+ *
2837
+ * @since 2.0
2838
+ *
2839
+ * @param string $email email address
2840
+ * @param int $size Size of the avatar image
2841
+ * @param string $default URL to a default image to use if no avatar is available
2842
+ * @param string $alt Alternative text to use in image tag. Defaults to blank
2843
+ * @return string <img> tag for the user's avatar
2844
+ */
2845
+ public function get_avatar($email, $size = '96', $default = '', $alt = false)
2846
+ {
2847
+ // WP setting for avatars is to show, so just use the built in function
2848
+ if (get_option('show_avatars')) {
2849
+ $avatar = get_avatar($email, $size, $default, $alt);
2850
+
2851
+ return $avatar;
2852
+ } else {
2853
+ // WP setting for avatar was to not show, but we do it anyway, using the same code as get_avatar() would have used
2854
+ if (false === $alt) {
2855
+ $safe_alt = '';
2856
+ } else {
2857
+ $safe_alt = esc_attr($alt);
2858
+ }
2859
+
2860
+ if (! is_numeric($size)) {
2861
+ $size = '96';
2862
+ }
2863
+
2864
+ if (empty($default)) {
2865
+ $avatar_default = get_option('avatar_default');
2866
+ if (empty($avatar_default)) {
2867
+ $default = 'mystery';
2868
+ } else {
2869
+ $default = $avatar_default;
2870
+ }
2871
+ }
2872
+
2873
+ if (! empty($email)) {
2874
+ $email_hash = md5(strtolower(trim($email)));
2875
+ }
2876
+
2877
+ if (is_ssl()) {
2878
+ $host = 'https://secure.gravatar.com';
2879
+ } else {
2880
+ if (! empty($email)) {
2881
+ $host = sprintf('http://%d.gravatar.com', hexdec($email_hash[0]) % 2);
2882
+ } else {
2883
+ $host = 'http://0.gravatar.com';
2884
+ }
2885
+ }
2886
+
2887
+ if ('mystery' == $default) {
2888
+ $default = "$host/avatar/ad516503a11cd5ca435acc9bb6523536?s={$size}";
2889
+ } elseif ('blank' == $default) {
2890
+ $default = $email ? 'blank' : includes_url('images/blank.gif');
2891
+ } elseif (! empty($email) && 'gravatar_default' == $default) {
2892
+ $default = '';
2893
+ } elseif ('gravatar_default' == $default) {
2894
+ $default = "$host/avatar/?s={$size}";
2895
+ } elseif (empty($email)) {
2896
+ $default = "$host/avatar/?d=$default&amp;s={$size}";
2897
+ } elseif (strpos($default, 'http://') === 0) {
2898
+ $default = add_query_arg('s', $size, $default);
2899
+ }
2900
+
2901
+ if (! empty($email)) {
2902
+ $out = "$host/avatar/";
2903
+ $out .= $email_hash;
2904
+ $out .= '?s=' . $size;
2905
+ $out .= '&amp;d=' . urlencode($default);
2906
+
2907
+ $rating = get_option('avatar_rating');
2908
+ if (! empty($rating)) {
2909
+ $out .= "&amp;r={$rating}";
2910
+ }
2911
+
2912
+ $out = str_replace('&#038;', '&amp;', esc_url($out));
2913
+ $avatar = "<img alt='{$safe_alt}' src='{$out}' class='avatar avatar-{$size} photo' height='{$size}' width='{$size}' />";
2914
+ } else {
2915
+ $out = esc_url($default);
2916
+ $avatar = "<img alt='{$safe_alt}' src='{$out}' class='avatar avatar-{$size} photo avatar-default' height='{$size}' width='{$size}' />";
2917
+ }
2918
+
2919
+ /**
2920
+ * Filter the avatar to retrieve.
2921
+ * Same filter WordPress uses
2922
+ *
2923
+ * @since 2.0.19
2924
+ *
2925
+ * @param string $avatar Image tag for the user's avatar.
2926
+ * @param int|object|string $id_or_email A user ID, email address, or comment object.
2927
+ * @param int $size Square avatar width and height in pixels to retrieve.
2928
+ * @param string $alt Alternative text to use in the avatar image tag.
2929
+ * Default empty.
2930
+ */
2931
+ $avatar = apply_filters('get_avatar', $avatar, $email, $size, $default, $alt);
2932
+
2933
+ return $avatar;
2934
+ } // End if().
2935
+ }
2936
+
2937
+ /**
2938
+ * Quick stats above the log
2939
+ * Uses filter "simple_history/history_page/before_gui" to output its contents
2940
+ */
2941
+ public function output_quick_stats()
2942
+ {
2943
+ global $wpdb;
2944
+
2945
+ // Get number of events today
2946
+ $logQuery = new SimpleHistoryLogQuery();
2947
+ $logResults = $logQuery->query(array(
2948
+ 'posts_per_page' => 1,
2949
+ 'date_from' => strtotime('today')
2950
+ ));
2951
+
2952
+ $total_row_count = (int) $logResults['total_row_count'];
2953
+
2954
+ // Get sql query for where to read only loggers current user is allowed to read/view
2955
+ $sql_loggers_in = $this->getLoggersThatUserCanRead(get_current_user_id(), 'sql');
2956
+
2957
+ // Get number of users today, i.e. events with wp_user as initiator
2958
+ $sql_users_today = sprintf(
2959
+ '
2960
+ SELECT
2961
+ DISTINCT(c.value) AS user_id
2962
+ #h.id, h.logger, h.level, h.initiator, h.date
2963
+ FROM %3$s AS h
2964
+ INNER JOIN %4$s AS c
2965
+ ON c.history_id = h.id AND c.key = "_user_id"
2966
+ WHERE
2967
+ initiator = "wp_user"
2968
+ AND logger IN %1$s
2969
+ AND date > "%2$s"
2970
+ ',
2971
+ $sql_loggers_in,
2972
+ date('Y-m-d H:i', strtotime('today')),
2973
+ $wpdb->prefix . SimpleHistory::DBTABLE,
2974
+ $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS
2975
+ );
2976
+
2977
+ $cache_key = 'quick_stats_users_today_' . md5(serialize($sql_loggers_in));
2978
+ $cache_group = 'simple-history-' . $this->get_cache_incrementor();
2979
+ $results_users_today = wp_cache_get($cache_key, $cache_group);
2980
+
2981
+ if (false === $results_users_today) {
2982
+ $results_users_today = $wpdb->get_results($sql_users_today);
2983
+ wp_cache_set($cache_key, $results_users_today, $cache_group);
2984
+ }
2985
+
2986
+ $count_users_today = sizeof($results_users_today);
2987
+
2988
+ // Get number of other sources (not wp_user)
2989
+ $sql_other_sources_where = sprintf(
2990
+ '
2991
+ initiator <> "wp_user"
2992
+ AND logger IN %1$s
2993
+ AND date > "%2$s"
2994
+ ',
2995
+ $sql_loggers_in,
2996
+ date('Y-m-d H:i', strtotime('today')),
2997
+ $wpdb->prefix . SimpleHistory::DBTABLE,
2998
+ $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS
2999
+ );
3000
+
3001
+ $sql_other_sources_where = apply_filters('simple_history/quick_stats_where', $sql_other_sources_where);
3002
+
3003
+ $sql_other_sources = sprintf(
3004
+ '
3005
+ SELECT
3006
+ DISTINCT(h.initiator) AS initiator
3007
+ FROM %3$s AS h
3008
+ WHERE
3009
+ %5$s
3010
+ ',
3011
+ $sql_loggers_in,
3012
+ date('Y-m-d H:i', strtotime('today')),
3013
+ $wpdb->prefix . SimpleHistory::DBTABLE,
3014
+ $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS,
3015
+ $sql_other_sources_where // 5
3016
+ );
3017
+ // sf_d($sql_other_sources, '$sql_other_sources');
3018
+ $cache_key = 'quick_stats_results_other_sources_today_' . md5(serialize($sql_other_sources));
3019
+ $results_other_sources_today = wp_cache_get($cache_key, $cache_group);
3020
+
3021
+ if (false === $results_other_sources_today) {
3022
+ $results_other_sources_today = $wpdb->get_results($sql_other_sources);
3023
+ wp_cache_set($cache_key, $results_other_sources_today, $cache_group);
3024
+ }
3025
+
3026
+ $count_other_sources = sizeof($results_other_sources_today);
3027
+
3028
+ // sf_d($logResults, '$logResults');
3029
+ // sf_d($results_users_today, '$sql_users_today');
3030
+ // sf_d($results_other_sources_today, '$results_other_sources_today');
3031
+ ?>
3032
+ <div class="SimpleHistoryQuickStats">
3033
+ <p>
3034
+ <?php
3035
+ $msg_tmpl = '';
3036
+
3037
+ // No results today at all
3038
+ if ($total_row_count == 0) {
3039
+ $msg_tmpl = __('No events today so far.', 'simple-history');
3040
+ } else {
3041
+ /*
3042
+ Type of results
3043
+ x1 event today from 1 user.
3044
+ x1 event today from 1 source.
3045
+ 3 events today from 1 user.
3046
+ x2 events today from 2 users.
3047
+ x2 events today from 1 user and 1 other source.
3048
+ x3 events today from 2 users and 1 other source.
3049
+ x3 events today from 1 user and 2 other sources.
3050
+ x4 events today from 2 users and 2 other sources.
3051
+ */
3052
+
3053
+ // A single event existed and was from a user
3054
+ // 1 event today from 1 user.
3055
+ if ($total_row_count == 1 && $count_users_today == 1) {
3056
+ $msg_tmpl .= __('One event today from one user.', 'simple-history');
3057
+ }
3058
+
3059
+ // A single event existed and was from another source
3060
+ // 1 event today from 1 source.
3061
+ if ($total_row_count == 1 && ! $count_users_today) {
3062
+ $msg_tmpl .= __('One event today from one source.', 'simple-history');
3063
+ }
3064
+
3065
+ // Multiple events from a single user
3066
+ // 3 events today from one user.
3067
+ if ($total_row_count > 1 && $count_users_today == 1 && ! $count_other_sources) {
3068
+ $msg_tmpl .= __('%1$d events today from one user.', 'simple-history');
3069
+ }
3070
+
3071
+ // Multiple events from only users
3072
+ // 2 events today from 2 users.
3073
+ if ($total_row_count > 1 && $count_users_today == $total_row_count) {
3074
+ $msg_tmpl .= __('%1$d events today from %2$d users.', 'simple-history');
3075
+ }
3076
+
3077
+ // Multiple events from 1 single user and 1 single other source
3078
+ // 2 events today from 1 user and 1 other source.
3079
+ if ($total_row_count && 1 == $count_users_today && 1 == $count_other_sources) {
3080
+ $msg_tmpl .= __('%1$d events today from one user and one other source.', 'simple-history');
3081
+ }
3082
+
3083
+ // Multiple events from multple users but from only 1 single other source
3084
+ // 3 events today from 2 users and 1 other source.
3085
+ if ($total_row_count > 1 && $count_users_today > 1 && $count_other_sources == 1) {
3086
+ $msg_tmpl .= __('%1$d events today from one user and one other source.', 'simple-history');
3087
+ }
3088
+
3089
+ // Multiple events from 1 user but from multiple other source
3090
+ // 3 events today from 1 user and 2 other sources.
3091
+ if ($total_row_count > 1 && 1 == $count_users_today && $count_other_sources > 1) {
3092
+ $msg_tmpl .= __('%1$d events today from one user and %3$d other sources.', 'simple-history');
3093
+ }
3094
+
3095
+ // Multiple events from multiple user and from multiple other sources
3096
+ // 4 events today from 2 users and 2 other sources.
3097
+ if ($total_row_count > 1 && $count_users_today > 1 && $count_other_sources > 1) {
3098
+ $msg_tmpl .= __('%1$s events today from %2$d users and %3$d other sources.', 'simple-history');
3099
+ }
3100
+ }// End if().
3101
+
3102
+ // only show stats if we have something to output
3103
+ if ($msg_tmpl) {
3104
+ printf(
3105
+ $msg_tmpl,
3106
+ $logResults['total_row_count'], // 1
3107
+ $count_users_today, // 2
3108
+ $count_other_sources // 3
3109
+ );
3110
+
3111
+ // Space between texts
3112
+ /*
3113
+ echo " ";
3114
+
3115
+ // http://playground-root.ep/wp-admin/options-general.php?page=simple_history_settings_menu_slug&selected-tab=stats
3116
+ printf(
3117
+ '<a href="%1$s">View more stats</a>.',
3118
+ add_query_arg("selected-tab", "stats", menu_page_url(SimpleHistory::SETTINGS_MENU_SLUG, 0))
3119
+ );
3120
+ */
3121
+ }?>
3122
+ </p>
3123
+ </div>
3124
+ <?php
3125
+ } // output_quick_stats
3126
+
3127
+ /**
3128
+ * https://www.tollmanz.com/invalidation-schemes/
3129
+ *
3130
+ * @param $refresh bool
3131
+ * @return string
3132
+ */
3133
+ public static function get_cache_incrementor($refresh = false)
3134
+ {
3135
+ $incrementor_key = 'simple_history_incrementor';
3136
+ $incrementor_value = wp_cache_get($incrementor_key);
3137
+
3138
+ if (false === $incrementor_value || true === $refresh) {
3139
+ $incrementor_value = time();
3140
+ wp_cache_set($incrementor_key, $incrementor_value);
3141
+ }
3142
+
3143
+ // echo "<br>incrementor_value: $incrementor_value";
3144
+ return $incrementor_value;
3145
+ }
3146
+
3147
+ // Number of rows the last n days
3148
+ public function get_num_events_last_n_days($period_days = 28)
3149
+ {
3150
+ $transient_key = 'sh_' . md5(__METHOD__ . $period_days . '_2');
3151
+
3152
+ $count = get_transient($transient_key);
3153
+
3154
+ if (false === $count) {
3155
+ global $wpdb;
3156
+
3157
+ $sqlStringLoggersUserCanRead = $this->getLoggersThatUserCanRead(null, 'sql');
3158
+
3159
+ $sql = sprintf(
3160
+ '
3161
+ SELECT count(*)
3162
+ FROM %1$s
3163
+ WHERE UNIX_TIMESTAMP(date) >= %2$d
3164
+ AND logger IN %3$s
3165
+ ',
3166
+ $wpdb->prefix . SimpleHistory::DBTABLE,
3167
+ strtotime("-$period_days days"),
3168
+ $sqlStringLoggersUserCanRead
3169
+ );
3170
+
3171
+ $count = $wpdb->get_var($sql);
3172
+
3173
+ set_transient($transient_key, $count, HOUR_IN_SECONDS);
3174
+ }
3175
+
3176
+ return $count;
3177
+ } // get_num_events_last_n_days
3178
+
3179
+ public function get_num_events_per_day_last_n_days($period_days = 28)
3180
+ {
3181
+ $transient_key = 'sh_' . md5(__METHOD__ . $period_days . '_2');
3182
+
3183
+ $dates = get_transient($transient_key);
3184
+
3185
+ if (false === $dates) {
3186
+ global $wpdb;
3187
+
3188
+ $sqlStringLoggersUserCanRead = $this->getLoggersThatUserCanRead(null, 'sql');
3189
+
3190
+ $sql = sprintf(
3191
+ '
3192
+ SELECT
3193
+ date_format(date, "%%Y-%%m-%%d") AS yearDate,
3194
+ count(date) AS count
3195
+ FROM
3196
+ %1$s
3197
+ WHERE
3198
+ UNIX_TIMESTAMP(date) >= %2$d
3199
+ AND logger IN (%3$d)
3200
+ GROUP BY yearDate
3201
+ ORDER BY yearDate ASC
3202
+ ',
3203
+ $wpdb->prefix . SimpleHistory::DBTABLE,
3204
+ strtotime("-$period_days days"),
3205
+ $sqlStringLoggersUserCanRead
3206
+ );
3207
+
3208
+ $dates = $wpdb->get_results($sql);
3209
+
3210
+ set_transient($transient_key, $dates, HOUR_IN_SECONDS);
3211
+ // echo "set";exit;
3212
+ } else {
3213
+ // echo "get";exit;
3214
+ }
3215
+
3216
+ return $dates;
3217
+ } // get_num_events_per_day_for_period
3218
+
3219
+ // Number of unique events the last n days
3220
+ public function get_unique_events_for_days($days = 7)
3221
+ {
3222
+ global $wpdb;
3223
+
3224
+ $days = (int) $days;
3225
+
3226
+ $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
3227
+
3228
+ $cache_key = 'sh_' . md5(__METHOD__ . $days);
3229
+
3230
+ $numEvents = get_transient($cache_key);
3231
+
3232
+ if (false == $numEvents) {
3233
+ $sql = $wpdb->prepare(
3234
+ "
3235
+ SELECT count( DISTINCT occasionsID )
3236
+ FROM $table_name
3237
+ WHERE date >= DATE_ADD(CURDATE(), INTERVAL -%d DAY)
3238
+ ",
3239
+ $days
3240
+ );
3241
+
3242
+ $numEvents = $wpdb->get_var($sql);
3243
+
3244
+ set_transient($cache_key, $numEvents, HOUR_IN_SECONDS);
3245
+ }
3246
+
3247
+ return $numEvents;
3248
+ } // get_unique_events_for_days
3249
+
3250
+ /**
3251
+ * Output an admin notice about logger slug being to long
3252
+ */
3253
+ public function admin_notice_logger_slug_to_long()
3254
+ {
3255
+ ?>
3256
+ <div class="error notice">
3257
+ <p>
3258
+ <?php
3259
+ echo esc_html__(
3260
+ 'The slug for a logger in Simple History can be max 30 chars long.',
3261
+ 'simple-history'
3262
+ );
3263
+ ?></p>
3264
+ </div>
3265
+ <?php
3266
+ }
3267
+ } // class
inc/SimpleHistoryLogQuery.php CHANGED
@@ -1,133 +1,134 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /**
6
  * Queries the Simple History Log
7
  */
8
- class SimpleHistoryLogQuery {
 
9
 
10
- public function __construct() {
 
11
 
12
- /*
13
- if ( is_array($args) && ! empty($args) ) {
14
 
15
- return $this->query($args);
16
 
17
- }
18
- */
 
19
 
20
- }
 
 
21
 
22
- public function query( $args ) {
23
- $defaults = array(
24
 
25
- // overview | occasions
26
- 'type' => 'overview',
27
 
28
- // Number of posts to show per page. 0 to show all.
29
- 'posts_per_page' => 0,
30
 
31
- // Page to show. 1 = first page
32
- 'paged' => 1,
33
 
34
- // Array. Only get posts that are in array.
35
- 'post__in' => null,
36
 
37
- // array or html
38
- 'format' => 'array',
 
39
 
40
- // If max_id_first_page is set then only get rows
41
- // that have id equal or lower than this, to make
42
- 'max_id_first_page' => null,
43
 
44
- // if since_id is set the rows returned will only be rows with an ID greater than (i.e. more recent than) since_id
45
- 'since_id' => null,
 
 
46
 
47
- // date range
48
- // in unix datetime or Y-m-d H:i (or format compatible with strtotime())
49
- 'date_from' => null,
50
- 'date_to' => null,
51
 
52
- // months in format "Y-m"
53
- // array or comma separated
54
- 'months' => null,
 
55
 
56
- // dates in format
57
- // "month:2015-06" for june 2015
58
- // "lastdays:7" for the last 7 days
59
- 'dates' => null,
60
 
61
- // search
62
- 'search' => null,
63
 
64
- // log levels to include. comma separated or as array. defaults to all.
65
- 'loglevels' => null,
66
 
67
- // loggers to include. comma separated. defaults to all the user can read
68
- 'loggers' => null,
69
 
70
- 'messages' => null,
 
71
 
72
- // userID as number
73
- 'user' => null,
74
 
75
- // user ids, comma separated
76
- 'users' => null,
 
 
 
 
77
 
78
- // Can also contain:
79
- // occasionsCount
80
- // occasionsCountMaxReturn
81
- // occasionsID
82
- // If rows should be returned, or the actualy sql query used
83
- 'returnQuery' => false,
84
 
85
- );
 
 
 
 
86
 
87
- $args = wp_parse_args( $args, $defaults );
88
- // sf_d($args, "Run log query with args");
89
- $cache_key = 'SimpleHistoryLogQuery_' . md5( serialize( $args ) ) . '_get_' . md5( serialize( $_GET ) ) . '_userid_' . get_current_user_id();
90
- $cache_group = 'simple-history-' . SimpleHistory::get_cache_incrementor();
91
- $arr_return = wp_cache_get( $cache_key, $cache_group );
92
 
93
- if ( false !== $arr_return ) {
94
- return $arr_return;
95
- }
 
 
 
 
 
 
 
 
96
 
97
- /*
98
- Subequent occasions query thanks to this Stack Overflow thread:
99
- http://stackoverflow.com/questions/13566303/how-to-group-subsequent-rows-based-on-a-criteria-and-then-count-them-mysql/13567320#13567320
100
- Similar questions that I didn't manage to understand, work, or did try:
101
- - http://stackoverflow.com/questions/23651176/mysql-query-if-dates-are-subsequent
102
- - http://stackoverflow.com/questions/17651868/mysql-group-by-subsequent
103
- - http://stackoverflow.com/questions/4495242/mysql-number-of-subsequent-occurrences
104
- - http://stackoverflow.com/questions/20446242/postgresql-group-subsequent-rows
105
- - http://stackoverflow.com/questions/17061156/mysql-group-by-range
106
- - http://stackoverflow.com/questions/6602006/complicated-query-with-group-by-and-range-of-prices-in-mysql
107
- */
108
 
109
- global $wpdb;
 
110
 
111
- $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
112
- $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
113
-
114
- $where = '1 = 1';
115
- $limit = '';
116
- $inner_where = '1 = 1';
117
-
118
- if ( 'overview' === $args['type'] || 'single' === $args['type'] ) {
119
-
120
- // Set variables used by query
121
- $sql_set_var = "SET @a:='', @counter:=1, @groupby:=0";
122
- $wpdb->query( $sql_set_var );
123
-
124
- // New and slightly faster query
125
- // 1 = where
126
- // 2 = limit
127
- // 3 = db name
128
- // 4 = where for inner calc sql query thingie
129
- // 5 = db name contexts
130
- $sql_tmpl = '
131
  /*NO_SELECT_FOUND_ROWS*/
132
  SELECT
133
  SQL_CALC_FOUND_ROWS
@@ -173,20 +174,18 @@ class SimpleHistoryLogQuery {
173
  %2$s
174
  ';
175
 
176
- $sh = SimpleHistory::get_instance();
177
-
178
- // Only include loggers that the current user can view
179
- // @TODO: this causes error if user has no access to any logger at all
180
- $sql_loggers_user_can_view = $sh->getLoggersThatUserCanRead( get_current_user_id(), 'sql' );
181
- $inner_where .= " AND logger IN {$sql_loggers_user_can_view}";
182
-
183
- } elseif ( 'occasions' === $args['type'] ) {
184
-
185
- // Query template
186
- // 1 = where
187
- // 2 = limit
188
- // 3 = db name
189
- $sql_tmpl = '
190
  SELECT h.*,
191
  # fake columns that exist in overview query
192
  1 as subsequentOccasions
@@ -196,167 +195,147 @@ class SimpleHistoryLogQuery {
196
  %2$s
197
  ';
198
 
199
- $where .= ' AND h.id < ' . (int) $args['logRowID'];
200
- $where .= " AND h.occasionsID = '" . esc_sql( $args['occasionsID'] ) . "'";
201
-
202
- if ( isset( $args['occasionsCountMaxReturn'] ) && (int) $args['occasionsCountMaxReturn'] < (int) $args['occasionsCount'] ) {
203
-
204
- // Limit to max nn events if occasionsCountMaxReturn is set.
205
- // Used in gui to prevent top many events returned, that can stall the browser.
206
- $limit = 'LIMIT ' . (int) $args['occasionsCountMaxReturn'];
207
-
208
- } else {
209
-
210
- // Regular limit that gets all occasions
211
- $limit = 'LIMIT ' . (int) $args['occasionsCount'];
212
-
213
- }
214
- }// End if().
215
-
216
- // Determine limit
217
- // Both posts_per_page and paged must be set
218
- $is_limit_query = ( is_numeric( $args['posts_per_page'] ) && $args['posts_per_page'] > 0 );
219
- $is_limit_query = $is_limit_query && ( is_numeric( $args['paged'] ) && $args['paged'] > 0 );
220
- if ( $is_limit_query ) {
221
- $limit_offset = ($args['paged'] - 1) * $args['posts_per_page'];
222
- $limit .= sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] );
223
- }
224
-
225
- // Determine where
226
- if ( $args['post__in'] && is_array( $args['post__in'] ) ) {
227
-
228
- // make sure all vals are integers
229
- $args['post__in'] = array_map( 'intval', $args['post__in'] );
230
-
231
- $inner_where .= sprintf( ' AND id IN (%1$s)', implode( ',', $args['post__in'] ) );
232
-
233
- }
234
-
235
- // If max_id_first_page is then then only include rows
236
- // with id equal to or earlier
237
- if ( isset( $args['max_id_first_page'] ) && is_numeric( $args['max_id_first_page'] ) ) {
238
-
239
- $max_id_first_page = (int) $args['max_id_first_page'];
240
- $inner_where .= sprintf(
241
- ' AND id <= %1$d',
242
- $max_id_first_page
243
- );
244
-
245
- }
246
-
247
- if ( isset( $args['since_id'] ) && is_numeric( $args['since_id'] ) ) {
248
-
249
- $since_id = (int) $args['since_id'];
250
- /*
251
- $where .= sprintf(
252
- ' AND t.id > %1$d',
253
- $since_id
254
- );
255
- */
256
- // Add where to inner because that's faster
257
- $inner_where .= sprintf(
258
- ' AND id > %1$d',
259
- $since_id
260
- );
261
-
262
- }
263
-
264
- // Append date where
265
- if ( ! empty( $args['date_from'] ) ) {
266
-
267
- // date_from=2014-08-01
268
- // if date is not numeric assume Y-m-d H:i-format
269
- $date_from = $args['date_from'];
270
- if ( ! is_numeric( $date_from ) ) {
271
- $date_from = strtotime( $date_from );
272
- }
273
-
274
- $inner_where .= "\n" . sprintf( ' AND date >= "%1$s"', esc_sql( date( 'Y-m-d H:i:s', $date_from ) ) );
275
-
276
- }
277
-
278
- if ( ! empty( $args['date_to'] ) ) {
279
-
280
- // date_to=2014-08-01
281
- // if date is not numeric assume Y-m-d H:i-format
282
- $date_to = $args['date_to'];
283
- if ( ! is_numeric( $date_to ) ) {
284
- $date_to = strtotime( $date_to );
285
- }
286
-
287
- $inner_where .= "\n" . sprintf( ' AND date <= "%1$s"', date( 'Y-m-d H:i:s', $date_to ) );
288
-
289
- }
290
-
291
- /*
292
- AND date >= "2015-01-01 00:00:00" AND date <= "2015-01-31 00:00:00"
293
- */
294
- // echo $inner_where;exit;
295
- // dats
296
- // if months they translate to $args["months"] because we already have support for that
297
- // can't use months and dates and the same time
298
- if ( ! empty( $args['dates'] ) ) {
299
-
300
- if ( is_array( $args['dates'] ) ) {
301
- $arr_dates = $args['dates'];
302
- } else {
303
- $arr_dates = explode( ',', $args['dates'] );
304
- }
305
-
306
- $args['months'] = array();
307
- $args['lastdays'] = 0;
308
-
309
- foreach ( $arr_dates as $one_date ) {
310
-
311
- // If begins with "month:" then strip string and keep only month numbers
312
- if ( strpos( $one_date, 'month:' ) === 0 ) {
313
- $args['months'][] = substr( $one_date, strlen( 'month:' ) );
314
- } elseif ( strpos( $one_date, 'lastdays:' ) === 0 ) {
315
- // Only keep largest lastdays value
316
- $args['lastdays'] = max( $args['lastdays'], substr( $one_date, strlen( 'lastdays:' ) ) );
317
- // $args["lastdays"][] = substr($one_date, strlen("lastdays:"));
318
- }
319
- }
320
- }
321
-
322
- // lastdays, as int
323
- if ( ! empty( $args['lastdays'] ) ) {
324
-
325
- $inner_where .= sprintf('
326
  # lastdays
327
  AND date >= DATE(NOW()) - INTERVAL %d DAY
328
  ', $args['lastdays']);
 
329
 
330
- }
331
-
332
- // months, in format "Y-m"
333
- if ( ! empty( $args['months'] ) ) {
 
 
 
334
 
335
- if ( is_array( $args['months'] ) ) {
336
- $arr_months = $args['months'];
337
- } else {
338
- $arr_months = explode( ',', $args['months'] );
339
- }
340
-
341
- $sql_months = '
342
  # sql_months
343
  AND (
344
  ';
345
 
346
- foreach ( $arr_months as $one_month ) {
347
-
348
- // beginning of month
349
- // $ php -r ' echo date("Y-m-d H:i", strtotime("2014-08") ) . "\n";
350
- // >> 2014-08-01 00:00
351
- $date_month_beginning = strtotime( $one_month );
352
 
353
- // end of month
354
- // $ php -r ' echo date("Y-m-d H:i", strtotime("2014-08 + 1 month") ) . "\n";'
355
- // >> 2014-09-01 00:00
356
- $date_month_end = strtotime( "{$one_month} + 1 month" );
357
 
358
- $sql_months .= sprintf(
359
- '
360
  (
361
  date >= "%1$s"
362
  AND date <= "%2$s"
@@ -364,478 +343,446 @@ class SimpleHistoryLogQuery {
364
 
365
  OR
366
  ',
367
- date( 'Y-m-d H:i:s', $date_month_beginning ), // 1
368
- date( 'Y-m-d H:i:s', $date_month_end ) // 2
369
- );
370
-
371
- }
372
 
373
- $sql_months = trim( $sql_months );
374
- $sql_months = rtrim( $sql_months, ' OR ' );
375
 
376
- $sql_months .= '
377
  # end sql_months and wrap
378
  )
379
  ';
380
 
381
- $inner_where .= $sql_months;
382
- // echo $inner_where;exit;
383
- }// End if().
384
-
385
- // search
386
- if ( ! empty( $args['search'] ) ) {
387
-
388
- $search_words = $args['search'];
389
- $str_search_conditions = '';
390
- $arr_search_words = preg_split( '/[\s,]+/', $search_words );
391
-
392
- // create array of all searched words
393
- // split both spaces and commas and such
394
- $arr_sql_like_cols = array( 'message', 'logger', 'level' );
395
-
396
- foreach ( $arr_sql_like_cols as $one_col ) {
397
-
398
- $str_sql_search_words = '';
399
-
400
- foreach ( $arr_search_words as $one_search_word ) {
401
-
402
- if ( method_exists( $wpdb, 'esc_like' ) ) {
403
- $str_like = esc_sql( $wpdb->esc_like( $one_search_word ) );
404
- } else {
405
- $str_like = esc_sql( like_escape( $one_search_word ) );
406
- }
407
-
408
- $str_sql_search_words .= sprintf(
409
- ' AND %1$s LIKE "%2$s" ',
410
- $one_col,
411
- "%{$str_like}%"
412
- );
413
-
414
- }
415
-
416
- $str_sql_search_words = ltrim( $str_sql_search_words, ' AND ' );
417
-
418
- $str_search_conditions .= "\n" . sprintf(
419
- ' OR ( %1$s ) ',
420
- $str_sql_search_words
421
- );
422
-
423
- }
424
-
425
- $str_search_conditions = preg_replace( '/^OR /', ' ', trim( $str_search_conditions ) );
426
-
427
- // also search contexts
428
- $str_search_conditions .= "\n OR ( ";
429
- foreach ( $arr_search_words as $one_search_word ) {
430
-
431
- if ( method_exists( $wpdb, 'esc_like' ) ) {
432
- $str_like = esc_sql( $wpdb->esc_like( $one_search_word ) );
433
- } else {
434
- $str_like = esc_sql( like_escape( $one_search_word ) );
435
- }
436
-
437
- $str_search_conditions .= "\n" . sprintf(
438
- ' id IN ( SELECT history_id FROM %1$s AS c WHERE c.value LIKE "%2$s" ) AND ',
439
- $table_name_contexts, // 1
440
- '%' . $str_like . '%' // 2
441
- );
442
-
443
- }
444
- $str_search_conditions = preg_replace( '/ AND $/', '', $str_search_conditions );
445
-
446
- $str_search_conditions .= "\n ) "; // end or for contexts
447
-
448
- $inner_where .= "\n AND \n(\n {$str_search_conditions} \n ) ";
449
-
450
- // echo $inner_where;exit;
451
- }// End if().
452
-
453
- // log levels
454
- // comma separated
455
- // http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&type=overview&format=&posts_per_page=10&paged=1&max_id_first_page=27273&SimpleHistoryLogQuery-showDebug=0&loglevel=error,warn
456
- if ( ! empty( $args['loglevels'] ) ) {
457
-
458
- $sql_loglevels = '';
459
-
460
- if ( is_array( $args['loglevels'] ) ) {
461
- $arr_loglevels = $args['loglevels'];
462
- } else {
463
- $arr_loglevels = explode( ',', $args['loglevels'] );
464
- }
465
-
466
- foreach ( $arr_loglevels as $one_loglevel ) {
467
- $sql_loglevels .= sprintf( ' "%s", ', esc_sql( $one_loglevel ) );
468
- }
469
-
470
- if ( $sql_loglevels ) {
471
- $sql_loglevels = rtrim( $sql_loglevels, ' ,' );
472
- $sql_loglevels = "\n AND level IN ({$sql_loglevels}) ";
473
- }
474
-
475
- $inner_where .= $sql_loglevels;
476
-
477
- }
478
-
479
- // messages
480
- if ( ! empty( $args['messages'] ) ) {
481
-
482
- // print_r($args["messages"]);exit;
483
- /*
484
- Array
485
- (
486
- [0] => SimpleCommentsLogger:anon_comment_added,SimpleCommentsLogger:user_comment_added,SimpleCommentsLogger:anon_trackback_added,SimpleCommentsLogger:user_trackback_added,SimpleCommentsLogger:anon_pingback_added,SimpleCommentsLogger:user_pingback_added,SimpleCommentsLogger:comment_edited,SimpleCommentsLogger:trackback_edited,SimpleCommentsLogger:pingback_edited,SimpleCommentsLogger:comment_status_approve,SimpleCommentsLogger:trackback_status_approve,SimpleCommentsLogger:pingback_status_approve,SimpleCommentsLogger:comment_status_hold,SimpleCommentsLogger:trackback_status_hold,SimpleCommentsLogger:pingback_status_hold,SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam,SimpleCommentsLogger:comment_status_trash,SimpleCommentsLogger:trackback_status_trash,SimpleCommentsLogger:pingback_status_trash,SimpleCommentsLogger:comment_untrashed,SimpleCommentsLogger:trackback_untrashed,SimpleCommentsLogger:pingback_untrashed,SimpleCommentsLogger:comment_deleted,SimpleCommentsLogger:trackback_deleted,SimpleCommentsLogger:pingback_deleted
487
- [1] => SimpleCommentsLogger:SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam
488
- )
489
- */
490
-
491
- // Array with loggers and messages
492
- $arr_loggers_and_messages = array();
493
-
494
- // Tranform from get'et format to our own internal format
495
- foreach ( (array) $args['messages'] as $one_arr_messages_row ) {
496
-
497
- $arr_row_messages = explode( ',', $one_arr_messages_row );
498
- // print_r($arr_row_messages);#exit;
499
- /*
500
- Array
501
- (
502
- [0] => SimpleCommentsLogger:anon_comment_added
503
- [1] => SimpleCommentsLogger:user_comment_added
504
- [2] => SimpleCommentsLogger:anon_trackback_added
505
- */
506
- foreach ( $arr_row_messages as $one_row_logger_and_message ) {
507
-
508
- $arr_one_logger_and_message = explode( ':', $one_row_logger_and_message );
509
-
510
- if ( ! isset( $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ] ) ) {
511
- $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ] = array();
512
- }
513
-
514
- $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ][] = $arr_one_logger_and_message[1];
515
-
516
- }
517
- }
518
-
519
- // Now create sql where based on loggers and messages
520
- $sql_messages_where = ' AND (';
521
- // print_r($arr_loggers_and_messages);exit;
522
- foreach ( $arr_loggers_and_messages as $logger_slug => $logger_messages ) {
523
- $sql_messages_where .= sprintf(
524
- '
525
  (
526
  h.logger = "%1$s"
527
  AND c1.value IN (%2$s)
528
  )
529
  OR ',
530
- esc_sql( $logger_slug ),
531
- "'" . implode( "','", $logger_messages ) . "'"
532
- );
533
- }
534
- // remove last or
535
- $sql_messages_where = preg_replace( '/OR $/', '', $sql_messages_where );
536
-
537
- $sql_messages_where .= "\n )";
538
- // echo $sql_messages_where;exit;
539
- $where .= $sql_messages_where;
540
-
541
- /*
542
- print_r($arr_loggers_and_messages);exit;
543
- Array
544
- (
545
- [SimpleCommentsLogger] => Array
546
- (
547
- [0] => anon_comment_added
548
- [1] => user_comment_added
549
- [2] => anon_trackback_added
550
- [3] => user_trackback_added
551
- [4] => anon_pingback_added
552
- [5] => user_pingback_added
553
- [6] => comment_edited
554
- [7] => trackback_edited
555
- [8] => pingback_edited
556
- [9] => comment_status_approve
557
- [10] => trackback_status_approve
558
- [11] => pingback_status_approve
559
- [12] => comment_status_hold
560
- [13] => trackback_status_hold
561
- [14] => pingback_status_hold
562
- [15] => comment_status_spam
563
- [16] => trackback_status_spam
564
- [17] => pingback_status_spam
565
- [18] => comment_status_trash
566
- [19] => trackback_status_trash
567
- [20] => pingback_status_trash
568
- [21] => comment_untrashed
569
- [22] => trackback_untrashed
570
- [23] => pingback_untrashed
571
- [24] => comment_deleted
572
- [25] => trackback_deleted
573
- [26] => pingback_deleted
574
- )
575
-
576
- [SimpleUserLogger] => Array
577
- (
578
- [0] => SimpleUserLogger
579
- [1] => SimpleUserLogger
580
- )
581
-
582
- )
583
-
584
- */
585
-
586
- }// End if().
587
-
588
- // loggers
589
- // comma separated
590
- // http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&type=overview&format=&posts_per_page=10&paged=1&max_id_first_page=27273&SimpleHistoryLogQuery-showDebug=0&loggers=SimpleCommentsLogger,SimpleCoreUpdatesLogger
591
- if ( ! empty( $args['loggers'] ) ) {
592
-
593
- $sql_loggers = '';
594
- if ( is_array( $args['loggers'] ) ) {
595
- $arr_loggers = $args['loggers'];
596
- } else {
597
- $arr_loggers = explode( ',', $args['loggers'] );
598
- }
599
-
600
- // print_r($args["loggers"]);exit;
601
- // print_r($arr_loggers);exit;
602
- /*
603
- Example of version with logger + message keys
604
- Array
605
- (
606
- [0] => SimpleUserLogger:user_created
607
- [1] => SimpleUserLogger:user_deleted
608
- )
609
- */
610
-
611
- foreach ( $arr_loggers as $one_logger ) {
612
- $sql_loggers .= sprintf( ' "%s", ', esc_sql( $one_logger ) );
613
- }
614
-
615
- if ( $sql_loggers ) {
616
- $sql_loggers = rtrim( $sql_loggers, ' ,' );
617
- $sql_loggers = "\n AND logger IN ({$sql_loggers}) ";
618
- }
619
-
620
- $inner_where .= $sql_loggers;
621
-
622
- }
623
-
624
- // user, a single userID
625
- if ( ! empty( $args['user'] ) && is_numeric( $args['user'] ) ) {
626
-
627
- $userID = (int) $args['user'];
628
- $sql_user = sprintf(
629
- '
630
  AND id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value = %2$s )
631
  ',
632
- $table_name_contexts, // 1
633
- $userID // 2
634
- );
635
 
636
- $inner_where .= $sql_user;
 
637
 
638
- }
 
 
 
639
 
640
- // If users is array, make it comma separated.
641
- if ( isset( $args['users'] ) && is_array( $args['users'] ) ) {
642
- $args['users'] = implode( ',', $args['users'] );
643
- }
644
 
645
- // Users, comma separated.
646
- if ( ! empty( $args['users'] ) && is_string( $args['users'] ) ) {
647
 
648
- $users = explode( ',', $args['users'] );
649
- $users = array_map( 'intval', $users );
650
-
651
- if ( $users ) {
652
-
653
- $users_in = implode( ',', $users );
654
-
655
- $sql_user = sprintf(
656
- '
657
  AND id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value IN (%2$s) )
658
  ',
659
- $table_name_contexts, // 1
660
- $users_in // 2
661
- );
662
-
663
- $inner_where .= $sql_user;
664
-
665
- // echo $inner_where;exit;
666
- }
667
- }
668
-
669
- /**
670
- * Filter the sql template
671
- *
672
- * @since 2.0
673
- *
674
- * @param string $sql_tmpl
675
- */
676
- $sql_tmpl = apply_filters( 'simple_history/log_query_sql_template', $sql_tmpl );
677
-
678
- /**
679
- * Filter the sql template where clause
680
- *
681
- * @since 2.0
682
- *
683
- * @param string $where
684
- */
685
- $where = apply_filters( 'simple_history/log_query_sql_where', $where );
686
-
687
- /**
688
- * Filter the sql template limit
689
- *
690
- * @since 2.0
691
- *
692
- * @param string $limit
693
- */
694
- $limit = apply_filters( 'simple_history/log_query_limit', $limit );
695
-
696
- /**
697
- * Filter the sql template limit
698
- *
699
- * @since 2.0
700
- *
701
- * @param string $limit
702
- */
703
- $inner_where = apply_filters( 'simple_history/log_query_inner_where', $inner_where );
704
-
705
- $sql = sprintf(
706
- $sql_tmpl, // sprintf template
707
- $where, // 1
708
- $limit, // 2
709
- $table_name, // 3
710
- $inner_where,// 4
711
- $table_name_contexts // 5
712
- );
713
-
714
- /**
715
- * Filter the final sql query
716
- *
717
- * @since 2.0
718
- *
719
- * @param string $sql
720
- */
721
- $sql = apply_filters( 'simple_history/log_query_sql', $sql );
722
-
723
- // Remove comments below to debug query (includes query in json result)
724
- // $include_query_in_result = true;
725
- if ( isset( $_GET['SimpleHistoryLogQuery-showDebug'] ) && $_GET['SimpleHistoryLogQuery-showDebug'] ) {
726
-
727
- echo '<pre>';
728
- echo $sql_set_var;
729
- echo $sql;
730
- exit;
731
-
732
- }
733
-
734
- // Only return sql query
735
- if ( $args['returnQuery'] ) {
736
- return $sql;
737
- }
738
-
739
- $log_rows = $wpdb->get_results( $sql, OBJECT_K );
740
- $num_rows = sizeof( $log_rows );
741
-
742
- // Find total number of rows that we would have gotten without pagination
743
- // This is the number of rows with occasions taken into consideration
744
- $sql_found_rows = 'SELECT FOUND_ROWS()';
745
- $total_found_rows = (int) $wpdb->get_var( $sql_found_rows );
746
-
747
- // Add context
748
- $post_ids = wp_list_pluck( $log_rows, 'id' );
749
-
750
- if ( empty( $post_ids ) ) {
751
- $context_results = array();
752
- } else {
753
- $sql_context = sprintf( 'SELECT * FROM %2$s WHERE history_id IN (%1$s)', join( ',', $post_ids ), $table_name_contexts );
754
- $context_results = $wpdb->get_results( $sql_context );
755
- }
756
-
757
- foreach ( $context_results as $context_row ) {
758
-
759
- if ( ! isset( $log_rows[ $context_row->history_id ]->context ) ) {
760
- $log_rows[ $context_row->history_id ]->context = array();
761
- }
762
-
763
- $log_rows[ $context_row->history_id ]->context[ $context_row->key ] = $context_row->value;
764
-
765
- }
766
-
767
- // Remove id from keys, because they are cumbersome when working with JSON
768
- $log_rows = array_values( $log_rows );
769
- $min_id = null;
770
- $max_id = null;
771
-
772
- if ( sizeof( $log_rows ) ) {
773
-
774
- // Max id is simply the id of the first row
775
- $max_id = reset( $log_rows )->id;
776
-
777
- // Min id = to find the lowest id we must take occasions into consideration
778
- $last_row = end( $log_rows );
779
- $last_row_occasions_count = (int) $last_row->subsequentOccasions - 1;
780
- if ( $last_row_occasions_count === 0 ) {
781
-
782
- // Last row did not have any more occasions, so get min_id directly from the row
783
- $min_id = $last_row->id;
784
-
785
- } else {
786
-
787
- // Last row did have occaions, so fetch all occasions, and find id of last one
788
- $db_table = $wpdb->prefix . SimpleHistory::DBTABLE;
789
- $sql = sprintf(
790
- '
791
  SELECT id, date, occasionsID
792
  FROM %1$s
793
  WHERE id <= %2$s
794
  ORDER BY id DESC
795
  LIMIT %3$s
796
  ',
797
- $db_table,
798
- $last_row->id,
799
- $last_row_occasions_count + 1
800
- );
801
-
802
- $results = $wpdb->get_results( $sql );
803
-
804
- // the last occasion has the id we consider last in this paged result
805
- $min_id = end( $results )->id;
806
-
807
- }
808
- }// End if().
809
-
810
- // Calc pages
811
- if ( $args['posts_per_page'] ) {
812
- $pages_count = Ceil( $total_found_rows / (int) $args['posts_per_page'] );
813
- } else {
814
- $pages_count = 1;
815
- }
816
-
817
- // Create array to return
818
- // Make all rows a sub key because we want to add some meta info too
819
- $log_rows_count = sizeof( $log_rows );
820
- $page_rows_from = ( (int) $args['paged'] * (int) $args['posts_per_page'] ) - (int) $args['posts_per_page'] + 1;
821
- $page_rows_to = $page_rows_from + $log_rows_count - 1;
822
- $arr_return = array(
823
- 'total_row_count' => $total_found_rows,
824
- 'pages_count' => $pages_count,
825
- 'page_current' => (int) $args['paged'],
826
- 'page_rows_from' => $page_rows_from,
827
- 'page_rows_to' => $page_rows_to,
828
- 'max_id' => (int) $max_id,
829
- 'min_id' => (int) $min_id,
830
- 'log_rows_count' => $log_rows_count,
831
- 'log_rows' => $log_rows,
832
- );
833
-
834
- // sf_d($arr_return, '$arr_return');exit;
835
- wp_cache_set( $cache_key, $arr_return, $cache_group );
836
-
837
- return $arr_return;
838
-
839
- } // query
840
-
841
  } // class
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /**
6
  * Queries the Simple History Log
7
  */
8
+ class SimpleHistoryLogQuery
9
+ {
10
 
11
+ public function __construct()
12
+ {
13
 
14
+ /*
15
+ if ( is_array($args) && ! empty($args) ) {
16
 
17
+ return $this->query($args);
18
 
19
+ }
20
+ */
21
+ }
22
 
23
+ public function query($args)
24
+ {
25
+ $defaults = array(
26
 
27
+ // overview | occasions
28
+ 'type' => 'overview',
29
 
30
+ // Number of posts to show per page. 0 to show all.
31
+ 'posts_per_page' => 0,
32
 
33
+ // Page to show. 1 = first page
34
+ 'paged' => 1,
35
 
36
+ // Array. Only get posts that are in array.
37
+ 'post__in' => null,
38
 
39
+ // array or html
40
+ 'format' => 'array',
41
 
42
+ // If max_id_first_page is set then only get rows
43
+ // that have id equal or lower than this, to make
44
+ 'max_id_first_page' => null,
45
 
46
+ // if since_id is set the rows returned will only be rows with an ID greater than (i.e. more recent than) since_id
47
+ 'since_id' => null,
 
48
 
49
+ // date range
50
+ // in unix datetime or Y-m-d H:i (or format compatible with strtotime())
51
+ 'date_from' => null,
52
+ 'date_to' => null,
53
 
54
+ // months in format "Y-m"
55
+ // array or comma separated
56
+ 'months' => null,
 
57
 
58
+ // dates in format
59
+ // "month:2015-06" for june 2015
60
+ // "lastdays:7" for the last 7 days
61
+ 'dates' => null,
62
 
63
+ // search
64
+ 'search' => null,
 
 
65
 
66
+ // log levels to include. comma separated or as array. defaults to all.
67
+ 'loglevels' => null,
68
 
69
+ // loggers to include. comma separated. defaults to all the user can read
70
+ 'loggers' => null,
71
 
72
+ 'messages' => null,
 
73
 
74
+ // userID as number
75
+ 'user' => null,
76
 
77
+ // user ids, comma separated
78
+ 'users' => null,
79
 
80
+ // Can also contain:
81
+ // occasionsCount
82
+ // occasionsCountMaxReturn
83
+ // occasionsID
84
+ // If rows should be returned, or the actualy sql query used
85
+ 'returnQuery' => false,
86
 
87
+ );
 
 
 
 
 
88
 
89
+ $args = wp_parse_args($args, $defaults);
90
+ // sf_d($args, "Run log query with args");
91
+ $cache_key = 'SimpleHistoryLogQuery_' . md5(serialize($args)) . '_get_' . md5(serialize($_GET)) . '_userid_' . get_current_user_id();
92
+ $cache_group = 'simple-history-' . SimpleHistory::get_cache_incrementor();
93
+ $arr_return = wp_cache_get($cache_key, $cache_group);
94
 
95
+ if (false !== $arr_return) {
96
+ return $arr_return;
97
+ }
 
 
98
 
99
+ /*
100
+ Subequent occasions query thanks to this Stack Overflow thread:
101
+ http://stackoverflow.com/questions/13566303/how-to-group-subsequent-rows-based-on-a-criteria-and-then-count-them-mysql/13567320#13567320
102
+ Similar questions that I didn't manage to understand, work, or did try:
103
+ - http://stackoverflow.com/questions/23651176/mysql-query-if-dates-are-subsequent
104
+ - http://stackoverflow.com/questions/17651868/mysql-group-by-subsequent
105
+ - http://stackoverflow.com/questions/4495242/mysql-number-of-subsequent-occurrences
106
+ - http://stackoverflow.com/questions/20446242/postgresql-group-subsequent-rows
107
+ - http://stackoverflow.com/questions/17061156/mysql-group-by-range
108
+ - http://stackoverflow.com/questions/6602006/complicated-query-with-group-by-and-range-of-prices-in-mysql
109
+ */
110
 
111
+ global $wpdb;
 
 
 
 
 
 
 
 
 
 
112
 
113
+ $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
114
+ $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
115
 
116
+ $where = '1 = 1';
117
+ $limit = '';
118
+ $inner_where = '1 = 1';
119
+
120
+ if ('overview' === $args['type'] || 'single' === $args['type']) {
121
+ // Set variables used by query
122
+ $sql_set_var = "SET @a:='', @counter:=1, @groupby:=0";
123
+ $wpdb->query($sql_set_var);
124
+
125
+ // New and slightly faster query
126
+ // 1 = where
127
+ // 2 = limit
128
+ // 3 = db name
129
+ // 4 = where for inner calc sql query thingie
130
+ // 5 = db name contexts
131
+ $sql_tmpl = '
 
 
 
 
132
  /*NO_SELECT_FOUND_ROWS*/
133
  SELECT
134
  SQL_CALC_FOUND_ROWS
174
  %2$s
175
  ';
176
 
177
+ $sh = SimpleHistory::get_instance();
178
+
179
+ // Only include loggers that the current user can view
180
+ // @TODO: this causes error if user has no access to any logger at all
181
+ $sql_loggers_user_can_view = $sh->getLoggersThatUserCanRead(get_current_user_id(), 'sql');
182
+ $inner_where .= " AND logger IN {$sql_loggers_user_can_view}";
183
+ } elseif ('occasions' === $args['type']) {
184
+ // Query template
185
+ // 1 = where
186
+ // 2 = limit
187
+ // 3 = db name
188
+ $sql_tmpl = '
 
 
189
  SELECT h.*,
190
  # fake columns that exist in overview query
191
  1 as subsequentOccasions
195
  %2$s
196
  ';
197
 
198
+ $where .= ' AND h.id < ' . (int) $args['logRowID'];
199
+ $where .= " AND h.occasionsID = '" . esc_sql($args['occasionsID']) . "'";
200
+
201
+ if (isset($args['occasionsCountMaxReturn']) && (int) $args['occasionsCountMaxReturn'] < (int) $args['occasionsCount']) {
202
+ // Limit to max nn events if occasionsCountMaxReturn is set.
203
+ // Used in gui to prevent top many events returned, that can stall the browser.
204
+ $limit = 'LIMIT ' . (int) $args['occasionsCountMaxReturn'];
205
+ } else {
206
+ // Regular limit that gets all occasions
207
+ $limit = 'LIMIT ' . (int) $args['occasionsCount'];
208
+ }
209
+ }// End if().
210
+
211
+ // Determine limit
212
+ // Both posts_per_page and paged must be set
213
+ $is_limit_query = ( is_numeric($args['posts_per_page']) && $args['posts_per_page'] > 0 );
214
+ $is_limit_query = $is_limit_query && ( is_numeric($args['paged']) && $args['paged'] > 0 );
215
+ if ($is_limit_query) {
216
+ $limit_offset = ($args['paged'] - 1) * $args['posts_per_page'];
217
+ $limit .= sprintf('LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page']);
218
+ }
219
+
220
+ // Determine where
221
+ if ($args['post__in'] && is_array($args['post__in'])) {
222
+ // make sure all vals are integers
223
+ $args['post__in'] = array_map('intval', $args['post__in']);
224
+
225
+ $inner_where .= sprintf(' AND id IN (%1$s)', implode(',', $args['post__in']));
226
+ }
227
+
228
+ // If max_id_first_page is then then only include rows
229
+ // with id equal to or earlier
230
+ if (isset($args['max_id_first_page']) && is_numeric($args['max_id_first_page'])) {
231
+ $max_id_first_page = (int) $args['max_id_first_page'];
232
+ $inner_where .= sprintf(
233
+ ' AND id <= %1$d',
234
+ $max_id_first_page
235
+ );
236
+ }
237
+
238
+ if (isset($args['since_id']) && is_numeric($args['since_id'])) {
239
+ $since_id = (int) $args['since_id'];
240
+ /*
241
+ $where .= sprintf(
242
+ ' AND t.id > %1$d',
243
+ $since_id
244
+ );
245
+ */
246
+ // Add where to inner because that's faster
247
+ $inner_where .= sprintf(
248
+ ' AND id > %1$d',
249
+ $since_id
250
+ );
251
+ }
252
+
253
+ // Append date where
254
+ if (! empty($args['date_from'])) {
255
+ // date_from=2014-08-01
256
+ // if date is not numeric assume Y-m-d H:i-format
257
+ $date_from = $args['date_from'];
258
+ if (! is_numeric($date_from)) {
259
+ $date_from = strtotime($date_from);
260
+ }
261
+
262
+ $inner_where .= "\n" . sprintf(' AND date >= "%1$s"', esc_sql(date('Y-m-d H:i:s', $date_from)));
263
+ }
264
+
265
+ if (! empty($args['date_to'])) {
266
+ // date_to=2014-08-01
267
+ // if date is not numeric assume Y-m-d H:i-format
268
+ $date_to = $args['date_to'];
269
+ if (! is_numeric($date_to)) {
270
+ $date_to = strtotime($date_to);
271
+ }
272
+
273
+ $inner_where .= "\n" . sprintf(' AND date <= "%1$s"', date('Y-m-d H:i:s', $date_to));
274
+ }
275
+
276
+ /*
277
+ AND date >= "2015-01-01 00:00:00" AND date <= "2015-01-31 00:00:00"
278
+ */
279
+ // echo $inner_where;exit;
280
+ // dats
281
+ // if months they translate to $args["months"] because we already have support for that
282
+ // can't use months and dates and the same time
283
+ if (! empty($args['dates'])) {
284
+ if (is_array($args['dates'])) {
285
+ $arr_dates = $args['dates'];
286
+ } else {
287
+ $arr_dates = explode(',', $args['dates']);
288
+ }
289
+
290
+ $args['months'] = array();
291
+ $args['lastdays'] = 0;
292
+
293
+ foreach ($arr_dates as $one_date) {
294
+ // If begins with "month:" then strip string and keep only month numbers
295
+ if (strpos($one_date, 'month:') === 0) {
296
+ $args['months'][] = substr($one_date, strlen('month:'));
297
+ } elseif (strpos($one_date, 'lastdays:') === 0) {
298
+ // Only keep largest lastdays value
299
+ $args['lastdays'] = max($args['lastdays'], substr($one_date, strlen('lastdays:')));
300
+ // $args["lastdays"][] = substr($one_date, strlen("lastdays:"));
301
+ }
302
+ }
303
+ }
304
+
305
+ // lastdays, as int
306
+ if (! empty($args['lastdays'])) {
307
+ $inner_where .= sprintf('
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  # lastdays
309
  AND date >= DATE(NOW()) - INTERVAL %d DAY
310
  ', $args['lastdays']);
311
+ }
312
 
313
+ // months, in format "Y-m"
314
+ if (! empty($args['months'])) {
315
+ if (is_array($args['months'])) {
316
+ $arr_months = $args['months'];
317
+ } else {
318
+ $arr_months = explode(',', $args['months']);
319
+ }
320
 
321
+ $sql_months = '
 
 
 
 
 
 
322
  # sql_months
323
  AND (
324
  ';
325
 
326
+ foreach ($arr_months as $one_month) {
327
+ // beginning of month
328
+ // $ php -r ' echo date("Y-m-d H:i", strtotime("2014-08") ) . "\n";
329
+ // >> 2014-08-01 00:00
330
+ $date_month_beginning = strtotime($one_month);
 
331
 
332
+ // end of month
333
+ // $ php -r ' echo date("Y-m-d H:i", strtotime("2014-08 + 1 month") ) . "\n";'
334
+ // >> 2014-09-01 00:00
335
+ $date_month_end = strtotime("{$one_month} + 1 month");
336
 
337
+ $sql_months .= sprintf(
338
+ '
339
  (
340
  date >= "%1$s"
341
  AND date <= "%2$s"
343
 
344
  OR
345
  ',
346
+ date('Y-m-d H:i:s', $date_month_beginning), // 1
347
+ date('Y-m-d H:i:s', $date_month_end) // 2
348
+ );
349
+ }
 
350
 
351
+ $sql_months = trim($sql_months);
352
+ $sql_months = rtrim($sql_months, ' OR ');
353
 
354
+ $sql_months .= '
355
  # end sql_months and wrap
356
  )
357
  ';
358
 
359
+ $inner_where .= $sql_months;
360
+ // echo $inner_where;exit;
361
+ }// End if().
362
+
363
+ // search
364
+ if (! empty($args['search'])) {
365
+ $search_words = $args['search'];
366
+ $str_search_conditions = '';
367
+ $arr_search_words = preg_split('/[\s,]+/', $search_words);
368
+
369
+ // create array of all searched words
370
+ // split both spaces and commas and such
371
+ $arr_sql_like_cols = array( 'message', 'logger', 'level' );
372
+
373
+ foreach ($arr_sql_like_cols as $one_col) {
374
+ $str_sql_search_words = '';
375
+
376
+ foreach ($arr_search_words as $one_search_word) {
377
+ if (method_exists($wpdb, 'esc_like')) {
378
+ $str_like = esc_sql($wpdb->esc_like($one_search_word));
379
+ } else {
380
+ $str_like = esc_sql(like_escape($one_search_word));
381
+ }
382
+
383
+ $str_sql_search_words .= sprintf(
384
+ ' AND %1$s LIKE "%2$s" ',
385
+ $one_col,
386
+ "%{$str_like}%"
387
+ );
388
+ }
389
+
390
+ $str_sql_search_words = ltrim($str_sql_search_words, ' AND ');
391
+
392
+ $str_search_conditions .= "\n" . sprintf(
393
+ ' OR ( %1$s ) ',
394
+ $str_sql_search_words
395
+ );
396
+ }
397
+
398
+ $str_search_conditions = preg_replace('/^OR /', ' ', trim($str_search_conditions));
399
+
400
+ // also search contexts
401
+ $str_search_conditions .= "\n OR ( ";
402
+ foreach ($arr_search_words as $one_search_word) {
403
+ if (method_exists($wpdb, 'esc_like')) {
404
+ $str_like = esc_sql($wpdb->esc_like($one_search_word));
405
+ } else {
406
+ $str_like = esc_sql(like_escape($one_search_word));
407
+ }
408
+
409
+ $str_search_conditions .= "\n" . sprintf(
410
+ ' id IN ( SELECT history_id FROM %1$s AS c WHERE c.value LIKE "%2$s" ) AND ',
411
+ $table_name_contexts, // 1
412
+ '%' . $str_like . '%' // 2
413
+ );
414
+ }
415
+ $str_search_conditions = preg_replace('/ AND $/', '', $str_search_conditions);
416
+
417
+ $str_search_conditions .= "\n ) "; // end or for contexts
418
+
419
+ $inner_where .= "\n AND \n(\n {$str_search_conditions} \n ) ";
420
+
421
+ // echo $inner_where;exit;
422
+ }// End if().
423
+
424
+ // log levels
425
+ // comma separated
426
+ // http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&type=overview&format=&posts_per_page=10&paged=1&max_id_first_page=27273&SimpleHistoryLogQuery-showDebug=0&loglevel=error,warn
427
+ if (! empty($args['loglevels'])) {
428
+ $sql_loglevels = '';
429
+
430
+ if (is_array($args['loglevels'])) {
431
+ $arr_loglevels = $args['loglevels'];
432
+ } else {
433
+ $arr_loglevels = explode(',', $args['loglevels']);
434
+ }
435
+
436
+ foreach ($arr_loglevels as $one_loglevel) {
437
+ $sql_loglevels .= sprintf(' "%s", ', esc_sql($one_loglevel));
438
+ }
439
+
440
+ if ($sql_loglevels) {
441
+ $sql_loglevels = rtrim($sql_loglevels, ' ,');
442
+ $sql_loglevels = "\n AND level IN ({$sql_loglevels}) ";
443
+ }
444
+
445
+ $inner_where .= $sql_loglevels;
446
+ }
447
+
448
+ // messages
449
+ if (! empty($args['messages'])) {
450
+ // print_r($args["messages"]);exit;
451
+ /*
452
+ Array
453
+ (
454
+ [0] => SimpleCommentsLogger:anon_comment_added,SimpleCommentsLogger:user_comment_added,SimpleCommentsLogger:anon_trackback_added,SimpleCommentsLogger:user_trackback_added,SimpleCommentsLogger:anon_pingback_added,SimpleCommentsLogger:user_pingback_added,SimpleCommentsLogger:comment_edited,SimpleCommentsLogger:trackback_edited,SimpleCommentsLogger:pingback_edited,SimpleCommentsLogger:comment_status_approve,SimpleCommentsLogger:trackback_status_approve,SimpleCommentsLogger:pingback_status_approve,SimpleCommentsLogger:comment_status_hold,SimpleCommentsLogger:trackback_status_hold,SimpleCommentsLogger:pingback_status_hold,SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam,SimpleCommentsLogger:comment_status_trash,SimpleCommentsLogger:trackback_status_trash,SimpleCommentsLogger:pingback_status_trash,SimpleCommentsLogger:comment_untrashed,SimpleCommentsLogger:trackback_untrashed,SimpleCommentsLogger:pingback_untrashed,SimpleCommentsLogger:comment_deleted,SimpleCommentsLogger:trackback_deleted,SimpleCommentsLogger:pingback_deleted
455
+ [1] => SimpleCommentsLogger:SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam
456
+ )
457
+ */
458
+
459
+ // Array with loggers and messages
460
+ $arr_loggers_and_messages = array();
461
+
462
+ // Tranform from get'et format to our own internal format
463
+ foreach ((array) $args['messages'] as $one_arr_messages_row) {
464
+ $arr_row_messages = explode(',', $one_arr_messages_row);
465
+ // print_r($arr_row_messages);#exit;
466
+ /*
467
+ Array
468
+ (
469
+ [0] => SimpleCommentsLogger:anon_comment_added
470
+ [1] => SimpleCommentsLogger:user_comment_added
471
+ [2] => SimpleCommentsLogger:anon_trackback_added
472
+ */
473
+ foreach ($arr_row_messages as $one_row_logger_and_message) {
474
+ $arr_one_logger_and_message = explode(':', $one_row_logger_and_message);
475
+
476
+ if (! isset($arr_loggers_and_messages[ $arr_one_logger_and_message[0] ])) {
477
+ $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ] = array();
478
+ }
479
+
480
+ $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ][] = $arr_one_logger_and_message[1];
481
+ }
482
+ }
483
+
484
+ // Now create sql where based on loggers and messages
485
+ $sql_messages_where = ' AND (';
486
+ // print_r($arr_loggers_and_messages);exit;
487
+ foreach ($arr_loggers_and_messages as $logger_slug => $logger_messages) {
488
+ $sql_messages_where .= sprintf(
489
+ '
 
 
 
 
 
 
 
 
 
 
 
 
 
490
  (
491
  h.logger = "%1$s"
492
  AND c1.value IN (%2$s)
493
  )
494
  OR ',
495
+ esc_sql($logger_slug),
496
+ "'" . implode("','", $logger_messages) . "'"
497
+ );
498
+ }
499
+ // remove last or
500
+ $sql_messages_where = preg_replace('/OR $/', '', $sql_messages_where);
501
+
502
+ $sql_messages_where .= "\n )";
503
+ // echo $sql_messages_where;exit;
504
+ $where .= $sql_messages_where;
505
+
506
+ /*
507
+ print_r($arr_loggers_and_messages);exit;
508
+ Array
509
+ (
510
+ [SimpleCommentsLogger] => Array
511
+ (
512
+ [0] => anon_comment_added
513
+ [1] => user_comment_added
514
+ [2] => anon_trackback_added
515
+ [3] => user_trackback_added
516
+ [4] => anon_pingback_added
517
+ [5] => user_pingback_added
518
+ [6] => comment_edited
519
+ [7] => trackback_edited
520
+ [8] => pingback_edited
521
+ [9] => comment_status_approve
522
+ [10] => trackback_status_approve
523
+ [11] => pingback_status_approve
524
+ [12] => comment_status_hold
525
+ [13] => trackback_status_hold
526
+ [14] => pingback_status_hold
527
+ [15] => comment_status_spam
528
+ [16] => trackback_status_spam
529
+ [17] => pingback_status_spam
530
+ [18] => comment_status_trash
531
+ [19] => trackback_status_trash
532
+ [20] => pingback_status_trash
533
+ [21] => comment_untrashed
534
+ [22] => trackback_untrashed
535
+ [23] => pingback_untrashed
536
+ [24] => comment_deleted
537
+ [25] => trackback_deleted
538
+ [26] => pingback_deleted
539
+ )
540
+
541
+ [SimpleUserLogger] => Array
542
+ (
543
+ [0] => SimpleUserLogger
544
+ [1] => SimpleUserLogger
545
+ )
546
+
547
+ )
548
+
549
+ */
550
+ }// End if().
551
+
552
+ // loggers
553
+ // comma separated
554
+ // http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&type=overview&format=&posts_per_page=10&paged=1&max_id_first_page=27273&SimpleHistoryLogQuery-showDebug=0&loggers=SimpleCommentsLogger,SimpleCoreUpdatesLogger
555
+ if (! empty($args['loggers'])) {
556
+ $sql_loggers = '';
557
+ if (is_array($args['loggers'])) {
558
+ $arr_loggers = $args['loggers'];
559
+ } else {
560
+ $arr_loggers = explode(',', $args['loggers']);
561
+ }
562
+
563
+ // print_r($args["loggers"]);exit;
564
+ // print_r($arr_loggers);exit;
565
+ /*
566
+ Example of version with logger + message keys
567
+ Array
568
+ (
569
+ [0] => SimpleUserLogger:user_created
570
+ [1] => SimpleUserLogger:user_deleted
571
+ )
572
+ */
573
+
574
+ foreach ($arr_loggers as $one_logger) {
575
+ $sql_loggers .= sprintf(' "%s", ', esc_sql($one_logger));
576
+ }
577
+
578
+ if ($sql_loggers) {
579
+ $sql_loggers = rtrim($sql_loggers, ' ,');
580
+ $sql_loggers = "\n AND logger IN ({$sql_loggers}) ";
581
+ }
582
+
583
+ $inner_where .= $sql_loggers;
584
+ }
585
+
586
+ // user, a single userID
587
+ if (! empty($args['user']) && is_numeric($args['user'])) {
588
+ $userID = (int) $args['user'];
589
+ $sql_user = sprintf(
590
+ '
 
 
 
 
591
  AND id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value = %2$s )
592
  ',
593
+ $table_name_contexts, // 1
594
+ $userID // 2
595
+ );
596
 
597
+ $inner_where .= $sql_user;
598
+ }
599
 
600
+ // If users is array, make it comma separated.
601
+ if (isset($args['users']) && is_array($args['users'])) {
602
+ $args['users'] = implode(',', $args['users']);
603
+ }
604
 
605
+ // Users, comma separated.
606
+ if (! empty($args['users']) && is_string($args['users'])) {
607
+ $users = explode(',', $args['users']);
608
+ $users = array_map('intval', $users);
609
 
610
+ if ($users) {
611
+ $users_in = implode(',', $users);
612
 
613
+ $sql_user = sprintf(
614
+ '
 
 
 
 
 
 
 
615
  AND id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value IN (%2$s) )
616
  ',
617
+ $table_name_contexts, // 1
618
+ $users_in // 2
619
+ );
620
+
621
+ $inner_where .= $sql_user;
622
+
623
+ // echo $inner_where;exit;
624
+ }
625
+ }
626
+
627
+ /**
628
+ * Filter the sql template
629
+ *
630
+ * @since 2.0
631
+ *
632
+ * @param string $sql_tmpl
633
+ */
634
+ $sql_tmpl = apply_filters('simple_history/log_query_sql_template', $sql_tmpl);
635
+
636
+ /**
637
+ * Filter the sql template where clause
638
+ *
639
+ * @since 2.0
640
+ *
641
+ * @param string $where
642
+ */
643
+ $where = apply_filters('simple_history/log_query_sql_where', $where);
644
+
645
+ /**
646
+ * Filter the sql template limit
647
+ *
648
+ * @since 2.0
649
+ *
650
+ * @param string $limit
651
+ */
652
+ $limit = apply_filters('simple_history/log_query_limit', $limit);
653
+
654
+ /**
655
+ * Filter the sql template limit
656
+ *
657
+ * @since 2.0
658
+ *
659
+ * @param string $limit
660
+ */
661
+ $inner_where = apply_filters('simple_history/log_query_inner_where', $inner_where);
662
+
663
+ $sql = sprintf(
664
+ $sql_tmpl, // sprintf template
665
+ $where, // 1
666
+ $limit, // 2
667
+ $table_name, // 3
668
+ $inner_where, // 4
669
+ $table_name_contexts // 5
670
+ );
671
+
672
+ /**
673
+ * Filter the final sql query
674
+ *
675
+ * @since 2.0
676
+ *
677
+ * @param string $sql
678
+ */
679
+ $sql = apply_filters('simple_history/log_query_sql', $sql);
680
+
681
+ // Remove comments below to debug query (includes query in json result)
682
+ // $include_query_in_result = true;
683
+ if (isset($_GET['SimpleHistoryLogQuery-showDebug']) && $_GET['SimpleHistoryLogQuery-showDebug']) {
684
+ echo '<pre>';
685
+ echo $sql_set_var;
686
+ echo $sql;
687
+ exit;
688
+ }
689
+
690
+ // Only return sql query
691
+ if ($args['returnQuery']) {
692
+ return $sql;
693
+ }
694
+
695
+ $log_rows = $wpdb->get_results($sql, OBJECT_K);
696
+ $num_rows = sizeof($log_rows);
697
+
698
+ // Find total number of rows that we would have gotten without pagination
699
+ // This is the number of rows with occasions taken into consideration
700
+ $sql_found_rows = 'SELECT FOUND_ROWS()';
701
+ $total_found_rows = (int) $wpdb->get_var($sql_found_rows);
702
+
703
+ // Add context
704
+ $post_ids = wp_list_pluck($log_rows, 'id');
705
+
706
+ if (empty($post_ids)) {
707
+ $context_results = array();
708
+ } else {
709
+ $sql_context = sprintf('SELECT * FROM %2$s WHERE history_id IN (%1$s)', join(',', $post_ids), $table_name_contexts);
710
+ $context_results = $wpdb->get_results($sql_context);
711
+ }
712
+
713
+ foreach ($context_results as $context_row) {
714
+ if (! isset($log_rows[ $context_row->history_id ]->context)) {
715
+ $log_rows[ $context_row->history_id ]->context = array();
716
+ }
717
+
718
+ $log_rows[ $context_row->history_id ]->context[ $context_row->key ] = $context_row->value;
719
+ }
720
+
721
+ // Remove id from keys, because they are cumbersome when working with JSON
722
+ $log_rows = array_values($log_rows);
723
+ $min_id = null;
724
+ $max_id = null;
725
+
726
+ if (sizeof($log_rows)) {
727
+ // Max id is simply the id of the first row
728
+ $max_id = reset($log_rows)->id;
729
+
730
+ // Min id = to find the lowest id we must take occasions into consideration
731
+ $last_row = end($log_rows);
732
+ $last_row_occasions_count = (int) $last_row->subsequentOccasions - 1;
733
+ if ($last_row_occasions_count === 0) {
734
+ // Last row did not have any more occasions, so get min_id directly from the row
735
+ $min_id = $last_row->id;
736
+ } else {
737
+ // Last row did have occaions, so fetch all occasions, and find id of last one
738
+ $db_table = $wpdb->prefix . SimpleHistory::DBTABLE;
739
+ $sql = sprintf(
740
+ '
 
 
 
 
 
 
 
 
741
  SELECT id, date, occasionsID
742
  FROM %1$s
743
  WHERE id <= %2$s
744
  ORDER BY id DESC
745
  LIMIT %3$s
746
  ',
747
+ $db_table,
748
+ $last_row->id,
749
+ $last_row_occasions_count + 1
750
+ );
751
+
752
+ $results = $wpdb->get_results($sql);
753
+
754
+ // the last occasion has the id we consider last in this paged result
755
+ $min_id = end($results)->id;
756
+ }
757
+ }// End if().
758
+
759
+ // Calc pages
760
+ if ($args['posts_per_page']) {
761
+ $pages_count = Ceil($total_found_rows / (int) $args['posts_per_page']);
762
+ } else {
763
+ $pages_count = 1;
764
+ }
765
+
766
+ // Create array to return
767
+ // Make all rows a sub key because we want to add some meta info too
768
+ $log_rows_count = sizeof($log_rows);
769
+ $page_rows_from = ( (int) $args['paged'] * (int) $args['posts_per_page'] ) - (int) $args['posts_per_page'] + 1;
770
+ $page_rows_to = $page_rows_from + $log_rows_count - 1;
771
+ $arr_return = array(
772
+ 'total_row_count' => $total_found_rows,
773
+ 'pages_count' => $pages_count,
774
+ 'page_current' => (int) $args['paged'],
775
+ 'page_rows_from' => $page_rows_from,
776
+ 'page_rows_to' => $page_rows_to,
777
+ 'max_id' => (int) $max_id,
778
+ 'min_id' => (int) $min_id,
779
+ 'log_rows_count' => $log_rows_count,
780
+ 'log_rows' => $log_rows,
781
+ );
782
+
783
+ // sf_d($arr_return, '$arr_return');exit;
784
+ wp_cache_set($cache_key, $arr_return, $cache_group);
785
+
786
+ return $arr_return;
787
+ } // query
 
 
 
788
  } // class
inc/helpers.php ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Helper function with same name as the SimpleLogger-class
5
+ *
6
+ * Makes call like this possible:
7
+ * SimpleLogger()->info("This is a message sent to the log");
8
+ */
9
+ function SimpleLogger()
10
+ {
11
+ return new SimpleLogger(SimpleHistory::get_instance());
12
+ }
13
+
14
+ /**
15
+ * Add event to history table
16
+ * This is here for backwards compatibility
17
+ * If you use this please consider using
18
+ * SimpleHistory()->info();
19
+ * instead
20
+ */
21
+ function simple_history_add($args)
22
+ {
23
+ $defaults = array(
24
+ 'action' => null,
25
+ 'object_type' => null,
26
+ 'object_subtype' => null,
27
+ 'object_id' => null,
28
+ 'object_name' => null,
29
+ 'user_id' => null,
30
+ 'description' => null
31
+ );
32
+
33
+ $context = wp_parse_args($args, $defaults);
34
+
35
+ $message = "{$context["object_type"]} {$context["object_name"]} {$context["action"]}";
36
+
37
+ SimpleLogger()->info($message, $context);
38
+ } // simple_history_add
39
+
40
+ /**
41
+ * Pretty much same as wp_text_diff() but with this you can set leading and trailing context lines
42
+ *
43
+ * @since 2.0.29
44
+ *
45
+ *
46
+ * Original description from wp_text_diff():
47
+ *
48
+ * Displays a human readable HTML representation of the difference between two strings.
49
+ *
50
+ * The Diff is available for getting the changes between versions. The output is
51
+ * HTML, so the primary use is for displaying the changes. If the two strings
52
+ * are equivalent, then an empty string will be returned.
53
+ *
54
+ * The arguments supported and can be changed are listed below.
55
+ *
56
+ * 'title' : Default is an empty string. Titles the diff in a manner compatible
57
+ * with the output.
58
+ * 'title_left' : Default is an empty string. Change the HTML to the left of the
59
+ * title.
60
+ * 'title_right' : Default is an empty string. Change the HTML to the right of
61
+ * the title.
62
+ *
63
+ * @see wp_parse_args() Used to change defaults to user defined settings.
64
+ * @uses Text_Diff
65
+ * @uses WP_Text_Diff_Renderer_Table
66
+ *
67
+ * @param string $left_string "old" (left) version of string
68
+ * @param string $right_string "new" (right) version of string
69
+ * @param string|array $args Optional. Change 'title', 'title_left', and 'title_right' defaults. And leading_context_lines and trailing_context_lines.
70
+ * @return string Empty string if strings are equivalent or HTML with differences.
71
+ */
72
+ function simple_history_text_diff($left_string, $right_string, $args = null)
73
+ {
74
+ $defaults = array(
75
+ 'title' => '',
76
+ 'title_left' => '',
77
+ 'title_right' => '',
78
+ 'leading_context_lines' => 1,
79
+ 'trailing_context_lines' => 1
80
+ );
81
+
82
+ $args = wp_parse_args($args, $defaults);
83
+
84
+ if (!class_exists('WP_Text_Diff_Renderer_Table')) {
85
+ require ABSPATH . WPINC . '/wp-diff.php';
86
+ }
87
+
88
+ $left_string = normalize_whitespace($left_string);
89
+ $right_string = normalize_whitespace($right_string);
90
+
91
+ $left_lines = explode("\n", $left_string);
92
+ $right_lines = explode("\n", $right_string);
93
+ $text_diff = new Text_Diff($left_lines, $right_lines);
94
+
95
+ $renderer = new WP_Text_Diff_Renderer_Table($args);
96
+ $renderer->_leading_context_lines = $args['leading_context_lines'];
97
+ $renderer->_trailing_context_lines = $args['trailing_context_lines'];
98
+
99
+ $diff = $renderer->render($text_diff);
100
+
101
+ if (!$diff) {
102
+ return '';
103
+ }
104
+
105
+ $r = '';
106
+
107
+ $r .= "<div class='SimpleHistory__diff__contents' tabindex='0'>";
108
+ $r .= "<div class='SimpleHistory__diff__contentsInner'>";
109
+
110
+ $r .= "<table class='diff SimpleHistory__diff'>\n";
111
+
112
+ if (!empty($args['show_split_view'])) {
113
+ $r .=
114
+ "<col class='content diffsplit left' /><col class='content diffsplit middle' /><col class='content diffsplit right' />";
115
+ } else {
116
+ $r .= "<col class='content' />";
117
+ }
118
+
119
+ if ($args['title'] || $args['title_left'] || $args['title_right']) {
120
+ $r .= '<thead>';
121
+ }
122
+ if ($args['title']) {
123
+ $r .= "<tr class='diff-title'><th colspan='4'>$args[title]</th></tr>\n";
124
+ }
125
+ if ($args['title_left'] || $args['title_right']) {
126
+ $r .= "<tr class='diff-sub-title'>\n";
127
+ $r .= "\t<td></td><th>$args[title_left]</th>\n";
128
+ $r .= "\t<td></td><th>$args[title_right]</th>\n";
129
+ $r .= "</tr>\n";
130
+ }
131
+ if ($args['title'] || $args['title_left'] || $args['title_right']) {
132
+ $r .= "</thead>\n";
133
+ }
134
+
135
+ $r .= "<tbody>\n$diff</div>\n</tbody>\n";
136
+ $r .= '</table>';
137
+
138
+ $r .= '</div>';
139
+ $r .= '</div>';
140
+
141
+ return $r;
142
+ }
143
+
144
+ /**
145
+ * Log variable(s) to error log.
146
+ * Any number of variables can be passed and each variable is print_r'ed to the error log.
147
+ *
148
+ * Example usage:
149
+ * sh_error_log(
150
+ * 'rest_request_after_callbacks:',
151
+ * $handler,
152
+ * $handler['callback'][0],
153
+ * $handler['callback'][1]
154
+ * );
155
+ */
156
+ function sh_error_log()
157
+ {
158
+ foreach (func_get_args() as $var) {
159
+ if (is_bool($var)) {
160
+ $bool_string = true === $var ? 'true' : 'false';
161
+ error_log("$bool_string (boolean value)");
162
+ } elseif (is_null($var)) {
163
+ error_log('null (null value)');
164
+ } else {
165
+ error_log(print_r($var, true));
166
+ }
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Return a name for a callable.
172
+ *
173
+ * Examples of return values:
174
+ * - WP_REST_Posts_Controller::get_items
175
+ * - WP_REST_Users_Controller::get_items"
176
+ * - WP_REST_Server::get_index
177
+ * - Redirection_Api_Redirect::route_bulk
178
+ * - wpcf7_rest_create_feedback
179
+ * - closure
180
+ *
181
+ * Function based on code found on stack overflow:
182
+ * https://stackoverflow.com/questions/34324576/print-name-or-definition-of-callable-in-php
183
+ *
184
+ * @param callable $callable The callable thing to check.
185
+ * @return string Name of callable.
186
+ */
187
+ function sh_get_callable_name($callable)
188
+ {
189
+ if (is_string($callable)) {
190
+ return trim($callable);
191
+ } elseif (is_array($callable)) {
192
+ if (is_object($callable[0])) {
193
+ return sprintf('%s::%s', get_class($callable[0]), trim($callable[1]));
194
+ } else {
195
+ return sprintf('%s::%s', trim($callable[0]), trim($callable[1]));
196
+ }
197
+ } elseif ($callable instanceof Closure) {
198
+ return 'closure';
199
+ } else {
200
+ return 'unknown';
201
+ }
202
+ }
203
+
204
+ /**
205
+ * PHP 5.3 compatible version of ucwords with second argument.
206
+ * Taken from http://php.net/manual/en/function.ucwords.php#105249.
207
+ *
208
+ * @param string $str String.
209
+ * @param string $separator String.
210
+ *
211
+ * @return string with words uppercased.
212
+ */
213
+ function sh_ucwords($str, $separator = ' ')
214
+ {
215
+ $str = str_replace($separator, ' ', $str);
216
+ $str = ucwords(strtolower($str));
217
+ $str = str_replace(' ', $separator, $str);
218
+ return $str;
219
+ }
index.php CHANGED
@@ -4,9 +4,8 @@
4
  * 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
8
- * presents those events in a very nice GUI.
9
- * Version: 2.31.1
10
  * Author: Pär Thernström
11
  * Author URI: http://simple-history.com/
12
  * License: GPL2
@@ -47,7 +46,7 @@ if ($ok_php_version && $ok_wp_version) {
47
  */
48
 
49
  if (!defined('SIMPLE_HISTORY_VERSION')) {
50
- define('SIMPLE_HISTORY_VERSION', '2.31.1');
51
  }
52
 
53
  if (!defined('SIMPLE_HISTORY_PATH')) {
@@ -69,6 +68,7 @@ if ($ok_php_version && $ok_wp_version) {
69
  /** Load required files */
70
  require_once __DIR__ . '/inc/SimpleHistory.php';
71
  require_once __DIR__ . '/inc/SimpleHistoryLogQuery.php';
 
72
 
73
  /** Boot up */
74
  SimpleHistory::get_instance();
@@ -84,35 +84,38 @@ if ($ok_php_version && $ok_wp_version) {
84
  $ok_wp_version = version_compare($GLOBALS['wp_version'], '4.5.1', '>=');
85
  $ok_php_version = version_compare(phpversion(), '5.3', '>=');
86
  ?>
87
- <div class="updated error">
88
- <?php
89
- if (!$ok_php_version) {
90
- echo '<p>';
91
- printf(
92
- /* translators: 1: PHP version */
93
- esc_html(
94
- __(
95
- 'Simple History is a great plugin, but to use it your server must have at least PHP 5.3 installed (you have version %s).',
96
- 'simple-history'
97
- )
98
- ),
99
- phpversion() // 1
100
- );
101
- echo '</p>';
102
- }
103
 
104
- if (!$ok_wp_version) {
105
- echo '<p>';
106
- printf(
107
- /* translators: 1: WordPress version */
108
- esc_html(
109
- __('Simple History requires WordPress version 4.5.1 or higher (you have version %s).', 'simple-history')
110
- ),
111
- $GLOBALS['wp_version'] // 1
112
- );
113
- echo '</p>';
114
- }?>
115
- </div>
116
- <?php
 
 
 
117
  }
118
  } // End if().
4
  * 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.32
 
9
  * Author: Pär Thernström
10
  * Author URI: http://simple-history.com/
11
  * License: GPL2
46
  */
47
 
48
  if (!defined('SIMPLE_HISTORY_VERSION')) {
49
+ define('SIMPLE_HISTORY_VERSION', '2.32');
50
  }
51
 
52
  if (!defined('SIMPLE_HISTORY_PATH')) {
68
  /** Load required files */
69
  require_once __DIR__ . '/inc/SimpleHistory.php';
70
  require_once __DIR__ . '/inc/SimpleHistoryLogQuery.php';
71
+ require_once __DIR__ . '/inc/helpers.php';
72
 
73
  /** Boot up */
74
  SimpleHistory::get_instance();
84
  $ok_wp_version = version_compare($GLOBALS['wp_version'], '4.5.1', '>=');
85
  $ok_php_version = version_compare(phpversion(), '5.3', '>=');
86
  ?>
87
+ <div class="updated error">
88
+ <?php
89
+ if (!$ok_php_version) {
90
+ echo '<p>';
91
+ printf(
92
+ /* translators: 1: PHP version */
93
+ esc_html(
94
+ __(
95
+ 'Simple History is a great plugin, but to use it your server must have at least PHP 5.3 installed (you have version %s).',
96
+ 'simple-history'
97
+ )
98
+ ),
99
+ phpversion() // 1
100
+ );
101
+ echo '</p>';
102
+ }
103
 
104
+ if (!$ok_wp_version) {
105
+ echo '<p>';
106
+ printf(
107
+ /* translators: 1: WordPress version */
108
+ esc_html(
109
+ __(
110
+ 'Simple History requires WordPress version 4.5.1 or higher (you have version %s).',
111
+ 'simple-history'
112
+ )
113
+ ),
114
+ $GLOBALS['wp_version'] // 1
115
+ );
116
+ echo '</p>';
117
+ }?>
118
+ </div>
119
+ <?php
120
  }
121
  } // End if().
loggers/AvailableUpdatesLogger.php CHANGED
@@ -6,313 +6,304 @@
6
  * @package SimpleHistory
7
  */
8
 
9
- if ( ! class_exists( 'AvailableUpdatesLogger' ) ) {
10
-
11
- /**
12
- * Class.
13
- */
14
- class AvailableUpdatesLogger extends SimpleLogger {
15
-
16
- /**
17
- * Slug for logger.
18
- *
19
- * @var $slug string
20
- */
21
- public $slug = __CLASS__;
22
-
23
- /**
24
- * Return logger info
25
- *
26
- * @return array
27
- */
28
- function getInfo() {
29
-
30
- $arr_info = array(
31
- 'name' => 'AvailableUpdatesLogger',
32
- 'description' => 'Logs found updates to WordPress, plugins, and themes',
33
- 'capability' => 'manage_options',
34
- 'messages' => array(
35
- 'core_update_available' => __( 'Found an update to WordPress.', 'simple-history' ),
36
- 'plugin_update_available' => __( 'Found an update to plugin "{plugin_name}"', 'simple-history' ),
37
- 'theme_update_available' => __( 'Found an update to theme "{theme_name}"', 'simple-history' ),
38
- ),
39
- 'labels' => array(
40
- 'search' => array(
41
- 'label' => _x( 'WordPress and plugins updates found', 'Plugin logger: updates found', 'simple-history' ),
42
- 'label_all' => _x( 'All found updates', 'Plugin logger: updates found', 'simple-history' ),
43
- 'options' => array(
44
- _x( 'WordPress updates found', 'Plugin logger: updates found', 'simple-history' ) => array(
45
- 'core_update_available'
46
- ),
47
- _x( 'Plugin updates found', 'Plugin logger: updates found', 'simple-history' ) => array(
48
- 'plugin_update_available',
49
- ),
50
- _x( 'Theme updates found', 'Plugin logger: updates found', 'simple-history' ) => array(
51
- 'theme_update_available'
52
- ),
53
- ),
54
- ), // search array.
55
- ), // labels.
56
- );
57
-
58
- return $arr_info;
59
-
60
- }
61
-
62
- /**
63
- * Called when logger is loaded.
64
- */
65
- function loaded() {
66
-
67
- // When WP is done checking for core updates it sets a site transient called "update_core"
68
- // set_site_transient( 'update_core', null ); // Uncomment to test
69
- add_action( 'set_site_transient_update_core', array( $this, 'on_setted_update_core_transient' ), 10, 1 );
70
-
71
- // Dito for plugins
72
- // set_site_transient( 'update_plugins', null ); // Uncomment to test
73
- add_action( 'set_site_transient_update_plugins', array( $this, 'on_setted_update_plugins_transient' ), 10, 1 );
74
-
75
- add_action( 'set_site_transient_update_themes', array( $this, 'on_setted_update_update_themes' ), 10, 1 );
76
-
77
- }
78
-
79
- function on_setted_update_core_transient( $updates ) {
80
-
81
- global $wp_version;
82
-
83
- $last_version_checked = get_option( "simplehistory_{$this->slug}_wp_core_version_available" );
84
-
85
- // During update of network sites this was not set, so make sure to check
86
- if ( empty( $updates->updates[0]->current ) ) {
87
- return;
88
- }
89
-
90
- $new_wp_core_version = $updates->updates[0]->current; // The new WP core version
91
-
92
- // Some plugins can mess with version, so get fresh from the version file.
93
- require_once ABSPATH . WPINC . '/version.php';
94
-
95
- // If found version is same version as we have logged about before then don't continue
96
- if ( $last_version_checked == $new_wp_core_version ) {
97
- return;
98
- }
99
-
100
- // is WP core update available?
101
- if ( 'upgrade' == $updates->updates[0]->response ) {
102
-
103
- $this->noticeMessage( 'core_update_available', array(
104
- 'wp_core_current_version' => $wp_version,
105
- 'wp_core_new_version' => $new_wp_core_version,
106
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
107
- ) );
108
-
109
- // Store updated version available, so we don't log that version again
110
- update_option( "simplehistory_{$this->slug}_wp_core_version_available", $new_wp_core_version );
111
-
112
- }
113
-
114
- }
115
-
116
- /**
117
- * Called when WordPress is done checking for plugin updates.
118
- * WP sets site transient 'update_plugins' when done.
119
- * Log found plugin updates.
120
- */
121
- function on_setted_update_plugins_transient( $updates ) {
122
-
123
- if ( empty( $updates->response ) || ! is_array( $updates->response ) ) {
124
- return;
125
- }
126
-
127
- // If we only want to notify about active plugins
128
- /*
129
  $active_plugins = get_option( 'active_plugins' );
130
  $active_plugins = array_flip( $active_plugins ); // find which plugins are active
131
  $plugins_need_update = array_intersect_key( $plugins_need_update, $active_plugins ); // only keep plugins that are active
132
  */
133
 
134
- // update_option( "simplehistory_{$this->slug}_wp_core_version_available", $new_wp_core_version );
135
- $option_key = "simplehistory_{$this->slug}_plugin_updates_available";
136
- $checked_updates = get_option( $option_key );
137
-
138
- if ( ! is_array( $checked_updates ) ) {
139
- $checked_updates = array();
140
- }
141
-
142
- // File needed plugin API
143
- if ( ! function_exists( 'get_plugin_data' ) ) {
144
- require_once ABSPATH . 'wp-admin/includes/plugin.php';
145
- }
146
-
147
- // For each available update.
148
- foreach ( $updates->response as $key => $data ) {
149
-
150
- // Make sure plugin directory exists or get_plugin_data will
151
- // give warning like
152
- // "PHP Warning: fread() expects parameter 1 to be resource, boolean given in /wp/wp-includes/functions.php on line 4837"
153
- $file = WP_PLUGIN_DIR . '/' . $key;
154
-
155
- // Continue with next plugin if plugin file did not exist.
156
- if ( ! file_exists( $file ) ) {
157
- continue;
158
- }
159
-
160
- $fp = fopen( $file, 'r' );
161
-
162
- // Continue with next plugin if plugin file could not be read.
163
- if (false === $fp) {
164
- continue;
165
- }
166
-
167
- $plugin_info = get_plugin_data( $file, true, false );
168
-
169
- $plugin_new_version = isset( $data->new_version ) ? $data->new_version : '';
170
-
171
- // Check if this plugin and this version has been checked/logged already.
172
- if ( ! array_key_exists( $key, $checked_updates ) ) {
173
- $checked_updates[ $key ] = array(
174
- 'checked_version' => null,
175
- );
176
- }
177
-
178
- if ( $checked_updates[ $key ]['checked_version'] == $plugin_new_version ) {
179
- // This version has been checked/logged already
180
- continue;
181
- }
182
-
183
- $checked_updates[ $key ]['checked_version'] = $plugin_new_version;
184
-
185
- $this->noticeMessage( 'plugin_update_available', array(
186
- 'plugin_name' => isset( $plugin_info['Name'] ) ? $plugin_info['Name'] : '',
187
- 'plugin_current_version' => isset( $plugin_info['Version'] ) ? $plugin_info['Version'] : '',
188
- 'plugin_new_version' => $plugin_new_version,
189
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
190
- // "plugin_info" => $plugin_info,
191
- // "remote_plugin_info" => $remote_plugin_info,
192
- // "active_plugins" => $active_plugins,
193
- // "updates" => $updates
194
- ) );
195
-
196
- } // End foreach().
197
-
198
- update_option( $option_key, $checked_updates );
199
-
200
- } // function
201
-
202
- function on_setted_update_update_themes( $updates ) {
203
-
204
- if ( empty( $updates->response ) || ! is_array( $updates->response ) ) {
205
- return;
206
- }
207
-
208
- // update_option( "simplehistory_{$this->slug}_wp_core_version_available", $new_wp_core_version );
209
- $option_key = "simplehistory_{$this->slug}_theme_updates_available";
210
- $checked_updates = get_option( $option_key );
211
-
212
- if ( ! is_array( $checked_updates ) ) {
213
- $checked_updates = array();
214
- }
215
-
216
- // For each available update
217
- foreach ( $updates->response as $key => $data ) {
218
-
219
- $theme_info = wp_get_theme( $key );
220
-
221
- // $message .= "\n" . sprintf( __( "Theme: %s is out of date. Please update from version %s to %s", "wp-updates-notifier" ), $theme_info['Name'], $theme_info['Version'], $data['new_version'] ) . "\n";
222
- $settings['notified']['theme'][ $key ] = $data['new_version']; // set theme version we are notifying about
223
-
224
- $theme_new_version = isset( $data['new_version'] ) ? $data['new_version'] : '';
225
-
226
- // check if this plugin and this version has been checked/logged already
227
- if ( ! array_key_exists( $key, $checked_updates ) ) {
228
- $checked_updates[ $key ] = array(
229
- 'checked_version' => null,
230
- );
231
- }
232
-
233
- if ( $checked_updates[ $key ]['checked_version'] == $theme_new_version ) {
234
- // This version has been checked/logged already
235
- continue;
236
- }
237
-
238
- $checked_updates[ $key ]['checked_version'] = $theme_new_version;
239
-
240
- $this->noticeMessage( 'theme_update_available', array(
241
- 'theme_name' => isset( $theme_info['Name'] ) ? $theme_info['Name'] : '',
242
- 'theme_current_version' => isset( $theme_info['Version'] ) ? $theme_info['Version'] : '',
243
- 'theme_new_version' => $theme_new_version,
244
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
245
- // "plugin_info" => $plugin_info,
246
- // "remote_plugin_info" => $remote_plugin_info,
247
- // "active_plugins" => $active_plugins,
248
- // "updates" => $updates,
249
- ) );
250
-
251
- } // End foreach().
252
-
253
- update_option( $option_key, $checked_updates );
254
-
255
- } // function
256
-
257
- /**
258
- * Append prev and current version of update object as details in the output
259
- */
260
- function getLogRowDetailsOutput( $row ) {
261
-
262
- $output = '';
263
-
264
- $current_version = null;
265
- $new_version = null;
266
- $context_message_key = isset( $row->context_message_key ) ? $row->context_message_key : null;
267
-
268
- $context = isset( $row->context ) ? $row->context : array();
269
-
270
- switch ( $context_message_key ) {
271
-
272
- case 'core_update_available':
273
- $current_version = isset( $context['wp_core_current_version'] ) ? $context['wp_core_current_version'] : null;
274
- $new_version = isset( $context['wp_core_new_version'] ) ? $context['wp_core_new_version'] : null;
275
- break;
276
-
277
- case 'plugin_update_available':
278
- $current_version = isset( $context['plugin_current_version'] ) ? $context['plugin_current_version'] : null;
279
- $new_version = isset( $context['plugin_new_version'] ) ? $context['plugin_new_version'] : null;
280
- break;
281
-
282
- case 'theme_update_available':
283
- $current_version = isset( $context['theme_current_version'] ) ? $context['theme_current_version'] : null;
284
- $new_version = isset( $context['theme_new_version'] ) ? $context['theme_new_version'] : null;
285
- break;
286
-
287
- }
288
-
289
- if ( $current_version && $new_version ) {
290
-
291
- $output .= '<p>';
292
- $output .= '<span class="SimpleHistoryLogitem__inlineDivided">';
293
- $output .= '<em>' . __( 'Available version', 'simple-history' ) . '</em> ' . esc_html( $new_version );
294
- $output .= '</span> ';
295
-
296
- $output .= '<span class="SimpleHistoryLogitem__inlineDivided">';
297
- $output .= '<em>' . __( 'Installed version', 'simple-history' ) . '</em> ' . esc_html( $current_version );
298
- $output .= '</span>';
299
-
300
- $output .= '</p>';
301
-
302
- // Add link to update-page, if user is allowed to that page.
303
- $is_allowed_to_update_page = current_user_can( 'update_core' ) || current_user_can( 'update_themes' ) || current_user_can( 'update_plugins' );
304
-
305
- if ( $is_allowed_to_update_page ) {
306
- $output .= sprintf( '<p><a href="%1$s">', admin_url( 'update-core.php' ) );
307
- $output .= __( 'View all updates', 'simple-history' );
308
- $output .= '</a></p>';
309
- }
310
- }
311
-
312
- return $output;
313
-
314
- }
315
-
316
- } // class
317
 
318
  } // End if().
6
  * @package SimpleHistory
7
  */
8
 
9
+ if (! class_exists('AvailableUpdatesLogger')) {
10
+
11
+ /**
12
+ * Class.
13
+ */
14
+ class AvailableUpdatesLogger extends SimpleLogger
15
+ {
16
+
17
+ /**
18
+ * Slug for logger.
19
+ *
20
+ * @var $slug string
21
+ */
22
+ public $slug = __CLASS__;
23
+
24
+ /**
25
+ * Return logger info
26
+ *
27
+ * @return array
28
+ */
29
+ function getInfo()
30
+ {
31
+
32
+ $arr_info = array(
33
+ 'name' => 'AvailableUpdatesLogger',
34
+ 'description' => 'Logs found updates to WordPress, plugins, and themes',
35
+ 'capability' => 'manage_options',
36
+ 'messages' => array(
37
+ 'core_update_available' => __('Found an update to WordPress.', 'simple-history'),
38
+ 'plugin_update_available' => __('Found an update to plugin "{plugin_name}"', 'simple-history'),
39
+ 'theme_update_available' => __('Found an update to theme "{theme_name}"', 'simple-history'),
40
+ ),
41
+ 'labels' => array(
42
+ 'search' => array(
43
+ 'label' => _x('WordPress and plugins updates found', 'Plugin logger: updates found', 'simple-history'),
44
+ 'label_all' => _x('All found updates', 'Plugin logger: updates found', 'simple-history'),
45
+ 'options' => array(
46
+ _x('WordPress updates found', 'Plugin logger: updates found', 'simple-history') => array(
47
+ 'core_update_available'
48
+ ),
49
+ _x('Plugin updates found', 'Plugin logger: updates found', 'simple-history') => array(
50
+ 'plugin_update_available',
51
+ ),
52
+ _x('Theme updates found', 'Plugin logger: updates found', 'simple-history') => array(
53
+ 'theme_update_available'
54
+ ),
55
+ ),
56
+ ), // search array.
57
+ ), // labels.
58
+ );
59
+
60
+ return $arr_info;
61
+ }
62
+
63
+ /**
64
+ * Called when logger is loaded.
65
+ */
66
+ function loaded()
67
+ {
68
+
69
+ // When WP is done checking for core updates it sets a site transient called "update_core"
70
+ // set_site_transient( 'update_core', null ); // Uncomment to test
71
+ add_action('set_site_transient_update_core', array( $this, 'on_setted_update_core_transient' ), 10, 1);
72
+
73
+ // Dito for plugins
74
+ // set_site_transient( 'update_plugins', null ); // Uncomment to test
75
+ add_action('set_site_transient_update_plugins', array( $this, 'on_setted_update_plugins_transient' ), 10, 1);
76
+
77
+ add_action('set_site_transient_update_themes', array( $this, 'on_setted_update_update_themes' ), 10, 1);
78
+ }
79
+
80
+ function on_setted_update_core_transient($updates)
81
+ {
82
+
83
+ global $wp_version;
84
+
85
+ $last_version_checked = get_option("simplehistory_{$this->slug}_wp_core_version_available");
86
+
87
+ // During update of network sites this was not set, so make sure to check
88
+ if (empty($updates->updates[0]->current)) {
89
+ return;
90
+ }
91
+
92
+ $new_wp_core_version = $updates->updates[0]->current; // The new WP core version
93
+
94
+ // Some plugins can mess with version, so get fresh from the version file.
95
+ require_once ABSPATH . WPINC . '/version.php';
96
+
97
+ // If found version is same version as we have logged about before then don't continue
98
+ if ($last_version_checked == $new_wp_core_version) {
99
+ return;
100
+ }
101
+
102
+ // is WP core update available?
103
+ if ('upgrade' == $updates->updates[0]->response) {
104
+ $this->noticeMessage('core_update_available', array(
105
+ 'wp_core_current_version' => $wp_version,
106
+ 'wp_core_new_version' => $new_wp_core_version,
107
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
108
+ ));
109
+
110
+ // Store updated version available, so we don't log that version again
111
+ update_option("simplehistory_{$this->slug}_wp_core_version_available", $new_wp_core_version);
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Called when WordPress is done checking for plugin updates.
117
+ * WP sets site transient 'update_plugins' when done.
118
+ * Log found plugin updates.
119
+ */
120
+ function on_setted_update_plugins_transient($updates)
121
+ {
122
+
123
+ if (empty($updates->response) || ! is_array($updates->response)) {
124
+ return;
125
+ }
126
+
127
+ // If we only want to notify about active plugins
128
+ /*
129
  $active_plugins = get_option( 'active_plugins' );
130
  $active_plugins = array_flip( $active_plugins ); // find which plugins are active
131
  $plugins_need_update = array_intersect_key( $plugins_need_update, $active_plugins ); // only keep plugins that are active
132
  */
133
 
134
+ // update_option( "simplehistory_{$this->slug}_wp_core_version_available", $new_wp_core_version );
135
+ $option_key = "simplehistory_{$this->slug}_plugin_updates_available";
136
+ $checked_updates = get_option($option_key);
137
+
138
+ if (! is_array($checked_updates)) {
139
+ $checked_updates = array();
140
+ }
141
+
142
+ // File needed plugin API
143
+ if (! function_exists('get_plugin_data')) {
144
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
145
+ }
146
+
147
+ // For each available update.
148
+ foreach ($updates->response as $key => $data) {
149
+ // Make sure plugin directory exists or get_plugin_data will
150
+ // give warning like
151
+ // "PHP Warning: fread() expects parameter 1 to be resource, boolean given in /wp/wp-includes/functions.php on line 4837"
152
+ $file = WP_PLUGIN_DIR . '/' . $key;
153
+
154
+ // Continue with next plugin if plugin file did not exist.
155
+ if (! file_exists($file)) {
156
+ continue;
157
+ }
158
+
159
+ $fp = fopen($file, 'r');
160
+
161
+ // Continue with next plugin if plugin file could not be read.
162
+ if (false === $fp) {
163
+ continue;
164
+ }
165
+
166
+ $plugin_info = get_plugin_data($file, true, false);
167
+
168
+ $plugin_new_version = isset($data->new_version) ? $data->new_version : '';
169
+
170
+ // Check if this plugin and this version has been checked/logged already.
171
+ if (! array_key_exists($key, $checked_updates)) {
172
+ $checked_updates[ $key ] = array(
173
+ 'checked_version' => null,
174
+ );
175
+ }
176
+
177
+ if ($checked_updates[ $key ]['checked_version'] == $plugin_new_version) {
178
+ // This version has been checked/logged already
179
+ continue;
180
+ }
181
+
182
+ $checked_updates[ $key ]['checked_version'] = $plugin_new_version;
183
+
184
+ $this->noticeMessage('plugin_update_available', array(
185
+ 'plugin_name' => isset($plugin_info['Name']) ? $plugin_info['Name'] : '',
186
+ 'plugin_current_version' => isset($plugin_info['Version']) ? $plugin_info['Version'] : '',
187
+ 'plugin_new_version' => $plugin_new_version,
188
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
189
+ // "plugin_info" => $plugin_info,
190
+ // "remote_plugin_info" => $remote_plugin_info,
191
+ // "active_plugins" => $active_plugins,
192
+ // "updates" => $updates
193
+ ));
194
+ } // End foreach().
195
+
196
+ update_option($option_key, $checked_updates);
197
+ } // function
198
+
199
+ function on_setted_update_update_themes($updates)
200
+ {
201
+
202
+ if (empty($updates->response) || ! is_array($updates->response)) {
203
+ return;
204
+ }
205
+
206
+ // update_option( "simplehistory_{$this->slug}_wp_core_version_available", $new_wp_core_version );
207
+ $option_key = "simplehistory_{$this->slug}_theme_updates_available";
208
+ $checked_updates = get_option($option_key);
209
+
210
+ if (! is_array($checked_updates)) {
211
+ $checked_updates = array();
212
+ }
213
+
214
+ // For each available update
215
+ foreach ($updates->response as $key => $data) {
216
+ $theme_info = wp_get_theme($key);
217
+
218
+ // $message .= "\n" . sprintf( __( "Theme: %s is out of date. Please update from version %s to %s", "wp-updates-notifier" ), $theme_info['Name'], $theme_info['Version'], $data['new_version'] ) . "\n";
219
+ $settings['notified']['theme'][ $key ] = $data['new_version']; // set theme version we are notifying about
220
+
221
+ $theme_new_version = isset($data['new_version']) ? $data['new_version'] : '';
222
+
223
+ // check if this plugin and this version has been checked/logged already
224
+ if (! array_key_exists($key, $checked_updates)) {
225
+ $checked_updates[ $key ] = array(
226
+ 'checked_version' => null,
227
+ );
228
+ }
229
+
230
+ if ($checked_updates[ $key ]['checked_version'] == $theme_new_version) {
231
+ // This version has been checked/logged already
232
+ continue;
233
+ }
234
+
235
+ $checked_updates[ $key ]['checked_version'] = $theme_new_version;
236
+
237
+ $this->noticeMessage('theme_update_available', array(
238
+ 'theme_name' => isset($theme_info['Name']) ? $theme_info['Name'] : '',
239
+ 'theme_current_version' => isset($theme_info['Version']) ? $theme_info['Version'] : '',
240
+ 'theme_new_version' => $theme_new_version,
241
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
242
+ // "plugin_info" => $plugin_info,
243
+ // "remote_plugin_info" => $remote_plugin_info,
244
+ // "active_plugins" => $active_plugins,
245
+ // "updates" => $updates,
246
+ ));
247
+ } // End foreach().
248
+
249
+ update_option($option_key, $checked_updates);
250
+ } // function
251
+
252
+ /**
253
+ * Append prev and current version of update object as details in the output
254
+ */
255
+ function getLogRowDetailsOutput($row)
256
+ {
257
+
258
+ $output = '';
259
+
260
+ $current_version = null;
261
+ $new_version = null;
262
+ $context_message_key = isset($row->context_message_key) ? $row->context_message_key : null;
263
+
264
+ $context = isset($row->context) ? $row->context : array();
265
+
266
+ switch ($context_message_key) {
267
+ case 'core_update_available':
268
+ $current_version = isset($context['wp_core_current_version']) ? $context['wp_core_current_version'] : null;
269
+ $new_version = isset($context['wp_core_new_version']) ? $context['wp_core_new_version'] : null;
270
+ break;
271
+
272
+ case 'plugin_update_available':
273
+ $current_version = isset($context['plugin_current_version']) ? $context['plugin_current_version'] : null;
274
+ $new_version = isset($context['plugin_new_version']) ? $context['plugin_new_version'] : null;
275
+ break;
276
+
277
+ case 'theme_update_available':
278
+ $current_version = isset($context['theme_current_version']) ? $context['theme_current_version'] : null;
279
+ $new_version = isset($context['theme_new_version']) ? $context['theme_new_version'] : null;
280
+ break;
281
+ }
282
+
283
+ if ($current_version && $new_version) {
284
+ $output .= '<p>';
285
+ $output .= '<span class="SimpleHistoryLogitem__inlineDivided">';
286
+ $output .= '<em>' . __('Available version', 'simple-history') . '</em> ' . esc_html($new_version);
287
+ $output .= '</span> ';
288
+
289
+ $output .= '<span class="SimpleHistoryLogitem__inlineDivided">';
290
+ $output .= '<em>' . __('Installed version', 'simple-history') . '</em> ' . esc_html($current_version);
291
+ $output .= '</span>';
292
+
293
+ $output .= '</p>';
294
+
295
+ // Add link to update-page, if user is allowed to that page.
296
+ $is_allowed_to_update_page = current_user_can('update_core') || current_user_can('update_themes') || current_user_can('update_plugins');
297
+
298
+ if ($is_allowed_to_update_page) {
299
+ $output .= sprintf('<p><a href="%1$s">', admin_url('update-core.php'));
300
+ $output .= __('View all updates', 'simple-history');
301
+ $output .= '</a></p>';
302
+ }
303
+ }
304
+
305
+ return $output;
306
+ }
307
+ } // class
 
 
 
 
 
 
 
 
 
308
 
309
  } // End if().
loggers/FileEditsLogger.php CHANGED
@@ -3,254 +3,252 @@
3
  /**
4
  * Logs edits to theme or plugin files done from Appearance -> Editor or Plugins -> Editor
5
  */
6
- class FileEditsLogger extends SimpleLogger {
7
-
8
- public $slug = __CLASS__;
9
-
10
- function getInfo() {
11
-
12
- $arr_info = array(
13
- 'name' => 'FileEditsLogger',
14
- 'description' => 'Logs edits to theme and plugin files',
15
- 'capability' => 'manage_options',
16
- 'messages' => array(
17
- 'theme_file_edited' => __( 'Edited file "{file_name}" in theme "{theme_name}"', 'simple-history' ),
18
- 'plugin_file_edited' => __( 'Edited file "{file_name}" in plugin "{plugin_name}"', 'simple-history' ),
19
- ),
20
- 'labels' => array(
21
- 'search' => array(
22
- 'label' => _x( 'Edited theme and plugin files', 'Plugin logger: file edits', 'simple-history' ),
23
- 'label_all' => _x( 'All file edits', 'Plugin logger: file edits', 'simple-history' ),
24
- 'options' => array(
25
- _x( 'Edited theme files', 'Plugin logger: file edits', 'simple-history' ) => array(
26
- 'theme_file_edited'
27
- ),
28
- _x( 'Edited plugin files', 'Plugin logger: file edits', 'simple-history' ) => array(
29
- 'plugin_file_edited',
30
- ),
31
- ),
32
- ),// search array
33
- ),// labels
34
- );
35
-
36
- return $arr_info;
37
-
38
- }
39
-
40
- function loaded() {
41
- add_action( 'load-theme-editor.php', array( $this, 'on_load_theme_editor' ), 10, 1 );
42
- add_action( 'load-plugin-editor.php', array( $this, 'on_load_plugin_editor' ), 10, 1 );
43
- }
44
-
45
- /**
46
- * Called when /wp/wp-admin/plugin-editor.php is loaded
47
- * Both using regular GET and during POST with updated file data
48
- *
49
- * todo:
50
- * - log edits
51
- * - log failed edits that result in error and plugin deactivation
52
- */
53
- public function on_load_plugin_editor() {
54
- if ( isset( $_POST ) && isset( $_POST['action'] ) ) {
55
-
56
- $action = isset( $_POST['action'] ) ? $_POST['action'] : null;
57
- $file = isset( $_POST['file'] ) ? $_POST['file'] : null;
58
- $plugin_file = isset( $_POST['plugin'] ) ? $_POST['plugin'] : null;
59
- $fileNewContents = isset( $_POST['newcontent'] ) ? wp_unslash( $_POST['newcontent'] ) : null;
60
- $scrollto = isset( $_POST['scrollto'] ) ? (int) $_POST['scrollto'] : 0;
61
-
62
- // if 'phperror' is set then there was an error and an edit is done and wp tries to activate the plugin again
63
- // $phperror = isset($_POST["phperror"]) ? $_POST["phperror"] : null;
64
- // Get info about the edited plugin
65
- $pluginInfo = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_file );
66
- $pluginName = isset( $pluginInfo['Name'] ) ? $pluginInfo['Name'] : null;
67
- $pluginVersion = isset( $pluginInfo['Version'] ) ? $pluginInfo['Version'] : null;
68
-
69
- // Get contents before save
70
- $fileContentsBeforeEdit = file_get_contents( WP_PLUGIN_DIR . '/' . $file );
71
-
72
- $context = array(
73
- 'file_name' => $plugin_file,
74
- 'plugin_name' => $pluginName,
75
- 'plugin_version' => $pluginVersion,
76
- 'old_file_contents' => $fileContentsBeforeEdit,
77
- 'new_file_contents' => $fileNewContents,
78
- '_occasionsID' => __CLASS__ . '/' . __FUNCTION__ . "/file-edit/$plugin_file/$file",
79
- );
80
-
81
- $loggerInstance = $this;
82
- add_filter( 'wp_redirect', function ( $location, $status ) use ( $context, $loggerInstance ) {
83
- $locationParsed = parse_url( $location );
84
-
85
- if ( $locationParsed === false || empty( $locationParsed['query'] ) ) {
86
- return $location;
87
- }
88
-
89
- parse_str( $locationParsed['query'], $queryStringParsed );
90
- // ddd($_POST, $context, $queryStringParsed, $location);
91
- if ( empty( $queryStringParsed ) ) {
92
- return $location;
93
- }
94
-
95
- // If query string "a=te" exists or "liveupdate=1" then plugin file was updated
96
- $teIsSet = isset( $queryStringParsed['a'] ) && $queryStringParsed['a'] === 'te';
97
- $liveUpdateIsSet = isset( $queryStringParsed['liveupdate'] ) && $queryStringParsed['liveupdate'] === '1';
98
- if ( $teIsSet || $liveUpdateIsSet ) {
99
- // File was updated
100
- $loggerInstance->infoMessage( 'plugin_file_edited', $context );
101
- }
102
-
103
- return $location;
104
-
105
- // location when successful edit to non-active plugin
106
- // http://wp-playground.dev/wp/wp-admin/plugin-editor.php?file=akismet/akismet.php&plugin=akismet/akismet.php&a=te&scrollto=0
107
- // locations when activated plugin edited successfully
108
- // plugin-editor.php?file=akismet%2Fakismet.php&plugin=akismet%2Fakismet.php&liveupdate=1&scrollto=0&networkwide&_wpnonce=b3f399fe94
109
- // plugin-editor.php?file=akismet%2Fakismet.php&phperror=1&_error_nonce=63511c266d
110
- // http://wp-playground.dev/wp/wp-admin/plugin-editor.php?file=akismet/akismet.php&plugin=akismet/akismet.php&a=te&scrollto=0
111
- // locations when editing active plugin and error occurs
112
- // plugin-editor.php?file=akismet%2Fakismet.php&plugin=akismet%2Fakismet.php&liveupdate=1&scrollto=0&networkwide&_wpnonce=b3f399fe94
113
- // plugin-editor.php?file=akismet%2Fakismet.php&phperror=1&_error_nonce=63511c266d
114
- // locations when error edit is fixed and saved and plugin is activated again
115
- // plugin-editor.php?file=akismet%2Fakismet.php&plugin=akismet%2Fakismet.php&liveupdate=1&scrollto=0&networkwide&_wpnonce=b3f399fe94
116
- // plugin-editor.php?file=akismet%2Fakismet.php&phperror=1&_error_nonce=63511c266d
117
- // http://wp-playground.dev/wp/wp-admin/plugin-editor.php?file=akismet/akismet.php&plugin=akismet/akismet.php&a=te&scrollto=0
118
- }, 10, 2 );
119
-
120
- }// End if().
121
- /*
122
- <?php if (isset($_GET['a'])) : ?>
123
- <div id="message" class="updated notice is-dismissible"><p><?php _e('File edited successfully.') ?></p></div>
124
- <?php elseif (isset($_GET['phperror'])) : ?>
125
- <div id="message" class="updated"><p><?php _e('This plugin has been deactivated because your changes resulted in a <strong>fatal error</strong>.') ?></p>
126
- */
127
- }
128
-
129
- /**
130
- * Called when /wp/wp-admin/theme-editor.php is loaded
131
- * Both using regular GET and during POST with updated file data
132
- *
133
- * When this action is fired we don't know if a file will be successfully saved or not.
134
- * There are no filters/actions fired when the edit is saved. On the end wp_redirect() is
135
- * called however and we know the location for the redirect and wp_redirect() has filters
136
- * so we hook onto that to save the edit.
137
- */
138
- public function on_load_theme_editor() {
139
- // Only continue if method is post and action is update
140
- if ( isset( $_POST ) && isset( $_POST['action'] ) && $_POST['action'] === 'update' ) {
141
- /*
142
- POST data is like
143
- array(8)
144
- '_wpnonce' => string(10) "9b5e46634f"
145
- '_wp_http_referer' => string(88) "/wp/wp-admin/theme-editor.php?file=style.css&theme=twentyfifteen&scrollto=0&upda…"
146
- 'newcontent' => string(104366) "/* Theme Name: Twenty Fifteen Theme URI: https://wordpress.org/themes/twentyfift…"
147
- 'action' => string(6) "update"
148
- 'file' => string(9) "style.css"
149
- 'theme' => string(13) "twentyfifteen"
150
- 'scrollto' => string(3) "638"
151
- 'submit' => string(11) "Update File"
152
- */
153
-
154
- $action = isset( $_POST['action'] ) ? $_POST['action'] : null;
155
- $file = isset( $_POST['file'] ) ? $_POST['file'] : null;
156
- $theme = isset( $_POST['theme'] ) ? $_POST['theme'] : null;
157
- $fileNewContents = isset( $_POST['newcontent'] ) ? wp_unslash( $_POST['newcontent'] ) : null;
158
- $scrollto = isset( $_POST['scrollto'] ) ? (int) $_POST['scrollto'] : 0;
159
-
160
- // Same code as in theme-editor.php
161
- if ( $theme ) {
162
- $stylesheet = $theme;
163
- } else {
164
- $stylesheet = get_stylesheet();
165
- }
166
-
167
- $theme = wp_get_theme( $stylesheet );
168
-
169
- if ( ! is_a( $theme, 'WP_Theme' ) ) {
170
- return;
171
- }
172
-
173
- // Same code as in theme-editor.php
174
- $relative_file = $file;
175
- $file = $theme->get_stylesheet_directory() . '/' . $relative_file;
176
-
177
- // Get file contents, so we have something to compare with later
178
- $fileContentsBeforeEdit = file_get_contents( $file );
179
-
180
- $context = array(
181
- 'theme_name' => $theme->name,
182
- 'theme_stylesheet_path' => $theme->get_stylesheet(),
183
- 'theme_stylesheet_dir' => $theme->get_stylesheet_directory(),
184
- 'file_name' => $relative_file,
185
- 'file_dir' => $file,
186
- 'old_file_contents' => $fileContentsBeforeEdit,
187
- 'new_file_contents' => $fileNewContents,
188
- '_occasionsID' => __CLASS__ . '/' . __FUNCTION__ . "/file-edit/$file",
189
- );
190
-
191
- // Hook into wp_redirect
192
- // This hook is only added when we know a POST is done from theme-editor.php
193
- $loggerInstance = $this;
194
- add_filter( 'wp_redirect', function ( $location, $status ) use ( $context, $loggerInstance ) {
195
- $locationParsed = parse_url( $location );
196
-
197
- if ( $locationParsed === false || empty( $locationParsed['query'] ) ) {
198
- return $location;
199
- }
200
-
201
- parse_str( $locationParsed['query'], $queryStringParsed );
202
-
203
- if ( empty( $queryStringParsed ) ) {
204
- return $location;
205
- }
206
-
207
- if ( isset( $queryStringParsed['updated'] ) && $queryStringParsed['updated'] ) {
208
- // File was updated
209
- $loggerInstance->infoMessage( 'theme_file_edited', $context );
210
- } else {
211
- // File was not updated. Unknown reason, but probably because could not be written.
212
- }
213
-
214
- return $location;
215
-
216
- }, 10, 2 ); // add_filter
217
- } // End if().
218
- }
219
-
220
-
221
- public function getLogRowDetailsOutput( $row ) {
222
-
223
- $context = $row->context;
224
- $message_key = isset( $context['_message_key'] ) ? $context['_message_key'] : null;
225
-
226
- if ( ! $message_key ) {
227
- return;
228
- }
229
-
230
- $out = '';
231
-
232
- $diff_table_output = '';
233
-
234
- if ( ! empty( $context['new_file_contents'] ) && ! empty( $context['old_file_contents'] ) ) {
235
- if ( $context['new_file_contents'] !== $context['old_file_contents'] ) {
236
- $diff_table_output .= sprintf(
237
- '<tr><td>%1$s</td><td>%2$s</td></tr>',
238
- __( 'File contents', 'simple-history' ),
239
- simple_history_text_diff( $context['old_file_contents'], $context['new_file_contents'] )
240
- );
241
- }
242
- }
243
-
244
- if ( $diff_table_output ) {
245
-
246
- $diff_table_output = '<table class="SimpleHistoryLogitem__keyValueTable">' . $diff_table_output . '</table>';
247
-
248
- }
249
-
250
- $out .= $diff_table_output;
251
-
252
- return $out;
253
-
254
- }
255
-
256
  } // class
3
  /**
4
  * Logs edits to theme or plugin files done from Appearance -> Editor or Plugins -> Editor
5
  */
6
+ class FileEditsLogger extends SimpleLogger
7
+ {
8
+
9
+ public $slug = __CLASS__;
10
+
11
+ function getInfo()
12
+ {
13
+
14
+ $arr_info = array(
15
+ 'name' => 'FileEditsLogger',
16
+ 'description' => 'Logs edits to theme and plugin files',
17
+ 'capability' => 'manage_options',
18
+ 'messages' => array(
19
+ 'theme_file_edited' => __('Edited file "{file_name}" in theme "{theme_name}"', 'simple-history'),
20
+ 'plugin_file_edited' => __('Edited file "{file_name}" in plugin "{plugin_name}"', 'simple-history'),
21
+ ),
22
+ 'labels' => array(
23
+ 'search' => array(
24
+ 'label' => _x('Edited theme and plugin files', 'Plugin logger: file edits', 'simple-history'),
25
+ 'label_all' => _x('All file edits', 'Plugin logger: file edits', 'simple-history'),
26
+ 'options' => array(
27
+ _x('Edited theme files', 'Plugin logger: file edits', 'simple-history') => array(
28
+ 'theme_file_edited'
29
+ ),
30
+ _x('Edited plugin files', 'Plugin logger: file edits', 'simple-history') => array(
31
+ 'plugin_file_edited',
32
+ ),
33
+ ),
34
+ ),// search array
35
+ ),// labels
36
+ );
37
+
38
+ return $arr_info;
39
+ }
40
+
41
+ function loaded()
42
+ {
43
+ add_action('load-theme-editor.php', array( $this, 'on_load_theme_editor' ), 10, 1);
44
+ add_action('load-plugin-editor.php', array( $this, 'on_load_plugin_editor' ), 10, 1);
45
+ }
46
+
47
+ /**
48
+ * Called when /wp/wp-admin/plugin-editor.php is loaded
49
+ * Both using regular GET and during POST with updated file data
50
+ *
51
+ * todo:
52
+ * - log edits
53
+ * - log failed edits that result in error and plugin deactivation
54
+ */
55
+ public function on_load_plugin_editor()
56
+ {
57
+ if (isset($_POST) && isset($_POST['action'])) {
58
+ $action = isset($_POST['action']) ? $_POST['action'] : null;
59
+ $file = isset($_POST['file']) ? $_POST['file'] : null;
60
+ $plugin_file = isset($_POST['plugin']) ? $_POST['plugin'] : null;
61
+ $fileNewContents = isset($_POST['newcontent']) ? wp_unslash($_POST['newcontent']) : null;
62
+ $scrollto = isset($_POST['scrollto']) ? (int) $_POST['scrollto'] : 0;
63
+
64
+ // if 'phperror' is set then there was an error and an edit is done and wp tries to activate the plugin again
65
+ // $phperror = isset($_POST["phperror"]) ? $_POST["phperror"] : null;
66
+ // Get info about the edited plugin
67
+ $pluginInfo = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin_file);
68
+ $pluginName = isset($pluginInfo['Name']) ? $pluginInfo['Name'] : null;
69
+ $pluginVersion = isset($pluginInfo['Version']) ? $pluginInfo['Version'] : null;
70
+
71
+ // Get contents before save
72
+ $fileContentsBeforeEdit = file_get_contents(WP_PLUGIN_DIR . '/' . $file);
73
+
74
+ $context = array(
75
+ 'file_name' => $plugin_file,
76
+ 'plugin_name' => $pluginName,
77
+ 'plugin_version' => $pluginVersion,
78
+ 'old_file_contents' => $fileContentsBeforeEdit,
79
+ 'new_file_contents' => $fileNewContents,
80
+ '_occasionsID' => __CLASS__ . '/' . __FUNCTION__ . "/file-edit/$plugin_file/$file",
81
+ );
82
+
83
+ $loggerInstance = $this;
84
+ add_filter('wp_redirect', function ($location, $status) use ($context, $loggerInstance) {
85
+ $locationParsed = parse_url($location);
86
+
87
+ if ($locationParsed === false || empty($locationParsed['query'])) {
88
+ return $location;
89
+ }
90
+
91
+ parse_str($locationParsed['query'], $queryStringParsed);
92
+ // ddd($_POST, $context, $queryStringParsed, $location);
93
+ if (empty($queryStringParsed)) {
94
+ return $location;
95
+ }
96
+
97
+ // If query string "a=te" exists or "liveupdate=1" then plugin file was updated
98
+ $teIsSet = isset($queryStringParsed['a']) && $queryStringParsed['a'] === 'te';
99
+ $liveUpdateIsSet = isset($queryStringParsed['liveupdate']) && $queryStringParsed['liveupdate'] === '1';
100
+ if ($teIsSet || $liveUpdateIsSet) {
101
+ // File was updated
102
+ $loggerInstance->infoMessage('plugin_file_edited', $context);
103
+ }
104
+
105
+ return $location;
106
+
107
+ // location when successful edit to non-active plugin
108
+ // http://wp-playground.dev/wp/wp-admin/plugin-editor.php?file=akismet/akismet.php&plugin=akismet/akismet.php&a=te&scrollto=0
109
+ // locations when activated plugin edited successfully
110
+ // plugin-editor.php?file=akismet%2Fakismet.php&plugin=akismet%2Fakismet.php&liveupdate=1&scrollto=0&networkwide&_wpnonce=b3f399fe94
111
+ // plugin-editor.php?file=akismet%2Fakismet.php&phperror=1&_error_nonce=63511c266d
112
+ // http://wp-playground.dev/wp/wp-admin/plugin-editor.php?file=akismet/akismet.php&plugin=akismet/akismet.php&a=te&scrollto=0
113
+ // locations when editing active plugin and error occurs
114
+ // plugin-editor.php?file=akismet%2Fakismet.php&plugin=akismet%2Fakismet.php&liveupdate=1&scrollto=0&networkwide&_wpnonce=b3f399fe94
115
+ // plugin-editor.php?file=akismet%2Fakismet.php&phperror=1&_error_nonce=63511c266d
116
+ // locations when error edit is fixed and saved and plugin is activated again
117
+ // plugin-editor.php?file=akismet%2Fakismet.php&plugin=akismet%2Fakismet.php&liveupdate=1&scrollto=0&networkwide&_wpnonce=b3f399fe94
118
+ // plugin-editor.php?file=akismet%2Fakismet.php&phperror=1&_error_nonce=63511c266d
119
+ // http://wp-playground.dev/wp/wp-admin/plugin-editor.php?file=akismet/akismet.php&plugin=akismet/akismet.php&a=te&scrollto=0
120
+ }, 10, 2);
121
+ }// End if().
122
+ /*
123
+ <?php if (isset($_GET['a'])) : ?>
124
+ <div id="message" class="updated notice is-dismissible"><p><?php _e('File edited successfully.') ?></p></div>
125
+ <?php elseif (isset($_GET['phperror'])) : ?>
126
+ <div id="message" class="updated"><p><?php _e('This plugin has been deactivated because your changes resulted in a <strong>fatal error</strong>.') ?></p>
127
+ */
128
+ }
129
+
130
+ /**
131
+ * Called when /wp/wp-admin/theme-editor.php is loaded
132
+ * Both using regular GET and during POST with updated file data
133
+ *
134
+ * When this action is fired we don't know if a file will be successfully saved or not.
135
+ * There are no filters/actions fired when the edit is saved. On the end wp_redirect() is
136
+ * called however and we know the location for the redirect and wp_redirect() has filters
137
+ * so we hook onto that to save the edit.
138
+ */
139
+ public function on_load_theme_editor()
140
+ {
141
+ // Only continue if method is post and action is update
142
+ if (isset($_POST) && isset($_POST['action']) && $_POST['action'] === 'update') {
143
+ /*
144
+ POST data is like
145
+ array(8)
146
+ '_wpnonce' => string(10) "9b5e46634f"
147
+ '_wp_http_referer' => string(88) "/wp/wp-admin/theme-editor.php?file=style.css&theme=twentyfifteen&scrollto=0&upda…"
148
+ 'newcontent' => string(104366) "/* Theme Name: Twenty Fifteen Theme URI: https://wordpress.org/themes/twentyfift…"
149
+ 'action' => string(6) "update"
150
+ 'file' => string(9) "style.css"
151
+ 'theme' => string(13) "twentyfifteen"
152
+ 'scrollto' => string(3) "638"
153
+ 'submit' => string(11) "Update File"
154
+ */
155
+
156
+ $action = isset($_POST['action']) ? $_POST['action'] : null;
157
+ $file = isset($_POST['file']) ? $_POST['file'] : null;
158
+ $theme = isset($_POST['theme']) ? $_POST['theme'] : null;
159
+ $fileNewContents = isset($_POST['newcontent']) ? wp_unslash($_POST['newcontent']) : null;
160
+ $scrollto = isset($_POST['scrollto']) ? (int) $_POST['scrollto'] : 0;
161
+
162
+ // Same code as in theme-editor.php
163
+ if ($theme) {
164
+ $stylesheet = $theme;
165
+ } else {
166
+ $stylesheet = get_stylesheet();
167
+ }
168
+
169
+ $theme = wp_get_theme($stylesheet);
170
+
171
+ if (! is_a($theme, 'WP_Theme')) {
172
+ return;
173
+ }
174
+
175
+ // Same code as in theme-editor.php
176
+ $relative_file = $file;
177
+ $file = $theme->get_stylesheet_directory() . '/' . $relative_file;
178
+
179
+ // Get file contents, so we have something to compare with later
180
+ $fileContentsBeforeEdit = file_get_contents($file);
181
+
182
+ $context = array(
183
+ 'theme_name' => $theme->name,
184
+ 'theme_stylesheet_path' => $theme->get_stylesheet(),
185
+ 'theme_stylesheet_dir' => $theme->get_stylesheet_directory(),
186
+ 'file_name' => $relative_file,
187
+ 'file_dir' => $file,
188
+ 'old_file_contents' => $fileContentsBeforeEdit,
189
+ 'new_file_contents' => $fileNewContents,
190
+ '_occasionsID' => __CLASS__ . '/' . __FUNCTION__ . "/file-edit/$file",
191
+ );
192
+
193
+ // Hook into wp_redirect
194
+ // This hook is only added when we know a POST is done from theme-editor.php
195
+ $loggerInstance = $this;
196
+ add_filter('wp_redirect', function ($location, $status) use ($context, $loggerInstance) {
197
+ $locationParsed = parse_url($location);
198
+
199
+ if ($locationParsed === false || empty($locationParsed['query'])) {
200
+ return $location;
201
+ }
202
+
203
+ parse_str($locationParsed['query'], $queryStringParsed);
204
+
205
+ if (empty($queryStringParsed)) {
206
+ return $location;
207
+ }
208
+
209
+ if (isset($queryStringParsed['updated']) && $queryStringParsed['updated']) {
210
+ // File was updated
211
+ $loggerInstance->infoMessage('theme_file_edited', $context);
212
+ } else {
213
+ // File was not updated. Unknown reason, but probably because could not be written.
214
+ }
215
+
216
+ return $location;
217
+ }, 10, 2); // add_filter
218
+ } // End if().
219
+ }
220
+
221
+
222
+ public function getLogRowDetailsOutput($row)
223
+ {
224
+
225
+ $context = $row->context;
226
+ $message_key = isset($context['_message_key']) ? $context['_message_key'] : null;
227
+
228
+ if (! $message_key) {
229
+ return;
230
+ }
231
+
232
+ $out = '';
233
+
234
+ $diff_table_output = '';
235
+
236
+ if (! empty($context['new_file_contents']) && ! empty($context['old_file_contents'])) {
237
+ if ($context['new_file_contents'] !== $context['old_file_contents']) {
238
+ $diff_table_output .= sprintf(
239
+ '<tr><td>%1$s</td><td>%2$s</td></tr>',
240
+ __('File contents', 'simple-history'),
241
+ simple_history_text_diff($context['old_file_contents'], $context['new_file_contents'])
242
+ );
243
+ }
244
+ }
245
+
246
+ if ($diff_table_output) {
247
+ $diff_table_output = '<table class="SimpleHistoryLogitem__keyValueTable">' . $diff_table_output . '</table>';
248
+ }
249
+
250
+ $out .= $diff_table_output;
251
+
252
+ return $out;
253
+ }
 
 
254
  } // class
loggers/PluginEnableMediaReplaceLogger.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /**
6
  * Logs attachments updated with the great Enable Media Replace plugin
@@ -8,98 +8,97 @@ defined( 'ABSPATH' ) or die();
8
  *
9
  * @since 2.2
10
  */
11
- class PluginEnableMediaReplaceLogger extends SimpleLogger {
12
-
13
- public $slug = __CLASS__;
14
-
15
- /**
16
- * Get array with information about this logger
17
- *
18
- * @return array
19
- */
20
- function getInfo() {
21
-
22
- $arr_info = array(
23
- 'name' => _x( 'Enable Media Replace Logger', 'PluginEnableMediaReplaceLogger', 'simple-history' ),
24
- 'description' => _x( 'Logs media updates made with the Enable Media Replace Plugin', 'PluginEnableMediaReplaceLogger', 'simple-history' ),
25
- 'name_via' => _x( 'Using plugin Enable Media Replace', 'PluginUserSwitchingLogger', 'simple-history' ),
26
- 'capability' => 'upload_files',
27
- 'messages' => array(
28
- 'replaced_file' => _x( 'Replaced attachment "{prev_attachment_title}" with new attachment "{new_attachment_title}"', 'PluginEnableMediaReplaceLogger', 'simple-history' ),
29
- ),
30
- );
31
-
32
- return $arr_info;
33
-
34
- }
35
-
36
- function loaded() {
37
-
38
- // Action that is called when Enable Media Replace loads it's admin options page (both when viewing and when posting new file to it)
39
- add_action( 'load-media_page_enable-media-replace/enable-media-replace', array( $this, 'on_load_plugin_admin_page' ), 10, 1 );
40
- }
41
-
42
- function on_load_plugin_admin_page() {
43
-
44
- if ( empty( $_POST ) ) {
45
- return;
46
- }
47
-
48
- if ( isset( $_GET['action'] ) && $_GET['action'] == 'media_replace_upload' ) {
49
-
50
- $attachment_id = empty( $_POST['ID'] ) ? null : (int) $_POST['ID'];
51
- $replace_type = empty( $_POST['replace_type'] ) ? null : sanitize_text_field( $_POST['replace_type'] );
52
- $new_file = empty( $_FILES['userfile'] ) ? null : (array) $_FILES['userfile'];
53
-
54
- $prev_attachment_post = get_post( $attachment_id );
55
-
56
- if ( empty( $attachment_id ) || empty( $new_file ) || empty( $prev_attachment_post ) ) {
57
- return;
58
- }
59
-
60
- /*
61
- get {
62
- "page": "enable-media-replace\/enable-media-replace.php",
63
- "noheader": "true",
64
- "action": "media_replace_upload",
65
- "attachment_id": "64085",
66
- "_wpnonce": "1089573e0c"
67
- }
68
-
69
- post {
70
- "ID": "64085",
71
- "replace_type": "replace"
72
- }
73
-
74
- files {
75
- "userfile": {
76
- "name": "earth-transparent.png",
77
- "type": "image\/png",
78
- "tmp_name": "\/Applications\/MAMP\/tmp\/php\/phpKA2XOo",
79
- "error": 0,
80
- "size": 4325729
81
- }
82
- }
83
- */
84
-
85
- $this->infoMessage('replaced_file', array(
86
- 'attachment_id' => $attachment_id,
87
- 'prev_attachment_title' => get_the_title( $prev_attachment_post ),
88
- 'new_attachment_title' => $new_file['name'],
89
- 'new_attachment_type' => $new_file['type'],
90
- 'new_attachment_size' => $new_file['size'],
91
- 'replace_type' => $replace_type,
92
- /*
93
- "get" => $_GET,
94
- "post" => $_POST,
95
- "files" => $_FILES,
96
- "old_attachment_post" => $prev_attachment_post,
97
- "old_attachment_meta" => $prev_attachment_meta
98
- */
99
- ));
100
-
101
- }// End if().
102
-
103
- }
104
-
105
  }
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logs attachments updated with the great Enable Media Replace plugin
8
  *
9
  * @since 2.2
10
  */
11
+ class PluginEnableMediaReplaceLogger extends SimpleLogger
12
+ {
13
+
14
+ public $slug = __CLASS__;
15
+
16
+ /**
17
+ * Get array with information about this logger
18
+ *
19
+ * @return array
20
+ */
21
+ function getInfo()
22
+ {
23
+
24
+ $arr_info = array(
25
+ 'name' => _x('Enable Media Replace Logger', 'PluginEnableMediaReplaceLogger', 'simple-history'),
26
+ 'description' => _x('Logs media updates made with the Enable Media Replace Plugin', 'PluginEnableMediaReplaceLogger', 'simple-history'),
27
+ 'name_via' => _x('Using plugin Enable Media Replace', 'PluginUserSwitchingLogger', 'simple-history'),
28
+ 'capability' => 'upload_files',
29
+ 'messages' => array(
30
+ 'replaced_file' => _x('Replaced attachment "{prev_attachment_title}" with new attachment "{new_attachment_title}"', 'PluginEnableMediaReplaceLogger', 'simple-history'),
31
+ ),
32
+ );
33
+
34
+ return $arr_info;
35
+ }
36
+
37
+ function loaded()
38
+ {
39
+
40
+ // Action that is called when Enable Media Replace loads it's admin options page (both when viewing and when posting new file to it)
41
+ add_action('load-media_page_enable-media-replace/enable-media-replace', array( $this, 'on_load_plugin_admin_page' ), 10, 1);
42
+ }
43
+
44
+ function on_load_plugin_admin_page()
45
+ {
46
+
47
+ if (empty($_POST)) {
48
+ return;
49
+ }
50
+
51
+ if (isset($_GET['action']) && $_GET['action'] == 'media_replace_upload') {
52
+ $attachment_id = empty($_POST['ID']) ? null : (int) $_POST['ID'];
53
+ $replace_type = empty($_POST['replace_type']) ? null : sanitize_text_field($_POST['replace_type']);
54
+ $new_file = empty($_FILES['userfile']) ? null : (array) $_FILES['userfile'];
55
+
56
+ $prev_attachment_post = get_post($attachment_id);
57
+
58
+ if (empty($attachment_id) || empty($new_file) || empty($prev_attachment_post)) {
59
+ return;
60
+ }
61
+
62
+ /*
63
+ get {
64
+ "page": "enable-media-replace\/enable-media-replace.php",
65
+ "noheader": "true",
66
+ "action": "media_replace_upload",
67
+ "attachment_id": "64085",
68
+ "_wpnonce": "1089573e0c"
69
+ }
70
+
71
+ post {
72
+ "ID": "64085",
73
+ "replace_type": "replace"
74
+ }
75
+
76
+ files {
77
+ "userfile": {
78
+ "name": "earth-transparent.png",
79
+ "type": "image\/png",
80
+ "tmp_name": "\/Applications\/MAMP\/tmp\/php\/phpKA2XOo",
81
+ "error": 0,
82
+ "size": 4325729
83
+ }
84
+ }
85
+ */
86
+
87
+ $this->infoMessage('replaced_file', array(
88
+ 'attachment_id' => $attachment_id,
89
+ 'prev_attachment_title' => get_the_title($prev_attachment_post),
90
+ 'new_attachment_title' => $new_file['name'],
91
+ 'new_attachment_type' => $new_file['type'],
92
+ 'new_attachment_size' => $new_file['size'],
93
+ 'replace_type' => $replace_type,
94
+ /*
95
+ "get" => $_GET,
96
+ "post" => $_POST,
97
+ "files" => $_FILES,
98
+ "old_attachment_post" => $prev_attachment_post,
99
+ "old_attachment_meta" => $prev_attachment_meta
100
+ */
101
+ ));
102
+ }// End if().
103
+ }
 
104
  }
loggers/PluginUserSwitchingLogger.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /**
6
  * Logs user switching from the great User Switching plugin
@@ -8,137 +8,133 @@ defined( 'ABSPATH' ) or die();
8
  *
9
  * @since 2.2
10
  */
11
- class PluginUserSwitchingLogger extends SimpleLogger {
12
-
13
- public $slug = __CLASS__;
14
-
15
- /**
16
- * Get array with information about this logger
17
- *
18
- * @return array
19
- */
20
- function getInfo() {
21
-
22
- $arr_info = array(
23
- 'name' => _x( 'User Switching Logger', 'PluginUserSwitchingLogger', 'simple-history' ),
24
- 'description' => _x( 'Logs user switches', 'PluginUserSwitchingLogger', 'simple-history' ),
25
- // Definition of via: by way of, through the medium or agency of; also : by means of
26
- 'name_via' => _x( 'Using plugin User Switching', 'PluginUserSwitchingLogger', 'simple-history' ),
27
- 'capability' => 'edit_users',
28
- 'messages' => array(
29
- 'switched_to_user' => _x( 'Switched to user "{user_login_to}" from user "{user_login_from}"', 'PluginUserSwitchingLogger', 'simple-history' ),
30
- 'switched_back_user' => _x( 'Switched back to user "{user_login_to}" from user "{user_login_from}"', 'PluginUserSwitchingLogger', 'simple-history' ),
31
- 'switched_back_themself' => _x( 'Switched back to user "{user_login_to}"', 'PluginUserSwitchingLogger', 'simple-history' ),
32
- 'switched_off_user' => _x( 'Switched off user "{user_login}"', 'PluginUserSwitchingLogger', 'simple-history' ),
33
- ),
34
- );
35
-
36
- return $arr_info;
37
-
38
- }
39
-
40
- function loaded() {
41
-
42
- add_action( 'switch_to_user', array( $this, 'on_switch_to_user' ), 10, 2 );
43
- add_action( 'switch_back_user', array( $this, 'on_switch_back_user' ), 10, 2 );
44
- add_action( 'switch_off_user', array( $this, 'on_switch_off_user' ), 10, 1 );
45
-
46
- }
47
-
48
- function on_switch_to_user( $user_id, $old_user_id ) {
49
-
50
- $user_to = get_user_by( 'id', $user_id );
51
- $user_from = get_user_by( 'id', $old_user_id );
52
-
53
- if ( ! is_a( $user_to, 'WP_User' ) || ! is_a( $user_from, 'WP_User' ) ) {
54
- return;
55
- }
56
-
57
- $this->infoMessage(
58
- 'switched_to_user',
59
- array(
60
- // It is the old user who initiates the switching
61
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
62
- '_user_id' => $old_user_id,
63
- 'user_id' => $user_id,
64
- 'old_user_id' => $old_user_id,
65
- 'user_login_to' => $user_to->user_login,
66
- 'user_login_from' => $user_from->user_login,
67
- )
68
- );
69
-
70
- }
71
-
72
- /**
73
- * Function is called when a user switches back to their originating account.
74
- * When you switch back after being logged off the
75
- *
76
- * Note: $old_user_id parameter is boolean false because there is no old user.
77
- *
78
- * @param int $user_id The ID of the user being switched back to.
79
- * @param int|false $old_user_id The ID of the user being switched from, or false if the user is switching back
80
- * after having been switched off.
81
- */
82
- function on_switch_back_user( $user_id, $old_user_id ) {
83
-
84
- $user_to = get_user_by( 'id', $user_id );
85
-
86
- $user_from = $old_user_id == false ? null : get_user_by( 'id', $old_user_id );
87
-
88
- if ( ! is_a( $user_to, 'WP_User' ) ) {
89
- return;
90
- }
91
-
92
- if ( $user_from ) {
93
-
94
- // User switched back from another user
95
- $this->infoMessage(
96
- 'switched_back_user',
97
- array(
98
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
99
- '_user_id' => $old_user_id,
100
- 'user_id' => $user_id,
101
- 'old_user_id' => $old_user_id,
102
- 'user_login_to' => $user_to->user_login,
103
- 'user_login_from' => $user_from->user_login,
104
- )
105
- );
106
-
107
- } else {
108
-
109
- // User switched back to themself (no prev user)
110
- $this->infoMessage(
111
- 'switched_back_themself',
112
- array(
113
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
114
- '_user_id' => $user_id,
115
- 'user_login_to' => $user_to->user_login,
116
- )
117
- );
118
-
119
- }
120
-
121
- }
122
-
123
- function on_switch_off_user( $user_id ) {
124
-
125
- $user = get_user_by( 'id', $user_id );
126
-
127
- if ( ! is_a( $user, 'WP_User' ) ) {
128
- return;
129
- }
130
-
131
- $this->infoMessage(
132
- 'switched_off_user',
133
- array(
134
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
135
- '_user_id' => $user_id,
136
-
137
- 'user_id' => $user_id,
138
- 'user_login' => $user->user_login,
139
- )
140
- );
141
-
142
- }
143
-
144
  }
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logs user switching from the great User Switching plugin
8
  *
9
  * @since 2.2
10
  */
11
+ class PluginUserSwitchingLogger extends SimpleLogger
12
+ {
13
+
14
+ public $slug = __CLASS__;
15
+
16
+ /**
17
+ * Get array with information about this logger
18
+ *
19
+ * @return array
20
+ */
21
+ function getInfo()
22
+ {
23
+
24
+ $arr_info = array(
25
+ 'name' => _x('User Switching Logger', 'PluginUserSwitchingLogger', 'simple-history'),
26
+ 'description' => _x('Logs user switches', 'PluginUserSwitchingLogger', 'simple-history'),
27
+ // Definition of via: by way of, through the medium or agency of; also : by means of
28
+ 'name_via' => _x('Using plugin User Switching', 'PluginUserSwitchingLogger', 'simple-history'),
29
+ 'capability' => 'edit_users',
30
+ 'messages' => array(
31
+ 'switched_to_user' => _x('Switched to user "{user_login_to}" from user "{user_login_from}"', 'PluginUserSwitchingLogger', 'simple-history'),
32
+ 'switched_back_user' => _x('Switched back to user "{user_login_to}" from user "{user_login_from}"', 'PluginUserSwitchingLogger', 'simple-history'),
33
+ 'switched_back_themself' => _x('Switched back to user "{user_login_to}"', 'PluginUserSwitchingLogger', 'simple-history'),
34
+ 'switched_off_user' => _x('Switched off user "{user_login}"', 'PluginUserSwitchingLogger', 'simple-history'),
35
+ ),
36
+ );
37
+
38
+ return $arr_info;
39
+ }
40
+
41
+ function loaded()
42
+ {
43
+
44
+ add_action('switch_to_user', array( $this, 'on_switch_to_user' ), 10, 2);
45
+ add_action('switch_back_user', array( $this, 'on_switch_back_user' ), 10, 2);
46
+ add_action('switch_off_user', array( $this, 'on_switch_off_user' ), 10, 1);
47
+ }
48
+
49
+ function on_switch_to_user($user_id, $old_user_id)
50
+ {
51
+
52
+ $user_to = get_user_by('id', $user_id);
53
+ $user_from = get_user_by('id', $old_user_id);
54
+
55
+ if (! is_a($user_to, 'WP_User') || ! is_a($user_from, 'WP_User')) {
56
+ return;
57
+ }
58
+
59
+ $this->infoMessage(
60
+ 'switched_to_user',
61
+ array(
62
+ // It is the old user who initiates the switching
63
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
64
+ '_user_id' => $old_user_id,
65
+ 'user_id' => $user_id,
66
+ 'old_user_id' => $old_user_id,
67
+ 'user_login_to' => $user_to->user_login,
68
+ 'user_login_from' => $user_from->user_login,
69
+ )
70
+ );
71
+ }
72
+
73
+ /**
74
+ * Function is called when a user switches back to their originating account.
75
+ * When you switch back after being logged off the
76
+ *
77
+ * Note: $old_user_id parameter is boolean false because there is no old user.
78
+ *
79
+ * @param int $user_id The ID of the user being switched back to.
80
+ * @param int|false $old_user_id The ID of the user being switched from, or false if the user is switching back
81
+ * after having been switched off.
82
+ */
83
+ function on_switch_back_user($user_id, $old_user_id)
84
+ {
85
+
86
+ $user_to = get_user_by('id', $user_id);
87
+
88
+ $user_from = $old_user_id == false ? null : get_user_by('id', $old_user_id);
89
+
90
+ if (! is_a($user_to, 'WP_User')) {
91
+ return;
92
+ }
93
+
94
+ if ($user_from) {
95
+ // User switched back from another user
96
+ $this->infoMessage(
97
+ 'switched_back_user',
98
+ array(
99
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
100
+ '_user_id' => $old_user_id,
101
+ 'user_id' => $user_id,
102
+ 'old_user_id' => $old_user_id,
103
+ 'user_login_to' => $user_to->user_login,
104
+ 'user_login_from' => $user_from->user_login,
105
+ )
106
+ );
107
+ } else {
108
+ // User switched back to themself (no prev user)
109
+ $this->infoMessage(
110
+ 'switched_back_themself',
111
+ array(
112
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
113
+ '_user_id' => $user_id,
114
+ 'user_login_to' => $user_to->user_login,
115
+ )
116
+ );
117
+ }
118
+ }
119
+
120
+ function on_switch_off_user($user_id)
121
+ {
122
+
123
+ $user = get_user_by('id', $user_id);
124
+
125
+ if (! is_a($user, 'WP_User')) {
126
+ return;
127
+ }
128
+
129
+ $this->infoMessage(
130
+ 'switched_off_user',
131
+ array(
132
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
133
+ '_user_id' => $user_id,
134
+
135
+ 'user_id' => $user_id,
136
+ 'user_login' => $user->user_login,
137
+ )
138
+ );
139
+ }
 
 
 
 
140
  }
loggers/Plugin_ACF.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Logger for the Advanced Custom Fields (ACF) plugin
@@ -9,1055 +9,1070 @@ defined( 'ABSPATH' ) || die();
9
  * @package SimpleHistory
10
  * @since 2.21
11
  */
12
- if ( ! class_exists( 'Plugin_ACF' ) ) {
13
-
14
- /**
15
- * Class for ACF logging.
16
- */
17
- class Plugin_ACF extends SimpleLogger {
18
-
19
- /**
20
- * The slug for this logger.
21
- *
22
- * @var string $slug
23
- */
24
- public $slug = __CLASS__;
25
-
26
- /**
27
- * Will contain field groups and fields, before and after post save.
28
- *
29
- * @var string $oldAndNewFieldGroupsAndFields
30
- */
31
- private $oldAndNewFieldGroupsAndFields = array(
32
- 'fieldGroup' => array(
33
- 'old' => null,
34
- 'new' => null,
35
- ),
36
- 'modifiedFields' => array(
37
- 'old' => null,
38
- 'new' => null,
39
- ),
40
- 'addedFields' => array(),
41
- 'deletedFields' => array(),
42
- );
43
-
44
- /**
45
- * Will contain the post data before save, i.e. the previous version of the post.
46
- *
47
- * @var string $oldPostData
48
- */
49
- private $oldPostData = array();
50
-
51
- /**
52
- * Get info for this logger.
53
- *
54
- * @return array Array with info about the logger.
55
- */
56
- public function getInfo() {
57
- $arr_info = array(
58
- 'name' => 'Plugin ACF',
59
- 'description' => _x( 'Logs ACF stuff', 'Logger: Plugin ACF', 'simple-history' ),
60
- 'name_via' => _x( 'Using plugin ACF', 'Logger: Plugin ACF', 'simple-history' ),
61
- 'capability' => 'manage_options',
62
- );
63
-
64
- return $arr_info;
65
- }
66
-
67
- private function isACFInstalled() {
68
- return defined( 'ACF' ) && ACF;
69
- }
70
-
71
- /**
72
- * Method called when logger is loaded.
73
- */
74
- public function loaded() {
75
-
76
- // Bail if no ACF found.
77
- if ( ! $this->isACFInstalled() ) {
78
- return;
79
- }
80
-
81
- // Remove ACF Fields from the post types that postlogger logs.
82
- add_filter( 'simple_history/post_logger/skip_posttypes', array( $this, 'remove_acf_from_postlogger' ) );
83
-
84
- // Get prev version of acf field group.
85
- // This is called before transition_post_status.
86
- add_filter( 'wp_insert_post_data', array( $this, 'on_wp_insert_post_data' ), 10, 2 );
87
-
88
- // Store old and new field data when a post is saved.
89
- add_action( 'transition_post_status', array( $this, 'on_transition_post_status' ), 5, 3 );
90
-
91
- // Append ACF data to post context
92
- add_filter( 'simple_history/post_logger/post_updated/context', array( $this, 'on_post_updated_context' ), 10, 2 );
93
-
94
- // Add ACF diff data to activity feed detailed output.
95
- add_filter( 'simple_history/post_logger/post_updated/diff_table_output', array( $this, 'on_diff_table_output_field_group' ), 10, 2 );
96
-
97
- // Store prev ACF field values before new values are added.
98
- // Called from filter admin_action_editpost that is fired at top of admin.php
99
- add_action( 'admin_action_editpost', array( $this, 'on_admin_action_editpost' ) );
100
-
101
- // Fired when ACF saves a post. Adds ACF context to logged row.
102
- add_filter( 'acf/save_post', array( $this, 'on_acf_save_post' ), 50 );
103
-
104
- // Fired after a log row is inserted. Add filter so field group save is is not logged again.
105
- add_action( 'simple_history/log/inserted', array( $this, 'on_log_inserted' ), 10, 3 );
106
- }
107
-
108
- /**
109
- * Fired after a log row is inserted.
110
- */
111
- public function on_log_inserted( $context, $data_parent_row, $simple_history_instance ) {
112
- $message_key = ! empty( $context['_message_key'] ) ? $context['_message_key'] : false;
113
- $logger = ! empty( $data_parent_row['logger'] ) ? $data_parent_row['logger'] : false;
114
- $post_id = ! empty( $context['post_id'] ) ? $context['post_id'] : false;
115
- $post_type = ! empty( $context['post_type'] ) ? $context['post_type'] : false;
116
-
117
- // Bail if not all required vars are set.
118
- if ( ! $message_key || ! $logger || ! $post_id || ! $post_type ) {
119
- return;
120
- }
121
-
122
- // Only act when logger was SimplePostLogger.
123
- if ( $logger !== 'SimplePostLogger' ) {
124
- return;
125
- }
126
-
127
- // Only act when the saved type was a ACF Field Group.
128
- if ( $post_type !== 'acf-field-group' ) {
129
- return;
130
- }
131
-
132
- // Ok, a row was inserted using the log function on SimplePostLogger,
133
- // now ACF will call save_post again and trigger
134
- // another log of the same row. To prevent this we
135
- // now add a filter to prevent the next log.
136
- add_filter( 'simple_history/post_logger/post_updated/ok_to_log', array( $this, 'prevent_second_acf_field_group_post_save_log' ), 10, 4 );
137
- }
138
-
139
- /**
140
- * Fired from SimpleLogger action 'simple_history/post_logger/post_updated/ok_to_log' and added only after
141
- * a row already has been logged.
142
- *
143
- * This function checks if post type logged by SimplePostLogger is a ACF Field Group, and if it is
144
- * then don't log that log. This way we prevent the post logger from logging the field group changes twice.
145
- */
146
- public function prevent_second_acf_field_group_post_save_log( $ok_to_log, $new_status, $old_status, $post ) {
147
- if ( isset( $post->post_type ) && $post->post_type === 'acf-field-group' ) {
148
- $ok_to_log = false;
149
- }
150
-
151
- return $ok_to_log;
152
- }
153
-
154
- /**
155
- * Append info about changes in ACF fields input,
156
- * i.e. store all info we later use to show changes that a user has done.
157
- *
158
- * Called when ACF saves a post.
159
- *
160
- * @param mixed int $post_id ID of post that is being saved. string "option" or "options" when saving an options page.
161
- */
162
- public function on_acf_save_post( $post_id ) {
163
- if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
164
- return;
165
- }
166
-
167
- // Only act when $post_id is numeric, can be "options" too when
168
- // ACF saves an options page.
169
- if ( ! is_numeric( $post_id ) ) {
170
- return;
171
- }
172
-
173
- // Don't act on post revision.
174
- if ( wp_is_post_revision( $post_id ) ) {
175
- return;
176
- }
177
-
178
- /*
179
- Meta values look like
180
- [product_images_0_image] => 625
181
- [_product_images_0_image] => field_59a091044812e
182
- [product_images_0_image_caption] => Image row yes
183
- [_product_images_0_image_caption] => field_59a0910f4812f
184
- [product_images_0_image_related_0_related_name] => Related one
185
- [_product_images_0_image_related_0_related_name] => field_59aaedd43ae11
186
- [product_images_0_image_related_0_related_item_post] =>
187
- [_product_images_0_image_related_0_related_item_post] => field_59aaede43ae12
188
- [product_images_0_image_related_1_related_name] => Another related
189
- [_product_images_0_image_related_1_related_name] => field_59aaedd43ae11
190
- [product_images_0_image_related_1_related_item_post] =>
191
- [_product_images_0_image_related_1_related_item_post] => field_59aaede43ae12
192
- [product_images_0_image_related] => 2
193
- [_product_images_0_image_related] => field_59aaedbc3ae10
194
- [product_images_1_image] => 574
195
- */
196
- $prev_post_meta = isset( $this->oldPostData['prev_post_meta'] ) ? $this->oldPostData['prev_post_meta'] : array();
197
-
198
- $new_post_meta = get_post_custom( $post_id );
199
- $new_post_meta = array_map( 'reset', $new_post_meta );
200
-
201
- // New and old post meta can contain different amount of keys,
202
- // join them so we have the name of all post meta thaf have been added, removed, or modified.
203
- $new_and_old_post_meta = array_merge( $prev_post_meta, $new_post_meta );
204
- ksort( $new_and_old_post_meta, SORT_REGULAR );
205
-
206
- // array1 - The array to compare from
207
- // array2 - An array to compare against
208
- // Returns an array containing all the values from array1 that are not present in any of the other arrays.
209
- // Keep only ACF fields in prev and new post meta.
210
- $prev_post_meta = $this->keep_only_acf_stuff_in_array( $prev_post_meta, $new_and_old_post_meta );
211
- $new_post_meta = $this->keep_only_acf_stuff_in_array( $new_post_meta, $new_and_old_post_meta );
212
- $new_and_old_post_meta_acf_fields = array_merge( $prev_post_meta, $new_post_meta );
213
-
214
- // Map field name with fieldkey so we can get field objects when needed.
215
- // Final array have values like:
216
- // [product_images_0_image] => field_59a091044812e
217
- // [product_images_0_image_caption] => field_59a0910f4812f
218
- // [product_images_0_image_related_0_related_name] => field_59aaedd43ae11.
219
- $fieldnames_to_field_keys = array();
220
- foreach ( $new_and_old_post_meta_acf_fields as $meta_key => $meta_value ) {
221
- // $key is like [product_images_0_image_related_1_related_name].
222
- // Get ACF fieldkey for that value. Will be in $new_and_old_post_meta
223
- // as the same as key but with underscore first
224
- $meta_key_to_look_for = "_{$meta_key}";
225
- if ( isset( $new_and_old_post_meta[ $meta_key_to_look_for ] ) ) {
226
- $fieldnames_to_field_keys[ $meta_key ] = $new_and_old_post_meta[ $meta_key_to_look_for ];
227
- }
228
- }
229
-
230
- // Compare old with new = get only changed, not added, deleted are here.
231
- $post_meta_diff1 = array_diff_assoc( $prev_post_meta, $new_post_meta );
232
-
233
- // Compare new with old = get an diff with added and changed stuff.
234
- $post_meta_diff2 = array_diff_assoc( $new_post_meta, $prev_post_meta );
235
-
236
- // Compare keys, gets added fields.
237
- $post_meta_added_fields = array_diff( array_keys( $post_meta_diff2 ), array_keys( $post_meta_diff1 ) );
238
- $post_meta_added_fields = array_values( $post_meta_added_fields );
239
-
240
- // Keys that exist in diff1 but not in diff2 = deleted.
241
- $post_meta_removed_fields = array_diff_assoc( array_keys( $post_meta_diff1 ), array_keys( $post_meta_diff2 ) );
242
- $post_meta_removed_fields = array_values( $post_meta_removed_fields );
243
-
244
- $post_meta_changed_fields = array_keys( $post_meta_diff1 );
245
-
246
- /*
247
- * value is changed: added to both diff and diff2
248
- * value is added, like in repeater: added to diff2 (not to diff)
249
- * $diff3: contains only added things.
250
- * Compare old and new values
251
- * Loop all keys in $new_and_old_post_meta
252
- * But act only on those whose keys begins with "_" and where the value begins with "field_" and ends with alphanum.
253
- */
254
-
255
- /*
256
- * We have the diff, now add it to the context
257
- * This is called after Simple History already has added its row
258
- * So... we must add to the context late somehow
259
- * Get the latest inserted row from the SimplePostLogger, check if that postID is
260
- * same as the
261
- */
262
- $post_logger = $this->simpleHistory->getInstantiatedLoggerBySlug( 'SimplePostLogger' );
263
-
264
- // Save ACF diff if detected post here is same as the last one used in Postlogger.
265
- if ( $post_id === $post_logger->lastInsertContext['post_id'] ) {
266
- $last_insert_id = $post_logger->lastInsertID;
267
-
268
- // Append new info to the context of history item with id $post_logger->lastInsertID.
269
- $acf_context = array();
270
- $acf_context = $this->add_acf_context( $acf_context, 'added', $post_meta_added_fields, $prev_post_meta, $new_post_meta, $fieldnames_to_field_keys );
271
- $acf_context = $this->add_acf_context( $acf_context, 'changed', $post_meta_changed_fields, $prev_post_meta, $new_post_meta, $fieldnames_to_field_keys );
272
- $acf_context = $this->add_acf_context( $acf_context, 'removed', $post_meta_removed_fields, $prev_post_meta, $new_post_meta, $fieldnames_to_field_keys );
273
-
274
- $post_logger->append_context( $last_insert_id, $acf_context );
275
-
276
- // Prev and new post meta for testing.
277
- /*
278
- $post_logger->append_context(
279
- $last_insert_id,
280
- array(
281
- 'prev_post_meta' => $prev_post_meta,
282
- )
283
- );
284
- $post_logger->append_context(
285
- $last_insert_id,
286
- array(
287
- 'new_post_meta' => $new_post_meta,
288
- )
289
- );
290
- */
291
- } // End if().
292
- }
293
-
294
- /**
295
- * Add ACF context for added, removed, or changed fields.
296
- *
297
- * @param array $context Context.
298
- * @param string $modify_type Type. added | removed | changed.
299
- * @param array $relevant_acf_fields Fields.
300
- * @param array $prev_post_meta Prev meta.
301
- * @param array $new_post_meta New meta.
302
- * @param array $fieldnames_to_field_keys Fieldnames to field keys mapping.
303
- * @return array Modified context.
304
- */
305
- public function add_acf_context( $context = array(), $modify_type = '', $relevant_acf_fields = array(), $prev_post_meta, $new_post_meta, $fieldnames_to_field_keys ) {
306
-
307
- if ( ! is_array( $context ) || empty( $modify_type ) || empty( $relevant_acf_fields ) ) {
308
- return $context;
309
- }
310
-
311
- $loopnum = 0;
312
- foreach ( $relevant_acf_fields as $field_slug ) {
313
- /*
314
- Store just the names to begin with
315
- acf_field_added_0 = url.
316
- acf_field_added_1 = first_name.
317
-
318
- If field slug contains a number, like in "product_images_2_image"
319
- that probably means that that field is a repeater with name "product_images"
320
- with a sub field called "image" and that the image is the 2:nd among it's selected sub fields.
321
-
322
- Example of how fields can look:
323
- acf_field_added_0 product_images_2_image
324
- acf_field_added_1 product_images_2_image_caption
325
- acf_field_added_2 product_images_2_image_related
326
- acf_field_changed_0 my_field_in_acf
327
- acf_field_changed_1 product_images
328
- acf_field_changed_2 price
329
- acf_field_changed_3 description
330
- */
331
- $context_key = "acf_field_{$modify_type}_{$loopnum}";
332
- $context[ "{$context_key}/slug" ] = $field_slug;
333
-
334
- /*
335
- * Try to get som extra info, like display name and type for this field.
336
- * For a nice context in the feed we want: parent field group name and type?
337
- */
338
- if ( isset( $fieldnames_to_field_keys[ $field_slug ] ) ) {
339
- $field_key = $fieldnames_to_field_keys[ $field_slug ];
340
- $context[ "{$context_key}/key" ] = $field_key;
341
-
342
- // Interesting stuff in field object:
343
- // - Label = the human readable name of the field
344
- // - Type = the type of the field
345
- // - Parent = id of parent field post id.
346
- $field_object = get_field_object( $field_key );
347
-
348
- if ( is_array( $field_object ) ) {
349
- $context[ "{$context_key}/label" ] = $field_object['label'];
350
- if ( ! empty( $field_object['type'] ) ) {
351
- $context[ "{$context_key}/type" ] = $field_object['type'];
352
- }
353
-
354
- // If no parent just continue to next field.
355
- if ( empty( $field_object['parent'] ) ) {
356
- continue;
357
- }
358
-
359
- // We have at least one parent, get them all, including the field group
360
- // $context[ "{$context_key}/field_parent_object" ] = $parent_field;
361
- $field_parents = array();
362
- $field_field_group = null;
363
-
364
- // Begin with the direct parent.
365
- $parent_field = $field_object;
366
-
367
- while ( ! empty( $parent_field['parent'] ) ) {
368
- $parentFieldParent = $parent_field['parent'];
369
-
370
- // acf-field | acf-field-group.
371
- $parent_field_post_type = get_post_type( $parentFieldParent );
372
-
373
- if ( false === $parent_field_post_type ) {
374
- break;
375
- }
376
-
377
- if ( 'acf-field' === $parent_field_post_type ) {
378
- // Field is when field is for example a sub field of a repeater.
379
- if ( function_exists( 'acf_get_field' ) ) {
380
- // Since ACF 5.7.10 the acf_get_field() function is available.
381
- $parent_field = acf_get_field( $parentFieldParent );
382
- } elseif ( function_exists( '_acf_get_field_by_id' ) ) {
383
- // ACF function _acf_get_field_by_id() is available before ACF 5.7.10.
384
- $parent_field = _acf_get_field_by_id( $parentFieldParent );
385
- }
386
- } elseif ( 'acf-field-group' === $parent_field_post_type ) {
387
- $parent_field = acf_get_field_group( $parentFieldParent );
388
- } else {
389
- // Unknown post type.
390
- break;
391
- }
392
-
393
- if ( false === $parent_field ) {
394
- break;
395
- }
396
-
397
- if ( 'acf-field' === $parent_field_post_type ) {
398
- $field_parents[] = $parent_field;
399
- } elseif ( 'acf-field-group' === $parent_field_post_type ) {
400
- $field_field_group = $parent_field;
401
- } // End if().
402
- } // End while().
403
-
404
- $field_parents = array_reverse( $field_parents );
405
-
406
- // Array with info about each parent.
407
- $arr_field_path = array();
408
-
409
- if ( ! empty( $field_field_group['title'] ) ) {
410
- $arr_field_path[] = array(
411
- 'name' => $field_field_group['title'],
412
- 'type' => 'field_group',
413
- );
414
- }
415
-
416
- foreach ( $field_parents as $one_field_parent ) {
417
- $arr_field_path[] = array(
418
- 'name' => $one_field_parent['label'],
419
- 'type' => 'field',
420
- 'field_type' => $one_field_parent['type'],
421
- );
422
- }
423
-
424
- if ( ! empty( $arr_field_path ) ) {
425
- $path_loop_num = 0;
426
- foreach ( $arr_field_path as $one_field_path ) {
427
- $context[ "{$context_key}/path_{$path_loop_num}/name" ] = $one_field_path['name'];
428
- $context[ "{$context_key}/path_{$path_loop_num}/type" ] = $one_field_path['type'];
429
- if ( ! empty( $one_field_path['field_type'] ) ) {
430
- $context[ "{$context_key}/path_{$path_loop_num}/field_type" ] = $one_field_path['field_type'];
431
- }
432
- $path_loop_num++;
433
- }
434
- }
435
-
436
- // Add value of fields if they are not part of
437
- // repeatable or flexible fields or similar.
438
- // error_log( "Final parents" . print_r( $field_parents, 1 ) );
439
- // error_log( "Final field group" . print_r( $field_field_group['title'], 1 ) );
440
- // error_log( "context" . print_r( $context, 1 ) );
441
- } // End if().
442
- } // End if().
443
-
444
- $loopnum++;
445
- } // End foreach().
446
-
447
- // error_log( "---------------------------" );
448
- // error_log( "field_path_string: $field_path_string");
449
- // error_log( "context" . print_r( $context, 1 ) );
450
- return $context;
451
- }
452
-
453
- /**
454
- * Clean array and keep only ACF related things.
455
- *
456
- * Remove
457
- * - underscore fields
458
- * - fields with value field_*
459
- *
460
- * Keep
461
- * - vals that are acf
462
- *
463
- * @param array $arr Array.
464
- * @param array $all_fields Array fields.
465
- */
466
- public function keep_only_acf_stuff_in_array( $arr, $all_fields ) {
467
- $new_arr = array();
468
-
469
- foreach ( $arr as $key => $val ) {
470
-
471
- // Don't keep keys that begin with underscore "_".
472
- if ( strpos( $key, '_' ) === 0 ) {
473
- continue;
474
- }
475
-
476
- // Don't keep keys that begin with "field_".
477
- if ( strpos( $val, 'field_' ) === 0 ) {
478
- continue;
479
- }
480
-
481
- // Don't keep fields that does not have a corresponding _field value.
482
- // Each key has both the name, for example 'color' and another
483
- // key called '_color'. We check that the underscore version exists
484
- // and contains 'field_'. After this check only ACF fields should exist
485
- // in the array..
486
- if ( ! isset( $all_fields[ "_{$key}" ] ) ) {
487
- continue;
488
- }
489
-
490
- if ( strpos( $all_fields[ "_{$key}" ], 'field_' ) !== 0 ) {
491
- continue;
492
- }
493
-
494
- $new_arr[ $key ] = $val;
495
- }
496
-
497
- return $new_arr;
498
- }
499
-
500
- /**
501
- * Store prev post meta when post is saved.
502
- * Stores data in $this->oldPostData.
503
- */
504
- public function on_admin_action_editpost() {
505
- $post_ID = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
506
-
507
- if ( ! $post_ID ) {
508
- return;
509
- }
510
-
511
- $prev_post = get_post( $post_ID );
512
-
513
- if ( is_wp_error( $prev_post ) ) {
514
- return;
515
- }
516
-
517
- $post_meta = get_post_custom( $post_ID );
518
-
519
- // Meta is array of arrays, get first value of each array value.
520
- $post_meta = array_map( 'reset', $post_meta );
521
-
522
- $this->oldPostData['prev_post_meta'] = $post_meta;
523
- }
524
-
525
- /**
526
- * Called from PostLogger and its diff table output using filter 'simple_history/post_logger/post_updated/diff_table_output'.
527
- * Diff table is generated only for post type 'acf-field-group'.
528
- *
529
- * @param string $diff_table_output
530
- * @param array $context
531
- * @return string
532
- */
533
- public function on_diff_table_output_field_group( $diff_table_output, $context ) {
534
- $post_type = ! empty( $context['post_type'] ) ? $context['post_type'] : false;
535
-
536
- // Bail if not ACF Field Group.
537
- if ( $post_type !== 'acf-field-group' ) {
538
- return $diff_table_output;
539
- }
540
-
541
- // Field group fields to check for and output if found
542
- $arrKeys = array(
543
- 'instruction_placement' => array(
544
- 'name' => _x( 'Instruction placement', 'Logger: Plugin ACF', 'simple-history' ),
545
- ),
546
- 'label_placement' => array(
547
- 'name' => _x( 'Label placement', 'Logger: Plugin ACF', 'simple-history' ),
548
- ),
549
- 'description' => array(
550
- 'name' => _x( 'Description', 'Logger: Plugin ACF', 'simple-history' ),
551
- ),
552
- 'menu_order' => array(
553
- 'name' => _x( 'Menu order', 'Logger: Plugin ACF', 'simple-history' ),
554
- ),
555
- 'position' => array(
556
- 'name' => _x( 'Position', 'Logger: Plugin ACF', 'simple-history' ),
557
- ),
558
- 'active' => array(
559
- 'name' => _x( 'Active', 'Logger: Plugin ACF', 'simple-history' ),
560
- ),
561
- 'style' => array(
562
- 'name' => _x( 'Style', 'Logger: Plugin ACF', 'simple-history' ),
563
- ),
564
- );
565
-
566
- foreach ( $arrKeys as $acfKey => $acfVals ) {
567
- if ( isset( $context[ "acf_new_$acfKey" ] ) && isset( $context[ "acf_prev_$acfKey" ] ) ) {
568
- $diff_table_output .= sprintf(
569
- '<tr>
 
 
 
 
 
 
 
 
 
 
570
  <td>%1$s</td>
571
  <td>
572
  <ins class="SimpleHistoryLogitem__keyValueTable__addedThing">%2$s</ins>
573
  <del class="SimpleHistoryLogitem__keyValueTable__removedThing">%3$s</del>
574
  </td>
575
  </tr>',
576
- $acfVals['name'],
577
- esc_html( $context[ "acf_new_$acfKey" ] ),
578
- esc_html( $context[ "acf_prev_$acfKey" ] )
579
- );
580
- }
581
- }
582
-
583
- // If only acf_hide_on_screen_removed exists nothing is outputed.
584
- $acf_hide_on_screen_added = empty( $context['acf_hide_on_screen_added'] ) ? null : $context['acf_hide_on_screen_added'];
585
- $acf_hide_on_screen_removed = empty( $context['acf_hide_on_screen_removed'] ) ? null : $context['acf_hide_on_screen_removed'];
586
-
587
- if ( $acf_hide_on_screen_added || $acf_hide_on_screen_removed ) {
588
- $strCheckedHideOnScreen = '';
589
- $strUncheckedHideOnScreen = '';
590
-
591
- if ( $acf_hide_on_screen_added ) {
592
- $strCheckedHideOnScreen = sprintf(
593
- '%1$s %2$s',
594
- _x( 'Checked', 'Logger: Plugin ACF', 'simple-history' ), // 1
595
- esc_html( $acf_hide_on_screen_added ) // 2
596
- );
597
- }
598
-
599
- if ( $acf_hide_on_screen_removed ) {
600
- $strUncheckedHideOnScreen = sprintf(
601
- '%1$s %2$s',
602
- _x( 'Unchecked', 'Logger: Plugin ACF', 'simple-history' ), // 1
603
- esc_html( $acf_hide_on_screen_removed ) // 2
604
- );
605
- }
606
-
607
- $diff_table_output .= sprintf(
608
- '<tr>
609
  <td>%1$s</td>
610
  <td>
611
  %2$s
612
  %3$s
613
  </td>
614
  </tr>',
615
- _x( 'Hide on screen', 'Logger: Plugin ACF', 'simple-history' ), // 1
616
- $strCheckedHideOnScreen, // 2
617
- $strUncheckedHideOnScreen // 3
618
- );
619
- }
620
-
621
- // Check for deleted fields.
622
- if ( isset( $context['acf_deleted_fields_0_key'] ) ) {
623
- // 1 or more deleted fields exist in context.
624
- $loopnum = 0;
625
- $strDeletedFields = '';
626
-
627
- while ( isset( $context[ "acf_deleted_fields_{$loopnum}_key" ] ) ) {
628
- $strDeletedFields .= sprintf(
629
- '%1$s (%3$s), ',
630
- esc_html( $context[ "acf_deleted_fields_{$loopnum}_label" ] ),
631
- esc_html( $context[ "acf_deleted_fields_{$loopnum}_name" ] ),
632
- esc_html( $context[ "acf_deleted_fields_{$loopnum}_type" ] )
633
- );
634
-
635
- $loopnum++;
636
- }
637
-
638
- $strDeletedFields = trim( $strDeletedFields, ', ' );
639
-
640
- $diff_table_output .= sprintf(
641
- '<tr>
642
  <td>%1$s</td>
643
  <td>%2$s</td>
644
  </tr>',
645
- _nx( 'Deleted field', 'Deleted fields', $loopnum, 'Logger: Plugin ACF', 'simple-history' ), // 1
646
- $strDeletedFields
647
- );
648
- } // if deleted fields
649
-
650
- // Check for added fields
651
- if ( isset( $context['acf_added_fields_0_key'] ) ) {
652
- // 1 or more deleted fields exist in context
653
- $loopnum = 0;
654
- $strAddedFields = '';
655
-
656
- while ( isset( $context[ "acf_added_fields_{$loopnum}_key" ] ) ) {
657
- $strAddedFields .= sprintf(
658
- '%1$s (%3$s), ',
659
- esc_html( $context[ "acf_added_fields_{$loopnum}_label" ] ), // 1
660
- esc_html( $context[ "acf_added_fields_{$loopnum}_name" ] ), // 2
661
- esc_html( $context[ "acf_added_fields_{$loopnum}_type" ] ) // 3
662
- );
663
-
664
- $loopnum++;
665
- }
666
-
667
- $strAddedFields = trim( $strAddedFields, ', ' );
668
-
669
- $diff_table_output .= sprintf(
670
- '<tr>
671
  <td>%1$s</td>
672
  <td>%2$s</td>
673
  </tr>',
674
- _nx( 'Added field', 'Added fields', $loopnum, 'Logger: Plugin ACF', 'simple-history' ), // 1
675
- $strAddedFields
676
- );
677
- } // if deleted fields
678
-
679
- // Check for modified fields
680
- if ( isset( $context['acf_modified_fields_0_ID_prev'] ) ) {
681
- // 1 or more modifiedfields exist in context
682
- $loopnum = 0;
683
- $strModifiedFields = '';
684
- $arrAddedFieldsKeysToCheck = array(
685
- 'name' => array(
686
- 'name' => _x( 'Name: ', 'Logger: Plugin ACF', 'simple-history' ),
687
- ),
688
- 'parent' => array(
689
- 'name' => _x( 'Parent: ', 'Logger: Plugin ACF', 'simple-history' ),
690
- ),
691
- 'key' => array(
692
- 'name' => _x( 'Key: ', 'Logger: Plugin ACF', 'simple-history' ),
693
- ),
694
- 'label' => array(
695
- 'name' => _x( 'Label: ', 'Logger: Plugin ACF', 'simple-history' ),
696
- ),
697
- 'type' => array(
698
- 'name' => _x( 'Type: ', 'Logger: Plugin ACF', 'simple-history' ),
699
- ),
700
- );
701
-
702
- while ( isset( $context[ "acf_modified_fields_{$loopnum}_name_prev" ] ) ) {
703
- // One modified field, with one or more changed things
704
- $strOneModifiedField = '';
705
-
706
- // Add the field name manually, if it is not among the changed field,
707
- // or we don't know what field the other changed values belongs to.
708
- /*
709
- if (empty($context["acf_modified_fields_{$loopnum}_name_new"])) {
710
- $strOneModifiedField .= sprintf(
711
- _x('Name: %1$s', 'Logger: Plugin ACF', 'simple-history'), // 1
712
- esc_html($context["acf_modified_fields_{$loopnum}_name_prev"]) // 2
713
- );
714
- }
715
- */
716
-
717
- // Add the label name manually, if it is not among the changed field,
718
- // or we don't know what field the other changed values belongs to.
719
- if ( empty( $context[ "acf_modified_fields_{$loopnum}_label_new" ] ) ) {
720
- $strOneModifiedField .= sprintf(
721
- _x( 'Label: %1$s', 'Logger: Plugin ACF', 'simple-history' ), // 1
722
- esc_html( $context[ "acf_modified_fields_{$loopnum}_label_prev" ] ) // 2
723
- );
724
- }
725
-
726
- // Check for other keys changed for this field
727
- foreach ( $arrAddedFieldsKeysToCheck as $oneAddedFieldKeyToCheck => $oneAddedFieldKeyToCheckVals ) {
728
- $newAndOldValsExists = isset( $context[ "acf_modified_fields_{$loopnum}_{$oneAddedFieldKeyToCheck}_new" ] ) && isset( $context[ "acf_modified_fields_{$loopnum}_{$oneAddedFieldKeyToCheck}_new" ] );
729
- if ( $newAndOldValsExists ) {
730
- $strOneModifiedField .= sprintf(
731
- '
732
  %4$s
733
  %3$s
734
  <ins class="SimpleHistoryLogitem__keyValueTable__addedThing">%1$s</ins>
735
  <del class="SimpleHistoryLogitem__keyValueTable__removedThing">%2$s</del>
736
  ',
737
- esc_html( $context[ "acf_modified_fields_{$loopnum}_{$oneAddedFieldKeyToCheck}_new" ] ), // 1
738
- esc_html( $context[ "acf_modified_fields_{$loopnum}_{$oneAddedFieldKeyToCheck}_prev" ] ), // 2
739
- esc_html( $oneAddedFieldKeyToCheckVals['name'] ), // 3
740
- empty( $strOneModifiedField ) ? '' : '<br>' // 4 new line
741
- );
742
- }
743
- }
744
-
745
- $strOneModifiedField = trim( $strOneModifiedField, ", \n\r\t" );
746
-
747
- if ( $strOneModifiedField ) {
748
- $strModifiedFields .= sprintf(
749
- '<tr>
750
  <td>%1$s</td>
751
  <td>%2$s</td>
752
  </tr>',
753
- _x( 'Modified field', 'Logger: Plugin ACF', 'simple-history' ), // 1
754
- $strOneModifiedField
755
- );
756
- }
757
-
758
- $loopnum++;
759
- }
760
-
761
- /*
762
- if ($strModifiedFields) {
763
- $strModifiedFields = sprintf(
764
- '<tr>
765
- <td>%1$s</td>
766
- <td>%2$s</td>
767
- </tr>',
768
- _nx('Modified field', 'Modified fields', $loopnum, 'Logger: Plugin ACF', 'simple-history'), // 1
769
- $strModifiedFields
770
- ) . $strModifiedFields;
771
- }*/
772
-
773
- $diff_table_output .= $strModifiedFields;
774
- } // if deleted fields
775
-
776
- return $diff_table_output;
777
- }
778
-
779
- /**
780
- * Append ACF data to post context.
781
- *
782
- * Called via filter `simple_history/post_logger/post_updated/context`.
783
- *
784
- * @param array $context
785
- * @param WP_Post $post
786
- */
787
- public function on_post_updated_context( $context, $post ) {
788
-
789
- // Only act if this is a ACF field group that is saved
790
- if ( $post->post_type !== 'acf-field-group' ) {
791
- return $context;
792
- }
793
-
794
- // Remove some keys that we don't want,
795
- // for example the content because that's just a json string
796
- // in acf-field-group posts.
797
- unset(
798
- $context['post_prev_post_content'],
799
- $context['post_new_post_content'],
800
- $context['post_prev_post_name'],
801
- $context['post_new_post_name'],
802
- $context['post_prev_post_date'],
803
- $context['post_new_post_date'],
804
- $context['post_prev_post_date_gmt'],
805
- $context['post_new_post_date_gmt']
806
- );
807
-
808
- $acf_data_diff = array();
809
-
810
- // 'fieldGroup' fields to check.
811
- $arr_field_group_keys_to_diff = array(
812
- 'menu_order',
813
- 'position',
814
- 'style',
815
- 'label_placement',
816
- 'instruction_placement',
817
- 'active',
818
- 'description',
819
- );
820
-
821
- $fieldGroup = $this->oldAndNewFieldGroupsAndFields['fieldGroup'];
822
-
823
- foreach ( $arr_field_group_keys_to_diff as $key ) {
824
- if ( isset( $fieldGroup['old'][ $key ] ) && isset( $fieldGroup['new'][ $key ] ) ) {
825
- $acf_data_diff = $this->add_diff( $acf_data_diff, $key, (string) $fieldGroup['old'][ $key ], (string) $fieldGroup['new'][ $key ] );
826
- }
827
- }
828
-
829
- foreach ( $acf_data_diff as $diff_key => $diff_values ) {
830
- $context[ "acf_prev_{$diff_key}" ] = $diff_values['old'];
831
- $context[ "acf_new_{$diff_key}" ] = $diff_values['new'];
832
- }
833
-
834
- // Add checked or uncheckd hide on screen-items to context
835
- $arrhHideOnScreenAdded = array();
836
- $arrHideOnScreenRemoved = array();
837
-
838
- $fieldGroup['new']['hide_on_screen'] = isset( $fieldGroup['new']['hide_on_screen'] ) && is_array( $fieldGroup['new']['hide_on_screen'] ) ? $fieldGroup['new']['hide_on_screen'] : array();
839
- $fieldGroup['old']['hide_on_screen'] = isset( $fieldGroup['old']['hide_on_screen'] ) && is_array( $fieldGroup['old']['hide_on_screen'] ) ? $fieldGroup['old']['hide_on_screen'] : array();
840
-
841
- // dd($fieldGroup['old']['hide_on_screen'], $fieldGroup['new']['hide_on_screen']);
842
- // Act when new or old hide_on_screen is set
843
- if ( ! empty( $fieldGroup['new']['hide_on_screen'] ) || ! empty( $fieldGroup['old']['hide_on_screen'] ) ) {
844
- $arrhHideOnScreenAdded = array_diff( $fieldGroup['new']['hide_on_screen'], $fieldGroup['old']['hide_on_screen'] );
845
- $arrHideOnScreenRemoved = array_diff( $fieldGroup['old']['hide_on_screen'], $fieldGroup['new']['hide_on_screen'] );
846
-
847
- // ddd($arrhHideOnScreenAdded, $arrHideOnScreenRemoved);
848
- if ( $arrhHideOnScreenAdded ) {
849
- $context['acf_hide_on_screen_added'] = implode( ',', $arrhHideOnScreenAdded );
850
- }
851
-
852
- if ( $arrHideOnScreenRemoved ) {
853
- $context['acf_hide_on_screen_removed'] = implode( ',', $arrHideOnScreenRemoved );
854
- }
855
- }
856
-
857
- // ddd($context, $arrhHideOnScreenAdded, $arrHideOnScreenRemoved);
858
- // Add removed fields to context
859
- if ( ! empty( $this->oldAndNewFieldGroupsAndFields['deletedFields'] ) && is_array( $this->oldAndNewFieldGroupsAndFields['deletedFields'] ) ) {
860
- $loopnum = 0;
861
- foreach ( $this->oldAndNewFieldGroupsAndFields['deletedFields'] as $oneDeletedField ) {
862
- $context[ "acf_deleted_fields_{$loopnum}_key" ] = $oneDeletedField['key'];
863
- $context[ "acf_deleted_fields_{$loopnum}_name" ] = $oneDeletedField['name'];
864
- $context[ "acf_deleted_fields_{$loopnum}_label" ] = $oneDeletedField['label'];
865
- $context[ "acf_deleted_fields_{$loopnum}_type" ] = $oneDeletedField['type'];
866
- $loopnum++;
867
- }
868
- }
869
-
870
- // Add added fields to context
871
- if ( ! empty( $this->oldAndNewFieldGroupsAndFields['addedFields'] ) && is_array( $this->oldAndNewFieldGroupsAndFields['addedFields'] ) ) {
872
- $loopnum = 0;
873
-
874
- foreach ( $this->oldAndNewFieldGroupsAndFields['addedFields'] as $oneAddedField ) {
875
- // Id not available here, wold be nice to have
876
- // $context["acf_added_fields_{$loopnum}_ID"] = $oneAddedField['ID'];
877
- $context[ "acf_added_fields_{$loopnum}_key" ] = $oneAddedField['key'];
878
- $context[ "acf_added_fields_{$loopnum}_name" ] = $oneAddedField['name'];
879
- $context[ "acf_added_fields_{$loopnum}_label" ] = $oneAddedField['label'];
880
- $context[ "acf_added_fields_{$loopnum}_type" ] = $oneAddedField['type'];
881
- $loopnum++;
882
- }
883
- }
884
-
885
- // Add modified fields to context
886
- // dd('on_post_updated_context', $context, $this->oldAndNewFieldGroupsAndFields);
887
- if ( ! empty( $this->oldAndNewFieldGroupsAndFields['modifiedFields']['old'] ) && ! empty( $this->oldAndNewFieldGroupsAndFields['modifiedFields']['new'] ) ) {
888
- $modifiedFields = $this->oldAndNewFieldGroupsAndFields['modifiedFields'];
889
-
890
- $arr_added_fields_keys_to_add = array(
891
- 'parent',
892
- 'key',
893
- 'label',
894
- 'name',
895
- 'type',
896
- );
897
-
898
- $loopnum = 0;
899
-
900
- foreach ( $modifiedFields['old'] as $modifiedFieldId => $modifiedFieldValues ) {
901
- // Both old and new values mest exist
902
- if ( empty( $modifiedFields['new'][ $modifiedFieldId ] ) ) {
903
- continue;
904
- }
905
-
906
- // Always add ID, name, and lavel
907
- $context[ "acf_modified_fields_{$loopnum}_ID_prev" ] = $modifiedFields['old'][ $modifiedFieldId ]['ID'];
908
- $context[ "acf_modified_fields_{$loopnum}_name_prev" ] = $modifiedFields['old'][ $modifiedFieldId ]['name'];
909
- $context[ "acf_modified_fields_{$loopnum}_label_prev" ] = $modifiedFields['old'][ $modifiedFieldId ]['label'];
910
-
911
- foreach ( $arr_added_fields_keys_to_add as $one_key_to_add ) {
912
- // Check that new and old exist.
913
- $new_exists = isset( $modifiedFields['new'][ $modifiedFieldId ][ $one_key_to_add ] );
914
- $old_exists = isset( $modifiedFields['old'][ $modifiedFieldId ][ $one_key_to_add ] );
915
-
916
- if ( ! $new_exists || ! $old_exists ) {
917
- continue;
918
- }
919
-
920
- // Only add to context if modified.
921
- if ( $modifiedFields['new'][ $modifiedFieldId ][ $one_key_to_add ] != $modifiedFields['old'][ $modifiedFieldId ][ $one_key_to_add ] ) {
922
- $context[ "acf_modified_fields_{$loopnum}_{$one_key_to_add}_prev" ] = $modifiedFields['old'][ $modifiedFieldId ][ $one_key_to_add ];
923
- $context[ "acf_modified_fields_{$loopnum}_{$one_key_to_add}_new" ] = $modifiedFields['new'][ $modifiedFieldId ][ $one_key_to_add ];
924
- }
925
- }
926
-
927
- $loopnum++;
928
- }
929
- }
930
-
931
- return $context;
932
- }
933
-
934
- public function add_diff( $post_data_diff, $key, $old_value, $new_value ) {
935
- if ( $old_value != $new_value ) {
936
- $post_data_diff[ $key ] = array(
937
- 'old' => $old_value,
938
- 'new' => $new_value,
939
- );
940
- }
941
-
942
- return $post_data_diff;
943
- }
944
-
945
- /**
946
- * Store a version of the field group as it was before the save
947
- * Called before field group post/values is added to db
948
- *
949
- * @param array $data Post data.
950
- * @param array $postarr Post data.
951
- */
952
- public function on_wp_insert_post_data( $data, $postarr ) {
953
-
954
- // Only do this if ACF field group is being saved.
955
- if ( $postarr['post_type'] !== 'acf-field-group' ) {
956
- return $data;
957
- }
958
-
959
- if ( empty( $postarr['ID'] ) ) {
960
- return $data;
961
- }
962
-
963
- if ( empty( $_POST['acf_field_group'] ) ) {
964
- return $data;
965
- }
966
-
967
- $this->oldAndNewFieldGroupsAndFields['fieldGroup']['old'] = acf_get_field_group( $postarr['ID'] );
968
-
969
- $this->oldAndNewFieldGroupsAndFields['fieldGroup']['new'] = acf_get_valid_field_group( $_POST['acf_field_group'] );
970
-
971
- return $data;
972
- }
973
-
974
- /**
975
- * ACF field group is saved
976
- * Called before ACF calls its save_post filter
977
- * Here we save the new fields values and also get the old values so we can compare
978
- */
979
- public function on_transition_post_status( $new_status, $old_status, $post ) {
980
- static $isCalled = false;
981
-
982
- if ( $isCalled ) {
983
- return;
984
- }
985
-
986
- $isCalled = true;
987
-
988
- $post_id = $post->ID;
989
-
990
- // do not act if this is an auto save routine
991
- if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
992
- return;
993
- }
994
-
995
- // bail early if not acf-field-group
996
- if ( $post->post_type !== 'acf-field-group' ) {
997
- return;
998
- }
999
-
1000
- // only save once! WordPress save's a revision as well.
1001
- if ( wp_is_post_revision( $post_id ) ) {
1002
- return;
1003
- }
1004
-
1005
- // Store info about fields that are going to be deleted
1006
- if ( ! empty( $_POST['_acf_delete_fields'] ) ) {
1007
- $deletedFieldsIDs = explode( '|', $_POST['_acf_delete_fields'] );
1008
- $deletedFieldsIDs = array_map( 'intval', $deletedFieldsIDs );
1009
-
1010
- foreach ( $deletedFieldsIDs as $id ) {
1011
- if ( ! $id ) {
1012
- continue;
1013
- }
1014
-
1015
- $field_info = acf_get_field( $id );
1016
-
1017
- if ( ! $field_info ) {
1018
- continue;
1019
- }
1020
-
1021
- $this->oldAndNewFieldGroupsAndFields['deletedFields'][ $id ] = $field_info;
1022
- }
1023
- }
1024
-
1025
- // Store info about added or modified fields
1026
- if ( ! empty( $_POST['acf_fields'] ) && is_array( $_POST['acf_fields'] ) ) {
1027
- foreach ( $_POST['acf_fields'] as $oneFieldAddedOrUpdated ) {
1028
- if ( empty( $oneFieldAddedOrUpdated['ID'] ) ) {
1029
- // New fields have no id
1030
- // 'ID' => string(0) ""
1031
- $this->oldAndNewFieldGroupsAndFields['addedFields'][] = $oneFieldAddedOrUpdated;
1032
- } else {
1033
- // Existing fields have an id
1034
- // 'ID' => string(3) "383"
1035
- $this->oldAndNewFieldGroupsAndFields['modifiedFields']['old'][ $oneFieldAddedOrUpdated['ID'] ] = acf_get_field( $oneFieldAddedOrUpdated['ID'] );
1036
-
1037
- $this->oldAndNewFieldGroupsAndFields['modifiedFields']['new'][ $oneFieldAddedOrUpdated['ID'] ] = $oneFieldAddedOrUpdated;
1038
- }
1039
- }
1040
- }
1041
-
1042
- // We don't do anything else here, but we make the actual logging
1043
- // in filter 'acf/update_field_group' beacuse it's safer because
1044
- // ACF has done it's validation and it's after ACF has saved the fields,
1045
- // so less likely that we make some critical error
1046
- }
1047
-
1048
-
1049
- /**
1050
- * Add the post types that ACF uses for fields to the array of post types
1051
- * that the default post logger should not log. If not each field will cause one
1052
- * post update log message.
1053
- */
1054
- public function remove_acf_from_postlogger( $skip_posttypes ) {
1055
- array_push(
1056
- $skip_posttypes,
1057
- 'acf-field'
1058
- );
1059
-
1060
- return $skip_posttypes;
1061
- }
1062
- } // Class.
 
 
 
 
 
1063
  } // End if().
1
  <?php
2
 
3
+ defined('ABSPATH') || die();
4
 
5
  /**
6
  * Logger for the Advanced Custom Fields (ACF) plugin
9
  * @package SimpleHistory
10
  * @since 2.21
11
  */
12
+ if (! class_exists('Plugin_ACF')) {
13
+
14
+ /**
15
+ * Class for ACF logging.
16
+ */
17
+ class Plugin_ACF extends SimpleLogger
18
+ {
19
+
20
+ /**
21
+ * The slug for this logger.
22
+ *
23
+ * @var string $slug
24
+ */
25
+ public $slug = __CLASS__;
26
+
27
+ /**
28
+ * Will contain field groups and fields, before and after post save.
29
+ *
30
+ * @var string $oldAndNewFieldGroupsAndFields
31
+ */
32
+ private $oldAndNewFieldGroupsAndFields = array(
33
+ 'fieldGroup' => array(
34
+ 'old' => null,
35
+ 'new' => null,
36
+ ),
37
+ 'modifiedFields' => array(
38
+ 'old' => null,
39
+ 'new' => null,
40
+ ),
41
+ 'addedFields' => array(),
42
+ 'deletedFields' => array(),
43
+ );
44
+
45
+ /**
46
+ * Will contain the post data before save, i.e. the previous version of the post.
47
+ *
48
+ * @var string $oldPostData
49
+ */
50
+ private $oldPostData = array();
51
+
52
+ /**
53
+ * Get info for this logger.
54
+ *
55
+ * @return array Array with info about the logger.
56
+ */
57
+ public function getInfo()
58
+ {
59
+ $arr_info = array(
60
+ 'name' => 'Plugin ACF',
61
+ 'description' => _x('Logs ACF stuff', 'Logger: Plugin ACF', 'simple-history'),
62
+ 'name_via' => _x('Using plugin ACF', 'Logger: Plugin ACF', 'simple-history'),
63
+ 'capability' => 'manage_options',
64
+ );
65
+
66
+ return $arr_info;
67
+ }
68
+
69
+ private function isACFInstalled()
70
+ {
71
+ return defined('ACF') && ACF;
72
+ }
73
+
74
+ /**
75
+ * Method called when logger is loaded.
76
+ */
77
+ public function loaded()
78
+ {
79
+
80
+ // Bail if no ACF found.
81
+ if (! $this->isACFInstalled()) {
82
+ return;
83
+ }
84
+
85
+ // Remove ACF Fields from the post types that postlogger logs.
86
+ add_filter('simple_history/post_logger/skip_posttypes', array( $this, 'remove_acf_from_postlogger' ));
87
+
88
+ // Get prev version of acf field group.
89
+ // This is called before transition_post_status.
90
+ add_filter('wp_insert_post_data', array( $this, 'on_wp_insert_post_data' ), 10, 2);
91
+
92
+ // Store old and new field data when a post is saved.
93
+ add_action('transition_post_status', array( $this, 'on_transition_post_status' ), 5, 3);
94
+
95
+ // Append ACF data to post context
96
+ add_filter('simple_history/post_logger/post_updated/context', array( $this, 'on_post_updated_context' ), 10, 2);
97
+
98
+ // Add ACF diff data to activity feed detailed output.
99
+ add_filter('simple_history/post_logger/post_updated/diff_table_output', array( $this, 'on_diff_table_output_field_group' ), 10, 2);
100
+
101
+ // Store prev ACF field values before new values are added.
102
+ // Called from filter admin_action_editpost that is fired at top of admin.php
103
+ add_action('admin_action_editpost', array( $this, 'on_admin_action_editpost' ));
104
+
105
+ // Fired when ACF saves a post. Adds ACF context to logged row.
106
+ add_filter('acf/save_post', array( $this, 'on_acf_save_post' ), 50);
107
+
108
+ // Fired after a log row is inserted. Add filter so field group save is is not logged again.
109
+ add_action('simple_history/log/inserted', array( $this, 'on_log_inserted' ), 10, 3);
110
+ }
111
+
112
+ /**
113
+ * Fired after a log row is inserted.
114
+ */
115
+ public function on_log_inserted($context, $data_parent_row, $simple_history_instance)
116
+ {
117
+ $message_key = ! empty($context['_message_key']) ? $context['_message_key'] : false;
118
+ $logger = ! empty($data_parent_row['logger']) ? $data_parent_row['logger'] : false;
119
+ $post_id = ! empty($context['post_id']) ? $context['post_id'] : false;
120
+ $post_type = ! empty($context['post_type']) ? $context['post_type'] : false;
121
+
122
+ // Bail if not all required vars are set.
123
+ if (! $message_key || ! $logger || ! $post_id || ! $post_type) {
124
+ return;
125
+ }
126
+
127
+ // Only act when logger was SimplePostLogger.
128
+ if ($logger !== 'SimplePostLogger') {
129
+ return;
130
+ }
131
+
132
+ // Only act when the saved type was a ACF Field Group.
133
+ if ($post_type !== 'acf-field-group') {
134
+ return;
135
+ }
136
+
137
+ // Ok, a row was inserted using the log function on SimplePostLogger,
138
+ // now ACF will call save_post again and trigger
139
+ // another log of the same row. To prevent this we
140
+ // now add a filter to prevent the next log.
141
+ add_filter('simple_history/post_logger/post_updated/ok_to_log', array( $this, 'prevent_second_acf_field_group_post_save_log' ), 10, 4);
142
+ }
143
+
144
+ /**
145
+ * Fired from SimpleLogger action 'simple_history/post_logger/post_updated/ok_to_log' and added only after
146
+ * a row already has been logged.
147
+ *
148
+ * This function checks if post type logged by SimplePostLogger is a ACF Field Group, and if it is
149
+ * then don't log that log. This way we prevent the post logger from logging the field group changes twice.
150
+ */
151
+ public function prevent_second_acf_field_group_post_save_log($ok_to_log, $new_status, $old_status, $post)
152
+ {
153
+ if (isset($post->post_type) && $post->post_type === 'acf-field-group') {
154
+ $ok_to_log = false;
155
+ }
156
+
157
+ return $ok_to_log;
158
+ }
159
+
160
+ /**
161
+ * Append info about changes in ACF fields input,
162
+ * i.e. store all info we later use to show changes that a user has done.
163
+ *
164
+ * Called when ACF saves a post.
165
+ *
166
+ * @param mixed int $post_id ID of post that is being saved. string "option" or "options" when saving an options page.
167
+ */
168
+ public function on_acf_save_post($post_id)
169
+ {
170
+ if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
171
+ return;
172
+ }
173
+
174
+ // Only act when $post_id is numeric, can be "options" too when
175
+ // ACF saves an options page.
176
+ if (! is_numeric($post_id)) {
177
+ return;
178
+ }
179
+
180
+ // Don't act on post revision.
181
+ if (wp_is_post_revision($post_id)) {
182
+ return;
183
+ }
184
+
185
+ /*
186
+ Meta values look like
187
+ [product_images_0_image] => 625
188
+ [_product_images_0_image] => field_59a091044812e
189
+ [product_images_0_image_caption] => Image row yes
190
+ [_product_images_0_image_caption] => field_59a0910f4812f
191
+ [product_images_0_image_related_0_related_name] => Related one
192
+ [_product_images_0_image_related_0_related_name] => field_59aaedd43ae11
193
+ [product_images_0_image_related_0_related_item_post] =>
194
+ [_product_images_0_image_related_0_related_item_post] => field_59aaede43ae12
195
+ [product_images_0_image_related_1_related_name] => Another related
196
+ [_product_images_0_image_related_1_related_name] => field_59aaedd43ae11
197
+ [product_images_0_image_related_1_related_item_post] =>
198
+ [_product_images_0_image_related_1_related_item_post] => field_59aaede43ae12
199
+ [product_images_0_image_related] => 2
200
+ [_product_images_0_image_related] => field_59aaedbc3ae10
201
+ [product_images_1_image] => 574
202
+ */
203
+ $prev_post_meta = isset($this->oldPostData['prev_post_meta']) ? $this->oldPostData['prev_post_meta'] : array();
204
+
205
+ $new_post_meta = get_post_custom($post_id);
206
+ $new_post_meta = array_map('reset', $new_post_meta);
207
+
208
+ // New and old post meta can contain different amount of keys,
209
+ // join them so we have the name of all post meta thaf have been added, removed, or modified.
210
+ $new_and_old_post_meta = array_merge($prev_post_meta, $new_post_meta);
211
+ ksort($new_and_old_post_meta, SORT_REGULAR);
212
+
213
+ // array1 - The array to compare from
214
+ // array2 - An array to compare against
215
+ // Returns an array containing all the values from array1 that are not present in any of the other arrays.
216
+ // Keep only ACF fields in prev and new post meta.
217
+ $prev_post_meta = $this->keep_only_acf_stuff_in_array($prev_post_meta, $new_and_old_post_meta);
218
+ $new_post_meta = $this->keep_only_acf_stuff_in_array($new_post_meta, $new_and_old_post_meta);
219
+ $new_and_old_post_meta_acf_fields = array_merge($prev_post_meta, $new_post_meta);
220
+
221
+ // Map field name with fieldkey so we can get field objects when needed.
222
+ // Final array have values like:
223
+ // [product_images_0_image] => field_59a091044812e
224
+ // [product_images_0_image_caption] => field_59a0910f4812f
225
+ // [product_images_0_image_related_0_related_name] => field_59aaedd43ae11.
226
+ $fieldnames_to_field_keys = array();
227
+ foreach ($new_and_old_post_meta_acf_fields as $meta_key => $meta_value) {
228
+ // $key is like [product_images_0_image_related_1_related_name].
229
+ // Get ACF fieldkey for that value. Will be in $new_and_old_post_meta
230
+ // as the same as key but with underscore first
231
+ $meta_key_to_look_for = "_{$meta_key}";
232
+ if (isset($new_and_old_post_meta[ $meta_key_to_look_for ])) {
233
+ $fieldnames_to_field_keys[ $meta_key ] = $new_and_old_post_meta[ $meta_key_to_look_for ];
234
+ }
235
+ }
236
+
237
+ // Compare old with new = get only changed, not added, deleted are here.
238
+ $post_meta_diff1 = array_diff_assoc($prev_post_meta, $new_post_meta);
239
+
240
+ // Compare new with old = get an diff with added and changed stuff.
241
+ $post_meta_diff2 = array_diff_assoc($new_post_meta, $prev_post_meta);
242
+
243
+ // Compare keys, gets added fields.
244
+ $post_meta_added_fields = array_diff(array_keys($post_meta_diff2), array_keys($post_meta_diff1));
245
+ $post_meta_added_fields = array_values($post_meta_added_fields);
246
+
247
+ // Keys that exist in diff1 but not in diff2 = deleted.
248
+ $post_meta_removed_fields = array_diff_assoc(array_keys($post_meta_diff1), array_keys($post_meta_diff2));
249
+ $post_meta_removed_fields = array_values($post_meta_removed_fields);
250
+
251
+ $post_meta_changed_fields = array_keys($post_meta_diff1);
252
+
253
+ /*
254
+ * value is changed: added to both diff and diff2
255
+ * value is added, like in repeater: added to diff2 (not to diff)
256
+ * $diff3: contains only added things.
257
+ * Compare old and new values
258
+ * Loop all keys in $new_and_old_post_meta
259
+ * But act only on those whose keys begins with "_" and where the value begins with "field_" and ends with alphanum.
260
+ */
261
+
262
+ /*
263
+ * We have the diff, now add it to the context
264
+ * This is called after Simple History already has added its row
265
+ * So... we must add to the context late somehow
266
+ * Get the latest inserted row from the SimplePostLogger, check if that postID is
267
+ * same as the
268
+ */
269
+ $post_logger = $this->simpleHistory->getInstantiatedLoggerBySlug('SimplePostLogger');
270
+
271
+ // Save ACF diff if detected post here is same as the last one used in Postlogger.
272
+ if ($post_id === $post_logger->lastInsertContext['post_id']) {
273
+ $last_insert_id = $post_logger->lastInsertID;
274
+
275
+ // Append new info to the context of history item with id $post_logger->lastInsertID.
276
+ $acf_context = array();
277
+ $acf_context = $this->add_acf_context($acf_context, 'added', $post_meta_added_fields, $prev_post_meta, $new_post_meta, $fieldnames_to_field_keys);
278
+ $acf_context = $this->add_acf_context($acf_context, 'changed', $post_meta_changed_fields, $prev_post_meta, $new_post_meta, $fieldnames_to_field_keys);
279
+ $acf_context = $this->add_acf_context($acf_context, 'removed', $post_meta_removed_fields, $prev_post_meta, $new_post_meta, $fieldnames_to_field_keys);
280
+
281
+ $post_logger->append_context($last_insert_id, $acf_context);
282
+
283
+ // Prev and new post meta for testing.
284
+ /*
285
+ $post_logger->append_context(
286
+ $last_insert_id,
287
+ array(
288
+ 'prev_post_meta' => $prev_post_meta,
289
+ )
290
+ );
291
+ $post_logger->append_context(
292
+ $last_insert_id,
293
+ array(
294
+ 'new_post_meta' => $new_post_meta,
295
+ )
296
+ );
297
+ */
298
+ } // End if().
299
+ }
300
+
301
+ /**
302
+ * Add ACF context for added, removed, or changed fields.
303
+ *
304
+ * @param array $context Context.
305
+ * @param string $modify_type Type. added | removed | changed.
306
+ * @param array $relevant_acf_fields Fields.
307
+ * @param array $prev_post_meta Prev meta.
308
+ * @param array $new_post_meta New meta.
309
+ * @param array $fieldnames_to_field_keys Fieldnames to field keys mapping.
310
+ * @return array Modified context.
311
+ */
312
+ public function add_acf_context($context = array(), $modify_type = '', $relevant_acf_fields = array(), $prev_post_meta, $new_post_meta, $fieldnames_to_field_keys)
313
+ {
314
+
315
+ if (! is_array($context) || empty($modify_type) || empty($relevant_acf_fields)) {
316
+ return $context;
317
+ }
318
+
319
+ $loopnum = 0;
320
+ foreach ($relevant_acf_fields as $field_slug) {
321
+ /*
322
+ Store just the names to begin with
323
+ acf_field_added_0 = url.
324
+ acf_field_added_1 = first_name.
325
+
326
+ If field slug contains a number, like in "product_images_2_image"
327
+ that probably means that that field is a repeater with name "product_images"
328
+ with a sub field called "image" and that the image is the 2:nd among it's selected sub fields.
329
+
330
+ Example of how fields can look:
331
+ acf_field_added_0 product_images_2_image
332
+ acf_field_added_1 product_images_2_image_caption
333
+ acf_field_added_2 product_images_2_image_related
334
+ acf_field_changed_0 my_field_in_acf
335
+ acf_field_changed_1 product_images
336
+ acf_field_changed_2 price
337
+ acf_field_changed_3 description
338
+ */
339
+ $context_key = "acf_field_{$modify_type}_{$loopnum}";
340
+ $context[ "{$context_key}/slug" ] = $field_slug;
341
+
342
+ /*
343
+ * Try to get som extra info, like display name and type for this field.
344
+ * For a nice context in the feed we want: parent field group name and type?
345
+ */
346
+ if (isset($fieldnames_to_field_keys[ $field_slug ])) {
347
+ $field_key = $fieldnames_to_field_keys[ $field_slug ];
348
+ $context[ "{$context_key}/key" ] = $field_key;
349
+
350
+ // Interesting stuff in field object:
351
+ // - Label = the human readable name of the field
352
+ // - Type = the type of the field
353
+ // - Parent = id of parent field post id.
354
+ $field_object = get_field_object($field_key);
355
+
356
+ if (is_array($field_object)) {
357
+ $context[ "{$context_key}/label" ] = $field_object['label'];
358
+ if (! empty($field_object['type'])) {
359
+ $context[ "{$context_key}/type" ] = $field_object['type'];
360
+ }
361
+
362
+ // If no parent just continue to next field.
363
+ if (empty($field_object['parent'])) {
364
+ continue;
365
+ }
366
+
367
+ // We have at least one parent, get them all, including the field group
368
+ // $context[ "{$context_key}/field_parent_object" ] = $parent_field;
369
+ $field_parents = array();
370
+ $field_field_group = null;
371
+
372
+ // Begin with the direct parent.
373
+ $parent_field = $field_object;
374
+
375
+ while (! empty($parent_field['parent'])) {
376
+ $parentFieldParent = $parent_field['parent'];
377
+
378
+ // acf-field | acf-field-group.
379
+ $parent_field_post_type = get_post_type($parentFieldParent);
380
+
381
+ if (false === $parent_field_post_type) {
382
+ break;
383
+ }
384
+
385
+ if ('acf-field' === $parent_field_post_type) {
386
+ // Field is when field is for example a sub field of a repeater.
387
+ if (function_exists('acf_get_field')) {
388
+ // Since ACF 5.7.10 the acf_get_field() function is available.
389
+ $parent_field = acf_get_field($parentFieldParent);
390
+ } elseif (function_exists('_acf_get_field_by_id')) {
391
+ // ACF function _acf_get_field_by_id() is available before ACF 5.7.10.
392
+ $parent_field = _acf_get_field_by_id($parentFieldParent);
393
+ }
394
+ } elseif ('acf-field-group' === $parent_field_post_type) {
395
+ $parent_field = acf_get_field_group($parentFieldParent);
396
+ } else {
397
+ // Unknown post type.
398
+ break;
399
+ }
400
+
401
+ if (false === $parent_field) {
402
+ break;
403
+ }
404
+
405
+ if ('acf-field' === $parent_field_post_type) {
406
+ $field_parents[] = $parent_field;
407
+ } elseif ('acf-field-group' === $parent_field_post_type) {
408
+ $field_field_group = $parent_field;
409
+ } // End if().
410
+ } // End while().
411
+
412
+ $field_parents = array_reverse($field_parents);
413
+
414
+ // Array with info about each parent.
415
+ $arr_field_path = array();
416
+
417
+ if (! empty($field_field_group['title'])) {
418
+ $arr_field_path[] = array(
419
+ 'name' => $field_field_group['title'],
420
+ 'type' => 'field_group',
421
+ );
422
+ }
423
+
424
+ foreach ($field_parents as $one_field_parent) {
425
+ $arr_field_path[] = array(
426
+ 'name' => $one_field_parent['label'],
427
+ 'type' => 'field',
428
+ 'field_type' => $one_field_parent['type'],
429
+ );
430
+ }
431
+
432
+ if (! empty($arr_field_path)) {
433
+ $path_loop_num = 0;
434
+ foreach ($arr_field_path as $one_field_path) {
435
+ $context[ "{$context_key}/path_{$path_loop_num}/name" ] = $one_field_path['name'];
436
+ $context[ "{$context_key}/path_{$path_loop_num}/type" ] = $one_field_path['type'];
437
+ if (! empty($one_field_path['field_type'])) {
438
+ $context[ "{$context_key}/path_{$path_loop_num}/field_type" ] = $one_field_path['field_type'];
439
+ }
440
+ $path_loop_num++;
441
+ }
442
+ }
443
+
444
+ // Add value of fields if they are not part of
445
+ // repeatable or flexible fields or similar.
446
+ // error_log( "Final parents" . print_r( $field_parents, 1 ) );
447
+ // error_log( "Final field group" . print_r( $field_field_group['title'], 1 ) );
448
+ // error_log( "context" . print_r( $context, 1 ) );
449
+ } // End if().
450
+ } // End if().
451
+
452
+ $loopnum++;
453
+ } // End foreach().
454
+
455
+ // error_log( "---------------------------" );
456
+ // error_log( "field_path_string: $field_path_string");
457
+ // error_log( "context" . print_r( $context, 1 ) );
458
+ return $context;
459
+ }
460
+
461
+ /**
462
+ * Clean array and keep only ACF related things.
463
+ *
464
+ * Remove
465
+ * - underscore fields
466
+ * - fields with value field_*
467
+ *
468
+ * Keep
469
+ * - vals that are acf
470
+ *
471
+ * @param array $arr Array.
472
+ * @param array $all_fields Array fields.
473
+ */
474
+ public function keep_only_acf_stuff_in_array($arr, $all_fields)
475
+ {
476
+ $new_arr = array();
477
+
478
+ foreach ($arr as $key => $val) {
479
+ // Don't keep keys that begin with underscore "_".
480
+ if (strpos($key, '_') === 0) {
481
+ continue;
482
+ }
483
+
484
+ // Don't keep keys that begin with "field_".
485
+ if (strpos($val, 'field_') === 0) {
486
+ continue;
487
+ }
488
+
489
+ // Don't keep fields that does not have a corresponding _field value.
490
+ // Each key has both the name, for example 'color' and another
491
+ // key called '_color'. We check that the underscore version exists
492
+ // and contains 'field_'. After this check only ACF fields should exist
493
+ // in the array..
494
+ if (! isset($all_fields[ "_{$key}" ])) {
495
+ continue;
496
+ }
497
+
498
+ if (strpos($all_fields[ "_{$key}" ], 'field_') !== 0) {
499
+ continue;
500
+ }
501
+
502
+ $new_arr[ $key ] = $val;
503
+ }
504
+
505
+ return $new_arr;
506
+ }
507
+
508
+ /**
509
+ * Store prev post meta when post is saved.
510
+ * Stores data in $this->oldPostData.
511
+ */
512
+ public function on_admin_action_editpost()
513
+ {
514
+ $post_ID = isset($_POST['post_ID']) ? (int) $_POST['post_ID'] : 0;
515
+
516
+ if (! $post_ID) {
517
+ return;
518
+ }
519
+
520
+ $prev_post = get_post($post_ID);
521
+
522
+ if (is_wp_error($prev_post)) {
523
+ return;
524
+ }
525
+
526
+ $post_meta = get_post_custom($post_ID);
527
+
528
+ // Meta is array of arrays, get first value of each array value.
529
+ $post_meta = array_map('reset', $post_meta);
530
+
531
+ $this->oldPostData['prev_post_meta'] = $post_meta;
532
+ }
533
+
534
+ /**
535
+ * Called from PostLogger and its diff table output using filter 'simple_history/post_logger/post_updated/diff_table_output'.
536
+ * Diff table is generated only for post type 'acf-field-group'.
537
+ *
538
+ * @param string $diff_table_output
539
+ * @param array $context
540
+ * @return string
541
+ */
542
+ public function on_diff_table_output_field_group($diff_table_output, $context)
543
+ {
544
+ $post_type = ! empty($context['post_type']) ? $context['post_type'] : false;
545
+
546
+ // Bail if not ACF Field Group.
547
+ if ($post_type !== 'acf-field-group') {
548
+ return $diff_table_output;
549
+ }
550
+
551
+ // Field group fields to check for and output if found
552
+ $arrKeys = array(
553
+ 'instruction_placement' => array(
554
+ 'name' => _x('Instruction placement', 'Logger: Plugin ACF', 'simple-history'),
555
+ ),
556
+ 'label_placement' => array(
557
+ 'name' => _x('Label placement', 'Logger: Plugin ACF', 'simple-history'),
558
+ ),
559
+ 'description' => array(
560
+ 'name' => _x('Description', 'Logger: Plugin ACF', 'simple-history'),
561
+ ),
562
+ 'menu_order' => array(
563
+ 'name' => _x('Menu order', 'Logger: Plugin ACF', 'simple-history'),
564
+ ),
565
+ 'position' => array(
566
+ 'name' => _x('Position', 'Logger: Plugin ACF', 'simple-history'),
567
+ ),
568
+ 'active' => array(
569
+ 'name' => _x('Active', 'Logger: Plugin ACF', 'simple-history'),
570
+ ),
571
+ 'style' => array(
572
+ 'name' => _x('Style', 'Logger: Plugin ACF', 'simple-history'),
573
+ ),
574
+ );
575
+
576
+ foreach ($arrKeys as $acfKey => $acfVals) {
577
+ if (isset($context[ "acf_new_$acfKey" ]) && isset($context[ "acf_prev_$acfKey" ])) {
578
+ $diff_table_output .= sprintf(
579
+ '<tr>
580
  <td>%1$s</td>
581
  <td>
582
  <ins class="SimpleHistoryLogitem__keyValueTable__addedThing">%2$s</ins>
583
  <del class="SimpleHistoryLogitem__keyValueTable__removedThing">%3$s</del>
584
  </td>
585
  </tr>',
586
+ $acfVals['name'],
587
+ esc_html($context[ "acf_new_$acfKey" ]),
588
+ esc_html($context[ "acf_prev_$acfKey" ])
589
+ );
590
+ }
591
+ }
592
+
593
+ // If only acf_hide_on_screen_removed exists nothing is outputed.
594
+ $acf_hide_on_screen_added = empty($context['acf_hide_on_screen_added']) ? null : $context['acf_hide_on_screen_added'];
595
+ $acf_hide_on_screen_removed = empty($context['acf_hide_on_screen_removed']) ? null : $context['acf_hide_on_screen_removed'];
596
+
597
+ if ($acf_hide_on_screen_added || $acf_hide_on_screen_removed) {
598
+ $strCheckedHideOnScreen = '';
599
+ $strUncheckedHideOnScreen = '';
600
+
601
+ if ($acf_hide_on_screen_added) {
602
+ $strCheckedHideOnScreen = sprintf(
603
+ '%1$s %2$s',
604
+ _x('Checked', 'Logger: Plugin ACF', 'simple-history'), // 1
605
+ esc_html($acf_hide_on_screen_added) // 2
606
+ );
607
+ }
608
+
609
+ if ($acf_hide_on_screen_removed) {
610
+ $strUncheckedHideOnScreen = sprintf(
611
+ '%1$s %2$s',
612
+ _x('Unchecked', 'Logger: Plugin ACF', 'simple-history'), // 1
613
+ esc_html($acf_hide_on_screen_removed) // 2
614
+ );
615
+ }
616
+
617
+ $diff_table_output .= sprintf(
618
+ '<tr>
619
  <td>%1$s</td>
620
  <td>
621
  %2$s
622
  %3$s
623
  </td>
624
  </tr>',
625
+ _x('Hide on screen', 'Logger: Plugin ACF', 'simple-history'), // 1
626
+ $strCheckedHideOnScreen, // 2
627
+ $strUncheckedHideOnScreen // 3
628
+ );
629
+ }
630
+
631
+ // Check for deleted fields.
632
+ if (isset($context['acf_deleted_fields_0_key'])) {
633
+ // 1 or more deleted fields exist in context.
634
+ $loopnum = 0;
635
+ $strDeletedFields = '';
636
+
637
+ while (isset($context[ "acf_deleted_fields_{$loopnum}_key" ])) {
638
+ $strDeletedFields .= sprintf(
639
+ '%1$s (%3$s), ',
640
+ esc_html($context[ "acf_deleted_fields_{$loopnum}_label" ]),
641
+ esc_html($context[ "acf_deleted_fields_{$loopnum}_name" ]),
642
+ esc_html($context[ "acf_deleted_fields_{$loopnum}_type" ])
643
+ );
644
+
645
+ $loopnum++;
646
+ }
647
+
648
+ $strDeletedFields = trim($strDeletedFields, ', ');
649
+
650
+ $diff_table_output .= sprintf(
651
+ '<tr>
652
  <td>%1$s</td>
653
  <td>%2$s</td>
654
  </tr>',
655
+ _nx('Deleted field', 'Deleted fields', $loopnum, 'Logger: Plugin ACF', 'simple-history'), // 1
656
+ $strDeletedFields
657
+ );
658
+ } // if deleted fields
659
+
660
+ // Check for added fields
661
+ if (isset($context['acf_added_fields_0_key'])) {
662
+ // 1 or more deleted fields exist in context
663
+ $loopnum = 0;
664
+ $strAddedFields = '';
665
+
666
+ while (isset($context[ "acf_added_fields_{$loopnum}_key" ])) {
667
+ $strAddedFields .= sprintf(
668
+ '%1$s (%3$s), ',
669
+ esc_html($context[ "acf_added_fields_{$loopnum}_label" ]), // 1
670
+ esc_html($context[ "acf_added_fields_{$loopnum}_name" ]), // 2
671
+ esc_html($context[ "acf_added_fields_{$loopnum}_type" ]) // 3
672
+ );
673
+
674
+ $loopnum++;
675
+ }
676
+
677
+ $strAddedFields = trim($strAddedFields, ', ');
678
+
679
+ $diff_table_output .= sprintf(
680
+ '<tr>
681
  <td>%1$s</td>
682
  <td>%2$s</td>
683
  </tr>',
684
+ _nx('Added field', 'Added fields', $loopnum, 'Logger: Plugin ACF', 'simple-history'), // 1
685
+ $strAddedFields
686
+ );
687
+ } // if deleted fields
688
+
689
+ // Check for modified fields
690
+ if (isset($context['acf_modified_fields_0_ID_prev'])) {
691
+ // 1 or more modifiedfields exist in context
692
+ $loopnum = 0;
693
+ $strModifiedFields = '';
694
+ $arrAddedFieldsKeysToCheck = array(
695
+ 'name' => array(
696
+ 'name' => _x('Name: ', 'Logger: Plugin ACF', 'simple-history'),
697
+ ),
698
+ 'parent' => array(
699
+ 'name' => _x('Parent: ', 'Logger: Plugin ACF', 'simple-history'),
700
+ ),
701
+ 'key' => array(
702
+ 'name' => _x('Key: ', 'Logger: Plugin ACF', 'simple-history'),
703
+ ),
704
+ 'label' => array(
705
+ 'name' => _x('Label: ', 'Logger: Plugin ACF', 'simple-history'),
706
+ ),
707
+ 'type' => array(
708
+ 'name' => _x('Type: ', 'Logger: Plugin ACF', 'simple-history'),
709
+ ),
710
+ );
711
+
712
+ while (isset($context[ "acf_modified_fields_{$loopnum}_name_prev" ])) {
713
+ // One modified field, with one or more changed things
714
+ $strOneModifiedField = '';
715
+
716
+ // Add the field name manually, if it is not among the changed field,
717
+ // or we don't know what field the other changed values belongs to.
718
+ /*
719
+ if (empty($context["acf_modified_fields_{$loopnum}_name_new"])) {
720
+ $strOneModifiedField .= sprintf(
721
+ _x('Name: %1$s', 'Logger: Plugin ACF', 'simple-history'), // 1
722
+ esc_html($context["acf_modified_fields_{$loopnum}_name_prev"]) // 2
723
+ );
724
+ }
725
+ */
726
+
727
+ // Add the label name manually, if it is not among the changed field,
728
+ // or we don't know what field the other changed values belongs to.
729
+ if (empty($context[ "acf_modified_fields_{$loopnum}_label_new" ])) {
730
+ $strOneModifiedField .= sprintf(
731
+ _x('Label: %1$s', 'Logger: Plugin ACF', 'simple-history'), // 1
732
+ esc_html($context[ "acf_modified_fields_{$loopnum}_label_prev" ]) // 2
733
+ );
734
+ }
735
+
736
+ // Check for other keys changed for this field
737
+ foreach ($arrAddedFieldsKeysToCheck as $oneAddedFieldKeyToCheck => $oneAddedFieldKeyToCheckVals) {
738
+ $newAndOldValsExists = isset($context[ "acf_modified_fields_{$loopnum}_{$oneAddedFieldKeyToCheck}_new" ]) && isset($context[ "acf_modified_fields_{$loopnum}_{$oneAddedFieldKeyToCheck}_new" ]);
739
+ if ($newAndOldValsExists) {
740
+ $strOneModifiedField .= sprintf(
741
+ '
742
  %4$s
743
  %3$s
744
  <ins class="SimpleHistoryLogitem__keyValueTable__addedThing">%1$s</ins>
745
  <del class="SimpleHistoryLogitem__keyValueTable__removedThing">%2$s</del>
746
  ',
747
+ esc_html($context[ "acf_modified_fields_{$loopnum}_{$oneAddedFieldKeyToCheck}_new" ]), // 1
748
+ esc_html($context[ "acf_modified_fields_{$loopnum}_{$oneAddedFieldKeyToCheck}_prev" ]), // 2
749
+ esc_html($oneAddedFieldKeyToCheckVals['name']), // 3
750
+ empty($strOneModifiedField) ? '' : '<br>' // 4 new line
751
+ );
752
+ }
753
+ }
754
+
755
+ $strOneModifiedField = trim($strOneModifiedField, ", \n\r\t");
756
+
757
+ if ($strOneModifiedField) {
758
+ $strModifiedFields .= sprintf(
759
+ '<tr>
760
  <td>%1$s</td>
761
  <td>%2$s</td>
762
  </tr>',
763
+ _x('Modified field', 'Logger: Plugin ACF', 'simple-history'), // 1
764
+ $strOneModifiedField
765
+ );
766
+ }
767
+
768
+ $loopnum++;
769
+ }
770
+
771
+ /*
772
+ if ($strModifiedFields) {
773
+ $strModifiedFields = sprintf(
774
+ '<tr>
775
+ <td>%1$s</td>
776
+ <td>%2$s</td>
777
+ </tr>',
778
+ _nx('Modified field', 'Modified fields', $loopnum, 'Logger: Plugin ACF', 'simple-history'), // 1
779
+ $strModifiedFields
780
+ ) . $strModifiedFields;
781
+ }*/
782
+
783
+ $diff_table_output .= $strModifiedFields;
784
+ } // if deleted fields
785
+
786
+ return $diff_table_output;
787
+ }
788
+
789
+ /**
790
+ * Append ACF data to post context.
791
+ *
792
+ * Called via filter `simple_history/post_logger/post_updated/context`.
793
+ *
794
+ * @param array $context
795
+ * @param WP_Post $post
796
+ */
797
+ public function on_post_updated_context($context, $post)
798
+ {
799
+
800
+ // Only act if this is a ACF field group that is saved
801
+ if ($post->post_type !== 'acf-field-group') {
802
+ return $context;
803
+ }
804
+
805
+ // Remove some keys that we don't want,
806
+ // for example the content because that's just a json string
807
+ // in acf-field-group posts.
808
+ unset(
809
+ $context['post_prev_post_content'],
810
+ $context['post_new_post_content'],
811
+ $context['post_prev_post_name'],
812
+ $context['post_new_post_name'],
813
+ $context['post_prev_post_date'],
814
+ $context['post_new_post_date'],
815
+ $context['post_prev_post_date_gmt'],
816
+ $context['post_new_post_date_gmt']
817
+ );
818
+
819
+ $acf_data_diff = array();
820
+
821
+ // 'fieldGroup' fields to check.
822
+ $arr_field_group_keys_to_diff = array(
823
+ 'menu_order',
824
+ 'position',
825
+ 'style',
826
+ 'label_placement',
827
+ 'instruction_placement',
828
+ 'active',
829
+ 'description',
830
+ );
831
+
832
+ $fieldGroup = $this->oldAndNewFieldGroupsAndFields['fieldGroup'];
833
+
834
+ foreach ($arr_field_group_keys_to_diff as $key) {
835
+ if (isset($fieldGroup['old'][ $key ]) && isset($fieldGroup['new'][ $key ])) {
836
+ $acf_data_diff = $this->add_diff($acf_data_diff, $key, (string) $fieldGroup['old'][ $key ], (string) $fieldGroup['new'][ $key ]);
837
+ }
838
+ }
839
+
840
+ foreach ($acf_data_diff as $diff_key => $diff_values) {
841
+ $context[ "acf_prev_{$diff_key}" ] = $diff_values['old'];
842
+ $context[ "acf_new_{$diff_key}" ] = $diff_values['new'];
843
+ }
844
+
845
+ // Add checked or uncheckd hide on screen-items to context
846
+ $arrhHideOnScreenAdded = array();
847
+ $arrHideOnScreenRemoved = array();
848
+
849
+ $fieldGroup['new']['hide_on_screen'] = isset($fieldGroup['new']['hide_on_screen']) && is_array($fieldGroup['new']['hide_on_screen']) ? $fieldGroup['new']['hide_on_screen'] : array();
850
+ $fieldGroup['old']['hide_on_screen'] = isset($fieldGroup['old']['hide_on_screen']) && is_array($fieldGroup['old']['hide_on_screen']) ? $fieldGroup['old']['hide_on_screen'] : array();
851
+
852
+ // dd($fieldGroup['old']['hide_on_screen'], $fieldGroup['new']['hide_on_screen']);
853
+ // Act when new or old hide_on_screen is set
854
+ if (! empty($fieldGroup['new']['hide_on_screen']) || ! empty($fieldGroup['old']['hide_on_screen'])) {
855
+ $arrhHideOnScreenAdded = array_diff($fieldGroup['new']['hide_on_screen'], $fieldGroup['old']['hide_on_screen']);
856
+ $arrHideOnScreenRemoved = array_diff($fieldGroup['old']['hide_on_screen'], $fieldGroup['new']['hide_on_screen']);
857
+
858
+ // ddd($arrhHideOnScreenAdded, $arrHideOnScreenRemoved);
859
+ if ($arrhHideOnScreenAdded) {
860
+ $context['acf_hide_on_screen_added'] = implode(',', $arrhHideOnScreenAdded);
861
+ }
862
+
863
+ if ($arrHideOnScreenRemoved) {
864
+ $context['acf_hide_on_screen_removed'] = implode(',', $arrHideOnScreenRemoved);
865
+ }
866
+ }
867
+
868
+ // ddd($context, $arrhHideOnScreenAdded, $arrHideOnScreenRemoved);
869
+ // Add removed fields to context
870
+ if (! empty($this->oldAndNewFieldGroupsAndFields['deletedFields']) && is_array($this->oldAndNewFieldGroupsAndFields['deletedFields'])) {
871
+ $loopnum = 0;
872
+ foreach ($this->oldAndNewFieldGroupsAndFields['deletedFields'] as $oneDeletedField) {
873
+ $context[ "acf_deleted_fields_{$loopnum}_key" ] = $oneDeletedField['key'];
874
+ $context[ "acf_deleted_fields_{$loopnum}_name" ] = $oneDeletedField['name'];
875
+ $context[ "acf_deleted_fields_{$loopnum}_label" ] = $oneDeletedField['label'];
876
+ $context[ "acf_deleted_fields_{$loopnum}_type" ] = $oneDeletedField['type'];
877
+ $loopnum++;
878
+ }
879
+ }
880
+
881
+ // Add added fields to context
882
+ if (! empty($this->oldAndNewFieldGroupsAndFields['addedFields']) && is_array($this->oldAndNewFieldGroupsAndFields['addedFields'])) {
883
+ $loopnum = 0;
884
+
885
+ foreach ($this->oldAndNewFieldGroupsAndFields['addedFields'] as $oneAddedField) {
886
+ // Id not available here, wold be nice to have
887
+ // $context["acf_added_fields_{$loopnum}_ID"] = $oneAddedField['ID'];
888
+ $context[ "acf_added_fields_{$loopnum}_key" ] = $oneAddedField['key'];
889
+ $context[ "acf_added_fields_{$loopnum}_name" ] = $oneAddedField['name'];
890
+ $context[ "acf_added_fields_{$loopnum}_label" ] = $oneAddedField['label'];
891
+ $context[ "acf_added_fields_{$loopnum}_type" ] = $oneAddedField['type'];
892
+ $loopnum++;
893
+ }
894
+ }
895
+
896
+ // Add modified fields to context
897
+ // dd('on_post_updated_context', $context, $this->oldAndNewFieldGroupsAndFields);
898
+ if (! empty($this->oldAndNewFieldGroupsAndFields['modifiedFields']['old']) && ! empty($this->oldAndNewFieldGroupsAndFields['modifiedFields']['new'])) {
899
+ $modifiedFields = $this->oldAndNewFieldGroupsAndFields['modifiedFields'];
900
+
901
+ $arr_added_fields_keys_to_add = array(
902
+ 'parent',
903
+ 'key',
904
+ 'label',
905
+ 'name',
906
+ 'type',
907
+ );
908
+
909
+ $loopnum = 0;
910
+
911
+ foreach ($modifiedFields['old'] as $modifiedFieldId => $modifiedFieldValues) {
912
+ // Both old and new values mest exist
913
+ if (empty($modifiedFields['new'][ $modifiedFieldId ])) {
914
+ continue;
915
+ }
916
+
917
+ // Always add ID, name, and lavel
918
+ $context[ "acf_modified_fields_{$loopnum}_ID_prev" ] = $modifiedFields['old'][ $modifiedFieldId ]['ID'];
919
+ $context[ "acf_modified_fields_{$loopnum}_name_prev" ] = $modifiedFields['old'][ $modifiedFieldId ]['name'];
920
+ $context[ "acf_modified_fields_{$loopnum}_label_prev" ] = $modifiedFields['old'][ $modifiedFieldId ]['label'];
921
+
922
+ foreach ($arr_added_fields_keys_to_add as $one_key_to_add) {
923
+ // Check that new and old exist.
924
+ $new_exists = isset($modifiedFields['new'][ $modifiedFieldId ][ $one_key_to_add ]);
925
+ $old_exists = isset($modifiedFields['old'][ $modifiedFieldId ][ $one_key_to_add ]);
926
+
927
+ if (! $new_exists || ! $old_exists) {
928
+ continue;
929
+ }
930
+
931
+ // Only add to context if modified.
932
+ if ($modifiedFields['new'][ $modifiedFieldId ][ $one_key_to_add ] != $modifiedFields['old'][ $modifiedFieldId ][ $one_key_to_add ]) {
933
+ $context[ "acf_modified_fields_{$loopnum}_{$one_key_to_add}_prev" ] = $modifiedFields['old'][ $modifiedFieldId ][ $one_key_to_add ];
934
+ $context[ "acf_modified_fields_{$loopnum}_{$one_key_to_add}_new" ] = $modifiedFields['new'][ $modifiedFieldId ][ $one_key_to_add ];
935
+ }
936
+ }
937
+
938
+ $loopnum++;
939
+ }
940
+ }
941
+
942
+ return $context;
943
+ }
944
+
945
+ public function add_diff($post_data_diff, $key, $old_value, $new_value)
946
+ {
947
+ if ($old_value != $new_value) {
948
+ $post_data_diff[ $key ] = array(
949
+ 'old' => $old_value,
950
+ 'new' => $new_value,
951
+ );
952
+ }
953
+
954
+ return $post_data_diff;
955
+ }
956
+
957
+ /**
958
+ * Store a version of the field group as it was before the save
959
+ * Called before field group post/values is added to db
960
+ *
961
+ * @param array $data Post data.
962
+ * @param array $postarr Post data.
963
+ */
964
+ public function on_wp_insert_post_data($data, $postarr)
965
+ {
966
+
967
+ // Only do this if ACF field group is being saved.
968
+ if ($postarr['post_type'] !== 'acf-field-group') {
969
+ return $data;
970
+ }
971
+
972
+ if (empty($postarr['ID'])) {
973
+ return $data;
974
+ }
975
+
976
+ if (empty($_POST['acf_field_group'])) {
977
+ return $data;
978
+ }
979
+
980
+ $this->oldAndNewFieldGroupsAndFields['fieldGroup']['old'] = acf_get_field_group($postarr['ID']);
981
+
982
+ $this->oldAndNewFieldGroupsAndFields['fieldGroup']['new'] = acf_get_valid_field_group($_POST['acf_field_group']);
983
+
984
+ return $data;
985
+ }
986
+
987
+ /**
988
+ * ACF field group is saved
989
+ * Called before ACF calls its save_post filter
990
+ * Here we save the new fields values and also get the old values so we can compare
991
+ */
992
+ public function on_transition_post_status($new_status, $old_status, $post)
993
+ {
994
+ static $isCalled = false;
995
+
996
+ if ($isCalled) {
997
+ return;
998
+ }
999
+
1000
+ $isCalled = true;
1001
+
1002
+ $post_id = $post->ID;
1003
+
1004
+ // do not act if this is an auto save routine
1005
+ if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
1006
+ return;
1007
+ }
1008
+
1009
+ // bail early if not acf-field-group
1010
+ if ($post->post_type !== 'acf-field-group') {
1011
+ return;
1012
+ }
1013
+
1014
+ // only save once! WordPress save's a revision as well.
1015
+ if (wp_is_post_revision($post_id)) {
1016
+ return;
1017
+ }
1018
+
1019
+ // Store info about fields that are going to be deleted
1020
+ if (! empty($_POST['_acf_delete_fields'])) {
1021
+ $deletedFieldsIDs = explode('|', $_POST['_acf_delete_fields']);
1022
+ $deletedFieldsIDs = array_map('intval', $deletedFieldsIDs);
1023
+
1024
+ foreach ($deletedFieldsIDs as $id) {
1025
+ if (! $id) {
1026
+ continue;
1027
+ }
1028
+
1029
+ $field_info = acf_get_field($id);
1030
+
1031
+ if (! $field_info) {
1032
+ continue;
1033
+ }
1034
+
1035
+ $this->oldAndNewFieldGroupsAndFields['deletedFields'][ $id ] = $field_info;
1036
+ }
1037
+ }
1038
+
1039
+ // Store info about added or modified fields
1040
+ if (! empty($_POST['acf_fields']) && is_array($_POST['acf_fields'])) {
1041
+ foreach ($_POST['acf_fields'] as $oneFieldAddedOrUpdated) {
1042
+ if (empty($oneFieldAddedOrUpdated['ID'])) {
1043
+ // New fields have no id
1044
+ // 'ID' => string(0) ""
1045
+ $this->oldAndNewFieldGroupsAndFields['addedFields'][] = $oneFieldAddedOrUpdated;
1046
+ } else {
1047
+ // Existing fields have an id
1048
+ // 'ID' => string(3) "383"
1049
+ $this->oldAndNewFieldGroupsAndFields['modifiedFields']['old'][ $oneFieldAddedOrUpdated['ID'] ] = acf_get_field($oneFieldAddedOrUpdated['ID']);
1050
+
1051
+ $this->oldAndNewFieldGroupsAndFields['modifiedFields']['new'][ $oneFieldAddedOrUpdated['ID'] ] = $oneFieldAddedOrUpdated;
1052
+ }
1053
+ }
1054
+ }
1055
+
1056
+ // We don't do anything else here, but we make the actual logging
1057
+ // in filter 'acf/update_field_group' beacuse it's safer because
1058
+ // ACF has done it's validation and it's after ACF has saved the fields,
1059
+ // so less likely that we make some critical error
1060
+ }
1061
+
1062
+
1063
+ /**
1064
+ * Add the post types that ACF uses for fields to the array of post types
1065
+ * that the default post logger should not log. If not each field will cause one
1066
+ * post update log message.
1067
+ */
1068
+ public function remove_acf_from_postlogger($skip_posttypes)
1069
+ {
1070
+ array_push(
1071
+ $skip_posttypes,
1072
+ 'acf-field'
1073
+ );
1074
+
1075
+ return $skip_posttypes;
1076
+ }
1077
+ } // Class.
1078
  } // End if().
loggers/Plugin_BeaverBuilder.php CHANGED
@@ -6,90 +6,109 @@ defined('ABSPATH') or die();
6
  * Logger for Beaver Builder
7
  */
8
  if (!class_exists('Plugin_BeaverBuilder')) {
9
- class Plugin_BeaverBuilder extends SimpleLogger
10
- {
11
- public $slug = __CLASS__;
12
 
13
- function getInfo()
14
- {
15
- $arr_info = array(
16
- 'name' => 'Plugin Beaver Builder',
17
- 'description' => _x(
18
- 'Logs various things in Beaver Builder',
19
- 'Logger: Plugin Beaver Builder',
20
- 'simple-history'
21
- ),
22
- 'name_via' => _x(
23
- 'Using plugin Beaver Builder',
24
- 'Logger: Plugin Beaver Builder',
25
- 'simple-history'
26
- ),
27
- 'capability' => 'manage_options',
28
- 'messages' => array(
29
- 'layout_saved' => __(
30
- 'Layout "{layout_name}" updated',
31
- 'simple-history'
32
- ),
33
- 'template_saved' => __(
34
- 'Template "{layout_name}" updated',
35
- 'simple-history'
36
- ),
37
- 'draft_saved' => __(
38
- 'Draft "{layout_name}" updated',
39
- 'simple-history'
40
- ),
41
- 'admin_saved' => __(
42
- 'Beaver Builder settings saved',
43
- 'simple-history'
44
- )
45
- )
46
- );
47
 
48
- return $arr_info;
49
- }
50
 
51
- function loaded()
52
- {
53
- if (!class_exists('FLBuilder')) {
54
- return;
55
- }
56
-
57
- add_action(
58
- 'fl_builder_after_save_layout',
59
- array($this, 'save_layout'),
60
- 10,
61
- 4
62
- );
63
- add_action(
64
- 'fl_builder_after_save_user_template',
65
- array($this, 'save_layout'),
66
- 10,
67
- 4
68
- );
69
- add_action(
70
- 'fl_builder_after_save_draft',
71
- array($this, 'save_layout'),
72
- 10,
73
- 4
74
- );
75
- add_action('fl_builder_admin_settings_save', array(
76
- $this,
77
- 'save_admin'
78
- ));
79
- }
80
 
81
- function save_layout($post_id, $publish, $data, $settings)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  {
83
- $post = get_post($post_id);
84
  $context = array(
85
  'layout_name' => $post->post_name
86
  );
87
- $this->noticeMessage('layout_saved', $context);
88
  }
89
-
90
- function save_admin()
91
  {
92
- $this->noticeMessage('admin_saved');
 
 
 
93
  }
94
- } // class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  } // End if().
6
  * Logger for Beaver Builder
7
  */
8
  if (!class_exists('Plugin_BeaverBuilder')) {
9
+ class Plugin_BeaverBuilder extends SimpleLogger
10
+ {
11
+ public $slug = __CLASS__;
12
 
13
+ function getInfo()
14
+ {
15
+ $arr_info = array(
16
+ 'name' => 'Plugin Beaver Builder',
17
+ 'description' => _x(
18
+ 'Logs various things in Beaver Builder',
19
+ 'Logger: Plugin Beaver Builder',
20
+ 'simple-history'
21
+ ),
22
+ 'name_via' => _x(
23
+ 'Using plugin Beaver Builder',
24
+ 'Logger: Plugin Beaver Builder',
25
+ 'simple-history'
26
+ ),
27
+ 'capability' => 'manage_options',
28
+ 'messages' => array(
29
+ 'layout_saved' => __(
30
+ 'Layout "{layout_name}" updated',
31
+ 'simple-history'
32
+ ),
33
+ 'template_saved' => __(
34
+ 'Template "{layout_name}" updated',
35
+ 'simple-history'
36
+ ),
37
+ 'draft_saved' => __(
38
+ 'Draft "{layout_name}" updated',
39
+ 'simple-history'
40
+ ),
41
+ 'admin_saved' => __(
42
+ 'Beaver Builder settings saved',
43
+ 'simple-history'
44
+ )
45
+ )
46
+ );
47
 
48
+ return $arr_info;
49
+ }
50
 
51
+ function loaded()
52
+ {
53
+ if (!class_exists('FLBuilder')) {
54
+ return;
55
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
+ add_action(
58
+ 'fl_builder_after_save_layout',
59
+ array($this, 'save_layout'),
60
+ 10,
61
+ 4
62
+ );
63
+ add_action(
64
+ 'fl_builder_after_save_user_template',
65
+ array($this, 'save_template'),
66
+ 10,
67
+ 1
68
+ );
69
+ add_action(
70
+ 'fl_builder_after_save_draft',
71
+ array($this, 'save_draft'),
72
+ 10,
73
+ 2
74
+ );
75
+ add_action('fl_builder_admin_settings_save', array(
76
+ $this,
77
+ 'save_admin'
78
+ ));
79
+ }
80
+
81
+ function save_template($post_id)
82
  {
83
+ $post = get_post($post_id);
84
  $context = array(
85
  'layout_name' => $post->post_name
86
  );
87
+ $this->noticeMessage('template_saved', $context);
88
  }
89
+
90
+ function save_draft($post_id, $publish)
91
  {
92
+ $context = array(
93
+ 'layout_name' => $post_id
94
+ );
95
+ $this->noticeMessage('draft_saved', $context);
96
  }
97
+
98
+ function save_layout($post_id, $publish, $data, $settings)
99
+ {
100
+ $post = get_post($post_id);
101
+ $context = array(
102
+ 'layout_name' => $post->post_name
103
+ );
104
+ if ( $publish ) {
105
+ $this->noticeMessage('layout_saved', $context);
106
+ }
107
+ }
108
+
109
+ function save_admin()
110
+ {
111
+ $this->noticeMessage('admin_saved');
112
+ }
113
+ } // class
114
  } // End if().
loggers/Plugin_DuplicatePost.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /**
6
  * Logger for the Duplicate Post plugin
@@ -9,119 +9,124 @@ defined( 'ABSPATH' ) or die();
9
  * @package SimpleHistory
10
  * @since 2.13
11
  */
12
- if ( ! class_exists( 'Plugin_DuplicatePost' ) ) {
13
-
14
- class Plugin_DuplicatePost extends SimpleLogger {
15
-
16
- public $slug = __CLASS__;
17
-
18
- public function getInfo() {
19
- $arr_info = array(
20
- 'name' => 'Plugin Duplicate Posts',
21
- 'description' => _x( 'Logs posts and pages cloned using plugin Duplicate Post', 'Logger: Plugin Duplicate Post', 'simple-history' ),
22
- 'name_via' => _x( 'Using plugin Duplicate Posts', 'Logger: Plugin Duplicate Post', 'simple-history' ),
23
- 'capability' => 'manage_options',
24
- 'messages' => array(
25
- 'post_duplicated' => _x( 'Cloned "{duplicated_post_title}" to a new post', 'Logger: Plugin Duplicate Post', 'simple-history' ),
26
- ),
27
- );
28
-
29
- return $arr_info;
30
- }
31
-
32
- public function loaded() {
33
- require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
34
-
35
- $pluginFilePath = 'duplicate-post/duplicate-post.php';
36
- $isPluginActive = is_plugin_active( 'duplicate-post/duplicate-post.php' );
37
-
38
- if ( ! $isPluginActive ) {
39
- return;
40
- }
41
-
42
- // When a copy have been made of a post or page
43
- // the action 'dp_duplicate_page' or 'dp_duplicate_post'
44
- // is fired with args $new_post_id, $post, $status.
45
- // We add actions with prio 20 so we probably run after
46
- // the plugins own
47
- add_action( 'dp_duplicate_post', array( $this, 'onDpDuplicatePost' ), 100, 3 );
48
- add_action( 'dp_duplicate_page', array( $this, 'onDpDuplicatePost' ), 100, 3 );
49
- }
50
-
51
- /**
52
- * A post or page was duplicated
53
- *
54
- * @param $new_post_id
55
- * @param $post old post that a copy was made of
56
- * @param $status
57
- */
58
- public function onDpDuplicatePost( $newPostID, $post, $status ) {
59
- $new_post = get_post( $newPostID );
60
-
61
- $context = array(
62
- 'new_post_title' => $new_post->post_title,
63
- 'new_post_id' => $new_post->ID,
64
- 'duplicated_post_title' => $post->post_title,
65
- 'duplicated_post_id' => $post->ID,
66
- // "duplicate_new_post_id" => $newPostID,
67
- // "status" => $status
68
- );
69
-
70
- $this->infoMessage(
71
- 'post_duplicated',
72
- $context
73
- );
74
- }
75
-
76
- /**
77
- * Modify plain output to include link to post
78
- */
79
- public function getLogRowPlainTextOutput( $row ) {
80
-
81
- $context = $row->context;
82
- $new_post_id = isset( $context['new_post_id'] ) ? $context['new_post_id'] : null;
83
- $duplicated_post_id = isset( $context['duplicated_post_id'] ) ? $context['duplicated_post_id'] : null;
84
- $duplicated_post_title = isset( $context['duplicated_post_title'] ) ? $context['duplicated_post_title'] : null;
85
- $message_key = isset( $context['_message_key'] ) ? $context['_message_key'] : null;
86
-
87
- $message = $row->message;
88
-
89
- // Check if post still is available
90
- // It will return a WP_Post Object if post still is in system
91
- // If post is deleted from trash (not just moved there), then null is returned
92
- $postDuplicated = get_post( $duplicated_post_id );
93
- $post_is_available = is_a( $postDuplicated, 'WP_Post' );
94
-
95
- // Try to get singular name
96
- $post_type = isset( $postDuplicated->post_type ) ? $postDuplicated->post_type : '';
97
- $post_type_obj = get_post_type_object( $post_type );
98
-
99
- if ( ! is_null( $post_type_obj ) ) {
100
- if ( ! empty( $post_type_obj->labels->singular_name ) ) {
101
- $context['duplicated_post_post_type_singular_name'] = strtolower( $post_type_obj->labels->singular_name );
102
- }
103
- }
104
-
105
- $context['duplicated_post_edit_link'] = get_edit_post_link( $duplicated_post_id );
106
- $context['new_post_edit_link'] = get_edit_post_link( $new_post_id );
107
-
108
- // If post is not available any longer then we can't link to it, so keep plain message then
109
- // Also keep plain format if user is not allowed to edit post (edit link is empty)
110
- if ( $post_is_available && $context['duplicated_post_edit_link'] ) {
111
- $message = _x( 'Cloned {duplicated_post_post_type_singular_name} <a href="{duplicated_post_edit_link}">"{duplicated_post_title}"</a> to <a href="{new_post_edit_link}">a new {duplicated_post_post_type_singular_name}</a>', 'Logger: Plugin Duplicate Post', 'simple-history' );
112
- } // End if().
113
-
114
- $context['new_post_edit_link'] = isset( $context['new_post_edit_link'] ) ? esc_html( $context['new_post_edit_link'] ) : '';
115
-
116
- $context['duplicated_post_edit_link'] = isset( $context['duplicated_post_edit_link'] ) ? esc_html( $context['duplicated_post_edit_link'] ) : '';
117
-
118
- $context['duplicated_post_title'] = isset( $context['duplicated_post_title'] ) ? esc_html( $context['duplicated_post_title'] ) : '';
119
-
120
- $context['duplicated_post_title'] = isset( $context['duplicated_post_title'] ) ? esc_html( $context['duplicated_post_title'] ) : '';
121
-
122
- $context['duplicated_post_post_type_singular_name'] = isset( $context['duplicated_post_post_type_singular_name'] ) ? esc_html( $context['duplicated_post_post_type_singular_name'] ) : '';
123
-
124
- return $this->interpolate( $message, $context, $row );
125
- }
126
- } // class
 
 
 
 
 
127
  } // End if().
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logger for the Duplicate Post plugin
9
  * @package SimpleHistory
10
  * @since 2.13
11
  */
12
+ if (! class_exists('Plugin_DuplicatePost')) {
13
+
14
+ class Plugin_DuplicatePost extends SimpleLogger
15
+ {
16
+
17
+ public $slug = __CLASS__;
18
+
19
+ public function getInfo()
20
+ {
21
+ $arr_info = array(
22
+ 'name' => 'Plugin Duplicate Posts',
23
+ 'description' => _x('Logs posts and pages cloned using plugin Duplicate Post', 'Logger: Plugin Duplicate Post', 'simple-history'),
24
+ 'name_via' => _x('Using plugin Duplicate Posts', 'Logger: Plugin Duplicate Post', 'simple-history'),
25
+ 'capability' => 'manage_options',
26
+ 'messages' => array(
27
+ 'post_duplicated' => _x('Cloned "{duplicated_post_title}" to a new post', 'Logger: Plugin Duplicate Post', 'simple-history'),
28
+ ),
29
+ );
30
+
31
+ return $arr_info;
32
+ }
33
+
34
+ public function loaded()
35
+ {
36
+ require_once(ABSPATH . 'wp-admin/includes/plugin.php');
37
+
38
+ $pluginFilePath = 'duplicate-post/duplicate-post.php';
39
+ $isPluginActive = is_plugin_active('duplicate-post/duplicate-post.php');
40
+
41
+ if (! $isPluginActive) {
42
+ return;
43
+ }
44
+
45
+ // When a copy have been made of a post or page
46
+ // the action 'dp_duplicate_page' or 'dp_duplicate_post'
47
+ // is fired with args $new_post_id, $post, $status.
48
+ // We add actions with prio 20 so we probably run after
49
+ // the plugins own
50
+ add_action('dp_duplicate_post', array( $this, 'onDpDuplicatePost' ), 100, 3);
51
+ add_action('dp_duplicate_page', array( $this, 'onDpDuplicatePost' ), 100, 3);
52
+ }
53
+
54
+ /**
55
+ * A post or page was duplicated
56
+ *
57
+ * @param $new_post_id
58
+ * @param $post old post that a copy was made of
59
+ * @param $status
60
+ */
61
+ public function onDpDuplicatePost($newPostID, $post, $status)
62
+ {
63
+ $new_post = get_post($newPostID);
64
+
65
+ $context = array(
66
+ 'new_post_title' => $new_post->post_title,
67
+ 'new_post_id' => $new_post->ID,
68
+ 'duplicated_post_title' => $post->post_title,
69
+ 'duplicated_post_id' => $post->ID,
70
+ // "duplicate_new_post_id" => $newPostID,
71
+ // "status" => $status
72
+ );
73
+
74
+ $this->infoMessage(
75
+ 'post_duplicated',
76
+ $context
77
+ );
78
+ }
79
+
80
+ /**
81
+ * Modify plain output to include link to post
82
+ */
83
+ public function getLogRowPlainTextOutput($row)
84
+ {
85
+
86
+ $context = $row->context;
87
+ $new_post_id = isset($context['new_post_id']) ? $context['new_post_id'] : null;
88
+ $duplicated_post_id = isset($context['duplicated_post_id']) ? $context['duplicated_post_id'] : null;
89
+ $duplicated_post_title = isset($context['duplicated_post_title']) ? $context['duplicated_post_title'] : null;
90
+ $message_key = isset($context['_message_key']) ? $context['_message_key'] : null;
91
+
92
+ $message = $row->message;
93
+
94
+ // Check if post still is available
95
+ // It will return a WP_Post Object if post still is in system
96
+ // If post is deleted from trash (not just moved there), then null is returned
97
+ $postDuplicated = get_post($duplicated_post_id);
98
+ $post_is_available = is_a($postDuplicated, 'WP_Post');
99
+
100
+ // Try to get singular name
101
+ $post_type = isset($postDuplicated->post_type) ? $postDuplicated->post_type : '';
102
+ $post_type_obj = get_post_type_object($post_type);
103
+
104
+ if (! is_null($post_type_obj)) {
105
+ if (! empty($post_type_obj->labels->singular_name)) {
106
+ $context['duplicated_post_post_type_singular_name'] = strtolower($post_type_obj->labels->singular_name);
107
+ }
108
+ }
109
+
110
+ $context['duplicated_post_edit_link'] = get_edit_post_link($duplicated_post_id);
111
+ $context['new_post_edit_link'] = get_edit_post_link($new_post_id);
112
+
113
+ // If post is not available any longer then we can't link to it, so keep plain message then
114
+ // Also keep plain format if user is not allowed to edit post (edit link is empty)
115
+ if ($post_is_available && $context['duplicated_post_edit_link']) {
116
+ $message = _x('Cloned {duplicated_post_post_type_singular_name} <a href="{duplicated_post_edit_link}">"{duplicated_post_title}"</a> to <a href="{new_post_edit_link}">a new {duplicated_post_post_type_singular_name}</a>', 'Logger: Plugin Duplicate Post', 'simple-history');
117
+ } // End if().
118
+
119
+ $context['new_post_edit_link'] = isset($context['new_post_edit_link']) ? esc_html($context['new_post_edit_link']) : '';
120
+
121
+ $context['duplicated_post_edit_link'] = isset($context['duplicated_post_edit_link']) ? esc_html($context['duplicated_post_edit_link']) : '';
122
+
123
+ $context['duplicated_post_title'] = isset($context['duplicated_post_title']) ? esc_html($context['duplicated_post_title']) : '';
124
+
125
+ $context['duplicated_post_title'] = isset($context['duplicated_post_title']) ? esc_html($context['duplicated_post_title']) : '';
126
+
127
+ $context['duplicated_post_post_type_singular_name'] = isset($context['duplicated_post_post_type_singular_name']) ? esc_html($context['duplicated_post_post_type_singular_name']) : '';
128
+
129
+ return $this->interpolate($message, $context, $row);
130
+ }
131
+ } // class
132
  } // End if().
loggers/Plugin_LimitLoginAttempts.php CHANGED
@@ -1,243 +1,233 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /**
6
  * Logger for the (old but still) very popular plugin Limit Login Attempts
7
  * https://sv.wordpress.org/plugins/limit-login-attempts/
8
  */
9
- if ( ! class_exists( 'Plugin_LimitLoginAttempts' ) ) {
10
-
11
- class Plugin_LimitLoginAttempts extends SimpleLogger {
12
-
13
- public $slug = __CLASS__;
14
-
15
- function getInfo() {
16
-
17
- $arr_info = array(
18
- 'name' => 'Plugin Limit Login Attempts',
19
- 'description' => _x( 'Logs failed login attempts, lockouts, and configuration changes made in the plugin Limit Login Attempts', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
20
- 'name_via' => _x( 'Using plugin Limit Login Attempts', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
21
- 'capability' => 'manage_options',
22
- 'messages' => array(
23
- // 'user_locked_out' => _x( 'User locked out', "Logger: Plugin Limit Login Attempts", "simple-history" ),
24
- 'failed_login_whitelisted' => _x( 'Failed login attempt from whitelisted IP', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
25
- 'failed_login' => _x( 'Was locked out because too many failed login attempts', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
26
- 'cleared_ip_log' => _x( 'Cleared IP log', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
27
- 'reseted_lockout_count' => _x( 'Reseted lockout count', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
28
- 'cleared_current_lockouts' => _x( 'Cleared current lockouts', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
29
- 'updated_options' => _x( 'Updated options', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
30
- ),
31
- /*
32
- "labels" => array(
33
- "search" => array(
34
- "label" => _x( "Limit Login Attempts", "Logger: Plugin Limit Login Attempts", "simple-history" ),
35
- "options" => array(
36
- _x( "xxxPages not found", "User logger: 404", "simple-history" ) => array(
37
- "page_not_found",
38
- ),
39
- ),
40
- ), // end search
41
- ),*/ // end labels
42
- );
43
-
44
- return $arr_info;
45
-
46
- }
47
-
48
- function loaded() {
49
-
50
- require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
51
-
52
- $pluginFilePath = 'limit-login-attempts/limit-login-attempts.php';
53
- $isPluginActive = is_plugin_active( $pluginFilePath );
54
-
55
- // Only continue to add filters if plugin is active.
56
- // This minimise the risk of plugin errors, because plugin
57
- // has been forked to new versions.
58
- if ( ! $isPluginActive ) {
59
- return;
60
- }
61
-
62
- add_filter( 'pre_option_limit_login_lockouts_total', array( $this, 'on_option_limit_login_lockouts_total' ), 10, 1 );
63
-
64
- add_action( 'load-settings_page_limit-login-attempts', array( $this, 'on_load_settings_page' ), 10, 1 );
65
-
66
- }
67
-
68
- /**
69
- * Fired when plugin options screen is loaded
70
- */
71
- function on_load_settings_page( $a ) {
72
-
73
- if ( $_POST && wp_verify_nonce( $_POST['_wpnonce'], 'limit-login-attempts-options' ) ) {
74
-
75
- // Settings saved
76
- if ( isset( $_POST['clear_log'] ) ) {
77
- $this->noticeMessage( 'cleared_ip_log' );
78
- }
79
-
80
- if ( isset( $_POST['reset_total'] ) ) {
81
- $this->noticeMessage( 'reseted_lockout_count' );
82
- }
83
-
84
- if ( isset( $_POST['reset_current'] ) ) {
85
- $this->noticeMessage( 'cleared_current_lockouts' );
86
- }
87
-
88
- if ( isset( $_POST['update_options'] ) ) {
89
-
90
- $options = array(
91
- 'client_type' => sanitize_text_field( $_POST['client_type'] ),
92
- 'allowed_retries' => sanitize_text_field( $_POST['allowed_retries'] ),
93
- 'lockout_duration' => sanitize_text_field( $_POST['lockout_duration'] ) * 60,
94
- 'valid_duration' => sanitize_text_field( $_POST['valid_duration'] ) * 3600,
95
- 'allowed_lockouts' => sanitize_text_field( $_POST['allowed_lockouts'] ),
96
- 'long_duration' => sanitize_text_field( $_POST['long_duration'] ) * 3600,
97
- 'email_after' => sanitize_text_field( $_POST['email_after'] ),
98
- 'cookies' => (isset( $_POST['cookies'] ) && $_POST['cookies'] == '1') ? 'yes' : 'no',
99
- );
100
-
101
- $v = array();
102
- if ( isset( $_POST['lockout_notify_log'] ) ) {
103
- $v[] = 'log';
104
- }
105
- if ( isset( $_POST['lockout_notify_email'] ) ) {
106
- $v[] = 'email';
107
- }
108
- $lockout_notify = implode( ',', $v );
109
- $options['lockout_notify'] = $lockout_notify;
110
-
111
- $this->noticeMessage('updated_options', array(
112
- 'options' => $options,
113
- ));
114
-
115
- }
116
- }// End if().
117
-
118
- }
119
-
120
- /**
121
- * When option value is updated
122
- * do same checks as plugin itself does
123
- * and log if we match something
124
- */
125
- function on_option_limit_login_lockouts_total( $value ) {
126
-
127
- global $limit_login_just_lockedout;
128
-
129
- if ( ! $limit_login_just_lockedout ) {
130
- return $value;
131
- }
132
-
133
- $ip = limit_login_get_address();
134
- $whitelisted = is_limit_login_ip_whitelisted( $ip );
135
-
136
- $retries = get_option( 'limit_login_retries' );
137
- if ( ! is_array( $retries ) ) {
138
- $retries = array();
139
- }
140
-
141
- if ( isset( $retries[ $ip ] ) && ( ( $retries[ $ip ] / limit_login_option( 'allowed_retries' ) ) % limit_login_option( 'notify_email_after' ) ) != 0 ) {
142
-
143
- // $this->notice( "user locked out but don't log" );
144
- // return;
145
- }
146
-
147
- /* Format message. First current lockout duration */
148
- $lockout_type = '';
149
- if ( ! isset( $retries[ $ip ] ) ) {
150
- /* longer lockout */
151
- $lockout_type = 'longer';
152
- $count = limit_login_option( 'allowed_retries' ) * limit_login_option( 'allowed_lockouts' );
153
- $lockouts = limit_login_option( 'allowed_lockouts' );
154
- $time = round( limit_login_option( 'long_duration' ) / 3600 );
155
- // $when = sprintf( _n( '%d hour', '%d hours', $time, "Logger: Plugin Limit Login Attempts", 'limit-login-attempts' ), $time );
156
- } else {
157
- /* normal lockout */
158
- $lockout_type = 'normal';
159
- $count = $retries[ $ip ];
160
- $lockouts = floor( $count / limit_login_option( 'allowed_retries' ) );
161
- $time = round( limit_login_option( 'lockout_duration' ) / 60 );
162
- // $when = sprintf( _n( '%d minute', '%d minutes', $time, 'limit-login-attempts' ), $time );
163
- }
164
-
165
- if ( $whitelisted ) {
166
- // $subject = __( "Failed login attempts from whitelisted IP", 'limit-login-attempts' );
167
- $message_key = 'failed_login_whitelisted';
168
- } else {
169
- // $subject = __( "Too many failed login attempts", 'limit-login-attempts' );
170
- $message_key = 'failed_login';
171
- }
172
-
173
- $this->noticeMessage( $message_key, array(
174
- '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
175
- 'value' => $value,
176
- 'limit_login_just_lockedout' => $limit_login_just_lockedout,
177
- // "retries" => $retries,
178
- // "whitelisted" => $whitelisted, // bool, true | false
179
- // "subject" => $subject,
180
- // "message" => $message,
181
- 'count' => $count, // num of failed login attempts before block
182
- 'time' => $time, // duration in minutes for block
183
- 'lockouts' => $lockouts,
184
- 'ip' => $ip,
185
- 'lockout_type' => $lockout_type,
186
- ) );
187
-
188
- return $value;
189
-
190
- }
191
-
192
-
193
- /**
194
- * Add some extra info
195
- */
196
- function getLogRowDetailsOutput( $row ) {
197
-
198
- $output = '';
199
-
200
- $context = isset( $row->context ) ? $row->context : array();
201
-
202
- $message_key = $row->context_message_key;
203
-
204
- if ( 'failed_login' == $message_key ) {
205
-
206
- $count = $context['count'];
207
- $lockouts = $context['lockouts'];
208
- $ip = $context['ip'];
209
- // $whitelisted = $context["whitelisted"];
210
- $lockout_type = $context['lockout_type'];
211
- $time = $context['time'];
212
-
213
- $output .= sprintf(
214
- '<p>' . _x( '%1$d failed login attempts (%2$d lockout(s)) from IP: %3$s', 'Logger: Plugin Limit Login Attempts', 'simple-history' ) . '</p>',
215
- $count, // 1
216
- $lockouts, // 2
217
- $ip // 3
218
- );
219
-
220
- if ( 'longer' == $lockout_type ) {
221
-
222
- $when = sprintf( _nx( '%d hour', '%d hours', $time, 'Logger: Plugin Limit Login Attempts', 'limit-login-attempts' ), $time );
223
-
224
- } elseif ( 'normal' == $lockout_type ) {
225
-
226
- $when = sprintf( _nx( '%d minute', '%d minutes', $time, 'Logger: Plugin Limit Login Attempts', 'limit-login-attempts' ), $time );
227
-
228
- }
229
-
230
- $output .= '<p>' . sprintf(
231
- _x( 'IP was blocked for %1$s', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
232
- $when // 1
233
- ) . '</p>';
234
-
235
- }
236
-
237
- return $output;
238
-
239
- }
240
-
241
- } // class
242
 
243
  } // End if().
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logger for the (old but still) very popular plugin Limit Login Attempts
7
  * https://sv.wordpress.org/plugins/limit-login-attempts/
8
  */
9
+ if (! class_exists('Plugin_LimitLoginAttempts')) {
10
+
11
+ class Plugin_LimitLoginAttempts extends SimpleLogger
12
+ {
13
+
14
+ public $slug = __CLASS__;
15
+
16
+ function getInfo()
17
+ {
18
+
19
+ $arr_info = array(
20
+ 'name' => 'Plugin Limit Login Attempts',
21
+ 'description' => _x('Logs failed login attempts, lockouts, and configuration changes made in the plugin Limit Login Attempts', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
22
+ 'name_via' => _x('Using plugin Limit Login Attempts', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
23
+ 'capability' => 'manage_options',
24
+ 'messages' => array(
25
+ // 'user_locked_out' => _x( 'User locked out', "Logger: Plugin Limit Login Attempts", "simple-history" ),
26
+ 'failed_login_whitelisted' => _x('Failed login attempt from whitelisted IP', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
27
+ 'failed_login' => _x('Was locked out because too many failed login attempts', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
28
+ 'cleared_ip_log' => _x('Cleared IP log', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
29
+ 'reseted_lockout_count' => _x('Reseted lockout count', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
30
+ 'cleared_current_lockouts' => _x('Cleared current lockouts', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
31
+ 'updated_options' => _x('Updated options', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
32
+ ),
33
+ /*
34
+ "labels" => array(
35
+ "search" => array(
36
+ "label" => _x( "Limit Login Attempts", "Logger: Plugin Limit Login Attempts", "simple-history" ),
37
+ "options" => array(
38
+ _x( "xxxPages not found", "User logger: 404", "simple-history" ) => array(
39
+ "page_not_found",
40
+ ),
41
+ ),
42
+ ), // end search
43
+ ),*/ // end labels
44
+ );
45
+
46
+ return $arr_info;
47
+ }
48
+
49
+ function loaded()
50
+ {
51
+
52
+ require_once(ABSPATH . 'wp-admin/includes/plugin.php');
53
+
54
+ $pluginFilePath = 'limit-login-attempts/limit-login-attempts.php';
55
+ $isPluginActive = is_plugin_active($pluginFilePath);
56
+
57
+ // Only continue to add filters if plugin is active.
58
+ // This minimise the risk of plugin errors, because plugin
59
+ // has been forked to new versions.
60
+ if (! $isPluginActive) {
61
+ return;
62
+ }
63
+
64
+ add_filter('pre_option_limit_login_lockouts_total', array( $this, 'on_option_limit_login_lockouts_total' ), 10, 1);
65
+
66
+ add_action('load-settings_page_limit-login-attempts', array( $this, 'on_load_settings_page' ), 10, 1);
67
+ }
68
+
69
+ /**
70
+ * Fired when plugin options screen is loaded
71
+ */
72
+ function on_load_settings_page($a)
73
+ {
74
+
75
+ if ($_POST && wp_verify_nonce($_POST['_wpnonce'], 'limit-login-attempts-options')) {
76
+ // Settings saved
77
+ if (isset($_POST['clear_log'])) {
78
+ $this->noticeMessage('cleared_ip_log');
79
+ }
80
+
81
+ if (isset($_POST['reset_total'])) {
82
+ $this->noticeMessage('reseted_lockout_count');
83
+ }
84
+
85
+ if (isset($_POST['reset_current'])) {
86
+ $this->noticeMessage('cleared_current_lockouts');
87
+ }
88
+
89
+ if (isset($_POST['update_options'])) {
90
+ $options = array(
91
+ 'client_type' => sanitize_text_field($_POST['client_type']),
92
+ 'allowed_retries' => sanitize_text_field($_POST['allowed_retries']),
93
+ 'lockout_duration' => sanitize_text_field($_POST['lockout_duration']) * 60,
94
+ 'valid_duration' => sanitize_text_field($_POST['valid_duration']) * 3600,
95
+ 'allowed_lockouts' => sanitize_text_field($_POST['allowed_lockouts']),
96
+ 'long_duration' => sanitize_text_field($_POST['long_duration']) * 3600,
97
+ 'email_after' => sanitize_text_field($_POST['email_after']),
98
+ 'cookies' => (isset($_POST['cookies']) && $_POST['cookies'] == '1') ? 'yes' : 'no',
99
+ );
100
+
101
+ $v = array();
102
+ if (isset($_POST['lockout_notify_log'])) {
103
+ $v[] = 'log';
104
+ }
105
+ if (isset($_POST['lockout_notify_email'])) {
106
+ $v[] = 'email';
107
+ }
108
+ $lockout_notify = implode(',', $v);
109
+ $options['lockout_notify'] = $lockout_notify;
110
+
111
+ $this->noticeMessage('updated_options', array(
112
+ 'options' => $options,
113
+ ));
114
+ }
115
+ }// End if().
116
+ }
117
+
118
+ /**
119
+ * When option value is updated
120
+ * do same checks as plugin itself does
121
+ * and log if we match something
122
+ */
123
+ function on_option_limit_login_lockouts_total($value)
124
+ {
125
+
126
+ global $limit_login_just_lockedout;
127
+
128
+ if (! $limit_login_just_lockedout) {
129
+ return $value;
130
+ }
131
+
132
+ $ip = limit_login_get_address();
133
+ $whitelisted = is_limit_login_ip_whitelisted($ip);
134
+
135
+ $retries = get_option('limit_login_retries');
136
+ if (! is_array($retries)) {
137
+ $retries = array();
138
+ }
139
+
140
+ if (isset($retries[ $ip ]) && ( ( $retries[ $ip ] / limit_login_option('allowed_retries') ) % limit_login_option('notify_email_after') ) != 0) {
141
+ // $this->notice( "user locked out but don't log" );
142
+ // return;
143
+ }
144
+
145
+ /* Format message. First current lockout duration */
146
+ $lockout_type = '';
147
+ if (! isset($retries[ $ip ])) {
148
+ /* longer lockout */
149
+ $lockout_type = 'longer';
150
+ $count = limit_login_option('allowed_retries') * limit_login_option('allowed_lockouts');
151
+ $lockouts = limit_login_option('allowed_lockouts');
152
+ $time = round(limit_login_option('long_duration') / 3600);
153
+ // $when = sprintf( _n( '%d hour', '%d hours', $time, "Logger: Plugin Limit Login Attempts", 'limit-login-attempts' ), $time );
154
+ } else {
155
+ /* normal lockout */
156
+ $lockout_type = 'normal';
157
+ $count = $retries[ $ip ];
158
+ $lockouts = floor($count / limit_login_option('allowed_retries'));
159
+ $time = round(limit_login_option('lockout_duration') / 60);
160
+ // $when = sprintf( _n( '%d minute', '%d minutes', $time, 'limit-login-attempts' ), $time );
161
+ }
162
+
163
+ if ($whitelisted) {
164
+ // $subject = __( "Failed login attempts from whitelisted IP", 'limit-login-attempts' );
165
+ $message_key = 'failed_login_whitelisted';
166
+ } else {
167
+ // $subject = __( "Too many failed login attempts", 'limit-login-attempts' );
168
+ $message_key = 'failed_login';
169
+ }
170
+
171
+ $this->noticeMessage($message_key, array(
172
+ '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
173
+ 'value' => $value,
174
+ 'limit_login_just_lockedout' => $limit_login_just_lockedout,
175
+ // "retries" => $retries,
176
+ // "whitelisted" => $whitelisted, // bool, true | false
177
+ // "subject" => $subject,
178
+ // "message" => $message,
179
+ 'count' => $count, // num of failed login attempts before block
180
+ 'time' => $time, // duration in minutes for block
181
+ 'lockouts' => $lockouts,
182
+ 'ip' => $ip,
183
+ 'lockout_type' => $lockout_type,
184
+ ));
185
+
186
+ return $value;
187
+ }
188
+
189
+
190
+ /**
191
+ * Add some extra info
192
+ */
193
+ function getLogRowDetailsOutput($row)
194
+ {
195
+
196
+ $output = '';
197
+
198
+ $context = isset($row->context) ? $row->context : array();
199
+
200
+ $message_key = $row->context_message_key;
201
+
202
+ if ('failed_login' == $message_key) {
203
+ $count = $context['count'];
204
+ $lockouts = $context['lockouts'];
205
+ $ip = $context['ip'];
206
+ // $whitelisted = $context["whitelisted"];
207
+ $lockout_type = $context['lockout_type'];
208
+ $time = $context['time'];
209
+
210
+ $output .= sprintf(
211
+ '<p>' . _x('%1$d failed login attempts (%2$d lockout(s)) from IP: %3$s', 'Logger: Plugin Limit Login Attempts', 'simple-history') . '</p>',
212
+ $count, // 1
213
+ $lockouts, // 2
214
+ $ip // 3
215
+ );
216
+
217
+ if ('longer' == $lockout_type) {
218
+ $when = sprintf(_nx('%d hour', '%d hours', $time, 'Logger: Plugin Limit Login Attempts', 'limit-login-attempts'), $time);
219
+ } elseif ('normal' == $lockout_type) {
220
+ $when = sprintf(_nx('%d minute', '%d minutes', $time, 'Logger: Plugin Limit Login Attempts', 'limit-login-attempts'), $time);
221
+ }
222
+
223
+ $output .= '<p>' . sprintf(
224
+ _x('IP was blocked for %1$s', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
225
+ $when // 1
226
+ ) . '</p>';
227
+ }
228
+
229
+ return $output;
230
+ }
231
+ } // class
 
 
 
 
 
 
 
 
 
 
232
 
233
  } // End if().
loggers/Plugin_Redirection.php CHANGED
@@ -1,399 +1,410 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Logger for the Redirection plugin
7
  * https://wordpress.org/plugins/redirection/
8
  */
9
- if ( ! class_exists( 'Plugin_Redirection' ) ) {
10
-
11
- /**
12
- * Class to log things from the Redirection plugin.
13
- */
14
- class Plugin_Redirection extends SimpleLogger {
15
-
16
- /**
17
- * Logger slug.
18
- *
19
- * @var string
20
- */
21
- public $slug = __CLASS__;
22
-
23
- /**
24
- * Return info about logger.
25
- *
26
- * @return array Array with plugin info.
27
- */
28
- public function getInfo() {
29
-
30
- $arr_info = array(
31
- 'name' => 'Redirection',
32
- 'description' => _x( 'Text', 'Logger: Redirection', 'simple-history' ),
33
- 'name_via' => _x( 'In plugin Redirection', 'Logger: Redirection', 'simple-history' ),
34
- 'capability' => 'manage_options',
35
- 'messages' => array(
36
- 'redirection_redirection_added' => _x( 'Added a redirection for URL "{source_url}"', 'Logger: Redirection', 'simple-history' ),
37
- 'redirection_redirection_edited' => _x( 'Edited redirection for URL "{prev_source_url}"', 'Logger: Redirection', 'simple-history' ),
38
- 'redirection_redirection_enabled' => _x( 'Enabled redirection for {items_count} URL(s)', 'Logger: Redirection', 'simple-history' ),
39
- 'redirection_redirection_disabled' => _x( 'Disabled redirection for {items_count} URL(s)', 'Logger: Redirection', 'simple-history' ),
40
- 'redirection_redirection_deleted' => _x( 'Deleted redirection for {items_count} URL(s)', 'Logger: Redirection', 'simple-history' ),
41
- 'redirection_options_saved' => _x( 'Updated redirection options', 'Logger: Redirection', 'simple-history' ),
42
- 'redirection_options_removed_all' => _x( 'Removed all redirection options and deactivated plugin', 'Logger: Redirection', 'simple-history' ),
43
- 'redirection_group_added' => _x( 'Added redirection group "{group_name}"', 'Logger: Redirection', 'simple-history' ),
44
- 'redirection_group_enabled' => _x( 'Enabled {items_count} redirection group(s)', 'Logger: Redirection', 'simple-history' ),
45
- 'redirection_group_disabled' => _x( 'Disabled {items_count} redirection group(s)', 'Logger: Redirection', 'simple-history' ),
46
- 'redirection_group_deleted' => _x( 'Deleted {items_count} redirection group(s)', 'Logger: Redirection', 'simple-history' ),
47
- ),
48
-
49
- /*
50
- "labels" => array(
51
- "search" => array(
52
- "label" => _x("Plugin Redirection", "Logger: Redirection", "simple-history"),
53
- "label_all" => _x("All posts & pages activity", "Logger: Redirection", "simple-history"),
54
- "options" => array(
55
- _x("Posts created", "Logger: Redirection", "simple-history") => array(
56
- "post_created"
57
- ),
58
- _x("Posts updated", "Logger: Redirection", "simple-history") => array(
59
- "post_updated"
60
- ),
61
- _x("Posts trashed", "Logger: Redirection", "simple-history") => array(
62
- "post_trashed"
63
- ),
64
- _x("Posts deleted", "Logger: Redirection", "simple-history") => array(
65
- "post_deleted"
66
- ),
67
- _x("Posts restored", "Logger: Redirection", "simple-history") => array(
68
- "post_restored"
69
- ),
70
- )
71
- ) // end search array
72
- ) // end labels
73
- */
74
- );
75
-
76
- return $arr_info;
77
-
78
- }
79
-
80
- /**
81
- * Called when logger is loaded.
82
- */
83
- public function loaded() {
84
- // Redirection plugin uses the WP REST API, so catch when requests do the API is done.
85
- // We use filter *_before_callbacks so we can access the old title
86
- // of the Redirection object, i.e. before new values are saved.
87
- add_filter( 'rest_request_before_callbacks', array( $this, 'on_rest_request_before_callbacks' ), 10, 3 );
88
- }
89
-
90
- /**
91
- * Fired when WP REST API call is done.
92
- *
93
- * @param WP_HTTP_Response $response Result to send to the client. Usually a WP_REST_Response.
94
- * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
95
- * @param WP_REST_Request $request Request used to generate the response.
96
- *
97
- * @return WP_HTTP_Response $response
98
- */
99
- public function on_rest_request_before_callbacks( $response, $handler, $request ) {
100
- // Callback must be set.
101
- if ( ! isset( $handler['callback'] ) ) {
102
- return $response;
103
- }
104
-
105
- $callback = $handler['callback'];
106
-
107
- $callable_name = sh_get_callable_name( $callback );
108
-
109
- $ok_redirection_api_callable_names = array(
110
- 'Redirection_Api_Redirect::route_bulk',
111
- 'Redirection_Api_Redirect::route_create',
112
- 'Redirection_Api_Redirect::route_update',
113
- 'Redirection_Api_Group::route_create',
114
- 'Redirection_Api_Group::route_bulk',
115
- 'Redirection_Api_Settings::route_save_settings',
116
- );
117
-
118
- // Bail directly if this is not a Redirection API call.
119
- if ( ! in_array( $callable_name, $ok_redirection_api_callable_names ) ) {
120
- return $response;
121
- }
122
-
123
- if ( 'Redirection_Api_Redirect::route_create' === $callable_name ) {
124
- $this->log_redirection_add( $request );
125
- } elseif ( 'Redirection_Api_Redirect::route_update' === $callable_name ) {
126
- $this->log_redirection_edit( $request );
127
- } else if ( 'Redirection_Api_Redirect::route_bulk' === $callable_name ) {
128
- $bulk_action = $request->get_param( 'bulk' );
129
-
130
- $bulk_items = $request->get_param( 'items' );
131
- $bulk_items = explode( ',', $bulk_items );
132
-
133
- if ( is_array( $bulk_items ) ) {
134
- $bulk_items = array_map( 'intval', $bulk_items );
135
- }
136
-
137
- if ( empty( $bulk_items ) ) {
138
- return $response;
139
- }
140
-
141
- if ( 'enable' === $bulk_action ) {
142
- $this->log_redirection_enable_or_disable( $request, $bulk_items );
143
- } elseif ( 'disable' === $bulk_action ) {
144
- $this->log_redirection_enable_or_disable( $request, $bulk_items );
145
- } elseif ( 'delete' === $bulk_action ) {
146
- $this->log_redirection_delete( $request, $bulk_items );
147
- }
148
- } elseif ( 'Redirection_Api_Group::route_create' === $callable_name ) {
149
- $this->log_group_add( $request );
150
- } else if ( 'Redirection_Api_Group::route_bulk' === $callable_name ) {
151
- $bulk_action = $request->get_param( 'bulk' );
152
-
153
- $bulk_items = $request->get_param( 'items' );
154
- $bulk_items = explode( ',', $bulk_items );
155
-
156
- if ( is_array( $bulk_items ) ) {
157
- $bulk_items = array_map( 'intval', $bulk_items );
158
- }
159
-
160
- if ( empty( $bulk_items ) ) {
161
- return $response;
162
- }
163
-
164
- if ( 'enable' === $bulk_action ) {
165
- $this->log_group_enable_or_disable( $request, $bulk_items );
166
- } elseif ( 'disable' === $bulk_action ) {
167
- $this->log_group_enable_or_disable( $request, $bulk_items );
168
- } elseif ( 'delete' === $bulk_action ) {
169
- $this->log_group_delete( $request, $bulk_items );
170
- }
171
- } else if ( 'Redirection_Api_Settings::route_save_settings' == $callable_name ) {
172
- $this->log_options_save( $request );
173
- }
174
-
175
- return $response;
176
- }
177
-
178
- /**
179
- * Log when a Redirection group is deleted.
180
- *
181
- * @param object $req Request.
182
- * @param array $bulk_items Array with item ids.
183
- */
184
- public function log_group_delete( $req, $bulk_items ) {
185
- $context = array(
186
- 'items' => $bulk_items,
187
- 'items_count' => count( $bulk_items ),
188
- );
189
-
190
- $this->infoMessage(
191
- 'redirection_group_deleted',
192
- $context
193
- );
194
- }
195
-
196
- /**
197
- * Log when a Redirection grouop is added
198
- *
199
- * @param WP_REST_Request $req Request.
200
- */
201
- public function log_group_add( $req ) {
202
- $group_name = $req->get_param( 'name' );
203
-
204
- if ( ! $group_name ) {
205
- return;
206
- }
207
-
208
- $context = array(
209
- 'group_name' => $group_name,
210
- );
211
-
212
- $this->infoMessage(
213
- 'redirection_group_added',
214
- $context
215
- );
216
- }
217
-
218
- /**
219
- * Log enabling and disabling of redirection groups.
220
- *
221
- * @param object $req Request.
222
- * @param array $bulk_items Array with item ids.
223
- */
224
- public function log_group_enable_or_disable( $req, $bulk_items ) {
225
- $bulk_action = $req->get_param( 'bulk' );
226
-
227
- $message_key = 'enable' === $bulk_action ? 'redirection_group_enabled' : 'redirection_group_disabled';
228
-
229
- $context = array(
230
- 'items' => $bulk_items,
231
- 'items_count' => count( $bulk_items ),
232
- );
233
-
234
- $this->infoMessage(
235
- $message_key,
236
- $context
237
- );
238
- }
239
-
240
- /**
241
- * Log when options are saved.
242
- *
243
- * @param object $req Request.
244
- */
245
- protected function log_options_save( $req ) {
246
- $this->infoMessage( 'redirection_options_saved' );
247
- }
248
-
249
- /**
250
- * Log the deletion of a redirection.
251
- *
252
- * @param object $req Request.
253
- * @param array $bulk_items Array with item ids.
254
- */
255
- protected function log_redirection_delete( $req, $bulk_items ) {
256
- $context = array(
257
- 'items' => $bulk_items,
258
- 'items_count' => count( $bulk_items ),
259
- );
260
-
261
- $message_key = 'redirection_redirection_deleted';
262
-
263
- $this->infoMessage(
264
- $message_key,
265
- $context
266
- );
267
- }
268
-
269
- /**
270
- * Log enable or disable of items.
271
- *
272
- * @param Object $req Req.
273
- * @param Array $bulk_items Array.
274
- */
275
- protected function log_redirection_enable_or_disable( $req, $bulk_items ) {
276
- $bulk_action = $req->get_param( 'bulk' );
277
-
278
- $message_key = 'enable' === $bulk_action ? 'redirection_redirection_enabled' : 'redirection_redirection_disabled';
279
-
280
- $context = array(
281
- 'items' => $bulk_items,
282
- 'items_count' => count( $bulk_items ),
283
- );
284
-
285
- $this->infoMessage(
286
- $message_key,
287
- $context
288
- );
289
- }
290
-
291
- /**
292
- * Log when a Redirection is added.
293
- *
294
- * @param WP_REST_Request $req Request.
295
- */
296
- protected function log_redirection_add( $req ) {
297
- $action_data = $req->get_param( 'action_data' );
298
-
299
- if ( ! $action_data || ! is_array( $action_data ) ) {
300
- return false;
301
- }
302
-
303
- $context = array(
304
- 'source_url' => $req->get_param( 'url' ),
305
- 'target_url' => $action_data['url'],
306
- );
307
-
308
- $this->infoMessage( 'redirection_redirection_added', $context );
309
- }
310
-
311
- /**
312
- * Log when a Redirection is changed.
313
- *
314
- * @param WP_REST_Request $req Request.
315
- */
316
- protected function log_redirection_edit( $req ) {
317
- $action_data = $req->get_param( 'action_data' );
318
-
319
- if ( ! $action_data || ! is_array( $action_data ) ) {
320
- return false;
321
- }
322
-
323
- $message_key = 'redirection_redirection_edited';
324
-
325
- $redirection_id = $req->get_param( 'id' );
326
-
327
- $context = array(
328
- 'new_source_url' => $req->get_param( 'url' ),
329
- 'new_target' => $action_data,
330
- 'redirection_id' => $redirection_id,
331
- );
332
-
333
- // Get old values.
334
- $redirection_item = Red_Item::get_by_id( $redirection_id );
335
-
336
- if ( false !== $redirection_item ) {
337
- $context['prev_source_url'] = $redirection_item->get_url();
338
- $context['prev_target'] = maybe_unserialize($redirection_item->get_action_data());
339
- }
340
-
341
- $this->infoMessage(
342
- $message_key,
343
- $context
344
- );
345
- }
346
-
347
- /**
348
- * Return more info about an logged redirection event.
349
- *
350
- * @param array $row Row with info.
351
- */
352
- public function getLogRowDetailsOutput( $row ) {
353
- $context = $row->context;
354
- $message_key = $context['_message_key'];
355
-
356
- $out = '';
357
-
358
- if ( 'redirection_redirection_edited' === $message_key ) {
359
- if ( $context['new_source_url'] !== $context['prev_source_url'] ) {
360
- $diff_table_output = sprintf(
361
- '<tr>
 
 
 
 
 
 
 
 
 
 
 
 
362
  <td>%1$s</td>
363
  <td>
364
  <ins class="SimpleHistoryLogitem__keyValueTable__addedThing">%2$s</ins>
365
  <del class="SimpleHistoryLogitem__keyValueTable__removedThing">%3$s</del>
366
  </td>
367
  </tr>',
368
- esc_html_x( 'Source URL', 'Logger: Redirection', 'simple-history' ), // 1
369
- esc_html( $context['new_source_url'] ), // 2
370
- esc_html( $context['prev_source_url'] ) // 3
371
- );
372
 
373
- $out .= '<table class="SimpleHistoryLogitem__keyValueTable">' . $diff_table_output . '</table>';
374
- }
375
 
376
- if ( $context['new_target'] !== $context['prev_target'] ) {
377
- $diff_table_output = sprintf(
378
- '<tr>
379
  <td>%1$s</td>
380
  <td>
381
  <ins class="SimpleHistoryLogitem__keyValueTable__addedThing">%2$s</ins>
382
  <del class="SimpleHistoryLogitem__keyValueTable__removedThing">%3$s</del>
383
  </td>
384
  </tr>',
385
- esc_html_x( 'Target', 'Logger: Redirection', 'simple-history' ), // 1
386
- esc_html( $context['new_target'] ), // 2
387
- esc_html( $context['prev_target'] ) // 3
388
- );
389
-
390
- $out .= '<table class="SimpleHistoryLogitem__keyValueTable">' . $diff_table_output . '</table>';
391
- }
392
- }
393
-
394
- return $out;
395
- }
396
-
397
- } // class
398
 
399
  } // End if().
1
  <?php
2
 
3
+ defined('ABSPATH') || die();
4
 
5
  /**
6
  * Logger for the Redirection plugin
7
  * https://wordpress.org/plugins/redirection/
8
  */
9
+ if (! class_exists('Plugin_Redirection')) {
10
+
11
+ /**
12
+ * Class to log things from the Redirection plugin.
13
+ */
14
+ class Plugin_Redirection extends SimpleLogger
15
+ {
16
+
17
+ /**
18
+ * Logger slug.
19
+ *
20
+ * @var string
21
+ */
22
+ public $slug = __CLASS__;
23
+
24
+ /**
25
+ * Return info about logger.
26
+ *
27
+ * @return array Array with plugin info.
28
+ */
29
+ public function getInfo()
30
+ {
31
+
32
+ $arr_info = array(
33
+ 'name' => 'Redirection',
34
+ 'description' => _x('Text', 'Logger: Redirection', 'simple-history'),
35
+ 'name_via' => _x('In plugin Redirection', 'Logger: Redirection', 'simple-history'),
36
+ 'capability' => 'manage_options',
37
+ 'messages' => array(
38
+ 'redirection_redirection_added' => _x('Added a redirection for URL "{source_url}"', 'Logger: Redirection', 'simple-history'),
39
+ 'redirection_redirection_edited' => _x('Edited redirection for URL "{prev_source_url}"', 'Logger: Redirection', 'simple-history'),
40
+ 'redirection_redirection_enabled' => _x('Enabled redirection for {items_count} URL(s)', 'Logger: Redirection', 'simple-history'),
41
+ 'redirection_redirection_disabled' => _x('Disabled redirection for {items_count} URL(s)', 'Logger: Redirection', 'simple-history'),
42
+ 'redirection_redirection_deleted' => _x('Deleted redirection for {items_count} URL(s)', 'Logger: Redirection', 'simple-history'),
43
+ 'redirection_options_saved' => _x('Updated redirection options', 'Logger: Redirection', 'simple-history'),
44
+ 'redirection_options_removed_all' => _x('Removed all redirection options and deactivated plugin', 'Logger: Redirection', 'simple-history'),
45
+ 'redirection_group_added' => _x('Added redirection group "{group_name}"', 'Logger: Redirection', 'simple-history'),
46
+ 'redirection_group_enabled' => _x('Enabled {items_count} redirection group(s)', 'Logger: Redirection', 'simple-history'),
47
+ 'redirection_group_disabled' => _x('Disabled {items_count} redirection group(s)', 'Logger: Redirection', 'simple-history'),
48
+ 'redirection_group_deleted' => _x('Deleted {items_count} redirection group(s)', 'Logger: Redirection', 'simple-history'),
49
+ ),
50
+
51
+ /*
52
+ "labels" => array(
53
+ "search" => array(
54
+ "label" => _x("Plugin Redirection", "Logger: Redirection", "simple-history"),
55
+ "label_all" => _x("All posts & pages activity", "Logger: Redirection", "simple-history"),
56
+ "options" => array(
57
+ _x("Posts created", "Logger: Redirection", "simple-history") => array(
58
+ "post_created"
59
+ ),
60
+ _x("Posts updated", "Logger: Redirection", "simple-history") => array(
61
+ "post_updated"
62
+ ),
63
+ _x("Posts trashed", "Logger: Redirection", "simple-history") => array(
64
+ "post_trashed"
65
+ ),
66
+ _x("Posts deleted", "Logger: Redirection", "simple-history") => array(
67
+ "post_deleted"
68
+ ),
69
+ _x("Posts restored", "Logger: Redirection", "simple-history") => array(
70
+ "post_restored"
71
+ ),
72
+ )
73
+ ) // end search array
74
+ ) // end labels
75
+ */
76
+ );
77
+
78
+ return $arr_info;
79
+ }
80
+
81
+ /**
82
+ * Called when logger is loaded.
83
+ */
84
+ public function loaded()
85
+ {
86
+ // Redirection plugin uses the WP REST API, so catch when requests do the API is done.
87
+ // We use filter *_before_callbacks so we can access the old title
88
+ // of the Redirection object, i.e. before new values are saved.
89
+ add_filter('rest_request_before_callbacks', array( $this, 'on_rest_request_before_callbacks' ), 10, 3);
90
+ }
91
+
92
+ /**
93
+ * Fired when WP REST API call is done.
94
+ *
95
+ * @param WP_HTTP_Response $response Result to send to the client. Usually a WP_REST_Response.
96
+ * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
97
+ * @param WP_REST_Request $request Request used to generate the response.
98
+ *
99
+ * @return WP_HTTP_Response $response
100
+ */
101
+ public function on_rest_request_before_callbacks($response, $handler, $request)
102
+ {
103
+ // Callback must be set.
104
+ if (! isset($handler['callback'])) {
105
+ return $response;
106
+ }
107
+
108
+ $callback = $handler['callback'];
109
+
110
+ $callable_name = sh_get_callable_name($callback);
111
+
112
+ $ok_redirection_api_callable_names = array(
113
+ 'Redirection_Api_Redirect::route_bulk',
114
+ 'Redirection_Api_Redirect::route_create',
115
+ 'Redirection_Api_Redirect::route_update',
116
+ 'Redirection_Api_Group::route_create',
117
+ 'Redirection_Api_Group::route_bulk',
118
+ 'Redirection_Api_Settings::route_save_settings',
119
+ );
120
+
121
+ // Bail directly if this is not a Redirection API call.
122
+ if (! in_array($callable_name, $ok_redirection_api_callable_names)) {
123
+ return $response;
124
+ }
125
+
126
+ if ('Redirection_Api_Redirect::route_create' === $callable_name) {
127
+ $this->log_redirection_add($request);
128
+ } elseif ('Redirection_Api_Redirect::route_update' === $callable_name) {
129
+ $this->log_redirection_edit($request);
130
+ } elseif ('Redirection_Api_Redirect::route_bulk' === $callable_name) {
131
+ $bulk_action = $request->get_param('bulk');
132
+
133
+ $bulk_items = $request->get_param('items');
134
+ $bulk_items = explode(',', $bulk_items);
135
+
136
+ if (is_array($bulk_items)) {
137
+ $bulk_items = array_map('intval', $bulk_items);
138
+ }
139
+
140
+ if (empty($bulk_items)) {
141
+ return $response;
142
+ }
143
+
144
+ if ('enable' === $bulk_action) {
145
+ $this->log_redirection_enable_or_disable($request, $bulk_items);
146
+ } elseif ('disable' === $bulk_action) {
147
+ $this->log_redirection_enable_or_disable($request, $bulk_items);
148
+ } elseif ('delete' === $bulk_action) {
149
+ $this->log_redirection_delete($request, $bulk_items);
150
+ }
151
+ } elseif ('Redirection_Api_Group::route_create' === $callable_name) {
152
+ $this->log_group_add($request);
153
+ } elseif ('Redirection_Api_Group::route_bulk' === $callable_name) {
154
+ $bulk_action = $request->get_param('bulk');
155
+
156
+ $bulk_items = $request->get_param('items');
157
+ $bulk_items = explode(',', $bulk_items);
158
+
159
+ if (is_array($bulk_items)) {
160
+ $bulk_items = array_map('intval', $bulk_items);
161
+ }
162
+
163
+ if (empty($bulk_items)) {
164
+ return $response;
165
+ }
166
+
167
+ if ('enable' === $bulk_action) {
168
+ $this->log_group_enable_or_disable($request, $bulk_items);
169
+ } elseif ('disable' === $bulk_action) {
170
+ $this->log_group_enable_or_disable($request, $bulk_items);
171
+ } elseif ('delete' === $bulk_action) {
172
+ $this->log_group_delete($request, $bulk_items);
173
+ }
174
+ } elseif ('Redirection_Api_Settings::route_save_settings' == $callable_name) {
175
+ $this->log_options_save($request);
176
+ }
177
+
178
+ return $response;
179
+ }
180
+
181
+ /**
182
+ * Log when a Redirection group is deleted.
183
+ *
184
+ * @param object $req Request.
185
+ * @param array $bulk_items Array with item ids.
186
+ */
187
+ public function log_group_delete($req, $bulk_items)
188
+ {
189
+ $context = array(
190
+ 'items' => $bulk_items,
191
+ 'items_count' => count($bulk_items),
192
+ );
193
+
194
+ $this->infoMessage(
195
+ 'redirection_group_deleted',
196
+ $context
197
+ );
198
+ }
199
+
200
+ /**
201
+ * Log when a Redirection grouop is added
202
+ *
203
+ * @param WP_REST_Request $req Request.
204
+ */
205
+ public function log_group_add($req)
206
+ {
207
+ $group_name = $req->get_param('name');
208
+
209
+ if (! $group_name) {
210
+ return;
211
+ }
212
+
213
+ $context = array(
214
+ 'group_name' => $group_name,
215
+ );
216
+
217
+ $this->infoMessage(
218
+ 'redirection_group_added',
219
+ $context
220
+ );
221
+ }
222
+
223
+ /**
224
+ * Log enabling and disabling of redirection groups.
225
+ *
226
+ * @param object $req Request.
227
+ * @param array $bulk_items Array with item ids.
228
+ */
229
+ public function log_group_enable_or_disable($req, $bulk_items)
230
+ {
231
+ $bulk_action = $req->get_param('bulk');
232
+
233
+ $message_key = 'enable' === $bulk_action ? 'redirection_group_enabled' : 'redirection_group_disabled';
234
+
235
+ $context = array(
236
+ 'items' => $bulk_items,
237
+ 'items_count' => count($bulk_items),
238
+ );
239
+
240
+ $this->infoMessage(
241
+ $message_key,
242
+ $context
243
+ );
244
+ }
245
+
246
+ /**
247
+ * Log when options are saved.
248
+ *
249
+ * @param object $req Request.
250
+ */
251
+ protected function log_options_save($req)
252
+ {
253
+ $this->infoMessage('redirection_options_saved');
254
+ }
255
+
256
+ /**
257
+ * Log the deletion of a redirection.
258
+ *
259
+ * @param object $req Request.
260
+ * @param array $bulk_items Array with item ids.
261
+ */
262
+ protected function log_redirection_delete($req, $bulk_items)
263
+ {
264
+ $context = array(
265
+ 'items' => $bulk_items,
266
+ 'items_count' => count($bulk_items),
267
+ );
268
+
269
+ $message_key = 'redirection_redirection_deleted';
270
+
271
+ $this->infoMessage(
272
+ $message_key,
273
+ $context
274
+ );
275
+ }
276
+
277
+ /**
278
+ * Log enable or disable of items.
279
+ *
280
+ * @param Object $req Req.
281
+ * @param Array $bulk_items Array.
282
+ */
283
+ protected function log_redirection_enable_or_disable($req, $bulk_items)
284
+ {
285
+ $bulk_action = $req->get_param('bulk');
286
+
287
+ $message_key = 'enable' === $bulk_action ? 'redirection_redirection_enabled' : 'redirection_redirection_disabled';
288
+
289
+ $context = array(
290
+ 'items' => $bulk_items,
291
+ 'items_count' => count($bulk_items),
292
+ );
293
+
294
+ $this->infoMessage(
295
+ $message_key,
296
+ $context
297
+ );
298
+ }
299
+
300
+ /**
301
+ * Log when a Redirection is added.
302
+ *
303
+ * @param WP_REST_Request $req Request.
304
+ */
305
+ protected function log_redirection_add($req)
306
+ {
307
+ $action_data = $req->get_param('action_data');
308
+
309
+ if (! $action_data || ! is_array($action_data)) {
310
+ return false;
311
+ }
312
+
313
+ $context = array(
314
+ 'source_url' => $req->get_param('url'),
315
+ 'target_url' => $action_data['url'],
316
+ );
317
+
318
+ $this->infoMessage('redirection_redirection_added', $context);
319
+ }
320
+
321
+ /**
322
+ * Log when a Redirection is changed.
323
+ *
324
+ * @param WP_REST_Request $req Request.
325
+ */
326
+ protected function log_redirection_edit($req)
327
+ {
328
+ $action_data = $req->get_param('action_data');
329
+
330
+ if (! $action_data || ! is_array($action_data)) {
331
+ return false;
332
+ }
333
+
334
+ $message_key = 'redirection_redirection_edited';
335
+
336
+ $redirection_id = $req->get_param('id');
337
+
338
+ $context = array(
339
+ 'new_source_url' => $req->get_param('url'),
340
+ 'new_target' => $action_data,
341
+ 'redirection_id' => $redirection_id,
342
+ );
343
+
344
+ // Get old values.
345
+ $redirection_item = Red_Item::get_by_id($redirection_id);
346
+
347
+ if (false !== $redirection_item) {
348
+ $context['prev_source_url'] = $redirection_item->get_url();
349
+ $context['prev_target'] = maybe_unserialize($redirection_item->get_action_data());
350
+ }
351
+
352
+ $this->infoMessage(
353
+ $message_key,
354
+ $context
355
+ );
356
+ }
357
+
358
+ /**
359
+ * Return more info about an logged redirection event.
360
+ *
361
+ * @param array $row Row with info.
362
+ */
363
+ public function getLogRowDetailsOutput($row)
364
+ {
365
+ $context = $row->context;
366
+ $message_key = $context['_message_key'];
367
+
368
+ $out = '';
369
+
370
+ if ('redirection_redirection_edited' === $message_key) {
371
+ if ($context['new_source_url'] !== $context['prev_source_url']) {
372
+ $diff_table_output = sprintf(
373
+ '<tr>
374
  <td>%1$s</td>
375
  <td>
376
  <ins class="SimpleHistoryLogitem__keyValueTable__addedThing">%2$s</ins>
377
  <del class="SimpleHistoryLogitem__keyValueTable__removedThing">%3$s</del>
378
  </td>
379
  </tr>',
380
+ esc_html_x('Source URL', 'Logger: Redirection', 'simple-history'), // 1
381
+ esc_html($context['new_source_url']), // 2
382
+ esc_html($context['prev_source_url']) // 3
383
+ );
384
 
385
+ $out .= '<table class="SimpleHistoryLogitem__keyValueTable">' . $diff_table_output . '</table>';
386
+ }
387
 
388
+ if ($context['new_target'] !== $context['prev_target']) {
389
+ $diff_table_output = sprintf(
390
+ '<tr>
391
  <td>%1$s</td>
392
  <td>
393
  <ins class="SimpleHistoryLogitem__keyValueTable__addedThing">%2$s</ins>
394
  <del class="SimpleHistoryLogitem__keyValueTable__removedThing">%3$s</del>
395
  </td>
396
  </tr>',
397
+ esc_html_x('Target', 'Logger: Redirection', 'simple-history'), // 1
398
+ esc_html($context['new_target']), // 2
399
+ esc_html($context['prev_target']) // 3
400
+ );
401
+
402
+ $out .= '<table class="SimpleHistoryLogitem__keyValueTable">' . $diff_table_output . '</table>';
403
+ }
404
+ }
405
+
406
+ return $out;
407
+ }
408
+ } // class
 
409
 
410
  } // End if().
loggers/Plugin_UltimateMembers_Logger.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /**
6
  *
@@ -8,49 +8,50 @@ defined( 'ABSPATH' ) or die();
8
  *
9
  * @since 2.2
10
  */
11
- class Plugin_UltimateMembers_Logger extends SimpleLogger {
12
-
13
- public $slug = __CLASS__;
14
-
15
- /**
16
- * Get array with information about this logger
17
- *
18
- * @return array
19
- */
20
- function getInfo() {
21
-
22
- $arr_info = array(
23
- 'name' => _x( 'Ultimate Members Logger', 'PluginUltimateMembersLogger', 'simple-history' ),
24
- 'description' => _x( 'Logs actions from the Ultimate Members plugin', 'PluginUltimateMembersLogger', 'simple-history' ),
25
- 'capability' => 'edit_users',
26
- 'messages' => array(
27
- 'logged_in' => _x( 'Logged in', 'PluginUltimateMembersLogger', 'simple-history' ),
28
- ),
29
- );
30
-
31
- return $arr_info;
32
-
33
- }
34
-
35
- function loaded() {
36
-
37
- // Action that is called when Enable Media Replace loads it's admin options page (both when viewing and when posting new file to it)
38
- add_action( 'um_on_login_before_redirect', array( $this, 'on_um_on_login_before_redirect' ), 10, 1 );
39
- }
40
-
41
- function on_um_on_login_before_redirect( $user_id ) {
42
-
43
- $this->infoMessage('logged_in', array(
44
- // "user_id" => $user_id,
45
- /*
46
- "get" => $_GET,
47
- "post" => $_POST,
48
- "files" => $_FILES,
49
- "old_attachment_post" => $prev_attachment_post,
50
- "old_attachment_meta" => $prev_attachment_meta
51
- */
52
- ));
53
-
54
- }
55
-
 
56
  }
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /**
6
  *
8
  *
9
  * @since 2.2
10
  */
11
+ class Plugin_UltimateMembers_Logger extends SimpleLogger
12
+ {
13
+
14
+ public $slug = __CLASS__;
15
+
16
+ /**
17
+ * Get array with information about this logger
18
+ *
19
+ * @return array
20
+ */
21
+ function getInfo()
22
+ {
23
+
24
+ $arr_info = array(
25
+ 'name' => _x('Ultimate Members Logger', 'PluginUltimateMembersLogger', 'simple-history'),
26
+ 'description' => _x('Logs actions from the Ultimate Members plugin', 'PluginUltimateMembersLogger', 'simple-history'),
27
+ 'capability' => 'edit_users',
28
+ 'messages' => array(
29
+ 'logged_in' => _x('Logged in', 'PluginUltimateMembersLogger', 'simple-history'),
30
+ ),
31
+ );
32
+
33
+ return $arr_info;
34
+ }
35
+
36
+ function loaded()
37
+ {
38
+
39
+ // Action that is called when Enable Media Replace loads it's admin options page (both when viewing and when posting new file to it)
40
+ add_action('um_on_login_before_redirect', array( $this, 'on_um_on_login_before_redirect' ), 10, 1);
41
+ }
42
+
43
+ function on_um_on_login_before_redirect($user_id)
44
+ {
45
+
46
+ $this->infoMessage('logged_in', array(
47
+ // "user_id" => $user_id,
48
+ /*
49
+ "get" => $_GET,
50
+ "post" => $_POST,
51
+ "files" => $_FILES,
52
+ "old_attachment_post" => $prev_attachment_post,
53
+ "old_attachment_meta" => $prev_attachment_meta
54
+ */
55
+ ));
56
+ }
57
  }
loggers/SimpleCategoriesLogger.php CHANGED
@@ -1,298 +1,323 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /**
6
  * Logs changes to categories and tags and taxonomies
7
  */
8
- class SimpleCategoriesLogger extends SimpleLogger {
9
-
10
- public $slug = __CLASS__;
11
-
12
- /**
13
- * Get array with information about this logger
14
- *
15
- * @return array
16
- */
17
- function getInfo() {
18
-
19
- $arr_info = array(
20
- 'name' => __( 'Categories Logger', 'simple-history' ),
21
- 'description' => 'Logs changes to categories, tags, and taxonomies',
22
- 'messages' => array(
23
- 'created_term' => __( 'Added term "{term_name}" in taxonomy "{term_taxonomy}"', 'simple-history' ),
24
- 'deleted_term' => __( 'Deleted term "{term_name}" from taxonomy "{term_taxonomy}"', 'simple-history' ),
25
- 'edited_term' => __( 'Edited term "{to_term_name}" in taxonomy "{to_term_taxonomy}"', 'simple-history' ),
26
- ),
27
- 'labels' => array(
28
- 'search' => array(
29
- 'label' => _x( 'Categories', 'Categories logger: search', 'simple-history'),
30
- 'label_all' => _x( 'All category activity', 'Category logger: search', 'simple-history' ),
31
- 'options' => array(
32
- _x( 'Term created', 'Category logger: search', 'simple-history' ) => array(
33
- 'created_term'
34
- ),
35
- _x( 'Term deleted', 'Category logger: search', 'simple-history' ) => array(
36
- 'deleted_term'
37
- ),
38
- _x( 'Term edited', 'Category logger: search', 'simple-history' ) => array(
39
- 'edited_term'
40
- ),
41
- )
42
- ) // end search array
43
- ) // end labels
44
- );
45
-
46
- return $arr_info;
47
-
48
- }
49
-
50
- /**
51
- * Called when the logger is loaded.
52
- */
53
- public function loaded() {
54
- // Fires after a new term is created, and after the term cache has been cleaned..
55
- add_action( 'created_term', array( $this, 'on_created_term' ), 10, 3 );
56
-
57
- // Hook to this filter to see if it will cause a hierarchy loop.
58
- add_action( 'delete_term', array( $this, 'on_delete_term' ), 10, 4 );
59
-
60
- // Filter the term parent.
61
- add_action( 'wp_update_term_parent', array( $this, 'on_wp_update_term_parent' ), 10, 5 );
62
- }
63
-
64
- /**
65
- * Filter the term parent.
66
- * Only way for Simple History to get both old and new term name.
67
- * For example 'edited_term' does not contain enough info to know what the term was called before the update.
68
- *
69
- * @param int $parent ID of the parent term.
70
- * @param int $term_id Term ID.
71
- * @param string $taxonomy Taxonomy slug.
72
- * @param array $parsed_args An array of potentially altered update arguments for the given term.
73
- * @param array $term_update_args An array of update arguments for the given term.
74
- */
75
- function on_wp_update_term_parent( $parent = null, $term_id = null, $taxonomy = null, $parsed_args = null, $term_update_args = null ) {
76
-
77
- $term_before_edited = get_term_by( 'id', $term_id, $taxonomy );
78
-
79
- if ( ! $term_before_edited || empty( $term_update_args ) ) {
80
- return $parent;
81
- }
82
-
83
- $term_id = $term_before_edited->term_id;
84
-
85
- $from_term_name = $term_before_edited->name;
86
- $from_term_taxonomy = $term_before_edited->taxonomy;
87
- $from_term_slug = $term_before_edited->slug;
88
- $from_term_description = $term_before_edited->description;
89
-
90
- $to_term_name = $term_update_args['name'];
91
- $to_term_taxonomy = $term_update_args['taxonomy'];
92
- $to_term_slug = $term_update_args['slug'];
93
- $to_term_description = $term_update_args['description'];
94
-
95
- $do_log_term = $this->ok_to_log_taxonomy( $from_term_taxonomy );
96
-
97
- if ( ! $do_log_term ) {
98
- return $parent;
99
- }
100
-
101
- $this->infoMessage(
102
- 'edited_term',
103
- array(
104
- 'term_id' => $term_id,
105
- 'from_term_name' => $from_term_name,
106
- 'from_term_taxonomy' => $from_term_taxonomy,
107
- 'from_term_slug' => $from_term_slug,
108
- 'from_term_description' => $from_term_description,
109
- 'to_term_name' => $to_term_name,
110
- 'to_term_taxonomy' => $to_term_taxonomy,
111
- 'to_term_slug' => $to_term_slug,
112
- 'to_term_description' => $to_term_description,
113
- )
114
- );
115
-
116
- return $parent;
117
-
118
- }
119
-
120
- /**
121
- * Check if it's ok to log a taxonomy.
122
- * We skip some taxonomies, for example Polylang translation terms that fill the log with
123
- * messages like 'Edited term "pll_5a3643a142c80" in taxonomy "post_translations"' otherwise.
124
- *
125
- * @since 2.21
126
- * @param string $from_term_taxonomy Slug of taxonomy.
127
- * @return bool True or false.
128
- */
129
- function ok_to_log_taxonomy( $from_term_taxonomy = '' ) {
130
- if ( empty( $from_term_taxonomy ) ) {
131
- return false;
132
- }
133
-
134
- $skip_taxonomies = $this->get_skip_taxonomies();
135
-
136
- $do_log = ! in_array( $from_term_taxonomy, $skip_taxonomies, true );
137
-
138
- return $do_log;
139
- }
140
-
141
- /**
142
- * Get taxonomies to skip.
143
- *
144
- * @since 2.21
145
- * @return array Array with taxonomies.
146
- */
147
- function get_skip_taxonomies() {
148
-
149
- $taxonomies_to_skip = array(
150
- // Polylang taxonomies used to store translation mappings.
151
- 'post_translations',
152
- 'term_translations',
153
- );
154
-
155
- $taxonomies_to_skip = apply_filters( 'simple_history/categories_logger/skip_taxonomies', $taxonomies_to_skip );
156
-
157
- return $taxonomies_to_skip;
158
- }
159
-
160
- /*
161
- * Fires after a new term is created, and after the term cache has been cleaned.
162
- *
163
- * @since 2.3.0
164
- *
165
- * @param int $term_id Term ID.
166
- * @param int $tt_id Term taxonomy ID.
167
- * @param string $taxonomy Taxonomy slug.
168
- */
169
- function on_created_term( $term_id = null, $tt_id = null, $taxonomy = null ) {
170
-
171
- $term = get_term_by( 'id', $term_id, $taxonomy );
172
-
173
- if ( ! $term ) {
174
- return;
175
- }
176
-
177
- $term_name = $term->name;
178
- $term_taxonomy = $term->taxonomy;
179
- $term_id = $term->term_id;
180
-
181
- $do_log_term = $this->ok_to_log_taxonomy( $term_taxonomy );
182
-
183
- if ( ! $do_log_term ) {
184
- return;
185
- }
186
-
187
- $this->infoMessage(
188
- 'created_term',
189
- array(
190
- 'term_id' => $term_id,
191
- 'term_name' => $term_name,
192
- 'term_taxonomy' => $term_taxonomy,
193
- )
194
- );
195
-
196
- }
197
-
198
-
199
- /**
200
- * Fires after a term is deleted from the database and the cache is cleaned.
201
- *
202
- * @param int $term Term ID.
203
- * @param int $tt_id Term taxonomy ID.
204
- * @param string $taxonomy Taxonomy slug.
205
- * @param mixed $deleted_term Copy of the already-deleted term, in the form specified
206
- * by the parent function. WP_Error otherwise.
207
- */
208
- function on_delete_term( $term = null, $tt_id = null, $taxonomy = null, $deleted_term = null ) {
209
-
210
- if ( is_wp_error( $deleted_term ) ) {
211
- return;
212
- }
213
-
214
- $term_name = $deleted_term->name;
215
- $term_taxonomy = $deleted_term->taxonomy;
216
- $term_id = $deleted_term->term_id;
217
-
218
- $do_log_term = $this->ok_to_log_taxonomy( $term_taxonomy );
219
-
220
- if ( ! $do_log_term ) {
221
- return;
222
- }
223
-
224
- $this->infoMessage(
225
- 'deleted_term',
226
- array(
227
- 'term_id' => $term_id,
228
- 'term_name' => $term_name,
229
- 'term_taxonomy' => $term_taxonomy,
230
- )
231
- );
232
-
233
- }
234
-
235
- /**
236
- * Modify plain output to include link to term and taxonomy.
237
- *
238
- * @param array $row Row data.
239
- */
240
- public function getLogRowPlainTextOutput( $row ) {
241
- $context = $row->context;
242
- $message_key = isset( $context['_message_key'] ) ? $context['_message_key'] : null;
243
-
244
- // Default to original log message.
245
- $message = $row->message;
246
-
247
- // Get term that was created, edited, or removed.
248
- $term_id = isset( $context['term_id'] ) ? (int) $context['term_id'] : null;
249
-
250
- // Get taxonomy for term.
251
- if ( 'created_term' === $message_key || 'deleted_term' === $message_key ) {
252
- $term_taxonomy = isset( $context['term_taxonomy'] ) ? (string) $context['term_taxonomy'] : null;
253
- } elseif ( 'edited_term' === $message_key ) {
254
- $term_taxonomy = isset( $context['from_term_taxonomy'] ) ? (string) $context['from_term_taxonomy'] : null;
255
- }
256
-
257
- $tax_edit_link = add_query_arg(
258
- array(
259
- 'taxonomy' => $term_taxonomy,
260
- ),
261
- admin_url( 'term.php' )
262
- );
263
-
264
- $context['tax_edit_link'] = $tax_edit_link;
265
-
266
- $term_object = get_term( $term_id, $term_taxonomy );
267
-
268
- if ( is_wp_error( $term_object ) ) {
269
- return $this->interpolate( $message, $context, $row );
270
- }
271
-
272
- $term_edit_link = get_edit_tag_link( $term_id, $term_object->taxonomy );
273
- $context['term_edit_link'] = $term_edit_link;
274
-
275
- if ( 'created_term' === $message_key && ! empty( $term_edit_link ) && ! empty( $tax_edit_link ) ) {
276
- $message = _x(
277
- 'Added term <a href="{term_edit_link}">"{term_name}"</a> in taxonomy <a href="{tax_edit_link}">"{term_taxonomy}"</a>',
278
- 'Categories logger: detailed plain text output for created term',
279
- 'simple-history'
280
- );
281
- } elseif ( 'deleted_term' === $message_key && ! empty( $tax_edit_link ) ) {
282
- $message = _x(
283
- 'Deleted term "{term_name}" from taxonomy <a href="{tax_edit_link}">"{term_taxonomy}"</a>',
284
- 'Categories logger: detailed plain text output for deleted term',
285
- 'simple-history'
286
- );
287
- } elseif ( 'edited_term' === $message_key && ! empty( $term_edit_link ) && ! empty( $tax_edit_link ) ) {
288
- $message = _x(
289
- 'Edited term <a href="{term_edit_link}">"{to_term_name}"</a> in taxonomy <a href="{tax_edit_link}">"{to_term_taxonomy}"</a>',
290
- 'Categories logger: detailed plain text output for edited term',
291
- 'simple-history'
292
- );
293
- }
294
-
295
- return $this->interpolate( $message, $context, $row );
296
- }
297
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  }
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logs changes to categories and tags and taxonomies
7
  */
8
+ class SimpleCategoriesLogger extends SimpleLogger
9
+ {
10
+
11
+ public $slug = __CLASS__;
12
+
13
+ /**
14
+ * Get array with information about this logger
15
+ *
16
+ * @return array
17
+ */
18
+ public function getInfo()
19
+ {
20
+
21
+ $arr_info = array(
22
+ 'name' => __('Categories Logger', 'simple-history'),
23
+ 'description' => 'Logs changes to categories, tags, and taxonomies',
24
+ 'messages' => array(
25
+ 'created_term' => __('Added term "{term_name}" in taxonomy "{term_taxonomy}"', 'simple-history'),
26
+ 'deleted_term' => __('Deleted term "{term_name}" from taxonomy "{term_taxonomy}"', 'simple-history'),
27
+ 'edited_term' => __('Edited term "{to_term_name}" in taxonomy "{to_term_taxonomy}"', 'simple-history'),
28
+ ),
29
+ 'labels' => array(
30
+ 'search' => array(
31
+ 'label' => _x('Categories', 'Categories logger: search', 'simple-history'),
32
+ 'label_all' => _x('All category activity', 'Category logger: search', 'simple-history'),
33
+ 'options' => array(
34
+ _x('Term created', 'Category logger: search', 'simple-history') => array(
35
+ 'created_term'
36
+ ),
37
+ _x('Term deleted', 'Category logger: search', 'simple-history') => array(
38
+ 'deleted_term'
39
+ ),
40
+ _x('Term edited', 'Category logger: search', 'simple-history') => array(
41
+ 'edited_term'
42
+ ),
43
+ )
44
+ ) // end search array
45
+ ) // end labels
46
+ );
47
+
48
+ return $arr_info;
49
+ }
50
+
51
+ /**
52
+ * Called when the logger is loaded.
53
+ */
54
+ public function loaded()
55
+ {
56
+ // Fires after a new term is created, and after the term cache has been cleaned..
57
+ add_action('created_term', array( $this, 'on_created_term' ), 10, 3);
58
+
59
+ // Hook to this filter to see if it will cause a hierarchy loop.
60
+ add_action('delete_term', array( $this, 'on_delete_term' ), 10, 4);
61
+
62
+ // Filter the term parent.
63
+ add_action('wp_update_term_parent', array( $this, 'on_wp_update_term_parent' ), 10, 5);
64
+ }
65
+
66
+ /**
67
+ * Filter the term parent.
68
+ * Only way for Simple History to get both old and new term name.
69
+ * For example 'edited_term' does not contain enough info to know what the term was called before the update.
70
+ *
71
+ * @param int $parent ID of the parent term.
72
+ * @param int $term_id Term ID.
73
+ * @param string $taxonomy Taxonomy slug.
74
+ * @param array $parsed_args An array of potentially altered update arguments for the given term.
75
+ * @param array $term_update_args An array of update arguments for the given term.
76
+ */
77
+ public function on_wp_update_term_parent($parent = null, $term_id = null, $taxonomy = null, $parsed_args = null, $term_update_args = null)
78
+ {
79
+
80
+ $term_before_edited = get_term_by('id', $term_id, $taxonomy);
81
+
82
+ if (! $term_before_edited || empty($term_update_args)) {
83
+ return $parent;
84
+ }
85
+
86
+ $term_id = $term_before_edited->term_id;
87
+
88
+ $from_term_name = $term_before_edited->name;
89
+ $from_term_taxonomy = $term_before_edited->taxonomy;
90
+ $from_term_slug = $term_before_edited->slug;
91
+ $from_term_description = $term_before_edited->description;
92
+
93
+ $to_term_name = $term_update_args['name'];
94
+ $to_term_taxonomy = $term_update_args['taxonomy'];
95
+ $to_term_slug = $term_update_args['slug'];
96
+ $to_term_description = $term_update_args['description'];
97
+
98
+ $do_log_term = $this->ok_to_log_taxonomy($from_term_taxonomy);
99
+
100
+ if (! $do_log_term) {
101
+ return $parent;
102
+ }
103
+
104
+ $this->infoMessage(
105
+ 'edited_term',
106
+ array(
107
+ 'term_id' => $term_id,
108
+ 'from_term_name' => $from_term_name,
109
+ 'from_term_taxonomy' => $from_term_taxonomy,
110
+ 'from_term_slug' => $from_term_slug,
111
+ 'from_term_description' => $from_term_description,
112
+ 'to_term_name' => $to_term_name,
113
+ 'to_term_taxonomy' => $to_term_taxonomy,
114
+ 'to_term_slug' => $to_term_slug,
115
+ 'to_term_description' => $to_term_description,
116
+ )
117
+ );
118
+
119
+ return $parent;
120
+ }
121
+
122
+ /**
123
+ * Check if it's ok to log a taxonomy.
124
+ * We skip some taxonomies, for example Polylang translation terms that fill the log with
125
+ * messages like 'Edited term "pll_5a3643a142c80" in taxonomy "post_translations"' otherwise.
126
+ *
127
+ * @since 2.21
128
+ * @param string $from_term_taxonomy Slug of taxonomy.
129
+ * @return bool True or false.
130
+ */
131
+ public function ok_to_log_taxonomy($from_term_taxonomy = '')
132
+ {
133
+ if (empty($from_term_taxonomy)) {
134
+ return false;
135
+ }
136
+
137
+ $skip_taxonomies = $this->get_skip_taxonomies();
138
+
139
+ $do_log = ! in_array($from_term_taxonomy, $skip_taxonomies, true);
140
+
141
+ return $do_log;
142
+ }
143
+
144
+ /**
145
+ * Get taxonomies to skip.
146
+ *
147
+ * @since 2.21
148
+ * @return array Array with taxonomies.
149
+ */
150
+ public function get_skip_taxonomies()
151
+ {
152
+
153
+ $taxonomies_to_skip = array(
154
+ // Polylang taxonomies used to store translation mappings.
155
+ 'post_translations',
156
+ 'term_translations',
157
+ );
158
+
159
+ $taxonomies_to_skip = apply_filters('simple_history/categories_logger/skip_taxonomies', $taxonomies_to_skip);
160
+
161
+ return $taxonomies_to_skip;
162
+ }
163
+
164
+ /*
165
+ * Fires after a new term is created, and after the term cache has been cleaned.
166
+ *
167
+ * @since 2.3.0
168
+ *
169
+ * @param int $term_id Term ID.
170
+ * @param int $tt_id Term taxonomy ID.
171
+ * @param string $taxonomy Taxonomy slug.
172
+ */
173
+ public function on_created_term($term_id = null, $tt_id = null, $taxonomy = null)
174
+ {
175
+
176
+ $term = get_term_by('id', $term_id, $taxonomy);
177
+
178
+ if (! $term) {
179
+ return;
180
+ }
181
+
182
+ $term_name = $term->name;
183
+ $term_taxonomy = $term->taxonomy;
184
+ $term_id = $term->term_id;
185
+
186
+ $do_log_term = $this->ok_to_log_taxonomy($term_taxonomy);
187
+
188
+ if (! $do_log_term) {
189
+ return;
190
+ }
191
+
192
+ $this->infoMessage(
193
+ 'created_term',
194
+ array(
195
+ 'term_id' => $term_id,
196
+ 'term_name' => $term_name,
197
+ 'term_taxonomy' => $term_taxonomy,
198
+ )
199
+ );
200
+ }
201
+
202
+
203
+ /**
204
+ * Fires after a term is deleted from the database and the cache is cleaned.
205
+ *
206
+ * @param int $term Term ID.
207
+ * @param int $tt_id Term taxonomy ID.
208
+ * @param string $taxonomy Taxonomy slug.
209
+ * @param mixed $deleted_term Copy of the already-deleted term, in the form specified
210
+ * by the parent function. WP_Error otherwise.
211
+ */
212
+ public function on_delete_term($term = null, $tt_id = null, $taxonomy = null, $deleted_term = null)
213
+ {
214
+
215
+ if (is_wp_error($deleted_term)) {
216
+ return;
217
+ }
218
+
219
+ $term_name = $deleted_term->name;
220
+ $term_taxonomy = $deleted_term->taxonomy;
221
+ $term_id = $deleted_term->term_id;
222
+
223
+ $do_log_term = $this->ok_to_log_taxonomy($term_taxonomy);
224
+
225
+ if (! $do_log_term) {
226
+ return;
227
+ }
228
+
229
+ $this->infoMessage(
230
+ 'deleted_term',
231
+ array(
232
+ 'term_id' => $term_id,
233
+ 'term_name' => $term_name,
234
+ 'term_taxonomy' => $term_taxonomy,
235
+ )
236
+ );
237
+ }
238
+
239
+ /**
240
+ * Modify plain output to include link to term and taxonomy.
241
+ *
242
+ * @param array $row Row data.
243
+ */
244
+ public function getLogRowPlainTextOutput($row)
245
+ {
246
+ $context = $row->context;
247
+ $message_key = isset($context['_message_key']) ? $context['_message_key'] : null;
248
+
249
+ // Default to original log message.
250
+ $message = $row->message;
251
+
252
+ // Get term that was created, edited, or removed.
253
+ $term_id = isset($context['term_id']) ? (int) $context['term_id'] : null;
254
+
255
+ // Get taxonomy for term.
256
+ if ('created_term' === $message_key || 'deleted_term' === $message_key) {
257
+ $term_taxonomy = isset($context['term_taxonomy']) ? (string) $context['term_taxonomy'] : null;
258
+ } elseif ('edited_term' === $message_key) {
259
+ $term_taxonomy = isset($context['from_term_taxonomy']) ? (string) $context['from_term_taxonomy'] : null;
260
+ }
261
+
262
+ $tax_edit_link = add_query_arg(
263
+ array(
264
+ 'taxonomy' => $term_taxonomy,
265
+ ),
266
+ admin_url('term.php')
267
+ );
268
+
269
+ $context['tax_edit_link'] = $tax_edit_link;
270
+
271
+ $term_object = get_term($term_id, $term_taxonomy);
272
+
273
+ if (is_wp_error($term_object)) {
274
+ return $this->interpolate($message, $context, $row);
275
+ }
276
+
277
+ $term_edit_link = isset($term_object) ? get_edit_tag_link($term_id, $term_object->taxonomy) : null;
278
+ $context['term_edit_link'] = $term_edit_link;
279
+
280
+ // Get taxonomy name to use in log but fall back to taxonomy slug if
281
+ // taxonomy has been deleted.
282
+ $context['termTaxonomySlugOrName'] = isset($context['term_taxonomy']) ? $context['term_taxonomy'] : null;
283
+ $context['toTermTaxonomySlugOrName'] = isset($context['to_term_taxonomy']) ? $context['to_term_taxonomy'] : null;
284
+
285
+ if (isset($context['term_taxonomy']) && $context['term_taxonomy']) {
286
+ $termTaxonomyObject = get_taxonomy($context['term_taxonomy']);
287
+ if (is_a($termTaxonomyObject, 'WP_Taxonomy')) {
288
+ $termTaxonomyObjectLabels = get_taxonomy_labels($termTaxonomyObject);
289
+ $context['termTaxonomySlugOrName'] = $termTaxonomyObjectLabels->singular_name;
290
+ }
291
+ }
292
+
293
+ if (isset($context['to_term_taxonomy']) && $context['to_term_taxonomy']) {
294
+ $termTaxonomyObject = get_taxonomy($context['to_term_taxonomy']);
295
+ if (is_a($termTaxonomyObject, 'WP_Taxonomy')) {
296
+ $termTaxonomyObjectLabels = get_taxonomy_labels($termTaxonomyObject);
297
+ $context['toTermTaxonomySlugOrName'] = $termTaxonomyObjectLabels->singular_name;
298
+ }
299
+ }
300
+
301
+ if ('created_term' === $message_key && ! empty($term_edit_link) && ! empty($tax_edit_link)) {
302
+ $message = _x(
303
+ 'Added term <a href="{term_edit_link}">"{term_name}"</a> in taxonomy <a href="{tax_edit_link}">"{termTaxonomySlugOrName}"</a>',
304
+ 'Categories logger: detailed plain text output for created term',
305
+ 'simple-history'
306
+ );
307
+ } elseif ('deleted_term' === $message_key && ! empty($tax_edit_link)) {
308
+ $message = _x(
309
+ 'Deleted term "{term_name}" from taxonomy <a href="{tax_edit_link}">"{termTaxonomySlugOrName}"</a>',
310
+ 'Categories logger: detailed plain text output for deleted term',
311
+ 'simple-history'
312
+ );
313
+ } elseif ('edited_term' === $message_key && ! empty($term_edit_link) && ! empty($tax_edit_link)) {
314
+ $message = _x(
315
+ 'Edited term <a href="{term_edit_link}">"{to_term_name}"</a> in taxonomy <a href="{tax_edit_link}">"{toTermTaxonomySlugOrName}"</a>',
316
+ 'Categories logger: detailed plain text output for edited term',
317
+ 'simple-history'
318
+ );
319
+ }
320
+
321
+ return $this->interpolate($message, $context, $row);
322
+ }
323
  }
loggers/SimpleCommentsLogger.php CHANGED
@@ -1,53 +1,55 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /**
6
  * Logs things related to comments
7
  */
8
- class SimpleCommentsLogger extends SimpleLogger {
9
-
10
-
11
- public $slug = __CLASS__;
12
-
13
- function __construct( $sh ) {
14
-
15
- parent::__construct( $sh );
16
-
17
- // Add option to not show spam comments, because to much things getting logged
18
- // add_filter("simple_history/log_query_sql_where", array($this, "maybe_modify_log_query_sql_where"));
19
- add_filter( 'simple_history/log_query_inner_where', array( $this, 'maybe_modify_log_query_sql_where' ) );
20
- add_filter( 'simple_history/quick_stats_where', array( $this, 'maybe_modify_log_query_sql_where' ) );
21
-
22
- }
23
-
24
- /**
25
- * Modify sql query to exclude comments of type spam
26
- *
27
- * @param string $where sql query where
28
- */
29
- function maybe_modify_log_query_sql_where( $where ) {
30
-
31
- // since 19 sept 2016 we do include spam, to skip the subquery
32
- // spam comments should not be logged anyway since some time
33
- $include_spam = true;
34
-
35
- /**
36
- * Filter option to include spam or not in the gui
37
- * By default spam is not included, because it can fill the log
38
- * with too much events
39
- *
40
- * @since 2.0
41
- *
42
- * @param bool $include_spam Default false
43
- */
44
- $include_spam = apply_filters( 'simple_history/comments_logger/include_spam', $include_spam );
45
-
46
- if ( $include_spam ) {
47
- return $where;
48
- }
49
-
50
- $where .= sprintf('
 
 
51
  AND id NOT IN (
52
 
53
  SELECT id
@@ -76,733 +78,712 @@ class SimpleCommentsLogger extends SimpleLogger {
76
  )
77
  ', $this->db_table, $this->db_table_contexts, $this->slug);
78
 
79
- // echo $where;
80
- return $where;
81
-
82
- }
83
-
84
- /**
85
- * Get array with information about this logger
86
- *
87
- * @return array
88
- */
89
- function getInfo() {
90
-
91
- $arr_info = array(
92
- 'name' => 'Comments Logger',
93
- 'description' => 'Logs comments, and modifications to them',
94
- 'capability' => 'moderate_comments',
95
- 'messages' => array(
96
-
97
- // Comments
98
- 'anon_comment_added' => _x(
99
- 'Added a comment to {comment_post_type} "{comment_post_title}"',
100
- 'A comment was added to the database by a non-logged in internet user',
101
- 'simple-history'
102
- ),
103
-
104
- 'user_comment_added' => _x(
105
- 'Added a comment to {comment_post_type} "{comment_post_title}"',
106
- 'A comment was added to the database by a logged in user',
107
- 'simple-history'
108
- ),
109
-
110
- 'comment_status_approve' => _x(
111
- 'Approved a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
112
- 'A comment was approved',
113
- 'simple-history'
114
- ),
115
-
116
- 'comment_status_hold' => _x(
117
- 'Unapproved a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
118
- 'A comment was was unapproved',
119
- 'simple-history'
120
- ),
121
-
122
- 'comment_status_spam' => _x(
123
- 'Marked a comment to post "{comment_post_title}" as spam',
124
- 'A comment was marked as spam',
125
- 'simple-history'
126
- ),
127
-
128
- 'comment_status_trash' => _x(
129
- 'Trashed a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
130
- 'A comment was marked moved to the trash',
131
- 'simple-history'
132
- ),
133
-
134
- 'comment_untrashed' => _x(
135
- 'Restored a comment to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash',
136
- 'A comment was restored from the trash',
137
- 'simple-history'
138
- ),
139
-
140
- 'comment_deleted' => _x(
141
- 'Deleted a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
142
- 'A comment was deleted',
143
- 'simple-history'
144
- ),
145
-
146
- 'comment_edited' => _x(
147
- 'Edited a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
148
- 'A comment was edited',
149
- 'simple-history'
150
- ),
151
-
152
- // Trackbacks
153
- 'anon_trackback_added' => _x(
154
- 'Added a trackback to {comment_post_type} "{comment_post_title}"',
155
- 'A trackback was added to the database by a non-logged in internet user',
156
- 'simple-history'
157
- ),
158
-
159
- 'user_trackback_added' => _x(
160
- 'Added a trackback to {comment_post_type} "{comment_post_title}"',
161
- 'A trackback was added to the database by a logged in user',
162
- 'simple-history'
163
- ),
164
-
165
- 'trackback_status_approve' => _x(
166
- 'Approved a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
167
- 'A trackback was approved',
168
- 'simple-history'
169
- ),
170
-
171
- 'trackback_status_hold' => _x(
172
- 'Unapproved a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
173
- 'A trackback was was unapproved',
174
- 'simple-history'
175
- ),
176
-
177
- 'trackback_status_spam' => _x(
178
- 'Marked a trackback to post "{comment_post_title}" as spam',
179
- 'A trackback was marked as spam',
180
- 'simple-history'
181
- ),
182
-
183
- 'trackback_status_trash' => _x(
184
- 'Trashed a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
185
- 'A trackback was marked moved to the trash',
186
- 'simple-history'
187
- ),
188
-
189
- 'trackback_untrashed' => _x(
190
- 'Restored a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash',
191
- 'A trackback was restored from the trash',
192
- 'simple-history'
193
- ),
194
-
195
- 'trackback_deleted' => _x(
196
- 'Deleted a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
197
- 'A trackback was deleted',
198
- 'simple-history'
199
- ),
200
-
201
- 'trackback_edited' => _x(
202
- 'Edited a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
203
- 'A trackback was edited',
204
- 'simple-history'
205
- ),
206
-
207
- // Pingbacks
208
- 'anon_pingback_added' => _x(
209
- 'Added a pingback to {comment_post_type} "{comment_post_title}"',
210
- 'A trackback was added to the database by a non-logged in internet user',
211
- 'simple-history'
212
- ),
213
-
214
- 'user_pingback_added' => _x(
215
- 'Added a pingback to {comment_post_type} "{comment_post_title}"',
216
- 'A pingback was added to the database by a logged in user',
217
- 'simple-history'
218
- ),
219
-
220
- 'pingback_status_approve' => _x(
221
- 'Approved a pingback to "{comment_post_title}" by "{comment_author}"" ({comment_author_email})',
222
- 'A pingback was approved',
223
- 'simple-history'
224
- ),
225
-
226
- 'pingback_status_hold' => _x(
227
- 'Unapproved a pingback to "{comment_post_title}" by "{comment_author}" ({comment_author_email})',
228
- 'A pingback was was unapproved',
229
- 'simple-history'
230
- ),
231
-
232
- 'pingback_status_spam' => _x(
233
- 'Marked a pingback to post "{comment_post_title}" as spam',
234
- 'A pingback was marked as spam',
235
- 'simple-history'
236
- ),
237
-
238
- 'pingback_status_trash' => _x(
239
- 'Trashed a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
240
- 'A pingback was marked moved to the trash',
241
- 'simple-history'
242
- ),
243
-
244
- 'pingback_untrashed' => _x(
245
- 'Restored a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash',
246
- 'A pingback was restored from the trash',
247
- 'simple-history'
248
- ),
249
-
250
- 'pingback_deleted' => _x(
251
- 'Deleted a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
252
- 'A pingback was deleted',
253
- 'simple-history'
254
- ),
255
-
256
- 'pingback_edited' => _x(
257
- 'Edited a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
258
- 'A pingback was edited',
259
- 'simple-history'
260
- ),
261
-
262
- ), // end messages
263
-
264
- 'labels' => array(
265
-
266
- 'search' => array(
267
- 'label' => _x( 'Comments', 'Comments logger: search', 'simple-history' ),
268
- 'label_all' => _x( 'All comments activity', 'Comments logger: search', 'simple-history' ),
269
- 'options' => array(
270
- _x( 'Added comments', 'Comments logger: search', 'simple-history' ) => array(
271
- 'anon_comment_added',
272
- 'user_comment_added',
273
- 'anon_trackback_added',
274
- 'user_trackback_added',
275
- 'anon_pingback_added',
276
- 'user_pingback_added',
277
- ),
278
- _x( 'Edited comments', 'Comments logger: search', 'simple-history' ) => array(
279
- 'comment_edited',
280
- 'trackback_edited',
281
- 'pingback_edited',
282
- ),
283
- _x( 'Approved comments', 'Comments logger: search', 'simple-history' ) => array(
284
- 'comment_status_approve',
285
- 'trackback_status_approve',
286
- 'pingback_status_approve',
287
- ),
288
- _x( 'Held comments', 'Comments logger: search', 'simple-history' ) => array(
289
- 'comment_status_hold',
290
- 'trackback_status_hold',
291
- 'pingback_status_hold',
292
- ),
293
- _x( 'Comments status changed to spam', 'Comments logger: search', 'simple-history' ) => array(
294
- 'comment_status_spam',
295
- 'trackback_status_spam',
296
- 'pingback_status_spam',
297
- ),
298
- _x( 'Trashed comments', 'Comments logger: search', 'simple-history' ) => array(
299
- 'comment_status_trash',
300
- 'trackback_status_trash',
301
- 'pingback_status_trash',
302
- ),
303
- _x( 'Untrashed comments', 'Comments logger: search', 'simple-history' ) => array(
304
- 'comment_untrashed',
305
- 'trackback_untrashed',
306
- 'pingback_untrashed',
307
- ),
308
- _x( 'Deleted comments', 'Comments logger: search', 'simple-history' ) => array(
309
- 'comment_deleted',
310
- 'trackback_deleted',
311
- 'pingback_deleted',
312
- ),
313
- ),
314
- ),// end search
315
-
316
- ),// labels
317
-
318
- );
319
-
320
- return $arr_info;
321
-
322
- }
323
-
324
- public function loaded() {
325
-
326
- /**
327
- * Fires immediately after a comment is inserted into the database.
328
- */
329
- add_action( 'comment_post', array( $this, 'on_comment_post' ), 10, 2 );
330
-
331
- /**
332
- * Fires after a comment status has been updated in the database.
333
- * The hook also fires immediately before comment status transition hooks are fired.
334
- */
335
- add_action( 'wp_set_comment_status', array( $this, 'on_wp_set_comment_status' ), 10, 2 );
336
-
337
- /**
338
- *Fires immediately after a comment is restored from the Trash.
339
- */
340
- add_action( 'untrashed_comment', array( $this, 'on_untrashed_comment' ), 10, 1 );
341
-
342
- /**
343
- * Fires immediately before a comment is deleted from the database.
344
- */
345
- add_action( 'delete_comment', array( $this, 'on_delete_comment' ), 10, 1 );
346
-
347
- /**
348
- * Fires immediately after a comment is updated in the database.
349
- * The hook also fires immediately before comment status transition hooks are fired.
350
- */
351
- add_action( 'edit_comment', array( $this, 'on_edit_comment' ), 10, 1 );
352
-
353
- }
354
-
355
- /**
356
- * Get comments context
357
- *
358
- * @param int $comment_ID
359
- * @return mixed array with context if comment found, false if comment not found
360
- */
361
- public function get_context_for_comment( $comment_ID ) {
362
-
363
- // get_comment passes comment_ID by reference, so it can be unset by that function
364
- $comment_ID_original = $comment_ID;
365
- $comment_data = get_comment( $comment_ID );
366
-
367
- if ( is_null( $comment_data ) ) {
368
- return false;
369
- }
370
-
371
- $comment_parent_post = get_post( $comment_data->comment_post_ID );
372
-
373
- $context = array(
374
- 'comment_ID' => $comment_ID_original,
375
- 'comment_author' => $comment_data->comment_author,
376
- 'comment_author_email' => $comment_data->comment_author_email,
377
- 'comment_author_url' => $comment_data->comment_author_url,
378
- 'comment_author_IP' => $comment_data->comment_author_IP,
379
- 'comment_content' => $comment_data->comment_content,
380
- 'comment_approved' => $comment_data->comment_approved,
381
- 'comment_agent' => $comment_data->comment_agent,
382
- 'comment_type' => $comment_data->comment_type,
383
- 'comment_parent' => $comment_data->comment_parent,
384
- 'comment_post_ID' => $comment_data->comment_post_ID,
385
- 'comment_post_title' => $comment_parent_post->post_title,
386
- 'comment_post_type' => $comment_parent_post->post_type,
387
- );
388
-
389
- // Note: comment type is empty for normal comments
390
- if ( empty( $context['comment_type'] ) ) {
391
- $context['comment_type'] = 'comment';
392
- }
393
-
394
- return $context;
395
-
396
- }
397
-
398
- public function on_edit_comment( $comment_ID ) {
399
-
400
- $context = $this->get_context_for_comment( $comment_ID );
401
- if ( ! $context ) {
402
- return;
403
- }
404
-
405
- $this->infoMessage(
406
- "{$context["comment_type"]}_edited",
407
- $context
408
- );
409
-
410
- }
411
-
412
- public function on_delete_comment( $comment_ID ) {
413
-
414
- $context = $this->get_context_for_comment( $comment_ID );
415
-
416
- if ( ! $context ) {
417
- return;
418
- }
419
-
420
- $comment_data = get_comment( $comment_ID );
421
-
422
- // add occasions if comment was considered spam
423
- // if not added, spam comments can easily flood the log
424
- // Deletions of spam easiy flood log
425
- if ( isset( $comment_data->comment_approved ) && 'spam' === $comment_data->comment_approved ) {
426
-
427
- // since 2.5.5: don't log deletion of spam comments
428
- return;
429
- // $context["_occasionsID"] = __CLASS__ . '/' . __FUNCTION__ . "/anon_{$context["comment_type"]}_deleted/type:spam";
430
- }
431
-
432
- $this->infoMessage(
433
- "{$context["comment_type"]}_deleted",
434
- $context
435
- );
436
-
437
- }
438
-
439
- public function on_untrashed_comment( $comment_ID ) {
440
-
441
- $context = $this->get_context_for_comment( $comment_ID );
442
- if ( ! $context ) {
443
- return;
444
- }
445
-
446
- $this->infoMessage(
447
- "{$context["comment_type"]}_untrashed",
448
- $context
449
- );
450
-
451
- }
452
-
453
- /**
454
- * Fires after a comment status has been updated in the database.
455
- * The hook also fires immediately before comment status transition hooks are fired.
456
- *
457
- * @param int $comment_id The comment ID.
458
- * @param string|bool $comment_status The comment status. Possible values include 'hold',
459
- * 'approve', 'spam', 'trash', or false.
460
- * do_action( 'wp_set_comment_status', $comment_id, $comment_status );
461
- */
462
- public function on_wp_set_comment_status( $comment_ID, $comment_status ) {
463
-
464
- $context = $this->get_context_for_comment( $comment_ID );
465
-
466
- if ( ! $context ) {
467
- return;
468
- }
469
-
470
- /*
471
- $comment_status:
472
- approve
473
- comment was approved
474
- spam
475
- comment was marked as spam
476
- trash
477
- comment was trashed
478
- hold
479
- comment was un-approved
480
- */
481
- $message = "{$context["comment_type"]}_status_{$comment_status}";
482
-
483
- $this->infoMessage(
484
- $message,
485
- $context
486
- );
487
-
488
- }
489
-
490
- /**
491
- * Fires immediately after a comment is inserted into the database.
492
- */
493
- public function on_comment_post( $comment_ID, $comment_approved ) {
494
-
495
- $context = $this->get_context_for_comment( $comment_ID );
496
-
497
- if ( ! $context ) {
498
- return;
499
- }
500
-
501
- // since 2.5.5: no more logging of spam comments
502
- if ( isset( $comment_approved ) && 'spam' === $comment_approved ) {
503
- return;
504
- }
505
-
506
- $comment_data = get_comment( $comment_ID );
507
-
508
- $message = '';
509
-
510
- if ( $comment_data->user_id ) {
511
-
512
- // comment was from a logged in user
513
- $message = "user_{$context["comment_type"]}_added";
514
-
515
- } else {
516
-
517
- // comment was from a non-logged in user
518
- $message = "anon_{$context["comment_type"]}_added";
519
- $context['_initiator'] = SimpleLoggerLogInitiators::WEB_USER;
520
-
521
- // add occasions if comment is considered spam
522
- // if not added, spam comments can easily flood the log
523
- if ( isset( $comment_data->comment_approved ) && 'spam' === $comment_data->comment_approved ) {
524
- $context['_occasionsID'] = __CLASS__ . '/' . __FUNCTION__ . "/anon_{$context["comment_type"]}_added/type:spam";
525
- }
526
- }
527
-
528
- $this->infoMessage(
529
- $message,
530
- $context
531
- );
532
-
533
- }
534
-
535
-
536
- /**
537
- * Modify plain output to inlcude link to post
538
- * and link to comment
539
- */
540
- public function getLogRowPlainTextOutput( $row ) {
541
-
542
- $message = $row->message;
543
- $context = $row->context;
544
- $message_key = $context['_message_key'];
545
-
546
- // Message is untranslated here, so get translated text
547
- // Can't call parent __FUNCTION__ because it will interpolate too, which we don't want
548
- if ( ! empty( $message_key ) ) {
549
- $message = $this->messages[ $message_key ]['translated_text'];
550
- }
551
-
552
- // Wrap links around {comment_post_title}
553
- $comment_post_ID = isset( $context['comment_post_ID'] ) ? (int) $context['comment_post_ID'] : null;
554
- if ( $comment_post_ID && $comment_post = get_post( $comment_post_ID ) ) {
555
-
556
- $edit_post_link = get_edit_post_link( $comment_post_ID );
557
-
558
- if ( $edit_post_link ) {
559
-
560
- $message = str_replace(
561
- '"{comment_post_title}"',
562
- "<a href='{$edit_post_link}'>\"{comment_post_title}\"</a>",
563
- $message
564
- );
565
-
566
- }
567
- }
568
-
569
- return $this->interpolate( $message, $context, $row );
570
-
571
- }
572
-
573
-
574
- /**
575
- * Get output for detailed log section
576
- */
577
- function getLogRowDetailsOutput( $row ) {
578
-
579
- $context = $row->context;
580
- $message_key = $context['_message_key'];
581
- $output = '';
582
- // print_r($row);exit;
583
- /*
584
- if ( 'spam' !== $commentdata['comment_approved'] ) { // If it's spam save it silently for later crunching
585
- if ( '0' == $commentdata['comment_approved'] ) { // comment not spam, but not auto-approved
586
- wp_notify_moderator( $comment_ID );
587
- */
588
- /*
589
- if ( isset( $context["comment_approved"] ) && $context["comment_approved"] == '0' ) {
590
- $output .= "<br>comment was automatically approved";
591
- } else {
592
- $output .= "<br>comment was not automatically approved";
593
- }*/
594
-
595
- $comment_text = '';
596
- if ( isset( $context['comment_content'] ) && $context['comment_content'] ) {
597
- $comment_text = $context['comment_content'];
598
- $comment_text = wp_trim_words( $comment_text, 20 );
599
- $comment_text = wpautop( $comment_text );
600
- }
601
-
602
- // Keys to show
603
- $arr_plugin_keys = array();
604
- $comment_type = isset( $context['comment_type'] ) ? $context['comment_type'] : '';
605
-
606
- switch ( $comment_type ) {
607
-
608
- case 'trackback';
609
-
610
- $arr_plugin_keys = array(
611
- 'trackback_status' => _x( 'Status', 'comments logger - detailed output comment status', 'simple-history' ),
612
- // "trackback_type" => _x("Trackback type", "comments logger - detailed output comment type", "simple-history"),
613
- 'trackback_author' => _x( 'Name', 'comments logger - detailed output author', 'simple-history' ),
614
- 'trackback_author_email' => _x( 'Email', 'comments logger - detailed output email', 'simple-history' ),
615
- 'trackback_content' => _x( 'Content', 'comments logger - detailed output content', 'simple-history' ),
616
- );
617
-
618
- break;
619
-
620
- case 'pingback';
621
-
622
- $arr_plugin_keys = array(
623
-
624
- 'pingback_status' => _x( 'Status', 'comments logger - detailed output comment status', 'simple-history' ),
625
- // "pingback_type" => _x("Pingback type", "comments logger - detailed output comment type", "simple-history"),
626
- 'pingback_author' => _x( 'Name', 'comments logger - detailed output author', 'simple-history' ),
627
- 'pingback_author_email' => _x( 'Email', 'comments logger - detailed output email', 'simple-history' ),
628
- 'pingback_content' => _x( 'Content', 'comments logger - detailed output content', 'simple-history' ),
629
-
630
- );
631
-
632
- break;
633
-
634
- case 'comment';
635
- default;
636
-
637
- $arr_plugin_keys = array(
638
- 'comment_status' => _x( 'Status', 'comments logger - detailed output comment status', 'simple-history' ),
639
- // "comment_type" => _x("Comment type", "comments logger - detailed output comment type", "simple-history"),
640
- 'comment_author' => _x( 'Name', 'comments logger - detailed output author', 'simple-history' ),
641
- 'comment_author_email' => _x( 'Email', 'comments logger - detailed output email', 'simple-history' ),
642
- 'comment_content' => _x( 'Comment', 'comments logger - detailed output content', 'simple-history' ),
643
- );
644
-
645
- break;
646
-
647
- // "comment_author_url" => _x("Author URL", "comments logger - detailed output author", "simple-history"),
648
- // "comment_author_IP" => _x("IP number", "comments logger - detailed output IP", "simple-history"),
649
- }// End switch().
650
-
651
- $arr_plugin_keys = apply_filters( 'simple_history/comments_logger/row_details_plugin_info_keys', $arr_plugin_keys );
652
-
653
- // Start output of plugin meta data table
654
- $output .= "<table class='SimpleHistoryLogitem__keyValueTable'>";
655
-
656
- foreach ( $arr_plugin_keys as $key => $desc ) {
657
-
658
- switch ( $key ) {
659
-
660
- case 'comment_content':
661
- case 'trackback_content':
662
- case 'pingback_content':
663
- $desc_output = $comment_text;
664
- break;
665
-
666
- case 'comment_author':
667
- case 'trackback_author':
668
- case 'pingback_author':
669
-
670
- $desc_output = '';
671
-
672
- if ( isset( $context[ $key ] ) ) {
673
- $desc_output .= esc_html( $context[ $key ] );
674
- }
675
-
676
- /*
677
- if ( isset( $context["comment_author_email"] ) ) {
678
-
679
- $gravatar_email = $context["comment_author_email"];
680
- $avatar = $this->simpleHistory->get_avatar( $gravatar_email, 14, "blank" );
681
- $desc_output .= "<span class='SimpleCommentsLogger__gravatar'>{$avatar}</span>";
682
-
683
- }
684
- */
685
-
686
- break;
687
-
688
- case 'comment_status':
689
- case 'trackback_status':
690
- case 'pingback_status':
691
-
692
- if ( isset( $context['comment_approved'] ) ) {
693
-
694
- if ( $context['comment_approved'] === 'spam' ) {
695
- $desc_output = __( 'Spam', 'simple-history' );
696
- } elseif ( $context['comment_approved'] == 1 ) {
697
- $desc_output = __( 'Approved', 'simple-history' );
698
- } elseif ( $context['comment_approved'] == 0 ) {
699
- $desc_output = __( 'Pending', 'simple-history' );
700
- }
701
- }
702
-
703
- break;
704
-
705
- case 'comment_type':
706
- case 'trackback_type':
707
- case 'pingback_type':
708
-
709
- if ( isset( $context['comment_type'] ) ) {
710
-
711
- if ( $context['comment_type'] === 'trackback' ) {
712
- $desc_output = __( 'Trackback', 'simple-history' );
713
- } elseif ( $context['comment_type'] === 'pingback' ) {
714
- $desc_output = __( 'Pingback', 'simple-history' );
715
- } elseif ( $context['comment_type'] === 'comment' ) {
716
- $desc_output = __( 'Comment', 'simple-history' );
717
- } else {
718
- $desc_output = '';
719
- }
720
- }
721
-
722
- break;
723
-
724
- default;
725
-
726
- if ( isset( $context[ $key ] ) ) {
727
- $desc_output = esc_html( $context[ $key ] );
728
- }
729
-
730
- break;
731
- }// End switch().
732
-
733
- // Skip empty rows
734
- if ( empty( $desc_output ) ) {
735
- continue;
736
- }
737
-
738
- $output .= sprintf(
739
- '
740
  <tr>
741
  <td>%1$s</td>
742
  <td>%2$s</td>
743
  </tr>
744
  ',
745
- esc_html( $desc ),
746
- $desc_output
747
- );
748
-
749
- }// End foreach().
750
-
751
- // Add link to edit comment
752
- $comment_ID = isset( $context['comment_ID'] ) && is_numeric( $context['comment_ID'] ) ? (int) $context['comment_ID'] : false;
753
-
754
- if ( $comment_ID ) {
755
-
756
- $comment = get_comment( $comment_ID );
757
-
758
- if ( $comment ) {
759
-
760
- // http://site.local/wp/wp-admin/comment.php?action=editcomment&c=
761
- $edit_comment_link = get_edit_comment_link( $comment_ID );
762
-
763
- // Edit link sometimes does not contain comment ID
764
- // Probably because comment has been removed or something
765
- // So only continue if link does not end with "=""
766
- if ( $edit_comment_link && $edit_comment_link[ strlen( $edit_comment_link ) -1 ] !== '=' ) {
767
-
768
- $output .= sprintf(
769
- '
770
  <tr>
771
  <td></td>
772
  <td><a href="%2$s">%1$s</a></td>
773
  </tr>
774
  ',
775
- _x( 'View/Edit', 'comments logger - edit comment', 'simple-history' ),
776
- $edit_comment_link
777
- );
778
-
779
- }
780
- }
781
- } // End if().
782
-
783
- // End table
784
- $output .= '</table>';
785
-
786
- return $output;
787
-
788
- }
789
-
790
- function adminCSS() {
791
- ?>
792
- <style>
793
- .SimpleCommentsLogger__gravatar {
794
- line-height: 1;
795
- border-radius: 50%;
796
- overflow: hidden;
797
- margin-right: .5em;
798
- margin-left: .5em;
799
- display: inline-block;
800
- }
801
- .SimpleCommentsLogger__gravatar img {
802
- display: block;
803
- }
804
- </style>
805
- <?php
806
- }
807
-
808
  }
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logs things related to comments
7
  */
8
+ class SimpleCommentsLogger extends SimpleLogger
9
+ {
10
+
11
+
12
+ public $slug = __CLASS__;
13
+
14
+ function __construct($sh)
15
+ {
16
+
17
+ parent::__construct($sh);
18
+
19
+ // Add option to not show spam comments, because to much things getting logged
20
+ // add_filter("simple_history/log_query_sql_where", array($this, "maybe_modify_log_query_sql_where"));
21
+ add_filter('simple_history/log_query_inner_where', array( $this, 'maybe_modify_log_query_sql_where' ));
22
+ add_filter('simple_history/quick_stats_where', array( $this, 'maybe_modify_log_query_sql_where' ));
23
+ }
24
+
25
+ /**
26
+ * Modify sql query to exclude comments of type spam
27
+ *
28
+ * @param string $where sql query where
29
+ */
30
+ function maybe_modify_log_query_sql_where($where)
31
+ {
32
+
33
+ // since 19 sept 2016 we do include spam, to skip the subquery
34
+ // spam comments should not be logged anyway since some time
35
+ $include_spam = true;
36
+
37
+ /**
38
+ * Filter option to include spam or not in the gui
39
+ * By default spam is not included, because it can fill the log
40
+ * with too much events
41
+ *
42
+ * @since 2.0
43
+ *
44
+ * @param bool $include_spam Default false
45
+ */
46
+ $include_spam = apply_filters('simple_history/comments_logger/include_spam', $include_spam);
47
+
48
+ if ($include_spam) {
49
+ return $where;
50
+ }
51
+
52
+ $where .= sprintf('
53
  AND id NOT IN (
54
 
55
  SELECT id
78
  )
79
  ', $this->db_table, $this->db_table_contexts, $this->slug);
80
 
81
+ // echo $where;
82
+ return $where;
83
+ }
84
+
85
+ /**
86
+ * Get array with information about this logger
87
+ *
88
+ * @return array
89
+ */
90
+ function getInfo()
91
+ {
92
+
93
+ $arr_info = array(
94
+ 'name' => 'Comments Logger',
95
+ 'description' => 'Logs comments, and modifications to them',
96
+ 'capability' => 'moderate_comments',
97
+ 'messages' => array(
98
+
99
+ // Comments
100
+ 'anon_comment_added' => _x(
101
+ 'Added a comment to {comment_post_type} "{comment_post_title}"',
102
+ 'A comment was added to the database by a non-logged in internet user',
103
+ 'simple-history'
104
+ ),
105
+
106
+ 'user_comment_added' => _x(
107
+ 'Added a comment to {comment_post_type} "{comment_post_title}"',
108
+ 'A comment was added to the database by a logged in user',
109
+ 'simple-history'
110
+ ),
111
+
112
+ 'comment_status_approve' => _x(
113
+ 'Approved a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
114
+ 'A comment was approved',
115
+ 'simple-history'
116
+ ),
117
+
118
+ 'comment_status_hold' => _x(
119
+ 'Unapproved a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
120
+ 'A comment was was unapproved',
121
+ 'simple-history'
122
+ ),
123
+
124
+ 'comment_status_spam' => _x(
125
+ 'Marked a comment to post "{comment_post_title}" as spam',
126
+ 'A comment was marked as spam',
127
+ 'simple-history'
128
+ ),
129
+
130
+ 'comment_status_trash' => _x(
131
+ 'Trashed a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
132
+ 'A comment was marked moved to the trash',
133
+ 'simple-history'
134
+ ),
135
+
136
+ 'comment_untrashed' => _x(
137
+ 'Restored a comment to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash',
138
+ 'A comment was restored from the trash',
139
+ 'simple-history'
140
+ ),
141
+
142
+ 'comment_deleted' => _x(
143
+ 'Deleted a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
144
+ 'A comment was deleted',
145
+ 'simple-history'
146
+ ),
147
+
148
+ 'comment_edited' => _x(
149
+ 'Edited a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
150
+ 'A comment was edited',
151
+ 'simple-history'
152
+ ),
153
+
154
+ // Trackbacks
155
+ 'anon_trackback_added' => _x(
156
+ 'Added a trackback to {comment_post_type} "{comment_post_title}"',
157
+ 'A trackback was added to the database by a non-logged in internet user',
158
+ 'simple-history'
159
+ ),
160
+
161
+ 'user_trackback_added' => _x(
162
+ 'Added a trackback to {comment_post_type} "{comment_post_title}"',
163
+ 'A trackback was added to the database by a logged in user',
164
+ 'simple-history'
165
+ ),
166
+
167
+ 'trackback_status_approve' => _x(
168
+ 'Approved a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
169
+ 'A trackback was approved',
170
+ 'simple-history'
171
+ ),
172
+
173
+ 'trackback_status_hold' => _x(
174
+ 'Unapproved a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
175
+ 'A trackback was was unapproved',
176
+ 'simple-history'
177
+ ),
178
+
179
+ 'trackback_status_spam' => _x(
180
+ 'Marked a trackback to post "{comment_post_title}" as spam',
181
+ 'A trackback was marked as spam',
182
+ 'simple-history'
183
+ ),
184
+
185
+ 'trackback_status_trash' => _x(
186
+ 'Trashed a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
187
+ 'A trackback was marked moved to the trash',
188
+ 'simple-history'
189
+ ),
190
+
191
+ 'trackback_untrashed' => _x(
192
+ 'Restored a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash',
193
+ 'A trackback was restored from the trash',
194
+ 'simple-history'
195
+ ),
196
+
197
+ 'trackback_deleted' => _x(
198
+ 'Deleted a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
199
+ 'A trackback was deleted',
200
+ 'simple-history'
201
+ ),
202
+
203
+ 'trackback_edited' => _x(
204
+ 'Edited a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
205
+ 'A trackback was edited',
206
+ 'simple-history'
207
+ ),
208
+
209
+ // Pingbacks
210
+ 'anon_pingback_added' => _x(
211
+ 'Added a pingback to {comment_post_type} "{comment_post_title}"',
212
+ 'A trackback was added to the database by a non-logged in internet user',
213
+ 'simple-history'
214
+ ),
215
+
216
+ 'user_pingback_added' => _x(
217
+ 'Added a pingback to {comment_post_type} "{comment_post_title}"',
218
+ 'A pingback was added to the database by a logged in user',
219
+ 'simple-history'
220
+ ),
221
+
222
+ 'pingback_status_approve' => _x(
223
+ 'Approved a pingback to "{comment_post_title}" by "{comment_author}"" ({comment_author_email})',
224
+ 'A pingback was approved',
225
+ 'simple-history'
226
+ ),
227
+
228
+ 'pingback_status_hold' => _x(
229
+ 'Unapproved a pingback to "{comment_post_title}" by "{comment_author}" ({comment_author_email})',
230
+ 'A pingback was was unapproved',
231
+ 'simple-history'
232
+ ),
233
+
234
+ 'pingback_status_spam' => _x(
235
+ 'Marked a pingback to post "{comment_post_title}" as spam',
236
+ 'A pingback was marked as spam',
237
+ 'simple-history'
238
+ ),
239
+
240
+ 'pingback_status_trash' => _x(
241
+ 'Trashed a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
242
+ 'A pingback was marked moved to the trash',
243
+ 'simple-history'
244
+ ),
245
+
246
+ 'pingback_untrashed' => _x(
247
+ 'Restored a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash',
248
+ 'A pingback was restored from the trash',
249
+ 'simple-history'
250
+ ),
251
+
252
+ 'pingback_deleted' => _x(
253
+ 'Deleted a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
254
+ 'A pingback was deleted',
255
+ 'simple-history'
256
+ ),
257
+
258
+ 'pingback_edited' => _x(
259
+ 'Edited a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
260
+ 'A pingback was edited',
261
+ 'simple-history'
262
+ ),
263
+
264
+ ), // end messages
265
+
266
+ 'labels' => array(
267
+
268
+ 'search' => array(
269
+ 'label' => _x('Comments', 'Comments logger: search', 'simple-history'),
270
+ 'label_all' => _x('All comments activity', 'Comments logger: search', 'simple-history'),
271
+ 'options' => array(
272
+ _x('Added comments', 'Comments logger: search', 'simple-history') => array(
273
+ 'anon_comment_added',
274
+ 'user_comment_added',
275
+ 'anon_trackback_added',
276
+ 'user_trackback_added',
277
+ 'anon_pingback_added',
278
+ 'user_pingback_added',
279
+ ),
280
+ _x('Edited comments', 'Comments logger: search', 'simple-history') => array(
281
+ 'comment_edited',
282
+ 'trackback_edited',
283
+ 'pingback_edited',
284
+ ),
285
+ _x('Approved comments', 'Comments logger: search', 'simple-history') => array(
286
+ 'comment_status_approve',
287
+ 'trackback_status_approve',
288
+ 'pingback_status_approve',
289
+ ),
290
+ _x('Held comments', 'Comments logger: search', 'simple-history') => array(
291
+ 'comment_status_hold',
292
+ 'trackback_status_hold',
293
+ 'pingback_status_hold',
294
+ ),
295
+ _x('Comments status changed to spam', 'Comments logger: search', 'simple-history') => array(
296
+ 'comment_status_spam',
297
+ 'trackback_status_spam',
298
+ 'pingback_status_spam',
299
+ ),
300
+ _x('Trashed comments', 'Comments logger: search', 'simple-history') => array(
301
+ 'comment_status_trash',
302
+ 'trackback_status_trash',
303
+ 'pingback_status_trash',
304
+ ),
305
+ _x('Untrashed comments', 'Comments logger: search', 'simple-history') => array(
306
+ 'comment_untrashed',
307
+ 'trackback_untrashed',
308
+ 'pingback_untrashed',
309
+ ),
310
+ _x('Deleted comments', 'Comments logger: search', 'simple-history') => array(
311
+ 'comment_deleted',
312
+ 'trackback_deleted',
313
+ 'pingback_deleted',
314
+ ),
315
+ ),
316
+ ),// end search
317
+
318
+ ),// labels
319
+
320
+ );
321
+
322
+ return $arr_info;
323
+ }
324
+
325
+ public function loaded()
326
+ {
327
+
328
+ /**
329
+ * Fires immediately after a comment is inserted into the database.
330
+ */
331
+ add_action('comment_post', array( $this, 'on_comment_post' ), 10, 2);
332
+
333
+ /**
334
+ * Fires after a comment status has been updated in the database.
335
+ * The hook also fires immediately before comment status transition hooks are fired.
336
+ */
337
+ add_action('wp_set_comment_status', array( $this, 'on_wp_set_comment_status' ), 10, 2);
338
+
339
+ /**
340
+ *Fires immediately after a comment is restored from the Trash.
341
+ */
342
+ add_action('untrashed_comment', array( $this, 'on_untrashed_comment' ), 10, 1);
343
+
344
+ /**
345
+ * Fires immediately before a comment is deleted from the database.
346
+ */
347
+ add_action('delete_comment', array( $this, 'on_delete_comment' ), 10, 1);
348
+
349
+ /**
350
+ * Fires immediately after a comment is updated in the database.
351
+ * The hook also fires immediately before comment status transition hooks are fired.
352
+ */
353
+ add_action('edit_comment', array( $this, 'on_edit_comment' ), 10, 1);
354
+ }
355
+
356
+ /**
357
+ * Get comments context
358
+ *
359
+ * @param int $comment_ID
360
+ * @return mixed array with context if comment found, false if comment not found
361
+ */
362
+ public function get_context_for_comment($comment_ID)
363
+ {
364
+
365
+ // get_comment passes comment_ID by reference, so it can be unset by that function
366
+ $comment_ID_original = $comment_ID;
367
+ $comment_data = get_comment($comment_ID);
368
+
369
+ if (is_null($comment_data)) {
370
+ return false;
371
+ }
372
+
373
+ $comment_parent_post = get_post($comment_data->comment_post_ID);
374
+
375
+ $context = array(
376
+ 'comment_ID' => $comment_ID_original,
377
+ 'comment_author' => $comment_data->comment_author,
378
+ 'comment_author_email' => $comment_data->comment_author_email,
379
+ 'comment_author_url' => $comment_data->comment_author_url,
380
+ 'comment_author_IP' => $comment_data->comment_author_IP,
381
+ 'comment_content' => $comment_data->comment_content,
382
+ 'comment_approved' => $comment_data->comment_approved,
383
+ 'comment_agent' => $comment_data->comment_agent,
384
+ 'comment_type' => $comment_data->comment_type,
385
+ 'comment_parent' => $comment_data->comment_parent,
386
+ 'comment_post_ID' => $comment_data->comment_post_ID,
387
+ 'comment_post_title' => $comment_parent_post->post_title,
388
+ 'comment_post_type' => $comment_parent_post->post_type,
389
+ );
390
+
391
+ // Note: comment type is empty for normal comments
392
+ if (empty($context['comment_type'])) {
393
+ $context['comment_type'] = 'comment';
394
+ }
395
+
396
+ return $context;
397
+ }
398
+
399
+ public function on_edit_comment($comment_ID)
400
+ {
401
+
402
+ $context = $this->get_context_for_comment($comment_ID);
403
+ if (! $context) {
404
+ return;
405
+ }
406
+
407
+ $this->infoMessage(
408
+ "{$context["comment_type"]}_edited",
409
+ $context
410
+ );
411
+ }
412
+
413
+ public function on_delete_comment($comment_ID)
414
+ {
415
+
416
+ $context = $this->get_context_for_comment($comment_ID);
417
+
418
+ if (! $context) {
419
+ return;
420
+ }
421
+
422
+ $comment_data = get_comment($comment_ID);
423
+
424
+ // add occasions if comment was considered spam
425
+ // if not added, spam comments can easily flood the log
426
+ // Deletions of spam easiy flood log
427
+ if (isset($comment_data->comment_approved) && 'spam' === $comment_data->comment_approved) {
428
+ // since 2.5.5: don't log deletion of spam comments
429
+ return;
430
+ // $context["_occasionsID"] = __CLASS__ . '/' . __FUNCTION__ . "/anon_{$context["comment_type"]}_deleted/type:spam";
431
+ }
432
+
433
+ $this->infoMessage(
434
+ "{$context["comment_type"]}_deleted",
435
+ $context
436
+ );
437
+ }
438
+
439
+ public function on_untrashed_comment($comment_ID)
440
+ {
441
+
442
+ $context = $this->get_context_for_comment($comment_ID);
443
+ if (! $context) {
444
+ return;
445
+ }
446
+
447
+ $this->infoMessage(
448
+ "{$context["comment_type"]}_untrashed",
449
+ $context
450
+ );
451
+ }
452
+
453
+ /**
454
+ * Fires after a comment status has been updated in the database.
455
+ * The hook also fires immediately before comment status transition hooks are fired.
456
+ *
457
+ * @param int $comment_id The comment ID.
458
+ * @param string|bool $comment_status The comment status. Possible values include 'hold',
459
+ * 'approve', 'spam', 'trash', or false.
460
+ * do_action( 'wp_set_comment_status', $comment_id, $comment_status );
461
+ */
462
+ public function on_wp_set_comment_status($comment_ID, $comment_status)
463
+ {
464
+
465
+ $context = $this->get_context_for_comment($comment_ID);
466
+
467
+ if (! $context) {
468
+ return;
469
+ }
470
+
471
+ /*
472
+ $comment_status:
473
+ approve
474
+ comment was approved
475
+ spam
476
+ comment was marked as spam
477
+ trash
478
+ comment was trashed
479
+ hold
480
+ comment was un-approved
481
+ */
482
+ $message = "{$context["comment_type"]}_status_{$comment_status}";
483
+
484
+ $this->infoMessage(
485
+ $message,
486
+ $context
487
+ );
488
+ }
489
+
490
+ /**
491
+ * Fires immediately after a comment is inserted into the database.
492
+ */
493
+ public function on_comment_post($comment_ID, $comment_approved)
494
+ {
495
+
496
+ $context = $this->get_context_for_comment($comment_ID);
497
+
498
+ if (! $context) {
499
+ return;
500
+ }
501
+
502
+ // since 2.5.5: no more logging of spam comments
503
+ if (isset($comment_approved) && 'spam' === $comment_approved) {
504
+ return;
505
+ }
506
+
507
+ $comment_data = get_comment($comment_ID);
508
+
509
+ $message = '';
510
+
511
+ if ($comment_data->user_id) {
512
+ // comment was from a logged in user
513
+ $message = "user_{$context["comment_type"]}_added";
514
+ } else {
515
+ // comment was from a non-logged in user
516
+ $message = "anon_{$context["comment_type"]}_added";
517
+ $context['_initiator'] = SimpleLoggerLogInitiators::WEB_USER;
518
+
519
+ // add occasions if comment is considered spam
520
+ // if not added, spam comments can easily flood the log
521
+ if (isset($comment_data->comment_approved) && 'spam' === $comment_data->comment_approved) {
522
+ $context['_occasionsID'] = __CLASS__ . '/' . __FUNCTION__ . "/anon_{$context["comment_type"]}_added/type:spam";
523
+ }
524
+ }
525
+
526
+ $this->infoMessage(
527
+ $message,
528
+ $context
529
+ );
530
+ }
531
+
532
+
533
+ /**
534
+ * Modify plain output to inlcude link to post
535
+ * and link to comment
536
+ */
537
+ public function getLogRowPlainTextOutput($row)
538
+ {
539
+
540
+ $message = $row->message;
541
+ $context = $row->context;
542
+ $message_key = $context['_message_key'];
543
+
544
+ // Message is untranslated here, so get translated text
545
+ // Can't call parent __FUNCTION__ because it will interpolate too, which we don't want
546
+ if (! empty($message_key)) {
547
+ $message = $this->messages[ $message_key ]['translated_text'];
548
+ }
549
+
550
+ // Wrap links around {comment_post_title}
551
+ $comment_post_ID = isset($context['comment_post_ID']) ? (int) $context['comment_post_ID'] : null;
552
+ if ($comment_post_ID && $comment_post = get_post($comment_post_ID)) {
553
+ $edit_post_link = get_edit_post_link($comment_post_ID);
554
+
555
+ if ($edit_post_link) {
556
+ $message = str_replace(
557
+ '"{comment_post_title}"',
558
+ "<a href='{$edit_post_link}'>\"{comment_post_title}\"</a>",
559
+ $message
560
+ );
561
+ }
562
+ }
563
+
564
+ return $this->interpolate($message, $context, $row);
565
+ }
566
+
567
+
568
+ /**
569
+ * Get output for detailed log section
570
+ */
571
+ function getLogRowDetailsOutput($row)
572
+ {
573
+
574
+ $context = $row->context;
575
+ $message_key = $context['_message_key'];
576
+ $output = '';
577
+ // print_r($row);exit;
578
+ /*
579
+ if ( 'spam' !== $commentdata['comment_approved'] ) { // If it's spam save it silently for later crunching
580
+ if ( '0' == $commentdata['comment_approved'] ) { // comment not spam, but not auto-approved
581
+ wp_notify_moderator( $comment_ID );
582
+ */
583
+ /*
584
+ if ( isset( $context["comment_approved"] ) && $context["comment_approved"] == '0' ) {
585
+ $output .= "<br>comment was automatically approved";
586
+ } else {
587
+ $output .= "<br>comment was not automatically approved";
588
+ }*/
589
+
590
+ $comment_text = '';
591
+ if (isset($context['comment_content']) && $context['comment_content']) {
592
+ $comment_text = $context['comment_content'];
593
+ $comment_text = wp_trim_words($comment_text, 20);
594
+ $comment_text = wpautop($comment_text);
595
+ }
596
+
597
+ // Keys to show
598
+ $arr_plugin_keys = array();
599
+ $comment_type = isset($context['comment_type']) ? $context['comment_type'] : '';
600
+
601
+ switch ($comment_type) {
602
+ case 'trackback';
603
+
604
+ $arr_plugin_keys = array(
605
+ 'trackback_status' => _x('Status', 'comments logger - detailed output comment status', 'simple-history'),
606
+ // "trackback_type" => _x("Trackback type", "comments logger - detailed output comment type", "simple-history"),
607
+ 'trackback_author' => _x('Name', 'comments logger - detailed output author', 'simple-history'),
608
+ 'trackback_author_email' => _x('Email', 'comments logger - detailed output email', 'simple-history'),
609
+ 'trackback_content' => _x('Content', 'comments logger - detailed output content', 'simple-history'),
610
+ );
611
+
612
+ break;
613
+
614
+ case 'pingback';
615
+
616
+ $arr_plugin_keys = array(
617
+
618
+ 'pingback_status' => _x('Status', 'comments logger - detailed output comment status', 'simple-history'),
619
+ // "pingback_type" => _x("Pingback type", "comments logger - detailed output comment type", "simple-history"),
620
+ 'pingback_author' => _x('Name', 'comments logger - detailed output author', 'simple-history'),
621
+ 'pingback_author_email' => _x('Email', 'comments logger - detailed output email', 'simple-history'),
622
+ 'pingback_content' => _x('Content', 'comments logger - detailed output content', 'simple-history'),
623
+
624
+ );
625
+
626
+ break;
627
+
628
+ case 'comment';
629
+ default;
630
+
631
+ $arr_plugin_keys = array(
632
+ 'comment_status' => _x('Status', 'comments logger - detailed output comment status', 'simple-history'),
633
+ // "comment_type" => _x("Comment type", "comments logger - detailed output comment type", "simple-history"),
634
+ 'comment_author' => _x('Name', 'comments logger - detailed output author', 'simple-history'),
635
+ 'comment_author_email' => _x('Email', 'comments logger - detailed output email', 'simple-history'),
636
+ 'comment_content' => _x('Comment', 'comments logger - detailed output content', 'simple-history'),
637
+ );
638
+
639
+ break;
640
+
641
+ // "comment_author_url" => _x("Author URL", "comments logger - detailed output author", "simple-history"),
642
+ // "comment_author_IP" => _x("IP number", "comments logger - detailed output IP", "simple-history"),
643
+ }// End switch().
644
+
645
+ $arr_plugin_keys = apply_filters('simple_history/comments_logger/row_details_plugin_info_keys', $arr_plugin_keys);
646
+
647
+ // Start output of plugin meta data table
648
+ $output .= "<table class='SimpleHistoryLogitem__keyValueTable'>";
649
+
650
+ foreach ($arr_plugin_keys as $key => $desc) {
651
+ switch ($key) {
652
+ case 'comment_content':
653
+ case 'trackback_content':
654
+ case 'pingback_content':
655
+ $desc_output = $comment_text;
656
+ break;
657
+
658
+ case 'comment_author':
659
+ case 'trackback_author':
660
+ case 'pingback_author':
661
+ $desc_output = '';
662
+
663
+ if (isset($context[ $key ])) {
664
+ $desc_output .= esc_html($context[ $key ]);
665
+ }
666
+
667
+ /*
668
+ if ( isset( $context["comment_author_email"] ) ) {
669
+
670
+ $gravatar_email = $context["comment_author_email"];
671
+ $avatar = $this->simpleHistory->get_avatar( $gravatar_email, 14, "blank" );
672
+ $desc_output .= "<span class='SimpleCommentsLogger__gravatar'>{$avatar}</span>";
673
+
674
+ }
675
+ */
676
+
677
+ break;
678
+
679
+ case 'comment_status':
680
+ case 'trackback_status':
681
+ case 'pingback_status':
682
+ if (isset($context['comment_approved'])) {
683
+ if ($context['comment_approved'] === 'spam') {
684
+ $desc_output = __('Spam', 'simple-history');
685
+ } elseif ($context['comment_approved'] == 1) {
686
+ $desc_output = __('Approved', 'simple-history');
687
+ } elseif ($context['comment_approved'] == 0) {
688
+ $desc_output = __('Pending', 'simple-history');
689
+ }
690
+ }
691
+
692
+ break;
693
+
694
+ case 'comment_type':
695
+ case 'trackback_type':
696
+ case 'pingback_type':
697
+ if (isset($context['comment_type'])) {
698
+ if ($context['comment_type'] === 'trackback') {
699
+ $desc_output = __('Trackback', 'simple-history');
700
+ } elseif ($context['comment_type'] === 'pingback') {
701
+ $desc_output = __('Pingback', 'simple-history');
702
+ } elseif ($context['comment_type'] === 'comment') {
703
+ $desc_output = __('Comment', 'simple-history');
704
+ } else {
705
+ $desc_output = '';
706
+ }
707
+ }
708
+
709
+ break;
710
+
711
+ default;
712
+
713
+ if (isset($context[ $key ])) {
714
+ $desc_output = esc_html($context[ $key ]);
715
+ }
716
+
717
+ break;
718
+ }// End switch().
719
+
720
+ // Skip empty rows
721
+ if (empty($desc_output)) {
722
+ continue;
723
+ }
724
+
725
+ $output .= sprintf(
726
+ '
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
727
  <tr>
728
  <td>%1$s</td>
729
  <td>%2$s</td>
730
  </tr>
731
  ',
732
+ esc_html($desc),
733
+ $desc_output
734
+ );
735
+ }// End foreach().
736
+
737
+ // Add link to edit comment
738
+ $comment_ID = isset($context['comment_ID']) && is_numeric($context['comment_ID']) ? (int) $context['comment_ID'] : false;
739
+
740
+ if ($comment_ID) {
741
+ $comment = get_comment($comment_ID);
742
+
743
+ if ($comment) {
744
+ // http://site.local/wp/wp-admin/comment.php?action=editcomment&c=
745
+ $edit_comment_link = get_edit_comment_link($comment_ID);
746
+
747
+ // Edit link sometimes does not contain comment ID
748
+ // Probably because comment has been removed or something
749
+ // So only continue if link does not end with "=""
750
+ if ($edit_comment_link && $edit_comment_link[ strlen($edit_comment_link) -1 ] !== '=') {
751
+ $output .= sprintf(
752
+ '
 
 
 
 
753
  <tr>
754
  <td></td>
755
  <td><a href="%2$s">%1$s</a></td>
756
  </tr>
757
  ',
758
+ _x('View/Edit', 'comments logger - edit comment', 'simple-history'),
759
+ $edit_comment_link
760
+ );
761
+ }
762
+ }
763
+ } // End if().
764
+
765
+ // End table
766
+ $output .= '</table>';
767
+
768
+ return $output;
769
+ }
770
+
771
+ function adminCSS()
772
+ {
773
+ ?>
774
+ <style>
775
+ .SimpleCommentsLogger__gravatar {
776
+ line-height: 1;
777
+ border-radius: 50%;
778
+ overflow: hidden;
779
+ margin-right: .5em;
780
+ margin-left: .5em;
781
+ display: inline-block;
782
+ }
783
+ .SimpleCommentsLogger__gravatar img {
784
+ display: block;
785
+ }
786
+ </style>
787
+ <?php
788
+ }
 
 
789
  }
loggers/SimpleCoreUpdatesLogger.php CHANGED
@@ -1,115 +1,116 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /**
6
  * Logs WordPress core updates
7
  */
8
- class SimpleCoreUpdatesLogger extends SimpleLogger {
9
-
10
- public $slug = __CLASS__;
11
-
12
- public function loaded() {
13
-
14
- add_action( '_core_updated_successfully', array( $this, 'on_core_updated' ) );
15
- add_action( 'update_feedback', array( $this, 'on_update_feedback' ) );
16
-
17
- // Can't log db updates at the moment, because loaded() is not called yet when the action fires
18
- // add_action( 'wp_upgrade', array( $this, "on_wp_upgrade" ), 10, 2 );
19
- }
20
-
21
- /**
22
- * Fires after a site is fully upgraded.
23
- * The database, that is.
24
- *
25
- * @param int $wp_db_version The new $wp_db_version.
26
- * @param int $wp_current_db_version The old (current) $wp_db_version.
27
- */
28
- function on_wp_upgrade( $wp_db_version, $wp_current_db_version ) {
29
-
30
- $this->debugMessage(
31
- 'core_db_version_updated',
32
- array(
33
- 'new_version' => $wp_db_version,
34
- 'prev_version' => $wp_current_db_version,
35
- )
36
- );
37
-
38
- }
39
-
40
- /**
41
- * We need to store the WordPress version we are updating from.
42
- * 'update_feedback' is a suitable filter.
43
- */
44
- function on_update_feedback() {
45
-
46
- if ( ! empty( $GLOBALS['wp_version'] ) && ! isset( $GLOBALS[ 'simple_history_' . $this->slug . '_wp_version' ] ) ) {
47
- $GLOBALS[ 'simple_history_' . $this->slug . '_wp_version' ] = $GLOBALS['wp_version'];
48
- }
49
-
50
- }
51
-
52
- /**
53
- * Get array with information about this logger
54
- *
55
- * @return array
56
- */
57
- function getInfo() {
58
-
59
- $arr_info = array(
60
- 'name' => 'Core Updates Logger',
61
- 'description' => 'Logs the update of WordPress (manual and automatic updates)',
62
- 'capability' => 'update_core',
63
- 'messages' => array(
64
- 'core_updated' => __( 'Updated WordPress to {new_version} from {prev_version}', 'simple-history' ),
65
- 'core_auto_updated' => __( 'WordPress auto-updated to {new_version} from {prev_version}', 'simple-history' ),
66
- 'core_db_version_updated' => __( 'WordPress database version updated to {new_version} from {prev_version}', 'simple-history' ),
67
- ),
68
- 'labels' => array(
69
- 'search' => array(
70
- 'label' => _x( 'WordPress Core', 'User logger: search', 'simple-history' ),
71
- 'options' => array(
72
- _x( 'WordPress core updates', 'User logger: search', 'simple-history' ) => array(
73
- 'core_updated',
74
- 'core_auto_updated',
75
- ),
76
- ),
77
- ),// end search array
78
- ),// end labels
79
- );
80
-
81
- return $arr_info;
82
-
83
- }
84
-
85
- /**
86
- * Called when WordPress is updated
87
- *
88
- * @param string $new_wp_version
89
- */
90
- public function on_core_updated( $new_wp_version ) {
91
-
92
- $old_wp_version = empty( $GLOBALS[ 'simple_history_' . $this->slug . '_wp_version' ] ) ? $GLOBALS['wp_version'] : $GLOBALS[ 'simple_history_' . $this->slug . '_wp_version' ];
93
-
94
- $auto_update = true;
95
- if ( $GLOBALS['pagenow'] == 'update-core.php' ) {
96
- $auto_update = false;
97
- }
98
-
99
- if ( $auto_update ) {
100
- $message = 'core_auto_updated';
101
- } else {
102
- $message = 'core_updated';
103
- }
104
-
105
- $this->noticeMessage(
106
- $message,
107
- array(
108
- 'prev_version' => $old_wp_version,
109
- 'new_version' => $new_wp_version,
110
- )
111
- );
112
-
113
- }
114
-
 
115
  }
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logs WordPress core updates
7
  */
8
+ class SimpleCoreUpdatesLogger extends SimpleLogger
9
+ {
10
+
11
+ public $slug = __CLASS__;
12
+
13
+ public function loaded()
14
+ {
15
+
16
+ add_action('_core_updated_successfully', array( $this, 'on_core_updated' ));
17
+ add_action('update_feedback', array( $this, 'on_update_feedback' ));
18
+
19
+ // Can't log db updates at the moment, because loaded() is not called yet when the action fires
20
+ // add_action( 'wp_upgrade', array( $this, "on_wp_upgrade" ), 10, 2 );
21
+ }
22
+
23
+ /**
24
+ * Fires after a site is fully upgraded.
25
+ * The database, that is.
26
+ *
27
+ * @param int $wp_db_version The new $wp_db_version.
28
+ * @param int $wp_current_db_version The old (current) $wp_db_version.
29
+ */
30
+ function on_wp_upgrade($wp_db_version, $wp_current_db_version)
31
+ {
32
+
33
+ $this->debugMessage(
34
+ 'core_db_version_updated',
35
+ array(
36
+ 'new_version' => $wp_db_version,
37
+ 'prev_version' => $wp_current_db_version,
38
+ )
39
+ );
40
+ }
41
+
42
+ /**
43
+ * We need to store the WordPress version we are updating from.
44
+ * 'update_feedback' is a suitable filter.
45
+ */
46
+ function on_update_feedback()
47
+ {
48
+
49
+ if (! empty($GLOBALS['wp_version']) && ! isset($GLOBALS[ 'simple_history_' . $this->slug . '_wp_version' ])) {
50
+ $GLOBALS[ 'simple_history_' . $this->slug . '_wp_version' ] = $GLOBALS['wp_version'];
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Get array with information about this logger
56
+ *
57
+ * @return array
58
+ */
59
+ function getInfo()
60
+ {
61
+
62
+ $arr_info = array(
63
+ 'name' => 'Core Updates Logger',
64
+ 'description' => 'Logs the update of WordPress (manual and automatic updates)',
65
+ 'capability' => 'update_core',
66
+ 'messages' => array(
67
+ 'core_updated' => __('Updated WordPress to {new_version} from {prev_version}', 'simple-history'),
68
+ 'core_auto_updated' => __('WordPress auto-updated to {new_version} from {prev_version}', 'simple-history'),
69
+ 'core_db_version_updated' => __('WordPress database version updated to {new_version} from {prev_version}', 'simple-history'),
70
+ ),
71
+ 'labels' => array(
72
+ 'search' => array(
73
+ 'label' => _x('WordPress Core', 'User logger: search', 'simple-history'),
74
+ 'options' => array(
75
+ _x('WordPress core updates', 'User logger: search', 'simple-history') => array(
76
+ 'core_updated',
77
+ 'core_auto_updated',
78
+ ),
79
+ ),
80
+ ),// end search array
81
+ ),// end labels
82
+ );
83
+
84
+ return $arr_info;
85
+ }
86
+
87
+ /**
88
+ * Called when WordPress is updated
89
+ *
90
+ * @param string $new_wp_version
91
+ */
92
+ public function on_core_updated($new_wp_version)
93
+ {
94
+
95
+ $old_wp_version = empty($GLOBALS[ 'simple_history_' . $this->slug . '_wp_version' ]) ? $GLOBALS['wp_version'] : $GLOBALS[ 'simple_history_' . $this->slug . '_wp_version' ];
96
+
97
+ $auto_update = true;
98
+ if ($GLOBALS['pagenow'] == 'update-core.php') {
99
+ $auto_update = false;
100
+ }
101
+
102
+ if ($auto_update) {
103
+ $message = 'core_auto_updated';
104
+ } else {
105
+ $message = 'core_updated';
106
+ }
107
+
108
+ $this->noticeMessage(
109
+ $message,
110
+ array(
111
+ 'prev_version' => $old_wp_version,
112
+ 'new_version' => $new_wp_version,
113
+ )
114
+ );
115
+ }
116
  }
loggers/SimpleExportLogger.php CHANGED
@@ -1,75 +1,75 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /**
6
  * Logs WordPress exports
7
  */
8
- class SimpleExportLogger extends SimpleLogger {
9
-
10
-
11
- public $slug = __CLASS__;
12
-
13
- /**
14
- * Get array with information about this logger
15
- *
16
- * @return array
17
- */
18
- function getInfo() {
19
-
20
- $arr_info = array(
21
- 'name' => __( 'Export Logger', 'simple-history' ),
22
- 'description' => __( 'Logs updates to WordPress export', 'simple-history' ),
23
- 'capability' => 'export',
24
- 'messages' => array(
25
- 'created_export' => __( 'Created XML export', 'simple-history' ),
26
- ),
27
- 'labels' => array(
28
- 'search' => array(
29
- 'label' => _x( 'Export', 'Export logger: search', 'simple-history' ),
30
- 'options' => array(
31
- _x( 'Created exports', 'Export logger: search', 'simple-history' ) => array(
32
- 'created_export'
33
- ),
34
- ),
35
- ),// end search array
36
- ),// end labels
37
- );
38
-
39
- return $arr_info;
40
-
41
- }
42
-
43
- function loaded() {
44
-
45
- add_action( 'export_wp', array( $this, 'on_export_wp' ), 10, 1 );
46
-
47
- }
48
-
49
- function on_export_wp( $args ) {
50
-
51
- $this->infoMessage(
52
- 'created_export',
53
- array(
54
- 'args' => $this->simpleHistory->json_encode( $args ),
55
- )
56
- );
57
-
58
- }
59
-
60
- /**
61
- * Get detailed output
62
- */
63
- /*
64
- function getLogRowDetailsOutput($row) {
65
-
66
- $context = $row->context;
67
- $message_key = $context["_message_key"];
68
- $output = "";
69
-
70
- return $output;
71
-
72
- }
73
- */
74
-
75
  }
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logs WordPress exports
7
  */
8
+ class SimpleExportLogger extends SimpleLogger
9
+ {
10
+
11
+
12
+ public $slug = __CLASS__;
13
+
14
+ /**
15
+ * Get array with information about this logger
16
+ *
17
+ * @return array
18
+ */
19
+ function getInfo()
20
+ {
21
+
22
+ $arr_info = array(
23
+ 'name' => __('Export Logger', 'simple-history'),
24
+ 'description' => __('Logs updates to WordPress export', 'simple-history'),
25
+ 'capability' => 'export',
26
+ 'messages' => array(
27
+ 'created_export' => __('Created XML export', 'simple-history'),
28
+ ),
29
+ 'labels' => array(
30
+ 'search' => array(
31
+ 'label' => _x('Export', 'Export logger: search', 'simple-history'),
32
+ 'options' => array(
33
+ _x('Created exports', 'Export logger: search', 'simple-history') => array(
34
+ 'created_export'
35
+ ),
36
+ ),
37
+ ),// end search array
38
+ ),// end labels
39
+ );
40
+
41
+ return $arr_info;
42
+ }
43
+
44
+ function loaded()
45
+ {
46
+
47
+ add_action('export_wp', array( $this, 'on_export_wp' ), 10, 1);
48
+ }
49
+
50
+ function on_export_wp($args)
51
+ {
52
+
53
+ $this->infoMessage(
54
+ 'created_export',
55
+ array(
56
+ 'args' => $this->simpleHistory->json_encode($args),
57
+ )
58
+ );
59
+ }
60
+
61
+ /**
62
+ * Get detailed output
63
+ */
64
+ /*
65
+ function getLogRowDetailsOutput($row) {
66
+
67
+ $context = $row->context;
68
+ $message_key = $context["_message_key"];
69
+ $output = "";
70
+
71
+ return $output;
72
+
73
+ }
74
+ */
75
  }
loggers/SimpleLegacyLogger.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /**
6
  * Logger for events stored earlier than v2
@@ -8,100 +8,98 @@ defined( 'ABSPATH' ) or die();
8
  *
9
  * @since 2.0
10
  */
11
- class SimpleLegacyLogger extends SimpleLogger {
12
-
13
-
14
- /**
15
- * Unique slug for this logger
16
- * Will be saved in DB and used to associate each log row with its logger
17
- */
18
- public $slug = 'SimpleLegacyLogger';
19
-
20
- public function __construct() {
21
-
22
- // $this->info(__CLASS__ . " construct()");
23
- }
24
-
25
- /**
26
- * Get array with information about this logger
27
- *
28
- * @return array
29
- */
30
- function getInfo() {
31
-
32
- $arr_info = array(
33
- 'name' => 'Legacy Logger',
34
- 'description' => 'Formats old events',
35
- 'capability' => 'edit_pages',
36
- 'messages' => array(),
37
- /*
38
- "labels" => array(
39
- "search" => array(
40
- "label" => _x("Export", "Export logger: search", "simple-history"),
41
- "options" => array(
42
- _x("Exports created", "Core updates logger: search", "simple-history") => array(
43
- "created_export"
 
 
 
44
  ),
45
- )
46
- ) // end search array
47
- ) // end labels
48
- */
49
 
50
- );
51
 
52
- return $arr_info;
 
53
 
54
- }
 
55
 
56
- public function getLogRowPlainTextOutput( $row ) {
 
57
 
58
- $message = $row->message;
59
- $context = $row->context;
60
 
61
- $out = '';
62
 
63
- global $wpdb;
64
-
65
- // Get old columns for this event
66
- $sql = sprintf('
67
  SELECT * FROM %1$s
68
  WHERE id = %2$d
69
  ',
70
- $wpdb->prefix . SimpleHistory::DBTABLE,
71
- $row->id
72
- );
73
-
74
- $one_item = $wpdb->get_row( $sql );
75
-
76
- // $out .= print_r($row, true);
77
- // Code mostly from version 1.x
78
- $object_type = ucwords( $one_item->object_type );
79
- $object_name = esc_html( $one_item->object_name );
80
- $user = get_user_by( 'id', $one_item->user_id );
81
- $user_nicename = esc_html( @$user->user_nicename );
82
- $user_email = esc_html( @$user->user_email );
83
- $description = '';
84
-
85
- if ( $user_nicename ) {
86
- $description .= sprintf( __( 'By %s', 'simple-history' ), $user_nicename );
87
 
88
- }
89
 
90
- if ( isset( $one_item->occasions ) ) {
91
- $description .= sprintf( __( '%d occasions', 'simple-history' ), sizeof( $one_item->occasions ) );
 
 
 
 
 
 
92
 
93
- }
 
 
94
 
95
- $item_title = esc_html( $object_type ) . ' "' . esc_html( $object_name ) . "\" {$one_item->action}";
96
- $item_title = html_entity_decode( $item_title, ENT_COMPAT, 'UTF-8' );
 
97
 
98
- $out .= "$item_title";
99
- $out .= "<br>$description";
100
-
101
- return $out;
102
-
103
- }
104
 
 
 
105
 
 
 
106
  }
107
-
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logger for events stored earlier than v2
8
  *
9
  * @since 2.0
10
  */
11
+ class SimpleLegacyLogger extends SimpleLogger
12
+ {
13
+
14
+
15
+ /**
16
+ * Unique slug for this logger
17
+ * Will be saved in DB and used to associate each log row with its logger
18
+ */
19
+ public $slug = 'SimpleLegacyLogger';
20
+
21
+ public function __construct()
22
+ {
23
+
24
+ // $this->info(__CLASS__ . " construct()");
25
+ }
26
+
27
+ /**
28
+ * Get array with information about this logger
29
+ *
30
+ * @return array
31
+ */
32
+ function getInfo()
33
+ {
34
+
35
+ $arr_info = array(
36
+ 'name' => 'Legacy Logger',
37
+ 'description' => 'Formats old events',
38
+ 'capability' => 'edit_pages',
39
+ 'messages' => array(),
40
+ /*
41
+ "labels" => array(
42
+ "search" => array(
43
+ "label" => _x("Export", "Export logger: search", "simple-history"),
44
+ "options" => array(
45
+ _x("Exports created", "Core updates logger: search", "simple-history") => array(
46
+ "created_export"
47
  ),
48
+ )
49
+ ) // end search array
50
+ ) // end labels
51
+ */
52
 
53
+ );
54
 
55
+ return $arr_info;
56
+ }
57
 
58
+ public function getLogRowPlainTextOutput($row)
59
+ {
60
 
61
+ $message = $row->message;
62
+ $context = $row->context;
63
 
64
+ $out = '';
 
65
 
66
+ global $wpdb;
67
 
68
+ // Get old columns for this event
69
+ $sql = sprintf(
70
+ '
 
71
  SELECT * FROM %1$s
72
  WHERE id = %2$d
73
  ',
74
+ $wpdb->prefix . SimpleHistory::DBTABLE,
75
+ $row->id
76
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
+ $one_item = $wpdb->get_row($sql);
79
 
80
+ // $out .= print_r($row, true);
81
+ // Code mostly from version 1.x
82
+ $object_type = ucwords($one_item->object_type);
83
+ $object_name = esc_html($one_item->object_name);
84
+ $user = get_user_by('id', $one_item->user_id);
85
+ $user_nicename = esc_html(@$user->user_nicename);
86
+ $user_email = esc_html(@$user->user_email);
87
+ $description = '';
88
 
89
+ if ($user_nicename) {
90
+ $description .= sprintf(__('By %s', 'simple-history'), $user_nicename);
91
+ }
92
 
93
+ if (isset($one_item->occasions)) {
94
+ $description .= sprintf(__('%d occasions', 'simple-history'), sizeof($one_item->occasions));
95
+ }
96
 
97
+ $item_title = esc_html($object_type) . ' "' . esc_html($object_name) . "\" {$one_item->action}";
98
+ $item_title = html_entity_decode($item_title, ENT_COMPAT, 'UTF-8');
 
 
 
 
99
 
100
+ $out .= "$item_title";
101
+ $out .= "<br>$description";
102
 
103
+ return $out;
104
+ }
105
  }
 
loggers/SimpleLogger.php CHANGED
@@ -86,7 +86,7 @@ class SimpleLogger
86
  *
87
  * @return array
88
  */
89
- function getInfo()
90
  {
91
  $arr_info = array(
92
  // The logger slug. Defaulting to the class name is nice and logical I think
@@ -115,7 +115,7 @@ class SimpleLogger
115
  * @since 2.5.4
116
  * @return Mixed
117
  */
118
- function getInfoValueByKey($key)
119
  {
120
  $arr_info = $this->getInfo();
121
 
@@ -147,7 +147,7 @@ class SimpleLogger
147
  * @param array $context
148
  * @param array $row Currently not always passed, because loggers need to be updated to support this...
149
  */
150
- function interpolate($message, $context = array(), $row = null)
151
  {
152
  if (!is_array($context)) {
153
  return $message;
@@ -207,7 +207,7 @@ class SimpleLogger
207
  *
208
  * @return string HTML
209
  */
210
- function getLogRowHeaderOutput($row)
211
  {
212
  // HTML for initiator
213
  $initiator_html = '';
@@ -473,21 +473,18 @@ class SimpleLogger
473
  $local_date_format = $time_format;
474
 
475
  // Show local time as date and hours when event is a bit older.
476
- if (
477
- $time_current - HOUR_IN_SECONDS * 6 >
478
  $date_datetime->getTimestamp()
479
  ) {
480
  $local_date_format = $date_and_time_format;
481
  }
482
 
483
- if (
484
- $time_current - $date_datetime->getTimestamp() <=
485
  $time_ago_just_now_max_time
486
  ) {
487
  // Show "just now" if event is very recent.
488
  $str_when = __('Just now', 'simple-history');
489
- } elseif (
490
- $time_current - $date_datetime->getTimestamp() >
491
  $time_ago_max_time
492
  ) {
493
  /* Translators: Date format for log row header, see http://php.net/date */
@@ -509,8 +506,8 @@ class SimpleLogger
509
  );
510
  }
511
 
512
- $item_permalink = admin_url('index.php?page=simple_history_page');
513
- if (!empty($row->id)) {
514
  $item_permalink .= "#item/{$row->id}";
515
  }
516
 
@@ -1293,8 +1290,7 @@ class SimpleLogger
1293
  } // End if().
1294
 
1295
  // Detect XML-RPC calls and append to context, if not already there.
1296
- if (
1297
- defined('XMLRPC_REQUEST') &&
1298
  XMLRPC_REQUEST &&
1299
  !isset($context['_xmlrpc_request'])
1300
  ) {
@@ -1386,8 +1382,7 @@ class SimpleLogger
1386
  true
1387
  );
1388
 
1389
- if (
1390
- $anonymize_ip_address &&
1391
  function_exists('wp_privacy_anonymize_ip')
1392
  ) {
1393
  $remote_addr = wp_privacy_anonymize_ip($remote_addr);
@@ -1419,8 +1414,7 @@ class SimpleLogger
1419
  // valid, add to context, with loop index appended so we can store many IPs.
1420
  $key_lower = strtolower($key);
1421
 
1422
- if (
1423
- $anonymize_ip_address &&
1424
  function_exists('wp_privacy_anonymize_ip')
1425
  ) {
1426
  $ip = wp_privacy_anonymize_ip($ip);
@@ -1438,8 +1432,7 @@ class SimpleLogger
1438
  } // End if().
1439
 
1440
  // Append http referer.
1441
- if (
1442
- !isset($context['_server_http_referer']) &&
1443
  isset($_SERVER['HTTP_REFERER'])
1444
  ) {
1445
  $context['_server_http_referer'] = $_SERVER['HTTP_REFERER'];
@@ -1552,7 +1545,7 @@ class SimpleLogger
1552
  * @param array $row Row with info.
1553
  * @return array Headers
1554
  */
1555
- function get_event_ip_number_headers($row)
1556
  {
1557
  $ip_keys = $this->get_ip_number_header_keys();
1558
  $arr_found_additional_ip_headers = array();
@@ -1586,10 +1579,9 @@ class SimpleLogger
1586
  * @param string $ip IP number.
1587
  * @return bool
1588
  */
1589
- function validate_ip($ip)
1590
  {
1591
- if (
1592
- filter_var(
1593
  $ip,
1594
  FILTER_VALIDATE_IP,
1595
  FILTER_FLAG_IPV4 |
@@ -1608,7 +1600,7 @@ class SimpleLogger
1608
  * The CSS that you output will only be outputed
1609
  * on pages where Simple History is used.
1610
  */
1611
- function adminCSS()
1612
  {
1613
  /*
1614
  ?>
@@ -1626,7 +1618,7 @@ class SimpleLogger
1626
  * The JS that you output will only be outputed
1627
  * on pages where Simple History is used.
1628
  */
1629
- function adminJS()
1630
  {
1631
  /*
1632
  ?>
86
  *
87
  * @return array
88
  */
89
+ public function getInfo()
90
  {
91
  $arr_info = array(
92
  // The logger slug. Defaulting to the class name is nice and logical I think
115
  * @since 2.5.4
116
  * @return Mixed
117
  */
118
+ public function getInfoValueByKey($key)
119
  {
120
  $arr_info = $this->getInfo();
121
 
147
  * @param array $context
148
  * @param array $row Currently not always passed, because loggers need to be updated to support this...
149
  */
150
+ public function interpolate($message, $context = array(), $row = null)
151
  {
152
  if (!is_array($context)) {
153
  return $message;
207
  *
208
  * @return string HTML
209
  */
210
+ public function getLogRowHeaderOutput($row)
211
  {
212
  // HTML for initiator
213
  $initiator_html = '';
473
  $local_date_format = $time_format;
474
 
475
  // Show local time as date and hours when event is a bit older.
476
+ if ($time_current - HOUR_IN_SECONDS * 6 >
 
477
  $date_datetime->getTimestamp()
478
  ) {
479
  $local_date_format = $date_and_time_format;
480
  }
481
 
482
+ if ($time_current - $date_datetime->getTimestamp() <=
 
483
  $time_ago_just_now_max_time
484
  ) {
485
  // Show "just now" if event is very recent.
486
  $str_when = __('Just now', 'simple-history');
487
+ } elseif ($time_current - $date_datetime->getTimestamp() >
 
488
  $time_ago_max_time
489
  ) {
490
  /* Translators: Date format for log row header, see http://php.net/date */
506
  );
507
  }
508
 
509
+ $item_permalink = admin_url( apply_filters( 'simple_history/admin_location', 'index' ) . '.php?page=simple_history_page' );
510
+ if ( ! empty( $row->id ) ) {
511
  $item_permalink .= "#item/{$row->id}";
512
  }
513
 
1290
  } // End if().
1291
 
1292
  // Detect XML-RPC calls and append to context, if not already there.
1293
+ if (defined('XMLRPC_REQUEST') &&
 
1294
  XMLRPC_REQUEST &&
1295
  !isset($context['_xmlrpc_request'])
1296
  ) {
1382
  true
1383
  );
1384
 
1385
+ if ($anonymize_ip_address &&
 
1386
  function_exists('wp_privacy_anonymize_ip')
1387
  ) {
1388
  $remote_addr = wp_privacy_anonymize_ip($remote_addr);
1414
  // valid, add to context, with loop index appended so we can store many IPs.
1415
  $key_lower = strtolower($key);
1416
 
1417
+ if ($anonymize_ip_address &&
 
1418
  function_exists('wp_privacy_anonymize_ip')
1419
  ) {
1420
  $ip = wp_privacy_anonymize_ip($ip);
1432
  } // End if().
1433
 
1434
  // Append http referer.
1435
+ if (!isset($context['_server_http_referer']) &&
 
1436
  isset($_SERVER['HTTP_REFERER'])
1437
  ) {
1438
  $context['_server_http_referer'] = $_SERVER['HTTP_REFERER'];
1545
  * @param array $row Row with info.
1546
  * @return array Headers
1547
  */
1548
+ public function get_event_ip_number_headers($row)
1549
  {
1550
  $ip_keys = $this->get_ip_number_header_keys();
1551
  $arr_found_additional_ip_headers = array();
1579
  * @param string $ip IP number.
1580
  * @return bool
1581
  */
1582
+ public function validate_ip($ip)
1583
  {
1584
+ if (filter_var(
 
1585
  $ip,
1586
  FILTER_VALIDATE_IP,
1587
  FILTER_FLAG_IPV4 |
1600
  * The CSS that you output will only be outputed
1601
  * on pages where Simple History is used.
1602
  */
1603
+ public function adminCSS()
1604
  {
1605
  /*
1606
  ?>
1618
  * The JS that you output will only be outputed
1619
  * on pages where Simple History is used.
1620
  */
1621
+ public function adminJS()
1622
  {
1623
  /*
1624
  ?>
loggers/SimpleMediaLogger.php CHANGED
@@ -1,374 +1,351 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /**
6
  * Logs media uploads
7
  */
8
- class SimpleMediaLogger extends SimpleLogger {
9
-
10
-
11
- public $slug = 'SimpleMediaLogger';
12
-
13
- /**
14
- * Get array with information about this logger
15
- *
16
- * @return array
17
- */
18
- function getInfo() {
19
-
20
- $arr_info = array(
21
- 'name' => 'Media/Attachments Logger',
22
- 'description' => 'Logs media uploads and edits',
23
- 'capability' => 'edit_pages',
24
- 'messages' => array(
25
- 'attachment_created' => __( 'Created {post_type} "{attachment_title}"', 'simple-history' ),
26
- 'attachment_updated' => __( 'Edited {post_type} "{attachment_title}"', 'simple-history' ),
27
- 'attachment_deleted' => __( 'Deleted {post_type} "{attachment_title}" ("{attachment_filename}")', 'simple-history' ),
28
- ),
29
- 'labels' => array(
30
- 'search' => array(
31
- 'label' => _x( 'Media', 'Media logger: search', 'simple-history' ),
32
- 'label_all' => _x( 'All media activity', 'Media logger: search', 'simple-history' ),
33
- 'options' => array(
34
- _x( 'Added media', 'Media logger: search', 'simple-history' ) => array(
35
- 'attachment_created'
36
- ),
37
- _x( 'Updated media', 'Media logger: search', 'simple-history' ) => array(
38
- 'attachment_updated'
39
- ),
40
- _x( 'Deleted media', 'Media logger: search', 'simple-history' ) => array(
41
- 'attachment_deleted'
42
- ),
43
- ),
44
- ),// end search array
45
- ),// end labels
46
- );
47
-
48
- return $arr_info;
49
-
50
- }
51
-
52
- public function loaded() {
53
-
54
- add_action( 'admin_init', array( $this, 'on_admin_init' ) );
55
-
56
- add_action( 'xmlrpc_call_success_mw_newMediaObject', array( $this, 'on_mw_newMediaObject' ), 10, 2 );
57
-
58
- add_filter( 'simple_history/rss_item_link', array( $this, 'filter_rss_item_link' ), 10, 2 );
59
-
60
- }
61
-
62
- function on_admin_init() {
63
-
64
- add_action( 'add_attachment', array( $this, 'on_add_attachment' ) );
65
- add_action( 'edit_attachment', array( $this, 'on_edit_attachment' ) );
66
- add_action( 'delete_attachment', array( $this, 'on_delete_attachment' ) );
67
-
68
- }
69
-
70
- /**
71
- * Filter that fires after a new attachment has been added via the XML-RPC MovableType API.
72
- *
73
- * @since 2.0.21
74
- *
75
- * @param int $id ID of the new attachment.
76
- * @param array $args An array of arguments to add the attachment.
77
- */
78
- function on_mw_newMediaObject( $attachment_id, $args ) {
79
-
80
- $attachment_post = get_post( $attachment_id );
81
- $filename = esc_html( wp_basename( $attachment_post->guid ) );
82
- $mime = get_post_mime_type( $attachment_post );
83
- $file = get_attached_file( $attachment_id );
84
- $file_size = false;
85
-
86
- if ( file_exists( $file ) ) {
87
- $file_size = filesize( $file );
88
- }
89
-
90
- $this->infoMessage(
91
- 'attachment_created',
92
- array(
93
- 'post_type' => get_post_type( $attachment_post ),
94
- 'attachment_id' => $attachment_id,
95
- 'attachment_title' => get_the_title( $attachment_post ),
96
- 'attachment_filename' => $filename,
97
- 'attachment_mime' => $mime,
98
- 'attachment_filesize' => $file_size,
99
- )
100
- );
101
-
102
- }
103
-
104
- /**
105
- * Modify plain output to inlcude link to post
106
- */
107
- public function getLogRowPlainTextOutput( $row ) {
108
-
109
- $message = $row->message;
110
- $context = $row->context;
111
- $message_key = $context['_message_key'];
112
-
113
- $attachment_id = $context['attachment_id'];
114
- $attachment_post = get_post( $attachment_id );
115
- $attachment_is_available = is_a( $attachment_post, 'WP_Post' );
116
-
117
- // Only link to attachment if it is still available
118
- if ( $attachment_is_available ) {
119
-
120
- if ( 'attachment_updated' == $message_key ) {
121
-
122
- $message = __( 'Edited {post_type} <a href="{edit_link}">"{attachment_title}"</a>', 'simple-history' );
123
-
124
- } elseif ( 'attachment_created' == $message_key ) {
125
-
126
- $message = __( 'Uploaded {post_type} <a href="{edit_link}">"{attachment_title}"</a>', 'simple-history' );
127
-
128
- }
129
-
130
- $context['post_type'] = esc_html( $context['post_type'] );
131
- $context['attachment_filename'] = esc_html( $context['attachment_filename'] );
132
- $context['edit_link'] = get_edit_post_link( $attachment_id );
133
-
134
- $message = $this->interpolate( $message, $context, $row );
135
-
136
- } else {
137
-
138
- // Attachment post is not available, attachment has probably been deleted
139
- $message = parent::getLogRowPlainTextOutput( $row );
140
-
141
- }
142
-
143
- return $message;
144
-
145
- }
146
-
147
- /**
148
- * Get output for detailed log section
149
- *
150
- * @param array $row Row.
151
- */
152
- function getLogRowDetailsOutput( $row ) {
153
-
154
- $context = $row->context;
155
- $message_key = $context['_message_key'];
156
- $output = '';
157
-
158
- $attachment_id = $context['attachment_id'];
159
- $attachment_post = get_post( $attachment_id );
160
- $attachment_is_available = is_a( $attachment_post, 'WP_Post' );
161
-
162
- if ( 'attachment_updated' == $message_key ) {
163
-
164
- // Attachment is changed = don't show thumbs and all
165
- } elseif ( 'attachment_deleted' == $message_key ) {
166
-
167
- // Attachment is deleted = don't show thumbs and all
168
- } elseif ( 'attachment_created' == $message_key ) {
169
-
170
- // Attachment is created/uploaded = show details with image thumbnail
171
- $attachment_id = $context['attachment_id'];
172
- $filetype = wp_check_filetype( $context['attachment_filename'] );
173
- $file_url = wp_get_attachment_url( $attachment_id );
174
- $edit_link = get_edit_post_link( $attachment_id );
175
- $attached_file = get_attached_file( $attachment_id );
176
- $message = '';
177
- $full_src = false;
178
-
179
- // Is true if attachment is an image. But for example PDFs can have thumbnail images, but they are not considered to be image.
180
- $is_image = wp_attachment_is_image( $attachment_id );
181
-
182
- // $message .= $is_image ? "is images yes" : "is image no";
183
- $is_video = strpos( $filetype['type'], 'video/' ) !== false;
184
- $is_audio = strpos( $filetype['type'], 'audio/' ) !== false;
185
-
186
- $full_image_width = null;
187
- $full_image_height = null;
188
-
189
- if ( $is_image ) {
190
-
191
- $thumb_src = wp_get_attachment_image_src( $attachment_id, 'medium' );
192
- $full_src = wp_get_attachment_image_src( $attachment_id, 'full' );
193
-
194
- $full_image_width = $full_src[1];
195
- $full_image_height = $full_src[2];
196
-
197
- // is_image is also true for mime types that WP can't create thumbs for
198
- // so we need to check that wp got an resized version
199
- if ( $full_image_width && $full_image_height ) {
200
-
201
- $context['full_image_width'] = $full_image_width;
202
- $context['full_image_height'] = $full_image_height;
203
-
204
- // Only output thumb if file exists
205
- // For example images deleted on file system but not in WP cause broken images (rare case, but has happened to me.)
206
- if ( file_exists( $attached_file ) && $thumb_src ) {
207
- $context['attachment_thumb'] = sprintf( '<div class="SimpleHistoryLogitemThumbnail"><img src="%1$s" alt=""></div>', $thumb_src[0] );
208
- }
209
- }
210
- } elseif ( $is_audio ) {
211
-
212
- $content = sprintf( '[audio src="%1$s"]', $file_url );
213
- $context['attachment_thumb'] = do_shortcode( $content );
214
-
215
- } elseif ( $is_video ) {
216
-
217
- $content = sprintf( '[video src="%1$s"]', $file_url );
218
- $context['attachment_thumb'] = do_shortcode( $content );
219
-
220
- } else {
221
-
222
- // Use WordPress icon for other media types.
223
- if ( $attachment_is_available ) {
224
- $context['attachment_thumb'] = sprintf(
225
- '%1$s',
226
- wp_get_attachment_image( $attachment_id, array( 350, 500 ), true ) // Placeholder 1.
227
- );
228
- }
229
- }// End if().
230
-
231
- $context['attachment_size_format'] = size_format( $row->context['attachment_filesize'] );
232
- $context['attachment_filetype_extension'] = strtoupper( $filetype['ext'] );
233
-
234
- if ( ! empty( $context['attachment_thumb'] ) ) {
235
-
236
- if ( $is_image ) {
237
- $message .= "<a class='SimpleHistoryLogitemThumbnailLink' href='" . $edit_link . "'>";
238
- }
239
-
240
- $message .= __( '{attachment_thumb}', 'simple-history' );
241
-
242
- if ( $is_image ) {
243
- $message .= '</a>';
244
- }
245
- }
246
-
247
- $message .= "<p class='SimpleHistoryLogitem--logger-SimpleMediaLogger--attachment-meta'>";
248
- $message .= "<span class='SimpleHistoryLogitem__inlineDivided'>" . __( '{attachment_size_format}', 'simple-history' ) . '</span> ';
249
- $message .= "<span class='SimpleHistoryLogitem__inlineDivided'>" . __( '{attachment_filetype_extension}', 'simple-history' ) . '</span>';
250
-
251
- if ( $full_image_width && $full_image_height ) {
252
-
253
- $message .= " <span class='SimpleHistoryLogitem__inlineDivided'>" . __( '{full_image_width} × {full_image_height}', 'simple-history' ) . '</span>';
254
-
255
- }
256
-
257
- $message .= '</p>';
258
-
259
- $output .= $this->interpolate( $message, $context, $row );
260
-
261
- }// End if().
262
-
263
- return $output;
264
-
265
- }
266
-
267
- /**
268
- * Called when an attachment is added
269
- */
270
- function on_add_attachment( $attachment_id ) {
271
-
272
- $attachment_post = get_post( $attachment_id );
273
- $filename = esc_html( wp_basename( $attachment_post->guid ) );
274
- $mime = get_post_mime_type( $attachment_post );
275
- $file = get_attached_file( $attachment_id );
276
- $file_size = false;
277
-
278
- if ( file_exists( $file ) ) {
279
- $file_size = filesize( $file );
280
- }
281
-
282
- $this->infoMessage(
283
- 'attachment_created',
284
- array(
285
- 'post_type' => get_post_type( $attachment_post ),
286
- 'attachment_id' => $attachment_id,
287
- 'attachment_title' => get_the_title( $attachment_post ),
288
- 'attachment_filename' => $filename,
289
- 'attachment_mime' => $mime,
290
- 'attachment_filesize' => $file_size,
291
- )
292
- );
293
-
294
- }
295
-
296
- /**
297
- * An attachmet is changed
298
- * is this only being called if the title of the attachment is changed?!
299
- *
300
- * @param int $attachment_id
301
- */
302
- function on_edit_attachment( $attachment_id ) {
303
-
304
- $attachment_post = get_post( $attachment_id );
305
- $filename = esc_html( wp_basename( $attachment_post->guid ) );
306
- $mime = get_post_mime_type( $attachment_post );
307
- $file = get_attached_file( $attachment_id );
308
-
309
- $this->infoMessage(
310
- 'attachment_updated',
311
- array(
312
- 'post_type' => get_post_type( $attachment_post ),
313
- 'attachment_id' => $attachment_id,
314
- 'attachment_title' => get_the_title( $attachment_post ),
315
- 'attachment_filename' => $filename,
316
- 'attachment_mime' => $mime,
317
- )
318
- );
319
-
320
- }
321
-
322
- /**
323
- * Called when an attachment is deleted
324
- */
325
- function on_delete_attachment( $attachment_id ) {
326
-
327
- $attachment_post = get_post( $attachment_id );
328
- $filename = esc_html( wp_basename( $attachment_post->guid ) );
329
- $mime = get_post_mime_type( $attachment_post );
330
- $file = get_attached_file( $attachment_id );
331
-
332
- $this->infoMessage(
333
- 'attachment_deleted',
334
- array(
335
- 'post_type' => get_post_type( $attachment_post ),
336
- 'attachment_id' => $attachment_id,
337
- 'attachment_title' => get_the_title( $attachment_post ),
338
- 'attachment_filename' => $filename,
339
- 'attachment_mime' => $mime,
340
- )
341
- );
342
-
343
- }
344
-
345
- /**
346
- * Modify RSS links so they go directly to the correct media in wp admin
347
- *
348
- * @since 2.0.23
349
- * @param string $link
350
- * @param array $row
351
- */
352
- public function filter_rss_item_link( $link, $row ) {
353
-
354
- if ( $row->logger != $this->slug ) {
355
- return $link;
356
- }
357
-
358
- if ( isset( $row->context['attachment_id'] ) ) {
359
-
360
- $permalink = add_query_arg( array(
361
- 'action' => 'edit',
362
- 'post' => $row->context['attachment_id'],
363
- ), admin_url( 'post.php' ) );
364
-
365
- if ( $permalink ) {
366
- $link = $permalink;
367
- }
368
- }
369
-
370
- return $link;
371
-
372
- }
373
-
374
  }
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logs media uploads
7
  */
8
+ class SimpleMediaLogger extends SimpleLogger
9
+ {
10
+
11
+
12
+ public $slug = 'SimpleMediaLogger';
13
+
14
+ /**
15
+ * Get array with information about this logger
16
+ *
17
+ * @return array
18
+ */
19
+ function getInfo()
20
+ {
21
+
22
+ $arr_info = array(
23
+ 'name' => 'Media/Attachments Logger',
24
+ 'description' => 'Logs media uploads and edits',
25
+ 'capability' => 'edit_pages',
26
+ 'messages' => array(
27
+ 'attachment_created' => __('Created {post_type} "{attachment_title}"', 'simple-history'),
28
+ 'attachment_updated' => __('Edited {post_type} "{attachment_title}"', 'simple-history'),
29
+ 'attachment_deleted' => __('Deleted {post_type} "{attachment_title}" ("{attachment_filename}")', 'simple-history'),
30
+ ),
31
+ 'labels' => array(
32
+ 'search' => array(
33
+ 'label' => _x('Media', 'Media logger: search', 'simple-history'),
34
+ 'label_all' => _x('All media activity', 'Media logger: search', 'simple-history'),
35
+ 'options' => array(
36
+ _x('Added media', 'Media logger: search', 'simple-history') => array(
37
+ 'attachment_created'
38
+ ),
39
+ _x('Updated media', 'Media logger: search', 'simple-history') => array(
40
+ 'attachment_updated'
41
+ ),
42
+ _x('Deleted media', 'Media logger: search', 'simple-history') => array(
43
+ 'attachment_deleted'
44
+ ),
45
+ ),
46
+ ),// end search array
47
+ ),// end labels
48
+ );
49
+
50
+ return $arr_info;
51
+ }
52
+
53
+ public function loaded()
54
+ {
55
+
56
+ add_action('admin_init', array( $this, 'on_admin_init' ));
57
+
58
+ add_action('xmlrpc_call_success_mw_newMediaObject', array( $this, 'on_mw_newMediaObject' ), 10, 2);
59
+
60
+ add_filter('simple_history/rss_item_link', array( $this, 'filter_rss_item_link' ), 10, 2);
61
+ }
62
+
63
+ function on_admin_init()
64
+ {
65
+
66
+ add_action('add_attachment', array( $this, 'on_add_attachment' ));
67
+ add_action('edit_attachment', array( $this, 'on_edit_attachment' ));
68
+ add_action('delete_attachment', array( $this, 'on_delete_attachment' ));
69
+ }
70
+
71
+ /**
72
+ * Filter that fires after a new attachment has been added via the XML-RPC MovableType API.
73
+ *
74
+ * @since 2.0.21
75
+ *
76
+ * @param int $id ID of the new attachment.
77
+ * @param array $args An array of arguments to add the attachment.
78
+ */
79
+ function on_mw_newMediaObject($attachment_id, $args)
80
+ {
81
+
82
+ $attachment_post = get_post($attachment_id);
83
+ $filename = esc_html(wp_basename($attachment_post->guid));
84
+ $mime = get_post_mime_type($attachment_post);
85
+ $file = get_attached_file($attachment_id);
86
+ $file_size = false;
87
+
88
+ if (file_exists($file)) {
89
+ $file_size = filesize($file);
90
+ }
91
+
92
+ $this->infoMessage(
93
+ 'attachment_created',
94
+ array(
95
+ 'post_type' => get_post_type($attachment_post),
96
+ 'attachment_id' => $attachment_id,
97
+ 'attachment_title' => get_the_title($attachment_post),
98
+ 'attachment_filename' => $filename,
99
+ 'attachment_mime' => $mime,
100
+ 'attachment_filesize' => $file_size,
101
+ )
102
+ );
103
+ }
104
+
105
+ /**
106
+ * Modify plain output to inlcude link to post
107
+ */
108
+ public function getLogRowPlainTextOutput($row)
109
+ {
110
+
111
+ $message = $row->message;
112
+ $context = $row->context;
113
+ $message_key = $context['_message_key'];
114
+
115
+ $attachment_id = $context['attachment_id'];
116
+ $attachment_post = get_post($attachment_id);
117
+ $attachment_is_available = is_a($attachment_post, 'WP_Post');
118
+
119
+ // Only link to attachment if it is still available
120
+ if ($attachment_is_available) {
121
+ if ('attachment_updated' == $message_key) {
122
+ $message = __('Edited {post_type} <a href="{edit_link}">"{attachment_title}"</a>', 'simple-history');
123
+ } elseif ('attachment_created' == $message_key) {
124
+ $message = __('Uploaded {post_type} <a href="{edit_link}">"{attachment_title}"</a>', 'simple-history');
125
+ }
126
+
127
+ $context['post_type'] = esc_html($context['post_type']);
128
+ $context['attachment_filename'] = esc_html($context['attachment_filename']);
129
+ $context['edit_link'] = get_edit_post_link($attachment_id);
130
+
131
+ $message = $this->interpolate($message, $context, $row);
132
+ } else {
133
+ // Attachment post is not available, attachment has probably been deleted
134
+ $message = parent::getLogRowPlainTextOutput($row);
135
+ }
136
+
137
+ return $message;
138
+ }
139
+
140
+ /**
141
+ * Get output for detailed log section
142
+ *
143
+ * @param array $row Row.
144
+ */
145
+ function getLogRowDetailsOutput($row)
146
+ {
147
+
148
+ $context = $row->context;
149
+ $message_key = $context['_message_key'];
150
+ $output = '';
151
+
152
+ $attachment_id = $context['attachment_id'];
153
+ $attachment_post = get_post($attachment_id);
154
+ $attachment_is_available = is_a($attachment_post, 'WP_Post');
155
+
156
+ if ('attachment_updated' == $message_key) {
157
+ // Attachment is changed = don't show thumbs and all
158
+ } elseif ('attachment_deleted' == $message_key) {
159
+ // Attachment is deleted = don't show thumbs and all
160
+ } elseif ('attachment_created' == $message_key) {
161
+ // Attachment is created/uploaded = show details with image thumbnail
162
+ $attachment_id = $context['attachment_id'];
163
+ $filetype = wp_check_filetype($context['attachment_filename']);
164
+ $file_url = wp_get_attachment_url($attachment_id);
165
+ $edit_link = get_edit_post_link($attachment_id);
166
+ $attached_file = get_attached_file($attachment_id);
167
+ $message = '';
168
+ $full_src = false;
169
+
170
+ // Is true if attachment is an image. But for example PDFs can have thumbnail images, but they are not considered to be image.
171
+ $is_image = wp_attachment_is_image($attachment_id);
172
+
173
+ // $message .= $is_image ? "is images yes" : "is image no";
174
+ $is_video = strpos($filetype['type'], 'video/') !== false;
175
+ $is_audio = strpos($filetype['type'], 'audio/') !== false;
176
+
177
+ $full_image_width = null;
178
+ $full_image_height = null;
179
+
180
+ if ($is_image) {
181
+ $thumb_src = wp_get_attachment_image_src($attachment_id, 'medium');
182
+ $full_src = wp_get_attachment_image_src($attachment_id, 'full');
183
+
184
+ $full_image_width = $full_src[1];
185
+ $full_image_height = $full_src[2];
186
+
187
+ // is_image is also true for mime types that WP can't create thumbs for
188
+ // so we need to check that wp got an resized version
189
+ if ($full_image_width && $full_image_height) {
190
+ $context['full_image_width'] = $full_image_width;
191
+ $context['full_image_height'] = $full_image_height;
192
+
193
+ // Only output thumb if file exists
194
+ // For example images deleted on file system but not in WP cause broken images (rare case, but has happened to me.)
195
+ if (file_exists($attached_file) && $thumb_src) {
196
+ $context['attachment_thumb'] = sprintf('<div class="SimpleHistoryLogitemThumbnail"><img src="%1$s" alt=""></div>', $thumb_src[0]);
197
+ }
198
+ }
199
+ } elseif ($is_audio) {
200
+ $content = sprintf('[audio src="%1$s"]', $file_url);
201
+ $context['attachment_thumb'] = do_shortcode($content);
202
+ } elseif ($is_video) {
203
+ $content = sprintf('[video src="%1$s"]', $file_url);
204
+ $context['attachment_thumb'] = do_shortcode($content);
205
+ } else {
206
+ // Use WordPress icon for other media types.
207
+ if ($attachment_is_available) {
208
+ $context['attachment_thumb'] = sprintf(
209
+ '%1$s',
210
+ wp_get_attachment_image($attachment_id, array( 350, 500 ), true) // Placeholder 1.
211
+ );
212
+ }
213
+ }// End if().
214
+
215
+ $context['attachment_size_format'] = size_format($row->context['attachment_filesize']);
216
+ $context['attachment_filetype_extension'] = strtoupper($filetype['ext']);
217
+
218
+ if (! empty($context['attachment_thumb'])) {
219
+ if ($is_image) {
220
+ $message .= "<a class='SimpleHistoryLogitemThumbnailLink' href='" . $edit_link . "'>";
221
+ }
222
+
223
+ $message .= __('{attachment_thumb}', 'simple-history');
224
+
225
+ if ($is_image) {
226
+ $message .= '</a>';
227
+ }
228
+ }
229
+
230
+ $message .= "<p class='SimpleHistoryLogitem--logger-SimpleMediaLogger--attachment-meta'>";
231
+ $message .= "<span class='SimpleHistoryLogitem__inlineDivided'>" . __('{attachment_size_format}', 'simple-history') . '</span> ';
232
+ $message .= "<span class='SimpleHistoryLogitem__inlineDivided'>" . __('{attachment_filetype_extension}', 'simple-history') . '</span>';
233
+
234
+ if ($full_image_width && $full_image_height) {
235
+ $message .= " <span class='SimpleHistoryLogitem__inlineDivided'>" . __('{full_image_width} × {full_image_height}', 'simple-history') . '</span>';
236
+ }
237
+
238
+ $message .= '</p>';
239
+
240
+ $output .= $this->interpolate($message, $context, $row);
241
+ }// End if().
242
+
243
+ return $output;
244
+ }
245
+
246
+ /**
247
+ * Called when an attachment is added
248
+ */
249
+ function on_add_attachment($attachment_id)
250
+ {
251
+
252
+ $attachment_post = get_post($attachment_id);
253
+ $filename = esc_html(wp_basename($attachment_post->guid));
254
+ $mime = get_post_mime_type($attachment_post);
255
+ $file = get_attached_file($attachment_id);
256
+ $file_size = false;
257
+
258
+ if (file_exists($file)) {
259
+ $file_size = filesize($file);
260
+ }
261
+
262
+ $this->infoMessage(
263
+ 'attachment_created',
264
+ array(
265
+ 'post_type' => get_post_type($attachment_post),
266
+ 'attachment_id' => $attachment_id,
267
+ 'attachment_title' => get_the_title($attachment_post),
268
+ 'attachment_filename' => $filename,
269
+ 'attachment_mime' => $mime,
270
+ 'attachment_filesize' => $file_size,
271
+ )
272
+ );
273
+ }
274
+
275
+ /**
276
+ * An attachmet is changed
277
+ * is this only being called if the title of the attachment is changed?!
278
+ *
279
+ * @param int $attachment_id
280
+ */
281
+ function on_edit_attachment($attachment_id)
282
+ {
283
+
284
+ $attachment_post = get_post($attachment_id);
285
+ $filename = esc_html(wp_basename($attachment_post->guid));
286
+ $mime = get_post_mime_type($attachment_post);
287
+ $file = get_attached_file($attachment_id);
288
+
289
+ $this->infoMessage(
290
+ 'attachment_updated',
291
+ array(
292
+ 'post_type' => get_post_type($attachment_post),
293
+ 'attachment_id' => $attachment_id,
294
+ 'attachment_title' => get_the_title($attachment_post),
295
+ 'attachment_filename' => $filename,
296
+ 'attachment_mime' => $mime,
297
+ )
298
+ );
299
+ }
300
+
301
+ /**
302
+ * Called when an attachment is deleted
303
+ */
304
+ function on_delete_attachment($attachment_id)
305
+ {
306
+
307
+ $attachment_post = get_post($attachment_id);
308
+ $filename = esc_html(wp_basename($attachment_post->guid));
309
+ $mime = get_post_mime_type($attachment_post);
310
+ $file = get_attached_file($attachment_id);
311
+
312
+ $this->infoMessage(
313
+ 'attachment_deleted',
314
+ array(
315
+ 'post_type' => get_post_type($attachment_post),
316
+ 'attachment_id' => $attachment_id,
317
+ 'attachment_title' => get_the_title($attachment_post),
318
+ 'attachment_filename' => $filename,
319
+ 'attachment_mime' => $mime,
320
+ )
321
+ );
322
+ }
323
+
324
+ /**
325
+ * Modify RSS links so they go directly to the correct media in wp admin
326
+ *
327
+ * @since 2.0.23
328
+ * @param string $link
329
+ * @param array $row
330
+ */
331
+ public function filter_rss_item_link($link, $row)
332
+ {
333
+
334
+ if ($row->logger != $this->slug) {
335
+ return $link;
336
+ }
337
+
338
+ if (isset($row->context['attachment_id'])) {
339
+ $permalink = add_query_arg(array(
340
+ 'action' => 'edit',
341
+ 'post' => $row->context['attachment_id'],
342
+ ), admin_url('post.php'));
343
+
344
+ if ($permalink) {
345
+ $link = $permalink;
346
+ }
347
+ }
348
+
349
+ return $link;
350
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  }
loggers/SimpleMenuLogger.php CHANGED
@@ -1,378 +1,377 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /**
6
  * Logs WordPress menu edits
7
  */
8
- class SimpleMenuLogger extends SimpleLogger {
9
-
10
-
11
- public $slug = __CLASS__;
12
-
13
- /**
14
- * Get array with information about this logger
15
- *
16
- * @return array
17
- */
18
- function getInfo() {
19
-
20
- $arr_info = array(
21
- 'name' => 'Menu Logger',
22
- 'description' => 'Logs menu edits',
23
- 'capability' => 'edit_theme_options',
24
- 'messages' => array(
25
- 'created_menu' => __( 'Created menu "{menu_name}"', 'simple-history' ),
26
- 'edited_menu' => __( 'Edited menu "{menu_name}"', 'simple-history' ),
27
- 'deleted_menu' => __( 'Deleted menu "{menu_name}"', 'simple-history' ),
28
- 'edited_menu_item' => __( 'Edited a menu item', 'simple-history' ),
29
- 'edited_menu_locations' => __( 'Updated menu locations', 'simple-history' ),
30
- ),
31
- 'labels' => array(
32
- 'search' => array(
33
- 'label' => _x( 'Menus', 'Menu logger: search', 'simple-history' ),
34
- 'label_all' => _x( 'All menu activity', 'Menu updates logger: search', 'simple-history' ),
35
- 'options' => array(
36
- _x( 'Created menus', 'Menu updates logger: search', 'simple-history' ) => array(
37
- 'created_menu'
38
- ),
39
- _x( 'Edited menus', 'Menu updates logger: search', 'simple-history' ) => array(
40
- 'edited_menu',
41
- 'edited_menu_item',
42
- 'edited_menu_locations',
43
- ),
44
- _x( 'Deleted menus', 'Menu updates logger: search', 'simple-history' ) => array(
45
- 'deleted_menu'
46
- ),
47
- ),
48
- ),// end search array
49
- ),// end labels
50
- );
51
-
52
- return $arr_info;
53
-
54
- }
55
-
56
- function loaded() {
57
-
58
- /*
59
- * Fires after a navigation menu has been successfully deleted.
60
- *
61
- * @since 3.0.0
62
- *
63
- * @param int $term_id ID of the deleted menu.
64
- do_action( 'wp_delete_nav_menu', $menu->term_id );
65
- */
66
- // add_action("wp_delete_nav_menu", array($this, "on_wp_delete_nav_menu"), 10, 1 );
67
- add_action( 'load-nav-menus.php', array( $this, 'on_load_nav_menus_page_detect_delete' ) );
68
-
69
- /*
70
- * Fires after a navigation menu is successfully created.
71
- *
72
- * @since 3.0.0
73
- *
74
- * @param int $term_id ID of the new menu.
75
- * @param array $menu_data An array of menu data.
76
- do_action( 'wp_create_nav_menu', $_menu['term_id'], $menu_data );
77
- */
78
- add_action( 'wp_create_nav_menu', array( $this, 'on_wp_create_nav_menu' ), 10, 2 );
79
-
80
- /*
81
- * Fires after a navigation menu item has been updated.
82
- *
83
- * @since 3.0.0
84
- *
85
- * @see wp_update_nav_menu_items()
86
- *
87
- * @param int $menu_id ID of the updated menu.
88
- * @param int $menu_item_db_id ID of the updated menu item.
89
- * @param array $args An array of arguments used to update a menu item.
90
- do_action( 'wp_update_nav_menu_item', $menu_id, $menu_item_db_id, $args );
91
- */
92
-
93
- // This is fired when adding nav items in the editor, not at save, so not
94
- // good to log because user might not end up saving the changes
95
- // add_action("wp_update_nav_menu_item", array($this, "on_wp_update_nav_menu_item"), 10, 3 );
96
- // Fired before "wp_update_nav_menu" below, to remember menu layput before it's updated
97
- // so we can't detect changes
98
- add_action( 'load-nav-menus.php', array( $this, 'on_load_nav_menus_page_detect_update' ) );
99
-
100
- /*
101
- * Fires after a navigation menu has been successfully updated.
102
- *
103
- * @since 3.0.0
104
- *
105
- * @param int $menu_id ID of the updated menu.
106
- * @param array $menu_data An array of menu data.
107
- do_action( 'wp_update_nav_menu', $menu_id, $menu_data );
108
- */
109
- // add_action("wp_update_nav_menu", array($this, "on_wp_update_nav_menu"), 10, 2 );
110
- // Detect meny location change in "manage locations"
111
- add_action( 'load-nav-menus.php', array( $this, 'on_load_nav_menus_page_detect_locations_update' ) );
112
- }
113
-
114
- /**
115
- * Can't use action "wp_delete_nav_menu" beacuse
116
- * it's fired after menu is deleted, so we don't have the name in this action
117
- */
118
- function on_load_nav_menus_page_detect_delete() {
119
-
120
- /*
121
- http://playground-root.ep/wp-admin/nav-menus.php?menu=22&action=delete&0=http%3A%2F%2Fplayground-root.ep%2Fwp-admin%2F&_wpnonce=f52e8a31ba
122
- $_REQUEST:
123
- Array
124
- (
125
- [menu] => 22
126
- [action] => delete
127
- [0] => http://playground-root.ep/wp-admin/
128
- [_wpnonce] => f52e8a31ba
129
- )
130
- */
131
-
132
- // Check that needed vars are set
133
- if ( ! isset( $_REQUEST['menu'], $_REQUEST['action'] ) ) {
134
- return;
135
- }
136
-
137
- if ( 'delete' !== $_REQUEST['action'] ) {
138
- return;
139
- }
140
-
141
- $menu_id = $_REQUEST['menu'];
142
- if ( ! is_nav_menu( $menu_id ) ) {
143
- return;
144
- }
145
-
146
- $menu = wp_get_nav_menu_object( $menu_id );
147
-
148
- $this->infoMessage(
149
- 'deleted_menu',
150
- array(
151
- 'menu_term_id' => $menu_id,
152
- 'menu_name' => $menu->name,
153
- )
154
- );
155
-
156
- }
157
-
158
- /**
159
- * Fired after menu is deleted, so we don't have the name in this action
160
- * So that's why we can't use this only
161
- */
162
- /*
163
- function on_wp_delete_nav_menu($menu_term_id) {
164
-
165
- $this->infoMessage(
166
- "deleted_menu",
167
- array(
168
- "menu_term_id" => $menu_term_id,
169
- "menu" => print_r($menu, true),
170
- "request" => print_r($_REQUEST, true),
171
- )
172
- );
173
-
174
- }
175
- */
176
-
177
- function on_wp_create_nav_menu( $term_id, $menu_data ) {
178
-
179
- $menu = wp_get_nav_menu_object( $term_id );
180
-
181
- if ( ! $menu ) {
182
- return;
183
- }
184
-
185
- $this->infoMessage(
186
- 'created_menu',
187
- array(
188
- 'term_id' => $term_id,
189
- 'menu_name' => $menu->name,
190
- )
191
- );
192
-
193
- }
194
-
195
- /*
196
- function on_wp_update_nav_menu_item($menu_id, $menu_item_db_id, $args) {
197
-
198
- $this->infoMessage(
199
- "edited_menu_item",
200
- array(
201
- "menu_id" => $menu_id,
202
- "menu_item_db_id" => $menu_item_db_id,
203
- "args" => $this->simpleHistory->json_encode($args),
204
- "request" => $this->simpleHistory->json_encode($_REQUEST)
205
- )
206
- );
207
-
208
- }
209
- */
210
-
211
- /**
212
- * Detect menu being saved
213
- */
214
- function on_load_nav_menus_page_detect_update() {
215
-
216
- /*
217
- This is the data to be saved
218
- $_REQUEST:
219
- Array
220
- (
221
- [action] => update
222
- [menu] => 25
223
- [menu-name] => Main menu edit
224
- [menu-item-title] => Array
225
- (
226
- [25243] => My new page edited
227
- [25244] => My new page
228
- [25245] => This is my new page. How does it look in the logs? <h1>Hej!</h1>
229
- [25264] => This page have revisions
230
- [25265] => Lorem ipsum dolor sit amet
231
- )
232
- [menu-locations] => Array
233
- (
234
- [primary] => 25
235
- )
236
- )
237
- */
238
-
239
- // Check that needed vars are set
240
- if ( ! isset( $_REQUEST['menu'], $_REQUEST['action'], $_REQUEST['menu-name'] ) ) {
241
- return;
242
- }
243
-
244
- // Only go on for update action
245
- if ( 'update' !== $_REQUEST['action'] ) {
246
- return;
247
- }
248
-
249
- // Make sure we got the id of a menu
250
- $menu_id = $_REQUEST['menu'];
251
- if ( ! is_nav_menu( $menu_id ) ) {
252
- return;
253
- }
254
-
255
- // Get saved menu. May be empty if this is the first time we save the menu
256
- $arr_prev_menu_items = wp_get_nav_menu_items( $menu_id );
257
-
258
- // Compare new items to be saved with old version
259
- $old_ids = wp_list_pluck( $arr_prev_menu_items, 'db_id' );
260
- $new_ids = array_values( isset( $_POST['menu-item-db-id'] ) ? $_POST['menu-item-db-id'] : array() );
261
-
262
- // Get ids of added and removed post ids
263
- $arr_removed = array_diff( $old_ids, $new_ids );
264
- $arr_added = array_diff( $new_ids, $old_ids );
265
-
266
- // Get old version location
267
- // $prev_menu = wp_get_nav_menu_object( $menu_id );
268
- // $locations = get_registered_nav_menus();
269
- // $menu_locations = get_nav_menu_locations();
270
- $this->infoMessage(
271
- 'edited_menu',
272
- array(
273
- 'menu_id' => $menu_id,
274
- 'menu_name' => $_POST['menu-name'],
275
- 'menu_items_added' => sizeof( $arr_added ),
276
- 'menu_items_removed' => sizeof( $arr_removed ),
277
- // "request" => $this->simpleHistory->json_encode($_REQUEST)
278
- )
279
- );
280
-
281
- }
282
-
283
- /**
284
- * This seems to get called twice
285
- * one time with menu_data, a second without
286
- */
287
- /*
288
- function on_wp_update_nav_menu($menu_id, $menu_data = array()) {
289
-
290
- if (empty($menu_data)) {
291
- return;
292
- }
293
-
294
- $this->infoMessage(
295
- "edited_menu",
296
- array(
297
- "menu_id" => $menu_id,
298
- "menu_name" => $menu_data["menu-name"],
299
- "menu_data" => $this->simpleHistory->json_encode($menu_data),
300
- "request" => $this->simpleHistory->json_encode($_REQUEST)
301
- )
302
- );
303
-
304
- }
305
- */
306
-
307
- /**
308
- * Get detailed output
309
- */
310
- function getLogRowDetailsOutput( $row ) {
311
-
312
- $context = $row->context;
313
- $message_key = $context['_message_key'];
314
- $output = '';
315
-
316
- if ( 'edited_menu' == $message_key ) {
317
-
318
- if ( ! empty( $context['menu_items_added'] ) || ! empty( $context['menu_items_removed'] ) ) {
319
-
320
- $output .= '<p>';
321
-
322
- $output .= '<span class="SimpleHistoryLogitem__inlineDivided">';
323
- $output .= sprintf(
324
- _nx( '%1$s menu item added', '%1$s menu items added', $context['menu_items_added'], 'menu logger', 'simple-history' ),
325
- esc_attr( $context['menu_items_added'] )
326
- );
327
- $output .= '</span> ';
328
-
329
- $output .= '<span class="SimpleHistoryLogitem__inlineDivided">';
330
- $output .= sprintf(
331
- _nx( '%1$s menu item removed', '%1$s menu items removed', $context['menu_items_removed'], 'menu logger', 'simple-history' ),
332
- esc_attr( $context['menu_items_removed'] )
333
- );
334
- $output .= '</span> ';
335
-
336
- $output .= '</p>';
337
-
338
- }
339
- }
340
-
341
- return $output;
342
-
343
- }
344
-
345
- /**
346
- * Log updates to theme menu locations
347
- */
348
- function on_load_nav_menus_page_detect_locations_update() {
349
-
350
- // Check that needed vars are set
351
- if ( ! isset( $_REQUEST['menu'], $_REQUEST['action'] ) ) {
352
- return;
353
- }
354
-
355
- if ( 'locations' !== $_REQUEST['action'] ) {
356
- return;
357
- }
358
-
359
- /*
360
- Array
361
- (
362
- [menu-locations] => Array
363
- (
364
- [primary] => 25
365
- )
366
- )
367
- */
368
- $menu_locations = $_POST['menu-locations'];
369
-
370
- $this->infoMessage(
371
- 'edited_menu_locations',
372
- array(
373
- 'menu_locations' => $this->simpleHistory->json_encode( $menu_locations ),
374
- )
375
- );
376
-
377
- }
378
  }
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logs WordPress menu edits
7
  */
8
+ class SimpleMenuLogger extends SimpleLogger
9
+ {
10
+
11
+
12
+ public $slug = __CLASS__;
13
+
14
+ /**
15
+ * Get array with information about this logger
16
+ *
17
+ * @return array
18
+ */
19
+ function getInfo()
20
+ {
21
+
22
+ $arr_info = array(
23
+ 'name' => 'Menu Logger',
24
+ 'description' => 'Logs menu edits',
25
+ 'capability' => 'edit_theme_options',
26
+ 'messages' => array(
27
+ 'created_menu' => __('Created menu "{menu_name}"', 'simple-history'),
28
+ 'edited_menu' => __('Edited menu "{menu_name}"', 'simple-history'),
29
+ 'deleted_menu' => __('Deleted menu "{menu_name}"', 'simple-history'),
30
+ 'edited_menu_item' => __('Edited a menu item', 'simple-history'),
31
+ 'edited_menu_locations' => __('Updated menu locations', 'simple-history'),
32
+ ),
33
+ 'labels' => array(
34
+ 'search' => array(
35
+ 'label' => _x('Menus', 'Menu logger: search', 'simple-history'),
36
+ 'label_all' => _x('All menu activity', 'Menu updates logger: search', 'simple-history'),
37
+ 'options' => array(
38
+ _x('Created menus', 'Menu updates logger: search', 'simple-history') => array(
39
+ 'created_menu'
40
+ ),
41
+ _x('Edited menus', 'Menu updates logger: search', 'simple-history') => array(
42
+ 'edited_menu',
43
+ 'edited_menu_item',
44
+ 'edited_menu_locations',
45
+ ),
46
+ _x('Deleted menus', 'Menu updates logger: search', 'simple-history') => array(
47
+ 'deleted_menu'
48
+ ),
49
+ ),
50
+ ),// end search array
51
+ ),// end labels
52
+ );
53
+
54
+ return $arr_info;
55
+ }
56
+
57
+ function loaded()
58
+ {
59
+
60
+ /*
61
+ * Fires after a navigation menu has been successfully deleted.
62
+ *
63
+ * @since 3.0.0
64
+ *
65
+ * @param int $term_id ID of the deleted menu.
66
+ do_action( 'wp_delete_nav_menu', $menu->term_id );
67
+ */
68
+ // add_action("wp_delete_nav_menu", array($this, "on_wp_delete_nav_menu"), 10, 1 );
69
+ add_action('load-nav-menus.php', array( $this, 'on_load_nav_menus_page_detect_delete' ));
70
+
71
+ /*
72
+ * Fires after a navigation menu is successfully created.
73
+ *
74
+ * @since 3.0.0
75
+ *
76
+ * @param int $term_id ID of the new menu.
77
+ * @param array $menu_data An array of menu data.
78
+ do_action( 'wp_create_nav_menu', $_menu['term_id'], $menu_data );
79
+ */
80
+ add_action('wp_create_nav_menu', array( $this, 'on_wp_create_nav_menu' ), 10, 2);
81
+
82
+ /*
83
+ * Fires after a navigation menu item has been updated.
84
+ *
85
+ * @since 3.0.0
86
+ *
87
+ * @see wp_update_nav_menu_items()
88
+ *
89
+ * @param int $menu_id ID of the updated menu.
90
+ * @param int $menu_item_db_id ID of the updated menu item.
91
+ * @param array $args An array of arguments used to update a menu item.
92
+ do_action( 'wp_update_nav_menu_item', $menu_id, $menu_item_db_id, $args );
93
+ */
94
+
95
+ // This is fired when adding nav items in the editor, not at save, so not
96
+ // good to log because user might not end up saving the changes
97
+ // add_action("wp_update_nav_menu_item", array($this, "on_wp_update_nav_menu_item"), 10, 3 );
98
+ // Fired before "wp_update_nav_menu" below, to remember menu layput before it's updated
99
+ // so we can't detect changes
100
+ add_action('load-nav-menus.php', array( $this, 'on_load_nav_menus_page_detect_update' ));
101
+
102
+ /*
103
+ * Fires after a navigation menu has been successfully updated.
104
+ *
105
+ * @since 3.0.0
106
+ *
107
+ * @param int $menu_id ID of the updated menu.
108
+ * @param array $menu_data An array of menu data.
109
+ do_action( 'wp_update_nav_menu', $menu_id, $menu_data );
110
+ */
111
+ // add_action("wp_update_nav_menu", array($this, "on_wp_update_nav_menu"), 10, 2 );
112
+ // Detect meny location change in "manage locations"
113
+ add_action('load-nav-menus.php', array( $this, 'on_load_nav_menus_page_detect_locations_update' ));
114
+ }
115
+
116
+ /**
117
+ * Can't use action "wp_delete_nav_menu" beacuse
118
+ * it's fired after menu is deleted, so we don't have the name in this action
119
+ */
120
+ function on_load_nav_menus_page_detect_delete()
121
+ {
122
+
123
+ /*
124
+ http://playground-root.ep/wp-admin/nav-menus.php?menu=22&action=delete&0=http%3A%2F%2Fplayground-root.ep%2Fwp-admin%2F&_wpnonce=f52e8a31ba
125
+ $_REQUEST:
126
+ Array
127
+ (
128
+ [menu] => 22
129
+ [action] => delete
130
+ [0] => http://playground-root.ep/wp-admin/
131
+ [_wpnonce] => f52e8a31ba
132
+ )
133
+ */
134
+
135
+ // Check that needed vars are set
136
+ if (! isset($_REQUEST['menu'], $_REQUEST['action'])) {
137
+ return;
138
+ }
139
+
140
+ if ('delete' !== $_REQUEST['action']) {
141
+ return;
142
+ }
143
+
144
+ $menu_id = $_REQUEST['menu'];
145
+ if (! is_nav_menu($menu_id)) {
146
+ return;
147
+ }
148
+
149
+ $menu = wp_get_nav_menu_object($menu_id);
150
+
151
+ $this->infoMessage(
152
+ 'deleted_menu',
153
+ array(
154
+ 'menu_term_id' => $menu_id,
155
+ 'menu_name' => $menu->name,
156
+ )
157
+ );
158
+ }
159
+
160
+ /**
161
+ * Fired after menu is deleted, so we don't have the name in this action
162
+ * So that's why we can't use this only
163
+ */
164
+ /*
165
+ function on_wp_delete_nav_menu($menu_term_id) {
166
+
167
+ $this->infoMessage(
168
+ "deleted_menu",
169
+ array(
170
+ "menu_term_id" => $menu_term_id,
171
+ "menu" => print_r($menu, true),
172
+ "request" => print_r($_REQUEST, true),
173
+ )
174
+ );
175
+
176
+ }
177
+ */
178
+
179
+ function on_wp_create_nav_menu($term_id, $menu_data)
180
+ {
181
+
182
+ $menu = wp_get_nav_menu_object($term_id);
183
+
184
+ if (! $menu) {
185
+ return;
186
+ }
187
+
188
+ $this->infoMessage(
189
+ 'created_menu',
190
+ array(
191
+ 'term_id' => $term_id,
192
+ 'menu_name' => $menu->name,
193
+ )
194
+ );
195
+ }
196
+
197
+ /*
198
+ function on_wp_update_nav_menu_item($menu_id, $menu_item_db_id, $args) {
199
+
200
+ $this->infoMessage(
201
+ "edited_menu_item",
202
+ array(
203
+ "menu_id" => $menu_id,
204
+ "menu_item_db_id" => $menu_item_db_id,
205
+ "args" => $this->simpleHistory->json_encode($args),
206
+ "request" => $this->simpleHistory->json_encode($_REQUEST)
207
+ )
208
+ );
209
+
210
+ }
211
+ */
212
+
213
+ /**
214
+ * Detect menu being saved
215
+ */
216
+ function on_load_nav_menus_page_detect_update()
217
+ {
218
+
219
+ /*
220
+ This is the data to be saved
221
+ $_REQUEST:
222
+ Array
223
+ (
224
+ [action] => update
225
+ [menu] => 25
226
+ [menu-name] => Main menu edit
227
+ [menu-item-title] => Array
228
+ (
229
+ [25243] => My new page edited
230
+ [25244] => My new page
231
+ [25245] => This is my new page. How does it look in the logs? <h1>Hej!</h1>
232
+ [25264] => This page have revisions
233
+ [25265] => Lorem ipsum dolor sit amet
234
+ )
235
+ [menu-locations] => Array
236
+ (
237
+ [primary] => 25
238
+ )
239
+ )
240
+ */
241
+
242
+ // Check that needed vars are set
243
+ if (! isset($_REQUEST['menu'], $_REQUEST['action'], $_REQUEST['menu-name'])) {
244
+ return;
245
+ }
246
+
247
+ // Only go on for update action
248
+ if ('update' !== $_REQUEST['action']) {
249
+ return;
250
+ }
251
+
252
+ // Make sure we got the id of a menu
253
+ $menu_id = $_REQUEST['menu'];
254
+ if (! is_nav_menu($menu_id)) {
255
+ return;
256
+ }
257
+
258
+ // Get saved menu. May be empty if this is the first time we save the menu
259
+ $arr_prev_menu_items = wp_get_nav_menu_items($menu_id);
260
+
261
+ // Compare new items to be saved with old version
262
+ $old_ids = wp_list_pluck($arr_prev_menu_items, 'db_id');
263
+ $new_ids = array_values(isset($_POST['menu-item-db-id']) ? $_POST['menu-item-db-id'] : array());
264
+
265
+ // Get ids of added and removed post ids
266
+ $arr_removed = array_diff($old_ids, $new_ids);
267
+ $arr_added = array_diff($new_ids, $old_ids);
268
+
269
+ // Get old version location
270
+ // $prev_menu = wp_get_nav_menu_object( $menu_id );
271
+ // $locations = get_registered_nav_menus();
272
+ // $menu_locations = get_nav_menu_locations();
273
+ $this->infoMessage(
274
+ 'edited_menu',
275
+ array(
276
+ 'menu_id' => $menu_id,
277
+ 'menu_name' => $_POST['menu-name'],
278
+ 'menu_items_added' => sizeof($arr_added),
279
+ 'menu_items_removed' => sizeof($arr_removed),
280
+ // "request" => $this->simpleHistory->json_encode($_REQUEST)
281
+ )
282
+ );
283
+ }
284
+
285
+ /**
286
+ * This seems to get called twice
287
+ * one time with menu_data, a second without
288
+ */
289
+ /*
290
+ function on_wp_update_nav_menu($menu_id, $menu_data = array()) {
291
+
292
+ if (empty($menu_data)) {
293
+ return;
294
+ }
295
+
296
+ $this->infoMessage(
297
+ "edited_menu",
298
+ array(
299
+ "menu_id" => $menu_id,
300
+ "menu_name" => $menu_data["menu-name"],
301
+ "menu_data" => $this->simpleHistory->json_encode($menu_data),
302
+ "request" => $this->simpleHistory->json_encode($_REQUEST)
303
+ )
304
+ );
305
+
306
+ }
307
+ */
308
+
309
+ /**
310
+ * Get detailed output
311
+ */
312
+ function getLogRowDetailsOutput($row)
313
+ {
314
+
315
+ $context = $row->context;
316
+ $message_key = $context['_message_key'];
317
+ $output = '';
318
+
319
+ if ('edited_menu' == $message_key) {
320
+ if (! empty($context['menu_items_added']) || ! empty($context['menu_items_removed'])) {
321
+ $output .= '<p>';
322
+
323
+ $output .= '<span class="SimpleHistoryLogitem__inlineDivided">';
324
+ $output .= sprintf(
325
+ _nx('%1$s menu item added', '%1$s menu items added', $context['menu_items_added'], 'menu logger', 'simple-history'),
326
+ esc_attr($context['menu_items_added'])
327
+ );
328
+ $output .= '</span> ';
329
+
330
+ $output .= '<span class="SimpleHistoryLogitem__inlineDivided">';
331
+ $output .= sprintf(
332
+ _nx('%1$s menu item removed', '%1$s menu items removed', $context['menu_items_removed'], 'menu logger', 'simple-history'),
333
+ esc_attr($context['menu_items_removed'])
334
+ );
335
+ $output .= '</span> ';
336
+
337
+ $output .= '</p>';
338
+ }
339
+ }
340
+
341
+ return $output;
342
+ }
343
+
344
+ /**
345
+ * Log updates to theme menu locations
346
+ */
347
+ function on_load_nav_menus_page_detect_locations_update()
348
+ {
349
+
350
+ // Check that needed vars are set
351
+ if (! isset($_REQUEST['menu'], $_REQUEST['action'])) {
352
+ return;
353
+ }
354
+
355
+ if ('locations' !== $_REQUEST['action']) {
356
+ return;
357
+ }
358
+
359
+ /*
360
+ Array
361
+ (
362
+ [menu-locations] => Array
363
+ (
364
+ [primary] => 25
365
+ )
366
+ )
367
+ */
368
+ $menu_locations = $_POST['menu-locations'];
369
+
370
+ $this->infoMessage(
371
+ 'edited_menu_locations',
372
+ array(
373
+ 'menu_locations' => $this->simpleHistory->json_encode($menu_locations),
374
+ )
375
+ );
376
+ }
 
377
  }
loggers/SimpleOptionsLogger.php CHANGED
@@ -1,498 +1,464 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) or die();
4
 
5
  /**
6
  * Logs changes to wordpress options
7
  */
8
- class SimpleOptionsLogger extends SimpleLogger {
9
-
10
-
11
- public $slug = __CLASS__;
12
-
13
- /**
14
- * Get array with information about this logger
15
- *
16
- * @return array
17
- */
18
- function getInfo() {
19
-
20
- $arr_info = array(
21
- 'name' => 'Options Logger',
22
- 'description' => 'Logs updates to WordPress settings',
23
- 'capability' => 'manage_options',
24
- 'messages' => array(
25
- // 'option_updated' => __('Updated option "{option}" on settings page "{option_page}"', "simple-history"),
26
- 'option_updated' => __( 'Updated option "{option}"', 'simple-history' ),
27
- /*
28
- Updated option "default_comment_status" on settings page "discussion"
29
- Edited option "default_comment_status" on settings page "discussion"
30
- Modified option "default_comment_status" on settings page "discussion"
31
-
32
- Edited settings page "discussion" and the "default_comment_status" options
33
-
34
- */
35
- ),
36
- 'labels' => array(
37
- 'search' => array(
38
- 'label' => _x( 'Options', 'Options logger: search', 'simple-history' ),
39
- 'options' => array(
40
- _x( 'Changed options', 'Options logger: search', 'simple-history' ) => array(
41
- 'option_updated'
42
- ),
43
- ),
44
- ),// end search array
45
- ),// end labels
46
- );
47
-
48
- return $arr_info;
49
-
50
- }
51
-
52
- function loaded() {
53
-
54
- add_action( 'updated_option', array( $this, 'on_updated_option' ), 10, 3 );
55
-
56
- }
57
-
58
- function on_updated_option( $option, $old_value, $new_value ) {
59
-
60
- if ( empty( $_SERVER['REQUEST_URI'] ) ) {
61
- return;
62
- }
63
-
64
- $arr_option_pages = array(
65
- 0 => 'options.php',
66
- 1 => 'options-permalink.php',
67
- );
68
-
69
- // We only want to log options being added via pages in $arr_option_pages
70
- if ( ! in_array( basename( $_SERVER['REQUEST_URI'] ), $arr_option_pages ) || basename( dirname( $_SERVER['REQUEST_URI'] ) ) !== 'wp-admin' ) {
71
- return;
72
- }
73
-
74
- // Also only if "option_page" is set to one of these "built in" ones
75
- // We don't wanna start loging things from other plugins, like EDD
76
- $option_page = isset( $_REQUEST['option_page'] ) ? $_REQUEST['option_page'] : ''; // general | discussion | ...
77
-
78
- $arr_valid_option_pages = array(
79
- 'general',
80
- 'discussion',
81
- 'media',
82
- 'reading',
83
- 'writing',
84
- );
85
-
86
- $is_valid_options_page = $option_page && in_array( $option_page, $arr_valid_option_pages );
87
-
88
- // Permalink settings page does not post any "option_page", so use http referer instead
89
- if ( strpos( $_SERVER['REQUEST_URI'], 'options-permalink.php' ) !== false ) {
90
- $is_valid_options_page = true;
91
- $options_page = 'permalink';
92
- }
93
-
94
- if ( ! $is_valid_options_page ) {
95
- return;
96
- }
97
-
98
- // Check if option name is ok
99
- // For example if you change front page displays setting the "rewrite_rules" options gets updated too
100
- $arr_invalid_option_names = array(
101
- 'rewrite_rules'
102
- );
103
-
104
- if ( in_array( $option, $arr_invalid_option_names ) ) {
105
- return;
106
- }
107
-
108
- $context = array(
109
- 'option' => $option,
110
- 'old_value' => $old_value,
111
- 'new_value' => $new_value,
112
- 'option_page' => $option_page,
113
- // 'referer' => wp_get_referer(),
114
- // 'REQUEST_URI' => $_SERVER['REQUEST_URI'],
115
- // '$_REQUEST' => print_r($_REQUEST, true),
116
- );
117
-
118
- // Store a bit more about some options
119
- // Like "page_on_front" we also store post title
120
- // Check for a method for current option in this class and calls it automagically
121
- $methodname = "add_context_for_option_{$option}";
122
- if ( method_exists( $this, $methodname ) ) {
123
- $context = $this->$methodname( $context, $old_value, $new_value, $option, $option_page );
124
- }
125
-
126
- $this->infoMessage( 'option_updated', $context );
127
-
128
- }
129
-
130
- /**
131
- * Give some options better plain text output
132
- *
133
- * Not doing anything at the moment, because it was really difficaly to give them meaningful text values
134
- */
135
- public function getLogRowPlainTextOutput( $row ) {
136
-
137
- $message = $row->message;
138
- $context = $row->context;
139
- $message_key = $context['_message_key'];
140
-
141
- $return_message = '';
142
-
143
- // Only link to attachment if it is still available
144
- if ( 'option_updated' == $message_key ) {
145
-
146
- /*
147
- $option = isset( $context["option"] ) ? $context["option"] : null;
148
- $option_page = isset( $context["option_page"] ) ? $context["option_page"] : null;
149
- $new_value = isset( $context["new_value"] ) ? $context["new_value"] : null;
150
- $old_value = isset( $context["old_value"] ) ? $context["old_value"] : null;
151
-
152
- # $return_message = "";
153
- $arr_options_to_translate = array(
154
- "$option_page/blog_public" => array(
155
- "text" => "Updated setting Search Engine Visibility"
156
- ),
157
- "$option_page/rss_use_excerpt" => array(
158
- "text" => "Updated setting For each article in a feed, show"
159
- ),
160
- "$option_page/posts_per_rss" => array(
161
- "text" => "Updated setting for Syndication feeds show the most recent"
162
- ),
163
- "$option_page/posts_per_page" => array(
164
- "text" => "Updated setting for Blog pages show at most"
165
- )
166
- );
167
-
168
- if ( isset( $arr_options_to_translate[ "{$option_page}/{$option}" ] ) ) {
169
- $return_message = $arr_options_to_translate[ "{$option_page}/{$option}" ]["text"];
170
- }
171
- */
172
-
173
- }
174
-
175
- if ( empty( $return_message ) ) {
176
-
177
- // No specific text to output, fallback to default
178
- $return_message = parent::getLogRowPlainTextOutput( $row );
179
-
180
- }
181
-
182
- return $return_message;
183
-
184
- }
185
-
186
- /**
187
- * Get detailed output
188
- */
189
- function getLogRowDetailsOutput( $row ) {
190
-
191
- $context = $row->context;
192
- $message_key = $context['_message_key'];
193
- $output = '';
194
-
195
- $option = isset( $context['option'] ) ? $context['option'] : null;
196
- $option_page = isset( $context['option_page'] ) ? $context['option_page'] : null;
197
- $new_value = isset( $context['new_value'] ) ? $context['new_value'] : null;
198
- $old_value = isset( $context['old_value'] ) ? $context['old_value'] : null;
199
-
200
- $tmpl_row = '
201
  <tr>
202
  <td>%1$s</td>
203
  <td>%2$s</td>
204
  </tr>
205
  ';
206
 
207
- if ( 'option_updated' == $message_key ) {
208
-
209
- // $message = 'Old value was {old_value} and new value is {new_value}';
210
- $output .= "<table class='SimpleHistoryLogitem__keyValueTable'>";
211
-
212
- // Output old and new values
213
- if ( $context['new_value'] || $context['old_value'] ) {
214
-
215
- $option_custom_output = '';
216
- $methodname = "get_details_output_for_option_{$option}";
217
-
218
- if ( method_exists( $this, $methodname ) ) {
219
- $option_custom_output = $this->$methodname( $context, $old_value, $new_value, $option, $option_page, $tmpl_row );
220
- }
221
-
222
- if ( empty( $option_custom_output ) ) {
223
-
224
- // all other options or fallback if custom output did not find all it's stuff
225
- $more = __( '&hellip;' );
226
- $trim_length = 250;
227
-
228
- $trimmed_new_value = substr( $new_value, 0, $trim_length );
229
- $trimmed_old_value = substr( $old_value, 0, $trim_length );
230
-
231
- if ( strlen( $new_value ) > $trim_length ) {
232
- $trimmed_new_value .= $more;
233
- }
234
-
235
- if ( strlen( $old_value ) > $trim_length ) {
236
- $trimmed_old_value .= $more;
237
- }
238
-
239
- $output .= sprintf(
240
- $tmpl_row,
241
- __( 'New value', 'simple-history' ),
242
- esc_html( $trimmed_new_value )
243
- );
244
-
245
- $output .= sprintf(
246
- $tmpl_row,
247
- __( 'Old value', 'simple-history' ),
248
- esc_html( $trimmed_old_value )
249
- );
250
-
251
- } else {
252
-
253
- $output .= $option_custom_output;
254
-
255
- }
256
- } // End if().
257
-
258
- // If key option_page this was saved from regular settings pages
259
- if ( ! empty( $option_page ) ) {
260
-
261
- $output .= sprintf(
262
- '
263
  <tr>
264
  <td>%1$s</td>
265
  <td><a href="%3$s">%2$s</a></td>
266
  </tr>
267
  ',
268
- __( 'Settings page', 'simple-history' ),
269
- esc_html( $context['option_page'] ),
270
- admin_url( "options-{$option_page}.php" )
271
- );
272
-
273
- }
274
-
275
- // If option = permalink_structure then we did it from permalink page
276
- if ( ! empty( $option ) && ( 'permalink_structure' == $option || 'tag_base' == $option || 'category_base' == $option ) ) {
277
-
278
- $output .= sprintf(
279
- '
280
  <tr>
281
  <td>%1$s</td>
282
  <td><a href="%3$s">%2$s</a></td>
283
  </tr>
284
  ',
285
- __( 'Settings page', 'simple-history' ),
286
- 'permalink',
287
- admin_url( 'options-permalink.php' )
288
- );
289
-
290
- }
291
-
292
- $output .= '</table>';
293
-
294
- }// End if().
295
-
296
- return $output;
297
-
298
- }
299
-
300
-
301
- /**
302
- * Page on front = "Front page displays" -> Your latest posts / A static page
303
- * value 0 = Your latest post
304
- * value int n = A static page
305
- */
306
- function add_context_for_option_page_on_front( $context, $old_value, $new_value, $option, $option_page ) {
307
-
308
- if ( ! empty( $old_value ) && is_numeric( $old_value ) ) {
309
-
310
- $old_post = get_post( $old_value );
311
-
312
- if ( $old_post ) {
313
- $context['old_post_title'] = $old_post->post_title;
314
- }
315
- }
316
-
317
- if ( ! empty( $new_value ) && is_numeric( $new_value ) ) {
318
-
319
- $new_post = get_post( $new_value );
320
-
321
- if ( $new_post ) {
322
- $context['new_post_title'] = $new_post->post_title;
323
- }
324
- }
325
-
326
- return $context;
327
-
328
- }
329
-
330
- function add_context_for_option_page_for_posts( $context, $old_value, $new_value, $option, $option_page ) {
331
-
332
- // Get same info as for page_on_front
333
- $context = call_user_func_array( array( $this, 'add_context_for_option_page_on_front' ), func_get_args() );
334
-
335
- return $context;
336
-
337
- }
338
-
339
- function get_details_output_for_option_page_for_posts( $context, $old_value, $new_value, $option, $option_page ) {
340
-
341
- $output = call_user_func_array( array( $this, 'get_details_output_for_option_page_on_front' ), func_get_args() );
342
-
343
- return $output;
344
-
345
- }
346
-
347
- /**
348
- * Add detailed output for page_on_front
349
- *
350
- * @return string output
351
- */
352
- function get_details_output_for_option_page_on_front( $context, $old_value, $new_value, $option, $option_page, $tmpl_row ) {
353
-
354
- $output = '';
355
-
356
- if ( $new_value && ! empty( $context['new_post_title'] ) ) {
357
-
358
- $post_title_with_link = '';
359
-
360
- if ( get_post_status( $new_value ) ) {
361
- $post_title_with_link = sprintf( '<a href="%1$s">%2$s</a>', get_edit_post_link( $new_value ), esc_html( $context['new_post_title'] ) );
362
- } else {
363
- $post_title_with_link = esc_html( $context['new_post_title'] );
364
- }
365
-
366
- $output .= sprintf(
367
- $tmpl_row,
368
- __( 'New value', 'simple-history' ),
369
- sprintf( __( 'Page %1$s', 'simple-history' ), $post_title_with_link )
370
- );
371
-
372
- }
373
- if ( intval( $new_value ) == 0 ) {
374
-
375
- $output .= sprintf(
376
- $tmpl_row,
377
- __( 'New value', 'simple-history' ),
378
- __( 'Your latests posts', 'simple-history' )
379
- );
380
-
381
- }
382
-
383
- if ( $old_value && ! empty( $context['old_post_title'] ) ) {
384
- $post_title_with_link = '';
385
-
386
- if ( get_post_status( $old_value ) ) {
387
- $post_title_with_link = sprintf( '<a href="%1$s">%2$s</a>', get_edit_post_link( $old_value ), esc_html( $context['old_post_title'] ) );
388
- } else {
389
- $post_title_with_link = esc_html( $context['old_post_title'] );
390
- }
391
-
392
- $output .= sprintf(
393
- $tmpl_row,
394
- __( 'Old value', 'simple-history' ),
395
- sprintf( __( 'Page %1$s', 'simple-history' ), $post_title_with_link )
396
- );
397
-
398
- }
399
-
400
- if ( intval( $old_value ) == 0 ) {
401
-
402
- $output .= sprintf(
403
- $tmpl_row,
404
- __( 'Old value', 'simple-history' ),
405
- __( 'Your latests posts', 'simple-history' )
406
- );
407
-
408
- }
409
-
410
- return $output;
411
-
412
- } // custom output page_on_front
413
-
414
-
415
- /**
416
- * "default_category" = Writing Settings » Default Post Category
417
- */
418
- function add_context_for_option_default_category( $context, $old_value, $new_value, $option, $option_page ) {
419
-
420
- if ( ! empty( $old_value ) && is_numeric( $old_value ) ) {
421
-
422
- $old_category_name = get_the_category_by_ID( $old_value );
423
-
424
- if ( ! is_wp_error( $old_category_name ) ) {
425
-
426
- $context['old_category_name'] = $old_category_name;
427
-
428
- }
429
- }
430
-
431
- if ( ! empty( $new_value ) && is_numeric( $new_value ) ) {
432
-
433
- $new_category_name = get_the_category_by_ID( $new_value );
434
-
435
- if ( ! is_wp_error( $new_category_name ) ) {
436
-
437
- $context['new_category_name'] = $new_category_name;
438
-
439
- }
440
- }
441
-
442
- return $context;
443
-
444
- }
445
-
446
- function add_context_for_option_default_email_category( $context, $old_value, $new_value, $option, $option_page ) {
447
-
448
- $context = call_user_func_array( array( $this, 'add_context_for_option_default_category' ), func_get_args() );
449
-
450
- return $context;
451
-
452
- }
453
-
454
-
455
- /**
456
- * Add detailed output for default_category
457
- *
458
- * @return string output
459
- */
460
- function get_details_output_for_option_default_category( $context, $old_value, $new_value, $option, $option_page, $tmpl_row ) {
461
-
462
- $old_category_name = isset( $context['old_category_name'] ) ? $context['old_category_name'] : null;
463
- $new_category_name = isset( $context['new_category_name'] ) ? $context['new_category_name'] : null;
464
- $output = '';
465
-
466
- if ( $old_category_name ) {
467
-
468
- $output .= sprintf(
469
- $tmpl_row,
470
- __( 'Old value', 'simple-history' ),
471
- esc_html( $old_category_name )
472
- );
473
-
474
- }
475
-
476
- if ( $new_category_name ) {
477
-
478
- $output .= sprintf(
479
- $tmpl_row,
480
- __( 'New value', 'simple-history' ),
481
- esc_html( $new_category_name )
482
- );
483
-
484
- }
485
-
486
- return $output;
487
-
488
- }
489
-
490
- function get_details_output_for_option_default_email_category( $context, $old_value, $new_value, $option, $option_page, $tmpl_row ) {
491
-
492
- $output = call_user_func_array( array( $this, 'get_details_output_for_option_default_category' ), func_get_args() );
493
-
494
- return $output;
495
-
496
- }
497
-
498
  }
1
  <?php
2
 
3
+ defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logs changes to wordpress options
7
  */
8
+ class SimpleOptionsLogger extends SimpleLogger
9
+ {
10
+
11
+
12
+ public $slug = __CLASS__;
13
+
14
+ /**
15
+ * Get array with information about this logger
16
+ *
17
+ * @return array
18
+ */
19
+ function getInfo()
20
+ {
21
+
22
+ $arr_info = array(
23
+ 'name' => 'Options Logger',
24
+ 'description' => 'Logs updates to WordPress settings',
25
+ 'capability' => 'manage_options',
26
+ 'messages' => array(
27
+ // 'option_updated' => __('Updated option "{option}" on settings page "{option_page}"', "simple-history"),
28
+ 'option_updated' => __('Updated option "{option}"', 'simple-history'),
29
+ /*
30
+ Updated option "default_comment_status" on settings page "discussion"
31
+ Edited option "default_comment_status" on settings page "discussion"
32
+ Modified option "default_comment_status" on settings page "discussion"
33
+
34
+ Edited settings page "discussion" and the "default_comment_status" options
35
+
36
+ */
37
+ ),
38
+ 'labels' => array(
39
+ 'search' => array(
40
+ 'label' => _x('Options', 'Options logger: search', 'simple-history'),
41
+ 'options' => array(
42
+ _x('Changed options', 'Options logger: search', 'simple-history') => array(
43
+ 'option_updated'
44
+ ),
45
+ ),
46
+ ),// end search array
47
+ ),// end labels
48
+ );
49
+
50
+ return $arr_info;
51
+ }
52
+
53
+ function loaded()
54
+ {
55
+
56
+ add_action('updated_option', array( $this, 'on_updated_option' ), 10, 3);
57
+ }
58
+
59
+ function on_updated_option($option, $old_value, $new_value)
60
+ {
61
+
62
+ if (empty($_SERVER['REQUEST_URI'])) {
63
+ return;
64
+ }
65
+
66
+ $arr_option_pages = array(
67
+ 0 => 'options.php',
68
+ 1 => 'options-permalink.php',
69
+ );
70
+
71
+ // We only want to log options being added via pages in $arr_option_pages
72
+ if (! in_array(basename($_SERVER['REQUEST_URI']), $arr_option_pages) || basename(dirname($_SERVER['REQUEST_URI'])) !== 'wp-admin') {
73
+ return;
74
+ }
75
+
76
+ // Also only if "option_page" is set to one of these "built in" ones
77
+ // We don't wanna start loging things from other plugins, like EDD
78
+ $option_page = isset($_REQUEST['option_page']) ? $_REQUEST['option_page'] : ''; // general | discussion | ...
79
+
80
+ $arr_valid_option_pages = array(
81
+ 'general',
82
+ 'discussion',
83
+ 'media',
84
+ 'reading',
85
+ 'writing',
86
+ );
87
+
88
+ $is_valid_options_page = $option_page && in_array($option_page, $arr_valid_option_pages);
89
+
90
+ // Permalink settings page does not post any "option_page", so use http referer instead
91
+ if (strpos($_SERVER['REQUEST_URI'], 'options-permalink.php') !== false) {
92
+ $is_valid_options_page = true;
93
+ $options_page = 'permalink';
94
+ }
95
+
96
+ if (! $is_valid_options_page) {
97
+ return;
98
+ }
99
+
100
+ // Check if option name is ok
101
+ // For example if you change front page displays setting the "rewrite_rules" options gets updated too
102
+ $arr_invalid_option_names = array(
103
+ 'rewrite_rules'
104
+ );
105
+
106
+ if (in_array($option, $arr_invalid_option_names)) {
107
+ return;
108
+ }
109
+
110
+ $context = array(
111
+ 'option' => $option,
112
+ 'old_value' => $old_value,
113
+ 'new_value' => $new_value,
114
+ 'option_page' => $option_page,
115
+ // 'referer' => wp_get_referer(),
116
+ // 'REQUEST_URI' => $_SERVER['REQUEST_URI'],
117
+ // '$_REQUEST' => print_r($_REQUEST, true),
118
+ );
119
+
120
+ // Store a bit more about some options
121
+ // Like "page_on_front" we also store post title
122
+ // Check for a method for current option in this class and calls it automagically
123
+ $methodname = "add_context_for_option_{$option}";
124
+ if (method_exists($this, $methodname)) {
125
+ $context = $this->$methodname($context, $old_value, $new_value, $option, $option_page);
126
+ }
127
+
128
+ $this->infoMessage('option_updated', $context);
129
+ }
130
+
131
+ /**
132
+ * Give some options better plain text output
133
+ *
134
+ * Not doing anything at the moment, because it was really difficaly to give them meaningful text values
135
+ */
136
+ public function getLogRowPlainTextOutput($row)
137
+ {
138
+
139
+ $message = $row->message;
140
+ $context = $row->context;
141
+ $message_key = $context['_message_key'];
142
+
143
+ $return_message = '';
144
+
145
+ // Only link to attachment if it is still available
146
+ if ('option_updated' == $message_key) {
147
+ /*
148
+ $option = isset( $context["option"] ) ? $context["option"] : null;
149
+ $option_page = isset( $context["option_page"] ) ? $context["option_page"] : null;
150
+ $new_value = isset( $context["new_value"] ) ? $context["new_value"] : null;
151
+ $old_value = isset( $context["old_value"] ) ? $context["old_value"] : null;
152
+
153
+ # $return_message = "";
154
+ $arr_options_to_translate = array(
155
+ "$option_page/blog_public" => array(
156
+ "text" => "Updated setting Search Engine Visibility"
157
+ ),
158
+ "$option_page/rss_use_excerpt" => array(
159
+ "text" => "Updated setting For each article in a feed, show"
160
+ ),
161
+ "$option_page/posts_per_rss" => array(
162
+ "text" => "Updated setting for Syndication feeds show the most recent"
163
+ ),
164
+ "$option_page/posts_per_page" => array(
165
+ "text" => "Updated setting for Blog pages show at most"
166
+ )
167
+ );
168
+
169
+ if ( isset( $arr_options_to_translate[ "{$option_page}/{$option}" ] ) ) {
170
+ $return_message = $arr_options_to_translate[ "{$option_page}/{$option}" ]["text"];
171
+ }
172
+ */
173
+ }
174
+
175
+ if (empty($return_message)) {
176
+ // No specific text to output, fallback to default
177
+ $return_message = parent::getLogRowPlainTextOutput($row);
178
+ }
179
+
180
+ return $return_message;
181
+ }
182
+
183
+ /**
184
+ * Get detailed output
185
+ */
186
+ function getLogRowDetailsOutput($row)
187
+ {
188
+
189
+ $context = $row->context;
190
+ $message_key = $context['_message_key'];
191
+ $output = '';
192
+
193
+ $option = isset($context['option']) ? $context['option'] : null;
194
+ $option_page = isset($context['option_page']) ? $context['option_page'] : null;
195
+ $new_value = isset($context['new_value']) ? $context['new_value'] : null;
196
+ $old_value = isset($context['old_value']) ? $context['old_value'] : null;
197
+
198
+ $tmpl_row = '
 
 
199
  <tr>
200
  <td>%1$s</td>
201
  <td>%2$s</td>
202
  </tr>
203
  ';
204
 
205
+ if ('option_updated' == $message_key) {
206
+ // $message = 'Old value was {old_value} and new value is {new_value}';
207
+ $output .= "<table class='SimpleHistoryLogitem__keyValueTable'>";
208
+
209
+ // Output old and new values
210
+ if ($context['new_value'] || $context['old_value']) {
211
+ $option_custom_output = '';
212
+ $methodname = "get_details_output_for_option_{$option}";
213
+
214
+ if (method_exists($this, $methodname)) {
215
+ $option_custom_output = $this->$methodname($context, $old_value, $new_value, $option, $option_page, $tmpl_row);
216
+ }
217
+
218
+ if (empty($option_custom_output)) {
219
+ // all other options or fallback if custom output did not find all it's stuff
220
+ $more = __('&hellip;');
221
+ $trim_length = 250;
222
+
223
+ $trimmed_new_value = substr($new_value, 0, $trim_length);
224
+ $trimmed_old_value = substr($old_value, 0, $trim_length);
225
+
226
+ if (strlen($new_value) > $trim_length) {
227
+ $trimmed_new_value .= $more;
228
+ }
229
+
230
+ if (strlen($old_value) > $trim_length) {
231
+ $trimmed_old_value .= $more;
232
+ }
233
+
234
+ $output .= sprintf(
235
+ $tmpl_row,
236
+ __('New value', 'simple-history'),
237
+ esc_html($trimmed_new_value)
238
+ );
239
+
240
+ $output .= sprintf(
241
+ $tmpl_row,
242
+ __('Old value', 'simple-history'),
243
+ esc_html($trimmed_old_value)
244
+ );
245
+ } else {
246
+ $output .= $option_custom_output;
247
+ }
248
+ } // End if().
249
+
250
+ // If key option_page this was saved from regular settings pages
251
+ if (! empty($option_page)) {
252
+ $output .= sprintf(
253
+ '
 
 
 
 
 
 
 
254
  <tr>
255
  <td>%1$s</td>
256
  <td><a href="%3$s">%2$s</a></td>
257
  </tr>
258
  ',
259
+ __('Settings page', 'simple-history'),
260
+ esc_html($context['option_page']),
261
+ admin_url("options-{$option_page}.php")
262
+ );
263
+ }
264
+
265
+ // If option = permalink_structure then we did it from permalink page
266
+ if (! empty($option) && ( 'permalink_structure' == $option || 'tag_base' == $option || 'category_base' == $option )) {
267
+ $output .= sprintf(
268
+ '
 
 
269
  <tr>
270
  <td>%1$s</td>
271
  <td><a href="%3$s">%2$s</a></td>
272
  </tr>
273
  ',
274
+ __('Settings page', 'simple-history'),
275
+ 'permalink',
276
+ admin_url('options-permalink.php')
277
+ );
278
+ }
279
+
280
+ $output .= '</table>';
281
+ }// End if().
282
+
283
+ return $output;
284
+ }
285
+
286
+
287
+ /**
288
+ * Page on front = "Front page displays" -> Your latest posts / A static page
289
+ * value 0 = Your latest post
290
+ * value int n = A static page
291
+ */
292
+ function add_context_for_option_page_on_front($context, $old_value, $new_value, $option, $option_page)
293
+ {
294
+
295
+ if (! empty($old_value) && is_numeric($old_value)) {
296
+ $old_post = get_post($old_value);
297
+
298
+ if ($old_post) {
299
+ $context['old_post_title'] = $old_post->post_title;
300
+ }
301
+ }
302
+
303
+ if (! empty($new_value) && is_numeric($new_value)) {
304
+ $new_post = get_post($new_value);
305
+
306
+ if ($new_post) {
307
+ $context['new_post_title'] = $new_post->post_title;
308
+ }
309
+ }
310
+
311
+ return $context;
312
+ }
313
+
314
+ function add_context_for_option_page_for_posts($context, $old_value, $new_value, $option, $option_page)
315
+ {
316
+
317
+ // Get same info as for page_on_front
318
+ $context = call_user_func_array(array( $this, 'add_context_for_option_page_on_front' ), func_get_args());
319
+
320
+ return $context;
321
+ }
322
+
323
+ function get_details_output_for_option_page_for_posts($context, $old_value, $new_value, $option, $option_page)
324
+ {
325
+
326
+ $output = call_user_func_array(array( $this, 'get_details_output_for_option_page_on_front' ), func_get_args());
327
+
328
+ return $output;
329
+ }
330
+
331
+ /**
332
+ * Add detailed output for page_on_front
333
+ *
334
+ * @return string output
335
+ */
336
+ function get_details_output_for_option_page_on_front($context, $old_value, $new_value, $option, $option_page, $tmpl_row)
337
+ {
338
+
339
+ $output = '';
340
+
341
+ if ($new_value && ! empty($context['new_post_title'])) {
342
+ $post_title_with_link = '';
343
+
344
+ if (get_post_status($new_value)) {
345
+ $post_title_with_link = sprintf('<a href="%1$s">%2$s</a>', get_edit_post_link($new_value), esc_html($context['new_post_title']));
346
+ } else {
347
+ $post_title_with_link = esc_html($context['new_post_title']);
348
+ }
349
+
350
+ $output .= sprintf(
351
+ $tmpl_row,
352
+ __('New value', 'simple-history'),
353
+ sprintf(__('Page %1$s', 'simple-history'), $post_title_with_link)
354
+ );
355
+ }
356
+ if (intval($new_value) == 0) {
357
+ $output .= sprintf(
358
+ $tmpl_row,
359
+ __('New value', 'simple-history'),
360
+ __('Your latests posts', 'simple-history')
361
+ );
362
+ }
363
+
364
+ if ($old_value && ! empty($context['old_post_title'])) {
365
+ $post_title_with_link = '';
366
+
367
+ if (get_post_status($old_value)) {
368
+ $post_title_with_link = sprintf('<a href="%1$s">%2$s</a>', get_edit_post_link($old_value), esc_html($context['old_post_title']));
369
+ } else {
370
+ $post_title_with_link = esc_html($context['old_post_title']);
371
+ }
372
+
373
+ $output .= sprintf(
374
+ $tmpl_row,
375
+ __('Old value', 'simple-history'),
376
+ sprintf(__('Page %1$s', 'simple-history'), $post_title_with_link)
377
+ );
378
+ }
379
+
380
+ if (intval($old_value) == 0) {
381
+ $output .= sprintf(
382
+ $tmpl_row,
383
+ __('Old value', 'simple-history'),
384
+ __('Your latests posts', 'simple-history')
385
+ );
386
+ }
387
+
388
+ return $output;
389
+ } // custom output page_on_front
390
+
391
+
392
+ /**
393
+ * "default_category" = Writing Settings » Default Post Category
394
+ */
395
+ function add_context_for_option_default_category($context, $old_value, $new_value, $option, $option_page)
396
+ {
397
+
398
+ if (! empty($old_value) && is_numeric($old_value)) {
399
+ $old_category_name = get_the_category_by_ID($old_value);
400
+
401
+ if (! is_wp_error($old_category_name)) {
402
+ $context['old_category_name'] = $old_category_name;
403
+ }
404
+ }
405
+
406
+ if (! empty($new_value) && is_numeric($new_value)) {
407
+ $new_category_name = get_the_category_by_ID($new_value);
408
+
409
+ if (! is_wp_error($new_category_name)) {
410
+ $context['new_category_name'] = $new_category_name;
411
+ }
412
+ }
413
+
414
+ return $context;
415
+ }
416
+
417
+ function add_context_for_option_default_email_category($context, $old_value, $new_value, $option, $option_page)
418
+ {
419
+
420
+ $context = call_user_func_array(array( $this, 'add_context_for_option_default_category' ), func_get_args());
421
+
422
+ return $context;
423
+ }
424
+
425
+
426
+ /**
427
+ * Add detailed output for default_category
428
+ *
429
+ * @return string output
430
+ */
431
+ function get_details_output_for_option_default_category($context, $old_value, $new_value, $option, $option_page, $tmpl_row)
432
+ {
433
+
434
+ $old_category_name = isset($context['old_category_name']) ? $context['old_category_name'] : null;
435
+ $new_category_name = isset($context['new_category_name']) ? $context['new_category_name'] : null;
436
+ $output = '';
437
+
438
+ if ($old_category_name) {
439
+ $output .= sprintf(
440
+ $tmpl_row,
441
+ __('Old value', 'simple-history'),
442
+ esc_html($old_category_name)
443
+ );
444
+ }
445
+
446
+ if ($new_category_name) {
447
+ $output .= sprintf(
448
+ $tmpl_row,
449
+ __('New value', 'simple-history'),
450
+ esc_html($new_category_name)
451
+ );
452
+ }
453
+
454
+ return $output;
455
+ }
456
+
457
+ function get_details_output_for_option_default_email_category($context, $old_value, $new_value, $option, $option_page, $tmpl_row)
458
+ {
459
+
460
+ $output = call_user_func_array(array( $this, 'get_details_output_for_option_default_category' ), func_get_args());
461
+
462
+ return $output;
463
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
  }
loggers/SimplePluginLogger.php CHANGED
@@ -1,351 +1,359 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Logs plugin installs, updates, and deletions.
7
  */
8
- class SimplePluginLogger extends SimpleLogger {
9
-
10
- /**
11
- * The logger slug. Defaulting to the class name is nice and logical I think.
12
- *
13
- * @var string $slug
14
- */
15
- public $slug = __CLASS__;
16
-
17
- /**
18
- * This variable is set if a plugins has been disabled due to an error,
19
- * like when the plugin file does not exist. We need to store this in this
20
- * weird way because there is no other way for us to get the reason.
21
- *
22
- * @var string $latest_plugin_deactivation_because_of_error_reason
23
- */
24
- public $latest_plugin_deactivation_because_of_error_reason = array();
25
-
26
- /**
27
- * Get array with information about this logger
28
- *
29
- * @return array
30
- */
31
- public function getInfo() {
32
-
33
- $arr_info = array(
34
- 'name' => 'Plugin Logger',
35
- 'description' => 'Logs plugin installs, uninstalls and updates',
36
- 'capability' => 'activate_plugins',
37
- 'messages' => array(
38
-
39
- 'plugin_activated' => _x(
40
- 'Activated plugin "{plugin_name}"',
41
- 'Plugin was non-silently activated by a user',
42
- 'simple-history'
43
- ),
44
-
45
- 'plugin_deactivated' => _x(
46
- 'Deactivated plugin "{plugin_name}"',
47
- 'Plugin was non-silently deactivated by a user',
48
- 'simple-history'
49
- ),
50
-
51
- 'plugin_installed' => _x(
52
- 'Installed plugin "{plugin_name}"',
53
- 'Plugin was installed',
54
- 'simple-history'
55
- ),
56
-
57
- 'plugin_installed_failed' => _x(
58
- 'Failed to install plugin "{plugin_name}"',
59
- 'Plugin failed to install',
60
- 'simple-history'
61
- ),
62
-
63
- 'plugin_updated' => _x(
64
- 'Updated plugin "{plugin_name}" to version {plugin_version} from {plugin_prev_version}',
65
- 'Plugin was updated',
66
- 'simple-history'
67
- ),
68
-
69
- 'plugin_update_failed' => _x(
70
- 'Failed to update plugin "{plugin_name}"',
71
- 'Plugin update failed',
72
- 'simple-history'
73
- ),
74
-
75
- 'plugin_deleted' => _x(
76
- 'Deleted plugin "{plugin_name}"',
77
- 'Plugin files was deleted',
78
- 'simple-history'
79
- ),
80
-
81
- // Bulk versions.
82
- 'plugin_bulk_updated' => _x(
83
- 'Updated plugin "{plugin_name}" to {plugin_version} from {plugin_prev_version}',
84
- 'Plugin was updated in bulk',
85
- 'simple-history'
86
- ),
87
-
88
- // Plugin disabled due to some error.
89
- 'plugin_disabled_because_error' => _x(
90
- 'Deactivated plugin "{plugin_slug}" because of an error ("{deactivation_reason}").',
91
- 'Plugin was disabled because of an error',
92
- 'simple-history'
93
- ),
94
-
95
- ), // Messages.
96
- 'labels' => array(
97
- 'search' => array(
98
- 'label' => _x( 'Plugins', 'Plugin logger: search', 'simple-history' ),
99
- 'label_all' => _x( 'All plugin activity', 'Plugin logger: search', 'simple-history' ),
100
- 'options' => array(
101
- _x( 'Activated plugins', 'Plugin logger: search', 'simple-history' ) => array(
102
- 'plugin_activated',
103
- ),
104
- _x( 'Deactivated plugins', 'Plugin logger: search', 'simple-history' ) => array(
105
- 'plugin_deactivated',
106
- 'plugin_disabled_because_error',
107
- ),
108
- _x( 'Installed plugins', 'Plugin logger: search', 'simple-history' ) => array(
109
- 'plugin_installed',
110
- ),
111
- _x( 'Failed plugin installs', 'Plugin logger: search', 'simple-history' ) => array(
112
- 'plugin_installed_failed',
113
- ),
114
- _x( 'Updated plugins', 'Plugin logger: search', 'simple-history' ) => array(
115
- 'plugin_updated',
116
- 'plugin_bulk_updated',
117
- ),
118
- _x( 'Failed plugin updates', 'Plugin logger: search', 'simple-history' ) => array(
119
- 'plugin_update_failed',
120
- ),
121
- _x( 'Deleted plugins', 'Plugin logger: search', 'simple-history' ) => array(
122
- 'plugin_deleted',
123
- ),
124
- ),
125
- ), // search array.
126
- ), // labels.
127
- );
128
-
129
- return $arr_info;
130
-
131
- }
132
-
133
- /**
134
- * Plugin loaded
135
- */
136
- public function loaded() {
137
-
138
- /**
139
- * At least the plugin bulk upgrades fires this action before upgrade
140
- * We use it to fetch the current version of all plugins, before they are upgraded
141
- */
142
- add_filter( 'upgrader_pre_install', array( $this, 'save_versions_before_update' ), 10, 2 );
143
-
144
- // Clear our transient after an update is done
145
- // Removed because something probably changed in core and this was fired earlier than it used to be
146
- // add_action( 'delete_site_transient_update_plugins', array( $this, "remove_saved_versions" ) );
147
- // Fires after a plugin has been activated.
148
- // If a plugin is silently activated (such as during an update),
149
- // this hook does not fire.
150
- add_action( 'activated_plugin', array( $this, 'on_activated_plugin' ), 10, 2 );
151
-
152
- // Fires after a plugin is deactivated.
153
- // If a plugin is silently deactivated (such as during an update),
154
- // this hook does not fire.
155
- add_action( 'deactivated_plugin', array( $this, 'on_deactivated_plugin' ), 10, 2 );
156
-
157
- // Fires after the upgrades has done it's thing.
158
- // Check hook extra for upgrader initiator.
159
- add_action( 'upgrader_process_complete', array( $this, 'on_upgrader_process_complete' ), 10, 2 );
160
-
161
- // Detect files removed.
162
- add_action( 'setted_transient', array( $this, 'on_setted_transient_for_remove_files' ), 10, 2 );
163
-
164
- add_action( 'admin_action_delete-selected', array( $this, 'on_action_delete_selected' ), 10, 1 );
165
-
166
- // Ajax function to get info from GitHub repo. Used by "View plugin info"-link for plugin installs.
167
- add_action( 'wp_ajax_SimplePluginLogger_GetGitHubPluginInfo', array( $this, 'ajax_GetGitHubPluginInfo' ) );
168
-
169
- // If the Github Update plugin is not installed we need to get extra fields used by it.
170
- // So need to hook filter "extra_plugin_headers" ourself.
171
- add_filter(
172
- 'extra_plugin_headers', function( $arr_headers ) {
173
- $arr_headers[] = 'GitHub Plugin URI';
174
- return $arr_headers;
175
- }
176
- );
177
-
178
- // There is no way to use a filter and detect a plugin that is disabled because it can't be found or similar error.
179
- // So we hook into gettext and look for the usage of the error that is returned when this happens.
180
- add_filter( 'gettext', array( $this, 'on_gettext_detect_plugin_error_deactivation_reason' ), 10, 3 );
181
- add_filter( 'gettext', array( $this, 'on_gettext' ), 10, 3 );
182
-
183
- }
184
-
185
- /**
186
- * Things
187
- *
188
- * @param string $translation Translation.
189
- * @param string $text Text.
190
- * @param string $domain Domin.
191
- */
192
- function on_gettext_detect_plugin_error_deactivation_reason( $translation, $text, $domain ) {
193
-
194
- global $pagenow;
195
-
196
- // We only act on page plugins.php.
197
- if ( ! isset( $pagenow ) || 'plugins.php' !== $pagenow ) {
198
- return $translation;
199
- }
200
-
201
- // We only act if the untranslated text is among the following ones
202
- // (Literally these, no translation).
203
- $untranslated_texts = array(
204
- 'Plugin file does not exist.',
205
- 'Invalid plugin path.',
206
- 'The plugin does not have a valid header.',
207
- );
208
-
209
- if ( ! in_array( $text, $untranslated_texts, true ) ) {
210
- return $translation;
211
- }
212
-
213
- // Text was among our wanted texts.
214
- switch ( $text ) {
215
- case 'Plugin file does not exist.':
216
- $this->latest_plugin_deactivation_because_of_error_reason[] = 'file_does_not_exist';
217
- break;
218
- case 'Invalid plugin path.':
219
- $this->latest_plugin_deactivation_because_of_error_reason[] = 'invalid_path';
220
- break;
221
- case 'The plugin does not have a valid header.':
222
- $this->latest_plugin_deactivation_because_of_error_reason[] = 'no_valid_header';
223
- break;
224
- }
225
-
226
- return $translation;
227
- }
228
-
229
- /**
230
- * There is no way to use a filter and detect a plugin that is disabled because it can't be found or similar error.
231
- * we hook into gettext and look for the usage of the error that is returned when this happens.
232
- *
233
- * A plugin gets deactivated when plugins.php is visited function validate_active_plugins()
234
- * return new WP_Error('plugin_not_found', __('Plugin file does not exist.'));
235
- * and if invalid plugin is found then this is outputed
236
- * printf(
237
- * /* translators: 1: plugin file 2: error message
238
- * __( 'The plugin %1$s has been <strong>deactivated</strong> due to an error: %2$s' ),
239
- * '<code>' . esc_html( $plugin_file ) . '</code>',
240
- * $error->get_error_message() );
241
- *
242
- * @param string $translation Translation.
243
- * @param string $text Text.
244
- * @param string $domain Domin.
245
- */
246
- function on_gettext( $translation, $text, $domain ) {
247
-
248
- global $pagenow;
249
-
250
- // We only act on page plugins.php.
251
- if ( ! isset( $pagenow ) || 'plugins.php' !== $pagenow ) {
252
- return $translation;
253
- }
254
-
255
- // We only act if the untranslated text is among the following ones
256
- // (Literally these, no translation)
257
- $untranslated_texts = array(
258
- // This string is called later than the above
259
- 'The plugin %1$s has been <strong>deactivated</strong> due to an error: %2$s',
260
- );
261
-
262
- if ( ! in_array( $text, $untranslated_texts ) ) {
263
- return $translation;
264
- }
265
-
266
- // Directly after the string is translated 'esc_html' is called with the plugin name.
267
- // This is one of the few ways we can get the name of the plugin.
268
- // The esc_html filter is used pretty much but we make sure we only do our.
269
- // stuff the first time it's called (directly after the gettet for the plugin disabled-error..).
270
- $logger_instance = $this;
271
-
272
- add_filter(
273
- 'esc_html', function( $safe_text, $text ) use ( $logger_instance ) {
274
- static $is_called = false;
275
-
276
- if ( false === $is_called ) {
277
- $is_called = true;
278
-
279
- $deactivation_reason = array_shift( $logger_instance->latest_plugin_deactivation_because_of_error_reason );
280
-
281
- // We don't know what plugin that was that got this error and currently there does not seem to be a way to determine that.
282
- // So that's why we use such generic log messages.
283
- $logger_instance->warningMessage(
284
- 'plugin_disabled_because_error',
285
- array(
286
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
287
- 'plugin_slug' => $text,
288
- 'deactivation_reason' => $deactivation_reason,
289
- )
290
- );
291
- }
292
-
293
- return $safe_text;
294
- }, 10, 2
295
- );
296
-
297
- return $translation;
298
-
299
- } // on_gettext
300
-
301
- /**
302
- * Show readme from github in a modal win
303
- */
304
- function ajax_GetGitHubPluginInfo() {
305
-
306
- if ( ! current_user_can( 'install_plugins' ) ) {
307
- wp_die( __( "You don't have access to this page.", 'simple-history' ) );
308
- }
309
-
310
- $repo = isset( $_GET['repo'] ) ? (string) $_GET['repo'] : '';
311
-
312
- if ( ! $repo ) {
313
- wp_die( __( 'Could not find GitHub repository.', 'simple-history' ) );
314
- }
315
-
316
- $repo_parts = explode( '/', rtrim( $repo, '/' ) );
317
- if ( count( $repo_parts ) !== 5 ) {
318
- wp_die( __( 'Could not find GitHub repository.', 'simple-history' ) );
319
- }
320
-
321
- $repo_username = $repo_parts[3];
322
- $repo_repo = $repo_parts[4];
323
-
324
- // https://developer.github.com/v3/repos/contents/
325
- // https://api.github.com/repos/<username>/<repo>/readme
326
- $api_url = sprintf( 'https://api.github.com/repos/%1$s/%2$s/readme', urlencode( $repo_username ), urlencode( $repo_repo ) );
327
-
328
- // Get file. Use accept-header to get file as HTML instead of JSON
329
- $response = wp_remote_get(
330
- $api_url, array(
331
- 'headers' => array(
332
- 'accept' => 'application/vnd.github.VERSION.html',
333
- ),
334
- )
335
- );
336
-
337
- $response_body = wp_remote_retrieve_body( $response );
338
-
339
- $repo_info = '<p>' . sprintf(
340
- __( 'Viewing <code>readme</code> from repository <code><a target="_blank" href="%1$s">%2$s</a></code>.', 'simple-history' ),
341
- esc_url( $repo ),
342
- esc_html( $repo )
343
- ) . '</p>';
344
-
345
- $github_markdown_css_path = SIMPLE_HISTORY_PATH . '/css/github-markdown.css';
346
-
347
- printf(
348
- '
 
 
 
 
 
 
 
 
349
  <!doctype html>
350
  <style>
351
  body {
@@ -381,835 +389,791 @@ class SimplePluginLogger extends SimpleLogger {
381
  %2$s
382
  </div>
383
  ',
384
- $repo_info,
385
- $response_body,
386
- $github_markdown_css_path,
387
- esc_url( $repo ) // 4
388
- );
389
-
390
- // echo($response_body);
391
- exit;
392
-
393
- }
394
-
395
- /*
396
- * When a plugin has been deleted there is no way for us to get
397
- * the real name of the plugin, only the dir and main index file.
398
- * So before a plugin is deleted we save all needed info in a transient
399
- */
400
- function on_action_delete_selected() {
401
-
402
- // Same as in plugins.php
403
- if ( ! current_user_can( 'delete_plugins' ) ) {
404
- wp_die( __( 'You do not have sufficient permissions to delete plugins for this site.' ) );
405
- }
406
-
407
- // Verify delete must be set
408
- if ( ! isset( $_POST['verify-delete'] ) || ! $_POST['verify-delete'] ) {
409
- return;
410
- }
411
-
412
- // An arr of plugins must be set
413
- if ( ! isset( $_POST['checked'] ) || ! is_array( $_POST['checked'] ) ) {
414
- return;
415
- }
416
-
417
- // If we get this far it looks like a plugin is begin deleted
418
- // Get and save info about it
419
- $this->save_versions_before_update();
420
-
421
- }
422
-
423
- /**
424
- * Saves info about all installed plugins to an option.
425
- * When we are done logging then we remove the option.
426
- */
427
- function save_versions_before_update( $bool = null, $hook_extra = null ) {
428
-
429
- $plugins = get_plugins();
430
-
431
- // does not work
432
- $option_name = $this->slug . '_plugin_info_before_update';
433
-
434
- $r = update_option( $option_name, SimpleHistory::json_encode( $plugins ) );
435
-
436
- return $bool;
437
-
438
- }
439
-
440
- /**
441
- * Detect plugin being deleted
442
- * When WP is done deleting a plugin it sets a transient called plugins_delete_result:
443
- * set_transient('plugins_delete_result_' . $user_ID, $delete_result);
444
- *
445
- * We detect when that transient is set and then we have all info needed to log the plugin delete
446
- */
447
- public function on_setted_transient_for_remove_files( $transient = '', $value = '' ) {
448
-
449
- if ( ! $user_id = get_current_user_id() ) {
450
- return;
451
- }
452
-
453
- $transient_name = '_transient_plugins_delete_result_' . $user_id;
454
- if ( $transient_name !== $transient ) {
455
- return;
456
- }
457
-
458
- // We found the transient we were looking for
459
- if (
460
- isset( $_POST['action'] )
461
- && 'delete-selected' == $_POST['action']
462
- && isset( $_POST['checked'] )
463
- && is_array( $_POST['checked'] )
464
- ) {
465
-
466
- /*
467
- [checked] => Array
468
- (
469
- [0] => the-events-calendar/the-events-calendar.php
470
- )
471
- */
472
-
473
- $plugins_deleted = $_POST['checked'];
474
- $plugins_before_update = json_decode( get_option( $this->slug . '_plugin_info_before_update', false ), true );
475
-
476
- foreach ( $plugins_deleted as $plugin ) {
477
-
478
- $context = array(
479
- 'plugin' => $plugin, // plugin-name-folder/plugin-main-file.php
480
- );
481
-
482
- if ( is_array( $plugins_before_update ) && isset( $plugins_before_update[ $plugin ] ) ) {
483
- $context['plugin_name'] = $plugins_before_update[ $plugin ]['Name'];
484
- $context['plugin_title'] = $plugins_before_update[ $plugin ]['Title'];
485
- $context['plugin_description'] = $plugins_before_update[ $plugin ]['Description'];
486
- $context['plugin_author'] = $plugins_before_update[ $plugin ]['Author'];
487
- $context['plugin_version'] = $plugins_before_update[ $plugin ]['Version'];
488
- $context['plugin_url'] = $plugins_before_update[ $plugin ]['PluginURI'];
489
- }
490
-
491
- $this->infoMessage(
492
- 'plugin_deleted',
493
- $context
494
- );
495
-
496
- }
497
- }
498
-
499
- $this->remove_saved_versions();
500
-
501
- }
502
-
503
- /**
504
- * Save all plugin information before a plugin is updated or removed.
505
- * This way we can know both the old (pre updated/removed) and the current version of the plugin
506
- */
507
- /*
508
- public function save_versions_before_update() {
509
-
510
- $current_screen = get_current_screen();
511
- $request_uri = $_SERVER["SCRIPT_NAME"];
512
-
513
- // Only add option on pages where needed
514
- $do_store = false;
515
-
516
- if (
517
- SimpleHistory::ends_with( $request_uri, "/wp-admin/update.php" )
518
- && isset( $current_screen->base )
519
- && "update" == $current_screen->base
520
- ) {
521
-
522
- // Plugin update screen
523
- $do_store = true;
524
-
525
- } else if (
526
- SimpleHistory::ends_with( $request_uri, "/wp-admin/plugins.php" )
527
- && isset( $current_screen->base )
528
- && "plugins" == $current_screen->base
529
- && ( isset( $_POST["action"] ) && "delete-selected" == $_POST["action"] )
530
- ) {
531
-
532
- // Plugin delete screen, during delete
533
- $do_store = true;
534
-
535
- }
536
-
537
- if ( $do_store ) {
538
- update_option( $this->slug . "_plugin_info_before_update", SimpleHistory::json_encode( get_plugins() ) );
539
- }
540
-
541
- }
542
- */
543
-
544
- /**
545
- * when plugin updates are done wp_clean_plugins_cache() is called,
546
- * which in its turn run:
547
- * delete_site_transient( 'update_plugins' );
548
- * do_action( 'delete_site_transient_' . $transient, $transient );
549
- * delete_site_transient_update_plugins
550
- */
551
- public function remove_saved_versions() {
552
-
553
- delete_option( $this->slug . '_plugin_info_before_update' );
554
-
555
- }
556
-
557
- /**
558
- * Called when plugins is updated or installed
559
- * Called from class-wp-upgrader.php
560
- *
561
- * @param Plugin_Upgrader $this Plugin_Upgrader instance. In other contexts, $this, might
562
- * be a Theme_Upgrader or Core_Upgrade instance.
563
- * @param array $data {
564
- * Array of bulk item update data.
565
- */
566
- function on_upgrader_process_complete( $plugin_upgrader_instance, $arr_data ) {
567
-
568
- // Can't use get_plugins() here to get version of plugins updated from
569
- // Tested that, and it will get the new version (and that's the correct answer I guess. but too bad for us..)
570
- /*
571
- If an update fails then $plugin_upgrader_instance->skin->result->errors contains something like:
572
- Array
573
- (
574
- [remove_old_failed] => Array
575
- (
576
- [0] => Could not remove the old plugin.
577
- )
578
-
579
- )
580
- */
581
-
582
- /*
583
- # Contents of $arr_data in different scenarios
584
-
585
- ## WordPress core update
586
-
587
- $arr_data:
588
- Array
589
- (
590
- [action] => update
591
- [type] => core
592
- )
593
-
594
-
595
- # Plugin install
596
-
597
- $arr_data:
598
- Array
599
- (
600
- [type] => plugin
601
- [action] => install
602
- )
603
-
604
-
605
- ## Plugin update
606
-
607
- $arr_data:
608
- Array
609
- (
610
- [type] => plugin
611
- [action] => install
612
- )
613
-
614
- ## Bulk actions
615
-
616
- array(
617
- 'action' => 'update',
618
- 'type' => 'plugin',
619
- 'bulk' => true,
620
- 'plugins' => $plugins,
621
- )
622
-
623
- */
624
-
625
- // To keep track of if something was logged, so wen can output debug info only
626
- // only if we did not log anything
627
- $did_log = false;
628
-
629
- if ( isset( $arr_data['type'] ) && 'plugin' == $arr_data['type'] ) {
630
-
631
- // Single plugin install
632
- if ( isset( $arr_data['action'] ) && 'install' == $arr_data['action'] && ! $plugin_upgrader_instance->bulk ) {
633
-
634
- $upgrader_skin_options = isset( $plugin_upgrader_instance->skin->options ) && is_array( $plugin_upgrader_instance->skin->options ) ? $plugin_upgrader_instance->skin->options : array();
635
- $upgrader_skin_result = isset( $plugin_upgrader_instance->skin->result ) && is_array( $plugin_upgrader_instance->skin->result ) ? $plugin_upgrader_instance->skin->result : array();
636
- $upgrader_skin_api = isset( $plugin_upgrader_instance->skin->api ) ? $plugin_upgrader_instance->skin->api : (object) array();
637
-
638
- $plugin_slug = isset( $upgrader_skin_result['destination_name'] ) ? $upgrader_skin_result['destination_name'] : '';
639
-
640
- // Upgrader contains current info
641
- $context = array(
642
- 'plugin_slug' => $plugin_slug,
643
- 'plugin_name' => isset( $upgrader_skin_api->name ) ? $upgrader_skin_api->name : '',
644
- 'plugin_version' => isset( $upgrader_skin_api->version ) ? $upgrader_skin_api->version : '',
645
- 'plugin_author' => isset( $upgrader_skin_api->author ) ? $upgrader_skin_api->author : '',
646
- 'plugin_last_updated' => isset( $upgrader_skin_api->last_updated ) ? $upgrader_skin_api->last_updated : '',
647
- 'plugin_requires' => isset( $upgrader_skin_api->requires ) ? $upgrader_skin_api->requires : '',
648
- 'plugin_tested' => isset( $upgrader_skin_api->tested ) ? $upgrader_skin_api->tested : '',
649
- 'plugin_rating' => isset( $upgrader_skin_api->rating ) ? $upgrader_skin_api->rating : '',
650
- 'plugin_num_ratings' => isset( $upgrader_skin_api->num_ratings ) ? $upgrader_skin_api->num_ratings : '',
651
- 'plugin_downloaded' => isset( $upgrader_skin_api->downloaded ) ? $upgrader_skin_api->downloaded : '',
652
- 'plugin_added' => isset( $upgrader_skin_api->added ) ? $upgrader_skin_api->added : '',
653
- 'plugin_source_files' => $this->simpleHistory->json_encode( $plugin_upgrader_instance->result['source_files'] ),
654
-
655
- // To debug comment out these:
656
- // "debug_skin_options" => $this->simpleHistory->json_encode( $upgrader_skin_options ),
657
- // "debug_skin_result" => $this->simpleHistory->json_encode( $upgrader_skin_result ),
658
- );
659
-
660
- /*
661
- Detect install plugin from wordpress.org
662
- - options[type] = "web"
663
- - options[api] contains all we need
664
-
665
- Detect install from upload ZIP
666
- - options[type] = "upload"
667
-
668
- Also: plugins hosted at GitHub have a de-facto standard field of "GitHub Plugin URI"
669
- */
670
- $install_source = 'unknown';
671
- if ( isset( $upgrader_skin_options['type'] ) ) {
672
- $install_source = (string) $upgrader_skin_options['type'];
673
- }
674
-
675
- $context['plugin_install_source'] = $install_source;
676
-
677
- // If uploaded plugin store name of ZIP
678
- if ( 'upload' == $install_source ) {
679
-
680
- /*
681
- _debug_files
682
- {
683
- "pluginzip": {
684
- "name": "WPThumb-master.zip",
685
- "type": "application\/zip",
686
- "tmp_name": "\/Applications\/MAMP\/tmp\/php\/phpnThImc",
687
- "error": 0,
688
- "size": 2394625
689
- }
690
- }
691
- */
692
-
693
- if ( isset( $_FILES['pluginzip']['name'] ) ) {
694
- $plugin_upload_name = $_FILES['pluginzip']['name'];
695
- $context['plugin_upload_name'] = $plugin_upload_name;
696
- }
697
- }
698
-
699
- if ( is_a( $plugin_upgrader_instance->skin->result, 'WP_Error' ) ) {
700
-
701
- // Add errors
702
- // Errors is in original wp admin language
703
- $context['error_messages'] = $this->simpleHistory->json_encode( $plugin_upgrader_instance->skin->result->errors );
704
- $context['error_data'] = $this->simpleHistory->json_encode( $plugin_upgrader_instance->skin->result->error_data );
705
-
706
- $this->infoMessage(
707
- 'plugin_installed_failed',
708
- $context
709
- );
710
-
711
- $did_log = true;
712
-
713
- } else {
714
-
715
- // Plugin was successfully installed
716
- // Try to grab more info from the readme
717
- // Would be nice to grab a screenshot, but that is difficult since they often are stored remotely
718
- $plugin_destination = isset( $plugin_upgrader_instance->result['destination'] ) ? $plugin_upgrader_instance->result['destination'] : null;
719
-
720
- if ( $plugin_destination ) {
721
-
722
- $plugin_info = $plugin_upgrader_instance->plugin_info();
723
-
724
- $plugin_data = array();
725
- if ( file_exists( WP_PLUGIN_DIR . '/' . $plugin_info ) ) {
726
- $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_info, true, false );
727
- }
728
-
729
- $context['plugin_name'] = isset( $plugin_data['Name'] ) ? $plugin_data['Name'] : '';
730
- $context['plugin_description'] = isset( $plugin_data['Description'] ) ? $plugin_data['Description'] : '';
731
- $context['plugin_url'] = isset( $plugin_data['PluginURI'] ) ? $plugin_data['PluginURI'] : '';
732
- $context['plugin_version'] = isset( $plugin_data['Version'] ) ? $plugin_data['Version'] : '';
733
- $context['plugin_author'] = isset( $plugin_data['AuthorName'] ) ? $plugin_data['AuthorName'] : '';
734
-
735
- // Comment out these to debug plugin installs
736
- // $context["debug_plugin_data"] = $this->simpleHistory->json_encode( $plugin_data );
737
- // $context["debug_plugin_info"] = $this->simpleHistory->json_encode( $plugin_info );
738
- if ( ! empty( $plugin_data['GitHub Plugin URI'] ) ) {
739
- $context['plugin_github_url'] = $plugin_data['GitHub Plugin URI'];
740
- }
741
- }
742
-
743
- $this->infoMessage(
744
- 'plugin_installed',
745
- $context
746
- );
747
-
748
- $did_log = true;
749
-
750
- }// End if().
751
- } // End if().
752
-
753
- // Single plugin update
754
- if ( isset( $arr_data['action'] ) && 'update' == $arr_data['action'] && ! $plugin_upgrader_instance->bulk ) {
755
-
756
- // No plugin info in instance, so get it ourself
757
- $plugin_data = array();
758
- if ( file_exists( WP_PLUGIN_DIR . '/' . $arr_data['plugin'] ) ) {
759
- $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $arr_data['plugin'], true, false );
760
- }
761
-
762
- // autoptimize/autoptimize.php
763
- $plugin_slug = dirname( $arr_data['plugin'] );
764
-
765
- $context = array(
766
- 'plugin_slug' => $plugin_slug,
767
- 'request' => $this->simpleHistory->json_encode( $_REQUEST ),
768
- 'plugin_name' => $plugin_data['Name'],
769
- 'plugin_title' => $plugin_data['Title'],
770
- 'plugin_description' => $plugin_data['Description'],
771
- 'plugin_author' => $plugin_data['Author'],
772
- 'plugin_version' => $plugin_data['Version'],
773
- 'plugin_url' => $plugin_data['PluginURI'],
774
- 'plugin_source_files' => $this->simpleHistory->json_encode( $plugin_upgrader_instance->result['source_files'] ),
775
- );
776
-
777
- // update status for plugins are in response
778
- // plugin folder + index file = key
779
- // use transient to get url and package
780
- $update_plugins = get_site_transient( 'update_plugins' );
781
- if ( $update_plugins && isset( $update_plugins->response[ $arr_data['plugin'] ] ) ) {
782
-
783
- /*
784
- $update_plugins[plugin_path/slug]:
785
- {
786
- "id": "8986",
787
- "slug": "autoptimize",
788
- "plugin": "autoptimize/autoptimize.php",
789
- "new_version": "1.9.1",
790
- "url": "https://wordpress.org/plugins/autoptimize/",
791
- "package": "https://downloads.wordpress.org/plugin/autoptimize.1.9.1.zip"
792
- }
793
- */
794
- // for debug purposes the update_plugins key can be added
795
- // $context["update_plugins"] = $this->simpleHistory->json_encode( $update_plugins );
796
- $plugin_update_info = $update_plugins->response[ $arr_data['plugin'] ];
797
-
798
- // autoptimize/autoptimize.php
799
- if ( isset( $plugin_update_info->plugin ) ) {
800
- $context['plugin_update_info_plugin'] = $plugin_update_info->plugin;
801
- }
802
-
803
- // https://downloads.wordpress.org/plugin/autoptimize.1.9.1.zip
804
- if ( isset( $plugin_update_info->package ) ) {
805
- $context['plugin_update_info_package'] = $plugin_update_info->package;
806
- }
807
- }
808
-
809
- // To get old version we use our option
810
- $plugins_before_update = json_decode( get_option( $this->slug . '_plugin_info_before_update', false ), true );
811
- if ( is_array( $plugins_before_update ) && isset( $plugins_before_update[ $arr_data['plugin'] ] ) ) {
812
-
813
- $context['plugin_prev_version'] = $plugins_before_update[ $arr_data['plugin'] ]['Version'];
814
-
815
- }
816
-
817
- if ( is_a( $plugin_upgrader_instance->skin->result, 'WP_Error' ) ) {
818
-
819
- // Add errors
820
- // Errors is in original wp admin language
821
- $context['error_messages'] = json_encode( $plugin_upgrader_instance->skin->result->errors );
822
- $context['error_data'] = json_encode( $plugin_upgrader_instance->skin->result->error_data );
823
-
824
- $this->infoMessage(
825
- 'plugin_update_failed',
826
- $context
827
- );
828
-
829
- $did_log = true;
830
-
831
- } else {
832
-
833
- $this->infoMessage(
834
- 'plugin_updated',
835
- $context
836
- );
837
-
838
- // echo "on_upgrader_process_complete";
839
- // sf_d( $plugin_upgrader_instance, '$plugin_upgrader_instance' );
840
- // sf_d( $arr_data, '$arr_data' );
841
- $did_log = true;
842
-
843
- }
844
- } // End if().
845
-
846
- /**
847
- * For bulk updates $arr_data looks like:
848
- * Array
849
- * (
850
- * [action] => update
851
- * [type] => plugin
852
- * [bulk] => 1
853
- * [plugins] => Array
854
- * (
855
- * [0] => plugin-folder-1/plugin-index.php
856
- * [1] => my-plugin-folder/my-plugin.php
857
- * )
858
- * )
859
- */
860
- if ( isset( $arr_data['bulk'] ) && $arr_data['bulk'] && isset( $arr_data['action'] ) && 'update' == $arr_data['action'] ) {
861
-
862
- $plugins_updated = isset( $arr_data['plugins'] ) ? (array) $arr_data['plugins'] : array();
863
-
864
- foreach ( $plugins_updated as $plugin_name ) {
865
-
866
- $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_name, true, false );
867
-
868
- $plugin_slug = dirname( $plugin_name );
869
-
870
- $context = array(
871
- 'plugin_slug' => $plugin_slug,
872
- 'plugin_name' => $plugin_data['Name'],
873
- 'plugin_title' => $plugin_data['Title'],
874
- 'plugin_description' => $plugin_data['Description'],
875
- 'plugin_author' => $plugin_data['Author'],
876
- 'plugin_version' => $plugin_data['Version'],
877
- 'plugin_url' => $plugin_data['PluginURI'],
878
- );
879
-
880
- // get url and package
881
- $update_plugins = get_site_transient( 'update_plugins' );
882
- if ( $update_plugins && isset( $update_plugins->response[ $plugin_name ] ) ) {
883
-
884
- /*
885
- $update_plugins[plugin_path/slug]:
886
- {
887
- "id": "8986",
888
- "slug": "autoptimize",
889
- "plugin": "autoptimize/autoptimize.php",
890
- "new_version": "1.9.1",
891
- "url": "https://wordpress.org/plugins/autoptimize/",
892
- "package": "https://downloads.wordpress.org/plugin/autoptimize.1.9.1.zip"
893
- }
894
- */
895
-
896
- $plugin_update_info = $update_plugins->response[ $plugin_name ];
897
-
898
- // autoptimize/autoptimize.php
899
- if ( isset( $plugin_update_info->plugin ) ) {
900
- $context['plugin_update_info_plugin'] = $plugin_update_info->plugin;
901
- }
902
-
903
- // https://downloads.wordpress.org/plugin/autoptimize.1.9.1.zip
904
- if ( isset( $plugin_update_info->package ) ) {
905
- $context['plugin_update_info_package'] = $plugin_update_info->package;
906
- }
907
- }
908
-
909
- // To get old version we use our option
910
- // @TODO: this does not always work, why?
911
- $plugins_before_update = json_decode( get_option( $this->slug . '_plugin_info_before_update', false ), true );
912
- if ( is_array( $plugins_before_update ) && isset( $plugins_before_update[ $plugin_name ] ) ) {
913
-
914
- $context['plugin_prev_version'] = $plugins_before_update[ $plugin_name ]['Version'];
915
-
916
- }
917
-
918
- $this->infoMessage(
919
- 'plugin_bulk_updated',
920
- $context
921
- );
922
-
923
- }// End foreach().
924
- }// End if().
925
- } // End if().
926
-
927
- if ( ! $did_log ) {
928
- // echo "on_upgrader_process_complete";
929
- // sf_d( $plugin_upgrader_instance, '$plugin_upgrader_instance' );
930
- // sf_d( $arr_data, '$arr_data' );
931
- // exit;
932
- }
933
-
934
- $this->remove_saved_versions();
935
-
936
- } // on upgrader_process_complete
937
-
938
-
939
- /**
940
- * Plugin is activated
941
- * plugin_name is like admin-menu-tree-page-view/index.php
942
- */
943
- function on_activated_plugin( $plugin_name, $network_wide ) {
944
-
945
- /*
946
- Plugin data returned array contains the following:
947
- 'Name' - Name of the plugin, must be unique.
948
- 'Title' - Title of the plugin and the link to the plugin's web site.
949
- 'Description' - Description of what the plugin does and/or notes from the author.
950
- 'Author' - The author's name
951
- 'AuthorURI' - The authors web site address.
952
- 'Version' - The plugin version number.
953
- 'PluginURI' - Plugin web site address.
954
- 'TextDomain' - Plugin's text domain for localization.
955
- 'DomainPath' - Plugin's relative directory path to .mo files.
956
- 'Network' - Boolean. Whether the plugin can only be activated network wide.
957
- */
958
- $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_name, true, false );
959
-
960
- $plugin_slug = dirname( $plugin_name );
961
-
962
- $context = array(
963
- 'plugin_name' => $plugin_data['Name'],
964
- 'plugin_slug' => $plugin_slug,
965
- 'plugin_title' => $plugin_data['Title'],
966
- 'plugin_description' => $plugin_data['Description'],
967
- 'plugin_author' => $plugin_data['Author'],
968
- 'plugin_version' => $plugin_data['Version'],
969
- 'plugin_url' => $plugin_data['PluginURI'],
970
- );
971
-
972
- if ( ! empty( $plugin_data['GitHub Plugin URI'] ) ) {
973
- $context['plugin_github_url'] = $plugin_data['GitHub Plugin URI'];
974
- }
975
-
976
- $this->infoMessage( 'plugin_activated', $context );
977
-
978
- } // on_activated_plugin
979
-
980
- /**
981
- * Plugin is deactivated
982
- * plugin_name is like admin-menu-tree-page-view/index.php
983
- */
984
- function on_deactivated_plugin( $plugin_name ) {
985
-
986
- $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_name, true, false );
987
- $plugin_slug = dirname( $plugin_name );
988
-
989
- $context = array(
990
- 'plugin_name' => $plugin_data['Name'],
991
- 'plugin_slug' => $plugin_slug,
992
- 'plugin_title' => $plugin_data['Title'],
993
- 'plugin_description' => $plugin_data['Description'],
994
- 'plugin_author' => $plugin_data['Author'],
995
- 'plugin_version' => $plugin_data['Version'],
996
- 'plugin_url' => $plugin_data['PluginURI'],
997
- );
998
-
999
- if ( ! empty( $plugin_data['GitHub Plugin URI'] ) ) {
1000
- $context['plugin_github_url'] = $plugin_data['GitHub Plugin URI'];
1001
- }
1002
-
1003
- $this->infoMessage( 'plugin_deactivated', $context );
1004
-
1005
- } // on_deactivated_plugin
1006
-
1007
-
1008
- /**
1009
- * Get output for detailed log section
1010
- */
1011
- function getLogRowDetailsOutput( $row ) {
1012
-
1013
- $context = $row->context;
1014
- $message_key = $context['_message_key'];
1015
- $output = '';
1016
-
1017
- // When a plugin is installed we show a bit more information
1018
- // We do it only on install because we don't want to clutter to log,
1019
- // and when something is installed the description is most useul for other
1020
- // admins on the site
1021
- if ( 'plugin_installed' === $message_key ) {
1022
-
1023
- if ( isset( $context['plugin_description'] ) ) {
1024
-
1025
- // Description includes a link to author, remove that, i.e. all text after and including <cite>
1026
- $plugin_description = $context['plugin_description'];
1027
- $cite_pos = strpos( $plugin_description, '<cite>' );
1028
- if ( $cite_pos ) {
1029
- $plugin_description = substr( $plugin_description, 0, $cite_pos );
1030
- }
1031
-
1032
- // Keys to show
1033
- $arr_plugin_keys = array(
1034
- 'plugin_description' => _x( 'Description', 'plugin logger - detailed output', 'simple-history' ),
1035
- 'plugin_install_source' => _x( 'Source', 'plugin logger - detailed output install source', 'simple-history' ),
1036
- 'plugin_install_source_file' => _x( 'Source file name', 'plugin logger - detailed output install source', 'simple-history' ),
1037
- 'plugin_version' => _x( 'Version', 'plugin logger - detailed output version', 'simple-history' ),
1038
- 'plugin_author' => _x( 'Author', 'plugin logger - detailed output author', 'simple-history' ),
1039
- 'plugin_url' => _x( 'URL', 'plugin logger - detailed output url', 'simple-history' ),
1040
- // "plugin_downloaded" => _x("Downloads", "plugin logger - detailed output downloaded", "simple-history"),
1041
- // "plugin_requires" => _x("Requires", "plugin logger - detailed output author", "simple-history"),
1042
- // "plugin_tested" => _x("Compatible up to", "plugin logger - detailed output compatible", "simple-history"),
1043
- // also available: plugin_rating, plugin_num_ratings
1044
- );
1045
-
1046
- $arr_plugin_keys = apply_filters( 'simple_history/plugin_logger/row_details_plugin_info_keys', $arr_plugin_keys );
1047
-
1048
- // Start output of plugin meta data table
1049
- $output .= "<table class='SimpleHistoryLogitem__keyValueTable'>";
1050
-
1051
- foreach ( $arr_plugin_keys as $key => $desc ) {
1052
-
1053
- $desc_output = '';
1054
-
1055
- switch ( $key ) {
1056
-
1057
- case 'plugin_downloaded':
1058
- $desc_output = esc_html( number_format_i18n( (int) $context[ $key ] ) );
1059
- break;
1060
-
1061
- // author is already formatted
1062
- case 'plugin_author':
1063
- $desc_output = $context[ $key ];
1064
- break;
1065
-
1066
- // URL needs a link
1067
- case 'plugin_url':
1068
- $desc_output = sprintf( '<a href="%1$s">%2$s</a>', esc_attr( $context['plugin_url'] ), esc_html( $context['plugin_url'] ) );
1069
- break;
1070
-
1071
- case 'plugin_description':
1072
- $desc_output = $plugin_description;
1073
- break;
1074
-
1075
- case 'plugin_install_source':
1076
- if ( ! isset( $context[ $key ] ) ) {
1077
- break;
1078
- }
1079
-
1080
- if ( 'web' == $context[ $key ] ) {
1081
- $desc_output = esc_html( __( 'WordPress Plugin Repository', 'simple-history' ) );
1082
- } elseif ( 'upload' == $context[ $key ] ) {
1083
- // $plugin_upload_name = isset( $context["plugin_upload_name"] ) ? $context["plugin_upload_name"] : __("Unknown archive name", "simple-history");
1084
- $desc_output = esc_html( __( 'Uploaded ZIP archive', 'simple-history' ) );
1085
- // $desc_output = esc_html( sprintf( __('Uploaded ZIP archive (%1$s)', "simple-history"), $plugin_upload_name ) );
1086
- // $desc_output = esc_html( sprintf( __('%1$s (uploaded ZIP archive)', "simple-history"), $plugin_upload_name ) );
1087
- } else {
1088
- $desc_output = esc_html( $context[ $key ] );
1089
- }
1090
-
1091
- break;
1092
-
1093
- case 'plugin_install_source_file':
1094
- if ( ! isset( $context['plugin_upload_name'] ) || ! isset( $context['plugin_install_source'] ) ) {
1095
- break;
1096
- }
1097
-
1098
- if ( 'upload' == $context['plugin_install_source'] ) {
1099
- $plugin_upload_name = $context['plugin_upload_name'];
1100
- $desc_output = esc_html( $plugin_upload_name );
1101
- }
1102
-
1103
- break;
1104
-
1105
- default:
1106
- $desc_output = esc_html( $context[ $key ] );
1107
- break;
1108
- }// End switch().
1109
-
1110
- if ( ! trim( $desc_output ) ) {
1111
- continue;
1112
- }
1113
-
1114
- $output .= sprintf(
1115
- '
1116
  <tr>
1117
  <td>%1$s</td>
1118
  <td>%2$s</td>
1119
  </tr>
1120
  ',
1121
- esc_html( $desc ),
1122
- $desc_output
1123
- );
1124
-
1125
- }// End foreach().
1126
-
1127
- // Add link with more info about the plugin
1128
- // If plugin_install_source = web then it should be a wordpress.org-plugin
1129
- // If plugin_github_url is set then it's a zip from a github thingie
1130
- // so use link to that.
1131
- $plugin_slug = ! empty( $context['plugin_slug'] ) ? $context['plugin_slug'] : '';
1132
-
1133
- // Slug + web as install source = show link to wordpress.org
1134
- if ( $plugin_slug && isset( $context['plugin_install_source'] ) && $context['plugin_install_source'] == 'web' ) {
1135
-
1136
- $output .= sprintf(
1137
- '
1138
  <tr>
1139
  <td></td>
1140
  <td><a title="%2$s" class="thickbox" href="%1$s">%2$s</a></td>
1141
  </tr>
1142
  ',
1143
- admin_url( "plugin-install.php?tab=plugin-information&amp;plugin={$plugin_slug}&amp;section=&amp;TB_iframe=true&amp;width=640&amp;height=550" ),
1144
- esc_html_x( 'View plugin info', 'plugin logger: plugin info thickbox title view all info', 'simple-history' )
1145
- );
1146
-
1147
- } // End if().
1148
- elseif ( isset( $context['plugin_install_source'] ) && $context['plugin_install_source'] == 'upload' && ! empty( $context['plugin_github_url'] ) ) {
1149
-
1150
- // Can't embed iframe
1151
- // Must use API instead
1152
- // https://api.github.com/repos/<username>/<repo>/readme?callback=<callbackname>
1153
- $output .= sprintf(
1154
- '
1155
  <tr>
1156
  <td></td>
1157
  <td><a title="%2$s" class="thickbox" href="%1$s">%2$s</a></td>
1158
  </tr>
1159
  ',
1160
- admin_url( sprintf( 'admin-ajax.php?action=SimplePluginLogger_GetGitHubPluginInfo&getrepo&amp;repo=%1$s&amp;TB_iframe=true&amp;width=640&amp;height=550', esc_url_raw( $context['plugin_github_url'] ) ) ),
1161
- esc_html_x( 'View plugin info', 'plugin logger: plugin info thickbox title view all info', 'simple-history' )
1162
- );
1163
-
1164
- }
1165
-
1166
- $output .= '</table>';
1167
-
1168
- }// End if().
1169
- } elseif ( 'plugin_bulk_updated' === $message_key || 'plugin_updated' === $message_key || 'plugin_activated' === $message_key || 'plugin_deactivated' === $message_key ) {
1170
-
1171
- $plugin_slug = ! empty( $context['plugin_slug'] ) ? $context['plugin_slug'] : '';
1172
-
1173
- if ( $plugin_slug && empty( $context['plugin_github_url'] ) ) {
1174
-
1175
- $link_title = esc_html_x( 'View plugin info', 'plugin logger: plugin info thickbox title', 'simple-history' );
1176
- $url = admin_url( "plugin-install.php?tab=plugin-information&amp;plugin={$plugin_slug}&amp;section=&amp;TB_iframe=true&amp;width=640&amp;height=550" );
1177
-
1178
- if ( 'plugin_updated' == $message_key || 'plugin_bulk_updated' == $message_key ) {
1179
-
1180
- $link_title = esc_html_x( 'View changelog', 'plugin logger: plugin info thickbox title', 'simple-history' );
1181
-
1182
- if ( is_multisite() ) {
1183
- $url = network_admin_url( "plugin-install.php?tab=plugin-information&amp;plugin={$plugin_slug}&amp;section=changelog&amp;TB_iframe=true&amp;width=772&amp;height=550" );
1184
- } else {
1185
- $url = admin_url( "plugin-install.php?tab=plugin-information&amp;plugin={$plugin_slug}&amp;section=changelog&amp;TB_iframe=true&amp;width=772&amp;height=550" );
1186
- }
1187
- }
1188
-
1189
- $output .= sprintf(
1190
- '<p><a title="%2$s" class="thickbox" href="%1$s">%2$s</a></p>',
1191
- $url,
1192
- $link_title
1193
- );
1194
-
1195
- } elseif ( ! empty( $context['plugin_github_url'] ) ) {
1196
-
1197
- $output .= sprintf(
1198
- '
1199
  <tr>
1200
  <td></td>
1201
  <td><a title="%2$s" class="thickbox" href="%1$s">%2$s</a></td>
1202
  </tr>
1203
  ',
1204
- admin_url( sprintf( 'admin-ajax.php?action=SimplePluginLogger_GetGitHubPluginInfo&getrepo&amp;repo=%1$s&amp;TB_iframe=true&amp;width=640&amp;height=550', esc_url_raw( $context['plugin_github_url'] ) ) ),
1205
- esc_html_x( 'View plugin info', 'plugin logger: plugin info thickbox title view all info', 'simple-history' )
1206
- );
1207
-
1208
- } // End if().
1209
- } // End if().
1210
-
1211
- return $output;
1212
-
1213
- } // getLogRowDetailsOutput
1214
-
1215
  } // class SimplePluginLogger
1
  <?php
2
 
3
+ defined('ABSPATH') || die();
4
 
5
  /**
6
  * Logs plugin installs, updates, and deletions.
7
  */
8
+ class SimplePluginLogger extends SimpleLogger
9
+ {
10
+
11
+ /**
12
+ * The logger slug. Defaulting to the class name is nice and logical I think.
13
+ *
14
+ * @var string $slug
15
+ */
16
+ public $slug = __CLASS__;
17
+
18
+ /**
19
+ * This variable is set if a plugins has been disabled due to an error,
20
+ * like when the plugin file does not exist. We need to store this in this
21
+ * weird way because there is no other way for us to get the reason.
22
+ *
23
+ * @var string $latest_plugin_deactivation_because_of_error_reason
24
+ */
25
+ public $latest_plugin_deactivation_because_of_error_reason = array();
26
+
27
+ /**
28
+ * Get array with information about this logger
29
+ *
30
+ * @return array
31
+ */
32
+ public function getInfo()
33
+ {
34
+
35
+ $arr_info = array(
36
+ 'name' => 'Plugin Logger',
37
+ 'description' => 'Logs plugin installs, uninstalls and updates',
38
+ 'capability' => 'activate_plugins',
39
+ 'messages' => array(
40
+
41
+ 'plugin_activated' => _x(
42
+ 'Activated plugin "{plugin_name}"',
43
+ 'Plugin was non-silently activated by a user',
44
+ 'simple-history'
45
+ ),
46
+
47
+ 'plugin_deactivated' => _x(
48
+ 'Deactivated plugin "{plugin_name}"',
49
+ 'Plugin was non-silently deactivated by a user',
50
+ 'simple-history'
51
+ ),
52
+
53
+ 'plugin_installed' => _x(
54
+ 'Installed plugin "{plugin_name}"',
55
+ 'Plugin was installed',
56
+ 'simple-history'
57
+ ),
58
+
59
+ 'plugin_installed_failed' => _x(
60
+ 'Failed to install plugin "{plugin_name}"',
61
+ 'Plugin failed to install',
62
+ 'simple-history'
63
+ ),
64
+
65
+ 'plugin_updated' => _x(
66
+ 'Updated plugin "{plugin_name}" to version {plugin_version} from {plugin_prev_version}',
67
+ 'Plugin was updated',
68
+ 'simple-history'
69
+ ),
70
+
71
+ 'plugin_update_failed' => _x(
72
+ 'Failed to update plugin "{plugin_name}"',
73
+ 'Plugin update failed',
74
+ 'simple-history'
75
+ ),
76
+
77
+ 'plugin_deleted' => _x(
78
+ 'Deleted plugin "{plugin_name}"',
79
+ 'Plugin files was deleted',
80
+ 'simple-history'
81
+ ),
82
+
83
+ // Bulk versions.
84
+ 'plugin_bulk_updated' => _x(
85
+ 'Updated plugin "{plugin_name}" to {plugin_version} from {plugin_prev_version}',
86
+ 'Plugin was updated in bulk',
87
+ 'simple-history'
88
+ ),
89
+
90
+ // Plugin disabled due to some error.
91
+ 'plugin_disabled_because_error' => _x(
92
+ 'Deactivated plugin "{plugin_slug}" because of an error ("{deactivation_reason}").',
93
+ 'Plugin was disabled because of an error',
94
+ 'simple-history'
95
+ ),
96
+
97
+ ), // Messages.
98
+ 'labels' => array(
99
+ 'search' => array(
100
+ 'label' => _x('Plugins', 'Plugin logger: search', 'simple-history'),
101
+ 'label_all' => _x('All plugin activity', 'Plugin logger: search', 'simple-history'),
102
+ 'options' => array(
103
+ _x('Activated plugins', 'Plugin logger: search', 'simple-history') => array(
104
+ 'plugin_activated',
105
+ ),
106
+ _x('Deactivated plugins', 'Plugin logger: search', 'simple-history') => array(
107
+ 'plugin_deactivated',
108
+ 'plugin_disabled_because_error',
109
+ ),
110
+ _x('Installed plugins', 'Plugin logger: search', 'simple-history') => array(
111
+ 'plugin_installed',
112
+ ),
113
+ _x('Failed plugin installs', 'Plugin logger: search', 'simple-history') => array(
114
+ 'plugin_installed_failed',
115
+ ),
116
+ _x('Updated plugins', 'Plugin logger: search', 'simple-history') => array(
117
+ 'plugin_updated',
118
+ 'plugin_bulk_updated',
119
+ ),
120
+ _x('Failed plugin updates', 'Plugin logger: search', 'simple-history') => array(
121
+ 'plugin_update_failed',
122
+ ),
123
+ _x('Deleted plugins', 'Plugin logger: search', 'simple-history') => array(
124
+ 'plugin_deleted',
125
+ ),
126
+ ),
127
+ ), // search array.
128
+ ), // labels.
129
+ );
130
+
131
+ return $arr_info;
132
+ }
133
+
134
+ /**
135
+ * Plugin loaded
136
+ */
137
+ public function loaded()
138
+ {
139
+
140
+ /**
141
+ * At least the plugin bulk upgrades fires this action before upgrade
142
+ * We use it to fetch the current version of all plugins, before they are upgraded
143
+ */
144
+ add_filter('upgrader_pre_install', array( $this, 'save_versions_before_update' ), 10, 2);
145
+
146
+ // Clear our transient after an update is done
147
+ // Removed because something probably changed in core and this was fired earlier than it used to be
148
+ // add_action( 'delete_site_transient_update_plugins', array( $this, "remove_saved_versions" ) );
149
+ // Fires after a plugin has been activated.
150
+ // If a plugin is silently activated (such as during an update),
151
+ // this hook does not fire.
152
+ add_action('activated_plugin', array( $this, 'on_activated_plugin' ), 10, 2);
153
+
154
+ // Fires after a plugin is deactivated.
155
+ // If a plugin is silently deactivated (such as during an update),
156
+ // this hook does not fire.
157
+ add_action('deactivated_plugin', array( $this, 'on_deactivated_plugin' ), 10, 2);
158
+
159
+ // Fires after the upgrades has done it's thing.
160
+ // Check hook extra for upgrader initiator.
161
+ add_action('upgrader_process_complete', array( $this, 'on_upgrader_process_complete' ), 10, 2);
162
+
163
+ // Detect files removed.
164
+ add_action('setted_transient', array( $this, 'on_setted_transient_for_remove_files' ), 10, 2);
165
+
166
+ add_action('admin_action_delete-selected', array( $this, 'on_action_delete_selected' ), 10, 1);
167
+
168
+ // Ajax function to get info from GitHub repo. Used by "View plugin info"-link for plugin installs.
169
+ add_action('wp_ajax_SimplePluginLogger_GetGitHubPluginInfo', array( $this, 'ajax_GetGitHubPluginInfo' ));
170
+
171
+ // If the Github Update plugin is not installed we need to get extra fields used by it.
172
+ // So need to hook filter "extra_plugin_headers" ourself.
173
+ add_filter(
174
+ 'extra_plugin_headers',
175
+ function ($arr_headers) {
176
+ $arr_headers[] = 'GitHub Plugin URI';
177
+ return $arr_headers;
178
+ }
179
+ );
180
+
181
+ // There is no way to use a filter and detect a plugin that is disabled because it can't be found or similar error.
182
+ // So we hook into gettext and look for the usage of the error that is returned when this happens.
183
+ add_filter('gettext', array( $this, 'on_gettext_detect_plugin_error_deactivation_reason' ), 10, 3);
184
+ add_filter('gettext', array( $this, 'on_gettext' ), 10, 3);
185
+ }
186
+
187
+ /**
188
+ * Things
189
+ *
190
+ * @param string $translation Translation.
191
+ * @param string $text Text.
192
+ * @param string $domain Domin.
193
+ */
194
+ function on_gettext_detect_plugin_error_deactivation_reason($translation, $text, $domain)
195
+ {
196
+
197
+ global $pagenow;
198
+
199
+ // We only act on page plugins.php.
200
+ if (! isset($pagenow) || 'plugins.php' !== $pagenow) {
201
+ return $translation;
202
+ }
203
+
204
+ // We only act if the untranslated text is among the following ones
205
+ // (Literally these, no translation).
206
+ $untranslated_texts = array(
207
+ 'Plugin file does not exist.',
208
+ 'Invalid plugin path.',
209
+ 'The plugin does not have a valid header.',
210
+ );
211
+
212
+ if (! in_array($text, $untranslated_texts, true)) {
213
+ return $translation;
214
+ }
215
+
216
+ // Text was among our wanted texts.
217
+ switch ($text) {
218
+ case 'Plugin file does not exist.':
219
+ $this->latest_plugin_deactivation_because_of_error_reason[] = 'file_does_not_exist';
220
+ break;
221
+ case 'Invalid plugin path.':
222
+ $this->latest_plugin_deactivation_because_of_error_reason[] = 'invalid_path';
223
+ break;
224
+ case 'The plugin does not have a valid header.':
225
+ $this->latest_plugin_deactivation_because_of_error_reason[] = 'no_valid_header';
226
+ break;
227
+ }
228
+
229
+ return $translation;
230
+ }
231
+
232
+ /**
233
+ * There is no way to use a filter and detect a plugin that is disabled because it can't be found or similar error.
234
+ * we hook into gettext and look for the usage of the error that is returned when this happens.
235
+ *
236
+ * A plugin gets deactivated when plugins.php is visited function validate_active_plugins()
237
+ * return new WP_Error('plugin_not_found', __('Plugin file does not exist.'));
238
+ * and if invalid plugin is found then this is outputed
239
+ * printf(
240
+ * /* translators: 1: plugin file 2: error message
241
+ * __( 'The plugin %1$s has been <strong>deactivated</strong> due to an error: %2$s' ),
242
+ * '<code>' . esc_html( $plugin_file ) . '</code>',
243
+ * $error->get_error_message() );
244
+ *
245
+ * @param string $translation Translation.
246
+ * @param string $text Text.
247
+ * @param string $domain Domin.
248
+ */
249
+ function on_gettext($translation, $text, $domain)
250
+ {
251
+
252
+ global $pagenow;
253
+
254
+ // We only act on page plugins.php.
255
+ if (! isset($pagenow) || 'plugins.php' !== $pagenow) {
256
+ return $translation;
257
+ }
258
+
259
+ // We only act if the untranslated text is among the following ones
260
+ // (Literally these, no translation)
261
+ $untranslated_texts = array(
262
+ // This string is called later than the above
263
+ 'The plugin %1$s has been <strong>deactivated</strong> due to an error: %2$s',
264
+ );
265
+
266
+ if (! in_array($text, $untranslated_texts)) {
267
+ return $translation;
268
+ }
269
+
270
+ // Directly after the string is translated 'esc_html' is called with the plugin name.
271
+ // This is one of the few ways we can get the name of the plugin.
272
+ // The esc_html filter is used pretty much but we make sure we only do our.
273
+ // stuff the first time it's called (directly after the gettet for the plugin disabled-error..).
274
+ $logger_instance = $this;
275
+
276
+ add_filter(
277
+ 'esc_html',
278
+ function ($safe_text, $text) use ($logger_instance) {
279
+ static $is_called = false;
280
+
281
+ if (false === $is_called) {
282
+ $is_called = true;
283
+
284
+ $deactivation_reason = array_shift($logger_instance->latest_plugin_deactivation_because_of_error_reason);
285
+
286
+ // We don't know what plugin that was that got this error and currently there does not seem to be a way to determine that.
287
+ // So that's why we use such generic log messages.
288
+ $logger_instance->warningMessage(
289
+ 'plugin_disabled_because_error',
290
+ array(
291
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
292
+ 'plugin_slug' => $text,
293
+ 'deactivation_reason' => $deactivation_reason,
294
+ )
295
+ );
296
+ }
297
+
298
+ return $safe_text;
299
+ },
300
+ 10,
301
+ 2
302
+ );
303
+
304
+ return $translation;
305
+ } // on_gettext
306
+
307
+ /**
308
+ * Show readme from github in a modal win
309
+ */
310
+ function ajax_GetGitHubPluginInfo()
311
+ {
312
+
313
+ if (! current_user_can('install_plugins')) {
314
+ wp_die(__("You don't have access to this page.", 'simple-history'));
315
+ }
316
+
317
+ $repo = isset($_GET['repo']) ? (string) $_GET['repo'] : '';
318
+
319
+ if (! $repo) {
320
+ wp_die(__('Could not find GitHub repository.', 'simple-history'));
321
+ }
322
+
323
+ $repo_parts = explode('/', rtrim($repo, '/'));
324
+ if (count($repo_parts) !== 5) {
325
+ wp_die(__('Could not find GitHub repository.', 'simple-history'));
326
+ }
327
+
328
+ $repo_username = $repo_parts[3];
329
+ $repo_repo = $repo_parts[4];
330
+
331
+ // https://developer.github.com/v3/repos/contents/
332
+ // https://api.github.com/repos/<username>/<repo>/readme
333
+ $api_url = sprintf('https://api.github.com/repos/%1$s/%2$s/readme', urlencode($repo_username), urlencode($repo_repo));
334
+
335
+ // Get file. Use accept-header to get file as HTML instead of JSON
336
+ $response = wp_remote_get(
337
+ $api_url,
338
+ array(
339
+ 'headers' => array(
340
+ 'accept' => 'application/vnd.github.VERSION.html',
341
+ ),
342
+ )
343
+ );
344
+
345
+ $response_body = wp_remote_retrieve_body($response);
346
+
347
+ $repo_info = '<p>' . sprintf(
348
+ __('Viewing <code>readme</code> from repository <code><a target="_blank" href="%1$s">%2$s</a></code>.', 'simple-history'),
349
+ esc_url($repo),
350
+ esc_html($repo)
351
+ ) . '</p>';
352
+
353
+ $github_markdown_css_path = SIMPLE_HISTORY_PATH . '/css/github-markdown.css';
354
+
355
+ printf(
356
+ '
357
  <!doctype html>
358
  <style>
359
  body {
389
  %2$s
390
  </div>
391
  ',
392
+ $repo_info,
393
+ $response_body,
394
+ $github_markdown_css_path,
395
+ esc_url($repo) // 4
396
+ );
397
+
398
+ // echo($response_body);
399
+ exit;
400
+ }
401
+
402
+ /*
403
+ * When a plugin has been deleted there is no way for us to get
404
+ * the real name of the plugin, only the dir and main index file.
405
+ * So before a plugin is deleted we save all needed info in a transient
406
+ */
407
+ function on_action_delete_selected()
408
+ {
409
+
410
+ // Same as in plugins.php
411
+ if (! current_user_can('delete_plugins')) {
412
+ wp_die(__('You do not have sufficient permissions to delete plugins for this site.'));
413
+ }
414
+
415
+ // Verify delete must be set
416
+ if (! isset($_POST['verify-delete']) || ! $_POST['verify-delete']) {
417
+ return;
418
+ }
419
+
420
+ // An arr of plugins must be set
421
+ if (! isset($_POST['checked']) || ! is_array($_POST['checked'])) {
422
+ return;
423
+ }
424
+
425
+ // If we get this far it looks like a plugin is begin deleted
426
+ // Get and save info about it
427
+ $this->save_versions_before_update();
428
+ }
429
+
430
+ /**
431
+ * Saves info about all installed plugins to an option.
432
+ * When we are done logging then we remove the option.
433
+ */
434
+ function save_versions_before_update($bool = null, $hook_extra = null)
435
+ {
436
+
437
+ $plugins = get_plugins();
438
+
439
+ // does not work
440
+ $option_name = $this->slug . '_plugin_info_before_update';
441
+
442
+ $r = update_option($option_name, SimpleHistory::json_encode($plugins));
443
+
444
+ return $bool;
445
+ }
446
+
447
+ /**
448
+ * Detect plugin being deleted
449
+ * When WP is done deleting a plugin it sets a transient called plugins_delete_result:
450
+ * set_transient('plugins_delete_result_' . $user_ID, $delete_result);
451
+ *
452
+ * We detect when that transient is set and then we have all info needed to log the plugin delete
453
+ */
454
+ public function on_setted_transient_for_remove_files($transient = '', $value = '')
455
+ {
456
+
457
+ if (! $user_id = get_current_user_id()) {
458
+ return;
459
+ }
460
+
461
+ $transient_name = '_transient_plugins_delete_result_' . $user_id;
462
+ if ($transient_name !== $transient) {
463
+ return;
464
+ }
465
+
466
+ // We found the transient we were looking for
467
+ if (isset($_POST['action'])
468
+ && 'delete-selected' == $_POST['action']
469
+ && isset($_POST['checked'])
470
+ && is_array($_POST['checked'])
471
+ ) {
472
+ /*
473
+ [checked] => Array
474
+ (
475
+ [0] => the-events-calendar/the-events-calendar.php
476
+ )
477
+ */
478
+
479
+ $plugins_deleted = $_POST['checked'];
480
+ $plugins_before_update = json_decode(get_option($this->slug . '_plugin_info_before_update', false), true);
481
+
482
+ foreach ($plugins_deleted as $plugin) {
483
+ $context = array(
484
+ 'plugin' => $plugin, // plugin-name-folder/plugin-main-file.php
485
+ );
486
+
487
+ if (is_array($plugins_before_update) && isset($plugins_before_update[ $plugin ])) {
488
+ $context['plugin_name'] = $plugins_before_update[ $plugin ]['Name'];
489
+ $context['plugin_title'] = $plugins_before_update[ $plugin ]['Title'];
490
+ $context['plugin_description'] = $plugins_before_update[ $plugin ]['Description'];
491
+ $context['plugin_author'] = $plugins_before_update[ $plugin ]['Author'];
492
+ $context['plugin_version'] = $plugins_before_update[ $plugin ]['Version'];
493
+ $context['plugin_url'] = $plugins_before_update[ $plugin ]['PluginURI'];
494
+ }
495
+
496
+ $this->infoMessage(
497
+ 'plugin_deleted',
498
+ $context
499
+ );
500
+ }
501
+ }
502
+
503
+ $this->remove_saved_versions();
504
+ }
505
+
506
+ /**
507
+ * Save all plugin information before a plugin is updated or removed.
508
+ * This way we can know both the old (pre updated/removed) and the current version of the plugin
509
+ */
510
+ /*
511
+ public function save_versions_before_update() {
512
+
513
+ $current_screen = get_current_screen();
514
+ $request_uri = $_SERVER["SCRIPT_NAME"];
515
+
516
+ // Only add option on pages where needed
517
+ $do_store = false;
518
+
519
+ if (
520
+ SimpleHistory::ends_with( $request_uri, "/wp-admin/update.php" )
521
+ && isset( $current_screen->base )
522
+ && "update" == $current_screen->base
523
+ ) {
524
+
525
+ // Plugin update screen
526
+ $do_store = true;
527
+
528
+ } else if (
529
+ SimpleHistory::ends_with( $request_uri, "/wp-admin/plugins.php" )
530
+ && isset( $current_screen->base )
531
+ && "plugins" == $current_screen->base
532
+ && ( isset( $_POST["action"] ) && "delete-selected" == $_POST["action"] )
533
+ ) {
534
+
535
+ // Plugin delete screen, during delete
536
+ $do_store = true;
537
+
538
+ }
539
+
540
+ if ( $do_store ) {
541
+ update_option( $this->slug . "_plugin_info_before_update", SimpleHistory::json_encode( get_plugins() ) );
542
+ }
543
+
544
+ }
545
+ */
546
+
547
+ /**
548
+ * when plugin updates are done wp_clean_plugins_cache() is called,
549
+ * which in its turn run:
550
+ * delete_site_transient( 'update_plugins' );
551
+ * do_action( 'delete_site_transient_' . $transient, $transient );
552
+ * delete_site_transient_update_plugins
553
+ */
554
+ public function remove_saved_versions()
555
+ {
556
+
557
+ delete_option($this->slug . '_plugin_info_before_update');
558
+ }
559
+
560
+ /**
561
+ * Called when plugins is updated or installed
562
+ * Called from class-wp-upgrader.php
563
+ *
564
+ * @param Plugin_Upgrader $this Plugin_Upgrader instance. In other contexts, $this, might
565
+ * be a Theme_Upgrader or Core_Upgrade instance.
566
+ * @param array $data {
567
+ * Array of bulk item update data.
568
+ */
569
+ function on_upgrader_process_complete($plugin_upgrader_instance, $arr_data)
570
+ {
571
+
572
+ // Can't use get_plugins() here to get version of plugins updated from
573
+ // Tested that, and it will get the new version (and that's the correct answer I guess. but too bad for us..)
574
+ /*
575
+ If an update fails then $plugin_upgrader_instance->skin->result->errors contains something like:
576
+ Array
577
+ (
578
+ [remove_old_failed] => Array
579
+ (
580
+ [0] => Could not remove the old plugin.
581
+ )
582
+
583
+ )
584
+ */
585
+
586
+ /*
587
+ # Contents of $arr_data in different scenarios
588
+
589
+ ## WordPress core update
590
+
591
+ $arr_data:
592
+ Array
593
+ (
594
+ [action] => update
595
+ [type] => core
596
+ )
597
+
598
+
599
+ # Plugin install
600
+
601
+ $arr_data:
602
+ Array
603
+ (
604
+ [type] => plugin
605
+ [action] => install
606
+ )
607
+
608
+
609
+ ## Plugin update
610
+
611
+ $arr_data:
612
+ Array
613
+ (
614
+ [type] => plugin
615
+ [action] => install
616
+ )
617
+
618
+ ## Bulk actions
619
+
620
+ array(
621
+ 'action' => 'update',
622
+ 'type' => 'plugin',
623
+ 'bulk' => true,
624
+ 'plugins' => $plugins,
625
+ )
626
+
627
+ */
628
+
629
+ // To keep track of if something was logged, so wen can output debug info only
630
+ // only if we did not log anything
631
+ $did_log = false;
632
+
633
+ if (isset($arr_data['type']) && 'plugin' == $arr_data['type']) {
634
+ // Single plugin install
635
+ if (isset($arr_data['action']) && 'install' == $arr_data['action'] && ! $plugin_upgrader_instance->bulk) {
636
+ $upgrader_skin_options = isset($plugin_upgrader_instance->skin->options) && is_array($plugin_upgrader_instance->skin->options) ? $plugin_upgrader_instance->skin->options : array();
637
+ $upgrader_skin_result = isset($plugin_upgrader_instance->skin->result) && is_array($plugin_upgrader_instance->skin->result) ? $plugin_upgrader_instance->skin->result : array();
638
+ $upgrader_skin_api = isset($plugin_upgrader_instance->skin->api) ? $plugin_upgrader_instance->skin->api : (object) array();
639
+
640
+ $plugin_slug = isset($upgrader_skin_result['destination_name']) ? $upgrader_skin_result['destination_name'] : '';
641
+
642
+ // Upgrader contains current info
643
+ $context = array(
644
+ 'plugin_slug' => $plugin_slug,
645
+ 'plugin_name' => isset($upgrader_skin_api->name) ? $upgrader_skin_api->name : '',
646
+ 'plugin_version' => isset($upgrader_skin_api->version) ? $upgrader_skin_api->version : '',
647
+ 'plugin_author' => isset($upgrader_skin_api->author) ? $upgrader_skin_api->author : '',
648
+ 'plugin_last_updated' => isset($upgrader_skin_api->last_updated) ? $upgrader_skin_api->last_updated : '',
649
+ 'plugin_requires' => isset($upgrader_skin_api->requires) ? $upgrader_skin_api->requires : '',
650
+ 'plugin_tested' => isset($upgrader_skin_api->tested) ? $upgrader_skin_api->tested : '',
651
+ 'plugin_rating' => isset($upgrader_skin_api->rating) ? $upgrader_skin_api->rating : '',
652
+ 'plugin_num_ratings' => isset($upgrader_skin_api->num_ratings) ? $upgrader_skin_api->num_ratings : '',
653
+ 'plugin_downloaded' => isset($upgrader_skin_api->downloaded) ? $upgrader_skin_api->downloaded : '',
654
+ 'plugin_added' => isset($upgrader_skin_api->added) ? $upgrader_skin_api->added : '',
655
+ 'plugin_source_files' => $this->simpleHistory->json_encode($plugin_upgrader_instance->result['source_files']),
656
+
657
+ // To debug comment out these:
658
+ // "debug_skin_options" => $this->simpleHistory->json_encode( $upgrader_skin_options ),
659
+ // "debug_skin_result" => $this->simpleHistory->json_encode( $upgrader_skin_result ),
660
+ );
661
+
662
+ /*
663
+ Detect install plugin from wordpress.org
664
+ - options[type] = "web"
665
+ - options[api] contains all we need
666
+
667
+ Detect install from upload ZIP
668
+ - options[type] = "upload"
669
+
670
+ Also: plugins hosted at GitHub have a de-facto standard field of "GitHub Plugin URI"
671
+ */
672
+ $install_source = 'unknown';
673
+ if (isset($upgrader_skin_options['type'])) {
674
+ $install_source = (string) $upgrader_skin_options['type'];
675
+ }
676
+
677
+ $context['plugin_install_source'] = $install_source;
678
+
679
+ // If uploaded plugin store name of ZIP
680
+ if ('upload' == $install_source) {
681
+ /*
682
+ _debug_files
683
+ {
684
+ "pluginzip": {
685
+ "name": "WPThumb-master.zip",
686
+ "type": "application\/zip",
687
+ "tmp_name": "\/Applications\/MAMP\/tmp\/php\/phpnThImc",
688
+ "error": 0,
689
+ "size": 2394625
690
+ }
691
+ }
692
+ */
693
+
694
+ if (isset($_FILES['pluginzip']['name'])) {
695
+ $plugin_upload_name = $_FILES['pluginzip']['name'];
696
+ $context['plugin_upload_name'] = $plugin_upload_name;
697
+ }
698
+ }
699
+
700
+ if (is_a($plugin_upgrader_instance->skin->result, 'WP_Error')) {
701
+ // Add errors
702
+ // Errors is in original wp admin language
703
+ $context['error_messages'] = $this->simpleHistory->json_encode($plugin_upgrader_instance->skin->result->errors);
704
+ $context['error_data'] = $this->simpleHistory->json_encode($plugin_upgrader_instance->skin->result->error_data);
705
+
706
+ $this->infoMessage(
707
+ 'plugin_installed_failed',
708
+ $context
709
+ );
710
+
711
+ $did_log = true;
712
+ } else {
713
+ // Plugin was successfully installed
714
+ // Try to grab more info from the readme
715
+ // Would be nice to grab a screenshot, but that is difficult since they often are stored remotely
716
+ $plugin_destination = isset($plugin_upgrader_instance->result['destination']) ? $plugin_upgrader_instance->result['destination'] : null;
717
+
718
+ if ($plugin_destination) {
719
+ $plugin_info = $plugin_upgrader_instance->plugin_info();
720
+
721
+ $plugin_data = array();
722
+ if (file_exists(WP_PLUGIN_DIR . '/' . $plugin_info)) {
723
+ $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin_info, true, false);
724
+ }
725
+
726
+ $context['plugin_name'] = isset($plugin_data['Name']) ? $plugin_data['Name'] : '';
727
+ $context['plugin_description'] = isset($plugin_data['Description']) ? $plugin_data['Description'] : '';
728
+ $context['plugin_url'] = isset($plugin_data['PluginURI']) ? $plugin_data['PluginURI'] : '';
729
+ $context['plugin_version'] = isset($plugin_data['Version']) ? $plugin_data['Version'] : '';
730
+ $context['plugin_author'] = isset($plugin_data['AuthorName']) ? $plugin_data['AuthorName'] : '';
731
+
732
+ // Comment out these to debug plugin installs
733
+ // $context["debug_plugin_data"] = $this->simpleHistory->json_encode( $plugin_data );
734
+ // $context["debug_plugin_info"] = $this->simpleHistory->json_encode( $plugin_info );
735
+ if (! empty($plugin_data['GitHub Plugin URI'])) {
736
+ $context['plugin_github_url'] = $plugin_data['GitHub Plugin URI'];
737
+ }
738
+ }
739
+
740
+ $this->infoMessage(
741
+ 'plugin_installed',
742
+ $context
743
+ );
744
+
745
+ $did_log = true;
746
+ }// End if().
747
+ } // End if().
748
+
749
+ // Single plugin update
750
+ if (isset($arr_data['action']) && 'update' == $arr_data['action'] && ! $plugin_upgrader_instance->bulk) {
751
+ // No plugin info in instance, so get it ourself
752
+ $plugin_data = array();
753
+ if (file_exists(WP_PLUGIN_DIR . '/' . $arr_data['plugin'])) {
754
+ $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $arr_data['plugin'], true, false);
755
+ }
756
+
757
+ // autoptimize/autoptimize.php
758
+ $plugin_slug = dirname($arr_data['plugin']);
759
+
760
+ $context = array(
761
+ 'plugin_slug' => $plugin_slug,
762
+ 'request' => $this->simpleHistory->json_encode($_REQUEST),
763
+ 'plugin_name' => $plugin_data['Name'],
764
+ 'plugin_title' => $plugin_data['Title'],
765
+ 'plugin_description' => $plugin_data['Description'],
766
+ 'plugin_author' => $plugin_data['Author'],
767
+ 'plugin_version' => $plugin_data['Version'],
768
+ 'plugin_url' => $plugin_data['PluginURI'],
769
+ 'plugin_source_files' => $this->simpleHistory->json_encode($plugin_upgrader_instance->result['source_files']),
770
+ );
771
+
772
+ // update status for plugins are in response
773
+ // plugin folder + index file = key
774
+ // use transient to get url and package
775
+ $update_plugins = get_site_transient('update_plugins');
776
+ if ($update_plugins && isset($update_plugins->response[ $arr_data['plugin'] ])) {
777
+ /*
778
+ $update_plugins[plugin_path/slug]:
779
+ {
780
+ "id": "8986",
781
+ "slug": "autoptimize",
782
+ "plugin": "autoptimize/autoptimize.php",
783
+ "new_version": "1.9.1",
784
+ "url": "https://wordpress.org/plugins/autoptimize/",
785
+ "package": "https://downloads.wordpress.org/plugin/autoptimize.1.9.1.zip"
786
+ }
787
+ */
788
+ // for debug purposes the update_plugins key can be added
789
+ // $context["update_plugins"] = $this->simpleHistory->json_encode( $update_plugins );
790
+ $plugin_update_info = $update_plugins->response[ $arr_data['plugin'] ];
791
+
792
+ // autoptimize/autoptimize.php
793
+ if (isset($plugin_update_info->plugin)) {
794
+ $context['plugin_update_info_plugin'] = $plugin_update_info->plugin;
795
+ }
796
+
797
+ // https://downloads.wordpress.org/plugin/autoptimize.1.9.1.zip
798
+ if (isset($plugin_update_info->package)) {
799
+ $context['plugin_update_info_package'] = $plugin_update_info->package;
800
+ }
801
+ }
802
+
803
+ // To get old version we use our option
804
+ $plugins_before_update = json_decode(get_option($this->slug . '_plugin_info_before_update', false), true);
805
+ if (is_array($plugins_before_update) && isset($plugins_before_update[ $arr_data['plugin'] ])) {
806
+ $context['plugin_prev_version'] = $plugins_before_update[ $arr_data['plugin'] ]['Version'];
807
+ }
808
+
809
+ if (is_a($plugin_upgrader_instance->skin->result, 'WP_Error')) {
810
+ // Add errors
811
+ // Errors is in original wp admin language
812
+ $context['error_messages'] = json_encode($plugin_upgrader_instance->skin->result->errors);
813
+ $context['error_data'] = json_encode($plugin_upgrader_instance->skin->result->error_data);
814
+
815
+ $this->infoMessage(
816
+ 'plugin_update_failed',
817
+ $context
818
+ );
819
+
820
+ $did_log = true;
821
+ } else {
822
+ $this->infoMessage(
823
+ 'plugin_updated',
824
+ $context
825
+ );
826
+
827
+ // echo "on_upgrader_process_complete";
828
+ // sf_d( $plugin_upgrader_instance, '$plugin_upgrader_instance' );
829
+ // sf_d( $arr_data, '$arr_data' );
830
+ $did_log = true;
831
+ }
832
+ } // End if().
833
+
834
+ /**
835
+ * For bulk updates $arr_data looks like:
836
+ * Array
837
+ * (
838
+ * [action] => update
839
+ * [type] => plugin
840
+ * [bulk] => 1
841
+ * [plugins] => Array
842
+ * (
843
+ * [0] => plugin-folder-1/plugin-index.php
844
+ * [1] => my-plugin-folder/my-plugin.php
845
+ * )
846
+ * )
847
+ */
848
+ if (isset($arr_data['bulk']) && $arr_data['bulk'] && isset($arr_data['action']) && 'update' == $arr_data['action']) {
849
+ $plugins_updated = isset($arr_data['plugins']) ? (array) $arr_data['plugins'] : array();
850
+
851
+ foreach ($plugins_updated as $plugin_name) {
852
+ $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin_name, true, false);
853
+
854
+ $plugin_slug = dirname($plugin_name);
855
+
856
+ $context = array(
857
+ 'plugin_slug' => $plugin_slug,
858
+ 'plugin_name' => $plugin_data['Name'],
859
+ 'plugin_title' => $plugin_data['Title'],
860
+ 'plugin_description' => $plugin_data['Description'],
861
+ 'plugin_author' => $plugin_data['Author'],
862
+ 'plugin_version' => $plugin_data['Version'],
863
+ 'plugin_url' => $plugin_data['PluginURI'],
864
+ );
865
+
866
+ // get url and package
867
+ $update_plugins = get_site_transient('update_plugins');
868
+ if ($update_plugins && isset($update_plugins->response[ $plugin_name ])) {
869
+ /*
870
+ $update_plugins[plugin_path/slug]:
871
+ {
872
+ "id": "8986",
873
+ "slug": "autoptimize",
874
+ "plugin": "autoptimize/autoptimize.php",
875
+ "new_version": "1.9.1",
876
+ "url": "https://wordpress.org/plugins/autoptimize/",
877
+ "package": "https://downloads.wordpress.org/plugin/autoptimize.1.9.1.zip"
878
+ }
879
+ */
880
+
881
+ $plugin_update_info = $update_plugins->response[ $plugin_name ];
882
+
883
+ // autoptimize/autoptimize.php
884
+ if (isset($plugin_update_info->plugin)) {
885
+ $context['plugin_update_info_plugin'] = $plugin_update_info->plugin;
886
+ }
887
+
888
+ // https://downloads.wordpress.org/plugin/autoptimize.1.9.1.zip
889
+ if (isset($plugin_update_info->package)) {
890
+ $context['plugin_update_info_package'] = $plugin_update_info->package;
891
+ }
892
+ }
893
+
894
+ // To get old version we use our option
895
+ // @TODO: this does not always work, why?
896
+ $plugins_before_update = json_decode(get_option($this->slug . '_plugin_info_before_update', false), true);
897
+ if (is_array($plugins_before_update) && isset($plugins_before_update[ $plugin_name ])) {
898
+ $context['plugin_prev_version'] = $plugins_before_update[ $plugin_name ]['Version'];
899
+ }
900
+
901
+ $this->infoMessage(
902
+ 'plugin_bulk_updated',
903
+ $context
904
+ );
905
+ }// End foreach().
906
+ }// End if().
907
+ } // End if().
908
+
909
+ if (! $did_log) {
910
+ // echo "on_upgrader_process_complete";
911
+ // sf_d( $plugin_upgrader_instance, '$plugin_upgrader_instance' );
912
+ // sf_d( $arr_data, '$arr_data' );
913
+ // exit;
914
+ }
915
+
916
+ $this->remove_saved_versions();
917
+ } // on upgrader_process_complete
918
+
919
+
920
+ /**
921
+ * Plugin is activated
922
+ * plugin_name is like admin-menu-tree-page-view/index.php
923
+ */
924
+ function on_activated_plugin($plugin_name, $network_wide)
925
+ {
926
+
927
+ /*
928
+ Plugin data returned array contains the following:
929
+ 'Name' - Name of the plugin, must be unique.
930
+ 'Title' - Title of the plugin and the link to the plugin's web site.
931
+ 'Description' - Description of what the plugin does and/or notes from the author.
932
+ 'Author' - The author's name
933
+ 'AuthorURI' - The authors web site address.
934
+ 'Version' - The plugin version number.
935
+ 'PluginURI' - Plugin web site address.
936
+ 'TextDomain' - Plugin's text domain for localization.
937
+ 'DomainPath' - Plugin's relative directory path to .mo files.
938
+ 'Network' - Boolean. Whether the plugin can only be activated network wide.
939
+ */
940
+ $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin_name, true, false);
941
+
942
+ $plugin_slug = dirname($plugin_name);
943
+
944
+ $context = array(
945
+ 'plugin_name' => $plugin_data['Name'],
946
+ 'plugin_slug' => $plugin_slug,
947
+ 'plugin_title' => $plugin_data['Title'],
948
+ 'plugin_description' => $plugin_data['Description'],
949
+ 'plugin_author' => $plugin_data['Author'],
950
+ 'plugin_version' => $plugin_data['Version'],
951
+ 'plugin_url' => $plugin_data['PluginURI'],
952
+ );
953
+
954
+ if (! empty($plugin_data['GitHub Plugin URI'])) {
955
+ $context['plugin_github_url'] = $plugin_data['GitHub Plugin URI'];
956
+ }
957
+
958
+ $this->infoMessage('plugin_activated', $context);
959
+ } // on_activated_plugin
960
+
961
+ /**
962
+ * Plugin is deactivated
963
+ * plugin_name is like admin-menu-tree-page-view/index.php
964
+ */
965
+ function on_deactivated_plugin($plugin_name)
966
+ {
967
+
968
+ $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin_name, true, false);
969
+ $plugin_slug = dirname($plugin_name);
970
+
971
+ $context = array(
972
+ 'plugin_name' => $plugin_data['Name'],
973
+ 'plugin_slug' => $plugin_slug,
974
+ 'plugin_title' => $plugin_data['Title'],
975
+ 'plugin_description' => $plugin_data['Description'],
976
+ 'plugin_author' => $plugin_data['Author'],
977
+ 'plugin_version' => $plugin_data['Version'],
978
+ 'plugin_url' => $plugin_data['PluginURI'],
979
+ );
980
+
981
+ if (! empty($plugin_data['GitHub Plugin URI'])) {
982
+ $context['plugin_github_url'] = $plugin_data['GitHub Plugin URI'];
983
+ }
984
+
985
+ $this->infoMessage('plugin_deactivated', $context);
986
+ } // on_deactivated_plugin
987
+
988
+
989
+ /**
990
+ * Get output for detailed log section
991
+ */
992
+ function getLogRowDetailsOutput($row)
993
+ {
994
+
995
+ $context = $row->context;
996
+ $message_key = $context['_message_key'];
997
+ $output = '';
998
+
999
+ // When a plugin is installed we show a bit more information
1000
+ // We do it only on install because we don't want to clutter to log,
1001
+ // and when something is installed the description is most useul for other
1002
+ // admins on the site
1003
+ if ('plugin_installed' === $message_key) {
1004
+ if (isset($context['plugin_description'])) {
1005
+ // Description includes a link to author, remove that, i.e. all text after and including <cite>
1006
+ $plugin_description = $context['plugin_description'];
1007
+ $cite_pos = strpos($plugin_description, '<cite>');
1008
+ if ($cite_pos) {
1009
+ $plugin_description = substr($plugin_description, 0, $cite_pos);
1010
+ }
1011
+
1012
+ // Keys to show
1013
+ $arr_plugin_keys = array(
1014
+ 'plugin_description' => _x('Description', 'plugin logger - detailed output', 'simple-history'),
1015
+ 'plugin_install_source' => _x('Source', 'plugin logger - detailed output install source', 'simple-history'),
1016
+ 'plugin_install_source_file' => _x('Source file name', 'plugin logger - detailed output install source', 'simple-history'),
1017
+ 'plugin_version' => _x('Version', 'plugin logger - detailed output version', 'simple-history'),
1018
+ 'plugin_author' => _x('Author', 'plugin logger - detailed output author', 'simple-history'),
1019
+ 'plugin_url' => _x('URL', 'plugin logger - detailed output url', 'simple-history'),
1020
+ // "plugin_downloaded" => _x("Downloads", "plugin logger - detailed output downloaded", "simple-history"),
1021
+ // "plugin_requires" => _x("Requires", "plugin logger - detailed output author", "simple-history"),
1022
+ // "plugin_tested" => _x("Compatible up to", "plugin logger - detailed output compatible", "simple-history"),
1023
+ // also available: plugin_rating, plugin_num_ratings
1024
+ );
1025
+
1026
+ $arr_plugin_keys = apply_filters('simple_history/plugin_logger/row_details_plugin_info_keys', $arr_plugin_keys);
1027
+
1028
+ // Start output of plugin meta data table
1029
+ $output .= "<table class='SimpleHistoryLogitem__keyValueTable'>";
1030
+
1031
+ foreach ($arr_plugin_keys as $key => $desc) {
1032
+ $desc_output = '';
1033
+
1034
+ switch ($key) {
1035
+ case 'plugin_downloaded':
1036
+ $desc_output = esc_html(number_format_i18n((int) $context[ $key ]));
1037
+ break;
1038
+
1039
+ // author is already formatted
1040
+ case 'plugin_author':
1041
+ $desc_output = $context[ $key ];
1042
+ break;
1043
+
1044
+ // URL needs a link
1045
+ case 'plugin_url':
1046
+ $desc_output = sprintf('<a href="%1$s">%2$s</a>', esc_attr($context['plugin_url']), esc_html($context['plugin_url']));
1047
+ break;
1048
+
1049
+ case 'plugin_description':
1050
+ $desc_output = $plugin_description;
1051
+ break;
1052
+
1053
+ case 'plugin_install_source':
1054
+ if (! isset($context[ $key ])) {
1055
+ break;
1056
+ }
1057
+
1058
+ if ('web' == $context[ $key ]) {
1059
+ $desc_output = esc_html(__('WordPress Plugin Repository', 'simple-history'));
1060
+ } elseif ('upload' == $context[ $key ]) {
1061
+ // $plugin_upload_name = isset( $context["plugin_upload_name"] ) ? $context["plugin_upload_name"] : __("Unknown archive name", "simple-history");
1062
+ $desc_output = esc_html(__('Uploaded ZIP archive', 'simple-history'));
1063
+ // $desc_output = esc_html( sprintf( __('Uploaded ZIP archive (%1$s)', "simple-history"), $plugin_upload_name ) );
1064
+ // $desc_output = esc_html( sprintf( __('%1$s (uploaded ZIP archive)', "simple-history"), $plugin_upload_name ) );
1065
+ } else {
1066
+ $desc_output = esc_html($context[ $key ]);
1067
+ }
1068
+
1069
+ break;
1070
+
1071
+ case 'plugin_install_source_file':
1072
+ if (! isset($context['plugin_upload_name']) || ! isset($context['plugin_install_source'])) {
1073
+ break;
1074
+ }
1075
+
1076
+ if ('upload' == $context['plugin_install_source']) {
1077
+ $plugin_upload_name = $context['plugin_upload_name'];
1078
+ $desc_output = esc_html($plugin_upload_name);
1079
+ }
1080
+
1081
+ break;
1082
+
1083
+ default:
1084
+ $desc_output = esc_html($context[ $key ]);
1085
+ break;
1086
+ }// End switch().
1087
+
1088
+ if (! trim($desc_output)) {
1089
+ continue;
1090
+ }
1091
+
1092
+ $output .= sprintf(
1093
+ '
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1094
  <tr>
1095
  <td>%1$s</td>
1096
  <td>%2$s</td>
1097
  </tr>
1098
  ',
1099
+ esc_html($desc),
1100
+ $desc_output
1101
+ );
1102
+ }// End foreach().
1103
+
1104
+ // Add link with more info about the plugin
1105
+ // If plugin_install_source = web then it should be a wordpress.org-plugin
1106
+ // If plugin_github_url is set then it's a zip from a github thingie
1107
+ // so use link to that.
1108
+ $plugin_slug = ! empty($context['plugin_slug']) ? $context['plugin_slug'] : '';
1109
+
1110
+ // Slug + web as install source = show link to wordpress.org
1111
+ if ($plugin_slug && isset($context['plugin_install_source']) && $context['plugin_install_source'] == 'web') {
1112
+ $output .= sprintf(
1113
+ '
 
 
1114
  <tr>
1115
  <td></td>
1116
  <td><a title="%2$s" class="thickbox" href="%1$s">%2$s</a></td>
1117
  </tr>
1118
  ',
1119
+ admin_url("plugin-install.php?tab=plugin-information&amp;plugin={$plugin_slug}&amp;section=&amp;TB_iframe=true&amp;width=640&amp;height=550"),
1120
+ esc_html_x('View plugin info', 'plugin logger: plugin info thickbox title view all info', 'simple-history')
1121
+ );
1122
+ } // End if().
1123
+ elseif (isset($context['plugin_install_source']) && $context['plugin_install_source'] == 'upload' && ! empty($context['plugin_github_url'])) {
1124
+ // Can't embed iframe
1125
+ // Must use API instead
1126
+ // https://api.github.com/repos/<username>/<repo>/readme?callback=<callbackname>
1127
+ $output .= sprintf(
1128
+ '
 
 
1129
  <tr>
1130
  <td></td>
1131
  <td><a title="%2$s" class="thickbox" href="%1$s">%2$s</a></td>
1132
  </tr>
1133
  ',
1134
+ admin_url(sprintf('admin-ajax.php?action=SimplePluginLogger_GetGitHubPluginInfo&getrepo&amp;repo=%1$s&amp;TB_iframe=true&amp;width=640&amp;height=550', esc_url_raw($context['plugin_github_url']))),
1135
+ esc_html_x('View plugin info', 'plugin logger: plugin info thickbox title view all info', 'simple-history')
1136
+ );
1137
+ }
1138
+
1139
+ $output .= '</table>';
1140
+ }// End if().
1141
+ } elseif ('plugin_bulk_updated' === $message_key || 'plugin_updated' === $message_key || 'plugin_activated' === $message_key || 'plugin_deactivated' === $message_key) {
1142
+ $plugin_slug = ! empty($context['plugin_slug']) ? $context['plugin_slug'] : '';
1143
+
1144
+ if ($plugin_slug && empty($context['plugin_github_url'])) {
1145
+ $link_title = esc_html_x('View plugin info', 'plugin logger: plugin info thickbox title', 'simple-history');
1146
+ $url = admin_url("plugin-install.php?tab=plugin-information&amp;plugin={$plugin_slug}&amp;section=&amp;TB_iframe=true&amp;width=640&amp;height=550");
1147
+
1148
+ if ('plugin_updated' == $message_key || 'plugin_bulk_updated' == $message_key) {
1149
+ $link_title = esc_html_x('View changelog', 'plugin logger: plugin info thickbox title', 'simple-history');
1150
+
1151
+ if (is_multisite()) {
1152
+ $url = network_admin_url("plugin-install.php?tab=plugin-information&amp;plugin={$plugin_slug}&amp;section=changelog&amp;TB_iframe=true&amp;width=772&amp;height=550");
1153
+ } else {
1154
+ $url = admin_url("plugin-install.php?tab=plugin-information&amp;plugin={$plugin_slug}&amp;section=changelog&amp;TB_iframe=true&amp;width=772&amp;height=550");
1155
+ }
1156
+ }
1157
+
1158
+ $output .= sprintf(
1159
+ '<p><a title="%2$s" class="thickbox" href="%1$s">%2$s</a></p>',
1160
+ $url,
1161
+ $link_title
1162
+ );
1163
+ } elseif (! empty($context['plugin_github_url'])) {
1164
+ $output .= sprintf(
1165
+ '
 
 
 
 
 
 
 
1166
  <tr>
1167
  <td></td>
1168
  <td><a title="%2$s" class="thickbox" href="%1$s">%2$s</a></td>
1169
  </tr>
1170
  ',
1171
+ admin_url(sprintf('admin-ajax.php?action=SimplePluginLogger_GetGitHubPluginInfo&getrepo&amp;repo=%1$s&amp;TB_iframe=true&amp;width=640&amp;height=550', esc_url_raw($context['plugin_github_url']))),
1172
+ esc_html_x('View plugin info', 'plugin logger: plugin info thickbox title view all info', 'simple-history')
1173
+ );
1174
+ } // End if().
1175
+ } // End if().
1176
+
1177
+ return $output;
1178
+ } // getLogRowDetailsOutput
 
 
 
1179
  } // class SimplePluginLogger
loggers/SimplePostLogger.php CHANGED
@@ -5,9 +5,9 @@ defined('ABSPATH') || die();
5
  /**
6
  * Todo/@HERE
7
  * - [ ] install and test with ACF again
8
- * - Install 5.7.13 and then each save or preview results in 2 or 3 adds to the log.
9
  * - The second save saves all the post meta. So it's technically two saves but not for the user.
10
- * Both requests have the same HTTP_X_WP_NONCE
11
  * - [ ] test REST API update from curl or similar
12
  * - [ ] test REST API from Android/Ios-apps
13
  * - [ ] Save auto-saves? Not done by user but still done...
@@ -18,1684 +18,1405 @@ defined('ABSPATH') || die();
18
  */
19
  class SimplePostLogger extends SimpleLogger
20
  {
21
- // The logger slug. Defaulting to the class name is nice and logical I think.
22
- public $slug = __CLASS__;
23
-
24
- // Array that will contain previous post data, before data is updated.
25
- // Array format is
26
- // [post_id] => [post_data, post_meta].
27
- // post_data = WP_Post object, post_meta = post meta array.
28
- private $old_post_data = array();
29
-
30
- public function loaded()
31
- {
32
- add_action('admin_action_editpost', array(
33
- $this,
34
- 'on_admin_action_editpost'
35
- ));
36
- add_action(
37
- 'transition_post_status',
38
- array($this, 'on_transition_post_status'),
39
- 10,
40
- 3
41
- );
42
- add_action('delete_post', array($this, 'on_delete_post'));
43
- add_action('untrash_post', array($this, 'on_untrash_post'));
44
-
45
- $this->add_xml_rpc_hooks();
46
- $this->add_rest_hooks();
47
-
48
- add_filter(
49
- 'simple_history/rss_item_link',
50
- array($this, 'filter_rss_item_link'),
51
- 10,
52
- 2
53
- );
54
- }
55
-
56
- /**
57
- * Add hooks to catch updates via REST API, i.e. the new Gutenberg editor.
58
- */
59
- function add_rest_hooks()
60
- {
61
- // Get all post types.
62
- $post_types = get_post_types(array(), 'objects');
63
-
64
- // Add actions for each post type.
65
- foreach ($post_types as $post_type) {
66
- // class-wp-rest-posts-controller.php fires two actions in
67
- // the update_item() method: pre_insert and after_insert.
68
-
69
- // Rest pre insert is fired before an updated post is inserted into db.
70
- add_action(
71
- "rest_pre_insert_{$post_type->name}",
72
- array($this, 'on_rest_pre_insert'),
73
- 10,
74
- 2
75
- );
76
-
77
- // Rest insert happens after the post has been updated: "Fires after a single post is completely created or updated via the REST API."
78
- // add_action( "rest_after_insert_{$post_type->name}", array( $this, 'on_rest_after_insert' ), 10, 3 );
79
- }
80
- }
81
-
82
- /**
83
- * Filter "rest_pre_insert_{$this->post_type}" filters a post before it is inserted via the REST API.
84
- * Fired from class-wp-rest-posts-controller.php.
85
- *
86
- * Here we can get the old post object.
87
- *
88
- * @param stdClass $prepared_post An object representing a single post prepared
89
- * for inserting or updating the database, i.e. the new updated post.
90
- * @param WP_REST_Request $request Request object.
91
- * @return stdClass $prepared_post
92
- */
93
- function on_rest_pre_insert($prepared_post, $request)
94
- {
95
- // $prepared_post = stdClass Object with new and modified content.
96
- // changes are not saved to post in db yet, so get_post( $prepared_post->ID ) will get old contents.
97
- /*
98
- stdClass Object
99
- (
100
- [ID] => 889
101
- [post_title] => gutenberg 1
102
- [post_content] => <!-- wp:paragraph -->
103
- <p>hejsan</p>
104
- <!-- /wp:paragraph -->
105
- [post_excerpt] =>
106
- [post_type] => post
107
- [page_template] =>
108
- )
109
- */
110
-
111
- // $old_post = post with old content and old meta
112
- $old_post = get_post($prepared_post->ID);
113
-
114
- $this->old_post_data[$old_post->ID] = array(
115
- 'post_data' => $old_post,
116
- 'post_meta' => get_post_custom($old_post->ID)
117
- );
118
-
119
- return $prepared_post;
120
- }
121
-
122
- /**
123
- * Fires after a single post is completely created or updated via the REST API.
124
- *
125
- * Here we can get the updated post, after it's updated in the db.
126
- *
127
- * @param WP_Post $post Inserted or updated post object.
128
- * @param WP_REST_Request $request Request object.
129
- * @param bool $creating True when creating a post, false when updating.
130
- */
131
- function on_rest_after_insert($post, $request, $creating)
132
- {
133
- $post = get_post($post->ID);
134
- $post_meta = get_post_custom($post->ID);
135
-
136
- $old_post = $this->old_post_data[$post->ID]['post_data'];
137
- $old_post_meta = $this->old_post_data[$post->ID]['post_meta'];
138
-
139
- // @HERE
140
- $args = array(
141
- 'new_post' => $post,
142
- 'new_post_meta' => $post_meta,
143
- 'old_post' => $old_post,
144
- 'old_post_meta' => $old_post_meta,
145
- 'old_status' => $old_post->post_status,
146
- '_debug_caller_method' => __METHOD__
147
- );
148
-
149
- $this->maybe_log_post_change($args);
150
- }
151
-
152
- /**
153
- * Filters to XML RPC calls needs to be added early, admin_init is to late.
154
- */
155
- function add_xml_rpc_hooks()
156
- {
157
- // Debug: log all XML-RPC requests
158
- /*
159
- add_action("xmlrpc_call", function($method) {
160
- SimpleLogger()->debug("XML-RPC call for method '{method}'", array("method" => $method));
161
- }, 10, 1);
162
- */
163
-
164
- add_action(
165
- 'xmlrpc_call_success_blogger_newPost',
166
- array($this, 'on_xmlrpc_newPost'),
167
- 10,
168
- 2
169
- );
170
- add_action(
171
- 'xmlrpc_call_success_mw_newPost',
172
- array($this, 'on_xmlrpc_newPost'),
173
- 10,
174
- 2
175
- );
176
-
177
- add_action(
178
- 'xmlrpc_call_success_blogger_editPost',
179
- array($this, 'on_xmlrpc_editPost'),
180
- 10,
181
- 2
182
- );
183
- add_action(
184
- 'xmlrpc_call_success_mw_editPost',
185
- array($this, 'on_xmlrpc_editPost'),
186
- 10,
187
- 2
188
- );
189
-
190
- add_action(
191
- 'xmlrpc_call_success_blogger_deletePost',
192
- array($this, 'on_xmlrpc_deletePost'),
193
- 10,
194
- 2
195
- );
196
- add_action(
197
- 'xmlrpc_call_success_wp_deletePage',
198
- array($this, 'on_xmlrpc_deletePost'),
199
- 10,
200
- 2
201
- );
202
-
203
- add_action('xmlrpc_call', array($this, 'on_xmlrpc_call'), 10, 1);
204
- }
205
-
206
- function on_xmlrpc_call($method)
207
- {
208
- $arr_methods_to_act_on = array('wp.deletePost');
209
-
210
- $raw_post_data = null;
211
- $message = null;
212
- $context = array();
213
-
214
- if (in_array($method, $arr_methods_to_act_on)) {
215
- // Setup common stuff
216
- $raw_post_data = file_get_contents('php://input');
217
- $context[
218
- 'wp.deletePost.xmldata'
219
- ] = $this->simpleHistory->json_encode($raw_post_data);
220
- $message = new IXR_Message($raw_post_data);
221
-
222
- if (!$message->parse()) {
223
- return;
224
- }
225
-
226
- $context[
227
- 'wp.deletePost.xmlrpc_message'
228
- ] = $this->simpleHistory->json_encode($message);
229
- $context[
230
- 'wp.deletePost.xmlrpc_message.messageType'
231
- ] = $this->simpleHistory->json_encode($message->messageType);
232
- $context[
233
- 'wp.deletePost.xmlrpc_message.methodName'
234
- ] = $this->simpleHistory->json_encode($message->methodName);
235
- $context[
236
- 'wp.deletePost.xmlrpc_message.messageParams'
237
- ] = $this->simpleHistory->json_encode($message->params);
238
-
239
- // Actions for delete post
240
- if ('wp.deletePost' == $method) {
241
- // 4 params, where the last is the post id
242
- if (!isset($message->params[3])) {
243
- return;
244
- }
245
-
246
- $post_ID = $message->params[3];
247
-
248
- $post = get_post($post_ID);
249
-
250
- $context = array(
251
- 'post_id' => $post->ID,
252
- 'post_type' => get_post_type($post),
253
- 'post_title' => get_the_title($post)
254
- );
255
-
256
- $this->infoMessage('post_trashed', $context);
257
- }
258
- } // End if().
259
- }
260
-
261
- /**
262
- * Get array with information about this logger
263
- *
264
- * @return array
265
- */
266
- function getInfo()
267
- {
268
- $arr_info = array(
269
- 'name' => 'Post Logger',
270
- 'description' =>
271
- 'Logs the creation and modification of posts and pages',
272
- 'capability' => 'edit_pages',
273
- 'messages' => array(
274
- 'post_created' => __(
275
- 'Created {post_type} "{post_title}"',
276
- 'simple-history'
277
- ),
278
- 'post_updated' => __(
279
- 'Updated {post_type} "{post_title}"',
280
- 'simple-history'
281
- ),
282
- 'post_restored' => __(
283
- 'Restored {post_type} "{post_title}" from trash',
284
- 'simple-history'
285
- ),
286
- 'post_deleted' => __(
287
- 'Deleted {post_type} "{post_title}"',
288
- 'simple-history'
289
- ),
290
- 'post_trashed' => __(
291
- 'Moved {post_type} "{post_title}" to the trash',
292
- 'simple-history'
293
- )
294
- ),
295
- 'labels' => array(
296
- 'search' => array(
297
- 'label' => _x(
298
- 'Posts & Pages',
299
- 'Post logger: search',
300
- 'simple-history'
301
- ),
302
- 'label_all' => _x(
303
- 'All posts & pages activity',
304
- 'Post logger: search',
305
- 'simple-history'
306
- ),
307
- 'options' => array(
308
- _x(
309
- 'Posts created',
310
- 'Post logger: search',
311
- 'simple-history'
312
- ) => array('post_created'),
313
- _x(
314
- 'Posts updated',
315
- 'Post logger: search',
316
- 'simple-history'
317
- ) => array('post_updated'),
318
- _x(
319
- 'Posts trashed',
320
- 'Post logger: search',
321
- 'simple-history'
322
- ) => array('post_trashed'),
323
- _x(
324
- 'Posts deleted',
325
- 'Post logger: search',
326
- 'simple-history'
327
- ) => array('post_deleted'),
328
- _x(
329
- 'Posts restored',
330
- 'Post logger: search',
331
- 'simple-history'
332
- ) => array('post_restored')
333
- )
334
- ) // end search array
335
- ) // end labels
336
- );
337
-
338
- return $arr_info;
339
- }
340
-
341
- /**
342
- * Get and store old info about a post that is being edited.
343
- * Needed to later compare old data with new data, to detect differences.
344
- * This function is called on edit screen but before post edits are saved.
345
- *
346
- * Can't use the regular filters like "pre_post_update" because custom fields are already written by then.
347
- *
348
- * @since 2.0.29
349
- */
350
- function on_admin_action_editpost()
351
- {
352
- $post_ID = isset($_POST['post_ID']) ? (int) $_POST['post_ID'] : 0;
353
-
354
- if (!$post_ID) {
355
- return;
356
- }
357
-
358
- if (!current_user_can('edit_post', $post_ID)) {
359
- return;
360
- }
361
-
362
- $prev_post_data = get_post($post_ID);
363
-
364
- if (is_wp_error($prev_post_data)) {
365
- return;
366
- }
367
-
368
- $this->old_post_data[$post_ID] = array(
369
- 'post_data' => $prev_post_data,
370
- 'post_meta' => get_post_custom($post_ID)
371
- );
372
- }
373
-
374
- /**
375
- * Fires after a post has been successfully deleted via the XML-RPC Blogger API.
376
- *
377
- * @since 2.0.21
378
- *
379
- * @param int $post_ID ID of the deleted post.
380
- * @param array $args An array of arguments to delete the post.
381
- */
382
- function on_xmlrpc_deletePost($post_ID, $args)
383
- {
384
- $post = get_post($post_ID);
385
-
386
- $context = array(
387
- 'post_id' => $post->ID,
388
- 'post_type' => get_post_type($post),
389
- 'post_title' => get_the_title($post)
390
- );
391
-
392
- $this->infoMessage('post_deleted', $context);
393
- }
394
-
395
- /**
396
- * Fires after a post has been successfully updated via the XML-RPC API.
397
- *
398
- * @since 2.0.21
399
- *
400
- * @param int $post_ID ID of the updated post.
401
- * @param array $args An array of arguments for the post to edit.
402
- */
403
- function on_xmlrpc_editPost($post_ID, $args)
404
- {
405
- $post = get_post($post_ID);
406
-
407
- $context = array(
408
- 'post_id' => $post->ID,
409
- 'post_type' => get_post_type($post),
410
- 'post_title' => get_the_title($post)
411
- );
412
-
413
- $this->infoMessage('post_updated', $context);
414
- }
415
-
416
- /**
417
- * Fires after a new post has been successfully created via the XML-RPC API.
418
- *
419
- * @since 2.0.21
420
- *
421
- * @param int $post_ID ID of the new post.
422
- * @param array $args An array of new post arguments.
423
- */
424
- function on_xmlrpc_newPost($post_ID, $args)
425
- {
426
- $post = get_post($post_ID);
427
-
428
- $context = array(
429
- 'post_id' => $post->ID,
430
- 'post_type' => get_post_type($post),
431
- 'post_title' => get_the_title($post)
432
- );
433
-
434
- $this->infoMessage('post_created', $context);
435
- }
436
-
437
- /**
438
- * Called when a post is restored from the trash
439
- * @param int $post_id
440
- */
441
- function on_untrash_post($post_id)
442
- {
443
- $post = get_post($post_id);
444
-
445
- if (!$this->ok_to_log_post_posttype($post)) {
446
- return;
447
- }
448
-
449
- $this->infoMessage('post_restored', array(
450
- 'post_id' => $post_id,
451
- 'post_type' => get_post_type($post),
452
- 'post_title' => get_the_title($post)
453
- ));
454
- }
455
-
456
- /**
457
- * Fired immediately before a post is deleted from the database.
458
- *
459
- * @param int $postid Post ID.
460
- */
461
- function on_delete_post($post_id)
462
- {
463
- $post = get_post($post_id);
464
-
465
- if (wp_is_post_revision($post_id)) {
466
- return;
467
- }
468
-
469
- if (
470
- $post->post_status === 'auto-draft' ||
471
- $post->post_status === 'inherit'
472
- ) {
473
- return;
474
- }
475
-
476
- $ok_to_log = true;
477
-
478
- if (!$this->ok_to_log_post_posttype($post)) {
479
- $ok_to_log = false;
480
- }
481
-
482
- /**
483
- * Filter to control logging.
484
- *
485
- * @param bool $ok_to_log If this post deletion should be logged.
486
- * @param int $post_id
487
- *
488
- * @return bool True to log, false to not log.
489
- *
490
- * @since 2.21
491
- */
492
- $ok_to_log = apply_filters(
493
- 'simple_history/post_logger/post_deleted/ok_to_log',
494
- $ok_to_log,
495
- $post_id
496
- );
497
-
498
- if (!$ok_to_log) {
499
- return;
500
- }
501
-
502
- /*
503
- Posts that have been in the trash for 30 days (default)
504
- are deleted using a cron job that is called with action hook "wp_scheduled_delete".
505
- We skip logging these because users are confused and think that the real post has been
506
- deleted.
507
- We detect this by checking $wp_current_filter for 'wp_scheduled_delete'
508
- [
509
- "wp_scheduled_delete",
510
- "delete_post",
511
- "simple_history\/log_argument\/context"
512
- ]
513
- */
514
- global $wp_current_filter;
515
- if (isset($wp_current_filter) && is_array($wp_current_filter)) {
516
- if (in_array('wp_scheduled_delete', $wp_current_filter, true)) {
517
- return;
518
- }
519
- }
520
-
521
- $this->infoMessage('post_deleted', array(
522
- 'post_id' => $post_id,
523
- 'post_type' => get_post_type($post),
524
- 'post_title' => get_the_title($post)
525
- ));
526
- }
527
-
528
- /**
529
- * Get an array of post types that should not be logged by this logger.
530
- *
531
- * @return Array with post type slugs to skip.
532
- */
533
- public function get_skip_posttypes()
534
- {
535
- $skip_posttypes = array(
536
- // Don't log nav_menu_updates.
537
- 'nav_menu_item',
538
- // Don't log jetpack migration-things.
539
- // https://wordpress.org/support/topic/updated-jetpack_migration-sidebars_widgets/.
540
- 'jetpack_migration',
541
- 'jp_sitemap',
542
- 'jp_img_sitemap',
543
- 'jp_sitemap_master'
544
- );
545
-
546
- /**
547
- * Filter to log what post types not to log
548
- *
549
- * @since 2.18
550
- */
551
- $skip_posttypes = apply_filters(
552
- 'simple_history/post_logger/skip_posttypes',
553
- $skip_posttypes
554
- );
555
-
556
- return $skip_posttypes;
557
- }
558
-
559
- /**
560
- * Check if post type is ok to log by logger
561
- *
562
- * @param Int or WP_Post $post Post the check.
563
- *
564
- * @return bool
565
- */
566
- public function ok_to_log_post_posttype($post)
567
- {
568
- $ok_to_log = true;
569
- $skip_posttypes = $this->get_skip_posttypes();
570
-
571
- if (in_array(get_post_type($post), $skip_posttypes, true)) {
572
- $ok_to_log = false;
573
- }
574
-
575
- return $ok_to_log;
576
- }
577
-
578
- /**
579
- * Maybe log a post creation, modification or deletion.
580
- *
581
- * Todo:
582
- * - support password protect.
583
- * - post_password is set
584
- *
585
- * @param array $args Array with old and new post data.
586
- */
587
- public function maybe_log_post_change($args)
588
- {
589
- $default_args = array(
590
- 'new_post',
591
- 'new_post_meta',
592
- 'old_post',
593
- 'old_post_meta',
594
- // Old status is included because that's the value we get in filter
595
- // "transation_post_status", when a previous post may not exist.
596
- 'old_status'
597
- );
598
-
599
- $args = wp_parse_args($args, $default_args);
600
-
601
- // Bail if needed args not set.
602
- if (!isset($args['new_post']) || !isset($args['new_post_meta'])) {
603
- return;
604
- }
605
-
606
- $new_status = isset($args['new_post']->post_status)
607
- ? $args['new_post']->post_status
608
- : null;
609
- $post = $args['new_post'];
610
- $new_post_data = array(
611
- 'post_data' => $post,
612
- 'post_meta' => $args['new_post_meta']
613
- );
614
-
615
- // Set old status to status from old post with fallback to old_status variable.
616
- $old_status = isset($args['old_post']->post_status)
617
- ? $args['old_post']->post_status
618
- : null;
619
- $old_status =
620
- !isset($old_status) && isset($args['old_status'])
621
- ? $args['old_status']
622
- : $old_status;
623
-
624
- $old_post = isset($args['old_post']) ? $args['old_post'] : null;
625
- $old_post_meta = isset($args['old_post_meta'])
626
- ? $args['old_post_meta']
627
- : null;
628
- $old_post_data = array(
629
- 'post_data' => $old_post,
630
- 'post_meta' => $old_post_meta
631
- );
632
-
633
- // Default to log.
634
- $ok_to_log = true;
635
-
636
- // Calls from the WordPress ios app/jetpack comes from non-admin-area
637
- // i.e. is_admin() is false
638
- // so don't log when outside admin area.
639
- if (!is_admin()) {
640
- $ok_to_log = false;
641
- }
642
-
643
- // Except when calls are from/for Jetpack/WordPress apps.
644
- // seems to be jetpack/app request when $_GET["for"] == "jetpack.
645
- $isXmlRpcRequest = defined('XMLRPC_REQUEST') && XMLRPC_REQUEST;
646
- if (
647
- $isXmlRpcRequest &&
648
- isset($_GET['for']) &&
649
- 'jetpack' === $_GET['for']
650
- ) {
651
- $ok_to_log = true;
652
- }
653
-
654
- // Also accept calls from REST API
655
- $isRestApiRequest =
656
- (defined('REST_API_REQUEST') && REST_API_REQUEST) ||
657
- (defined('REST_REQUEST') && REST_REQUEST);
658
- if ($isRestApiRequest) {
659
- $ok_to_log = true;
660
- }
661
-
662
- // Don't log revisions.
663
- if (wp_is_post_revision($post)) {
664
- $ok_to_log = false;
665
- }
666
-
667
- // Don't log Gutenberg saving meta boxes.
668
- if (isset($_GET['meta-box-loader']) && $_GET['meta-box-loader']) {
669
- $ok_to_log = false;
670
- }
671
-
672
- if (!$this->ok_to_log_post_posttype($post)) {
673
- $ok_to_log = false;
674
- }
675
-
676
- /**
677
- * Filter to control logging.
678
- *
679
- * @param bool $ok_to_log
680
- * @param $new_status
681
- * @param $old_status
682
- * @param $post
683
- *
684
- * @return bool True to log, false to not log.
685
- *
686
- * @since 2.21
687
- */
688
- $ok_to_log = apply_filters(
689
- 'simple_history/post_logger/post_updated/ok_to_log',
690
- $ok_to_log,
691
- $new_status,
692
- $old_status,
693
- $post
694
- );
695
-
696
- if (!$ok_to_log) {
697
- return;
698
- }
699
-
700
- /*
701
- From new to auto-draft <- ignore
702
- From new to inherit <- ignore
703
- From auto-draft to draft <- page/post created
704
- From draft to draft
705
- From draft to pending
706
- From pending to publish
707
- From pending to trash
708
- From something to publish = post published
709
- if not from & to = same, then user has changed something
710
- From draft to publish in future: status = "future"
711
- */
712
- $context = array(
713
- 'post_id' => $post->ID,
714
- 'post_type' => get_post_type($post),
715
- 'post_title' => get_the_title($post)
716
- );
717
-
718
- if (
719
- 'auto-draft' === $old_status &&
720
- ('auto-draft' !== $new_status && 'inherit' !== $new_status)
721
- ) {
722
- // Post created
723
- $this->infoMessage('post_created', $context);
724
- } elseif (
725
- 'auto-draft' === $new_status ||
726
- ('new' === $old_status && 'inherit' === $new_status)
727
- ) {
728
- // Post was automagically saved by WordPress
729
- return;
730
- } elseif ('trash' === $new_status) {
731
- // Post trashed
732
- $this->infoMessage('post_trashed', $context);
733
- } else {
734
- // Existing post was updated.
735
-
736
- // Also add diff between previous saved data and new data.
737
- if (isset($old_post_data) && isset($new_post_data)) {
738
- // Now we have both old and new post data, including custom fields, in the same format
739
- // So let's compare!
740
- $context = $this->add_post_data_diff_to_context(
741
- $context,
742
- $old_post_data,
743
- $new_post_data
744
- );
745
- }
746
-
747
- $context['_occasionsID'] =
748
- __CLASS__ . '/' . __FUNCTION__ . "/post_updated/{$post->ID}";
749
-
750
- /**
751
- * Modify the context saved.
752
- *
753
- * @param array $context
754
- * @param WP_Post $post
755
- */
756
- $context = apply_filters(
757
- 'simple_history/post_logger/post_updated/context',
758
- $context,
759
- $post
760
- );
761
-
762
- $this->infoMessage('post_updated', $context);
763
- } // End if().
764
- }
765
-
766
- /**
767
- * Fired when a post has changed status in the classical editor.
768
- * Only run in certain cases,
769
- * because when always enabled it catches a lots of edits made by plugins during cron jobs etc,
770
- * which by definition is not wrong, but perhaps not wanted/annoying.
771
- *
772
- * @param string $new_status One of auto-draft, inherit, draft, pending, publish, future.
773
- * @param string $old_status Same as above.
774
- * @param WP_Post $post New updated post.
775
- */
776
- function on_transition_post_status($new_status, $old_status, $post)
777
- {
778
- // $isRestApiRequest = ( defined( 'REST_API_REQUEST' ) && REST_API_REQUEST ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST );
779
- // $is_admin = is_admin();
780
- // False if not a revision, ID of revision's parent otherwise.
781
- // $post_is_revision = wp_is_post_revision( $post );
782
- // sh_error_log('on_transition_post_status', '$new_status', $new_status, '$old_status', $old_status, '$isRestApiRequest', $isRestApiRequest, '$is_admin', $is_admin, '$post_is_revision', $post_is_revision);
783
- // Bail if post is not a post.
784
- if (!is_a($post, 'WP_Post')) {
785
- return;
786
- }
787
-
788
- // $old_post_data_exists = ! empty( $this->old_post_data[ $post->ID ] );
789
-
790
- $old_post = null;
791
- $old_post_meta = null;
792
-
793
- if (!empty($this->old_post_data[$post->ID])) {
794
- $old_post = $this->old_post_data[$post->ID]['post_data'];
795
- $old_post_meta = $this->old_post_data[$post->ID]['post_meta'];
796
- }
797
-
798
- $args = array(
799
- 'new_post' => $post,
800
- 'new_post_meta' => get_post_custom($post->ID),
801
- 'old_post' => $old_post,
802
- 'old_post_meta' => $old_post_meta,
803
- 'old_status' => $old_status,
804
- '_debug_caller_method' => __METHOD__
805
- );
806
-
807
- $this->maybe_log_post_change($args);
808
- }
809
-
810
- /**
811
- * Adds diff data to the context array. Is called just before the event is logged.
812
- *
813
- * Since 2.0.29
814
- *
815
- * To detect
816
- * - categories
817
- * - tags
818
- *
819
- * @param array $context Array with context.
820
- * @param array $old_post_data Old/prev post data.
821
- * @param array $new_post_data New post data.
822
- * @return array $context with diff data added.
823
- */
824
- function add_post_data_diff_to_context(
825
- $context,
826
- $old_post_data,
827
- $new_post_data
828
- ) {
829
- $old_data = $old_post_data['post_data'];
830
- $new_data = $new_post_data['post_data'];
831
-
832
- // Will contain the differences.
833
- $post_data_diff = array();
834
-
835
- $arr_keys_to_diff = array(
836
- 'post_title',
837
- 'post_name',
838
- 'post_content',
839
- 'post_status',
840
- 'menu_order',
841
- 'post_date',
842
- 'post_date_gmt',
843
- 'post_excerpt',
844
- 'comment_status',
845
- 'ping_status',
846
- 'post_parent', // only id, need to get context for that, like name of parent at least?
847
- 'post_author' // only id, need to get more info for user.
848
- );
849
-
850
- foreach ($arr_keys_to_diff as $key) {
851
- if (isset($old_data->$key) && isset($new_data->$key)) {
852
- $post_data_diff = $this->add_diff(
853
- $post_data_diff,
854
- $key,
855
- $old_data->$key,
856
- $new_data->$key
857
- );
858
- }
859
- }
860
-
861
- // If changes where detected.
862
- if ($post_data_diff) {
863
- // Save at least 2 values for each detected value change, i.e. the old value and the new value.
864
- foreach ($post_data_diff as $diff_key => $diff_values) {
865
- $context["post_prev_{$diff_key}"] = $diff_values['old'];
866
- $context["post_new_{$diff_key}"] = $diff_values['new'];
867
-
868
- // If post_author then get more author info,
869
- // because just a user ID does not get us far.
870
- if ('post_author' == $diff_key) {
871
- $old_author_user = get_userdata((int) $diff_values['old']);
872
- $new_author_user = get_userdata((int) $diff_values['new']);
873
-
874
- if (
875
- is_a($old_author_user, 'WP_User') &&
876
- is_a($new_author_user, 'WP_User')
877
- ) {
878
- $context["post_prev_{$diff_key}/user_login"] =
879
- $old_author_user->user_login;
880
- $context["post_prev_{$diff_key}/user_email"] =
881
- $old_author_user->user_email;
882
- $context["post_prev_{$diff_key}/display_name"] =
883
- $old_author_user->display_name;
884
-
885
- $context["post_new_{$diff_key}/user_login"] =
886
- $new_author_user->user_login;
887
- $context["post_new_{$diff_key}/user_email"] =
888
- $new_author_user->user_email;
889
- $context["post_new_{$diff_key}/display_name"] =
890
- $new_author_user->display_name;
891
- }
892
- }
893
- }
894
- } // End if().
895
-
896
- // Compare custom fields.
897
- // Array with custom field keys to ignore because changed everytime or very internal.
898
- $arr_meta_keys_to_ignore = array(
899
- '_edit_lock',
900
- '_edit_last',
901
- '_post_restored_from',
902
- '_wp_page_template',
903
- '_thumbnail_id'
904
- );
905
-
906
- $meta_changes = array(
907
- 'added' => array(),
908
- 'removed' => array(),
909
- 'changed' => array()
910
- );
911
-
912
- $old_meta = isset($old_post_data['post_meta'])
913
- ? (array) $old_post_data['post_meta']
914
- : array();
915
- $new_meta = isset($new_post_data['post_meta'])
916
- ? (array) $new_post_data['post_meta']
917
- : array();
918
-
919
- // Add post featured thumb data.
920
- $context = $this->add_post_thumb_diff($context, $old_meta, $new_meta);
921
-
922
- // Page template is stored in _wp_page_template.
923
- if (
924
- isset($old_meta['_wp_page_template'][0]) &&
925
- isset($new_meta['_wp_page_template'][0])
926
- ) {
927
- /*
928
- Var is string with length 7: default
929
- Var is string with length 20: template-builder.php
930
- */
931
-
932
- if (
933
- $old_meta['_wp_page_template'][0] !==
934
- $new_meta['_wp_page_template'][0]
935
- ) {
936
- // Prev page template is different from new page template,
937
- // store template php file name.
938
- $context['post_prev_page_template'] =
939
- $old_meta['_wp_page_template'][0];
940
- $context['post_new_page_template'] =
941
- $new_meta['_wp_page_template'][0];
942
-
943
- $theme_templates = (array) $this->get_theme_templates();
944
-
945
- if (
946
- isset($theme_templates[$context['post_prev_page_template']])
947
- ) {
948
- $context['post_prev_page_template_name'] =
949
- $theme_templates[$context['post_prev_page_template']];
950
- }
951
-
952
- if (
953
- isset($theme_templates[$context['post_new_page_template']])
954
- ) {
955
- $context['post_new_page_template_name'] =
956
- $theme_templates[$context['post_new_page_template']];
957
- }
958
- }
959
- }
960
-
961
- // Remove fields that we have checked already and other that should be ignored.
962
- foreach ($arr_meta_keys_to_ignore as $key_to_ignore) {
963
- unset($old_meta[$key_to_ignore]);
964
- unset($new_meta[$key_to_ignore]);
965
- }
966
-
967
- // Look for added custom fields.
968
- foreach ($new_meta as $meta_key => $meta_value) {
969
- if (!isset($old_meta[$meta_key])) {
970
- $meta_changes['added'][$meta_key] = true;
971
- }
972
- }
973
-
974
- // Look for removed meta.
975
- // Does not work, if user clicks "delete" in edit screen then meta is removed using ajax.
976
- /*
977
- foreach ( $old_meta as $meta_key => $meta_value ) {
978
-
979
- if ( ! isset($new_meta[ $meta_key ] ) ) {
980
- $meta_changes["removed"][ $meta_key ] = true;
981
- }
982
-
983
- }
984
- */
985
-
986
- // Look for changed meta.
987
- foreach ($old_meta as $meta_key => $meta_value) {
988
- if (isset($new_meta[$meta_key])) {
989
- if (
990
- json_encode($old_meta[$meta_key]) !=
991
- json_encode($new_meta[$meta_key])
992
- ) {
993
- $meta_changes['changed'][$meta_key] = true;
994
- }
995
- }
996
- }
997
-
998
- if ($meta_changes['added']) {
999
- $context['post_meta_added'] = count($meta_changes['added']);
1000
- }
1001
-
1002
- if ($meta_changes['removed']) {
1003
- $context['post_meta_removed'] = count($meta_changes['removed']);
1004
- }
1005
-
1006
- if ($meta_changes['changed']) {
1007
- $context['post_meta_changed'] = count($meta_changes['changed']);
1008
- }
1009
-
1010
- // Check for changes in post visbility and post password usage and store in context.
1011
- // publish = public
1012
- // publish + post_password = password protected
1013
- // private = post private
1014
- $old_post_has_password = !empty($old_data->post_password);
1015
- $old_post_password = $old_post_has_password
1016
- ? $old_data->post_password
1017
- : null;
1018
- $old_post_status = isset($old_data->post_status)
1019
- ? $old_data->post_status
1020
- : null;
1021
-
1022
- $new_post_has_password = !empty($new_data->post_password);
1023
- $new_post_password = $new_post_has_password
1024
- ? $new_data->post_password
1025
- : null;
1026
- $new_post_status = isset($new_data->post_status)
1027
- ? $new_data->post_status
1028
- : null;
1029
-
1030
- if (
1031
- false === $old_post_has_password &&
1032
- 'publish' === $new_post_status &&
1033
- $new_post_has_password
1034
- ) {
1035
- // If updated post is published and password is set and old post did not have password set
1036
- // = post changed to be password protected.
1037
- $context['post_password_protected'] = true;
1038
- } elseif (
1039
- $old_post_has_password &&
1040
- 'publish' === $old_post_status &&
1041
- false === $new_post_has_password &&
1042
- 'publish' === $new_post_status
1043
- ) {
1044
- // Old post is publish and had password protection and new post is publish but no password
1045
- // = post changed to be un-password protected
1046
- $context['post_password_unprotected'] = true;
1047
- } elseif (
1048
- $old_post_has_password &&
1049
- $new_post_has_password &&
1050
- $old_post_password !== $new_post_password
1051
- ) {
1052
- // If old post had password and new post has password, but passwords are note same
1053
- // = post has changed password.
1054
- $context['post_password_changed'] = true;
1055
- } elseif (
1056
- 'private' === $new_post_status &&
1057
- 'private' !== $old_post_status
1058
- ) {
1059
- // If new status is private and old is not
1060
- // = post is changed to be private.
1061
- $context['post_private'] = true;
1062
- // Also check if password was set before.
1063
- if ($old_post_has_password) {
1064
- $context['post_password_unprotected'] = true;
1065
- }
1066
- }
1067
-
1068
- // Todo: detect sticky.
1069
- // Sticky is stored in option:
1070
- // $sticky_posts = get_option('sticky_posts');
1071
-
1072
- return $context;
1073
- }
1074
-
1075
- /**
1076
- * Return the current theme templates.
1077
- * Template will return untranslated.
1078
- * Uses the same approach as in class-wp-theme.php to get templates.
1079
- *
1080
- * @since 2.0.29
1081
- */
1082
- public function get_theme_templates()
1083
- {
1084
- $theme = wp_get_theme();
1085
- $page_templates = array();
1086
-
1087
- $files = (array) $theme->get_files('php', 1);
1088
-
1089
- foreach ($files as $file => $full_path) {
1090
- if (
1091
- !preg_match(
1092
- '|Template Name:(.*)$|mi',
1093
- file_get_contents($full_path),
1094
- $header
1095
- )
1096
- ) {
1097
- continue;
1098
- }
1099
- $page_templates[$file] = _cleanup_header_comment($header[1]);
1100
- }
1101
-
1102
- return $page_templates;
1103
- }
1104
-
1105
- /**
1106
- * Add diff to array if old and new values are different
1107
- *
1108
- * Since 2.0.29
1109
- *
1110
- * @param array $post_data_diff Post data diff.
1111
- * @param string $key Key.
1112
- * @param mixed $old_value Old value.
1113
- * @param mixed $new_value New value.
1114
- * @return array
1115
- */
1116
- public function add_diff($post_data_diff, $key, $old_value, $new_value)
1117
- {
1118
- if ($old_value != $new_value) {
1119
- $post_data_diff[$key] = array(
1120
- 'old' => $old_value,
1121
- 'new' => $new_value
1122
- );
1123
- }
1124
-
1125
- return $post_data_diff;
1126
- }
1127
-
1128
- /**
1129
- * Modify plain output to include link to post.
1130
- *
1131
- * @param array $row Row data.
1132
- */
1133
- public function getLogRowPlainTextOutput($row)
1134
- {
1135
- $context = $row->context;
1136
- $post_id = isset($context['post_id']) ? $context['post_id'] : 0;
1137
-
1138
- // Default to original log message.
1139
- $message = $row->message;
1140
-
1141
- // Check if post still is available.
1142
- // It will return a WP_Post Object if post still is in system.
1143
- // If post is deleted from trash (not just moved there), then null is returned.
1144
- $post = get_post($post_id);
1145
- $post_is_available = is_a($post, 'WP_Post');
1146
-
1147
- $message_key = isset($context['_message_key'])
1148
- ? $context['_message_key']
1149
- : null;
1150
-
1151
- // Try to get singular name.
1152
- $post_type = isset($context['post_type']) ? $context['post_type'] : '';
1153
- $post_type_obj = get_post_type_object($post_type);
1154
- if (!is_null($post_type_obj)) {
1155
- if (!empty($post_type_obj->labels->singular_name)) {
1156
- $context['post_type'] = strtolower(
1157
- $post_type_obj->labels->singular_name
1158
- );
1159
- }
1160
- }
1161
-
1162
- $context['edit_link'] = get_edit_post_link($post_id);
1163
-
1164
- // If post is not available any longer then we can't link to it, so keep plain message then.
1165
- // Also keep plain format if user is not allowed to edit post (edit link is empty).
1166
- if ($post_is_available && $context['edit_link']) {
1167
- if ('post_updated' == $message_key) {
1168
- $message = __(
1169
- 'Updated {post_type} <a href="{edit_link}">"{post_title}"</a>',
1170
- 'simple-history'
1171
- );
1172
- } elseif ('post_deleted' == $message_key) {
1173
- $message = __(
1174
- 'Deleted {post_type} "{post_title}"',
1175
- 'simple-history'
1176
- );
1177
- } elseif ('post_created' == $message_key) {
1178
- $message = __(
1179
- 'Created {post_type} <a href="{edit_link}">"{post_title}"</a>',
1180
- 'simple-history'
1181
- );
1182
- } elseif ('post_trashed' == $message_key) {
1183
- // While in trash we can still get actions to delete or restore if we follow the edit link.
1184
- $message = __(
1185
- 'Moved {post_type} <a href="{edit_link}">"{post_title}"</a> to the trash',
1186
- 'simple-history'
1187
- );
1188
- }
1189
- } // End if().
1190
-
1191
- $context['post_type'] = isset($context['post_type'])
1192
- ? esc_html($context['post_type'])
1193
- : '';
1194
- $context['post_title'] = isset($context['post_title'])
1195
- ? esc_html($context['post_title'])
1196
- : '';
1197
-
1198
- return $this->interpolate($message, $context, $row);
1199
- }
1200
-
1201
- /**
1202
- * Get details output for row.
1203
- *
1204
- * @param array $row Row data.
1205
- */
1206
- public function getLogRowDetailsOutput($row)
1207
- {
1208
- $context = $row->context;
1209
- $message_key = $context['_message_key'];
1210
-
1211
- $out = '';
1212
-
1213
- if ('post_updated' == $message_key) {
1214
- // Check for keys like "post_prev_post_title" and "post_new_post_title".
1215
- $diff_table_output = '';
1216
- $has_diff_values = false;
1217
-
1218
- foreach ($context as $key => $val) {
1219
- if (strpos($key, 'post_prev_') !== false) {
1220
- // Old value exists, new value must also exist for diff to be calculates.
1221
- $key_to_diff = substr($key, strlen('post_prev_'));
1222
-
1223
- $key_for_new_val = "post_new_{$key_to_diff}";
1224
-
1225
- if (isset($context[$key_for_new_val])) {
1226
- $post_old_value = $context[$key];
1227
- $post_new_value = $context[$key_for_new_val];
1228
- if ($post_old_value != $post_new_value) {
1229
- // Different diffs for different keys.
1230
- if ('post_title' == $key_to_diff) {
1231
- $has_diff_values = true;
1232
-
1233
- $diff_table_output .= sprintf(
1234
- '<tr><td>%1$s</td><td>%2$s</td></tr>',
1235
- __('Title', 'simple-history'),
1236
- simple_history_text_diff(
1237
- $post_old_value,
1238
- $post_new_value
1239
- )
1240
- );
1241
- } elseif ('post_content' == $key_to_diff) {
1242
- // Problem: to much text/content.
1243
- // Risks to fill the visual output.
1244
- // Maybe solution: use own diff function, that uses none or few context lines.
1245
- $has_diff_values = true;
1246
- $key_text_diff = simple_history_text_diff(
1247
- $post_old_value,
1248
- $post_new_value
1249
- );
1250
-
1251
- if ($key_text_diff) {
1252
- $diff_table_output .= sprintf(
1253
- '<tr><td>%1$s</td><td>%2$s</td></tr>',
1254
- __('Content', 'simple-history'),
1255
- $key_text_diff
1256
- );
1257
- }
1258
- } elseif ('post_status' == $key_to_diff) {
1259
- $has_diff_values = true;
1260
- $diff_table_output .= sprintf(
1261
- '<tr>
5
  /**
6
  * Todo/@HERE
7
  * - [ ] install and test with ACF again
8
+ * - Install 5.7.13 and then each save or preview results in 2 or 3 adds to the log.
9
  * - The second save saves all the post meta. So it's technically two saves but not for the user.
10
+ * Both requests have the same HTTP_X_WP_NONCE
11
  * - [ ] test REST API update from curl or similar
12
  * - [ ] test REST API from Android/Ios-apps
13
  * - [ ] Save auto-saves? Not done by user but still done...
18
  */
19
  class SimplePostLogger extends SimpleLogger
20
  {
21
+ // The logger slug. Defaulting to the class name is nice and logical I think.
22
+ public $slug = __CLASS__;
23
+
24
+ // Array that will contain previous post data, before data is updated.
25
+ // Array format is
26
+ // [post_id] => [post_data, post_meta].
27
+ // post_data = WP_Post object, post_meta = post meta array.
28
+ protected $old_post_data = array();
29
+
30
+ public function loaded()
31
+ {
32
+ add_action('admin_action_editpost', array($this, 'on_admin_action_editpost'));
33
+ add_action('transition_post_status', array($this, 'on_transition_post_status'), 10, 3);
34
+ add_action('delete_post', array($this, 'on_delete_post'));
35
+ add_action('untrash_post', array($this, 'on_untrash_post'));
36
+
37
+ $this->add_xml_rpc_hooks();
38
+ $this->add_rest_hooks();
39
+
40
+ add_filter('simple_history/rss_item_link', array($this, 'filter_rss_item_link'), 10, 2);
41
+ }
42
+
43
+ /**
44
+ * Add hooks to catch updates via REST API, i.e. the new Gutenberg editor.
45
+ */
46
+ public function add_rest_hooks()
47
+ {
48
+ // Get all post types.
49
+ $post_types = get_post_types(array(), 'objects');
50
+
51
+ // Add actions for each post type.
52
+ foreach ($post_types as $post_type) {
53
+ // class-wp-rest-posts-controller.php fires two actions in
54
+ // the update_item() method: pre_insert and after_insert.
55
+
56
+ // Rest pre insert is fired before an updated post is inserted into db.
57
+ add_action("rest_pre_insert_{$post_type->name}", array($this, 'on_rest_pre_insert'), 10, 2);
58
+
59
+ // Rest insert happens after the post has been updated: "Fires after a single post is completely created or updated via the REST API."
60
+ // add_action( "rest_after_insert_{$post_type->name}", array( $this, 'on_rest_after_insert' ), 10, 3 );
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Filter "rest_pre_insert_{$this->post_type}" filters a post before it is inserted via the REST API.
66
+ * Fired from class-wp-rest-posts-controller.php.
67
+ *
68
+ * Here we can get the old post object.
69
+ *
70
+ * @param stdClass $prepared_post An object representing a single post prepared
71
+ * for inserting or updating the database, i.e. the new updated post.
72
+ * @param WP_REST_Request $request Request object.
73
+ * @return stdClass $prepared_post
74
+ */
75
+ public function on_rest_pre_insert($prepared_post, $request)
76
+ {
77
+ // $prepared_post = stdClass Object with new and modified content.
78
+ // changes are not saved to post in db yet, so get_post( $prepared_post->ID ) will get old contents.
79
+ /*
80
+ stdClass Object
81
+ (
82
+ [ID] => 889
83
+ [post_title] => gutenberg 1
84
+ [post_content] => <!-- wp:paragraph -->
85
+ <p>hejsan</p>
86
+ <!-- /wp:paragraph -->
87
+ [post_excerpt] =>
88
+ [post_type] => post
89
+ [page_template] =>
90
+ )
91
+ */
92
+
93
+ // $old_post = post with old content and old meta
94
+ $old_post = get_post($prepared_post->ID);
95
+
96
+ $this->old_post_data[$old_post->ID] = array(
97
+ 'post_data' => $old_post,
98
+ 'post_meta' => get_post_custom($old_post->ID)
99
+ );
100
+
101
+ return $prepared_post;
102
+ }
103
+
104
+ /**
105
+ * Fires after a single post is completely created or updated via the REST API.
106
+ *
107
+ * Here we can get the updated post, after it's updated in the db.
108
+ *
109
+ * @param WP_Post $post Inserted or updated post object.
110
+ * @param WP_REST_Request $request Request object.
111
+ * @param bool $creating True when creating a post, false when updating.
112
+ */
113
+ public function on_rest_after_insert($post, $request, $creating)
114
+ {
115
+ $post = get_post($post->ID);
116
+ $post_meta = get_post_custom($post->ID);
117
+
118
+ $old_post = $this->old_post_data[$post->ID]['post_data'];
119
+ $old_post_meta = $this->old_post_data[$post->ID]['post_meta'];
120
+
121
+ $args = array(
122
+ 'new_post' => $post,
123
+ 'new_post_meta' => $post_meta,
124
+ 'old_post' => $old_post,
125
+ 'old_post_meta' => $old_post_meta,
126
+ 'old_status' => $old_post->post_status,
127
+ '_debug_caller_method' => __METHOD__
128
+ );
129
+
130
+ $this->maybe_log_post_change($args);
131
+ }
132
+
133
+ /**
134
+ * Filters to XML RPC calls needs to be added early, admin_init is to late.
135
+ */
136
+ public function add_xml_rpc_hooks()
137
+ {
138
+ // Debug: log all XML-RPC requests
139
+ /*
140
+ add_action("xmlrpc_call", function($method) {
141
+ SimpleLogger()->debug("XML-RPC call for method '{method}'", array("method" => $method));
142
+ }, 10, 1);
143
+ */
144
+
145
+ add_action('xmlrpc_call_success_blogger_newPost', array($this, 'on_xmlrpc_newPost'), 10, 2);
146
+ add_action('xmlrpc_call_success_mw_newPost', array($this, 'on_xmlrpc_newPost'), 10, 2);
147
+
148
+ add_action('xmlrpc_call_success_blogger_editPost', array($this, 'on_xmlrpc_editPost'), 10, 2);
149
+ add_action('xmlrpc_call_success_mw_editPost', array($this, 'on_xmlrpc_editPost'), 10, 2);
150
+
151
+ add_action('xmlrpc_call_success_blogger_deletePost', array($this, 'on_xmlrpc_deletePost'), 10, 2);
152
+ add_action('xmlrpc_call_success_wp_deletePage', array($this, 'on_xmlrpc_deletePost'), 10, 2);
153
+
154
+ add_action('xmlrpc_call', array($this, 'on_xmlrpc_call'), 10, 1);
155
+ }
156
+
157
+ public function on_xmlrpc_call($method)
158
+ {
159
+ $arr_methods_to_act_on = array('wp.deletePost');
160
+
161
+ $raw_post_data = null;
162
+ $message = null;
163
+ $context = array();
164
+
165
+ if (in_array($method, $arr_methods_to_act_on)) {
166
+ // Setup common stuff
167
+ $raw_post_data = file_get_contents('php://input');
168
+ $context['wp.deletePost.xmldata'] = $this->simpleHistory->json_encode($raw_post_data);
169
+ $message = new IXR_Message($raw_post_data);
170
+
171
+ if (!$message->parse()) {
172
+ return;
173
+ }
174
+
175
+ $context['wp.deletePost.xmlrpc_message'] = $this->simpleHistory->json_encode($message);
176
+ $context['wp.deletePost.xmlrpc_message.messageType'] = $this->simpleHistory->json_encode(
177
+ $message->messageType
178
+ );
179
+ $context['wp.deletePost.xmlrpc_message.methodName'] = $this->simpleHistory->json_encode(
180
+ $message->methodName
181
+ );
182
+ $context['wp.deletePost.xmlrpc_message.messageParams'] = $this->simpleHistory->json_encode(
183
+ $message->params
184
+ );
185
+
186
+ // Actions for delete post
187
+ if ('wp.deletePost' == $method) {
188
+ // 4 params, where the last is the post id
189
+ if (!isset($message->params[3])) {
190
+ return;
191
+ }
192
+
193
+ $post_ID = $message->params[3];
194
+
195
+ $post = get_post($post_ID);
196
+
197
+ $context = array(
198
+ 'post_id' => $post->ID,
199
+ 'post_type' => get_post_type($post),
200
+ 'post_title' => get_the_title($post)
201
+ );
202
+
203
+ $this->infoMessage('post_trashed', $context);
204
+ }
205
+ } // End if().
206
+ }
207
+
208
+ /*