PluginProbe
Stream – Activity Log & Audit Trail / trunk
Stream – Activity Log & Audit Trail vtrunk
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 trunk, at classes/class-log.php

370 lines 10.6 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 * 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 Instance of plugin object.
33 */
34 public function __construct( $plugin ) {
35 $this->plugin = $plugin;
36
37 // Ensure function used in various methods is pre-loaded.
38 if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
39 require_once ABSPATH . '/wp-admin/includes/plugin.php';
40 }
41 }
42
43 /**
44 * Log handler
45 *
46 * @param Connector $connector Connector responsible for logging the event.
47 * @param string $message sprintf-ready error message string.
48 * @param array $args sprintf (and extra) arguments to use.
49 * @param int $object_id Target object id.
50 * @param string $context Context of the event.
51 * @param string $action Action of the event.
52 * @param int $user_id User responsible for the event.
53 *
54 * @return mixed True if updated, otherwise false|WP_Error
55 */
56 public function log( $connector, $message, $args, $object_id, $context, $action, $user_id = null ) {
57 global $wp_roles;
58
59 if ( is_null( $user_id ) ) {
60 $user_id = get_current_user_id();
61 }
62
63 if ( is_null( $object_id ) ) {
64 $object_id = 0;
65 }
66
67 $wp_cron_tracking = isset( $this->plugin->settings->options['advanced_wp_cron_tracking'] ) ? $this->plugin->settings->options['advanced_wp_cron_tracking'] : false;
68 $author = new Author( $user_id );
69 $agent = $author->get_current_agent();
70
71 // WP Cron tracking requires opt-in and WP Cron to be enabled.
72 if ( ! $wp_cron_tracking && 'wp_cron' === $agent ) {
73 return false;
74 }
75
76 $ip_address = $this->plugin->get_client_ip_address();
77
78 $user = new \WP_User( $user_id );
79
80 if ( $this->is_record_excluded( $connector, $context, $action, $user, $ip_address ) ) {
81 return false;
82 }
83
84 $user_meta = array(
85 'user_email' => (string) ! empty( $user->user_email ) ? $user->user_email : '',
86 'display_name' => (string) $author->get_display_name(),
87 'user_login' => (string) ! empty( $user->user_login ) ? $user->user_login : '',
88 'user_role_label' => (string) $author->get_role(),
89 'agent' => (string) $agent,
90 );
91
92 if ( 'wp_cli' === $agent && function_exists( 'posix_getuid' ) ) {
93 $uid = posix_getuid();
94 $user_info = posix_getpwuid( $uid );
95
96 // Normalize the user info to an array if it's not already.
97 if ( ! is_array( $user_info ) ) {
98 $user_info = array( 'name' => 'unknown' );
99 }
100
101 $user_meta['system_user_id'] = (int) $uid;
102 $user_meta['system_user_name'] = (string) ( $user_info['name'] ?? 'unknown' );
103 }
104
105 // Prevent any meta with null values from being logged.
106 $stream_meta = array_filter(
107 $args,
108 function ( $value ) {
109 return ! is_null( $value );
110 }
111 );
112
113 // Add user meta to Stream meta.
114 $stream_meta['user_meta'] = $user_meta;
115
116 if ( ! empty( $user->roles ) ) {
117 $roles = array_values( $user->roles );
118 $role = $roles[0];
119 } elseif ( is_multisite() && is_super_admin() && $wp_roles->is_role( 'administrator' ) ) {
120 $role = 'administrator';
121 } else {
122 $role = '';
123 }
124
125 $recordarr = array(
126 'object_id' => (int) $object_id,
127 'site_id' => (int) is_multisite() ? get_current_site()->id : 1,
128 'blog_id' => (int) apply_filters( 'wp_stream_blog_id_logged', get_current_blog_id() ),
129 'user_id' => (int) $user_id,
130 'user_role' => (string) $role,
131 'created' => (string) current_time( 'mysql', true ),
132 'summary' => (string) vsprintf( $message, $args ),
133 'connector' => (string) $connector,
134 'context' => (string) $context,
135 'action' => (string) $action,
136 'ip' => (string) $ip_address,
137 'meta' => (array) $stream_meta,
138 );
139
140 if ( 0 === $recordarr['object_id'] ) {
141 unset( $recordarr['object_id'] );
142 }
143
144 $result = $this->plugin->db->insert( $recordarr );
145
146 // This is helpful in development environments:
147 // error_log( $this->debug_backtrace( $recordarr ) );.
148
149 return $result;
150 }
151
152 /**
153 * This function is use to check whether or not a record should be excluded from the log.
154 *
155 * @param string $connector Name of the connector being logged.
156 * @param string $context Name of the context being logged.
157 * @param string $action Name of the action being logged.
158 * @param \WP_User $user The user being logged.
159 * @param string $ip IP address being logged.
160 *
161 * @return bool
162 */
163 public function is_record_excluded( $connector, $context, $action, $user = null, $ip = null ) {
164 $exclude_record = false;
165
166 if ( is_null( $user ) ) {
167 $user = wp_get_current_user();
168 }
169
170 if ( ! empty( $user->roles ) ) {
171 $roles = array_values( $user->roles );
172 $role = $roles[0];
173 } else {
174 $role = '';
175 }
176 $record = array(
177 'connector' => $connector,
178 'context' => $context,
179 'action' => $action,
180 'author' => $user->ID,
181 'role' => $role,
182 'ip_address' => $ip,
183 );
184
185 $exclude_settings = isset( $this->plugin->settings->options['exclude_rules'] ) ? $this->plugin->settings->options['exclude_rules'] : array();
186
187 if ( is_multisite() && $this->plugin->is_network_activated() && ! is_network_admin() ) {
188 $multisite_options = (array) get_site_option( 'wp_stream_network', array() );
189 $exclude_settings = isset( $multisite_options['exclude_rules'] ) ? $multisite_options['exclude_rules'] : array();
190 }
191
192 foreach ( $this->exclude_rules_by_rows( $exclude_settings ) as $exclude_rule ) {
193 $exclude = array(
194 'connector' => ! empty( $exclude_rule['connector'] ) ? $exclude_rule['connector'] : null,
195 'context' => ! empty( $exclude_rule['context'] ) ? $exclude_rule['context'] : null,
196 'action' => ! empty( $exclude_rule['action'] ) ? $exclude_rule['action'] : null,
197 'ip_address' => ! empty( $exclude_rule['ip_address'] ) ? $exclude_rule['ip_address'] : null,
198 'author' => is_numeric( $exclude_rule['author_or_role'] ) ? absint( $exclude_rule['author_or_role'] ) : null,
199 'role' => ( ! empty( $exclude_rule['author_or_role'] ) && ! is_numeric( $exclude_rule['author_or_role'] ) ) ? $exclude_rule['author_or_role'] : null,
200 );
201
202 $exclude_rules = array_filter(
203 $exclude,
204 function ( $value ) {
205 return ! is_null( $value );
206 }
207 );
208
209 if ( $this->record_matches_rules( $record, $exclude_rules ) ) {
210 $exclude_record = true;
211 break;
212 }
213 }
214
215 /**
216 * Filters whether or not a record should be excluded from the log.
217 *
218 * If true, the record is not logged.
219 *
220 * @param array $exclude_record Whether the record should excluded.
221 * @param array $recordarr The record to log.
222 *
223 * @return bool
224 */
225 return apply_filters( 'wp_stream_is_record_excluded', $exclude_record, $record );
226 }
227
228 /**
229 * Check if a record to stored matches certain rules.
230 *
231 * @param array $record List of record parameters.
232 * @param array $exclude_rules List of record exclude rules.
233 *
234 * @return boolean
235 */
236 public function record_matches_rules( $record, $exclude_rules ) {
237 $matches_needed = count( $exclude_rules );
238 $matches_found = 0;
239 foreach ( $exclude_rules as $exclude_key => $exclude_value ) {
240 if ( ! isset( $record[ $exclude_key ] ) || is_null( $exclude_value ) ) {
241 continue;
242 }
243
244 if ( 'ip_address' === $exclude_key ) {
245 $ip_addresses = explode( ',', $exclude_value );
246
247 if ( in_array( $record['ip_address'], $ip_addresses, true ) ) {
248 ++$matches_found;
249 }
250 } elseif ( $record[ $exclude_key ] === $exclude_value ) {
251 ++$matches_found;
252 }
253 }
254
255 return $matches_found === $matches_needed;
256 }
257
258 /**
259 * Get all exclude rules by row because we store them by rule instead.
260 *
261 * @param array $rules List of rules indexed by rule ID.
262 *
263 * @return array
264 */
265 public function exclude_rules_by_rows( $rules ) {
266 $excludes = array();
267
268 // TODO: Move these to where the settings are generated to ensure they're in sync.
269 $rule_keys = array(
270 'exclude_row',
271 'author_or_role',
272 'connector',
273 'context',
274 'action',
275 'ip_address',
276 );
277
278 if ( empty( $rules['exclude_row'] ) ) {
279 return array();
280 }
281
282 foreach ( array_keys( $rules['exclude_row'] ) as $row_id ) {
283 $excludes[ $row_id ] = array();
284
285 foreach ( $rule_keys as $rule_key ) {
286 if ( isset( $rules[ $rule_key ][ $row_id ] ) ) {
287 $excludes[ $row_id ][ $rule_key ] = $rules[ $rule_key ][ $row_id ];
288 } else {
289 $excludes[ $row_id ][ $rule_key ] = null;
290 }
291 }
292 }
293
294 return $excludes;
295 }
296
297 /**
298 * Helper function to send a full backtrace of calls to the PHP error log for debugging
299 *
300 * @param array $recordarr Record argument array.
301 *
302 * @return string
303 */
304 public function debug_backtrace( $recordarr ) {
305 if ( version_compare( PHP_VERSION, '5.3.6', '<' ) ) {
306 return __( 'Debug backtrace requires at least PHP 5.3.6', 'stream' );
307 }
308
309 // Record details.
310 $summary = isset( $recordarr['summary'] ) ? $recordarr['summary'] : null;
311 $author = isset( $recordarr['author'] ) ? $recordarr['author'] : null;
312 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
313 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
314 $action = isset( $recordarr['action'] ) ? $recordarr['action'] : null;
315
316 // Stream meta.
317 $stream_meta = isset( $recordarr['meta'] ) ? $recordarr['meta'] : null;
318
319 unset( $stream_meta['user_meta'] );
320
321 if ( $stream_meta ) {
322 array_walk(
323 $stream_meta,
324 function ( &$value, $key ) {
325 $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
326 }
327 );
328 $stream_meta = implode( ', ', $stream_meta );
329 }
330
331 // User meta.
332 $user_meta = isset( $recordarr['meta']['user_meta'] ) ? $recordarr['meta']['user_meta'] : null;
333
334 if ( $user_meta ) {
335 array_walk(
336 $user_meta,
337 function ( &$value, $key ) {
338 $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
339 }
340 );
341
342 $user_meta = implode( ', ', $user_meta );
343 }
344
345 // Debug backtrace.
346 ob_start();
347
348 // @codingStandardsIgnoreStart
349 debug_print_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // Option to ignore args requires PHP 5.3.6
350 // @codingStandardsIgnoreEnd
351
352 $backtrace = ob_get_clean();
353 $backtrace = array_values( array_filter( explode( "\n", $backtrace ) ) );
354
355 $output = sprintf(
356 "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",
357 $summary,
358 $author,
359 $connector,
360 $context,
361 $action,
362 $stream_meta,
363 $user_meta,
364 implode( "\n", $backtrace )
365 );
366
367 return $output;
368 }
369 }
370