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
stream / classes / class-log.php

class-log.php in Stream – Activity Log & Audit Trail 3.4.2, at classes/class-log.php

371 lines 10.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WP_Stream;
4
5 class Log {
6
7 /**
8 * Hold Plugin class
9 *
10 * @var Plugin
11 */
12 public $plugin;
13
14 /**
15 * Hold Current visitors IP Address.
16 *
17 * @var string
18 */
19 private $ip_address;
20
21
22 /**
23 * Previous Stream record ID, used for chaining same-session records
24 *
25 * @var int
26 */
27 private $prev_record;
28
29 /**
30 * Class constructor.
31 *
32 * @param Plugin $plugin The main Plugin class.
33 */
34 public function __construct( $plugin ) {
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 }
47 }
48
49 /**
50 * Log handler
51 *
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.
59 *
60 * @return mixed True if updated, otherwise false|WP_Error
61 */
62 public function log( $connector, $message, $args, $object_id, $context, $action, $user_id = null ) {
63 global $wp_roles;
64
65 if ( is_null( $user_id ) ) {
66 $user_id = get_current_user_id();
67 }
68
69 if ( is_null( $object_id ) ) {
70 $object_id = 0;
71 }
72
73 $wp_cron_tracking = isset( $this->plugin->settings->options['advanced_wp_cron_tracking'] ) ? $this->plugin->settings->options['advanced_wp_cron_tracking'] : false;
74 $author = new Author( $user_id );
75 $agent = $author->get_current_agent();
76
77 // WP Cron tracking requires opt-in and WP Cron to be enabled.
78 if ( ! $wp_cron_tracking && 'wp_cron' === $agent ) {
79 return false;
80 }
81
82 $user = new \WP_User( $user_id );
83
84 if ( $this->is_record_excluded( $connector, $context, $action, $user ) ) {
85 return false;
86 }
87
88 $user_meta = array(
89 'user_email' => (string) ! empty( $user->user_email ) ? $user->user_email : '',
90 'display_name' => (string) $author->get_display_name(),
91 'user_login' => (string) ! empty( $user->user_login ) ? $user->user_login : '',
92 'user_role_label' => (string) $author->get_role(),
93 'agent' => (string) $agent,
94 );
95
96 if ( 'wp_cli' === $agent && function_exists( 'posix_getuid' ) ) {
97 $uid = posix_getuid();
98 $user_info = posix_getpwuid( $uid );
99
100 $user_meta['system_user_id'] = (int) $uid;
101 $user_meta['system_user_name'] = (string) $user_info['name'];
102 }
103
104 // Prevent any meta with null values from being logged.
105 $stream_meta = array_filter(
106 $args,
107 function ( $var ) {
108 return ! is_null( $var );
109 }
110 );
111
112 // Add user meta to Stream meta.
113 $stream_meta['user_meta'] = $user_meta;
114
115 // Get the current time in milliseconds.
116 $iso_8601_extended_date = wp_stream_get_iso_8601_extended_date();
117
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 }
126
127 $recordarr = array(
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,
140 );
141
142 if ( 0 === $recordarr['object_id'] ) {
143 unset( $recordarr['object_id'] );
144 }
145
146 $result = $this->plugin->db->insert( $recordarr );
147
148 // This is helpful in development environments:
149 // error_log( $this->debug_backtrace( $recordarr ) );
150
151 return $result;
152 }
153
154 /**
155 * This function is use to check whether or not a record should be excluded from the log.
156 *
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.
162 *
163 * @return bool
164 */
165 public function is_record_excluded( $connector, $context, $action, $user = null, $ip = null ) {
166 $exclude_record = false;
167
168 if ( is_null( $user ) ) {
169 $user = wp_get_current_user();
170 }
171
172 if ( is_null( $ip ) ) {
173 $ip = $this->ip_address;
174 } else {
175 $ip = wp_stream_filter_var( $ip, FILTER_VALIDATE_IP );
176 }
177
178 if ( ! empty( $user->roles ) ) {
179 $roles = array_values( $user->roles );
180 $role = $roles[0];
181 } else {
182 $role = '';
183 }
184 $record = array(
185 'connector' => $connector,
186 'context' => $context,
187 'action' => $action,
188 'author' => $user->ID,
189 'role' => $role,
190 'ip_address' => $ip,
191 );
192
193 $exclude_settings = isset( $this->plugin->settings->options['exclude_rules'] ) ? $this->plugin->settings->options['exclude_rules'] : array();
194
195 if ( is_multisite() && $this->plugin->is_network_activated() && ! is_network_admin() ) {
196 $multisite_options = (array) get_site_option( 'wp_stream_network', array() );
197 $exclude_settings = isset( $multisite_options['exclude_rules'] ) ? $multisite_options['exclude_rules'] : array();
198 }
199
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;
250 }
251 } elseif ( $record[ $exclude_key ] === $exclude_value ) {
252 return true;
253 }
254 }
255
256 return false;
257 }
258
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();
268
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 );
278
279 if ( empty( $rules['exclude_row'] ) ) {
280 return array();
281 }
282
283 foreach ( array_keys( $rules['exclude_row'] ) as $row_id ) {
284 $excludes[ $row_id ] = array();
285
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;
291 }
292 }
293 }
294
295 return $excludes;
296 }
297
298 /**
299 * Helper function to send a full backtrace of calls to the PHP error log for debugging
300 *
301 * @param array $recordarr Record argument array.
302 *
303 * @return string
304 */
305 public function debug_backtrace( $recordarr ) {
306 if ( version_compare( PHP_VERSION, '5.3.6', '<' ) ) {
307 return __( 'Debug backtrace requires at least PHP 5.3.6', 'wp_stream' );
308 }
309
310 // Record details.
311 $summary = isset( $recordarr['summary'] ) ? $recordarr['summary'] : null;
312 $author = isset( $recordarr['author'] ) ? $recordarr['author'] : null;
313 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
314 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
315 $action = isset( $recordarr['action'] ) ? $recordarr['action'] : null;
316
317 // Stream meta.
318 $stream_meta = isset( $recordarr['meta'] ) ? $recordarr['meta'] : null;
319
320 unset( $stream_meta['user_meta'] );
321
322 if ( $stream_meta ) {
323 array_walk(
324 $stream_meta,
325 function ( &$value, $key ) {
326 $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
327 }
328 );
329 $stream_meta = implode( ', ', $stream_meta );
330 }
331
332 // User meta.
333 $user_meta = isset( $recordarr['meta']['user_meta'] ) ? $recordarr['meta']['user_meta'] : null;
334
335 if ( $user_meta ) {
336 array_walk(
337 $user_meta,
338 function ( &$value, $key ) {
339 $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
340 }
341 );
342
343 $user_meta = implode( ', ', $user_meta );
344 }
345
346 // Debug backtrace.
347 ob_start();
348
349 // @codingStandardsIgnoreStart
350 debug_print_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // Option to ignore args requires PHP 5.3.6
351 // @codingStandardsIgnoreEnd
352
353 $backtrace = ob_get_clean();
354 $backtrace = array_values( array_filter( explode( "\n", $backtrace ) ) );
355
356 $output = sprintf(
357 "WP Stream Debug Backtrace\n\n Summary | %s\n Author | %s\n Connector | %s\n Context | %s\n Action | %s\nStream Meta | %s\nAuthor Meta | %s\n\n%s\n",
358 $summary,
359 $author,
360 $connector,
361 $context,
362 $action,
363 $stream_meta,
364 $user_meta,
365 implode( "\n", $backtrace )
366 );
367
368 return $output;
369 }
370 }
371