feat: add structured file-based logging with admin log viewer

- New WooList_Logger class writes to wp-content/uploads/woolist-logs/woolist.log
  - INFO level: subscription events, test connection results (always recorded)
  - ERROR level: API failures, config problems (always recorded + php error_log fallback)
  - DEBUG level: full request URLs (password redacted), raw responses, step-by-step
    flow (only when "Enable debug logging" is checked in settings)
  - Auto-rotates at 1 MB; log directory protected by .htaccess
- API class: logs every request URL (redacted) and raw response body at DEBUG,
  errors at ERROR; subscribe_email_to_list logs each step (lookup/create/add)
- Hooks class: logs hook fire, skip reasons, and sync intent at DEBUG/INFO/ERROR
- Shortcode class: logs AJAX submissions, coupon generation, and failures
- Admin: new Logging section with "Enable debug logging" checkbox;
  log viewer textarea (last 300 lines, dark theme) + Clear Log button
  both visible at bottom of WooCommerce → Settings → phpList tab

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 16:25:33 +01:00
parent 0429a282bc
commit f4c9e39493
6 changed files with 309 additions and 28 deletions

View File

@@ -101,24 +101,30 @@ class WooList_Shortcode {
public function handle_ajax(): void {
// 1. Verify nonce.
if ( ! check_ajax_referer( 'woolist_newsletter_nonce', 'nonce', false ) ) {
WooList_Logger::error( 'Newsletter AJAX: nonce verification failed' );
wp_send_json_error( [ 'message' => __( 'Security check failed. Please refresh and try again.', 'woolist-phplist' ) ], 403 );
}
// 2. Validate email.
$email = isset( $_POST['woolist_email'] ) ? sanitize_email( wp_unslash( $_POST['woolist_email'] ) ) : '';
if ( ! is_email( $email ) ) {
WooList_Logger::debug( 'Newsletter AJAX: invalid email submitted' );
wp_send_json_error( [ 'message' => __( 'Please enter a valid email address.', 'woolist-phplist' ) ], 400 );
}
WooList_Logger::debug( 'Newsletter AJAX: submission received email=' . $email );
// 3. Get list ID.
$list_id = (int) get_option( 'woolist_newsletter_list_id', 0 );
if ( $list_id < 1 ) {
WooList_Logger::error( 'Newsletter AJAX: no list ID configured' );
wp_send_json_error( [ 'message' => __( 'Newsletter is not configured. Please contact the site administrator.', 'woolist-phplist' ) ], 500 );
}
// 4. Subscribe email to phpList.
$result = $this->api->subscribe_email_to_list( $email, $list_id );
if ( ! $result['success'] ) {
WooList_Logger::error( 'Newsletter AJAX: subscription failed email=' . $email );
wp_send_json_error( [ 'message' => __( 'Could not subscribe your email. Please try again later.', 'woolist-phplist' ) ], 500 );
}
@@ -131,14 +137,18 @@ class WooList_Shortcode {
if ( $coupon_mode === 'fixed' ) {
$coupon_code = sanitize_text_field( get_option( 'woolist_coupon_fixed_code', '' ) );
WooList_Logger::debug( 'Newsletter AJAX: using fixed coupon code=' . $coupon_code );
} else {
$coupon_code = $this->generate_coupon( $email );
WooList_Logger::info( 'Newsletter AJAX: generated coupon code=' . $coupon_code . ' email=' . $email );
}
}
// Replace {coupon} placeholder in the thank-you message.
$thankyou_msg = str_replace( '{coupon}', esc_html( $coupon_code ), $thankyou_msg );
WooList_Logger::info( 'Newsletter AJAX: success email=' . $email );
wp_send_json_success(
[
'message' => wp_kses_post( $thankyou_msg ),
@@ -155,13 +165,15 @@ class WooList_Shortcode {
*/
private function generate_coupon( string $email ): string {
if ( ! class_exists( 'WC_Coupon' ) ) {
error_log( '[WooList] WC_Coupon class not available; cannot generate coupon.' );
WooList_Logger::error( 'WC_Coupon class not available; cannot generate coupon.' );
return '';
}
$discount_pct = (int) get_option( 'woolist_coupon_discount_pct', 10 );
$expiry_days = (int) get_option( 'woolist_coupon_expiry_days', 30 );
$coupon_code = 'WOOLIST-' . strtoupper( substr( md5( $email . time() ), 0, 8 ) );
$discount_pct = (int) get_option( 'woolist_coupon_discount_pct', 10 );
$expiry_days = (int) get_option( 'woolist_coupon_expiry_days', 30 );
$coupon_code = 'WOOLIST-' . strtoupper( substr( md5( $email . time() ), 0, 8 ) );
WooList_Logger::debug( 'Generating coupon code=' . $coupon_code . ' discount=' . $discount_pct . '% expiry_days=' . $expiry_days );
$expiry_date = '';
if ( $expiry_days > 0 ) {
@@ -180,11 +192,10 @@ class WooList_Shortcode {
);
if ( is_wp_error( $post_id ) ) {
error_log( '[WooList] Failed to create coupon post: ' . $post_id->get_error_message() );
WooList_Logger::error( 'Failed to create coupon post error=' . $post_id->get_error_message() );
return '';
}
// Set coupon meta via WC functions.
update_post_meta( $post_id, 'discount_type', 'percent' );
update_post_meta( $post_id, 'coupon_amount', (string) $discount_pct );
update_post_meta( $post_id, 'usage_limit', '1' );
@@ -196,6 +207,7 @@ class WooList_Shortcode {
update_post_meta( $post_id, 'date_expires', strtotime( $expiry_date ) );
}
WooList_Logger::debug( 'Coupon created post_id=' . $post_id . ' code=' . $coupon_code . ' expires=' . ( $expiry_date ?: 'never' ) );
return $coupon_code;
}
}