Simple History - Version 2.23

Version Description

(May 2018) =

  • Add logging of privacy and GDPR related functions in WordPress. Some of the new privacy related features in WordPress 4.9.6 that are logged:
    • Privacy policy page is created or changed to a new page.
    • Privacy data export is requested for a user and when this request is confirmed by the user and when the data for the request is downloaded by an admin or emailed to the user.
    • Erase Personal Data: Request is added for user to have their personal data erased, user confirms the data removal and when the deletion of user data is done.
  • Fix error when categories changes was shown in the log. Fixes https://wordpress.org/support/topic/php-notice-undefined-variable-term_object/.
  • Fix error when a ACF Field Group was saved.
  • Fix error when the IP address anonymization function tried to anonymize an empty IP adress. Could happen when for example running wp cron locally on your server.
  • Fix error when calling the REST API with an API endpoint with a closure as the callback. Fixes https://github.com/bonny/WordPress-Simple-History/issues/141.
  • Rewrote logger loading method so now it's possible to name your loggers in a WordPress codings standard compatible way. Also: made a bit more code more WordPress-ish.
  • The post types in the skip_posttypes filter are now also applied to deleted posts.
  • Add function sh_get_callable_name() that returns a human readable name for a callback.
Download this release

Release Info

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

Code changes from version 2.22.1 to 2.23

