feat: initial implementation of WooList phpList Integration plugin v1.0.0

- phpList REST API wrapper with subscriber get-or-create + list assignment
- WooCommerce Settings tab (5 sections: connection, orders, signup, newsletter)
- Test Connection button via admin-post action
- Hooks for order completed/cancelled and user_register events
- [woolist_newsletter] shortcode with jQuery AJAX, fixed & auto-generated coupons
- Responsive front-end form styles and JS with loading/success/error states

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 11:51:12 +01:00
commit 6e23e40bf3
7 changed files with 1055 additions and 0 deletions

View File

@@ -0,0 +1,341 @@
<?php
/**
* WooCommerce Settings tab for WooList — phpList Integration.
*
* @package WooList
*/
defined( 'ABSPATH' ) || exit;
class WooList_Admin {
/** @var WooList_API */
private WooList_API $api;
public function __construct( WooList_API $api ) {
$this->api = $api;
// Register the custom WooCommerce settings tab.
add_filter( 'woocommerce_settings_tabs_array', [ $this, 'add_settings_tab' ], 50 );
add_action( 'woocommerce_settings_tabs_woolist', [ $this, 'render_settings' ] );
add_action( 'woocommerce_update_options_woolist', [ $this, 'save_settings' ] );
// Test connection admin-post handler.
add_action( 'admin_post_woolist_test_connection', [ $this, 'handle_test_connection' ] );
// Enqueue admin JS/CSS only on the WC settings page.
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] );
}
/**
* Add "phpList" tab to WooCommerce → Settings.
*
* @param array $tabs Existing tabs.
* @return array
*/
public function add_settings_tab( array $tabs ): array {
$tabs['woolist'] = __( 'phpList', 'woolist-phplist' );
return $tabs;
}
/**
* Render the settings tab content.
*/
public function render_settings(): void {
woocommerce_admin_fields( $this->get_settings() );
$this->render_test_connection_button();
}
/**
* Save settings when the form is submitted.
*/
public function save_settings(): void {
woocommerce_update_options( $this->get_settings() );
}
/**
* Output the "Test Connection" button below the settings form.
*/
private function render_test_connection_button(): void {
$action_url = wp_nonce_url(
admin_url( 'admin-post.php?action=woolist_test_connection' ),
'woolist_test_connection'
);
// Display any stored transient notice.
$notice = get_transient( 'woolist_test_connection_notice_' . get_current_user_id() );
if ( $notice ) {
delete_transient( 'woolist_test_connection_notice_' . get_current_user_id() );
$type = $notice['success'] ? 'updated' : 'error';
printf(
'<div class="notice notice-%s inline"><p>%s</p></div>',
esc_attr( $type ),
esc_html( $notice['message'] )
);
}
echo '<p><a href="' . esc_url( $action_url ) . '" class="button button-secondary">'
. esc_html__( 'Test Connection', 'woolist-phplist' )
. '</a></p>';
}
/**
* Handle the "Test Connection" form action.
*/
public function handle_test_connection(): void {
check_admin_referer( 'woolist_test_connection' );
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( esc_html__( 'You do not have permission to perform this action.', 'woolist-phplist' ) );
}
$result = $this->api->lists_get();
$user_id = get_current_user_id();
if ( is_wp_error( $result ) ) {
set_transient(
'woolist_test_connection_notice_' . $user_id,
[
'success' => false,
'message' => __( 'Connection failed: ', 'woolist-phplist' ) . $result->get_error_message(),
],
60
);
} else {
$count = is_array( $result ) ? count( $result ) : '?';
set_transient(
'woolist_test_connection_notice_' . $user_id,
[
'success' => true,
'message' => sprintf(
/* translators: %d: number of lists found */
__( 'Connection successful! Found %d list(s) in phpList.', 'woolist-phplist' ),
$count
),
],
60
);
}
wp_safe_redirect( admin_url( 'admin.php?page=wc-settings&tab=woolist' ) );
exit;
}
/**
* Enqueue admin-side assets on WC Settings → phpList tab.
*/
public function enqueue_admin_assets(): void {
$screen = get_current_screen();
if ( ! $screen || strpos( $screen->id, 'woocommerce' ) === false ) {
return;
}
// No custom admin JS/CSS needed yet; WC handles the settings form.
}
/**
* Build and return the WooCommerce settings field definitions.
*
* @return array
*/
private function get_settings(): array {
return [
// ── Section 1: Connection ────────────────────────────────────────
[
'title' => __( 'phpList Connection', 'woolist-phplist' ),
'type' => 'title',
'id' => 'woolist_section_connection',
],
[
'title' => __( 'phpList Base URL', 'woolist-phplist' ),
'desc' => __( 'e.g. https://newsletter.example.com', 'woolist-phplist' ),
'id' => 'woolist_phplist_url',
'type' => 'text',
'default' => '',
'desc_tip' => true,
],
[
'title' => __( 'Login', 'woolist-phplist' ),
'desc' => __( 'phpList admin username.', 'woolist-phplist' ),
'id' => 'woolist_phplist_login',
'type' => 'text',
'default' => '',
'desc_tip' => true,
],
[
'title' => __( 'Password', 'woolist-phplist' ),
'desc' => __( 'phpList admin password.', 'woolist-phplist' ),
'id' => 'woolist_phplist_password',
'type' => 'password',
'default' => '',
'desc_tip' => true,
],
[
'type' => 'sectionend',
'id' => 'woolist_section_connection',
],
// ── Section 2: Completed Orders ──────────────────────────────────
[
'title' => __( 'Completed Orders', 'woolist-phplist' ),
'type' => 'title',
'id' => 'woolist_section_completed',
],
[
'title' => __( 'Enable sync', 'woolist-phplist' ),
'desc' => __( 'Subscribe customers to phpList when an order is completed.', 'woolist-phplist' ),
'id' => 'woolist_sync_completed',
'type' => 'checkbox',
'default' => 'no',
],
[
'title' => __( 'List ID', 'woolist-phplist' ),
'desc' => __( 'phpList list ID to subscribe completed-order customers to.', 'woolist-phplist' ),
'id' => 'woolist_completed_list_id',
'type' => 'number',
'default' => '',
'custom_attributes' => [ 'min' => '1' ],
'desc_tip' => true,
],
[
'type' => 'sectionend',
'id' => 'woolist_section_completed',
],
// ── Section 3: Cancelled Orders ──────────────────────────────────
[
'title' => __( 'Cancelled Orders', 'woolist-phplist' ),
'type' => 'title',
'id' => 'woolist_section_cancelled',
],
[
'title' => __( 'Enable sync', 'woolist-phplist' ),
'desc' => __( 'Subscribe customers to phpList when an order is cancelled.', 'woolist-phplist' ),
'id' => 'woolist_sync_cancelled',
'type' => 'checkbox',
'default' => 'no',
],
[
'title' => __( 'List ID', 'woolist-phplist' ),
'desc' => __( 'phpList list ID to subscribe cancelled-order customers to.', 'woolist-phplist' ),
'id' => 'woolist_cancelled_list_id',
'type' => 'number',
'default' => '',
'custom_attributes' => [ 'min' => '1' ],
'desc_tip' => true,
],
[
'type' => 'sectionend',
'id' => 'woolist_section_cancelled',
],
// ── Section 4: Account Signup ────────────────────────────────────
[
'title' => __( 'Account Signup', 'woolist-phplist' ),
'type' => 'title',
'id' => 'woolist_section_signup',
],
[
'title' => __( 'Enable sync', 'woolist-phplist' ),
'desc' => __( 'Subscribe new WordPress users to phpList when they register.', 'woolist-phplist' ),
'id' => 'woolist_sync_signup',
'type' => 'checkbox',
'default' => 'no',
],
[
'title' => __( 'List ID', 'woolist-phplist' ),
'desc' => __( 'phpList list ID for new account signups.', 'woolist-phplist' ),
'id' => 'woolist_signup_list_id',
'type' => 'number',
'default' => '',
'custom_attributes' => [ 'min' => '1' ],
'desc_tip' => true,
],
[
'type' => 'sectionend',
'id' => 'woolist_section_signup',
],
// ── Section 5: Newsletter Shortcode ──────────────────────────────
[
'title' => __( 'Newsletter Shortcode', 'woolist-phplist' ),
'type' => 'title',
'desc' => __( 'Settings for the [woolist_newsletter] shortcode.', 'woolist-phplist' ),
'id' => 'woolist_section_newsletter',
],
[
'title' => __( 'Enable sync', 'woolist-phplist' ),
'desc' => __( 'Enable the [woolist_newsletter] shortcode.', 'woolist-phplist' ),
'id' => 'woolist_sync_newsletter',
'type' => 'checkbox',
'default' => 'no',
],
[
'title' => __( 'List ID', 'woolist-phplist' ),
'desc' => __( 'phpList list ID for newsletter shortcode subscribers.', 'woolist-phplist' ),
'id' => 'woolist_newsletter_list_id',
'type' => 'number',
'default' => '',
'custom_attributes' => [ 'min' => '1' ],
'desc_tip' => true,
],
[
'title' => __( 'Enable incentive coupon', 'woolist-phplist' ),
'desc' => __( 'Send a WooCommerce coupon after newsletter signup.', 'woolist-phplist' ),
'id' => 'woolist_newsletter_enable_coupon',
'type' => 'checkbox',
'default' => 'no',
],
[
'title' => __( 'Coupon mode', 'woolist-phplist' ),
'desc' => __( 'Choose how the coupon is provided.', 'woolist-phplist' ),
'id' => 'woolist_coupon_mode',
'type' => 'select',
'default' => 'fixed',
'options' => [
'fixed' => __( 'Fixed code', 'woolist-phplist' ),
'autogenerate' => __( 'Auto-generate unique code', 'woolist-phplist' ),
],
'desc_tip' => true,
],
[
'title' => __( 'Fixed coupon code', 'woolist-phplist' ),
'desc' => __( 'The coupon code to show when mode is set to "Fixed code".', 'woolist-phplist' ),
'id' => 'woolist_coupon_fixed_code',
'type' => 'text',
'default' => '',
'desc_tip' => true,
],
[
'title' => __( 'Discount (%)', 'woolist-phplist' ),
'desc' => __( 'Percentage discount for auto-generated coupons.', 'woolist-phplist' ),
'id' => 'woolist_coupon_discount_pct',
'type' => 'number',
'default' => '10',
'custom_attributes' => [ 'min' => '1', 'max' => '100' ],
'desc_tip' => true,
],
[
'title' => __( 'Expiry (days)', 'woolist-phplist' ),
'desc' => __( 'Days until auto-generated coupon expires. Use 0 for no expiry.', 'woolist-phplist' ),
'id' => 'woolist_coupon_expiry_days',
'type' => 'number',
'default' => '30',
'custom_attributes' => [ 'min' => '0' ],
'desc_tip' => true,
],
[
'title' => __( 'Thank-you message', 'woolist-phplist' ),
'desc' => __( 'Message shown after signup. Use {coupon} as a placeholder for the coupon code.', 'woolist-phplist' ),
'id' => 'woolist_newsletter_thankyou',
'type' => 'textarea',
'default' => __( 'Thank you for subscribing! Use coupon {coupon} for 10% off your first order.', 'woolist-phplist' ),
'css' => 'width:100%;height:80px;',
'desc_tip' => true,
],
[
'type' => 'sectionend',
'id' => 'woolist_section_newsletter',
],
];
}
}

