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 -127 3.13.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,102 +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 ( '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 - }
290 + foreach ( array_keys( $rules['exclude_row'] ) as $row_id ) {
291 + $excludes[ $row_id ] = array();
234 292
235 - if ( $excluded ) {
236 - return true;
237 - }
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;
238 298 }
239 299 }
240 300 }
241 301
242 - return false;
302 + return $excludes;
243 303 }
244 304
245 305 /**
246 - * 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
247 307 *
248 - * @param array $recordarr
308 + * @param array $recordarr Record argument array.
249 309 *
250 - * @return void
310 + * @return string
251 311 */
252 312 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 313 if ( version_compare( PHP_VERSION, '5.3.6', '<' ) ) {
272 - error_log( 'WP Stream debug backtrace requires at least PHP 5.3.6' );
273 - return;
314 + return __( 'Debug backtrace requires at least PHP 5.3.6', 'wp_stream' );
274 315 }
275 316
276 - // Record details
317 + // Record details.
277 318 $summary = isset( $recordarr['summary'] ) ? $recordarr['summary'] : null;
278 319 $author = isset( $recordarr['author'] ) ? $recordarr['author'] : null;
279 320 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
280 321 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
@@ -279,33 +320,38 @@
279 320 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
280 321 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
281 322 $action = isset( $recordarr['action'] ) ? $recordarr['action'] : null;
282 323
283 - // Stream meta
324 + // Stream meta.
284 325 $stream_meta = isset( $recordarr['meta'] ) ? $recordarr['meta'] : null;
285 326
286 327 unset( $stream_meta['user_meta'] );
287 328
288 329 if ( $stream_meta ) {
289 - array_walk( $stream_meta, function( &$value, $key ) {
290 - $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
291 - });
292 -
330 + array_walk(
331 + $stream_meta,
332 + function ( &$value, $key ) {
333 + $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
334 + }
335 + );
293 336 $stream_meta = implode( ', ', $stream_meta );
294 337 }
295 338
296 - // User meta
339 + // User meta.
297 340 $user_meta = isset( $recordarr['meta']['user_meta'] ) ? $recordarr['meta']['user_meta'] : null;
298 341
299 342 if ( $user_meta ) {
300 - array_walk( $user_meta, function( &$value, $key ) {
301 - $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
302 - });
343 + array_walk(
344 + $user_meta,
345 + function ( &$value, $key ) {
346 + $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
347 + }
348 + );
303 349
304 350 $user_meta = implode( ', ', $user_meta );
305 351 }
306 352
307 - // Debug backtrace
353 + // Debug backtrace.
308 354 ob_start();
309 355
310 356 // @codingStandardsIgnoreStart
311 357 debug_print_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // Option to ignore args requires PHP 5.3.6
@@ -325,7 +371,7 @@
325 371 $user_meta,
326 372 implode( "\n", $backtrace )
327 373 );
328 374
329 - error_log( $output );
375 + return $output;
330 376 }
331 377 }