PluginProbe
Stream – Activity Log & Audit Trail / 3.8.0
Stream – Activity Log & Audit Trail v3.8.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 +189 -122 3.0.43.8.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,19 +56,21 @@
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 ) {
71 + global $wp_roles;
72 +
46 73 if ( is_null( $user_id ) ) {
47 74 $user_id = get_current_user_id();
48 75 }
49 76
@@ -54,9 +81,9 @@
54 81 $wp_cron_tracking = isset( $this->plugin->settings->options['advanced_wp_cron_tracking'] ) ? $this->plugin->settings->options['advanced_wp_cron_tracking'] : false;
55 82 $author = new Author( $user_id );
56 83 $agent = $author->get_current_agent();
57 84
58 - // 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.
59 86 if ( ! $wp_cron_tracking && 'wp_cron' === $agent ) {
60 87 return false;
61 88 }
62 89
@@ -81,43 +108,41 @@
81 108 $user_meta['system_user_id'] = (int) $uid;
82 109 $user_meta['system_user_name'] = (string) $user_info['name'];
83 110 }
84 111
85 - // Prevent any meta with null values from being logged
112 + // Prevent any meta with null values from being logged.
86 113 $stream_meta = array_filter(
87 114 $args,
88 - function( $var ) {
115 + function ( $var ) {
89 116 return ! is_null( $var );
90 117 }
91 118 );
92 119
93 - // Add user meta to Stream meta
120 + // Add user meta to Stream meta.
94 121 $stream_meta['user_meta'] = $user_meta;
95 122
96 - // All meta must be strings, so we will serialize any array meta values
97 - array_walk(
98 - $stream_meta,
99 - function( &$v ) {
100 - $v = (string) maybe_serialize( $v );
101 - }
102 - );
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 + }
103 131
104 - // Get the current time in milliseconds
105 - $iso_8601_extended_date = wp_stream_get_iso_8601_extended_date();
106 -
107 132 $recordarr = array(
108 - 'object_id' => (int) $object_id,
109 - 'site_id' => (int) is_multisite() ? get_current_site()->id : 1,
110 - 'blog_id' => (int) apply_filters( 'wp_stream_blog_id_logged', get_current_blog_id() ),
111 - 'user_id' => (int) $user_id,
112 - 'user_role' => (string) ! empty( $user->roles ) ? $user->roles[0] : '',
113 - 'created' => (string) $iso_8601_extended_date,
114 - 'summary' => (string) vsprintf( $message, $args ),
115 - 'connector' => (string) $connector,
116 - 'context' => (string) $context,
117 - 'action' => (string) $action,
118 - 'ip' => (string) wp_stream_filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP ),
119 - '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,
120 145 );
121 146
122 147 if ( 0 === $recordarr['object_id'] ) {
123 148 unset( $recordarr['object_id'] );
@@ -124,136 +149,173 @@
124 149 }
125 150
126 151 $result = $this->plugin->db->insert( $recordarr );
127 152
128 - $this->debug_backtrace( $recordarr );
153 + // This is helpful in development environments:
154 + // error_log( $this->debug_backtrace( $recordarr ) );.
129 155
130 156 return $result;
131 157 }
132 158
133 159 /**
134 - * 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.
135 161 *
136 - * @param string $connector Name of the connector being logged
137 - * @param string $context Name of the context being logged
138 - * @param string $action Name of the action being logged
139 - * @param \WP_User $user The user being logged
140 - * @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.
141 167 *
142 168 * @return bool
143 169 */
144 170 public function is_record_excluded( $connector, $context, $action, $user = null, $ip = null ) {
171 + $exclude_record = false;
172 +
145 173 if ( is_null( $user ) ) {
146 174 $user = wp_get_current_user();
147 175 }
148 176
149 177 if ( is_null( $ip ) ) {
150 - $ip = wp_stream_filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP );
178 + $ip = $this->ip_address;
151 179 } else {
152 180 $ip = wp_stream_filter_var( $ip, FILTER_VALIDATE_IP );
153 181 }
154 182
155 - $user_role = isset( $user->roles[0] ) ? $user->roles[0] : null;
156 -
183 + if ( ! empty( $user->roles ) ) {
184 + $roles = array_values( $user->roles );
185 + $role = $roles[0];
186 + } else {
187 + $role = '';
188 + }
157 189 $record = array(
158 190 'connector' => $connector,
159 191 'context' => $context,
160 192 'action' => $action,
161 193 'author' => $user->ID,
162 - 'role' => $user_role,
194 + 'role' => $role,
163 195 'ip_address' => $ip,
164 196 );
165 197
166 198 $exclude_settings = isset( $this->plugin->settings->options['exclude_rules'] ) ? $this->plugin->settings->options['exclude_rules'] : array();
167 199
168 - 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() ) {
169 201 $multisite_options = (array) get_site_option( 'wp_stream_network', array() );
170 - $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 + }
171 204
172 - if ( ! empty( $multisite_exclude_settings ) ) {
173 - foreach ( $multisite_exclude_settings['exclude_row'] as $key => $rule ) {
174 - $exclude_settings['exclude_row'][] = $multisite_exclude_settings['exclude_row'][ $key ];
175 - $exclude_settings['author_or_role'][] = $multisite_exclude_settings['author_or_role'][ $key ];
176 - $exclude_settings['connector'][] = $multisite_exclude_settings['connector'][ $key ];
177 - $exclude_settings['context'][] = $multisite_exclude_settings['context'][ $key ];
178 - $exclude_settings['action'][] = $multisite_exclude_settings['action'][ $key ];
179 - $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++;
180 257 }
258 + } elseif ( $record[ $exclude_key ] === $exclude_value ) {
259 + $matches_found++;
181 260 }
182 261 }
183 262
184 - if ( isset( $exclude_settings['exclude_row'] ) && ! empty( $exclude_settings['exclude_row'] ) ) {
185 - foreach ( $exclude_settings['exclude_row'] as $key => $value ) {
186 - // Prepare values
187 - $author_or_role = isset( $exclude_settings['author_or_role'][ $key ] ) ? $exclude_settings['author_or_role'][ $key ] : '';
188 - $connector = isset( $exclude_settings['connector'][ $key ] ) ? $exclude_settings['connector'][ $key ] : '';
189 - $context = isset( $exclude_settings['context'][ $key ] ) ? $exclude_settings['context'][ $key ] : '';
190 - $action = isset( $exclude_settings['action'][ $key ] ) ? $exclude_settings['action'][ $key ] : '';
191 - $ip_address = isset( $exclude_settings['ip_address'][ $key ] ) ? $exclude_settings['ip_address'][ $key ] : '';
263 + return $matches_found === $matches_needed;
264 + }
192 265
193 - $exclude = array(
194 - 'connector' => ! empty( $connector ) ? $connector : null,
195 - 'context' => ! empty( $context ) ? $context : null,
196 - 'action' => ! empty( $action ) ? $action : null,
197 - 'ip_address' => ! empty( $ip_address ) ? $ip_address : null,
198 - 'author' => is_numeric( $author_or_role ) ? absint( $author_or_role ) : null,
199 - 'role' => ( ! empty( $author_or_role ) && ! is_numeric( $author_or_role ) ) ? $author_or_role : null,
200 - );
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();
201 275
202 - $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 + );
203 285
204 - if ( ! empty( $exclude_rules ) ) {
205 - $excluded = true;
286 + if ( empty( $rules['exclude_row'] ) ) {
287 + return array();
288 + }
206 289
207 - foreach ( $exclude_rules as $exclude_key => $exclude_value ) {
208 - if ( $record[ $exclude_key ] !== $exclude_value ) {
209 - $excluded = false;
210 - break;
211 - }
212 - }
290 + foreach ( array_keys( $rules['exclude_row'] ) as $row_id ) {
291 + $excludes[ $row_id ] = array();
213 292
214 - if ( $excluded ) {
215 - return true;
216 - }
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;
217 298 }
218 299 }
219 300 }
220 301
221 - return false;
302 + return $excludes;
222 303 }
223 304
224 305 /**
225 - * 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
226 307 *
227 - * @param array $recordarr
308 + * @param array $recordarr Record argument array.
228 309 *
229 - * @return void
310 + * @return string
230 311 */
231 312 public function debug_backtrace( $recordarr ) {
232 - /**
233 - * Enable debug backtrace on records.
234 - *
235 - * This filter is for developer use only. When enabled, Stream will send
236 - * a full debug backtrace of PHP calls for each record. Optionally, you may
237 - * use the available $recordarr parameter to specify what types of records to
238 - * create backtrace logs for.
239 - *
240 - * @param array $recordarr
241 - *
242 - * @return bool Set to FALSE by default (backtrace disabled)
243 - */
244 - $enabled = apply_filters( 'wp_stream_debug_backtrace', false, $recordarr );
245 -
246 - if ( ! $enabled ) {
247 - return;
248 - }
249 -
250 313 if ( version_compare( PHP_VERSION, '5.3.6', '<' ) ) {
251 - error_log( 'WP Stream debug backtrace requires at least PHP 5.3.6' );
252 - return;
314 + return __( 'Debug backtrace requires at least PHP 5.3.6', 'wp_stream' );
253 315 }
254 316
255 - // Record details
317 + // Record details.
256 318 $summary = isset( $recordarr['summary'] ) ? $recordarr['summary'] : null;
257 319 $author = isset( $recordarr['author'] ) ? $recordarr['author'] : null;
258 320 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
259 321 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
@@ -258,33 +320,38 @@
258 320 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
259 321 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
260 322 $action = isset( $recordarr['action'] ) ? $recordarr['action'] : null;
261 323
262 - // Stream meta
324 + // Stream meta.
263 325 $stream_meta = isset( $recordarr['meta'] ) ? $recordarr['meta'] : null;
264 326
265 327 unset( $stream_meta['user_meta'] );
266 328
267 329 if ( $stream_meta ) {
268 - array_walk( $stream_meta, function( &$value, $key ) {
269 - $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
270 - });
271 -
330 + array_walk(
331 + $stream_meta,
332 + function ( &$value, $key ) {
333 + $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
334 + }
335 + );
272 336 $stream_meta = implode( ', ', $stream_meta );
273 337 }
274 338
275 - // User meta
339 + // User meta.
276 340 $user_meta = isset( $recordarr['meta']['user_meta'] ) ? $recordarr['meta']['user_meta'] : null;
277 341
278 342 if ( $user_meta ) {
279 - array_walk( $user_meta, function( &$value, $key ) {
280 - $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
281 - });
343 + array_walk(
344 + $user_meta,
345 + function ( &$value, $key ) {
346 + $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
347 + }
348 + );
282 349
283 350 $user_meta = implode( ', ', $user_meta );
284 351 }
285 352
286 - // Debug backtrace
353 + // Debug backtrace.
287 354 ob_start();
288 355
289 356 // @codingStandardsIgnoreStart
290 357 debug_print_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // Option to ignore args requires PHP 5.3.6
@@ -304,7 +371,7 @@
304 371 $user_meta,
305 372 implode( "\n", $backtrace )
306 373 );
307 374
308 - error_log( $output );
375 + return $output;
309 376 }
310 377 }