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-connector.php +26 -237 trunk3.4.2 View file →
@@ -1,17 +1,7 @@
1 1 <?php
2 -/**
3 - * Abstract class serving as the parent for all logger classes AKA "Connectors".
4 - * Common functionality for registering log events are defined here.
5 - *
6 - * @package WP_Stream;
7 - */
8 -
9 2 namespace WP_Stream;
10 3
11 -/**
12 - * Class - Connector
13 - */
14 4 abstract class Connector {
15 5 /**
16 6 * Connector slug
17 7 *
@@ -54,66 +44,27 @@
54 44 */
55 45 public $register_frontend = true;
56 46
57 47 /**
58 - * Holds connector registration status flag.
59 - *
60 - * @var bool
61 - */
62 - private $is_registered = false;
63 -
64 - /**
65 - * Is the connector currently registered?
66 - *
67 - * @return boolean
68 - */
69 - public function is_registered() {
70 - return $this->is_registered;
71 - }
72 -
73 - /**
74 48 * Register all context hooks
75 49 */
76 50 public function register() {
77 - if ( $this->is_registered ) {
78 - return;
79 - }
80 -
81 51 foreach ( $this->actions as $action ) {
82 52 add_action( $action, array( $this, 'callback' ), 10, 99 );
83 53 }
84 54
85 55 add_filter( 'wp_stream_action_links_' . $this->name, array( $this, 'action_links' ), 10, 2 );
86 -
87 - $this->is_registered = true;
88 56 }
89 57
90 58 /**
91 - * Unregister all context hooks
92 - */
93 - public function unregister() {
94 - if ( ! $this->is_registered ) {
95 - return;
96 - }
97 -
98 - foreach ( $this->actions as $action ) {
99 - remove_action( $action, array( $this, 'callback' ), 10, 99 );
100 - }
101 -
102 - remove_filter( 'wp_stream_action_links_' . $this->name, array( $this, 'action_links' ), 10, 2 );
103 -
104 - $this->is_registered = false;
105 - }
106 -
107 - /**
108 59 * Callback for all registered hooks throughout Stream
109 60 * Looks for a class method with the convention: "callback_{action name}"
110 61 */
111 62 public function callback() {
112 63 $action = current_filter();
113 - $callback = array( $this, 'callback_' . preg_replace( '/[^a-z0-9_]/', '_', $action ) );
64 + $callback = array( $this, 'callback_' . preg_replace( '/[^a-z0-9_\-]/', '_', $action ) );
114 65
115 - // For the sake of testing, trigger an action with the name of the callback.
66 + // For the sake of testing, trigger an action with the name of the callback
116 67 if ( defined( 'WP_STREAM_TESTS' ) && WP_STREAM_TESTS ) {
117 68 /**
118 69 * Action fires during testing to test the current callback
119 70 *
@@ -121,9 +72,9 @@
121 72 */
122 73 do_action( 'wp_stream_test_' . $callback[1] );
123 74 }
124 75
125 - // Call the real function.
76 + // Call the real function
126 77 if ( is_callable( $callback ) ) {
127 78 return call_user_func_array( $callback, func_get_args() );
128 79 }
129 80 }
@@ -130,10 +81,10 @@
130 81
131 82 /**
132 83 * Add action links to Stream drop row in admin list screen
133 84 *
134 - * @param array $links Previous links registered.
135 - * @param object $record Stream record.
85 + * @param array $links Previous links registered
86 + * @param object $record Stream record
136 87 *
137 88 * @filter wp_stream_action_links_{connector}
138 89 *
139 90 * @return array Action links
@@ -145,14 +96,14 @@
145 96
146 97 /**
147 98 * Log handler
148 99 *
149 - * @param string $message sprintf-ready error message string.
150 - * @param array $args sprintf (and extra) arguments to use.
151 - * @param int|null $object_id Target object id (if any).
152 - * @param string $context Context of the event.
153 - * @param string $action Action of the event.
154 - * @param int $user_id User responsible for the event.
100 + * @param string $message sprintf-ready error message string
101 + * @param array $args sprintf (and extra) arguments to use
102 + * @param int $object_id Target object id
103 + * @param string $context Context of the event
104 + * @param string $action Action of the event
105 + * @param int $user_id User responsible for the event
155 106 *
156 107 * @return bool
157 108 */
158 109 public function log( $message, $args, $object_id, $context, $action, $user_id = null ) {
@@ -157,16 +108,8 @@
157 108 */
158 109 public function log( $message, $args, $object_id, $context, $action, $user_id = null ) {
159 110 $connector = $this->name;
160 111
161 - /**
162 - * Override the data logged. Returning false to this filter will stop the data from being logged.
163 - * Examples of this filter in use can be found in some of the custom connectors.
164 - *
165 - * @see Connector_ACF::log_override()
166 - *
167 - * @return array|false An array of the data to be logged or false if it should not be logged.
168 - */
169 112 $data = apply_filters(
170 113 'wp_stream_log_data',
171 114 compact( 'connector', 'message', 'args', 'object_id', 'context', 'action', 'user_id' )
172 115 );
@@ -186,155 +129,11 @@
186 129 return call_user_func_array( array( wp_stream_get_instance()->log, 'log' ), compact( 'connector', 'message', 'args', 'object_id', 'context', 'action', 'user_id' ) );
187 130 }
188 131
189 132 /**
190 - * Substrings that mark a setting name as holding a credential.
191 - *
192 - * Deliberately matched as substrings so unknown third-party settings are
193 - * covered by default: connectors log arbitrary option arrays from other
194 - * plugins, and an allowlist cannot anticipate every field a payment gateway
195 - * or integration might add. Over-redacting a harmless field only costs a
196 - * little detail in the audit log; under-redacting persists a live credential
197 - * in a table that lower-privileged Stream viewers can read.
198 - *
199 - * @const array
200 - */
201 - const SECRET_KEY_PATTERNS = array(
202 - 'pass',
203 - 'secret',
204 - 'private_key',
205 - 'apikey',
206 - 'token',
207 - 'webhook',
208 - 'license',
209 - 'salt',
210 - 'credential',
211 - 'oauth',
212 - // PayPal NVP `api_signature`; over-matches e.g. `email_signature`.
213 - 'signature',
214 - );
215 -
216 - /**
217 - * Setting names, or suffixes of them, that hold a credential but do not
218 - * contain any of the substrings above.
219 - *
220 - * Matched against the end of the name so option prefixes used by individual
221 - * plugins (rg_gforms_key, woocommerce_..._key) are covered without treating
222 - * every name that merely contains "key" as sensitive.
223 - *
224 - * @const array
225 - */
226 - const SECRET_KEY_SUFFIXES = array(
227 - '_key',
228 - );
229 -
230 - /**
231 - * Names that end in a secret-looking suffix but are not credentials.
232 - *
233 - * Public halves of key pairs are meant to be published, and redacting them
234 - * removes useful audit detail for no benefit. Matched as substrings so the
235 - * various plugin prefixes are covered.
236 - *
237 - * Only consulted after SECRET_KEY_PATTERNS, so a name containing an
238 - * explicit secret marker is never exempted by appearing "public" too.
239 - *
240 - * @const array
241 - */
242 - const PUBLIC_KEY_PATTERNS = array(
243 - 'public_key',
244 - 'publishable_key',
245 - 'site_key',
246 - );
247 -
248 - /**
249 - * Placeholder stored in place of a redacted value.
250 - *
251 - * A distinct marker rather than an empty string, so a reader of the audit
252 - * trail can tell "this credential changed, value withheld" apart from "this
253 - * field was cleared" -- an empty string would conflate the two.
254 - *
255 - * @const string
256 - */
257 - const REDACTED_PLACEHOLDER = '[redacted]';
258 -
259 - /**
260 - * Whether a setting/field name looks like it holds a credential.
261 - *
262 - * @param string $key Setting or field name.
263 - * @return bool
264 - */
265 - public function is_secret_key( $key ) {
266 - if ( ! is_string( $key ) || '' === $key ) {
267 - return false;
268 - }
269 -
270 - $needle = strtolower( $key );
271 -
272 - // An explicit secret marker always wins. The public-name exemption
273 - // below is only there to stop the broad "_key" suffix rule from
274 - // swallowing published key halves, so it must not be able to rescue a
275 - // name that also says "secret", "private_key" or "webhook" -- that
276 - // would invert the over-redact-rather-than-under-redact preference.
277 - foreach ( self::SECRET_KEY_PATTERNS as $pattern ) {
278 - if ( false !== strpos( $needle, $pattern ) ) {
279 - return true;
280 - }
281 - }
282 -
283 - foreach ( self::PUBLIC_KEY_PATTERNS as $pattern ) {
284 - if ( false !== strpos( $needle, $pattern ) ) {
285 - return false;
286 - }
287 - }
288 -
289 - foreach ( self::SECRET_KEY_SUFFIXES as $suffix ) {
290 - if ( substr( $needle, -strlen( $suffix ) ) === $suffix ) {
291 - return true;
292 - }
293 - }
294 -
295 - return false;
296 - }
297 -
298 - /**
299 - * Redact credential values before they are persisted as record metadata.
300 - *
301 - * Accepts either a scalar (redacted when $key itself looks secret) or an
302 - * array of settings (each secret-looking member redacted, recursively).
303 - * Values are replaced rather than removed so the audit trail still shows
304 - * that the field changed, without retaining the credential.
305 - *
306 - * @param mixed $value Value about to be logged.
307 - * @param string $key Setting or field name the value belongs to.
308 - * @return mixed
309 - */
310 - public function redact_secret_values( $value, $key = '' ) {
311 - if ( is_array( $value ) ) {
312 - // A secret parent may key credentials by opaque IDs (e.g. Jetpack
313 - // `user_tokens` by user ID), so pass it down over child names.
314 - $inherited_key = $this->is_secret_key( $key ) ? $key : null;
315 -
316 - foreach ( $value as $child_key => $child_value ) {
317 - $value[ $child_key ] = $this->redact_secret_values(
318 - $child_value,
319 - null !== $inherited_key ? $inherited_key : (string) $child_key
320 - );
321 - }
322 -
323 - return $value;
324 - }
325 -
326 - if ( $this->is_secret_key( $key ) && ! empty( $value ) ) {
327 - return self::REDACTED_PLACEHOLDER;
328 - }
329 -
330 - return $value;
331 - }
332 -
333 - /**
334 133 * Save log data till shutdown, so other callbacks would be able to override
335 134 *
336 - * @param string $handle Special slug to be shared with other actions.
135 + * @param string $handle Special slug to be shared with other actions
337 136 * @note param mixed $arg1 Extra arguments to sent to log()
338 137 * @note param param mixed $arg2, etc..
339 138 */
340 139 public function delayed_log( $handle ) {
@@ -358,11 +157,11 @@
358 157
359 158 /**
360 159 * Compare two values and return changed keys if they are arrays
361 160 *
362 - * @param mixed $old_value Value before change.
363 - * @param mixed $new_value Value after change.
364 - * @param bool|int $deep Get array children changes keys as well, not just parents.
161 + * @param mixed $old_value Value before change
162 + * @param mixed $new_value Value after change
163 + * @param bool|int $deep Get array children changes keys as well, not just parents
365 164 *
366 165 * @return array
367 166 */
368 167 public function get_changed_keys( $old_value, $new_value, $deep = false ) {
@@ -380,10 +179,10 @@
380 179
381 180 $diff = array_udiff_assoc(
382 181 $old_value,
383 182 $new_value,
384 - function ( $value1, $value2 ) {
385 - // Compare potentially complex nested arrays.
183 + function( $value1, $value2 ) {
184 + // Compare potentially complex nested arrays
386 185 return wp_json_encode( $value1 ) !== wp_json_encode( $value2 );
387 186 }
388 187 );
389 188
@@ -388,9 +187,9 @@
388 187 );
389 188
390 189 $result = array_keys( $diff );
391 190
392 - // Find unexisting keys in old or new value.
191 + // find unexisting keys in old or new value
393 192 $common_keys = array_keys( array_intersect_key( $old_value, $new_value ) );
394 193 $unique_keys_old = array_values( array_diff( array_keys( $old_value ), $common_keys ) );
395 194 $unique_keys_new = array_values( array_diff( array_keys( $new_value ), $common_keys ) );
396 195
@@ -395,12 +194,12 @@
395 194 $unique_keys_new = array_values( array_diff( array_keys( $new_value ), $common_keys ) );
396 195
397 196 $result = array_merge( $result, $unique_keys_old, $unique_keys_new );
398 197
399 - // Remove numeric indexes.
198 + // remove numeric indexes
400 199 $result = array_filter(
401 200 $result,
402 - function ( $value ) {
201 + function( $value ) {
403 202 // @codingStandardsIgnoreStart
404 203 // check if is not valid number (is_int, is_numeric and ctype_digit are not enough)
405 204 return (string) (int) $value !== (string) $value;
406 205 // @codingStandardsIgnoreEnd
@@ -409,9 +208,9 @@
409 208
410 209 $result = array_values( array_unique( $result ) );
411 210
412 211 if ( false === $deep ) {
413 - return $result; // Return an numerical based array with changed TOP PARENT keys only.
212 + return $result; // Return an numerical based array with changed TOP PARENT keys only
414 213 }
415 214
416 215 $result = array_fill_keys( $result, null );
417 216
@@ -416,21 +215,21 @@
416 215 $result = array_fill_keys( $result, null );
417 216
418 217 foreach ( $result as $key => $val ) {
419 218 if ( in_array( $key, $unique_keys_old, true ) ) {
420 - $result[ $key ] = false; // Removed.
219 + $result[ $key ] = false; // Removed
421 220 } elseif ( in_array( $key, $unique_keys_new, true ) ) {
422 - $result[ $key ] = true; // Added.
423 - } elseif ( $deep ) { // Changed, find what changed, only if we're allowed to explore a new level.
221 + $result[ $key ] = true; // Added
222 + } elseif ( $deep ) { // Changed, find what changed, only if we're allowed to explore a new level
424 223 if ( is_array( $old_value[ $key ] ) && is_array( $new_value[ $key ] ) ) {
425 224 $inner = array();
426 225 $parent = $key;
427 - --$deep;
226 + $deep--;
428 227 $changed = $this->get_changed_keys( $old_value[ $key ], $new_value[ $key ], $deep );
429 228 foreach ( $changed as $child => $change ) {
430 229 $inner[ $parent . '::' . $child ] = $change;
431 230 }
432 - $result[ $key ] = 0; // Changed parent which has a changed children.
231 + $result[ $key ] = 0; // Changed parent which has a changed children
433 232 $result = array_merge( $result, $inner );
434 233 }
435 234 }
436 235 }
@@ -444,16 +243,6 @@
444 243 * @return bool
445 244 */
446 245 public function is_dependency_satisfied() {
447 246 return true;
448 - }
449 -
450 - /**
451 - * Escape % characters in a string to avoid Uncaught ValueErrors in $this->log().
452 - *
453 - * @param string $value The string value to be escaped.
454 - * @return string The escaped string.
455 - */
456 - public function escape_percentages( $value ) {
457 - return str_replace( '%', '%%', $value );
458 247 }
459 248 }