Files
WooBC/woo-business-central/admin/partials/wbc-admin-display.php
Malin c06d6e4352 fix: client secret getting corrupted on save
Two bugs fixed:

1. sanitize_text_field() was stripping special characters from Azure AD
   client secrets (e.g. %XX sequences, angle brackets). Replaced with
   trim() to preserve the raw secret before encryption.

2. All settings tabs shared one option group (wbc_settings), so saving
   from any tab would trigger sanitize callbacks for ALL settings. This
   caused checkboxes on other tabs to reset to 'no' and could interfere
   with the client secret. Split into per-tab groups: wbc_connection,
   wbc_sync, wbc_orders.

Also clears OAuth token cache when client secret is changed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 10:15:07 +01:00

400 lines
23 KiB
PHP

<?php
/**
* Admin settings page template
*
* @package WooBusinessCentral
*/
// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$tabs = array(
'connection' => __( 'Connection', 'woo-business-central' ),
'sync' => __( 'Sync Settings', 'woo-business-central' ),
'orders' => __( 'Order Settings', 'woo-business-central' ),
'logs' => __( 'Logs', 'woo-business-central' ),
);
?>
<div class="wrap wbc-admin-wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<nav class="nav-tab-wrapper wbc-nav-tabs">
<?php foreach ( $tabs as $tab_id => $tab_name ) : ?>
<a href="<?php echo esc_url( add_query_arg( 'tab', $tab_id ) ); ?>"
class="nav-tab <?php echo $current_tab === $tab_id ? 'nav-tab-active' : ''; ?>">
<?php echo esc_html( $tab_name ); ?>
</a>
<?php endforeach; ?>
</nav>
<div class="wbc-admin-content">
<?php if ( $current_tab === 'connection' ) : ?>
<!-- Connection Settings -->
<form method="post" action="options.php" class="wbc-settings-form">
<?php settings_fields( 'wbc_connection' ); ?>
<div class="wbc-card">
<h2><?php esc_html_e( 'Microsoft Azure AD Credentials', 'woo-business-central' ); ?></h2>
<p class="description">
<?php esc_html_e( 'Enter your Azure AD application credentials to connect to Business Central.', 'woo-business-central' ); ?>
</p>
<table class="form-table">
<tr>
<th scope="row">
<label for="wbc_tenant_id"><?php esc_html_e( 'Tenant ID', 'woo-business-central' ); ?></label>
</th>
<td>
<input type="text" id="wbc_tenant_id" name="wbc_tenant_id"
value="<?php echo esc_attr( get_option( 'wbc_tenant_id', '' ) ); ?>"
class="regular-text" />
<p class="description">
<?php esc_html_e( 'Your Azure AD tenant ID (GUID format).', 'woo-business-central' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wbc_client_id"><?php esc_html_e( 'Client ID', 'woo-business-central' ); ?></label>
</th>
<td>
<input type="text" id="wbc_client_id" name="wbc_client_id"
value="<?php echo esc_attr( get_option( 'wbc_client_id', '' ) ); ?>"
class="regular-text" />
<p class="description">
<?php esc_html_e( 'Application (client) ID from Azure AD.', 'woo-business-central' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wbc_client_secret"><?php esc_html_e( 'Client Secret', 'woo-business-central' ); ?></label>
</th>
<td>
<?php
$has_secret = ! empty( WBC_OAuth::get_client_secret() );
$placeholder = $has_secret ? '********' : '';
?>
<input type="password" id="wbc_client_secret" name="wbc_client_secret"
value="" placeholder="<?php echo esc_attr( $placeholder ); ?>"
class="regular-text" autocomplete="new-password" />
<p class="description">
<?php if ( $has_secret ) : ?>
<?php esc_html_e( 'Leave blank to keep existing secret. Enter a new value to update.', 'woo-business-central' ); ?>
<?php else : ?>
<?php esc_html_e( 'Client secret from Azure AD application.', 'woo-business-central' ); ?>
<?php endif; ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wbc_environment"><?php esc_html_e( 'Environment', 'woo-business-central' ); ?></label>
</th>
<td>
<select id="wbc_environment" name="wbc_environment">
<option value="production" <?php selected( get_option( 'wbc_environment', 'production' ), 'production' ); ?>>
<?php esc_html_e( 'Production', 'woo-business-central' ); ?>
</option>
<option value="sandbox" <?php selected( get_option( 'wbc_environment' ), 'sandbox' ); ?>>
<?php esc_html_e( 'Sandbox', 'woo-business-central' ); ?>
</option>
</select>
</td>
</tr>
<tr>
<th scope="row">
<label for="wbc_company_id"><?php esc_html_e( 'Company ID', 'woo-business-central' ); ?></label>
</th>
<td>
<input type="text" id="wbc_company_id" name="wbc_company_id"
value="<?php echo esc_attr( get_option( 'wbc_company_id', '' ) ); ?>"
class="regular-text" />
<button type="button" id="wbc-load-companies" class="button">
<?php esc_html_e( 'Load Companies', 'woo-business-central' ); ?>
</button>
<p class="description">
<?php esc_html_e( 'Business Central company ID (GUID). Click "Load Companies" after saving credentials.', 'woo-business-central' ); ?>
</p>
<div id="wbc-companies-list" style="display: none; margin-top: 10px;">
<select id="wbc-company-select">
<option value=""><?php esc_html_e( 'Select a company...', 'woo-business-central' ); ?></option>
</select>
</div>
</td>
</tr>
</table>
<p class="submit">
<?php submit_button( __( 'Save Settings', 'woo-business-central' ), 'primary', 'submit', false ); ?>
<button type="button" id="wbc-test-connection" class="button button-secondary">
<?php esc_html_e( 'Test Connection', 'woo-business-central' ); ?>
</button>
<span id="wbc-connection-status" class="wbc-status"></span>
</p>
</div>
</form>
<?php elseif ( $current_tab === 'sync' ) : ?>
<!-- Sync Settings -->
<form method="post" action="options.php" class="wbc-settings-form">
<?php settings_fields( 'wbc_sync' ); ?>
<div class="wbc-card">
<h2><?php esc_html_e( 'Product Sync Settings', 'woo-business-central' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row">
<label for="wbc_sync_frequency"><?php esc_html_e( 'Sync Frequency', 'woo-business-central' ); ?></label>
</th>
<td>
<select id="wbc_sync_frequency" name="wbc_sync_frequency">
<option value="hourly" <?php selected( get_option( 'wbc_sync_frequency', 'daily' ), 'hourly' ); ?>>
<?php esc_html_e( 'Hourly', 'woo-business-central' ); ?>
</option>
<option value="twice_daily" <?php selected( get_option( 'wbc_sync_frequency' ), 'twice_daily' ); ?>>
<?php esc_html_e( 'Twice Daily', 'woo-business-central' ); ?>
</option>
<option value="daily" <?php selected( get_option( 'wbc_sync_frequency' ), 'daily' ); ?>>
<?php esc_html_e( 'Daily', 'woo-business-central' ); ?>
</option>
</select>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Enable Stock Sync', 'woo-business-central' ); ?></th>
<td>
<label>
<input type="checkbox" name="wbc_enable_stock_sync" value="yes"
<?php checked( get_option( 'wbc_enable_stock_sync', 'yes' ), 'yes' ); ?> />
<?php esc_html_e( 'Sync stock levels from Business Central to WooCommerce', 'woo-business-central' ); ?>
</label>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Enable Price Sync', 'woo-business-central' ); ?></th>
<td>
<label>
<input type="checkbox" name="wbc_enable_price_sync" value="yes"
<?php checked( get_option( 'wbc_enable_price_sync', 'yes' ), 'yes' ); ?> />
<?php esc_html_e( 'Sync prices from Business Central to WooCommerce', 'woo-business-central' ); ?>
</label>
</td>
</tr>
</table>
<p class="submit">
<?php submit_button( __( 'Save Settings', 'woo-business-central' ), 'primary', 'submit', false ); ?>
</p>
</div>
<div class="wbc-card">
<h2><?php esc_html_e( 'Sync Status', 'woo-business-central' ); ?></h2>
<table class="wbc-status-table">
<tr>
<th><?php esc_html_e( 'Last Sync:', 'woo-business-central' ); ?></th>
<td><?php echo esc_html( WBC_Cron::get_last_sync_formatted() ); ?></td>
</tr>
<tr>
<th><?php esc_html_e( 'Next Scheduled Sync:', 'woo-business-central' ); ?></th>
<td><?php echo esc_html( WBC_Cron::get_next_sync_formatted() ); ?></td>
</tr>
</table>
<p>
<button type="button" id="wbc-manual-sync" class="button button-primary">
<?php esc_html_e( 'Sync Now', 'woo-business-central' ); ?>
</button>
<span id="wbc-sync-status" class="wbc-status"></span>
</p>
</div>
</form>
<?php elseif ( $current_tab === 'orders' ) : ?>
<!-- Order Settings -->
<form method="post" action="options.php" class="wbc-settings-form">
<?php settings_fields( 'wbc_orders' ); ?>
<div class="wbc-card">
<h2><?php esc_html_e( 'Order Sync Settings', 'woo-business-central' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Enable Order Sync', 'woo-business-central' ); ?></th>
<td>
<label>
<input type="checkbox" name="wbc_enable_order_sync" value="yes"
<?php checked( get_option( 'wbc_enable_order_sync', 'yes' ), 'yes' ); ?> />
<?php esc_html_e( 'Sync orders to Business Central when payment is received', 'woo-business-central' ); ?>
</label>
</td>
</tr>
<tr>
<th scope="row">
<label for="wbc_default_payment_terms_id"><?php esc_html_e( 'Default Payment Terms ID', 'woo-business-central' ); ?></label>
</th>
<td>
<input type="text" id="wbc_default_payment_terms_id" name="wbc_default_payment_terms_id"
value="<?php echo esc_attr( get_option( 'wbc_default_payment_terms_id', '' ) ); ?>"
class="regular-text" />
<p class="description">
<?php esc_html_e( 'Optional. Business Central payment terms ID to use for new orders.', 'woo-business-central' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wbc_default_shipment_method_id"><?php esc_html_e( 'Default Shipment Method ID', 'woo-business-central' ); ?></label>
</th>
<td>
<input type="text" id="wbc_default_shipment_method_id" name="wbc_default_shipment_method_id"
value="<?php echo esc_attr( get_option( 'wbc_default_shipment_method_id', '' ) ); ?>"
class="regular-text" />
<p class="description">
<?php esc_html_e( 'Optional. Business Central shipment method ID to use for new orders.', 'woo-business-central' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wbc_shipping_item_number"><?php esc_html_e( 'Shipping Item Number', 'woo-business-central' ); ?></label>
</th>
<td>
<input type="text" id="wbc_shipping_item_number" name="wbc_shipping_item_number"
value="<?php echo esc_attr( get_option( 'wbc_shipping_item_number', '' ) ); ?>"
class="regular-text" />
<p class="description">
<?php esc_html_e( 'Optional. Business Central item number to use for shipping charges.', 'woo-business-central' ); ?>
</p>
</td>
</tr>
</table>
<p class="submit">
<?php submit_button( __( 'Save Settings', 'woo-business-central' ), 'primary', 'submit', false ); ?>
</p>
</div>
</form>
<?php elseif ( $current_tab === 'logs' ) : ?>
<!-- Logs -->
<div class="wbc-card">
<h2><?php esc_html_e( 'Sync Logs', 'woo-business-central' ); ?></h2>
<p>
<button type="button" id="wbc-clear-logs" class="button">
<?php esc_html_e( 'Clear All Logs', 'woo-business-central' ); ?>
</button>
<a href="<?php echo esc_url( add_query_arg( array( 'wbc_export_logs' => '1', '_wpnonce' => wp_create_nonce( 'wbc_export_logs' ) ) ) ); ?>"
class="button">
<?php esc_html_e( 'Download CSV', 'woo-business-central' ); ?>
</a>
</p>
<?php
$log_level = isset( $_GET['log_level'] ) ? sanitize_text_field( wp_unslash( $_GET['log_level'] ) ) : '';
$page_num = isset( $_GET['log_page'] ) ? max( 1, absint( $_GET['log_page'] ) ) : 1;
$per_page = 50;
$logs_data = WBC_Admin::get_logs_for_display( array(
'level' => $log_level,
'limit' => $per_page,
'offset' => ( $page_num - 1 ) * $per_page,
) );
$logs = $logs_data['logs'];
$total = $logs_data['total'];
$total_pages = ceil( $total / $per_page );
?>
<div class="wbc-log-filters">
<label for="wbc-log-level-filter"><?php esc_html_e( 'Filter by level:', 'woo-business-central' ); ?></label>
<select id="wbc-log-level-filter" onchange="location = this.value;">
<option value="<?php echo esc_url( remove_query_arg( 'log_level' ) ); ?>"><?php esc_html_e( 'All', 'woo-business-central' ); ?></option>
<?php foreach ( array( 'DEBUG', 'INFO', 'WARNING', 'ERROR' ) as $level ) : ?>
<option value="<?php echo esc_url( add_query_arg( 'log_level', $level ) ); ?>" <?php selected( $log_level, $level ); ?>>
<?php echo esc_html( $level ); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<table class="wp-list-table widefat fixed striped wbc-logs-table">
<thead>
<tr>
<th class="column-timestamp"><?php esc_html_e( 'Timestamp', 'woo-business-central' ); ?></th>
<th class="column-level"><?php esc_html_e( 'Level', 'woo-business-central' ); ?></th>
<th class="column-context"><?php esc_html_e( 'Context', 'woo-business-central' ); ?></th>
<th class="column-message"><?php esc_html_e( 'Message', 'woo-business-central' ); ?></th>
</tr>
</thead>
<tbody>
<?php if ( empty( $logs ) ) : ?>
<tr>
<td colspan="4"><?php esc_html_e( 'No logs found.', 'woo-business-central' ); ?></td>
</tr>
<?php else : ?>
<?php foreach ( $logs as $log ) : ?>
<tr class="wbc-log-level-<?php echo esc_attr( strtolower( $log['level'] ) ); ?>">
<td class="column-timestamp">
<?php echo esc_html( wp_date( 'Y-m-d H:i:s', strtotime( $log['timestamp'] ) ) ); ?>
</td>
<td class="column-level">
<span class="wbc-log-badge wbc-log-badge-<?php echo esc_attr( strtolower( $log['level'] ) ); ?>">
<?php echo esc_html( $log['level'] ); ?>
</span>
</td>
<td class="column-context"><?php echo esc_html( $log['context'] ); ?></td>
<td class="column-message">
<?php echo esc_html( $log['message'] ); ?>
<?php if ( ! empty( $log['data'] ) ) : ?>
<button type="button" class="button-link wbc-toggle-data"
data-show-text="<?php esc_attr_e( 'Show data', 'woo-business-central' ); ?>"
data-hide-text="<?php esc_attr_e( 'Hide data', 'woo-business-central' ); ?>">
<?php esc_html_e( 'Show data', 'woo-business-central' ); ?>
</button>
<pre class="wbc-log-data" style="display: none;"><?php echo esc_html( $log['data'] ); ?></pre>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<?php if ( $total_pages > 1 ) : ?>
<div class="tablenav bottom">
<div class="tablenav-pages">
<span class="displaying-num">
<?php printf( esc_html__( '%d items', 'woo-business-central' ), $total ); ?>
</span>
<span class="pagination-links">
<?php if ( $page_num > 1 ) : ?>
<a class="prev-page button" href="<?php echo esc_url( add_query_arg( 'log_page', $page_num - 1 ) ); ?>">
&lsaquo;
</a>
<?php endif; ?>
<span class="paging-input">
<?php echo esc_html( $page_num ); ?> / <?php echo esc_html( $total_pages ); ?>
</span>
<?php if ( $page_num < $total_pages ) : ?>
<a class="next-page button" href="<?php echo esc_url( add_query_arg( 'log_page', $page_num + 1 ) ); ?>">
&rsaquo;
</a>
<?php endif; ?>
</span>
</div>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</div>