| @@ -129,18 +129,24 @@ | ||
| 129 | 129 | * baked into cached HTML and can be a full cache TTL out of date, so every |
| 130 | 130 | * write path re-checks here. |
| 131 | 131 | * |
| 132 | 132 | * @since 2.12.6 |
| 133 | + * @since 2.12.8 Filterable through `srfm_enable_logs`. | |
| 133 | 134 | * @return bool |
| 134 | 135 | */ |
| 135 | 136 | public static function is_enabled() { |
| 136 | 137 | $general = get_option( 'srfm_general_settings_options', [] ); |
| 138 | + $enabled = ! is_array( $general ) || ! isset( $general['srfm_enable_logs'] ) || (bool) $general['srfm_enable_logs']; | |
| 137 | 139 | |
| 138 | - if ( ! is_array( $general ) || ! isset( $general['srfm_enable_logs'] ) ) { | |
| 139 | - return true; | |
| 140 | - } | |
| 141 | - | |
| 142 | - return (bool) $general['srfm_enable_logs']; | |
| 140 | + /** | |
| 141 | + * Filter whether client logging is on, without changing the stored | |
| 142 | + * setting. SureForms Pro's Distraction Free mode turns it off here. | |
| 143 | + * | |
| 144 | + * @since 2.12.8 | |
| 145 | + * | |
| 146 | + * @param bool $enabled The stored setting (true when never saved). | |
| 147 | + */ | |
| 148 | + return (bool) apply_filters( 'srfm_enable_logs', $enabled ); | |
| 143 | 149 | } |
| 144 | 150 | |
| 145 | 151 | /** |
| 146 | 152 | * Whether an entry means the site is broken, rather than the visitor. |
| @@ -162,10 +168,10 @@ | ||
| 162 | 168 | public static function is_fault( array $entry ) { |
| 163 | 169 | $type = $entry['type'] ?? ''; |
| 164 | 170 | |
| 165 | 171 | // Allowlist, so an unrecognised or new category is not a fault by default. |
| 166 | - // 'blocked' is deliberately absent: it is the label the browser puts on a | |
| 167 | - // stop the visitor can clear themselves. | |
| 172 | + // A visitor-correctable stop never reaches here: sanitize_entry() drops the | |
| 173 | + // browser's 'blocked' type before anything is written. | |
| 168 | 174 | if ( in_array( $type, [ 'error', 'response', 'message' ], true ) ) { |
| 169 | 175 | return true; |
| 170 | 176 | } |
| 171 | 177 | |
| @@ -518,16 +524,17 @@ | ||
| 518 | 524 | |
| 519 | 525 | /** |
| 520 | 526 | * The most recent whole log lines, up to a character budget. |
| 521 | 527 | * |
| 522 | - * An excerpt for reading and pasting. The budget is the caller's: the details | |
| 523 | - * dialog shows it on screen and copies it to a clipboard, neither of which has | |
| 524 | - * a length limit worth designing around, while an excerpt embedded anywhere | |
| 525 | - * length-bound needs a smaller one. Newest entries are the ones that describe | |
| 526 | - * the failure being reported, so the tail is the useful end and the oldest are | |
| 527 | - * what a smaller budget drops. | |
| 528 | + * An excerpt for a support email. The budget is the caller's, and it is a | |
| 529 | + * ceiling: the excerpt goes into a mailto: URL, which a mail client drops | |
| 530 | + * whole when it runs past its length limit. Newest entries are the ones that | |
| 531 | + * describe the failure being reported, so the tail is the useful end and the | |
| 532 | + * oldest are what a smaller budget drops. | |
| 528 | 533 | * |
| 529 | - * Whole lines only -- half a JSON object helps nobody. | |
| 534 | + * Whole lines only -- half a JSON object helps nobody -- except when the | |
| 535 | + * newest line alone is over the budget. That one is cut and marked, because | |
| 536 | + * an empty excerpt helps nobody either. | |
| 530 | 537 | * |
| 531 | 538 | * @param int $max_chars Character budget for the returned text. |
| 532 | 539 | * @since 2.12.6 |
| 533 | 540 | * @return array{text:string,shown:int,total:int} |
| @@ -582,14 +589,21 @@ | ||
| 582 | 589 | |
| 583 | 590 | foreach ( array_reverse( $lines ) as $line ) { |
| 584 | 591 | $length = strlen( $line ) + 1; |
| 585 | 592 | |
| 586 | - // Always keep one line, even if it alone exceeds the budget: an empty | |
| 587 | - // excerpt is worse than a long one. | |
| 588 | 593 | if ( $used + $length > $max_chars && ! empty( $kept ) ) { |
| 589 | 594 | break; |
| 590 | 595 | } |
| 591 | 596 | |
| 597 | + // Always keep one line, since an empty excerpt is worse than a long one -- | |
| 598 | + // but never past the budget, which is a ceiling the caller relies on. The | |
| 599 | + // start is kept because it names what failed. Lines are wp_json_encode()d, | |
| 600 | + // so they are ASCII and a byte cut cannot split a character. | |
| 601 | + if ( $length > $max_chars ) { | |
| 602 | + $marker = ' [truncated]'; | |
| 603 | + $line = substr( $line, 0, max( 0, $max_chars - strlen( $marker ) ) ) . $marker; | |
| 604 | + } | |
| 605 | + | |
| 592 | 606 | array_unshift( $kept, $line ); |
| 593 | 607 | $used += $length; |
| 594 | 608 | } |
| 595 | 609 | |
| @@ -660,9 +674,16 @@ | ||
| 660 | 674 | * @since 2.12.6 |
| 661 | 675 | * @return array<string,mixed> Empty when nothing usable survived. |
| 662 | 676 | */ |
| 663 | 677 | public static function sanitize_entry( array $raw ) { |
| 664 | - $allowed_types = [ 'network', 'response', 'error', 'message', 'blocked', 'after_submission' ]; | |
| 678 | + // 'blocked' is deliberately absent. It is the browser's label for a stop the | |
| 679 | + // visitor can clear themselves -- a required field left empty, an expired | |
| 680 | + // captcha, a declined card, a rejection naming the field to fix -- and those | |
| 681 | + // were the most common lines in a real log. Every one of them was pasted into | |
| 682 | + // support reports about some other failure and consumed budget the log does | |
| 683 | + // not give back, because it stops at its cap rather than rotating. Dropped at | |
| 684 | + // the shape gate so no caller, present or future, can write one. | |
| 685 | + $allowed_types = [ 'network', 'response', 'error', 'message', 'after_submission' ]; | |
| 665 | 686 | $type = isset( $raw['type'] ) ? sanitize_key( Helper::get_string_value( $raw['type'] ) ) : ''; |
| 666 | 687 | |
| 667 | 688 | if ( ! in_array( $type, $allowed_types, true ) ) { |
| 668 | 689 | return []; |