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