PluginProbe
Stream – Activity Log & Audit Trail / 3.9.0
Stream – Activity Log & Audit Trail v3.9.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
stream / classes / class-log.php

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

378 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles top-level record keeping functionality.
4 *
5 * @package WP_Stream
6 */
7
8 namespace WP_Stream;
9
10 /**
11 * Class - Log
12 */
13 class Log {
14
15 /**
16 * Holds Instance of plugin object
17 *
18 * @var Plugin
19 */
20 public $plugin;
21
22 /**
23 * Hold Current visitors IP Address.
24 *
25 * @var string
26 */
27 private $ip_address;
28
29
30 /**
31 * Previous Stream record ID, used for chaining same-session records
32 *
33 * @var int
34 */
35 private $prev_record;
36
37 /**
38 * Class constructor.
39 *
40 * @param Plugin $plugin Instance of plugin object.
41 */
42 public function __construct( $plugin ) {
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 }
55 }
56
57 /**
58 * Log handler
59 *
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.
67 *
68 * @return mixed True if updated, otherwise false|WP_Error
69 */
70 public function log( $connector, $message, $args, $object_id, $context, $action, $user_id = null ) {
71 global $wp_roles;
72
73 if ( is_null( $user_id ) ) {
74 $user_id = get_current_user_id();
75 }
76
77 if ( is_null( $object_id ) ) {
78 $object_id = 0;
79 }
80
81 $wp_cron_tracking = isset( $this->plugin->settings->options['advanced_wp_cron_tracking'] ) ? $this->plugin->settings->options['advanced_wp_cron_tracking'] : false;
82 $author = new Author( $user_id );
83 $agent = $author->get_current_agent();
84
85 // WP Cron tracking requires opt-in and WP Cron to be enabled.
86 if ( ! $wp_cron_tracking && 'wp_cron' === $agent ) {
87 return false;
88 }
89
90 $user = new \WP_User( $user_id );
91
92 if ( $this->is_record_excluded( $connector, $context, $action, $user ) ) {
93 return false;
94 }
95
96 $user_meta = array(
97 'user_email' => (string) ! empty( $user->user_email ) ? $user->user_email : '',
98 'display_name' => (string) $author->get_display_name(),
99 'user_login' => (string) ! empty( $user->user_login ) ? $user->user_login : '',
100 'user_role_label' => (string) $author->get_role(),
101 'agent' => (string) $agent,
102 );
103
104 if ( 'wp_cli' === $agent && function_exists( 'posix_getuid' ) ) {
105 $uid = posix_getuid();
106 $user_info = posix_getpwuid( $uid );
107
108 $user_meta['system_user_id'] = (int) $uid;
109 $user_meta['system_user_name'] = (string) $user_info['name'];
110 }
111
112 // Prevent any meta with null values from being logged.
113 $stream_meta = array_filter(
114 $args,
115 function ( $var ) {
116 return ! is_null( $var );
117 }
118 );
119
120 // Add user meta to Stream meta.
121 $stream_meta['user_meta'] = $user_meta;
122
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 }
131
132 $recordarr = array(
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,
145 );
146
147 if ( 0 === $recordarr['object_id'] ) {
148 unset( $recordarr['object_id'] );
149 }
150
151 $result = $this->plugin->db->insert( $recordarr );
152
153 // This is helpful in development environments:
154 // error_log( $this->debug_backtrace( $recordarr ) );.
155
156 return $result;
157 }
158
159 /**
160 * This function is use to check whether or not a record should be excluded from the log.
161 *
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.
167 *
168 * @return bool
169 */
170 public function is_record_excluded( $connector, $context, $action, $user = null, $ip = null ) {
171 $exclude_record = false;
172
173 if ( is_null( $user ) ) {
174 $user = wp_get_current_user();
175 }
176
177 if ( is_null( $ip ) ) {
178 $ip = $this->ip_address;
179 } else {
180 $ip = wp_stream_filter_var( $ip, FILTER_VALIDATE_IP );
181 }
182
183 if ( ! empty( $user->roles ) ) {
184 $roles = array_values( $user->roles );
185 $role = $roles[0];
186 } else {
187 $role = '';
188 }
189 $record = array(
190 'connector' => $connector,
191 'context' => $context,
192 'action' => $action,
193 'author' => $user->ID,
194 'role' => $role,
195 'ip_address' => $ip,
196 );
197
198 $exclude_settings = isset( $this->plugin->settings->options['exclude_rules'] ) ? $this->plugin->settings->options['exclude_rules'] : array();
199
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 }
204
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++;
257 }
258 } elseif ( $record[ $exclude_key ] === $exclude_value ) {
259 $matches_found++;
260 }
261 }
262
263 return $matches_found === $matches_needed;
264 }
265
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();
275
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 );
285
286 if ( empty( $rules['exclude_row'] ) ) {
287 return array();
288 }
289
290 foreach ( array_keys( $rules['exclude_row'] ) as $row_id ) {
291 $excludes[ $row_id ] = array();
292
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;
298 }
299 }
300 }
301
302 return $excludes;
303 }
304
305 /**
306 * Helper function to send a full backtrace of calls to the PHP error log for debugging
307 *
308 * @param array $recordarr Record argument array.
309 *
310 * @return string
311 */
312 public function debug_backtrace( $recordarr ) {
313 if ( version_compare( PHP_VERSION, '5.3.6', '<' ) ) {
314 return __( 'Debug backtrace requires at least PHP 5.3.6', 'wp_stream' );
315 }
316
317 // Record details.
318 $summary = isset( $recordarr['summary'] ) ? $recordarr['summary'] : null;
319 $author = isset( $recordarr['author'] ) ? $recordarr['author'] : null;
320 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
321 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
322 $action = isset( $recordarr['action'] ) ? $recordarr['action'] : null;
323
324 // Stream meta.
325 $stream_meta = isset( $recordarr['meta'] ) ? $recordarr['meta'] : null;
326
327 unset( $stream_meta['user_meta'] );
328
329 if ( $stream_meta ) {
330 array_walk(
331 $stream_meta,
332 function ( &$value, $key ) {
333 $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
334 }
335 );
336 $stream_meta = implode( ', ', $stream_meta );
337 }
338
339 // User meta.
340 $user_meta = isset( $recordarr['meta']['user_meta'] ) ? $recordarr['meta']['user_meta'] : null;
341
342 if ( $user_meta ) {
343 array_walk(
344 $user_meta,
345 function ( &$value, $key ) {
346 $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
347 }
348 );
349
350 $user_meta = implode( ', ', $user_meta );
351 }
352
353 // Debug backtrace.
354 ob_start();
355
356 // @codingStandardsIgnoreStart
357 debug_print_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // Option to ignore args requires PHP 5.3.6
358 // @codingStandardsIgnoreEnd
359
360 $backtrace = ob_get_clean();
361 $backtrace = array_values( array_filter( explode( "\n", $backtrace ) ) );
362
363 $output = sprintf(
364 "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",
365 $summary,
366 $author,
367 $connector,
368 $context,
369 $action,
370 $stream_meta,
371 $user_meta,
372 implode( "\n", $backtrace )
373 );
374
375 return $output;
376 }
377 }
378