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 +189 -104 3.0.23.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 */
@@ -21,24 +32,37 @@
21 32 * @param Plugin $plugin The main Plugin class.
22 33 */
23 34 public function __construct( $plugin ) {
24 35 $this->plugin = $plugin;
36 +
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.
44 + if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
45 + require_once ABSPATH . '/wp-admin/includes/plugin.php';
46 + }
25 47 }
26 48
27 49 /**
28 50 * Log handler
29 51 *
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
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.
37 59 *
38 60 * @return mixed True if updated, otherwise false|WP_Error
39 61 */
40 62 public function log( $connector, $message, $args, $object_id, $context, $action, $user_id = null ) {
63 + global $wp_roles;
64 +
41 65 if ( is_null( $user_id ) ) {
42 66 $user_id = get_current_user_id();
43 67 }
44 68
@@ -49,9 +73,9 @@
49 73 $wp_cron_tracking = isset( $this->plugin->settings->options['advanced_wp_cron_tracking'] ) ? $this->plugin->settings->options['advanced_wp_cron_tracking'] : false;
50 74 $author = new Author( $user_id );
51 75 $agent = $author->get_current_agent();
52 76
53 - // 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.
54 78 if ( ! $wp_cron_tracking && 'wp_cron' === $agent ) {
55 79 return false;
56 80 }
57 81
@@ -76,121 +100,157 @@
76 100 $user_meta['system_user_id'] = (int) $uid;
77 101 $user_meta['system_user_name'] = (string) $user_info['name'];
78 102 }
79 103
80 - // Prevent any meta with null values from being logged
104 + // Prevent any meta with null values from being logged.
81 105 $stream_meta = array_filter(
82 106 $args,
83 - function( $var ) {
107 + function ( $var ) {
84 108 return ! is_null( $var );
85 109 }
86 110 );
87 111
88 - // Add user meta to Stream meta
112 + // Add user meta to Stream meta.
89 113 $stream_meta['user_meta'] = $user_meta;
90 114
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 - );
115 + // Get the current time in milliseconds.
116 + $iso_8601_extended_date = wp_stream_get_iso_8601_extended_date();
98 117
99 - // Get the current time in milliseconds
100 - $iso_8601_extended_date = wp_stream_get_iso_8601_extended_date();
118 + if ( ! empty( $user->roles ) ) {
119 + $roles = array_values( $user->roles );
120 + $role = $roles[0];
121 + } elseif ( is_multisite() && is_super_admin() && $wp_roles->is_role( 'administrator' ) ) {
122 + $role = 'administrator';
123 + } else {
124 + $role = '';
125 + }
101 126
102 127 $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,
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,
115 140 );
116 141
142 + if ( 0 === $recordarr['object_id'] ) {
143 + unset( $recordarr['object_id'] );
144 + }
145 +
117 146 $result = $this->plugin->db->insert( $recordarr );
118 147
119 - $this->debug_backtrace( $recordarr );
148 + // This is helpful in development environments:
149 + // error_log( $this->debug_backtrace( $recordarr ) );
120 150
121 151 return $result;
122 152 }
123 153
124 154 /**
125 - * 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.
126 156 *
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
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.
132 162 *
133 163 * @return bool
134 164 */
135 165 public function is_record_excluded( $connector, $context, $action, $user = null, $ip = null ) {
166 + $exclude_record = false;
167 +
136 168 if ( is_null( $user ) ) {
137 169 $user = wp_get_current_user();
138 170 }
139 171
140 172 if ( is_null( $ip ) ) {
141 - $ip = wp_stream_filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP );
173 + $ip = $this->ip_address;
142 174 } else {
143 175 $ip = wp_stream_filter_var( $ip, FILTER_VALIDATE_IP );
144 176 }
145 177
146 - $user_role = isset( $user->roles[0] ) ? $user->roles[0] : null;
147 -
178 + if ( ! empty( $user->roles ) ) {
179 + $roles = array_values( $user->roles );
180 + $role = $roles[0];
181 + } else {
182 + $role = '';
183 + }
148 184 $record = array(
149 185 'connector' => $connector,
150 186 'context' => $context,
151 187 'action' => $action,
152 188 'author' => $user->ID,
153 - 'role' => $user_role,
189 + 'role' => $role,
154 190 'ip_address' => $ip,
155 191 );
156 192
157 193 $exclude_settings = isset( $this->plugin->settings->options['exclude_rules'] ) ? $this->plugin->settings->options['exclude_rules'] : array();
158 194
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 ] : '';
195 + if ( is_multisite() && $this->plugin->is_network_activated() && ! is_network_admin() ) {
196 + $multisite_options = (array) get_site_option( 'wp_stream_network', array() );
197 + $exclude_settings = isset( $multisite_options['exclude_rules'] ) ? $multisite_options['exclude_rules'] : array();
198 + }
167 199
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 - );
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 + );
176 209
177 - $exclude_rules = array_filter( $exclude, 'strlen' );
210 + $exclude_rules = array_filter( $exclude, 'strlen' );
178 211
179 - if ( ! empty( $exclude_rules ) ) {
180 - $excluded = true;
212 + if ( $this->record_matches_rules( $record, $exclude_rules ) ) {
213 + $exclude_record = true;
214 + break;
215 + }
216 + }
181 217
182 - foreach ( $exclude_rules as $exclude_key => $exclude_value ) {
183 - if ( $record[ $exclude_key ] !== $exclude_value ) {
184 - $excluded = false;
185 - break;
186 - }
187 - }
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 + }
188 230
189 - if ( $excluded ) {
190 - return true;
191 - }
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;
192 250 }
251 + } elseif ( $record[ $exclude_key ] === $exclude_value ) {
252 + return true;
193 253 }
194 254 }
195 255
196 256 return false;
@@ -196,39 +256,59 @@
196 256 return false;
197 257 }
198 258
199 259 /**
200 - * Send a full backtrace of calls to the PHP error log for debugging
260 + * Get all exclude rules by row because we store them by rule instead.
201 261 *
202 - * @param array $recordarr
262 + * @param array $rules List of rules indexed by rule ID.
203 263 *
204 - * @return void
264 + * @return array
205 265 */
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 );
266 + public function exclude_rules_by_rows( $rules ) {
267 + $excludes = array();
220 268
221 - if ( ! $enabled ) {
222 - return;
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 + );
278 +
279 + if ( empty( $rules['exclude_row'] ) ) {
280 + return array();
223 281 }
224 282
283 + foreach ( array_keys( $rules['exclude_row'] ) as $row_id ) {
284 + $excludes[ $row_id ] = array();
285 +
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;
291 + }
292 + }
293 + }
294 +
295 + return $excludes;
296 + }
297 +
298 + /**
299 + * Helper function to send a full backtrace of calls to the PHP error log for debugging
300 + *
301 + * @param array $recordarr Record argument array.
302 + *
303 + * @return string
304 + */
305 + public function debug_backtrace( $recordarr ) {
225 306 if ( version_compare( PHP_VERSION, '5.3.6', '<' ) ) {
226 - error_log( 'WP Stream debug backtrace requires at least PHP 5.3.6' );
227 - return;
307 + return __( 'Debug backtrace requires at least PHP 5.3.6', 'wp_stream' );
228 308 }
229 309
230 - // Record details
310 + // Record details.
231 311 $summary = isset( $recordarr['summary'] ) ? $recordarr['summary'] : null;
232 312 $author = isset( $recordarr['author'] ) ? $recordarr['author'] : null;
233 313 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
234 314 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
@@ -233,33 +313,38 @@
233 313 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
234 314 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
235 315 $action = isset( $recordarr['action'] ) ? $recordarr['action'] : null;
236 316
237 - // Stream meta
317 + // Stream meta.
238 318 $stream_meta = isset( $recordarr['meta'] ) ? $recordarr['meta'] : null;
239 319
240 320 unset( $stream_meta['user_meta'] );
241 321
242 322 if ( $stream_meta ) {
243 - array_walk( $stream_meta, function( &$value, $key ) {
244 - $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
245 - });
246 -
323 + array_walk(
324 + $stream_meta,
325 + function ( &$value, $key ) {
326 + $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
327 + }
328 + );
247 329 $stream_meta = implode( ', ', $stream_meta );
248 330 }
249 331
250 - // User meta
332 + // User meta.
251 333 $user_meta = isset( $recordarr['meta']['user_meta'] ) ? $recordarr['meta']['user_meta'] : null;
252 334
253 335 if ( $user_meta ) {
254 - array_walk( $user_meta, function( &$value, $key ) {
255 - $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
256 - });
336 + array_walk(
337 + $user_meta,
338 + function ( &$value, $key ) {
339 + $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
340 + }
341 + );
257 342
258 343 $user_meta = implode( ', ', $user_meta );
259 344 }
260 345
261 - // Debug backtrace
346 + // Debug backtrace.
262 347 ob_start();
263 348
264 349 // @codingStandardsIgnoreStart
265 350 debug_print_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // Option to ignore args requires PHP 5.3.6
@@ -279,7 +364,7 @@
279 364 $user_meta,
280 365 implode( "\n", $backtrace )
281 366 );
282 367
283 - error_log( $output );
368 + return $output;
284 369 }
285 370 }