View File

@@ -0,0 +1,195 @@
<?php
/**
* phpList REST API wrapper.
*
* @package WooList
*/
defined( 'ABSPATH' ) || exit;
class WooList_API {
/**
* Retrieve a saved plugin option.
*
* @param string $key Option key (without prefix).
* @param mixed $default Default value.
* @return mixed
*/
public function get_option( string $key, $default = '' ) {
return get_option( 'woolist_' . $key, $default );
}
/**
* Build the full phpList REST API URL.
*
* All parameters are passed as query-string arguments because the phpList
* REST API reads them from $_GET regardless of the HTTP method used.
*
* @param string $cmd phpList API command.
* @param array $extra Additional query parameters.
* @return string|WP_Error Full URL or WP_Error when base URL is missing.
*/
public function build_url( string $cmd, array $extra = [] ) {
$base = $this->get_option( 'phplist_url' );
$base = rtrim( $base, '/' );
if ( empty( $base ) ) {
return new WP_Error( 'woolist_no_url', __( 'phpList base URL is not configured.', 'woolist-phplist' ) );
}
$params = array_merge(
[
'page' => 'call',
'pi' => 'restapi',
'login' => $this->get_option( 'phplist_login' ),
'password' => $this->get_option( 'phplist_password' ),
'cmd' => $cmd,
],
$extra
);
return $base . '/admin/?' . http_build_query( $params );
}
/**
* Execute an API call and return decoded JSON data.
*
* @param string $cmd phpList API command.
* @param array $extra Additional query parameters.
* @return array|WP_Error Decoded response data or WP_Error.
*/
public function call( string $cmd, array $extra = [] ) {
$url = $this->build_url( $cmd, $extra );
if ( is_wp_error( $url ) ) {
return $url;
}
$response = wp_remote_post( $url, [ 'timeout' => 15 ] );
if ( is_wp_error( $response ) ) {
error_log( '[WooList] API request failed for cmd=' . $cmd . ': ' . $response->get_error_message() );
return $response;
}
$code = wp_remote_retrieve_response_code( $response );
$body = wp_remote_retrieve_body( $response );
if ( $code < 200 || $code >= 300 ) {
error_log( '[WooList] API returned HTTP ' . $code . ' for cmd=' . $cmd );
return new WP_Error( 'woolist_http_error', 'HTTP error ' . $code );
}
$data = json_decode( $body, true );
if ( json_last_error() !== JSON_ERROR_NONE ) {
error_log( '[WooList] API returned invalid JSON for cmd=' . $cmd . ': ' . $body );
return new WP_Error( 'woolist_json_error', 'Invalid JSON response from phpList.' );
}
// phpList REST API signals errors via a "status" field.
if ( isset( $data['status'] ) && strtolower( $data['status'] ) === 'error' ) {
$message = $data['errormessage'] ?? $data['message'] ?? 'Unknown API error';
error_log( '[WooList] API error for cmd=' . $cmd . ': ' . $message );
return new WP_Error( 'woolist_api_error', $message );
}
return $data;
}
/**
* Retrieve a subscriber by email address.
*
* @param string $email Subscriber email.
* @return array|WP_Error
*/
public function subscriber_get_by_email( string $email ) {
return $this->call( 'subscriberGetByEmail', [ 'email' => rawurlencode( $email ) ] );
}
/**
* Add a new confirmed subscriber.
*
* @param string $email Subscriber email.
* @return array|WP_Error
*/
public function subscriber_add( string $email ) {
return $this->call(
'subscriberAdd',
[
'email' => rawurlencode( $email ),
'confirmed' => 1,
'htmlemail' => 1,
]
);
}
/**
* Add a subscriber (by ID) to a phpList list.
*
* @param int $list_id phpList list ID.
* @param int $subscriber_id phpList subscriber ID.
* @return array|WP_Error
*/
public function list_subscriber_add( int $list_id, int $subscriber_id ) {
return $this->call(
'listSubscriberAdd',
[
'listid' => $list_id,
'subscriberid' => $subscriber_id,
]
);
}
/**
* High-level helper: subscribe an email to a list.
*
* Gets or creates the subscriber, then adds them to the list.
*
* @param string $email Subscriber email address.
* @param int $list_id phpList list ID.
* @return array{success: bool, subscriber_id: int|null}
*/
public function subscribe_email_to_list( string $email, int $list_id ): array {
// Try to find existing subscriber.
$subscriber_id = null;
$existing = $this->subscriber_get_by_email( $email );
if ( ! is_wp_error( $existing ) && ! empty( $existing['id'] ) ) {
$subscriber_id = (int) $existing['id'];
} else {
// Create new subscriber.
$added = $this->subscriber_add( $email );
if ( is_wp_error( $added ) ) {
error_log( '[WooList] Could not add subscriber ' . $email . ': ' . $added->get_error_message() );
return [ 'success' => false, 'subscriber_id' => null ];
}
$subscriber_id = isset( $added['id'] ) ? (int) $added['id'] : null;
}
if ( ! $subscriber_id ) {
error_log( '[WooList] Could not determine subscriber ID for ' . $email );
return [ 'success' => false, 'subscriber_id' => null ];
}
// Add subscriber to the list.
$result = $this->list_subscriber_add( $list_id, $subscriber_id );
if ( is_wp_error( $result ) ) {
error_log( '[WooList] Could not add subscriber ' . $subscriber_id . ' to list ' . $list_id . ': ' . $result->get_error_message() );
return [ 'success' => false, 'subscriber_id' => $subscriber_id ];
}
return [ 'success' => true, 'subscriber_id' => $subscriber_id ];
}
/**
* Retrieve all lists (used for connection testing).
*
* @return array|WP_Error
*/
public function lists_get() {
return $this->call( 'listsGet' );
}
}

