PluginProbe
Stream – Activity Log & Audit Trail / 3.7.0
Stream – Activity Log & Audit Trail v3.7.0
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 +173 -121 3.0.63.7.0 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,16 +36,22 @@
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;
25 44
26 - // Ensure function used in various methods is pre-loaded
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.
27 52 if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
28 - require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
53 + require_once ABSPATH . '/wp-admin/includes/plugin.php';
29 54 }
30 55 }
31 56
32 57 /**
@@ -31,15 +56,15 @@
31 56
32 57 /**
33 58 * Log handler
34 59 *
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
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.
42 67 *
43 68 * @return mixed True if updated, otherwise false|WP_Error
44 69 */
45 70 public function log( $connector, $message, $args, $object_id, $context, $action, $user_id = null ) {
@@ -56,9 +81,9 @@
56 81 $wp_cron_tracking = isset( $this->plugin->settings->options['advanced_wp_cron_tracking'] ) ? $this->plugin->settings->options['advanced_wp_cron_tracking'] : false;
57 82 $author = new Author( $user_id );
58 83 $agent = $author->get_current_agent();
59 84
60 - // 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.
61 86 if ( ! $wp_cron_tracking && 'wp_cron' === $agent ) {
62 87 return false;
63 88 }
64 89
@@ -83,30 +108,19 @@
83 108 $user_meta['system_user_id'] = (int) $uid;
84 109 $user_meta['system_user_name'] = (string) $user_info['name'];
85 110 }
86 111
87 - // Prevent any meta with null values from being logged
112 + // Prevent any meta with null values from being logged.
88 113 $stream_meta = array_filter(
89 114 $args,
90 - function( $var ) {
115 + function ( $var ) {
91 116 return ! is_null( $var );
92 117 }
93 118 );
94 119
95 - // Add user meta to Stream meta
120 + // Add user meta to Stream meta.
96 121 $stream_meta['user_meta'] = $user_meta;
97 122
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
107 - $iso_8601_extended_date = wp_stream_get_iso_8601_extended_date();
108 -
109 123 if ( ! empty( $user->roles ) ) {
110 124 $roles = array_values( $user->roles );
111 125 $role = $roles[0];
112 126 } elseif ( is_multisite() && is_super_admin() && $wp_roles->is_role( 'administrator' ) ) {
@@ -115,20 +129,20 @@
115 129 $role = '';
116 130 }
117 131
118 132 $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,
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,
131 145 );
132 146
133 147 if ( 0 === $recordarr['object_id'] ) {
134 148 unset( $recordarr['object_id'] );
@@ -135,31 +149,34 @@
135 149 }
136 150
137 151 $result = $this->plugin->db->insert( $recordarr );
138 152
139 - $this->debug_backtrace( $recordarr );
153 + // This is helpful in development environments:
154 + // error_log( $this->debug_backtrace( $recordarr ) );.
140 155
141 156 return $result;
142 157 }
143 158
144 159 /**
145 - * 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.
146 161 *
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
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.
152 167 *
153 168 * @return bool
154 169 */
155 170 public function is_record_excluded( $connector, $context, $action, $user = null, $ip = null ) {
171 + $exclude_record = false;
172 +
156 173 if ( is_null( $user ) ) {
157 174 $user = wp_get_current_user();
158 175 }
159 176
160 177 if ( is_null( $ip ) ) {
161 - $ip = wp_stream_filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP );
178 + $ip = $this->ip_address;
162 179 } else {
163 180 $ip = wp_stream_filter_var( $ip, FILTER_VALIDATE_IP );
164 181 }
165 182
@@ -164,9 +181,9 @@
164 181 }
165 182
166 183 if ( ! empty( $user->roles ) ) {
167 184 $roles = array_values( $user->roles );
168 - $role = $roles[0];
185 + $role = $roles[0];
169 186 } else {
170 187 $role = '';
171 188 }
172 189 $record = array(
@@ -179,96 +196,126 @@
179 196 );
180 197
181 198 $exclude_settings = isset( $this->plugin->settings->options['exclude_rules'] ) ? $this->plugin->settings->options['exclude_rules'] : array();
182 199
183 - if ( is_multisite() && is_plugin_active_for_network( $this->plugin->locations['plugin'] ) && ! is_network_admin() ) {
200 + if ( is_multisite() && $this->plugin->is_network_activated() && ! is_network_admin() ) {
184 201 $multisite_options = (array) get_site_option( 'wp_stream_network', array() );
185 - $multisite_exclude_settings = isset( $multisite_options['exclude_rules'] ) ? $multisite_options['exclude_rules'] : array();
202 + $exclude_settings = isset( $multisite_options['exclude_rules'] ) ? $multisite_options['exclude_rules'] : array();
203 + }
186 204
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 ];
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 + );
214 +
215 + $exclude_rules = array_filter( $exclude, 'strlen' );
216 +
217 + if ( $this->record_matches_rules( $record, $exclude_rules ) ) {
218 + $exclude_record = true;
219 + break;
220 + }
221 + }
222 +
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 + }
235 +
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++;
195 257 }
258 + } elseif ( $record[ $exclude_key ] === $exclude_value ) {
259 + $matches_found++;
196 260 }
197 261 }
198 262
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 ] : '';
263 + return $matches_found === $matches_needed;
264 + }
207 265
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 - );
266 + /**
267 + * Get all exclude rules by row because we store them by rule instead.
268 + *
269 + * @param array $rules List of rules indexed by rule ID.
270 + *
271 + * @return array
272 + */
273 + public function exclude_rules_by_rows( $rules ) {
274 + $excludes = array();
216 275
217 - $exclude_rules = array_filter( $exclude, 'strlen' );
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 + );
218 285
219 - if ( ! empty( $exclude_rules ) ) {
220 - $excluded = true;
286 + if ( empty( $rules['exclude_row'] ) ) {
287 + return array();
288 + }
221 289
222 - foreach ( $exclude_rules as $exclude_key => $exclude_value ) {
223 - if ( $record[ $exclude_key ] !== $exclude_value ) {
224 - $excluded = false;
225 - break;
226 - }
227 - }
290 + foreach ( array_keys( $rules['exclude_row'] ) as $row_id ) {
291 + $excludes[ $row_id ] = array();
228 292
229 - if ( $excluded ) {
230 - return true;
231 - }
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;
232 298 }
233 299 }
234 300 }
235 301
236 - return false;
302 + return $excludes;
237 303 }
238 304
239 305 /**
240 - * Send a full backtrace of calls to the PHP error log for debugging
306 + * Helper function to send a full backtrace of calls to the PHP error log for debugging
241 307 *
242 - * @param array $recordarr
308 + * @param array $recordarr Record argument array.
243 309 *
244 - * @return void
310 + * @return string
245 311 */
246 312 public function debug_backtrace( $recordarr ) {
247 - /**
248 - * Enable debug backtrace on records.
249 - *
250 - * This filter is for developer use only. When enabled, Stream will send
251 - * a full debug backtrace of PHP calls for each record. Optionally, you may
252 - * use the available $recordarr parameter to specify what types of records to
253 - * create backtrace logs for.
254 - *
255 - * @param array $recordarr
256 - *
257 - * @return bool Set to FALSE by default (backtrace disabled)
258 - */
259 - $enabled = apply_filters( 'wp_stream_debug_backtrace', false, $recordarr );
260 -
261 - if ( ! $enabled ) {
262 - return;
263 - }
264 -
265 313 if ( version_compare( PHP_VERSION, '5.3.6', '<' ) ) {
266 - error_log( 'WP Stream debug backtrace requires at least PHP 5.3.6' );
267 - return;
314 + return __( 'Debug backtrace requires at least PHP 5.3.6', 'wp_stream' );
268 315 }
269 316
270 - // Record details
317 + // Record details.
271 318 $summary = isset( $recordarr['summary'] ) ? $recordarr['summary'] : null;
272 319 $author = isset( $recordarr['author'] ) ? $recordarr['author'] : null;
273 320 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
274 321 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
@@ -273,33 +320,38 @@
273 320 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
274 321 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
275 322 $action = isset( $recordarr['action'] ) ? $recordarr['action'] : null;
276 323
277 - // Stream meta
324 + // Stream meta.
278 325 $stream_meta = isset( $recordarr['meta'] ) ? $recordarr['meta'] : null;
279 326
280 327 unset( $stream_meta['user_meta'] );
281 328
282 329 if ( $stream_meta ) {
283 - array_walk( $stream_meta, function( &$value, $key ) {
284 - $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
285 - });
286 -
330 + array_walk(
331 + $stream_meta,
332 + function ( &$value, $key ) {
333 + $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
334 + }
335 + );
287 336 $stream_meta = implode( ', ', $stream_meta );
288 337 }
289 338
290 - // User meta
339 + // User meta.
291 340 $user_meta = isset( $recordarr['meta']['user_meta'] ) ? $recordarr['meta']['user_meta'] : null;
292 341
293 342 if ( $user_meta ) {
294 - array_walk( $user_meta, function( &$value, $key ) {
295 - $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
296 - });
343 + array_walk(
344 + $user_meta,
345 + function ( &$value, $key ) {
346 + $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
347 + }
348 + );
297 349
298 350 $user_meta = implode( ', ', $user_meta );
299 351 }
300 352
301 - // Debug backtrace
353 + // Debug backtrace.
302 354 ob_start();
303 355
304 356 // @codingStandardsIgnoreStart
305 357 debug_print_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // Option to ignore args requires PHP 5.3.6
@@ -319,7 +371,7 @@
319 371 $user_meta,
320 372 implode( "\n", $backtrace )
321 373 );
322 374
323 - error_log( $output );
375 + return $output;
324 376 }
325 377 }