Simple History - Version 2.16

Version Description

(May 2017) =

  • Added WP-CLI command for Simple History. Now you can write wp simple-history list to see the latest entries from the history log. For now list is the only available command. Let me know if you need more commands!
  • Added support for logging edits to theme files and plugin files. When a file is edited you will also get a quick diff on the changes, so you can see what CSS styles a client changed or what PHP changes they made in a plugin file.
  • Removed the edit file logger from the plugin logger, because it did not always work (checked wrong wp path). Intead the new Theme and plugins logger mentioned above will take care of this.
Download this release

Release Info

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

Code changes from version 2.15 to 2.16

dropins/SimpleHistoryDonateDropin.php CHANGED
@@ -18,7 +18,7 @@ class SimpleHistoryDonateDropin {
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);
@@ -30,7 +30,7 @@ class SimpleHistoryDonateDropin {
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(
@@ -39,7 +39,7 @@ class SimpleHistoryDonateDropin {
39
  );
40
 
41
  }
42
-
43
  return $links;
44
 
45
  }
@@ -49,9 +49,9 @@ class SimpleHistoryDonateDropin {
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
 
@@ -69,17 +69,17 @@ class SimpleHistoryDonateDropin {
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
- "http://eskapism.se/sida/donate/?utm_source=wordpress&utm_medium=pluginpage&utm_campaign=simplehistory",
76
  "http://www.amazon.co.uk/registry/wishlist/IAEZWNLQQICG"
77
  );
78
-
79
  }
80
 
81
 
82
  function settings_field_donate() {
83
  }
84
 
85
- } // end rss class
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);
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(
39
  );
40
 
41
  }
42
+
43
  return $links;
44
 
45
  }
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
 
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
+ "http://eskapism.se/sida/donate/?utm_source=wordpress&utm_medium=pluginpage&utm_campaign=simplehistory",
76
  "http://www.amazon.co.uk/registry/wishlist/IAEZWNLQQICG"
77
  );
78
+
79
  }
80
 
81
 
82
  function settings_field_donate() {
83
  }
84
 