View File

@@ -0,0 +1,104 @@
<?php
/**
* WooCommerce and WordPress action hooks for WooList.
*
* @package WooList
*/
defined( 'ABSPATH' ) || exit;
class WooList_Hooks {
/** @var WooList_API */
private WooList_API $api;
public function __construct( WooList_API $api ) {
$this->api = $api;
add_action( 'woocommerce_order_status_completed', [ $this, 'on_order_completed' ] );
add_action( 'woocommerce_order_status_cancelled', [ $this, 'on_order_cancelled' ] );
add_action( 'user_register', [ $this, 'on_user_register' ] );
}
/**
* Sync billing email to phpList when an order is marked completed.
*
* @param int $order_id WooCommerce order ID.
*/
public function on_order_completed( int $order_id ): void {
if ( get_option( 'woolist_sync_completed' ) !== 'yes' ) {
return;
}
$list_id = (int) get_option( 'woolist_completed_list_id', 0 );
if ( $list_id < 1 ) {
error_log( '[WooList] Completed order sync enabled but no list ID configured.' );
return;
}
$order = wc_get_order( $order_id );
if ( ! $order ) {
return;
}
$email = $order->get_billing_email();
if ( ! is_email( $email ) ) {
return;
}
$this->api->subscribe_email_to_list( $email, $list_id );
}
/**
* Sync billing email to phpList when an order is cancelled.
*
* @param int $order_id WooCommerce order ID.
*/
public function on_order_cancelled( int $order_id ): void {
if ( get_option( 'woolist_sync_cancelled' ) !== 'yes' ) {
return;
}
$list_id = (int) get_option( 'woolist_cancelled_list_id', 0 );
if ( $list_id < 1 ) {
error_log( '[WooList] Cancelled order sync enabled but no list ID configured.' );
return;
}
$order = wc_get_order( $order_id );
if ( ! $order ) {
return;
}
$email = $order->get_billing_email();
if ( ! is_email( $email ) ) {
return;
}
$this->api->subscribe_email_to_list( $email, $list_id );
}
/**
* Sync new user email to phpList when a WordPress account is created.
*
* @param int $user_id Newly registered user ID.
*/
public function on_user_register( int $user_id ): void {
if ( get_option( 'woolist_sync_signup' ) !== 'yes' ) {
return;
}
$list_id = (int) get_option( 'woolist_signup_list_id', 0 );
if ( $list_id < 1 ) {
error_log( '[WooList] Account signup sync enabled but no list ID configured.' );
return;
}
$user = get_userdata( $user_id );
if ( ! $user || ! is_email( $user->user_email ) ) {
return;
}
$this->api->subscribe_email_to_list( $user->user_email, $list_id );
}
}