inc/SimpleHistory.php CHANGED
@@ -166,36 +166,39 @@ class SimpleHistory {
166
  // Add some extra info to each logged context when SIMPLE_HISTORY_LOG_DEBUG is set and true
167
  if ( defined( 'SIMPLE_HISTORY_LOG_DEBUG' ) && SIMPLE_HISTORY_LOG_DEBUG ) {
168
 
169
- add_filter( 'simple_history/log_argument/context', function( $context, $level, $message, $logger ) {
 
170
 
171
- $sh = SimpleHistory::get_instance();
172
- $context['_debug_get'] = $sh->json_encode( $_GET );
173
- $context['_debug_post'] = $sh->json_encode( $_POST );
174
- $context['_debug_server'] = $sh->json_encode( $_SERVER );
175
- $context['_debug_files'] = $sh->json_encode( $_FILES );
176
- $context['_debug_php_sapi_name'] = php_sapi_name();
177
 
178
- global $argv;
179
- $context['_debug_argv'] = $sh->json_encode( $argv );
180
 
181
- $consts = get_defined_constants( true );
182
- $consts = $consts['user'];
183
- $context['_debug_user_constants'] = $sh->json_encode( $consts );
184
 
185
- $postdata = file_get_contents( 'php://input' );
186
- $context['_debug_http_raw_post_data'] = $sh->json_encode( $postdata );
187
 
188
- $context['_debug_wp_debug_backtrace_summary'] = wp_debug_backtrace_summary();
189
- $context['_debug_is_admin'] = json_encode( is_admin() );
190
- $context['_debug_is_doing_cron'] = json_encode( defined( 'DOING_CRON' ) && DOING_CRON );
 
191
 
192
- global $wp_current_filter;
193
- $context['_debug_current_filter_array'] = $wp_current_filter;
194
- $context['_debug_current_filter'] = current_filter();
195
 
196
- return $context;
197
 
198
- }, 10, 4 );
 
199
 
200
  }
201
 
@@ -530,11 +533,13 @@ class SimpleHistory {
530
  public function testlog_old() {
531
 
532
  // Log that an email has been sent
533
- simple_history_add( array(
534
- 'object_type' => 'Email',
535
- 'object_name' => 'Hi there',
536
- 'action' => 'was sent',
537
- ) );
 
 
538
 
539
  // Will show “Plugin your_plugin_name Edited” in the history log
540
  simple_history_add( 'action=edited&object_type=plugin&object_name=your_plugin_name' );
@@ -543,12 +548,14 @@ class SimpleHistory {
543
  simple_history_add( 'action=repaired&object_type=Starship&object_name=USS Enterprise' );
544
 
545
  // Log with some extra details about the email
546
- simple_history_add( array(
547
- 'object_type' => 'Email',
548
- 'object_name' => 'Hi there',
549
- 'action' => 'was sent',
550
- 'description' => 'The database query to generate the email took .3 seconds. This is email number 4 that is sent to this user',
551
- ) );
 
 
552
 
553
  }
554
 
@@ -583,8 +590,8 @@ class SimpleHistory {
583
  <script type="text/html" id="tmpl-simple-history-base">
584
 
585
  <div class="SimpleHistory__waitingForFirstLoad">
586
- <img src="<?php echo admin_url( '/images/spinner.gif' );?>" alt="" width="20" height="20">
587
- <?php echo _x( 'Loading history...', 'Message visible while waiting for log to load from server the first time', 'simple-history' )?>
588
  </div>
589
 
590
  <div class="SimpleHistoryLogitemsWrap">
@@ -621,7 +628,7 @@ class SimpleHistory {
621
  href="#">‹</a>
622
  <span class="SimpleHistoryPaginationInput">
623
  <input class="SimpleHistoryPaginationCurrentPage" title="{{ data.strings.currentPage }}" type="text" name="paged" value="{{ data.api_args.paged }}" size="4">
624
- <?php _x( 'of', 'page n of n', 'simple-history' )?>
625
  <span class="total-pages">{{ data.pages_count }}</span>
626
  </span>
627
  <a
@@ -645,7 +652,7 @@ class SimpleHistory {
645
  <div class="SimpleHistory-modal__background"></div>
646
  <div class="SimpleHistory-modal__content">
647
  <div class="SimpleHistory-modal__contentInner">
648
- <img class="SimpleHistory-modal__contentSpinner" src="<?php echo admin_url( '/images/spinner.gif' );?>" alt="">
649
  </div>
650
  <div class="SimpleHistory-modal__contentClose">
651
  <button class="button">✕</button>
@@ -713,17 +720,21 @@ class SimpleHistory {
713
 
714
  if ( empty( $args ) || ! $type ) {
715
 
716
- wp_send_json_error( array(
717
- _x( 'Not enough args specified', 'API: not enought arguments passed', 'simple-history' ),
718
- ) );
 
 
719
 
720
  }
721
 
722
  // User must have capability to view the history page
723
  if ( ! current_user_can( $this->get_view_history_capability() ) ) {
724
- wp_send_json_error( array(
725
- 'error' => 'CAPABILITY_ERROR',
726
- ) );
 
 
727
  }
728
 
729
  if ( isset( $args['id'] ) ) {
@@ -739,7 +750,6 @@ class SimpleHistory {
739
  case 'overview':
740
  case 'occasions':
741
  case 'single':
742
-
743
  // API use SimpleHistoryLogQuery, so simply pass args on to that
744
  $logQuery = new SimpleHistoryLogQuery();
745
  $data = $logQuery->query( $args );
@@ -886,6 +896,7 @@ class SimpleHistory {
886
 
887
  /**
888
  * Check if the current user can clear the log
 
889
  * @since 2.19
890
  * @return bool
891
  */
@@ -970,6 +981,7 @@ class SimpleHistory {
970
  $loggersDir = SIMPLE_HISTORY_PATH . 'loggers/';
971
 
972
  $loggersFiles = array(
 
973
  $loggersDir . 'SimpleCommentsLogger.php',
974
  $loggersDir . 'SimpleCoreUpdatesLogger.php',
975
  $loggersDir . 'SimpleExportLogger.php',
@@ -985,19 +997,20 @@ class SimpleHistory {
985
  $loggersDir . 'SimpleCategoriesLogger.php',
986
  $loggersDir . 'AvailableUpdatesLogger.php',
987
  $loggersDir . 'FileEditsLogger.php',
 
 
 
 
 
 
 
 
 
 
 
988
 
989
- // Loggers for third party plugins
990
- $loggersDir . "PluginUserSwitchingLogger.php",
991
- $loggersDir . "PluginEnableMediaReplaceLogger.php",
992
- $loggersDir . "Plugin_UltimateMembers_Logger.php",
993
- $loggersDir . "Plugin_LimitLoginAttempts.php",
994
- $loggersDir . "Plugin_Redirection.php",
995
- $loggersDir . "Plugin_DuplicatePost.php",
996
- $loggersDir . "Plugin_ACF.php"
997
- );
998
-
999
- // SimpleLogger.php must be loaded first and always since the other loggers extend it
1000
- // Include it manually so risk of anyone using filters or similar disables it
1001
  include_once $loggersDir . 'SimpleLogger.php';
1002
 
1003
  /**
@@ -1012,15 +1025,18 @@ class SimpleHistory {
1012
  */
1013
  $loggersFiles = apply_filters( 'simple_history/loggers_files', $loggersFiles );
1014
 
1015
- // Array with slug of loggers to instantiate
1016
- // Slug of logger must also be the name of the logger class
1017
- $arrLoggersToInstantiate = array();
1018
 
1019
- foreach ( $loggersFiles as $oneLoggerFile ) {
 
1020
 
1021
  $load_logger = true;
1022
 
1023
- $basename_no_suffix = basename( $oneLoggerFile, '.php' );
 
 
1024
 
1025
  /**
1026
  * Filter to completely skip loading of a logger
@@ -1028,7 +1044,7 @@ class SimpleHistory {
1028
  * @since 2.0.22
1029
  *
1030
  * @param bool if to load the logger. return false to not load it.
1031
- * @param string slug of logger
1032
  */
1033
  $load_logger = apply_filters( 'simple_history/logger/load_logger', $load_logger, $basename_no_suffix );
1034
 
@@ -1036,9 +1052,9 @@ class SimpleHistory {
1036
  continue;
1037
  }
1038
 
1039
- include_once $oneLoggerFile;
1040
 
1041
- $arrLoggersToInstantiate[] = $basename_no_suffix;
1042
 
1043
  }
1044
 
@@ -1052,99 +1068,86 @@ class SimpleHistory {
1052
  */
1053
  do_action( 'simple_history/add_custom_logger', $this );
1054
 
1055
- $arrLoggersToInstantiate = array_merge( $arrLoggersToInstantiate, $this->externalLoggers );
1056
 
1057
  /**
1058
  * Filter the array with names of loggers to instantiate.
1059
  *
1060
  * Array
1061
  * (
1062
- * [0] => SimpleCommentsLogger
1063
- * [1] => SimpleCoreUpdatesLogger
1064
- * ...
1065
  * )
1066
  *
1067
  * @since 2.0
1068
  *
1069
- * @param array $arrLoggersToInstantiate Array with class names
1070
  */
1071
- $arrLoggersToInstantiate = apply_filters( 'simple_history/loggers_to_instantiate', $arrLoggersToInstantiate );
 
 
 
 
 
 
1072
 
1073
- // Instantiate each logger
1074
- foreach ( $arrLoggersToInstantiate as $oneLoggerName ) {
 
 
 
 
 
 
 
 
 
 
 
1075
 
1076
- if ( ! class_exists( $oneLoggerName ) ) {
 
1077
  continue;
1078
  }
1079
 
1080
- $loggerInstance = new $oneLoggerName( $this );
1081
- if ( ! is_subclass_of( $loggerInstance, 'SimpleLogger' ) && ! is_a( $loggerInstance, 'SimpleLogger' ) ) {
 
 
1082
  continue;
1083
  }
1084
 
1085
- $loggerInstance->loaded();
1086
 
1087
- // Tell gettext-filter to add untranslated messages
1088
  $this->doFilterGettext = true;
1089
- $this->doFilterGettext_currentLogger = $loggerInstance;
1090
 
1091
- $loggerInfo = $loggerInstance->getInfo();
1092
 
1093
  // Check so no logger has a logger slug with more than 30 chars,
1094
  // because db column is only 30 chars.
1095
- if ( strlen( $loggerInstance->slug ) > 30 ) {
1096
  add_action( 'admin_notices', array( $this, 'admin_notice_logger_slug_to_long' ) );
1097
  }
1098
 
1099
- /*
1100
- $loggerInfo["messages"]
1101
- [messages] => Array
1102
- (
1103
- [anon_comment_added] => Lade till en kommentar till {comment_post_type} "{comment_post_title}"
1104
- [user_comment_added] => Lade till en kommentar till {comment_post_type} "{comment_post_title}"
1105
- [comment_status_approve] => Godkände en kommentar till "{comment_post_title}" av {comment_author} ({comment_author_email})
1106
- [comment_status_hold] => Godkände inte en kommentar till "{comment_post_title}" av {comment_author} ({comment_author_email})
1107
- */
1108
-
1109
- /*
1110
- $loggerInstance->messages
1111
- Array
1112
- (
1113
- [0] => Array
1114
- (
1115
- [untranslated_text] => Added a comment to {comment_post_type} "{comment_post_title}"
1116
- [translated_text] => Lade till en kommentar till {comment_post_type} "{comment_post_title}"
1117
- [domain] => simple-history
1118
- [context] => A comment was added to the database by a non-logged in internet user
1119
- )
1120
- */
1121
-
1122
- // Un-tell gettext filter
1123
  $this->doFilterGettext = false;
1124
  $this->doFilterGettext_currentLogger = null;
1125
 
1126
  // LoggerInfo contains all messages, both translated an not, by key.
1127
- // Add messages to the loggerInstance
1128
  $loopNum = 0;
1129
 
1130
  $arr_messages_by_message_key = array();
1131
 
1132
- if ( isset( $loggerInfo['messages'] ) ) {
1133
-
1134
- foreach ( (array) $loggerInfo['messages'] as $message_key => $message_translated ) {
1135
 
1136
- // Find message in array with both translated and non translated strings
1137
- foreach ( $loggerInstance->messages as $one_message_with_translation_info ) {
1138
 
1139
- /*
1140
- [0] => Array
1141
- (
1142
- [untranslated_text] => ...
1143
- [translated_text] => ...
1144
- [domain] => simple-history
1145
- [context] => ...
1146
- )
1147
- */
1148
  if ( $message_translated == $one_message_with_translation_info['translated_text'] ) {
1149
  $arr_messages_by_message_key[ $message_key ] = $one_message_with_translation_info;
1150
  continue;
@@ -1153,12 +1156,12 @@ class SimpleHistory {
1153
  }
1154
  }
1155
 
1156
- $loggerInstance->messages = $arr_messages_by_message_key;
1157
 
1158
- // Add logger to array of loggers
1159
- $this->instantiatedLoggers[ $loggerInstance->slug ] = array(
1160
- 'name' => $loggerInfo['name'],
1161
- 'instance' => $loggerInstance,
1162
  );
1163
 
1164
  }// End foreach().
@@ -1340,10 +1343,10 @@ class SimpleHistory {
1340
 
1341
  $settings_page_url = menu_page_url( SimpleHistory::SETTINGS_MENU_SLUG, 0 );
1342
 
1343
- if(empty($actions)){ // 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)
1344
  $actions = array();
1345
- }elseif(is_string($actions)){ // Convert the string (which it might've been retrieved as) to an array for future use as an array
1346
- $actions = array($actions);
1347
  }
1348
  $actions[] = "<a href='$settings_page_url'>" . __( 'Settings', 'simple-history' ) . '</a>';
1349
 
@@ -1395,7 +1398,7 @@ class SimpleHistory {
1395
 
1396
  ?>
1397
  <div class="SimpleHistoryGui"
1398
- data-pager-size='<?php echo $pager_size?>'
1399
  ></div>
1400
  <?php
1401
 
@@ -1444,19 +1447,21 @@ class SimpleHistory {
1444
  wp_enqueue_style( 'select2', SIMPLE_HISTORY_DIR_URL . 'js/select2/select2.min.css' );
1445
 
1446
  // Translations that we use in JavaScript
1447
- wp_localize_script( 'simple_history_script', 'simple_history_script_vars', array(
1448
- 'settingsConfirmClearLog' => __( 'Remove all log items?', 'simple-history' ),
1449
- 'pagination' => array(
1450
- 'goToTheFirstPage' => __( 'Go to the first page', 'simple-history' ),
1451
- 'goToThePrevPage' => __( 'Go to the previous page', 'simple-history' ),
1452
- 'goToTheNextPage' => __( 'Go to the next page', 'simple-history' ),
1453
- 'goToTheLastPage' => __( 'Go to the last page', 'simple-history' ),
1454
- 'currentPage' => __( 'Current page', 'simple-history' ),
1455
- ),
1456
- 'loadLogAPIError' => __( 'Oups, the log could not be loaded right now.', 'simple-history' ),
1457
- 'ajaxLoadError' => __( '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.', 'simple-history' ),
1458
- 'logNoHits' => __( 'Your search did not match any history events.', 'simple-history' ),
1459
- ) );
 
 
1460
 
1461
  // Call plugins adminCSS-method, so they can add their CSS
1462
  foreach ( $this->instantiatedLoggers as $one_logger ) {
@@ -1530,10 +1535,10 @@ class SimpleHistory {
1530
  // Table creation, used to be in register_activation_hook
1531
  // We change the varchar size to add one num just to force update of encoding. dbdelta didn't see it otherwise.
1532
  $sql = 'CREATE TABLE ' . $table_name . ' (
1533
- id bigint(20) NOT NULL AUTO_INCREMENT,
1534
- date datetime NOT NULL,
1535
- PRIMARY KEY (id)
1536
- ) CHARACTER SET=utf8;';
1537
 
1538
  // Upgrade db / fix utf for varchars
1539
  dbDelta( $sql );
@@ -1637,7 +1642,8 @@ class SimpleHistory {
1637
  update_option( 'simple_history_db_version', $db_version );
1638
 
1639
  // Update possible old items to use SimpleLegacyLogger
1640
- $sql = sprintf( '
 
1641
  UPDATE %1$s
1642
  SET
1643
  logger = "SimpleLegacyLogger",
@@ -1673,7 +1679,8 @@ class SimpleHistory {
1673
 
1674
  if ( in_array( 'action', $db_cools ) ) {
1675
 
1676
- $sql = sprintf( '
 
1677
  ALTER TABLE %1$s
1678
  MODIFY `action` varchar(255) NULL,
1679
  MODIFY `object_type` varchar(255) NULL,
@@ -1824,7 +1831,7 @@ Because Simple History was just recently installed, this feed does not contain m
1824
 
1825
  <h1 class="SimpleHistoryPageHeadline">
1826
  <div class="dashicons dashicons-backup SimpleHistoryPageHeadline__icon"></div>
1827
- <?php _e( 'Simple History Settings', 'simple-history' )?>
1828
  </h1>
1829
 
1830
  <?php
@@ -1853,9 +1860,11 @@ Because Simple History was just recently installed, this feed does not contain m
1853
  <?php
1854
 
1855
  // Output contents for selected tab
1856
- $arr_active_tab = wp_filter_object_list( $arr_settings_tabs, array(
1857
- 'slug' => $active_tab,
1858
- ) );
 
 
1859
  $arr_active_tab = current( $arr_active_tab );
1860
 
1861
  // We must have found an active tab and it must have a callable function
@@ -2065,7 +2074,7 @@ Because Simple History was just recently installed, this feed does not contain m
2065
 
2066
  <h1 class="SimpleHistoryPageHeadline">
2067
  <div class="dashicons dashicons-backup SimpleHistoryPageHeadline__icon"></div>
2068
- <?php echo _x( 'Simple History', 'history page headline', 'simple-history' )?>
2069
  </h1>
2070
 
2071
  <?php
@@ -2082,7 +2091,7 @@ Because Simple History was just recently installed, this feed does not contain m
2082
  <div class="SimpleHistoryGuiWrap">
2083
 
2084
  <div class="SimpleHistoryGui"
2085
- data-pager-size='<?php echo $pager_size?>'
2086
  ></div>
2087
 
2088
  <?php
@@ -2144,16 +2153,16 @@ Because Simple History was just recently installed, this feed does not contain m
2144
 
2145
  ?>
2146
  <select name="simple_history_pager_size">
2147
- <option <?php echo $current_pager_size == 5 ? 'selected' : ''?> value="5">5</option>
2148
- <option <?php echo $current_pager_size == 10 ? 'selected' : ''?> value="10">10</option>
2149
- <option <?php echo $current_pager_size == 15 ? 'selected' : ''?> value="15">15</option>
2150
- <option <?php echo $current_pager_size == 20 ? 'selected' : ''?> value="20">20</option>
2151
- <option <?php echo $current_pager_size == 25 ? 'selected' : ''?> value="25">25</option>
2152
- <option <?php echo $current_pager_size == 30 ? 'selected' : ''?> value="30">30</option>
2153
- <option <?php echo $current_pager_size == 40 ? 'selected' : ''?> value="40">40</option>
2154
- <option <?php echo $current_pager_size == 50 ? 'selected' : ''?> value="50">50</option>
2155
- <option <?php echo $current_pager_size == 75 ? 'selected' : ''?> value="75">75</option>
2156
- <option <?php echo $current_pager_size == 100 ? 'selected' : ''?> value="100">100</option>
2157
  </select>
2158
  <?php
2159
 
@@ -2168,16 +2177,16 @@ Because Simple History was just recently installed, this feed does not contain m
2168
 
2169
  ?>
2170
  <select name="simple_history_pager_size_dashboard">
2171
- <option <?php echo $current_pager_size == 5 ? 'selected' : ''?> value="5">5</option>
2172
- <option <?php echo $current_pager_size == 10 ? 'selected' : ''?> value="10">10</option>
2173
- <option <?php echo $current_pager_size == 15 ? 'selected' : ''?> value="15">15</option>
2174
- <option <?php echo $current_pager_size == 20 ? 'selected' : ''?> value="20">20</option>
2175
- <option <?php echo $current_pager_size == 25 ? 'selected' : ''?> value="25">25</option>
2176
- <option <?php echo $current_pager_size == 30 ? 'selected' : ''?> value="30">30</option>
2177
- <option <?php echo $current_pager_size == 40 ? 'selected' : ''?> value="40">40</option>
2178
- <option <?php echo $current_pager_size == 50 ? 'selected' : ''?> value="50">50</option>
2179
- <option <?php echo $current_pager_size == 75 ? 'selected' : ''?> value="75">75</option>
2180
- <option <?php echo $current_pager_size == 100 ? 'selected' : ''?> value="100">100</option>
2181
  </select>
2182
  <?php
2183
 
@@ -2193,13 +2202,13 @@ Because Simple History was just recently installed, this feed does not contain m
2193
 
2194
  ?>
2195
 
2196
- <input <?php echo $show_on_dashboard ? "checked='checked'" : ''?> type="checkbox" value="1" name="simple_history_show_on_dashboard" id="simple_history_show_on_dashboard" class="simple_history_show_on_dashboard" />
2197
- <label for="simple_history_show_on_dashboard"><?php _e( 'on the dashboard', 'simple-history' )?></label>
2198
 
2199
  <br />
2200
 
2201
- <input <?php echo $show_as_page ? "checked='checked'" : ''?> type="checkbox" value="1" name="simple_history_show_as_page" id="simple_history_show_as_page" class="simple_history_show_as_page" />
2202
- <label for="simple_history_show_as_page"><?php _e( 'as a page under the dashboard menu', 'simple-history' )?></label>
2203
 
2204
  <?php
2205
  }
@@ -2888,7 +2897,7 @@ Because Simple History was just recently installed, this feed does not contain m
2888
  * @param string $slug
2889
  * @return mixed logger instance if found, bool false if logger not found
2890
  */
2891
- public function getInstantiatedLoggerBySlug( $slug = "" ) {
2892
 
2893
  if ( empty( $slug ) ) {
2894
  return false;
@@ -3101,10 +3110,12 @@ Because Simple History was just recently installed, this feed does not contain m
3101
 
3102
  // Get number of events today
3103
  $logQuery = new SimpleHistoryLogQuery();
3104
- $logResults = $logQuery->query( array(
3105
- 'posts_per_page' => 1,
3106
- 'date_from' => strtotime( 'today' ),
3107
- ) );
 
 
3108
 
3109
  $total_row_count = (int) $logResults['total_row_count'];
3110
 
@@ -3112,7 +3123,8 @@ Because Simple History was just recently installed, this feed does not contain m
3112
  $sql_loggers_in = $this->getLoggersThatUserCanRead( get_current_user_id(), 'sql' );
3113
 
3114
  // Get number of users today, i.e. events with wp_user as initiator
3115
- $sql_users_today = sprintf( '
 
3116
  SELECT
3117
  DISTINCT(c.value) AS user_id
3118
  #h.id, h.logger, h.level, h.initiator, h.date
@@ -3156,7 +3168,8 @@ Because Simple History was just recently installed, this feed does not contain m
3156
 
3157
  $sql_other_sources_where = apply_filters( 'simple_history/quick_stats_where', $sql_other_sources_where );
3158
 
3159
- $sql_other_sources = sprintf( '
 
3160
  SELECT
3161
  DISTINCT(h.initiator) AS initiator
3162
  FROM %3$s AS h
@@ -3405,11 +3418,13 @@ Because Simple History was just recently installed, this feed does not contain m
3405
 
3406
  if ( false == $numEvents ) {
3407
 
3408
- $sql = $wpdb->prepare("
 
3409
  SELECT count( DISTINCT occasionsID )
3410
  FROM $table_name
3411
  WHERE date >= DATE_ADD(CURDATE(), INTERVAL -%d DAY)
3412
- ", $days);
 
3413
 
3414
  $numEvents = $wpdb->get_var( $sql );
3415
 
@@ -3490,11 +3505,11 @@ function simple_history_add( $args ) {
3490
  * The arguments supported and can be changed are listed below.
3491
  *
3492
  * 'title' : Default is an empty string. Titles the diff in a manner compatible
3493
- * with the output.
3494
  * 'title_left' : Default is an empty string. Change the HTML to the left of the
3495
- * title.
3496
  * 'title_right' : Default is an empty string. Change the HTML to the right of
3497
- * the title.
3498
  *
3499
  * @see wp_parse_args() Used to change defaults to user defined settings.
3500
  * @uses Text_Diff
@@ -3601,3 +3616,35 @@ function sh_error_log() {
3601
  }
3602
  }
3603
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  // Add some extra info to each logged context when SIMPLE_HISTORY_LOG_DEBUG is set and true
167
  if ( defined( 'SIMPLE_HISTORY_LOG_DEBUG' ) && SIMPLE_HISTORY_LOG_DEBUG ) {
168
 
169
+ add_filter(
170
+ 'simple_history/log_argument/context', function( $context, $level, $message, $logger ) {
171
 
172
+ $sh = SimpleHistory::get_instance();
173
+ $context['_debug_get'] = $sh->json_encode( $_GET );
174
+ $context['_debug_post'] = $sh->json_encode( $_POST );
175
+ $context['_debug_server'] = $sh->json_encode( $_SERVER );
176
+ $context['_debug_files'] = $sh->json_encode( $_FILES );
177
+ $context['_debug_php_sapi_name'] = php_sapi_name();
178
 
179
+ global $argv;
180
+ $context['_debug_argv'] = $sh->json_encode( $argv );
181
 
182
+ $consts = get_defined_constants( true );
183
+ $consts = $consts['user'];
184
+ $context['_debug_user_constants'] = $sh->json_encode( $consts );
185
 
186
+ $postdata = file_get_contents( 'php://input' );
187
+ $context['_debug_http_raw_post_data'] = $sh->json_encode( $postdata );
188
 
189
+ $context['_debug_wp_debug_backtrace_summary'] = wp_debug_backtrace_summary();
190
+ $context['_debug_is_admin'] = json_encode( is_admin() );
191
+ $context['_debug_is_ajax'] = json_encode( defined( 'DOING_AJAX' ) && DOING_AJAX );
192
+ $context['_debug_is_doing_cron'] = json_encode( defined( 'DOING_CRON' ) && DOING_CRON );
193
 
194
+ global $wp_current_filter;
195
+ $context['_debug_current_filter_array'] = $wp_current_filter;
196
+ $context['_debug_current_filter'] = current_filter();
197
 
198
+ return $context;
199
 
200
+ }, 10, 4
201
+ );
202
 
203
  }
204
 
533
  public function testlog_old() {
534
 
535
  // Log that an email has been sent
536
+ simple_history_add(
537
+ array(
538
+ 'object_type' => 'Email',
539
+ 'object_name' => 'Hi there',
540
+ 'action' => 'was sent',
541
+ )
542
+ );
543
 
544
  // Will show “Plugin your_plugin_name Edited” in the history log
545
  simple_history_add( 'action=edited&object_type=plugin&object_name=your_plugin_name' );
548
  simple_history_add( 'action=repaired&object_type=Starship&object_name=USS Enterprise' );
549
 
550
  // Log with some extra details about the email
551
+ simple_history_add(
552
+ array(
553
+ 'object_type' => 'Email',
554
+ 'object_name' => 'Hi there',
555
+ 'action' => 'was sent',
556
+ 'description' => 'The database query to generate the email took .3 seconds. This is email number 4 that is sent to this user',
557
+ )
558
+ );
559
 
560
  }
561
 
590
  <script type="text/html" id="tmpl-simple-history-base">
591
 
592
  <div class="SimpleHistory__waitingForFirstLoad">
593
+ <img src="<?php echo admin_url( '/images/spinner.gif' ); ?>" alt="" width="20" height="20">
594
+ <?php echo _x( 'Loading history...', 'Message visible while waiting for log to load from server the first time', 'simple-history' ); ?>
595
  </div>
596
 
597
  <div class="SimpleHistoryLogitemsWrap">
628
  href="#">‹</a>
629
  <span class="SimpleHistoryPaginationInput">
630
  <input class="SimpleHistoryPaginationCurrentPage" title="{{ data.strings.currentPage }}" type="text" name="paged" value="{{ data.api_args.paged }}" size="4">
631
+ <?php _x( 'of', 'page n of n', 'simple-history' ); ?>
632
  <span class="total-pages">{{ data.pages_count }}</span>
633
  </span>
634
  <a
652
  <div class="SimpleHistory-modal__background"></div>
653
  <div class="SimpleHistory-modal__content">
654
  <div class="SimpleHistory-modal__contentInner">
655
+ <img class="SimpleHistory-modal__contentSpinner" src="<?php echo admin_url( '/images/spinner.gif' ); ?>" alt="">
656
  </div>
657
  <div class="SimpleHistory-modal__contentClose">
658
  <button class="button">✕</button>
720
 
721
  if ( empty( $args ) || ! $type ) {
722
 
723
+ wp_send_json_error(
724
+ array(
725
+ _x( 'Not enough args specified', 'API: not enought arguments passed', 'simple-history' ),
726
+ )
727
+ );
728
 
729
  }
730
 
731
  // User must have capability to view the history page
732
  if ( ! current_user_can( $this->get_view_history_capability() ) ) {
733
+ wp_send_json_error(
734
+ array(
735
+ 'error' => 'CAPABILITY_ERROR',
736
+ )
737
+ );
738
  }
739
 
740
  if ( isset( $args['id'] ) ) {
750
  case 'overview':
751
  case 'occasions':
752
  case 'single':
 
753
  // API use SimpleHistoryLogQuery, so simply pass args on to that
754
  $logQuery = new SimpleHistoryLogQuery();
755
  $data = $logQuery->query( $args );
896
 
897
  /**
898
  * Check if the current user can clear the log
899
+ *
900
  * @since 2.19
901
  * @return bool
902
  */
981
  $loggersDir = SIMPLE_HISTORY_PATH . 'loggers/';
982
 
983
  $loggersFiles = array(
984
+ // Main loggers.
985
  $loggersDir . 'SimpleCommentsLogger.php',
986
  $loggersDir . 'SimpleCoreUpdatesLogger.php',
987
  $loggersDir . 'SimpleExportLogger.php',
997
  $loggersDir . 'SimpleCategoriesLogger.php',
998
  $loggersDir . 'AvailableUpdatesLogger.php',
999
  $loggersDir . 'FileEditsLogger.php',
1000
+ $loggersDir . 'class-sh-privacy-logger.php',
1001
+
1002
+ // Loggers for third party plugins.
1003
+ $loggersDir . 'PluginUserSwitchingLogger.php',
1004
+ $loggersDir . 'PluginEnableMediaReplaceLogger.php',
1005
+ $loggersDir . 'Plugin_UltimateMembers_Logger.php',
1006
+ $loggersDir . 'Plugin_LimitLoginAttempts.php',
1007
+ $loggersDir . 'Plugin_Redirection.php',
1008
+ $loggersDir . 'Plugin_DuplicatePost.php',
1009
+ $loggersDir . 'Plugin_ACF.php',
1010
+ );
1011
 
1012
+ // SimpleLogger.php must be loaded first and always since the other loggers extend it.
1013
+ // Include it manually so risk of anyone using filters or similar disables it.
 
 
 
 
 
 
 
 
 
 
1014
  include_once $loggersDir . 'SimpleLogger.php';
1015
 
1016
  /**
1025
  */
1026
  $loggersFiles = apply_filters( 'simple_history/loggers_files', $loggersFiles );
1027
 
1028
+ // Array with slug of loggers to instantiate.
1029
+ // Slug of logger must also be the name of the logger class.
1030
+ $arr_loggers_to_instantiate = array();
1031
 
1032
+ // $one_logger_file = "SimpleCommentsLogger.php", "class-privacy-logger.php", and so on.
1033
+ foreach ( $loggersFiles as $one_logger_file ) {
1034
 
1035
  $load_logger = true;
1036
 
1037
+ // SimpleCommentsLogger.php -> SimpleCommentsLogger.
1038
+ // class-privacy-logger.php -> class-privacy-logger.
1039
+ $basename_no_suffix = basename( $one_logger_file, '.php' );
1040
 
1041
  /**
1042
  * Filter to completely skip loading of a logger
1044
  * @since 2.0.22
1045
  *
1046
  * @param bool if to load the logger. return false to not load it.
1047
+ * @param string basename of logger, i.e. "SimpleCommentsLogger" or "class-privacy-logger"
1048
  */
1049
  $load_logger = apply_filters( 'simple_history/logger/load_logger', $load_logger, $basename_no_suffix );
1050
 
1052
  continue;
1053
  }
1054
 
1055
+ include_once $one_logger_file;
1056
 
1057
+ $arr_loggers_to_instantiate[] = $basename_no_suffix;
1058
 
1059
  }
1060
 
1068
  */
1069
  do_action( 'simple_history/add_custom_logger', $this );
1070
 
1071
+ $arr_loggers_to_instantiate = array_merge( $arr_loggers_to_instantiate, $this->externalLoggers );
1072
 
1073
  /**
1074
  * Filter the array with names of loggers to instantiate.
1075
  *
1076
  * Array
1077
  * (
1078
+ * [0] => SimpleCommentsLogger
1079
+ * [1] => SimpleCoreUpdatesLogger
1080
+ * ...
1081
  * )
1082
  *
1083
  * @since 2.0
1084
  *
1085
+ * @param array $arr_loggers_to_instantiate Array with class names
1086
  */
1087
+ $arr_loggers_to_instantiate = apply_filters( 'simple_history/loggers_to_instantiate', $arr_loggers_to_instantiate );
1088
+
1089
+ // Instantiate each logger.
1090
+ foreach ( $arr_loggers_to_instantiate as $one_logger_name ) {
1091
+
1092
+ // Detect logger class name.
1093
+ $logger_class_name = null;
1094
 
1095
+ if ( class_exists( $one_logger_name ) ) {
1096
+ // Logger name is "SimpleCommentsLogger".
1097
+ $logger_class_name = $one_logger_name;
1098
+ } else {
1099
+ // Check if class is "class-privacy-logger".
1100
+ $logger_snaked_name = substr( $one_logger_name, 6 );
1101
+ // "privacy-logger" -> "privacy_logger" -> Privacy_Logger
1102
+ $logger_snaked_name = str_replace( '-', '_', $logger_snaked_name );
1103
+ $logger_snaked_name = ucwords( $logger_snaked_name, '_' );
1104
+ if ( class_exists( $logger_snaked_name ) ) {
1105
+ $logger_class_name = $logger_snaked_name;
1106
+ }
1107
+ }
1108
 
1109
+ // Continue to load next logger if no valid logger class found.
1110
+ if ( ! $logger_class_name ) {
1111
  continue;
1112
  }
1113
 
1114
+ // Init found logger class.
1115
+ $logger_instance = new $logger_class_name( $this );
1116
+
1117
+ if ( ! is_subclass_of( $logger_instance, 'SimpleLogger' ) && ! is_a( $logger_instance, 'SimpleLogger' ) ) {
1118
  continue;
1119
  }
1120
 
1121
+ $logger_instance->loaded();
1122
 
1123
+ // Tell gettext-filter to add untranslated messages.
1124
  $this->doFilterGettext = true;
1125
+ $this->doFilterGettext_currentLogger = $logger_instance;
1126
 
1127
+ $logger_info = $logger_instance->getInfo();
1128
 
1129
  // Check so no logger has a logger slug with more than 30 chars,
1130
  // because db column is only 30 chars.
1131
+ if ( strlen( $logger_instance->slug ) > 30 ) {
1132
  add_action( 'admin_notices', array( $this, 'admin_notice_logger_slug_to_long' ) );
1133
  }
1134
 
1135
+ // Un-tell gettext filter.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1136
  $this->doFilterGettext = false;
1137
  $this->doFilterGettext_currentLogger = null;
1138
 
1139
  // LoggerInfo contains all messages, both translated an not, by key.
1140
+ // Add messages to the loggerInstance.
1141
  $loopNum = 0;
1142
 
1143
  $arr_messages_by_message_key = array();
1144
 
1145
+ if ( isset( $logger_info['messages'] ) ) {
 
 
1146
 
1147
+ foreach ( (array) $logger_info['messages'] as $message_key => $message_translated ) {
 
1148
 
1149
+ // Find message in array with both translated and non translated strings.
1150
+ foreach ( $logger_instance->messages as $one_message_with_translation_info ) {
 
 
 
 
 
 
 
1151
  if ( $message_translated == $one_message_with_translation_info['translated_text'] ) {
1152
  $arr_messages_by_message_key[ $message_key ] = $one_message_with_translation_info;
1153
  continue;
1156
  }
1157
  }
1158
 
1159
+ $logger_instance->messages = $arr_messages_by_message_key;
1160
 
1161
+ // Add logger to array of loggers.
1162
+ $this->instantiatedLoggers[ $logger_instance->slug ] = array(
1163
+ 'name' => $logger_info['name'],
1164
+ 'instance' => $logger_instance,
1165
  );
1166
 
1167
  }// End foreach().
1343
 
1344
  $settings_page_url = menu_page_url( SimpleHistory::SETTINGS_MENU_SLUG, 0 );
1345
 
1346
+ if ( empty( $actions ) ) { // 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)
1347
  $actions = array();
1348
+ } elseif ( is_string( $actions ) ) { // Convert the string (which it might've been retrieved as) to an array for future use as an array
1349
+ $actions = array( $actions );
1350
  }
1351
  $actions[] = "<a href='$settings_page_url'>" . __( 'Settings', 'simple-history' ) . '</a>';
1352
 
1398
 
1399
  ?>
1400
  <div class="SimpleHistoryGui"
1401
+ data-pager-size='<?php echo $pager_size; ?>'
1402
  ></div>
1403
  <?php
1404
 
1447
  wp_enqueue_style( 'select2', SIMPLE_HISTORY_DIR_URL . 'js/select2/select2.min.css' );
1448
 
1449
  // Translations that we use in JavaScript
1450
+ wp_localize_script(
1451
+ 'simple_history_script', 'simple_history_script_vars', array(
1452
+ 'settingsConfirmClearLog' => __( 'Remove all log items?', 'simple-history' ),
1453
+ 'pagination' => array(
1454
+ 'goToTheFirstPage' => __( 'Go to the first page', 'simple-history' ),
1455
+ 'goToThePrevPage' => __( 'Go to the previous page', 'simple-history' ),
1456
+ 'goToTheNextPage' => __( 'Go to the next page', 'simple-history' ),
1457
+ 'goToTheLastPage' => __( 'Go to the last page', 'simple-history' ),
1458
+ 'currentPage' => __( 'Current page', 'simple-history' ),
1459
+ ),
1460
+ 'loadLogAPIError' => __( 'Oups, the log could not be loaded right now.', 'simple-history' ),
1461
+ 'ajaxLoadError' => __( '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.', 'simple-history' ),
1462
+ 'logNoHits' => __( 'Your search did not match any history events.', 'simple-history' ),
1463
+ )
1464
+ );
1465
 
1466
  // Call plugins adminCSS-method, so they can add their CSS
1467
  foreach ( $this->instantiatedLoggers as $one_logger ) {
1535
  // Table creation, used to be in register_activation_hook
1536
  // We change the varchar size to add one num just to force update of encoding. dbdelta didn't see it otherwise.
1537
  $sql = 'CREATE TABLE ' . $table_name . ' (
1538
+ id bigint(20) NOT NULL AUTO_INCREMENT,
1539
+ date datetime NOT NULL,
1540
+ PRIMARY KEY (id)
1541
+ ) CHARACTER SET=utf8;';
1542
 
1543
  // Upgrade db / fix utf for varchars
1544
  dbDelta( $sql );
1642
  update_option( 'simple_history_db_version', $db_version );
1643
 
1644
  // Update possible old items to use SimpleLegacyLogger
1645
+ $sql = sprintf(
1646
+ '
1647
  UPDATE %1$s
1648
  SET
1649
  logger = "SimpleLegacyLogger",
1679
 
1680
  if ( in_array( 'action', $db_cools ) ) {
1681
 
1682
+ $sql = sprintf(
1683
+ '
1684
  ALTER TABLE %1$s
1685
  MODIFY `action` varchar(255) NULL,
1686
  MODIFY `object_type` varchar(255) NULL,
1831
 
1832
  <h1 class="SimpleHistoryPageHeadline">
1833
  <div class="dashicons dashicons-backup SimpleHistoryPageHeadline__icon"></div>
1834
+ <?php _e( 'Simple History Settings', 'simple-history' ); ?>
1835
  </h1>
1836
 
1837
  <?php
1860
  <?php
1861
 
1862
  // Output contents for selected tab
1863
+ $arr_active_tab = wp_filter_object_list(
1864
+ $arr_settings_tabs, array(
1865
+ 'slug' => $active_tab,
1866
+ )
1867
+ );
1868
  $arr_active_tab = current( $arr_active_tab );
1869
 
1870
  // We must have found an active tab and it must have a callable function
2074
 
2075
  <h1 class="SimpleHistoryPageHeadline">
2076
  <div class="dashicons dashicons-backup SimpleHistoryPageHeadline__icon"></div>
2077
+ <?php echo _x( 'Simple History', 'history page headline', 'simple-history' ); ?>
2078
  </h1>
2079
 
2080
  <?php
2091
  <div class="SimpleHistoryGuiWrap">
2092
 
2093
  <div class="SimpleHistoryGui"
2094
+ data-pager-size='<?php echo $pager_size; ?>'
2095
  ></div>
2096
 
2097
  <?php
2153
 
2154
  ?>
2155
  <select name="simple_history_pager_size">
2156
+ <option <?php echo $current_pager_size == 5 ? 'selected' : ''; ?> value="5">5</option>
2157
+ <option <?php echo $current_pager_size == 10 ? 'selected' : ''; ?> value="10">10</option>
2158
+ <option <?php echo $current_pager_size == 15 ? 'selected' : ''; ?> value="15">15</option>
2159
+ <option <?php echo $current_pager_size == 20 ? 'selected' : ''; ?> value="20">20</option>
2160
+ <option <?php echo $current_pager_size == 25 ? 'selected' : ''; ?> value="25">25</option>
2161
+ <option <?php echo $current_pager_size == 30 ? 'selected' : ''; ?> value="30">30</option>
2162
+ <option <?php echo $current_pager_size == 40 ? 'selected' : ''; ?> value="40">40</option>
2163
+ <option <?php echo $current_pager_size == 50 ? 'selected' : ''; ?> value="50">50</option>
2164
+ <option <?php echo $current_pager_size == 75 ? 'selected' : ''; ?> value="75">75</option>
2165
+ <option <?php echo $current_pager_size == 100 ? 'selected' : ''; ?> value="100">100</option>
2166
  </select>
2167
  <?php
2168
 
2177
 
2178
  ?>
2179
  <select name="simple_history_pager_size_dashboard">
2180
+ <option <?php echo $current_pager_size == 5 ? 'selected' : ''; ?> value="5">5</option>
2181
+ <option <?php echo $current_pager_size == 10 ? 'selected' : ''; ?> value="10">10</option>
2182
+ <option <?php echo $current_pager_size == 15 ? 'selected' : ''; ?> value="15">15</option>
2183
+ <option <?php echo $current_pager_size == 20 ? 'selected' : ''; ?> value="20">20</option>
2184
+ <option <?php echo $current_pager_size == 25 ? 'selected' : ''; ?> value="25">25</option>
2185
+ <option <?php echo $current_pager_size == 30 ? 'selected' : ''; ?> value="30">30</option>
2186
+ <option <?php echo $current_pager_size == 40 ? 'selected' : ''; ?> value="40">40</option>
2187
+ <option <?php echo $current_pager_size == 50 ? 'selected' : ''; ?> value="50">50</option>
2188
+ <option <?php echo $current_pager_size == 75 ? 'selected' : ''; ?> value="75">75</option>
2189
+ <option <?php echo $current_pager_size == 100 ? 'selected' : ''; ?> value="100">100</option>
2190
  </select>
2191
  <?php
2192
 
2202
 
2203
  ?>
2204
 
2205
+ <input <?php echo $show_on_dashboard ? "checked='checked'" : ''; ?> type="checkbox" value="1" name="simple_history_show_on_dashboard" id="simple_history_show_on_dashboard" class="simple_history_show_on_dashboard" />
2206
+ <label for="simple_history_show_on_dashboard"><?php _e( 'on the dashboard', 'simple-history' ); ?></label>
2207
 
2208
  <br />
2209
 
2210
+ <input <?php echo $show_as_page ? "checked='checked'" : ''; ?> type="checkbox" value="1" name="simple_history_show_as_page" id="simple_history_show_as_page" class="simple_history_show_as_page" />
2211
+ <label for="simple_history_show_as_page"><?php _e( 'as a page under the dashboard menu', 'simple-history' ); ?></label>
2212
 
2213
  <?php
2214
  }
2897
  * @param string $slug
2898
  * @return mixed logger instance if found, bool false if logger not found
2899
  */
2900
+ public function getInstantiatedLoggerBySlug( $slug = '' ) {
2901
 
2902
  if ( empty( $slug ) ) {
2903
  return false;
3110
 
3111
  // Get number of events today
3112
  $logQuery = new SimpleHistoryLogQuery();
3113
+ $logResults = $logQuery->query(
3114
+ array(
3115
+ 'posts_per_page' => 1,
3116
+ 'date_from' => strtotime( 'today' ),
3117
+ )
3118
+ );
3119
 
3120
  $total_row_count = (int) $logResults['total_row_count'];
3121
 
3123
  $sql_loggers_in = $this->getLoggersThatUserCanRead( get_current_user_id(), 'sql' );
3124
 
3125
  // Get number of users today, i.e. events with wp_user as initiator
3126
+ $sql_users_today = sprintf(
3127
+ '
3128
  SELECT
3129
  DISTINCT(c.value) AS user_id
3130
  #h.id, h.logger, h.level, h.initiator, h.date
3168
 
3169
  $sql_other_sources_where = apply_filters( 'simple_history/quick_stats_where', $sql_other_sources_where );
3170
 
3171
+ $sql_other_sources = sprintf(
3172
+ '
3173
  SELECT
3174
  DISTINCT(h.initiator) AS initiator
3175
  FROM %3$s AS h
3418
 
3419
  if ( false == $numEvents ) {
3420
 
3421
+ $sql = $wpdb->prepare(
3422
+ "
3423
  SELECT count( DISTINCT occasionsID )
3424
  FROM $table_name
3425
  WHERE date >= DATE_ADD(CURDATE(), INTERVAL -%d DAY)
3426
+ ", $days
3427
+ );
3428
 
3429
  $numEvents = $wpdb->get_var( $sql );
3430
 
3505
  * The arguments supported and can be changed are listed below.
3506
  *
3507
  * 'title' : Default is an empty string. Titles the diff in a manner compatible
3508
+ * with the output.
3509
  * 'title_left' : Default is an empty string. Change the HTML to the left of the
3510
+ * title.
3511
  * 'title_right' : Default is an empty string. Change the HTML to the right of
3512
+ * the title.
3513
  *
3514
  * @see wp_parse_args() Used to change defaults to user defined settings.
3515
  * @uses Text_Diff
3616
  }
3617
  }
3618
 
3619
+ /**
3620
+ * Return a name for a callable.
3621
+ *
3622
+ * Examples of return values:
3623
+ * - WP_REST_Posts_Controller::get_items
3624
+ * - WP_REST_Users_Controller::get_items"
3625
+ * - WP_REST_Server::get_index
3626
+ * - Redirection_Api_Redirect::route_bulk
3627
+ * - wpcf7_rest_create_feedback
3628
+ * - closure
3629
+ *
3630
+ * Function based on code found on stack overflow:
3631
+ * https://stackoverflow.com/questions/34324576/print-name-or-definition-of-callable-in-php
3632
+ *
3633
+ * @param callable $callable The callable thing to check.
3634
+ * @return string Name of callable.
3635
+ */
3636
+ function sh_get_callable_name( $callable ) {
3637
+ if ( is_string( $callable ) ) {
3638
+ return trim( $callable );
3639
+ } else if ( is_array( $callable ) ) {
3640
+ if ( is_object( $callable[0] ) ) {
3641
+ return sprintf( '%s::%s', get_class( $callable[0] ), trim( $callable[1] ) );
3642
+ } else {
3643
+ return sprintf( '%s::%s', trim( $callable[0] ), trim( $callable[1] ) );
3644
+ }
3645
+ } else if ( $callable instanceof Closure ) {
3646
+ return 'closure';
3647
+ } else {
3648
+ return 'unknown';
3649
+ }
3650
+ }
inc/SimpleHistoryIpAnonymizer.php CHANGED
@@ -35,6 +35,11 @@ class SimpleHistoryIpAnonymizer {
35
  * @return string The anonymized IP address. Returns an empty string when the IP address is invalid.
36
  */
37
  public function anonymize($address) {
 
 
 
 
 
38
  $packedAddress = inet_pton($address);
39
 
40
  if (strlen($packedAddress) == 4) {
35
  * @return string The anonymized IP address. Returns an empty string when the IP address is invalid.
36
  */
37
  public function anonymize($address) {
38
+
39
+ if (empty($address)) {
40
+ return "";
41
+ }
42
+
43
  $packedAddress = inet_pton($address);
44
 
45
  if (strlen($packedAddress) == 4) {
index.php CHANGED
@@ -5,7 +5,7 @@
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.22.1
9
  * Author: Pär Thernström
10
  * Author URI: http://simple-history.com/
11
  * License: GPL2
@@ -47,7 +47,7 @@ if ( $ok_php_version && $ok_wp_version ) {
47
  */
48
 
49
  if ( ! defined( 'SIMPLE_HISTORY_VERSION' ) ) {
50
- define( 'SIMPLE_HISTORY_VERSION', '2.22.1' );
51
  }
52
 
53
  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.23
9
  * Author: Pär Thernström
10
  * Author URI: http://simple-history.com/
11
  * License: GPL2
47
  */
48
 
49
  if ( ! defined( 'SIMPLE_HISTORY_VERSION' ) ) {
50
+ define( 'SIMPLE_HISTORY_VERSION', '2.23' );
51
  }
52
 
53
  if ( ! defined( 'SIMPLE_HISTORY_PATH' ) ) {
loggers/Plugin_ACF.php CHANGED
@@ -65,7 +65,7 @@ if ( ! class_exists( 'Plugin_ACF' ) ) {
65
  }
66
 
67
  private function isACFInstalled() {
68
- return defined('ACF') && ACF;
69
  }
70
 
71
  /**
@@ -79,7 +79,7 @@ if ( ! class_exists( 'Plugin_ACF' ) ) {
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.
@@ -143,8 +143,8 @@ if ( ! class_exists( 'Plugin_ACF' ) ) {
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
 
@@ -513,35 +513,35 @@ if ( ! class_exists( 'Plugin_ACF' ) ) {
513
  * @return string
514
  */
515
  public function on_diff_table_output_field_group( $diff_table_output, $context ) {
516
- $post_type = !empty($context['post_type']) ? $context['post_type'] : false;
517
 
518
- // Bail if not ACF Field Group.
519
- if ($post_type !== 'acf-field-group') {
520
- return '';
521
- }
522
 
523
  // Field group fields to check for and output if found
524
  $arrKeys = array(
525
  'instruction_placement' => array(
526
- 'name' => _x('Instruction placement', 'Logger: Plugin ACF', 'simple-history'),
527
  ),
528
  'label_placement' => array(
529
- 'name' => _x('Label placement', 'Logger: Plugin ACF', 'simple-history'),
530
  ),
531
  'description' => array(
532
- 'name' => _x('Description', 'Logger: Plugin ACF', 'simple-history'),
533
  ),
534
  'menu_order' => array(
535
- 'name' => _x('Menu order', 'Logger: Plugin ACF', 'simple-history'),
536
  ),
537
  'position' => array(
538
- 'name' => _x('Position', 'Logger: Plugin ACF', 'simple-history'),
539
  ),
540
  'active' => array(
541
- 'name' => _x('Active', 'Logger: Plugin ACF', 'simple-history'),
542
  ),
543
  'style' => array(
544
- 'name' => _x('Style', 'Logger: Plugin ACF', 'simple-history'),
545
  ),
546
  );
547
 
@@ -665,19 +665,19 @@ if ( ! class_exists( 'Plugin_ACF' ) ) {
665
  $strModifiedFields = '';
666
  $arrAddedFieldsKeysToCheck = array(
667
  'name' => array(
668
- 'name' => _x('Name: ', 'Logger: Plugin ACF', 'simple-history'),
669
  ),
670
  'parent' => array(
671
- 'name' => _x('Parent: ', 'Logger: Plugin ACF', 'simple-history'),
672
  ),
673
  'key' => array(
674
- 'name' => _x('Key: ', 'Logger: Plugin ACF', 'simple-history'),
675
  ),
676
  'label' => array(
677
- 'name' => _x('Label: ', 'Logger: Plugin ACF', 'simple-history'),
678
  ),
679
  'type' => array(
680
- 'name' => _x('Type: ', 'Logger: Plugin ACF', 'simple-history'),
681
  ),
682
  );
683
 
@@ -869,7 +869,7 @@ if ( ! class_exists( 'Plugin_ACF' ) ) {
869
  if ( ! empty( $this->oldAndNewFieldGroupsAndFields['modifiedFields']['old'] ) && ! empty( $this->oldAndNewFieldGroupsAndFields['modifiedFields']['new'] ) ) {
870
  $modifiedFields = $this->oldAndNewFieldGroupsAndFields['modifiedFields'];
871
 
872
- $arrAddedFieldsKeysToAdd = array(
873
  'parent',
874
  'key',
875
  'label',
@@ -890,12 +890,19 @@ if ( ! class_exists( 'Plugin_ACF' ) ) {
890
  $context[ "acf_modified_fields_{$loopnum}_name_prev" ] = $modifiedFields['old'][ $modifiedFieldId ]['name'];
891
  $context[ "acf_modified_fields_{$loopnum}_label_prev" ] = $modifiedFields['old'][ $modifiedFieldId ]['label'];
892
 
893
- foreach ( $arrAddedFieldsKeysToAdd as $oneKeyToAdd ) {
894
- // dd($modifiedFields);
895
- // Only add to context if modified
896
- if ( $modifiedFields['new'][ $modifiedFieldId ][ $oneKeyToAdd ] != $modifiedFields['old'][ $modifiedFieldId ][ $oneKeyToAdd ] ) {
897
- $context[ "acf_modified_fields_{$loopnum}_{$oneKeyToAdd}_prev" ] = $modifiedFields['old'][ $modifiedFieldId ][ $oneKeyToAdd ];
898
- $context[ "acf_modified_fields_{$loopnum}_{$oneKeyToAdd}_new" ] = $modifiedFields['new'][ $modifiedFieldId ][ $oneKeyToAdd ];
 
 
 
 
 
 
 
899
  }
900
  }
901
 
@@ -920,10 +927,13 @@ if ( ! class_exists( 'Plugin_ACF' ) ) {
920
  /**
921
  * Store a version of the field group as it was before the save
922
  * Called before field group post/values is added to db
 
 
 
923
  */
924
  public function on_wp_insert_post_data( $data, $postarr ) {
925
 
926
- // Only do this if ACF field group is being saved
927
  if ( $postarr['post_type'] !== 'acf-field-group' ) {
928
  return $data;
929
  }
65
  }
66
 
67
  private function isACFInstalled() {
68
+ return defined( 'ACF' ) && ACF;
69
  }
70
 
71
  /**
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.
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
 
513
  * @return string
514
  */
515
  public function on_diff_table_output_field_group( $diff_table_output, $context ) {
516
+ $post_type = ! empty( $context['post_type'] ) ? $context['post_type'] : false;
517
 
518
+ // Bail if not ACF Field Group.
519
+ if ( $post_type !== 'acf-field-group' ) {
520
+ return '';
521
+ }
522
 
523
  // Field group fields to check for and output if found
524
  $arrKeys = array(
525
  'instruction_placement' => array(
526
+ 'name' => _x( 'Instruction placement', 'Logger: Plugin ACF', 'simple-history' ),
527
  ),
528
  'label_placement' => array(
529
+ 'name' => _x( 'Label placement', 'Logger: Plugin ACF', 'simple-history' ),
530
  ),
531
  'description' => array(
532
+ 'name' => _x( 'Description', 'Logger: Plugin ACF', 'simple-history' ),
533
  ),
534
  'menu_order' => array(
535
+ 'name' => _x( 'Menu order', 'Logger: Plugin ACF', 'simple-history' ),
536
  ),
537
  'position' => array(
538
+ 'name' => _x( 'Position', 'Logger: Plugin ACF', 'simple-history' ),
539
  ),
540
  'active' => array(
541
+ 'name' => _x( 'Active', 'Logger: Plugin ACF', 'simple-history' ),
542
  ),
543
  'style' => array(
544
+ 'name' => _x( 'Style', 'Logger: Plugin ACF', 'simple-history' ),
545
  ),
546
  );
547
 
665
  $strModifiedFields = '';
666
  $arrAddedFieldsKeysToCheck = array(
667
  'name' => array(
668
+ 'name' => _x( 'Name: ', 'Logger: Plugin ACF', 'simple-history' ),
669
  ),
670
  'parent' => array(
671
+ 'name' => _x( 'Parent: ', 'Logger: Plugin ACF', 'simple-history' ),
672
  ),
673
  'key' => array(
674
+ 'name' => _x( 'Key: ', 'Logger: Plugin ACF', 'simple-history' ),
675
  ),
676
  'label' => array(
677
+ 'name' => _x( 'Label: ', 'Logger: Plugin ACF', 'simple-history' ),
678
  ),
679
  'type' => array(
680
+ 'name' => _x( 'Type: ', 'Logger: Plugin ACF', 'simple-history' ),
681
  ),
682
  );
683
 
869
  if ( ! empty( $this->oldAndNewFieldGroupsAndFields['modifiedFields']['old'] ) && ! empty( $this->oldAndNewFieldGroupsAndFields['modifiedFields']['new'] ) ) {
870
  $modifiedFields = $this->oldAndNewFieldGroupsAndFields['modifiedFields'];
871
 
872
+ $arr_added_fields_keys_to_add = array(
873
  'parent',
874
  'key',
875
  'label',
890
  $context[ "acf_modified_fields_{$loopnum}_name_prev" ] = $modifiedFields['old'][ $modifiedFieldId ]['name'];
891
  $context[ "acf_modified_fields_{$loopnum}_label_prev" ] = $modifiedFields['old'][ $modifiedFieldId ]['label'];
892
 
893
+ foreach ( $arr_added_fields_keys_to_add as $one_key_to_add ) {
894
+ // Check that new and old exist.
895
+ $new_exist = isset( $modifiedFields['new'][ $modifiedFieldId ][ $one_key_to_add ] );
896
+ $old_exists = isset( $modifiedFields['old'][ $modifiedFieldId ][ $one_key_to_add ] );
897
+
898
+ if ( ! $new_exists || ! $old_exists ) {
899
+ continue;
900
+ }
901
+
902
+ // Only add to context if modified.
903
+ if ( $modifiedFields['new'][ $modifiedFieldId ][ $one_key_to_add ] != $modifiedFields['old'][ $modifiedFieldId ][ $one_key_to_add ] ) {
904
+ $context[ "acf_modified_fields_{$loopnum}_{$one_key_to_add}_prev" ] = $modifiedFields['old'][ $modifiedFieldId ][ $one_key_to_add ];
905
+ $context[ "acf_modified_fields_{$loopnum}_{$one_key_to_add}_new" ] = $modifiedFields['new'][ $modifiedFieldId ][ $one_key_to_add ];
906
  }
907
  }
908
 
927
  /**
928
  * Store a version of the field group as it was before the save
929
  * Called before field group post/values is added to db
930
+ *
931
+ * @param array $data Post data.
932
+ * @param array $postarr Post data.
933
  */
934
  public function on_wp_insert_post_data( $data, $postarr ) {
935
 
936
+ // Only do this if ACF field group is being saved.
937
  if ( $postarr['post_type'] !== 'acf-field-group' ) {
938
  return $data;
939
  }
loggers/Plugin_Redirection.php CHANGED
@@ -97,36 +97,36 @@ if ( ! class_exists( 'Plugin_Redirection' ) ) {
97
  * @return WP_HTTP_Response $response
98
  */
99
  public function on_rest_request_before_callbacks( $response, $handler, $request ) {
100
- // API route callback object, for example "Redirection_Api_Redirect" Object.
101
- $route_callback_object = isset( $handler['callback'][0] ) ? $handler['callback'][0] : false;
102
-
103
- // In the case of redirection $route_callback_object must be a class
104
- // so bail if it is not.
105
- if ( gettype( $route_callback_object ) !== 'object' ) {
106
  return $response;
107
  }
108
 
109
- $route_callback_object_class = get_class( $route_callback_object );
 
 
110
 
111
- // Method name to call on callback class, for example "route_bulk".
112
- $route_callback_method = isset( $handler['callback'][1] ) ? $handler['callback'][1] : false;
113
 
114
- $redirection_api_classes = array(
115
- 'Redirection_Api_Redirect',
116
- 'Redirection_Api_Group',
117
- 'Redirection_Api_Settings',
 
 
 
118
  );
119
 
120
  // Bail directly if this is not a Redirection API call.
121
- if ( ! in_array( $route_callback_object_class, $redirection_api_classes ) ) {
122
  return $response;
123
  }
124
 
125
- if ( 'Redirection_Api_Redirect' == $route_callback_object_class && 'route_create' === $route_callback_method ) {
126
  $this->log_redirection_add( $request );
127
- } elseif ( 'route_update' === $route_callback_method ) {
128
  $this->log_redirection_edit( $request );
129
- } else if ( 'Redirection_Api_Redirect' == $route_callback_object_class && 'route_bulk' === $route_callback_method ) {
130
  $bulk_action = $request->get_param( 'bulk' );
131
 
132
  $bulk_items = $request->get_param( 'items' );
@@ -147,9 +147,9 @@ if ( ! class_exists( 'Plugin_Redirection' ) ) {
147
  } elseif ( 'delete' === $bulk_action ) {
148
  $this->log_redirection_delete( $request, $bulk_items );
149
  }
150
- } elseif ( 'Redirection_Api_Group' == $route_callback_object_class && 'route_create' === $route_callback_method ) {
151
  $this->log_group_add( $request );
152
- } else if ( 'Redirection_Api_Group' == $route_callback_object_class && 'route_bulk' === $route_callback_method ) {
153
  $bulk_action = $request->get_param( 'bulk' );
154
 
155
  $bulk_items = $request->get_param( 'items' );
@@ -170,7 +170,7 @@ if ( ! class_exists( 'Plugin_Redirection' ) ) {
170
  } elseif ( 'delete' === $bulk_action ) {
171
  $this->log_group_delete( $request, $bulk_items );
172
  }
173
- } else if ( 'Redirection_Api_Settings' == $route_callback_object_class ) {
174
  $this->log_options_save( $request );
175
  }
176
 
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
+ error_log( $callable_name );
 
110
 
111
+ $ok_redirection_api_callable_names = array(
112
+ 'Redirection_Api_Redirect::route_bulk',
113
+ 'Redirection_Api_Redirect::route_create',
114
+ 'Redirection_Api_Redirect::route_update',
115
+ 'Redirection_Api_Group::route_create',
116
+ 'Redirection_Api_Group::route_bulk',
117
+ 'Redirection_Api_Settings::route_save_settings',
118
  );
119
 
120
  // Bail directly if this is not a Redirection API call.
121
+ if ( ! in_array( $callable_name, $ok_redirection_api_callable_names ) ) {
122
  return $response;
123
  }
124
 
125
+ if ( 'Redirection_Api_Redirect::route_create' === $callable_name ) {
126
  $this->log_redirection_add( $request );
127
+ } elseif ( 'Redirection_Api_Redirect::route_update' === $callable_name ) {
128
  $this->log_redirection_edit( $request );
129
+ } else if ( 'Redirection_Api_Redirect::route_bulk' === $callable_name ) {
130
  $bulk_action = $request->get_param( 'bulk' );
131
 
132
  $bulk_items = $request->get_param( 'items' );
147
  } elseif ( 'delete' === $bulk_action ) {
148
  $this->log_redirection_delete( $request, $bulk_items );
149
  }
150
+ } elseif ( 'Redirection_Api_Group::route_create' === $callable_name ) {
151
  $this->log_group_add( $request );
152
+ } else if ( 'Redirection_Api_Group::route_bulk' === $callable_name ) {
153
  $bulk_action = $request->get_param( 'bulk' );
154
 
155
  $bulk_items = $request->get_param( 'items' );
170
  } elseif ( 'delete' === $bulk_action ) {
171
  $this->log_group_delete( $request, $bulk_items );
172
  }
173
+ } else if ( 'Redirection_Api_Settings::route_save_settings' == $callable_name ) {
174
  $this->log_options_save( $request );
175
  }
176
 
loggers/SimpleCategoriesLogger.php CHANGED
@@ -250,19 +250,21 @@ class SimpleCategoriesLogger extends SimpleLogger {
250
  $term_taxonomy = isset( $context['from_term_taxonomy'] ) ? (string) $context['from_term_taxonomy'] : null;
251
  }
252
 
253
- if ( is_wp_error( $term_object ) ) {
254
- return $this->interpolate( $message, $context, $row );
255
- }
256
-
257
  $tax_edit_link = add_query_arg(
258
  array(
259
  'taxonomy' => $term_taxonomy,
260
  ),
261
  admin_url( 'term.php' )
262
  );
 
263
  $context['tax_edit_link'] = $tax_edit_link;
264
 
265
  $term_object = get_term( $term_id, $term_taxonomy );
 
 
 
 
 
266
  $term_edit_link = get_edit_tag_link( $term_id, $term_object->taxonomy );
267
  $context['term_edit_link'] = $term_edit_link;
268
 
250
  $term_taxonomy = isset( $context['from_term_taxonomy'] ) ? (string) $context['from_term_taxonomy'] : null;
251
  }
252
 
 
 
 
 
253
  $tax_edit_link = add_query_arg(
254
  array(
255
  'taxonomy' => $term_taxonomy,
256
  ),
257
  admin_url( 'term.php' )
258
  );
259
+
260
  $context['tax_edit_link'] = $tax_edit_link;
261
 
262
  $term_object = get_term( $term_id, $term_taxonomy );
263
+
264
+ if ( is_wp_error( $term_object ) ) {
265
+ return $this->interpolate( $message, $context, $row );
266
+ }
267
+
268
  $term_edit_link = get_edit_tag_link( $term_id, $term_object->taxonomy );
269
  $context['term_edit_link'] = $term_edit_link;
270
 
loggers/SimplePostLogger.php CHANGED
@@ -274,7 +274,9 @@ class SimplePostLogger extends SimpleLogger {
274
  }
275
 
276
  /**
277
- * Called when a post is deleted from the trash
 
 
278
  */
279
  function on_delete_post( $post_id ) {
280
 
@@ -288,7 +290,23 @@ class SimplePostLogger extends SimpleLogger {
288
  return;
289
  }
290
 
291
- if ( 'nav_menu_item' == get_post_type( $post ) ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  return;
293
  }
294
 
@@ -349,6 +367,9 @@ class SimplePostLogger extends SimpleLogger {
349
 
350
  /**
351
  * Check if post type is ok to log by logger
 
 
 
352
  * @return bool
353
  */
354
  public function ok_to_log_post_posttype( $post ) {
274
  }
275
 
276
  /**
277
+ * Fired immediately before a post is deleted from the database.
278
+ *
279
+ * @param int $postid Post ID.
280
  */
281
  function on_delete_post( $post_id ) {
282
 
290
  return;
291
  }
292
 
293
+ if ( ! $this->ok_to_log_post_posttype( $post ) ) {
294
+ $ok_to_log = false;
295
+ }
296
+
297
+ /**
298
+ * Filter to control logging.
299
+ *
300
+ * @param bool $ok_to_log If this post deletion should be logged.
301
+ * @param int $post_id
302
+ *
303
+ * @return bool True to log, false to not log.
304
+ *
305
+ * @since 2.21
306
+ */
307
+ $ok_to_log = apply_filters( 'simple_history/post_logger/post_deleted/ok_to_log', $ok_to_log, $post_id );
308
+
309
+ if ( ! $ok_to_log ) {
310
  return;
311
  }
312
 
367
 
368
  /**
369
  * Check if post type is ok to log by logger
370
+ *
371
+ * @param Int or WP_Post $post Post the check.
372
+ *
373
  * @return bool
374
  */
375
  public function ok_to_log_post_posttype( $post ) {
loggers/class-sh-privacy-logger.php ADDED
@@ -0,0 +1,415 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Logger for privacy/GDPR related things.
4
+ *
5
+ * @package SimpleHistory
6
+ */
7
+
8
+ defined( 'ABSPATH' ) || die();
9
+
10
+ /**
11
+ * Logger for WordPress privacy/GDPR related things
12
+ *
13
+ * List of interesting filters:
14
+ * https://developer.wordpress.org/plugins/privacy/privacy-related-options-hooks-and-capabilities/
15
+ *
16
+ * Possible enhancements:
17
+ * - Log when status changes to "Retry".
18
+ * Something has status "request-failed".
19
+ * Set in _wp_personal_data_cleanup_requests()
20
+ * - Log when _wp_privacy_resend_request() is called
21
+ */
22
+
23
+ /**
24
+ * Class to log things from the Redirection plugin.
25
+ */
26
+ class SH_Privacy_Logger extends SimpleLogger {
27
+
28
+ /**
29
+ * Logger slug.
30
+ *
31
+ * @var string
32
+ */
33
+ public $slug = __CLASS__;
34
+
35
+ /**
36
+ * Return info about logger.
37
+ *
38
+ * @return array Array with plugin info.
39
+ */
40
+ public function getInfo() {
41
+ $arr_info = array(
42
+ 'name' => 'Privacy',
43
+ 'description' => _x( 'Log WordPress privacy related things', 'Logger: Privacy', 'simple-history' ),
44
+ 'capability' => 'manage_options',
45
+ 'messages' => array(
46
+ 'privacy_page_created' => _x( 'Created a new privacy page "{new_post_title}"', 'Logger: Privacy', 'simple-history' ),
47
+ 'privacy_page_set' => _x( 'Set privacy page to page "{new_post_title}"', 'Logger: Privacy', 'simple-history' ),
48
+ 'privacy_data_export_requested' => _x( 'Requested a privacy data export for user "{user_email}"', 'Logger: Privacy', 'simple-history' ),
49
+ 'privacy_data_export_admin_downloaded' => _x( 'Downloaded personal data export file for user "{user_email}"', 'Logger: Privacy', 'simple-history' ),
50
+ 'privacy_data_export_emailed' => _x( 'Sent email with personal data export download info for user "{user_email}"', 'Logger: Privacy', 'simple-history' ),
51
+ 'privacy_data_export_request_confirmed' => _x( 'Confirmed data export request for "{user_email}"', 'Logger: Privacy', 'simple-history' ),
52
+ 'privacy_data_export_removed' => _x( 'Removed data export request for "{user_email}"', 'Logger: Privacy', 'simple-history' ),
53
+ 'data_erasure_request_sent' => _x( 'Sent data erasure request for "{user_email}"', 'Logger: Privacy', 'simple-history' ),
54
+ 'data_erasure_request_confirmed' => _x( 'Confirmed data erasure request for "{user_email}"', 'Logger: Privacy', 'simple-history' ),
55
+ 'data_erasure_request_handled' => _x( 'Erased personal data for "{user_email}"', 'Logger: Privacy', 'simple-history' ),
56
+ 'data_erasure_request_removed' => _x( 'Removed personal data removal request for "{user_email}"', 'Logger: Privacy', 'simple-history' ),
57
+ ),
58
+ );
59
+
60
+ return $arr_info;
61
+ }
62
+
63
+ /**
64
+ * Called when logger is loaded.
65
+ */
66
+ public function loaded() {
67
+ // Exclude the post types used by the data export from the regular post logger updates.
68
+ add_filter( 'simple_history/post_logger/skip_posttypes', array( $this, 'remove_post_types_from_postlogger' ) );
69
+
70
+ // Add filters to detect when a privacy page is created and when a privacy page is set..
71
+ // We only Aadd the filters when the privacy page is loaded.
72
+ add_action( 'load-privacy.php', array( $this, 'on_load_privacy_page' ) );
73
+
74
+ // Add filters to detect data export related functions.
75
+ // We only add the filters when the tools page for export personal data is loaded.
76
+ add_action( 'load-tools_page_export_personal_data', array( $this, 'on_load_export_personal_data_page' ) );
77
+
78
+ // Request to export or remove data is stored in posts with post type "user_request",
79
+ // so we add a hook to catch all saves to that post.
80
+ add_action( 'save_post_user_request', array( $this, 'on_save_post_user_request' ), 10, 3 );
81
+
82
+ // Actions fired when user confirms an request action, like exporting their data.
83
+ add_action( 'user_request_action_confirmed', array( $this, 'on_user_request_action_confirmed' ), 10, 1 );
84
+
85
+ // Add filters to detect misc things that happen on export page.
86
+ add_action( 'load-tools_page_remove_personal_data', array( $this, 'on_load_page_remove_personal_data' ) );
87
+ }
88
+
89
+ /*
90
+ Erase Personal Data
91
+ - send request
92
+ - approve request
93
+ -
94
+ */
95
+
96
+ /**
97
+ * Remove privacy post types from the post types that the usual postlogger logs.
98
+ *
99
+ * @param array $skip_posttypes Posttypes to skip.
100
+ */
101
+ public function remove_post_types_from_postlogger( $skip_posttypes ) {
102
+ array_push(
103
+ $skip_posttypes,
104
+ 'user_request'
105
+ );
106
+
107
+ return $skip_posttypes;
108
+ }
109
+
110
+ /**
111
+ * User gets email and clicks in the confirm data export link.
112
+ * Ends up on page that looks kinda like the login page and with message
113
+ * "Tack för att du bekräftar din begäran om dataexport."
114
+ * "Webbplatsens administratör har informerats. Du kommer via e-post att få en länk för nedladdning av din dataexport när begäran har behandlats."
115
+ * URL is like
116
+ * http://wp-playground.localhost/wp/wp-login.php?action=confirmaction&request_id=806confirm_key=kOQEq4xkEI2DJdNZ
117
+ *
118
+ * Http://wp-playground.localhost/wp/wp-login.php?action=confirmaction&request_id=807&confirm_key=DXdqHbidLZGLB9uW0rTo
119
+ * Tack för att du bekräftar din begäran om att ta bort data.
120
+ * Webbplatsens administratör har informerats. Du kommer via e-post att få en notifiering när dina uppgifter har raderats.
121
+ *
122
+ * Fired when user confirms data export or data erasure.
123
+ *
124
+ * @param int $request_id Request ID.
125
+ */
126
+ public function on_user_request_action_confirmed( $request_id ) {
127
+ // Load user.php because we use functions from there.
128
+ if ( ! function_exists( 'get_editable_roles' ) ) {
129
+ require_once ABSPATH . 'wp-admin/includes/user.php';
130
+ }
131
+
132
+ // Check that new function since 4.9.6 exist before trying to use functions.
133
+ if ( ! function_exists( 'wp_get_user_request_data' ) ) {
134
+ return;
135
+ }
136
+
137
+ $user_request = wp_get_user_request_data( $request_id );
138
+ if ( ! $user_request ) {
139
+ return;
140
+ }
141
+
142
+ // User approved data export.
143
+ if ( 'export_personal_data' === $user_request->action_name && 'request-confirmed' === $user_request->status ) {
144
+ $this->infoMessage(
145
+ 'privacy_data_export_request_confirmed',
146
+ array(
147
+ 'user_email' => $user_request->email,
148
+ )
149
+ );
150
+ } else if ( 'remove_personal_data' === $user_request->action_name && 'request-confirmed' === $user_request->status ) {
151
+ $this->infoMessage(
152
+ 'data_erasure_request_confirmed',
153
+ array(
154
+ 'user_email' => $user_request->email,
155
+ )
156
+ );
157
+ }
158
+
159
+ sh_error_log( 'on_user_request_action_confirmed', $user_request );
160
+ }
161
+
162
+ /**
163
+ * Fires once a post of post type "user_request" has been saved.
164
+ * Used to catch data export request actions.
165
+ *
166
+ * @param int $post_ID Post ID.
167
+ * @param WP_Post $post Post object.
168
+ * @param bool $update Whether this is an existing post being updated or not.
169
+ */
170
+ public function on_save_post_user_request( $post_ID, $post, $update ) {
171
+ if ( empty( $post_ID ) ) {
172
+ return;
173
+ }
174
+
175
+ if ( ! is_a( $post, 'WP_Post' ) ) {
176
+ return;
177
+ }
178
+
179
+ // Check that new function since 4.9.6 exist before trying to use functions.
180
+ if ( ! function_exists( 'wp_get_user_request_data' ) ) {
181
+ return;
182
+ }
183
+
184
+ // Post id should be a of type WP_User_Request.
185
+ $user_request = wp_get_user_request_data( $post_ID );
186
+
187
+ if ( ! $user_request ) {
188
+ return;
189
+ }
190
+
191
+ $is_doing_ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
192
+
193
+ if ( ! $update && 'export_personal_data' === $user_request->action_name && 'request-pending' && $user_request->status ) {
194
+ // Add Data Export Request.
195
+ // An email will be sent to the user at this email address asking them to verify the request.
196
+ // Notice message in admin is "Confirmation request initiated successfully.".
197
+ $this->infoMessage(
198
+ 'privacy_data_export_requested',
199
+ array(
200
+ 'user_email' => $user_request->email,
201
+ )
202
+ );
203
+ } elseif ( $update && is_user_logged_in() && $is_doing_ajax && 'export_personal_data' === $user_request->action_name && 'request-completed' && $user_request->status ) {
204
+
205
+ $send_as_email = isset( $_POST['sendAsEmail'] ) ? 'true' === $_POST['sendAsEmail'] : false;
206
+
207
+ if ( $send_as_email ) {
208
+ // If send as email = true then email a link with the export to a user.
209
+ $this->infoMessage(
210
+ 'privacy_data_export_emailed',
211
+ array(
212
+ 'user_email' => $user_request->email,
213
+ )
214
+ );
215
+ } else if ( ! $send_as_email ) {
216
+ // If send as email = false then export file is downloaded by admin user in admin.
217
+ // Probably by clicking the download link near the user name.
218
+ $this->infoMessage(
219
+ 'privacy_data_export_admin_downloaded',
220
+ array(
221
+ 'user_email' => $user_request->email,
222
+ )
223
+ );
224
+ }
225
+ } else if ( ! $update && 'remove_personal_data' === $user_request->action_name && 'request-pending' === $user_request->status ) {
226
+ // Send request to user to remove user data.
227
+ $this->infoMessage(
228
+ 'data_erasure_request_sent',
229
+ array(
230
+ 'user_email' => $user_request->email,
231
+ )
232
+ );
233
+ } else if ( $update && 'remove_personal_data' === $user_request->action_name && 'request-completed' === $user_request->status ) {
234
+ // Admin clicked "Erase user data" in admin, after user has approved remove request.
235
+ $this->infoMessage(
236
+ 'data_erasure_request_handled',
237
+ array(
238
+ 'user_email' => $user_request->email,
239
+ )
240
+ );
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Fires before a post is deleted, at the start of wp_delete_post().
246
+ * We use this to detect if a post with post type 'user_request' is going to be deleted,
247
+ * meaning that a user request is removed.
248
+ *
249
+ * @param int $postid Post ID.
250
+ */
251
+ public function on_before_delete_post_on_remove_personal_data_page( $postid ) {
252
+ if ( empty( $postid ) ) {
253
+ return;
254
+ }
255
+
256
+ $post = get_post( $postid );
257
+
258
+ if ( ! is_a( $post, 'WP_Post' ) ) {
259
+ return;
260
+ }
261
+
262
+ if ( get_post_type( $post ) !== 'user_request' ) {
263
+ return;
264
+ }
265
+
266
+ if ( ! function_exists( 'wp_get_user_request_data' ) ) {
267
+ return;
268
+ }
269
+
270
+ $user_request = wp_get_user_request_data( $postid );
271
+
272
+ if ( ! $user_request ) {
273
+ return;
274
+ }
275
+
276
+ $action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : null;
277
+
278
+ if ( $user_request && 'delete' === $action ) {
279
+ // Looks like "Remove request" action.
280
+ $this->infoMessage(
281
+ 'data_erasure_request_removed',
282
+ array(
283
+ 'user_email' => $user_request->email,
284
+ )
285
+ );
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Fires before a post is deleted, at the start of wp_delete_post().
291
+ * We use this to detect if a post with post type 'user_request' is going to be deleted,
292
+ * meaning that a user request is removed.
293
+ *
294
+ * @param int $postid Post ID.
295
+ */
296
+ public function on_before_delete_post_on_personal_data_page( $postid ) {
297
+ if ( empty( $postid ) ) {
298
+ return;
299
+ }
300
+
301
+ $post = get_post( $postid );
302
+
303
+ if ( ! is_a( $post, 'WP_Post' ) ) {
304
+ return;
305
+ }
306
+
307
+ if ( get_post_type( $post ) !== 'user_request' ) {
308
+ return;
309
+ }
310
+
311
+ if ( ! function_exists( 'wp_get_user_request_data' ) ) {
312
+ return;
313
+ }
314
+
315
+ $user_request = wp_get_user_request_data( $postid );
316
+
317
+ if ( ! $user_request ) {
318
+ return;
319
+ }
320
+
321
+ $action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : null;
322
+
323
+ if ( $user_request && 'delete' === $action ) {
324
+ // Looks like "Remove request" action.
325
+ $this->infoMessage(
326
+ 'privacy_data_export_removed',
327
+ array(
328
+ 'user_email' => $user_request->email,
329
+ )
330
+ );
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Fired when the tools page for export data is loaded.
336
+ */
337
+ public function on_load_export_personal_data_page() {
338
+ // When requests are removed posts are removed.
339
+ add_action( 'before_delete_post', array( $this, 'on_before_delete_post_on_personal_data_page' ), 10, 1 );
340
+ }
341
+
342
+ /**
343
+ * Fired when the tools page for user data removal is loaded.
344
+ */
345
+ public function on_load_page_remove_personal_data() {
346
+ // When requests are removed posts are removed.
347
+ add_action( 'before_delete_post', array( $this, 'on_before_delete_post_on_remove_personal_data_page' ), 10, 1 );
348
+ }
349
+
350
+ /**
351
+ * Fired when the privacy admin page is loaded.
352
+ */
353
+ public function on_load_privacy_page() {
354
+ $action = isset( $_POST['action'] ) ? $_POST['action'] : '';
355
+ $option_name = 'wp_page_for_privacy_policy';
356
+
357
+ if ( 'create-privacy-page' === $action ) {
358
+ add_action( "update_option_{$option_name}", array( $this, 'on_update_option_create_privacy_page' ), 10, 3 );
359
+ } elseif ( 'set-privacy-page' === $action ) {
360
+ add_action( "update_option_{$option_name}", array( $this, 'on_update_option_set_privacy_page' ), 10, 3 );
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Fires after the value of a specific option has been successfully updated.
366
+ *
367
+ * @param mixed $old_value The old option value.
368
+ * @param mixed $value The new option value.
369
+ * @param string $option Option name.
370
+ */
371
+ public function on_update_option_create_privacy_page( $old_value, $value, $option ) {
372
+ $post = get_post( $value );
373
+
374
+ if ( is_a( $post, 'WP_Post' ) ) {
375
+ $new_post_title = $post->post_title;
376
+ }
377
+
378
+ $this->infoMessage(
379
+ 'privacy_page_created',
380
+ array(
381
+ 'prev_post_id' => $old_value,
382
+ 'new_post_id' => $value,
383
+ 'new_post_title' => $new_post_title,
384
+ )
385
+ );
386
+ }
387
+
388
+ /**
389
+ * Fires after the value of a specific option has been successfully updated.
390
+ *
391
+ * @param mixed $old_value The old option value.
392
+ * @param mixed $value The new option value.
393
+ * @param string $option Option name.
394
+ */
395
+ public function on_update_option_set_privacy_page( $old_value, $value, $option ) {
396
+
397
+ $post = get_post( $value );
398
+
399
+ if ( is_a( $post, 'WP_Post' ) ) {
400
+ $new_post_title = $post->post_title;
401
+ }
402
+
403
+ $this->infoMessage(
404
+ 'privacy_page_set',
405
+ array(
406
+ 'prev_post_id' => $old_value,
407
+ 'new_post_id' => $value,
408
+ 'new_post_title' => $new_post_title,
409
+ )
410
+ );
411
+ }
412
+
413
+ } // class
414
+
415
+
readme.txt CHANGED
@@ -5,7 +5,7 @@ Tags: history, log, changes, changelog, audit, trail, pages, attachments, users,
5
  Requires at least: 4.5.1
6
  Tested up to: 4.9
7
  Requires PHP: 5.3
8
- Stable tag: 2.22.1
9
 
10
  View changes made by users within WordPress. See who created a page, uploaded an attachment or approved an comment, and more.
11
 
@@ -38,6 +38,12 @@ see when someone has tried to log in, but failed. The log will then include ip a
38
  * **Menu edits**
39
  * **Option screens**<br>
40
  view details about changes made in the differnt settings sections of WordPress. Things like changes to the site title and the permalink structure will be logged.
 
 
 
 
 
 
41
 
42
 
43
  #### Support for third party plugins
@@ -66,7 +72,7 @@ The plugin [Duplicate Post](https://wordpress.org/plugins/duplicate-post/) allow
66
  clone posts of any type.
67
  Simple History will log when a clone of a post or page is done.
68
 
69
- #### RSS feed available
70
 
71
  There is also a **RSS feed of changes** available, so you can keep track of the changes made via your favorite RSS reader on your phone, on your iPad, or on your computer.
72
 
@@ -85,12 +91,7 @@ Or for debug purposes:
85
  _"The site feels slow since yesterday. Has anyone done anything special? ... Ah, Steven activated 'naughy-plugin-x',
86
  that must be it."_
87
 
88
- #### See it in action
89
-
90
- See the plugin in action with this short screencast:
91
- [youtube http://www.youtube.com/watch?v=4cu4kooJBzs]
92
-
93
- #### API so you can add your own events to Simple History
94
 
95
  If you are a theme or plugin developer and would like to add your own things/events to Simple History you can do that by using the function `SimpleLogger()` like this:
96
 
@@ -134,11 +135,9 @@ to your language then read about how this is done over at the [Polyglots handboo
134
  Development of this plugin takes place at GitHub. Please join in with feature requests, bug reports, or even pull requests!
135
  https://github.com/bonny/WordPress-Simple-History
136
 
137
- #### Donation & more plugins
138
-
139
- * If you like this plugin don't forget to [donate to support further development](http://eskapism.se/sida/donate/).
140
- * More [WordPress CMS plugins](https://profiles.wordpress.org/eskapism#content-plugins) by the same author.
141
 
 
142
 
143
  == Screenshots ==
144
 
@@ -163,6 +162,20 @@ A simple way to see any uncommon activity, for example an increased number of lo
163
 
164
  ## Changelog
165
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  = 2.22.1 (May 2018) =
167
 
168
  - Fix for some REST API Routes not working, for example when using WPCF7. Should fix https://wordpress.org/support/topic/errorexception-with-wpcf7/ and similar.
5
  Requires at least: 4.5.1
6
  Tested up to: 4.9
7
  Requires PHP: 5.3
8
+ Stable tag: 2.23
9
 
10
  View changes made by users within WordPress. See who created a page, uploaded an attachment or approved an comment, and more.
11
 
38
  * **Menu edits**
39
  * **Option screens**<br>
40
  view details about changes made in the differnt settings sections of WordPress. Things like changes to the site title and the permalink structure will be logged.
41
+ * **Privacy page**<br>
42
+ when a privacy page is created or set to a new page.
43
+ * **Data Export**<br>
44
+ see when a privacy data export request is added and when this request is approved by the user, downloaded by an admin, or emailed to the user.
45
+ * **User Data Erasure Requests**<br>
46
+ see when a user privacy data export request is added and when this request is approved by the user and when the user data is removed.
47
 
48
 
49
  #### Support for third party plugins
72
  clone posts of any type.
73
  Simple History will log when a clone of a post or page is done.
74
 
75
+ #### RSS feed with changes
76
 
77
  There is also a **RSS feed of changes** available, so you can keep track of the changes made via your favorite RSS reader on your phone, on your iPad, or on your computer.
78
 
91
  _"The site feels slow since yesterday. Has anyone done anything special? ... Ah, Steven activated 'naughy-plugin-x',
92
  that must be it."_
93
 
94
+ #### API so you can add your own events to the audit log
 
 
 
 
 
95
 
96
  If you are a theme or plugin developer and would like to add your own things/events to Simple History you can do that by using the function `SimpleLogger()` like this:
97
 
135
  Development of this plugin takes place at GitHub. Please join in with feature requests, bug reports, or even pull requests!
136
  https://github.com/bonny/WordPress-Simple-History
137
 
138
+ #### Donation
 
 
 
139
 
140
+ * If you like this plugin please consider [donating to support the development](https://www.paypal.me/eskapism).
141
 
142
  == Screenshots ==
143
 
162
 
163
  ## Changelog
164
 
165
+ = 2.23 (May 2018) =
166
+
167
+ - Add logging of privacy and GDPR related functions in WordPress. Some of the new [privacy related features in WordPress 4.9.6](https://wordpress.org/news/2018/05/wordpress-4-9-6-privacy-and-maintenance-release/) that are logged:
168
+ - Privacy policy page is created or changed to a new page.
169
+ - Privacy data export is requested for a user and when this request is confirmed by the user and when the data for the request is downloaded by an admin or emailed to the user.
170
+ - Erase Personal Data: Request is added for user to have their personal data erased, user confirms the data removal and when the deletion of user data is done.
171
+ - Fix error when categories changes was shown in the log. Fixes https://wordpress.org/support/topic/php-notice-undefined-variable-term_object/.
172
+ - Fix error when a ACF Field Group was saved.
173
+ - Fix error when the IP address anonymization function tried to anonymize an empty IP adress. Could happen when for example running wp cron locally on your server.
174
+ - Fix error when calling the REST API with an API endpoint with a closure as the callback. Fixes https://github.com/bonny/WordPress-Simple-History/issues/141.
175
+ - Rewrote logger loading method so now it's possible to name your loggers in a WordPress codings standard compatible way. Also: made a bit more code more WordPress-ish.
176
+ - The post types in the `skip_posttypes` filter are now also applied to deleted posts.
177
+ - Add function `sh_get_callable_name()` that returns a human readable name for a callback.
178
+
179
  = 2.22.1 (May 2018) =
180
 
181
  - Fix for some REST API Routes not working, for example when using WPCF7. Should fix https://wordpress.org/support/topic/errorexception-with-wpcf7/ and similar.