PluginProbe
Stream – Activity Log & Audit Trail / 3.6.1
Stream – Activity Log & Audit Trail v3.6.1
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 +196 -106 3.0.03.6.1 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,9 +108,9 @@
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 115 function ( $var ) {
84 116 return ! is_null( $var );
@@ -84,113 +116,146 @@
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
147 + if ( 0 === $recordarr['object_id'] ) {
148 + unset( $recordarr['object_id'] );
149 + }
150 +
117 151 $result = $this->plugin->db->insert( $recordarr );
118 152
119 - $this->debug_backtrace( $recordarr );
153 + // This is helpful in development environments:
154 + // error_log( $this->debug_backtrace( $recordarr ) );.
120 155
121 156 return $result;
122 157 }
123 158
124 159 /**
125 - * 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.
126 161 *
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
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.
132 167 *
133 168 * @return bool
134 169 */
135 170 public function is_record_excluded( $connector, $context, $action, $user = null, $ip = null ) {
171 + $exclude_record = false;
172 +
136 173 if ( is_null( $user ) ) {
137 174 $user = wp_get_current_user();
138 175 }
139 176
140 177 if ( is_null( $ip ) ) {
141 - $ip = wp_stream_filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP );
178 + $ip = $this->ip_address;
142 179 } else {
143 180 $ip = wp_stream_filter_var( $ip, FILTER_VALIDATE_IP );
144 181 }
145 182
146 - $user_role = isset( $user->roles[0] ) ? $user->roles[0] : null;
147 -
183 + if ( ! empty( $user->roles ) ) {
184 + $roles = array_values( $user->roles );
185 + $role = $roles[0];
186 + } else {
187 + $role = '';
188 + }
148 189 $record = array(
149 190 'connector' => $connector,
150 191 'context' => $context,
151 192 'action' => $action,
152 193 'author' => $user->ID,
153 - 'role' => $user_role,
194 + 'role' => $role,
154 195 'ip_address' => $ip,
155 196 );
156 197
157 198 $exclude_settings = isset( $this->plugin->settings->options['exclude_rules'] ) ? $this->plugin->settings->options['exclude_rules'] : array();
158 199
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 ] : '';
200 + if ( is_multisite() && $this->plugin->is_network_activated() && ! is_network_admin() ) {
201 + $multisite_options = (array) get_site_option( 'wp_stream_network', array() );
202 + $exclude_settings = isset( $multisite_options['exclude_rules'] ) ? $multisite_options['exclude_rules'] : array();
203 + }
167 204
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 - );
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 + );
176 214
177 - $exclude_rules = array_filter( $exclude, 'strlen' );
215 + $exclude_rules = array_filter( $exclude, 'strlen' );
178 216
179 - if ( ! empty( $exclude_rules ) ) {
180 - $excluded = true;
217 + if ( $this->record_matches_rules( $record, $exclude_rules ) ) {
218 + $exclude_record = true;
219 + break;
220 + }
221 + }
181 222
182 - foreach ( $exclude_rules as $exclude_key => $exclude_value ) {
183 - if ( $record[ $exclude_key ] !== $exclude_value ) {
184 - $excluded = false;
185 - break;
186 - }
187 - }
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 + }
188 235
189 - if ( $excluded ) {
190 - return true;
191 - }
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 + foreach ( $exclude_rules as $exclude_key => $exclude_value ) {
246 + if ( ! isset( $record[ $exclude_key ] ) ) {
247 + continue;
248 + }
249 +
250 + if ( 'ip_address' === $exclude_key ) {
251 + $ip_addresses = explode( ',', $exclude_value );
252 +
253 + if ( in_array( $record['ip_address'], $ip_addresses, true ) ) {
254 + return true;
192 255 }
256 + } elseif ( $record[ $exclude_key ] === $exclude_value ) {
257 + return true;
193 258 }
194 259 }
195 260
196 261 return false;
@@ -196,39 +261,59 @@
196 261 return false;
197 262 }
198 263
199 264 /**
200 - * Send a full backtrace of calls to the PHP error log for debugging
265 + * Get all exclude rules by row because we store them by rule instead.
201 266 *
202 - * @param array $recordarr
267 + * @param array $rules List of rules indexed by rule ID.
203 268 *
204 - * @return void
269 + * @return array
205 270 */
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 );
271 + public function exclude_rules_by_rows( $rules ) {
272 + $excludes = array();
220 273
221 - if ( ! $enabled ) {
222 - return;
274 + // TODO: Move these to where the settings are generated to ensure they're in sync.
275 + $rule_keys = array(
276 + 'exclude_row',
277 + 'author_or_role',
278 + 'connector',
279 + 'context',
280 + 'action',
281 + 'ip_address',
282 + );
283 +
284 + if ( empty( $rules['exclude_row'] ) ) {
285 + return array();
223 286 }
224 287
288 + foreach ( array_keys( $rules['exclude_row'] ) as $row_id ) {
289 + $excludes[ $row_id ] = array();
290 +
291 + foreach ( $rule_keys as $rule_key ) {
292 + if ( isset( $rules[ $rule_key ][ $row_id ] ) ) {
293 + $excludes[ $row_id ][ $rule_key ] = $rules[ $rule_key ][ $row_id ];
294 + } else {
295 + $excludes[ $row_id ][ $rule_key ] = null;
296 + }
297 + }
298 + }
299 +
300 + return $excludes;
301 + }
302 +
303 + /**
304 + * Helper function to send a full backtrace of calls to the PHP error log for debugging
305 + *
306 + * @param array $recordarr Record argument array.
307 + *
308 + * @return string
309 + */
310 + public function debug_backtrace( $recordarr ) {
225 311 if ( version_compare( PHP_VERSION, '5.3.6', '<' ) ) {
226 - error_log( 'WP Stream debug backtrace requires at least PHP 5.3.6' );
227 - return;
312 + return __( 'Debug backtrace requires at least PHP 5.3.6', 'wp_stream' );
228 313 }
229 314
230 - // Record details
315 + // Record details.
231 316 $summary = isset( $recordarr['summary'] ) ? $recordarr['summary'] : null;
232 317 $author = isset( $recordarr['author'] ) ? $recordarr['author'] : null;
233 318 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
234 319 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
@@ -233,33 +318,38 @@
233 318 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
234 319 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
235 320 $action = isset( $recordarr['action'] ) ? $recordarr['action'] : null;
236 321
237 - // Stream meta
322 + // Stream meta.
238 323 $stream_meta = isset( $recordarr['meta'] ) ? $recordarr['meta'] : null;
239 324
240 325 unset( $stream_meta['user_meta'] );
241 326
242 327 if ( $stream_meta ) {
243 - array_walk( $stream_meta, function( &$value, $key ) {
244 - $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
245 - });
246 -
328 + array_walk(
329 + $stream_meta,
330 + function ( &$value, $key ) {
331 + $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
332 + }
333 + );
247 334 $stream_meta = implode( ', ', $stream_meta );
248 335 }
249 336
250 - // User meta
337 + // User meta.
251 338 $user_meta = isset( $recordarr['meta']['user_meta'] ) ? $recordarr['meta']['user_meta'] : null;
252 339
253 340 if ( $user_meta ) {
254 - array_walk( $user_meta, function( &$value, $key ) {
255 - $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
256 - });
341 + array_walk(
342 + $user_meta,
343 + function ( &$value, $key ) {
344 + $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
345 + }
346 + );
257 347
258 348 $user_meta = implode( ', ', $user_meta );
259 349 }
260 350
261 - // Debug backtrace
351 + // Debug backtrace.
262 352 ob_start();
263 353
264 354 // @codingStandardsIgnoreStart
265 355 debug_print_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // Option to ignore args requires PHP 5.3.6
@@ -279,7 +369,7 @@
279 369 $user_meta,
280 370 implode( "\n", $backtrace )
281 371 );
282 372
283 - error_log( $output );
373 + return $output;
284 374 }
285 375 }