View File

@@ -0,0 +1,201 @@
<?php
/**
* [woolist_newsletter] shortcode and AJAX handler.
*
* @package WooList
*/
defined( 'ABSPATH' ) || exit;
class WooList_Shortcode {
/** @var WooList_API */
private WooList_API $api;
public function __construct( WooList_API $api ) {
$this->api = $api;
add_shortcode( 'woolist_newsletter', [ $this, 'render_shortcode' ] );
// Register AJAX handlers for logged-in and guest visitors.
add_action( 'wp_ajax_woolist_newsletter_submit', [ $this, 'handle_ajax' ] );
add_action( 'wp_ajax_nopriv_woolist_newsletter_submit', [ $this, 'handle_ajax' ] );
// Enqueue front-end assets.
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_assets' ] );
}
/**
* Enqueue CSS and JS for the newsletter form.
*/
public function enqueue_assets(): void {
wp_enqueue_style(
'woolist-public',
WOOLIST_URL . 'assets/css/woolist-public.css',
[],
WOOLIST_VERSION
);
wp_enqueue_script(
'woolist-public',
WOOLIST_URL . 'assets/js/woolist-public.js',
[ 'jquery' ],
WOOLIST_VERSION,
true
);
wp_localize_script(
'woolist-public',
'woolist',
[
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'woolist_newsletter_nonce' ),
'i18n' => [
'subscribing' => __( 'Subscribing…', 'woolist-phplist' ),
'subscribe' => __( 'Subscribe', 'woolist-phplist' ),
'error' => __( 'Something went wrong. Please try again.', 'woolist-phplist' ),
],
]
);
}
/**
* Render the [woolist_newsletter] shortcode.
*
* @return string HTML output.
*/
public function render_shortcode(): string {
if ( get_option( 'woolist_sync_newsletter' ) !== 'yes' ) {
if ( defined( 'WP_DEBUG' ) && WP_DEBUG && current_user_can( 'manage_options' ) ) {
return '<!-- WooList: newsletter shortcode is disabled in settings -->';
}
return '';
}
ob_start();
?>
<div class="woolist-newsletter-wrap">
<form id="woolist-newsletter-form" novalidate>
<div class="woolist-form-row">
<input
type="email"
name="woolist_email"
class="woolist-email-input"
placeholder="<?php esc_attr_e( 'Your email address', 'woolist-phplist' ); ?>"
required
/>
<button type="submit" class="woolist-submit-btn">
<?php esc_html_e( 'Subscribe', 'woolist-phplist' ); ?>
</button>
</div>
</form>
<div class="woolist-response" style="display:none;" aria-live="polite"></div>
</div>
<?php
return ob_get_clean();
}
/**
* Handle AJAX newsletter subscription submission.
*/
public function handle_ajax(): void {
// 1. Verify nonce.
if ( ! check_ajax_referer( 'woolist_newsletter_nonce', 'nonce', false ) ) {
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 ) ) {
wp_send_json_error( [ 'message' => __( 'Please enter a valid email address.', 'woolist-phplist' ) ], 400 );
}
// 3. Get list ID.
$list_id = (int) get_option( 'woolist_newsletter_list_id', 0 );
if ( $list_id < 1 ) {
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'] ) {
wp_send_json_error( [ 'message' => __( 'Could not subscribe your email. Please try again later.', 'woolist-phplist' ) ], 500 );
}
// 5. Handle coupon generation.
$coupon_code = '';
$thankyou_msg = get_option( 'woolist_newsletter_thankyou', __( 'Thank you for subscribing!', 'woolist-phplist' ) );
if ( get_option( 'woolist_newsletter_enable_coupon' ) === 'yes' ) {
$coupon_mode = get_option( 'woolist_coupon_mode', 'fixed' );
if ( $coupon_mode === 'fixed' ) {
$coupon_code = sanitize_text_field( get_option( 'woolist_coupon_fixed_code', '' ) );
} else {
$coupon_code = $this->generate_coupon( $email );
}
}
// Replace {coupon} placeholder in the thank-you message.
$thankyou_msg = str_replace( '{coupon}', esc_html( $coupon_code ), $thankyou_msg );
wp_send_json_success(
[
'message' => wp_kses_post( $thankyou_msg ),
'coupon' => $coupon_code,
]
);
}
/**
* Generate a unique WooCommerce percentage coupon for the subscriber.
*
* @param string $email Subscriber email.
* @return string Generated coupon code, or empty string on failure.
*/
private function generate_coupon( string $email ): string {
if ( ! class_exists( 'WC_Coupon' ) ) {
error_log( '[WooList] 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 ) );
$expiry_date = '';
if ( $expiry_days > 0 ) {
$expiry_date = gmdate( 'Y-m-d', strtotime( '+' . $expiry_days . ' days' ) );
}
$post_id = wp_insert_post(
[
'post_title' => $coupon_code,
'post_name' => $coupon_code,
'post_status' => 'publish',
'post_type' => 'shop_coupon',
'post_excerpt' => 'WooList newsletter signup coupon for ' . $email,
],
true
);
if ( is_wp_error( $post_id ) ) {
error_log( '[WooList] Failed to create coupon post: ' . $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' );
update_post_meta( $post_id, 'usage_limit_per_user', '1' );
update_post_meta( $post_id, 'individual_use', 'yes' );
update_post_meta( $post_id, 'customer_email', [ $email ] );
if ( $expiry_date ) {
update_post_meta( $post_id, 'date_expires', strtotime( $expiry_date ) );
}
return $coupon_code;
}
}