PluginProbe
Stream – Activity Log & Audit Trail / 4.0.1
Stream – Activity Log & Audit Trail v4.0.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
stream / classes / class-log.php

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

365 lines 10.4 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 $user_meta['system_user_id'] = (int) $uid;
97 $user_meta['system_user_name'] = (string) $user_info['name'];
98 }
99
100 // Prevent any meta with null values from being logged.
101 $stream_meta = array_filter(
102 $args,
103 function ( $value ) {
104 return ! is_null( $value );
105 }
106 );
107
108 // Add user meta to Stream meta.
109 $stream_meta['user_meta'] = $user_meta;
110
111 if ( ! empty( $user->roles ) ) {
112 $roles = array_values( $user->roles );
113 $role = $roles[0];
114 } elseif ( is_multisite() && is_super_admin() && $wp_roles->is_role( 'administrator' ) ) {
115 $role = 'administrator';
116 } else {
117 $role = '';
118 }
119
120 $recordarr = array(
121 'object_id' => (int) $object_id,
122 'site_id' => (int) is_multisite() ? get_current_site()->id : 1,
123 'blog_id' => (int) apply_filters( 'wp_stream_blog_id_logged', get_current_blog_id() ),
124 'user_id' => (int) $user_id,
125 'user_role' => (string) $role,
126 'created' => (string) current_time( 'mysql', true ),
127 'summary' => (string) vsprintf( $message, $args ),
128 'connector' => (string) $connector,
129 'context' => (string) $context,
130 'action' => (string) $action,
131 'ip' => (string) $ip_address,
132 'meta' => (array) $stream_meta,
133 );
134
135 if ( 0 === $recordarr['object_id'] ) {
136 unset( $recordarr['object_id'] );
137 }
138
139 $result = $this->plugin->db->insert( $recordarr );
140
141 // This is helpful in development environments:
142 // error_log( $this->debug_backtrace( $recordarr ) );.
143
144 return $result;
145 }
146
147 /**
148 * This function is use to check whether or not a record should be excluded from the log.
149 *
150 * @param string $connector Name of the connector being logged.
151 * @param string $context Name of the context being logged.
152 * @param string $action Name of the action being logged.
153 * @param \WP_User $user The user being logged.
154 * @param string $ip IP address being logged.
155 *
156 * @return bool
157 */
158 public function is_record_excluded( $connector, $context, $action, $user = null, $ip = null ) {
159 $exclude_record = false;
160
161 if ( is_null( $user ) ) {
162 $user = wp_get_current_user();
163 }
164
165 if ( ! empty( $user->roles ) ) {
166 $roles = array_values( $user->roles );
167 $role = $roles[0];
168 } else {
169 $role = '';
170 }
171 $record = array(
172 'connector' => $connector,
173 'context' => $context,
174 'action' => $action,
175 'author' => $user->ID,
176 'role' => $role,
177 'ip_address' => $ip,
178 );
179
180 $exclude_settings = isset( $this->plugin->settings->options['exclude_rules'] ) ? $this->plugin->settings->options['exclude_rules'] : array();
181
182 if ( is_multisite() && $this->plugin->is_network_activated() && ! is_network_admin() ) {
183 $multisite_options = (array) get_site_option( 'wp_stream_network', array() );
184 $exclude_settings = isset( $multisite_options['exclude_rules'] ) ? $multisite_options['exclude_rules'] : array();
185 }
186
187 foreach ( $this->exclude_rules_by_rows( $exclude_settings ) as $exclude_rule ) {
188 $exclude = array(
189 'connector' => ! empty( $exclude_rule['connector'] ) ? $exclude_rule['connector'] : null,
190 'context' => ! empty( $exclude_rule['context'] ) ? $exclude_rule['context'] : null,
191 'action' => ! empty( $exclude_rule['action'] ) ? $exclude_rule['action'] : null,
192 'ip_address' => ! empty( $exclude_rule['ip_address'] ) ? $exclude_rule['ip_address'] : null,
193 'author' => is_numeric( $exclude_rule['author_or_role'] ) ? absint( $exclude_rule['author_or_role'] ) : null,
194 'role' => ( ! empty( $exclude_rule['author_or_role'] ) && ! is_numeric( $exclude_rule['author_or_role'] ) ) ? $exclude_rule['author_or_role'] : null,
195 );
196
197 $exclude_rules = array_filter(
198 $exclude,
199 function ( $value ) {
200 return ! is_null( $value );
201 }
202 );
203
204 if ( $this->record_matches_rules( $record, $exclude_rules ) ) {
205 $exclude_record = true;
206 break;
207 }
208 }
209
210 /**
211 * Filters whether or not a record should be excluded from the log.
212 *
213 * If true, the record is not logged.
214 *
215 * @param array $exclude_record Whether the record should excluded.
216 * @param array $recordarr The record to log.
217 *
218 * @return bool
219 */
220 return apply_filters( 'wp_stream_is_record_excluded', $exclude_record, $record );
221 }
222
223 /**
224 * Check if a record to stored matches certain rules.
225 *
226 * @param array $record List of record parameters.
227 * @param array $exclude_rules List of record exclude rules.
228 *
229 * @return boolean
230 */
231 public function record_matches_rules( $record, $exclude_rules ) {
232 $matches_needed = count( $exclude_rules );
233 $matches_found = 0;
234 foreach ( $exclude_rules as $exclude_key => $exclude_value ) {
235 if ( ! isset( $record[ $exclude_key ] ) || is_null( $exclude_value ) ) {
236 continue;
237 }
238
239 if ( 'ip_address' === $exclude_key ) {
240 $ip_addresses = explode( ',', $exclude_value );
241
242 if ( in_array( $record['ip_address'], $ip_addresses, true ) ) {
243 ++$matches_found;
244 }
245 } elseif ( $record[ $exclude_key ] === $exclude_value ) {
246 ++$matches_found;
247 }
248 }
249
250 return $matches_found === $matches_needed;
251 }
252
253 /**
254 * Get all exclude rules by row because we store them by rule instead.
255 *
256 * @param array $rules List of rules indexed by rule ID.
257 *
258 * @return array
259 */
260 public function exclude_rules_by_rows( $rules ) {
261 $excludes = array();
262
263 // TODO: Move these to where the settings are generated to ensure they're in sync.
264 $rule_keys = array(
265 'exclude_row',
266 'author_or_role',
267 'connector',
268 'context',
269 'action',
270 'ip_address',
271 );
272
273 if ( empty( $rules['exclude_row'] ) ) {
274 return array();
275 }
276
277 foreach ( array_keys( $rules['exclude_row'] ) as $row_id ) {
278 $excludes[ $row_id ] = array();
279
280 foreach ( $rule_keys as $rule_key ) {
281 if ( isset( $rules[ $rule_key ][ $row_id ] ) ) {
282 $excludes[ $row_id ][ $rule_key ] = $rules[ $rule_key ][ $row_id ];
283 } else {
284 $excludes[ $row_id ][ $rule_key ] = null;
285 }
286 }
287 }
288
289 return $excludes;
290 }
291
292 /**
293 * Helper function to send a full backtrace of calls to the PHP error log for debugging
294 *
295 * @param array $recordarr Record argument array.
296 *
297 * @return string
298 */
299 public function debug_backtrace( $recordarr ) {
300 if ( version_compare( PHP_VERSION, '5.3.6', '<' ) ) {
301 return __( 'Debug backtrace requires at least PHP 5.3.6', 'wp_stream' );
302 }
303
304 // Record details.
305 $summary = isset( $recordarr['summary'] ) ? $recordarr['summary'] : null;
306 $author = isset( $recordarr['author'] ) ? $recordarr['author'] : null;
307 $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
308 $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
309 $action = isset( $recordarr['action'] ) ? $recordarr['action'] : null;
310
311 // Stream meta.
312 $stream_meta = isset( $recordarr['meta'] ) ? $recordarr['meta'] : null;
313
314 unset( $stream_meta['user_meta'] );
315
316 if ( $stream_meta ) {
317 array_walk(
318 $stream_meta,
319 function ( &$value, $key ) {
320 $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
321 }
322 );
323 $stream_meta = implode( ', ', $stream_meta );
324 }
325
326 // User meta.
327 $user_meta = isset( $recordarr['meta']['user_meta'] ) ? $recordarr['meta']['user_meta'] : null;
328
329 if ( $user_meta ) {
330 array_walk(
331 $user_meta,
332 function ( &$value, $key ) {
333 $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
334 }
335 );
336
337 $user_meta = implode( ', ', $user_meta );
338 }
339
340 // Debug backtrace.
341 ob_start();
342
343 // @codingStandardsIgnoreStart
344 debug_print_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // Option to ignore args requires PHP 5.3.6
345 // @codingStandardsIgnoreEnd
346
347 $backtrace = ob_get_clean();
348 $backtrace = array_values( array_filter( explode( "\n", $backtrace ) ) );
349
350 $output = sprintf(
351 "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",
352 $summary,
353 $author,
354 $connector,
355 $context,
356 $action,
357 $stream_meta,
358 $user_meta,
359 implode( "\n", $backtrace )
360 );
361
362 return $output;
363 }
364 }
365