PluginProbe
Stream – Activity Log & Audit Trail / 3.9.2
Stream – Activity Log & Audit Trail v3.9.2
4.4.0 4.3.0 4.2.2 4.2.1 trunk 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.1 3.1.1 3.10.0 3.2.0 3.2.1 3.2.2 3.2.3 All 50 releases
← All changes | classes/class-log.php +200 -108 3.0.13.9.2 View file →
@@ -1,15 +1,34 @@
1 1 <?php
2 +/**
3 + * Handles top-level record keeping functionality.
4 + *
5 + * @package WP_Stream
6 + */
7 +
2 8 namespace WP_Stream;
3 9
10 +/**
11 + * Class - Log
12 + */
4 13 class Log {
14 +
5 15 /**
6 - * Hold Plugin class
16 + * Holds Instance of plugin object
17 + *
7 18 * @var Plugin
8 19 */
9 20 public $plugin;
10 21
11 22 /**
23 + * Hold Current visitors IP Address.
24 + *
25 + * @var string
26 + */
27 + private $ip_address;
28 +
29 +
30 + /**
12 31 * Previous Stream record ID, used for chaining same-session records
13 32 *
14 33 * @var int
15 34 */
@@ -17,28 +36,41 @@
17 36
18 37 /**
19 38 * Class constructor.
20 39 *
21 - * @param Plugin $plugin The main Plugin class.
40 + * @param Plugin $plugin Instance of plugin object.
22 41 */
23 42 public function __construct( $plugin ) {
24 43 $this->plugin = $plugin;
44 +
45 + // Support proxy mode by checking the `X-Forwarded-For` header first.
46 + $ip_address = wp_stream_filter_input( INPUT_SERVER, 'HTTP_X_FORWARDED_FOR', FILTER_VALIDATE_IP );
47 + $ip_address = $ip_address ? $ip_address : wp_stream_filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP );
48 +
49 + $this->ip_address = $ip_address;
50 +
51 + // Ensure function used in various methods is pre-loaded.
52 + if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
53 + require_once ABSPATH . '/wp-admin/includes/plugin.php';
54 + }
25 55 }
26 56
27 57 /**
28 58 * Log handler
29 59 *
30 - * @param Connector $connector Connector responsible for logging the event
31 - * @param string $message sprintf-ready error message string
32 - * @param array $args sprintf (and extra) arguments to use
33 - * @param int $object_id Target object id
34 - * @param string $context Context of the event
35 - * @param string $action Action of the event
36 - * @param int $user_id User responsible for the event
60 + * @param Connector $connector Connector responsible for logging the event.
61 + * @param string $message sprintf-ready error message string.
62 + * @param array $args sprintf (and extra) arguments to use.
63 + * @param int $object_id Target object id.
64 + * @param string $context Context of the event.
65 + * @param string $action Action of the event.
66 + * @param int $user_id User responsible for the event.
37 67 *
38 68 * @return mixed True if updated, otherwise false|WP_Error
39 69 */
40 70 public function log( $connector, $message, $args, $object_id, $context, $action, $user_id = null ) {
71 + global $wp_roles;
72 +
41 73 if ( is_null( $user_id ) ) {
42 74 $user_id = get_current_user_id();
43 75 }
44 76
@@ -49,9 +81,9 @@
49 81 $wp_cron_tracking = isset( $this->plugin->settings->options['advanced_wp_cron_tracking'] ) ? $this->plugin->settings->options['advanced_wp_cron_tracking'] : false;
50 82 $author = new Author( $user_id );
51 83 $agent = $author->get_current_agent();
52 84
53 - // WP Cron tracking requires opt-in and WP Cron to be enabled
85 + // WP Cron tracking requires opt-in and WP Cron to be enabled.
54 86 if ( ! $wp_cron_tracking && 'wp_cron' === $agent ) {
55 87 return false;
56 88 }
57 89
@@ -76,159 +108,214 @@
76 108 $user_meta['system_user_id'] = (int) $uid;
77 109 $user_meta['system_user_name'] = (string) $user_info['name'];
78 110 }
79 111
80 - // Prevent any meta with null values from being logged
112 + // Prevent any meta with null values from being logged.
81 113 $stream_meta = array_filter(
82 114 $args,
83 - function( $var ) {
115 + function ( $var ) {
84 116 return ! is_null( $var );
85 117 }
86 118 );
87 119
88 - // Add user meta to Stream meta
120 + // Add user meta to Stream meta.
89 121 $stream_meta['user_meta'] = $user_meta;
90 122
91 - // All meta must be strings, so we will serialize any array meta values
92 - array_walk(
93 - $stream_meta,
94 - function( &$v ) {
95 - $v = (string) maybe_serialize( $v );
96 - }
97 - );
123 + if ( ! empty( $user->roles ) ) {
124 + $roles = array_values( $user->roles );
125 + $role = $roles[0];
126 + } elseif ( is_multisite() && is_super_admin() && $wp_roles->is_role( 'administrator' ) ) {
127 + $role = 'administrator';
128 + } else {
129 + $role = '';
130 + }
98 131
99 - // Get the current time in milliseconds
100 - $iso_8601_extended_date = wp_stream_get_iso_8601_extended_date();
101 -
102 132 $recordarr = array(
103 - 'object_id' => (int) $object_id,
104 - 'site_id' => (int) is_multisite() ? get_current_site()->id : 1,
105 - 'blog_id' => (int) apply_filters( 'wp_stream_blog_id_logged', get_current_blog_id() ),
106 - 'user_id' => (int) $user_id,
107 - 'user_role' => (string) ! empty( $user->roles ) ? $user->roles[0] : '',
108 - 'created' => (string) $iso_8601_extended_date,
109 - 'summary' => (string) vsprintf( $message, $args ),
110 - 'connector' => (string) $connector,
111 - 'context' => (string) $context,
112 - 'action' => (string) $action,
113 - 'ip' => (string) wp_stream_filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP ),
114 - 'meta' => (array) $stream_meta,
133 + 'object_id' => (int) $object_id,
134 + 'site_id' => (int) is_multisite() ? get_current_site()->id : 1,
135 + 'blog_id' => (int) apply_filters( 'wp_stream_blog_id_logged', get_current_blog_id() ),
136 + 'user_id' => (int) $user_id,
137 + 'user_role' => (string) $role,
138 + 'created' => (string) current_time( 'mysql', true ),
139 + 'summary' => (string) vsprintf( $message, $args ),
140 + 'connector' => (string) $connector,
141 + 'context' => (string) $context,
142 + 'action' => (string) $action,
143 + 'ip' => (string) $this->ip_address,
144 + 'meta' => (array) $stream_meta,
115 145 );
116 146
147 + if ( 0 === $recordarr['object_id'] ) {
148 + unset( $recordarr['object_id'] );
149 + }
150 +
117 151 $result = $this->plugin->db->insert( $recordarr );
118 152
119 - $this->debug_backtrace( $recordarr );
153 + // This is helpful in development environments:
154 + // error_log( $this->debug_backtrace( $recordarr ) );.
120 155
121 156 return $result;
122 157 }
123 158
124 159 /**
125 - * This function is use to check whether or not a record should be excluded from the log
160 + * This function is use to check whether or not a record should be excluded from the log.
126 161 *
127 - * @param string $connector Name of the connector being logged
128 - * @param string $context Name of the context being logged
129 - * @param string $action Name of the action being logged
130 - * @param \WP_User $user The user being logged
131 - * @param string $ip IP address being logged
162 + * @param string $connector Name of the connector being logged.
163 + * @param string $context Name of the context being logged.
164 + * @param string $action Name of the action being logged.
165 + * @param \WP_User $user The user being logged.
166 + * @param string $ip IP address being logged.
132 167 *
133 168 * @return bool
134 169 */
135 170 public function is_record_excluded( $connector, $context, $action, $user = null, $ip = null ) {
171 + $exclude_record = false;
172 +
136 173 if ( is_null( $user ) ) {
137 174 $user = wp_get_current_user();
138 175 }
139 176
140 177 if ( is_null( $ip ) ) {
141 - $ip = wp_stream_filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP );
178 + $ip = $this->ip_address;
142 179 } else {
143 180 $ip = wp_stream_filter_var( $ip, FILTER_VALIDATE_IP );
144 181 }
145 182
146 - $user_role = isset( $user->roles[0] ) ? $user->roles[0] : null;
147 -
183 + if ( ! empty( $user->roles ) ) {
184 + $roles = array_values( $user->roles );
185 + $role = $roles[0];
186 + } else {
187 + $role = '';
188 + }
148 189 $record = array(
149 190 'connector' => $connector,
150 191 'context' => $context,
151 192 'action' => $action,
152 193 'author' => $user->ID,
153 - 'role' => $user_role,
194 + 'role' => $role,
154 195 'ip_address' => $ip,
155 196 );
156 197
157 198 $exclude_settings = isset( $this->plugin->settings->options['exclude_rules'] ) ? $this->plugin->settings->options['exclude_rules'] : array();
158 199
159 - if ( isset( $exclude_settings['exclude_row'] ) && ! empty( $exclude_settings['exclude_row'] ) ) {
160 - foreach ( $exclude_settings['exclude_row'] as $key => $value ) {
161 - // Prepare values
162 - $author_or_role = isset( $exclude_settings['author_or_role'][ $key ] ) ? $exclude_settings['author_or_role'][ $key ] : '';
163 - $connector = isset( $exclude_settings['connector'][ $key ] ) ? $exclude_settings['connector'][ $key ] : '';
164 - $context = isset( $exclude_settings['context'][ $key ] ) ? $exclude_settings['context'][ $key ] : '';
165 - $action = isset( $exclude_settings['action'][ $key ] ) ? $exclude_settings['action'][ $key ] : '';
166 - $ip_address = isset( $exclude_settings['ip_address'][ $key ] ) ? $exclude_settings['ip_address'][ $key ] : '';
200 + if ( is_multisite() && $this->plugin->is_network_activated() && ! is_network_admin() ) {
201 + $multisite_options = (array) get_site_option( 'wp_stream_network', array() );
202 + $exclude_settings = isset( $multisite_options['exclude_rules'] ) ? $multisite_options['exclude_rules'] : array();
203 + }
167 204
168 - $exclude = array(
169 - 'connector' => ! empty( $connector ) ? $connector : null,
170 - 'context' => ! empty( $context ) ? $context : null,
171 - 'action' => ! empty( $action ) ? $action : null,
172 - 'ip_address' => ! empty( $ip_address ) ? $ip_address : null,
173 - 'author' => is_numeric( $author_or_role ) ? absint( $author_or_role ) : null,
174 - 'role' => ( ! empty( $author_or_role ) && ! is_numeric( $author_or_role ) ) ? $author_or_role : null,
175 - );
205 + foreach ( $this->exclude_rules_by_rows( $exclude_settings ) as $exclude_rule ) {
206 + $exclude = array(
207 + 'connector' => ! empty( $exclude_rule['connector'] ) ? $exclude_rule['connector'] : null,
208 + 'context' => ! empty( $exclude_rule['context'] ) ? $exclude_rule['context'] : null,
209 + 'action' => ! empty( $exclude_rule['action'] ) ? $exclude_rule['action'] : null,
210 + 'ip_address' => ! empty( $exclude_rule['ip_address'] ) ? $exclude_rule['ip_address'] : null,
211 + 'author' => is_numeric( $exclude_rule['author_or_role'] ) ? absint( $exclude_rule['author_or_role'] ) : null,
212 + 'role' => ( ! empty( $exclude_rule['author_or_role'] ) && ! is_numeric( $exclude_rule['author_or_role'] ) ) ? $exclude_rule['author_or_role'] : null,
213 + );
176 214
177 - $exclude_rules = array_filter( $exclude, 'strlen' );
215 + $exclude_rules = array_filter( $exclude, 'strlen' );
178 216
179 - if ( ! empty( $exclude_rules ) ) {
180 - $excluded = true;
217 + if ( $this->record_matches_rules( $record, $exclude_rules ) ) {
218 + $exclude_record = true;
219 + break;
220 + }
221 + }
181 222
182 - foreach ( $exclude_rules as $exclude_key => $exclude_value ) {
183 - if ( $record[ $exclude_key ] !== $exclude_value ) {
184 - $excluded = false;
185 - break;
186 - }
187 - }
223 + /**
224 + * Filters whether or not a record should be excluded from the log.
225 + *
226 + * If true, the record is not logged.
227 + *
228 + * @param array $exclude_record Whether the record should excluded.
229 + * @param array $recordarr The record to log.
230 + *
231 + * @return bool
232 + */
233 + return apply_filters( 'wp_stream_is_record_excluded', $exclude_record, $record );
234 + }
188 235
189 - if ( $excluded ) {
190 - return true;
191 - }
236 + /**
237 + * Check if a record to stored matches certain rules.
238 + *
239 + * @param array $record List of record parameters.
240 + * @param array $exclude_rules List of record exclude rules.
241 + *
242 + * @return boolean
243 + */
244 + public function record_matches_rules( $record, $exclude_rules ) {
245 + $matches_needed = count( $exclude_rules );
246 + $matches_found = 0;
247 + foreach ( $exclude_rules as $exclude_key => $exclude_value ) {
248 + if ( ! isset( $record[ $exclude_key ] ) || is_null( $exclude_value ) ) {
249 + continue;
250 + }
251 +
252 + if ( 'ip_address' === $exclude_key ) {
253 + $ip_addresses = explode( ',', $exclude_value );
254 +
255 + if ( in_array( $record['ip_address'], $ip_addresses, true ) ) {
256 + $matches_found++;
192 257 }
258 + } elseif ( $record[ $exclude_key ] === $exclude_value ) {
259 + $matches_found++;
193 260 }
194 261 }
195 262
196 - return false;
263 + return $matches_found === $matches_needed;
197 264 }
198 265
199 266 /**
200 - * Send a full backtrace of calls to the PHP error log for debugging
267 + * Get all exclude rules by row because we store them by rule instead.
201 268 *
202 - * @param array $recordarr
269 + * @param array $rules List of rules indexed by rule ID.
203 270 *
204 - * @return void
271 + * @return array
205 272 */
206 - public function debug_backtrace( $recordarr ) {
207 - /**
208 - * Enable debug backtrace on records.
209 - *
210 - * This filter is for developer use only. When enabled, Stream will send
211 - * a full debug backtrace of PHP calls for each record. Optionally, you may
212 - * use the available $recordarr parameter to specify what types of records to
213 - * create backtrace logs for.
214 - *
215 - * @param array $recordarr
216 - *
217 - * @return bool Set to FALSE by default (backtrace disabled)
218 - */
219 - $enabled = apply_filters( 'wp_stream_debug_backtrace', false, $recordarr );
273 + public function exclude_rules_by_rows( $rules ) {
274 + $excludes = array();
220 275
221 - if ( ! $enabled ) {
222 - return;
276 + // TODO: Move these to where the settings are generated to ensure they're in sync.
277 + $rule_keys = array(
278 + 'exclude_row',
279 + 'author_or_role',
280 + 'connector',
281 + 'context',
282 + 'action',
283 + 'ip_address',
284 + );
285 +
286 + if ( empty( $rules['exclude_row'] ) ) {
287 + return array();
223 288 }
224 289
290 + foreach ( array_keys( $rules['exclude_row'] ) as $row_id ) {
291 + $excludes[ $row_id ] = array();
292 +
293 + foreach ( $rule_keys as $rule_key ) {
294 + if ( isset( $rules[ $rule_key ][ $row_id ] ) ) {
295 + $excludes[ $row_id ][ $rule_key ] = $rules[ $rule_key ][ $row_id ];
296 + } else {
297 + $excludes[ $row_id ][ $rule_key ] = null;
298 + }
299 + }
300 + }
301 +
302 + return $excludes;
303 + }
304 +
305 + /**
306 + * Helper function to send a full backtrace of calls to the PHP error log for debugging
307 + *
308 + * @param array $recordarr Record argument array.
309 + *
310 + * @return string
311 + */
312 + public function debug_backtrace( $recordarr ) {
225 313 if ( version_compare( PHP_VERSION, '5.3.6', '<' ) ) {
226 - error_log( 'WP Stream debug backtrace requires at least PHP 5.3.6' );
227 - return;
314 + return __( 'Debug backtrace requires at least PHP 5.3.6', 'wp_stream' );
228 315 }
229 316
230 - // Record details
317 + // Record details.
231 318 $summary = isset( $recordarr['summary'] ) ? $recordarr['summary'] : null;
232 319 $author = isset( $recordarr['author'] ) ? $recordarr['author'] : null;
233 320 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
234 321 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
@@ -233,33 +320,38 @@
233 320 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
234 321 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
235 322 $action = isset( $recordarr['action'] ) ? $recordarr['action'] : null;
236 323
237 - // Stream meta
324 + // Stream meta.
238 325 $stream_meta = isset( $recordarr['meta'] ) ? $recordarr['meta'] : null;
239 326
240 327 unset( $stream_meta['user_meta'] );
241 328
242 329 if ( $stream_meta ) {
243 - array_walk( $stream_meta, function( &$value, $key ) {
244 - $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
245 - });
246 -
330 + array_walk(
331 + $stream_meta,
332 + function ( &$value, $key ) {
333 + $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
334 + }
335 + );
247 336 $stream_meta = implode( ', ', $stream_meta );
248 337 }
249 338
250 - // User meta
339 + // User meta.
251 340 $user_meta = isset( $recordarr['meta']['user_meta'] ) ? $recordarr['meta']['user_meta'] : null;
252 341
253 342 if ( $user_meta ) {
254 - array_walk( $user_meta, function( &$value, $key ) {
255 - $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
256 - });
343 + array_walk(
344 + $user_meta,
345 + function ( &$value, $key ) {
346 + $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
347 + }
348 + );
257 349
258 350 $user_meta = implode( ', ', $user_meta );
259 351 }
260 352
261 - // Debug backtrace
353 + // Debug backtrace.
262 354 ob_start();
263 355
264 356 // @codingStandardsIgnoreStart
265 357 debug_print_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // Option to ignore args requires PHP 5.3.6
@@ -279,7 +371,7 @@
279 371 $user_meta,
280 372 implode( "\n", $backtrace )
281 373 );
282 374
283 - error_log( $output );
375 + return $output;
284 376 }
285 377 }