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 +192 -120 3.0.33.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,43 +108,41 @@
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
117 147 if ( 0 === $recordarr['object_id'] ) {
118 148 unset( $recordarr['object_id'] );
@@ -119,136 +149,173 @@
119 149 }
120 150
121 151 $result = $this->plugin->db->insert( $recordarr );
122 152
123 - $this->debug_backtrace( $recordarr );
153 + // This is helpful in development environments:
154 + // error_log( $this->debug_backtrace( $recordarr ) );.
124 155
125 156 return $result;
126 157 }
127 158
128 159 /**
129 - * 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.
130 161 *
131 - * @param string $connector Name of the connector being logged
132 - * @param string $context Name of the context being logged
133 - * @param string $action Name of the action being logged
134 - * @param \WP_User $user The user being logged
135 - * @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.
136 167 *
137 168 * @return bool
138 169 */
139 170 public function is_record_excluded( $connector, $context, $action, $user = null, $ip = null ) {
171 + $exclude_record = false;
172 +
140 173 if ( is_null( $user ) ) {
141 174 $user = wp_get_current_user();
142 175 }
143 176
144 177 if ( is_null( $ip ) ) {
145 - $ip = wp_stream_filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP );
178 + $ip = $this->ip_address;
146 179 } else {
147 180 $ip = wp_stream_filter_var( $ip, FILTER_VALIDATE_IP );
148 181 }
149 182
150 - $user_role = isset( $user->roles[0] ) ? $user->roles[0] : null;
151 -
183 + if ( ! empty( $user->roles ) ) {
184 + $roles = array_values( $user->roles );
185 + $role = $roles[0];
186 + } else {
187 + $role = '';
188 + }
152 189 $record = array(
153 190 'connector' => $connector,
154 191 'context' => $context,
155 192 'action' => $action,
156 193 'author' => $user->ID,
157 - 'role' => $user_role,
194 + 'role' => $role,
158 195 'ip_address' => $ip,
159 196 );
160 197
161 198 $exclude_settings = isset( $this->plugin->settings->options['exclude_rules'] ) ? $this->plugin->settings->options['exclude_rules'] : array();
162 199
163 - 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() ) {
164 201 $multisite_options = (array) get_site_option( 'wp_stream_network', array() );
165 - $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 + }
166 204
167 - if ( ! empty( $multisite_exclude_settings ) ) {
168 - foreach ( $multisite_exclude_settings['exclude_row'] as $key => $rule ) {
169 - $exclude_settings['exclude_row'][] = $multisite_exclude_settings['exclude_row'][ $key ];
170 - $exclude_settings['author_or_role'][] = $multisite_exclude_settings['author_or_role'][ $key ];
171 - $exclude_settings['connector'][] = $multisite_exclude_settings['connector'][ $key ];
172 - $exclude_settings['context'][] = $multisite_exclude_settings['context'][ $key ];
173 - $exclude_settings['action'][] = $multisite_exclude_settings['action'][ $key ];
174 - $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++;
175 257 }
258 + } elseif ( $record[ $exclude_key ] === $exclude_value ) {
259 + $matches_found++;
176 260 }
177 261 }
178 262
179 - if ( isset( $exclude_settings['exclude_row'] ) && ! empty( $exclude_settings['exclude_row'] ) ) {
180 - foreach ( $exclude_settings['exclude_row'] as $key => $value ) {
181 - // Prepare values
182 - $author_or_role = isset( $exclude_settings['author_or_role'][ $key ] ) ? $exclude_settings['author_or_role'][ $key ] : '';
183 - $connector = isset( $exclude_settings['connector'][ $key ] ) ? $exclude_settings['connector'][ $key ] : '';
184 - $context = isset( $exclude_settings['context'][ $key ] ) ? $exclude_settings['context'][ $key ] : '';
185 - $action = isset( $exclude_settings['action'][ $key ] ) ? $exclude_settings['action'][ $key ] : '';
186 - $ip_address = isset( $exclude_settings['ip_address'][ $key ] ) ? $exclude_settings['ip_address'][ $key ] : '';
263 + return $matches_found === $matches_needed;
264 + }
187 265
188 - $exclude = array(
189 - 'connector' => ! empty( $connector ) ? $connector : null,
190 - 'context' => ! empty( $context ) ? $context : null,
191 - 'action' => ! empty( $action ) ? $action : null,
192 - 'ip_address' => ! empty( $ip_address ) ? $ip_address : null,
193 - 'author' => is_numeric( $author_or_role ) ? absint( $author_or_role ) : null,
194 - 'role' => ( ! empty( $author_or_role ) && ! is_numeric( $author_or_role ) ) ? $author_or_role : null,
195 - );
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();
196 275
197 - $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 + );
198 285
199 - if ( ! empty( $exclude_rules ) ) {
200 - $excluded = true;
286 + if ( empty( $rules['exclude_row'] ) ) {
287 + return array();
288 + }
201 289
202 - foreach ( $exclude_rules as $exclude_key => $exclude_value ) {
203 - if ( $record[ $exclude_key ] !== $exclude_value ) {
204 - $excluded = false;
205 - break;
206 - }
207 - }
290 + foreach ( array_keys( $rules['exclude_row'] ) as $row_id ) {
291 + $excludes[ $row_id ] = array();
208 292
209 - if ( $excluded ) {
210 - return true;
211 - }
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;
212 298 }
213 299 }
214 300 }
215 301
216 - return false;
302 + return $excludes;
217 303 }
218 304
219 305 /**
220 - * 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
221 307 *
222 - * @param array $recordarr
308 + * @param array $recordarr Record argument array.
223 309 *
224 - * @return void
310 + * @return string
225 311 */
226 312 public function debug_backtrace( $recordarr ) {
227 - /**
228 - * Enable debug backtrace on records.
229 - *
230 - * This filter is for developer use only. When enabled, Stream will send
231 - * a full debug backtrace of PHP calls for each record. Optionally, you may
232 - * use the available $recordarr parameter to specify what types of records to
233 - * create backtrace logs for.
234 - *
235 - * @param array $recordarr
236 - *
237 - * @return bool Set to FALSE by default (backtrace disabled)
238 - */
239 - $enabled = apply_filters( 'wp_stream_debug_backtrace', false, $recordarr );
240 -
241 - if ( ! $enabled ) {
242 - return;
243 - }
244 -
245 313 if ( version_compare( PHP_VERSION, '5.3.6', '<' ) ) {
246 - error_log( 'WP Stream debug backtrace requires at least PHP 5.3.6' );
247 - return;
314 + return __( 'Debug backtrace requires at least PHP 5.3.6', 'wp_stream' );
248 315 }
249 316
250 - // Record details
317 + // Record details.
251 318 $summary = isset( $recordarr['summary'] ) ? $recordarr['summary'] : null;
252 319 $author = isset( $recordarr['author'] ) ? $recordarr['author'] : null;
253 320 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
254 321 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
@@ -253,33 +320,38 @@
253 320 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
254 321 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
255 322 $action = isset( $recordarr['action'] ) ? $recordarr['action'] : null;
256 323
257 - // Stream meta
324 + // Stream meta.
258 325 $stream_meta = isset( $recordarr['meta'] ) ? $recordarr['meta'] : null;
259 326
260 327 unset( $stream_meta['user_meta'] );
261 328
262 329 if ( $stream_meta ) {
263 - array_walk( $stream_meta, function( &$value, $key ) {
264 - $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
265 - });
266 -
330 + array_walk(
331 + $stream_meta,
332 + function ( &$value, $key ) {
333 + $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
334 + }
335 + );
267 336 $stream_meta = implode( ', ', $stream_meta );
268 337 }
269 338
270 - // User meta
339 + // User meta.
271 340 $user_meta = isset( $recordarr['meta']['user_meta'] ) ? $recordarr['meta']['user_meta'] : null;
272 341
273 342 if ( $user_meta ) {
274 - array_walk( $user_meta, function( &$value, $key ) {
275 - $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
276 - });
343 + array_walk(
344 + $user_meta,
345 + function ( &$value, $key ) {
346 + $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
347 + }
348 + );
277 349
278 350 $user_meta = implode( ', ', $user_meta );
279 351 }
280 352
281 - // Debug backtrace
353 + // Debug backtrace.
282 354 ob_start();
283 355
284 356 // @codingStandardsIgnoreStart
285 357 debug_print_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // Option to ignore args requires PHP 5.3.6
@@ -299,7 +371,7 @@
299 371 $user_meta,
300 372 implode( "\n", $backtrace )
301 373 );
302 374
303 - error_log( $output );
375 + return $output;
304 376 }
305 377 }