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