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