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