85
+ }
dropins/SimpleHistoryWPCLIDropin.php ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ defined( 'ABSPATH' ) or die();
4
+
5
+ /*
6
+ Dropin Name: WP CLI
7
+ 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
+
22
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
23
+ $this->register_commands();
24
+ }
25
+
26
+ }
27
+
28
+ private function register_commands() {
29
+ $commandConfigurationOptions = array(
30
+ 'shortdesc' => 'Lists the history log',
31
+ 'synopsis' => array(
32
+ array(
33
+ 'type' => 'assoc',
34
+ 'name' => 'format',
35
+ 'optional' => true,
36
+ 'default' => 'table',
37
+ 'options' => array( 'table', 'json', 'csv', 'yaml' ),
38
+ ),
39
+ array(
40
+ 'type' => 'assoc',
41
+ 'name' => 'count',
42
+ 'optional' => true,
43
+ 'default' => '10',
44
+ ),
45
+ ),
46
+ 'when' => 'after_wp_load',
47
+ );
48
+
49
+ WP_CLI::add_command( 'simple-history list', array($this, 'commandList'), $commandConfigurationOptions );
50
+ }
51
+
52
+ private function getInitiatorTextFromRow( $row ) {
53
+ if (! isset( $row->initiator )) {
54
+ return false;
55
+ }
56
+
57
+ $initiator = $row->initiator;
58
+ $initiatorText = '';
59
+
60
+ switch ($initiator) {
61
+ case "wp":
62
+ $initiatorText = 'WordPress';
63
+ break;
64
+ case "wp_cli":
65
+ $initiatorText = 'WP-CLI';
66
+ break;
67
+ case "wp_user":
68
+ $user_id = isset($row->context["_user_id"]) ? $row->context["_user_id"] : null;
69
+
70
+ if ( $user_id > 0 && $user = get_user_by("id", $user_id) ) {
71
+ // User still exists
72
+
73
+ // Get user role, as done in user-edit.php
74
+ $wp_roles = $GLOBALS["wp_roles"];
75
+ $all_roles = (array) $wp_roles->roles;
76
+ $user_roles = array_intersect( array_values( (array) $user->roles ), array_keys( (array) $wp_roles->roles ));
77
+ $user_role = array_shift( $user_roles );
78
+
79
+ $initiatorText = sprintf(
80
+ '%1$s (%2$s)' ,
81
+ $user->user_login, // 1
82
+ $user->user_email // 2
83
+ );
84
+
85
+ } else if ($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
+ } // if user exists or not
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
+ }
104
+
105
+ return $initiatorText;
106
+ }
107
+
108
+ /**
109
+ * The function for the command "list"
110
+ */
111
+ public function commandList( $args, $assoc_args ) {
112
+
113
+ if ( ! is_numeric($assoc_args["count"]) ) {
114
+ WP_CLI::error( __('Error: parameter "count" must be a number', 'simple-history' ) );
115
+ }
116
+
117
+ // Override capability check: if you can run wp cli commands you can read all loggers
118
+ add_action( 'simple_history/loggers_user_can_read/can_read_single_logger', '__return_true', 10, 3);
119
+
120
+ // WP_CLI::log( sprintf( 'Showing %1$d events from Simple History', $assoc_args["count"] ) );
121
+
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
+
139
+ $header_output = strip_tags( html_entity_decode( $header_output, ENT_QUOTES, 'UTF-8') );
140
+ $header_output = trim(preg_replace('/\s\s+/', ' ', $header_output));
141
+
142
+ $text_output = strip_tags( html_entity_decode( $text_output, ENT_QUOTES, 'UTF-8') );
143
+
144
+ $eventsCleaned[] = array(
145
+ "date" => get_date_from_gmt( $row->date ),
146
+ // "initiator" => $row->initiator,
147
+ "initiator" => $this->getInitiatorTextFromRow( $row ),
148
+ "logger" => $row->logger,
149
+ "level" => $row->level,
150
+ "who_when" => $header_output,
151
+ "description" => $text_output,
152
+ "count" => $row->subsequentOccasions,
153
+ // "details" => $details_output
154
+ );
155
+ }
156
+
157
+ #print_r($events);
158
+ #print_r($eventsCleaned);
159
+ /*
160
+ [9] => stdClass Object
161
+ (
162
+ [id] => 735
163
+ [logger] => AvailableUpdatesLogger
164
+ [level] => notice
165
+ [date] => 2017-05-19 12:45:13
166
+ [message] => Found an update to plugin "{plugin_name}"
167
+ [initiator] => wp
168
+ [occasionsID] => 9a2d42eebea5c3cd2b16db0c38258016
169
+ [subsequentOccasions] => 1
170
+ [rep] => 1
171
+ [repeated] => 10
172
+ [occasionsIDType] => 9a2d42eebea5c3cd2b16db0c38258016
173
+ [context_message_key] => plugin_update_available
174
+ [context] => Array
175
+ (
176
+ [plugin_name] => WooCommerce
177
+ [plugin_current_version] => 3.0.6
178
+ [plugin_new_version] => 3.0.7
179
+ [_message_key] => plugin_update_available
180
+ [_server_remote_addr] => ::1
181
+ [_server_http_referer] => http://wp-playground.dev/wp/wp-cron.php?doing_wp_cron=1495197902.1593680381774902343750
182
+ )
183
+
184
+ )
185
+ */
186
+
187
+ $fields = array(
188
+ 'date',
189
+ 'initiator',
190
+ 'description',
191
+ 'level',
192
+ 'count',
193
+ );
194
+
195
+ WP_CLI\Utils\format_items( $assoc_args['format'], $eventsCleaned, $fields );
196
+
197
+ }
198
+
199
+ }
inc/SimpleHistory.php CHANGED
@@ -881,6 +881,7 @@ class SimpleHistory {
881
  $loggersDir . "SimpleUserLogger.php",
882
  $loggersDir . "SimpleCategoriesLogger.php",
883
  $loggersDir . "AvailableUpdatesLogger.php",
 
884
 
885
  // Loggers for third party plugins
886
  $loggersDir . "PluginUserSwitchingLogger.php",
@@ -1080,6 +1081,7 @@ class SimpleHistory {
1080
  $dropinsDir . "SimpleHistorySettingsDebugDropin.php",
1081
  $dropinsDir . "SimpleHistorySidebarDropin.php",
1082
  $dropinsDir . "SimpleHistorySidebarStats.php",
 
1083
  );
1084
 
1085
  /**
881
  $loggersDir . "SimpleUserLogger.php",
882
  $loggersDir . "SimpleCategoriesLogger.php",
883
  $loggersDir . "AvailableUpdatesLogger.php",
884
+ $loggersDir . "FileEditsLogger.php",
885
 
886
  // Loggers for third party plugins
887
  $loggersDir . "PluginUserSwitchingLogger.php",
1081
  $dropinsDir . "SimpleHistorySettingsDebugDropin.php",
1082
  $dropinsDir . "SimpleHistorySidebarDropin.php",
1083
  $dropinsDir . "SimpleHistorySidebarStats.php",
1084
+ $dropinsDir . "SimpleHistoryWPCLIDropin.php",
1085
  );
1086
 
1087
  /**
index.php CHANGED
@@ -5,7 +5,7 @@ Plugin URI: http://simple-history.com
5
  Text Domain: simple-history
6
  Domain Path: /languages
7
  Description: Plugin that logs various things that occur in WordPress and then presents those events in a very nice GUI.
8
- Version: 2.15
9
  Author: Pär Thernström
10
  Author URI: http://simple-history.com/
11
  License: GPL2
@@ -42,7 +42,7 @@ if ( version_compare( phpversion(), "5.3", ">=") ) {
42
  // register_activation_hook( trailingslashit(WP_PLUGIN_DIR) . trailingslashit( plugin_basename(__DIR__) ) . "index.php" , array("SimpleHistory", "on_plugin_activate" ) );
43
 
44
  if ( ! defined( 'SIMPLE_HISTORY_VERSION' ) ) {
45
- define( 'SIMPLE_HISTORY_VERSION', '2.15' );
46
  }
47
 
48
  if ( ! defined( 'SIMPLE_HISTORY_PATH' ) ) {
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.16
9
  Author: Pär Thernström
10
  Author URI: http://simple-history.com/
11
  License: GPL2
42
  // register_activation_hook( trailingslashit(WP_PLUGIN_DIR) . trailingslashit( plugin_basename(__DIR__) ) . "index.php" , array("SimpleHistory", "on_plugin_activate" ) );
43
 
44
  if ( ! defined( 'SIMPLE_HISTORY_VERSION' ) ) {
45
+ define( 'SIMPLE_HISTORY_VERSION', '2.16' );
46
  }
47
 
48
  if ( ! defined( 'SIMPLE_HISTORY_PATH' ) ) {
loggers/FileEditsLogger.php ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
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
+
65
+ // Get info about the edited plugin
66
+ $pluginInfo = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_file );
67
+ $pluginName = isset( $pluginInfo['Name'] ) ? $pluginInfo['Name'] : null;
68
+ $pluginVersion = isset( $pluginInfo['Version'] ) ? $pluginInfo['Version'] : null;
69
+
70
+ // Get contents before save
71
+ $fileContentsBeforeEdit = file_get_contents( WP_PLUGIN_DIR . '/' . $file );
72
+
73
+ $context = array(
74
+ "file_name" => $plugin_file,
75
+ "plugin_name" => $pluginName,
76
+ "plugin_version" => $pluginVersion,
77
+ "old_file_contents" => $fileContentsBeforeEdit,
78
+ "new_file_contents" => $fileNewContents,
79
+ "_occasionsID" => __CLASS__ . '/' . __FUNCTION__ . "/file-edit/$plugin_file/$file"
80
+ );
81
+
82
+ #ddd($_POST, $context, $action, $file, $plugin, $phperror, $fileNewContents, $scrollto);
83
+ $loggerInstance = $this;
84
+ add_filter( 'wp_redirect', function ( $location, $status ) use ( $context, $loggerInstance ) {
85
+ error_log($location);
86
+
87
+ $locationParsed = parse_url( $location );
88
+
89
+ if ( $locationParsed === false || empty( $locationParsed['query'] ) ) {
90
+ return $location;
91
+ }
92
+
93
+ parse_str( $locationParsed['query'], $queryStringParsed);
94
+ #ddd($_POST, $context, $queryStringParsed, $location);
95
+
96
+ if (empty($queryStringParsed)) {
97
+ return $location;
98
+ }
99
+
100
+ // If query string "a=te" exists or "liveupdate=1" then plugin file was updated
101
+ $teIsSet = isset($queryStringParsed['a']) && $queryStringParsed['a'] === 'te';
102
+ $liveUpdateIsSet = isset($queryStringParsed['liveupdate']) && $queryStringParsed['liveupdate'] === '1';
103
+ if ( $teIsSet || $liveUpdateIsSet ) {
104
+ // File was updated
105
+ $loggerInstance->infoMessage('plugin_file_edited', $context);
106
+ }
107
+
108
+ return $location;
109
+
110
+ // location when successful edit to non-active plugin
111
+ // http://wp-playground.dev/wp/wp-admin/plugin-editor.php?file=akismet/akismet.php&plugin=akismet/akismet.php&a=te&scrollto=0
112
+
113
+ // locations when activated plugin edited successfully
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
+ // http://wp-playground.dev/wp/wp-admin/plugin-editor.php?file=akismet/akismet.php&plugin=akismet/akismet.php&a=te&scrollto=0
117
+
118
+ // locations when editing active plugin and error occurs
119
+ // plugin-editor.php?file=akismet%2Fakismet.php&plugin=akismet%2Fakismet.php&liveupdate=1&scrollto=0&networkwide&_wpnonce=b3f399fe94
120
+ // plugin-editor.php?file=akismet%2Fakismet.php&phperror=1&_error_nonce=63511c266d
121
+
122
+ // locations when error edit is fixed and saved and plugin is activated again
123
+ // plugin-editor.php?file=akismet%2Fakismet.php&plugin=akismet%2Fakismet.php&liveupdate=1&scrollto=0&networkwide&_wpnonce=b3f399fe94
124
+ // plugin-editor.php?file=akismet%2Fakismet.php&phperror=1&_error_nonce=63511c266d
125
+ // http://wp-playground.dev/wp/wp-admin/plugin-editor.php?file=akismet/akismet.php&plugin=akismet/akismet.php&a=te&scrollto=0
126
+
127
+ }, 10, 2 );
128
+
129
+ }
130
+ /*
131
+ <?php if (isset($_GET['a'])) : ?>
132
+ <div id="message" class="updated notice is-dismissible"><p><?php _e('File edited successfully.') ?></p></div>
133
+ <?php elseif (isset($_GET['phperror'])) : ?>
134
+ <div id="message" class="updated"><p><?php _e('This plugin has been deactivated because your changes resulted in a <strong>fatal error</strong>.') ?></p>
135
+ */
136
+ }
137
+
138
+ /**
139
+ * Called when /wp/wp-admin/theme-editor.php is loaded
140
+ * Both using regular GET and during POST with updated file data
141
+ *
142
+ * When this action is fired we don't know if a file will be successfully saved or not.
143
+ * There are no filters/actions fired when the edit is saved. On the end wp_redirect() is
144
+ * called however and we know the location for the redirect and wp_redirect() has filters
145
+ * so we hook onto that to save the edit.
146
+ */
147
+ public function on_load_theme_editor() {
148
+ // Only continue if method is post and action is update
149
+ if (isset($_POST) && isset($_POST["action"]) && $_POST["action"] === "update") {
150
+ /*
151
+ POST data is like
152
+ array(8)
153
+ '_wpnonce' => string(10) "9b5e46634f"
154
+ '_wp_http_referer' => string(88) "/wp/wp-admin/theme-editor.php?file=style.css&theme=twentyfifteen&scrollto=0&upda…"
155
+ 'newcontent' => string(104366) "/* Theme Name: Twenty Fifteen Theme URI: https://wordpress.org/themes/twentyfift…"
156
+ 'action' => string(6) "update"
157
+ 'file' => string(9) "style.css"
158
+ 'theme' => string(13) "twentyfifteen"
159
+ 'scrollto' => string(3) "638"
160
+ 'submit' => string(11) "Update File"
161
+ */
162
+
163
+ $action = isset($_POST["action"]) ? $_POST["action"] : null;
164
+ $file = isset($_POST["file"]) ? $_POST["file"] : null;
165
+ $theme = isset($_POST["theme"]) ? $_POST["theme"] : null;
166
+ $fileNewContents = isset($_POST["newcontent"]) ? wp_unslash( $_POST["newcontent"] ) : null;
167
+ $scrollto = isset($_POST["scrollto"]) ? (int) $_POST["scrollto"] : 0;
168
+
169
+ // Same code as in theme-editor.php
170
+ if ( $theme ) {
171
+ $stylesheet = $theme;
172
+ } else {
173
+ $stylesheet = get_stylesheet();
174
+ }
175
+
176
+ $theme = wp_get_theme( $stylesheet );
177
+
178
+ if (! is_a($theme, 'WP_Theme')) {
179
+ return;
180
+ }
181
+
182
+ // Same code as in theme-editor.php
183
+ $relative_file = $file;
184
+ $file = $theme->get_stylesheet_directory() . '/' . $relative_file;
185
+
186
+ // Get file contents, so we have something to compare with later
187
+ $fileContentsBeforeEdit = file_get_contents($file);
188
+
189
+ $context = array(
190
+ "theme_name" => $theme->name,
191
+ "theme_stylesheet_path" => $theme->get_stylesheet(),
192
+ "theme_stylesheet_dir" => $theme->get_stylesheet_directory(),
193
+ "file_name" => $relative_file,
194
+ "file_dir" => $file,
195
+ "old_file_contents" => $fileContentsBeforeEdit,
196
+ "new_file_contents" => $fileNewContents,
197
+ "_occasionsID" => __CLASS__ . '/' . __FUNCTION__ . "/file-edit/$file"
198
+ );
199
+
200
+ // Hook into wp_redirect
201
+ // This hook is only added when we know a POST is done from theme-editor.php
202
+ $loggerInstance = $this;
203
+ add_filter( 'wp_redirect', function ( $location, $status ) use ( $context, $loggerInstance ) {
204
+ $locationParsed = parse_url( $location );
205
+
206
+ if ( $locationParsed === false || empty( $locationParsed['query'] ) ) {
207
+ return $location;
208
+ }
209
+
210
+ parse_str( $locationParsed['query'], $queryStringParsed);
211
+
212
+ if (empty($queryStringParsed)) {
213
+ return $location;
214
+ }
215
+
216
+ if (isset($queryStringParsed["updated"]) && $queryStringParsed["updated"]) {
217
+ // File was updated
218
+ $loggerInstance->infoMessage('theme_file_edited', $context);
219
+ } else {
220
+ // File was not updated. Unknown reason, but probably because could not be written.
221
+ }
222
+
223
+ return $location;
224
+
225
+ }, 10, 2 ); // add_filter
226
+ } // if post action update
227
+ }
228
+
229
+
230
+ public function getLogRowDetailsOutput( $row ) {
231
+
232
+ $context = $row->context;
233
+ $message_key = isset( $context["_message_key"] ) ? $context["_message_key"] : null;
234
+
235
+ if (! $message_key) {
236
+ return;
237
+ }
238
+
239
+ $out = '';
240
+
241
+ $diff_table_output = '';
242
+
243
+ if ( ! empty($context['new_file_contents']) && ! empty($context['old_file_contents']) ) {
244
+ if ( $context['new_file_contents'] !== $context['old_file_contents'] ) {
245
+ $diff_table_output .= sprintf(
246
+ '<tr><td>%1$s</td><td>%2$s</td></tr>',
247
+ __("File contents", "simple-history"),
248
+ simple_history_text_diff($context['old_file_contents'], $context['new_file_contents'])
249
+ );
250
+ }
251
+ }
252
+
253
+ if ( $diff_table_output ) {
254
+
255
+ $diff_table_output = '<table class="SimpleHistoryLogitem__keyValueTable">' . $diff_table_output . '</table>';
256
+
257
+ }
258
+
259
+ $out .= $diff_table_output;
260
+
261
+ return $out;
262
+
263
+ }
264
+
265
+ } // class
loggers/PluginUserSwitchingLogger.php CHANGED
@@ -60,7 +60,6 @@ class PluginUserSwitchingLogger extends SimpleLogger {
60
  // It is the old user who initiates the switching
61
  "_initiator" => SimpleLoggerLogInitiators::WP_USER,
62
  "_user_id" => $old_user_id,
63
-
64
  "user_id" => $user_id,
65
  "old_user_id" => $old_user_id,
66
  "user_login_to" => $user_to->user_login,
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,
loggers/SimplePluginLogger.php CHANGED
@@ -59,12 +59,6 @@ class SimplePluginLogger extends SimpleLogger {
59
  'simple-history'
60
  ),
61
 
62
- 'plugin_file_edited' => _x(
63
- 'Edited plugin file "{plugin_edited_file}"',
64
- 'Plugin file edited',
65
- 'simple-history'
66
- ),
67
-
68
  'plugin_deleted' => _x(
69
  'Deleted plugin "{plugin_name}"',
70
  'Plugin files was deleted',
@@ -111,9 +105,6 @@ class SimplePluginLogger extends SimpleLogger {
111
  _x("Failed plugin updates", "Plugin logger: search", "simple-history") => array(
112
  'plugin_update_failed'
113
  ),
114
- _x("Edited plugin files", "Plugin logger: search", "simple-history") => array(
115
- 'plugin_file_edited'
116
- ),
117
  _x("Deleted plugins", "Plugin logger: search", "simple-history") => array(
118
  'plugin_deleted'
119
  ),
@@ -152,9 +143,6 @@ class SimplePluginLogger extends SimpleLogger {
152
  // Check hook extra for upgrader initiator
153
  add_action( 'upgrader_process_complete', array( $this, "on_upgrader_process_complete" ), 10, 2 );
154
 
155
- // Dirty check for things that we can't catch using filters or actions
156
- add_action( 'admin_init', array( $this, "check_filterless_things" ) );
157
-
158
  // Detect files removed
159
  add_action( 'setted_transient', array( $this, 'on_setted_transient_for_remove_files' ), 10, 2 );
160
 
@@ -479,64 +467,6 @@ class SimplePluginLogger extends SimpleLogger {
479
 
480
  }
481
 
482
- function check_filterless_things() {
483
-
484
- // Var is string with length 113: /wp-admin/plugin-editor.php?file=my-plugin%2Fviews%2Fplugin-file.php
485
- $referer = wp_get_referer();
486
-
487
- // contains key "path" with value like "/wp-admin/plugin-editor.php"
488
- $referer_info = parse_url($referer);
489
-
490
- if ( "/wp-admin/plugin-editor.php" === $referer_info["path"] ) {
491
-
492
- // We are in plugin editor
493
- // Check for plugin edit saved
494
- if ( isset( $_POST["newcontent"] ) && isset( $_POST["action"] ) && "update" == $_POST["action"] && isset( $_POST["file"] ) && ! empty( $_POST["file"] ) ) {
495
-
496
- // A file was edited
497
- $file = $_POST["file"];
498
-
499
- // $plugins = get_plugins();
500
- // http://codex.wordpress.org/Function_Reference/wp_text_diff
501
-
502
- // Generate a diff of changes
503
- if ( ! class_exists( 'WP_Text_Diff_Renderer_Table' ) ) {
504
- require_once( ABSPATH . WPINC . '/wp-diff.php' );
505
- }
506
-
507
- $original_file_contents = file_get_contents( WP_PLUGIN_DIR . "/" . $file );
508
- $new_file_contents = wp_unslash( $_POST["newcontent"] );
509
-
510
- $left_lines = explode("\n", $original_file_contents);
511
- $right_lines = explode("\n", $new_file_contents);
512
- $text_diff = new Text_Diff($left_lines, $right_lines);
513
-
514
- $num_added_lines = $text_diff->countAddedLines();
515
- $num_removed_lines = $text_diff->countDeletedLines();
516
-
517
- // Generate a diff in classic diff format
518
- $renderer = new Text_Diff_Renderer();
519
- $diff = $renderer->render($text_diff);
520
-
521
- $this->infoMessage(
522
- 'plugin_file_edited',
523
- array(
524
- "plugin_edited_file" => $file,
525
- "plugin_edit_diff" => $diff,
526
- "plugin_edit_num_added_lines" => $num_added_lines,
527
- "plugin_edit_num_removed_lines" => $num_removed_lines,
528
- )
529
- );
530
-
531
- $did_log = true;
532
-
533
- }
534
-
535
- }
536
-
537
-
538
- }
539
-
540
  /**
541
  * Called when plugins is updated or installed
542
  * Called from class-wp-upgrader.php
@@ -1184,7 +1114,7 @@ class SimplePluginLogger extends SimpleLogger {
1184
  if ( "plugin_updated" == $message_key || "plugin_bulk_updated" == $message_key ) {
1185
 
1186
  $link_title = esc_html_x("View changelog", "plugin logger: plugin info thickbox title", "simple-history");
1187
-
1188
  if ( is_multisite() ) {
1189
  $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" );
1190
  } else {
59
  'simple-history'
60
  ),
61
 
 
 
 
 
 
 
62
  'plugin_deleted' => _x(
63
  'Deleted plugin "{plugin_name}"',
64
  'Plugin files was deleted',
105
  _x("Failed plugin updates", "Plugin logger: search", "simple-history") => array(
106
  'plugin_update_failed'
107
  ),
 
 
 
108
  _x("Deleted plugins", "Plugin logger: search", "simple-history") => array(
109
  'plugin_deleted'
110
  ),
143
  // Check hook extra for upgrader initiator
144
  add_action( 'upgrader_process_complete', array( $this, "on_upgrader_process_complete" ), 10, 2 );
145
 
 
 
 
146
  // Detect files removed
147
  add_action( 'setted_transient', array( $this, 'on_setted_transient_for_remove_files' ), 10, 2 );
148
 
467
 
468
  }
469
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  /**
471
  * Called when plugins is updated or installed
472
  * Called from class-wp-upgrader.php
1114
  if ( "plugin_updated" == $message_key || "plugin_bulk_updated" == $message_key ) {
1115
 
1116
  $link_title = esc_html_x("View changelog", "plugin logger: plugin info thickbox title", "simple-history");
1117
+
1118
  if ( is_multisite() ) {
1119
  $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" );
1120
  } else {
readme.txt CHANGED
@@ -4,7 +4,7 @@ Donate link: http://eskapism.se/sida/donate/
4
  Tags: history, log, changes, changelog, audit, trail, pages, attachments, users, dashboard, admin, syslog, feed, activity, stream, audit trail, brute-force
5
  Requires at least: 4.5.1
6
  Tested up to: 4.7
7
- Stable tag: 2.15
8
 
9
  View changes made by users within WordPress. See who created a page, uploaded an attachment or approved an comment, and more.
10
 
@@ -162,6 +162,13 @@ A simple way to see any uncommon activity, for example an increased number of lo
162
 
163
  ## Changelog
164
 
 
 
 
 
 
 
 
165
  = 2.15 (May 2017) =
166
 
167
  - Use thumbnail version of PDF preview instead of full size image.
4
  Tags: history, log, changes, changelog, audit, trail, pages, attachments, users, dashboard, admin, syslog, feed, activity, stream, audit trail, brute-force
5
  Requires at least: 4.5.1
6
  Tested up to: 4.7
7
+ Stable tag: 2.16
8
 
9
  View changes made by users within WordPress. See who created a page, uploaded an attachment or approved an comment, and more.
10
 
162
 
163
  ## Changelog
164
 
165
+ = 2.16 (May 2017) =
166
+
167
+ - Added [WP-CLI](https://wp-cli.org) command for Simple History. Now you can write `wp simple-history list` to see the latest entries from the history log. For now `list` is the only available command. Let me know if you need more commands!
168
+ - Added support for logging edits to theme files and plugin files. When a file is edited you will also get a quick diff on the changes,
169
+ so you can see what CSS styles a client changed or what PHP changes they made in a plugin file.
170
+ - Removed the edit file logger from the plugin logger, because it did not always work (checked wrong wp path). Intead the new Theme and plugins logger mentioned above will take care of this.
171
+
172
  = 2.15 (May 2017) =
173
 
174
  - Use thumbnail version of PDF preview instead of full size image.